English 中文(简体)
C# XmlSerializer——输出多种Xml碎片控制新线
原标题:C# XmlSerializer - output multiple xml fragments controlling new lines

I want to be able to write fragments of indented XML with no namespaces, no XML preambles, and for line endings, using XmlSerializer for each fragment and using a single XmlWriter instance for all the fragments. How can I do that?

XmlSerializer.Serialize() produces indented output when serializing to a generic output Stream, but it uses " " for line endings and I can t find how to configure that.

我可以编成一个<代码>XmlWriter,该编码可以详细配置,但只有在你完成单一文件而不是多个文件碎片时,才会进行编辑,因为<代码>XmlSerializer将放弃一个例外情况,即<编码>Conformance Level.Fragment <>/code>:

不能要求具有一致性的作者撰写书面文件。 分裂

并且,如果作为工作重心一叫XmlWriter.Write Whitespace(");,登革站就残疾。 具体地说, 如果我创建<代码>XmlWriter,就如:

XmlWriter xw = XmlWriter.Create(System.Console.Out, new XmlWriterSettings()
{
    ConformanceLevel = ConformanceLevel.Fragment,
    NamespaceHandling = NamespaceHandling.Default,
    NewLineChars = "
",
    Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), // supress BOM
    Indent = true,
    NewLineHandling = NewLineHandling.Replace,
    OmitXmlDeclaration = true,
    WriteEndDocumentOnClose = false,
    CloseOutput = false,
    CheckCharacters = false,
    NewLineOnAttributes = false,
});

MVE

https://dotnetfiddle.net/qGLIlL It does allow me to serialize multiple objects without creating a new XmlWriter for each one. But there s no indentation.

<flyingMonkey name="Koko"><limbs><limb name="leg" /><limb name="arm" /><limb name="tail" /><limb name="wing" /></limbs></flyingMonkey>

<flyingMonkey name="New Name"><limbs><limb name="leg" /><limb name="arm" /><limb name="tail" /><limb name="wing" /></limbs></flyingMonkey>
public class Program {
    public static void Main()
    {
        XmlWriter xw = XmlWriter.Create(System.Console.Out, new XmlWriterSettings()
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            NamespaceHandling = NamespaceHandling.Default,
            NewLineChars = "
",
            Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), // supress BOM
            Indent = true,
            NewLineHandling = NewLineHandling.Replace,
            OmitXmlDeclaration = true,
            WriteEndDocumentOnClose = false,
            CloseOutput = false,
            CheckCharacters = false,
            NewLineOnAttributes = false,
        });
        var noNamespace = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

        // without this line I get an error:
        //   "不能要求具有一致性的作者撰写书面文件。 分裂."
        xw.WriteWhitespace("");

        FlyingMonkey monkey = FlyingMonkey.Create();
        XmlSerializer ser = new XmlSerializer(typeof(FlyingMonkey), defaultNamespace: null);
        ser.Serialize(xw, monkey, noNamespace);
        xw.WriteWhitespace("

");
        monkey.name = "New Name";
        ser.Serialize(xw, monkey, noNamespace);
    }
}

[System.Xml.Serialization.XmlTypeAttribute(TypeName = "flyingMonkey", Namespace=null)]
public class FlyingMonkey
{
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string name;

    public Limb[] limbs;

    public static FlyingMonkey Create() =>
        new FlyingMonkey()
        {
            name = "Koko",
            limbs = new Limb[]
            {
                new Limb() { name = "leg" }, new Limb() { name = "arm" },
                new Limb() { name = "tail" }, new Limb() { name = "wing" },
            }
        };
}

[System.Xml.Serialization.XmlTypeAttribute(TypeName = "limb", Namespace=null)]
public class Limb
{
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public string name;
}

What kinda works:

XmlWriter xw = XmlWriter.Create(System.Console.Out, new XmlWriterSettings()
{
    ConformanceLevel = ConformanceLevel.Auto,
    NamespaceHandling = NamespaceHandling.Default,
    NewLineChars = "
",
    Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), // supress BOM
    Indent = true,
    NewLineHandling = NewLineHandling.Replace,
    OmitXmlDeclaration = true,
    WriteEndDocumentOnClose = false,
    CloseOutput = false,
    CheckCharacters = false,
    NewLineOnAttributes = false,
});
var noNamespace = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

// This is not needed anymore. If I invoke that, it will kill indentation for some reason.
// xw.WriteWhitespace("");

FlyingMonkey monkey = FlyingMonkey.Create();
XmlSerializer ser = new XmlSerializer(typeof(FlyingMonkey), defaultNamespace: null);
ser.Serialize(xw, monkey, noNamespace);
// xw.WriteWhitespace("

");
// monkey.name = "New Name";
// ser.Serialize(xw, monkey, noNamespace); // this second serialization throws InvalidOperationException

它印有右线,但打得 t,让你写另一个反对XmlWriter案。

<flyingMonkey name="Koko">
  <limbs>
    <limb name="leg" />
    <limb name="arm" />
    <limb name="tail" />
    <limb name="wing" />
  </limbs>
</flyingMonkey>

我想重复我对<代码>XmlWriter的单一例子,因为我需要撰写多达100k的内容,并为每增加一个<代码>XmlWriter。

最佳回答

你的基本问题是:<代码>。 XmlSerializer的目的是使XML文件——不按碎片序列编号。 如果你试图按<代码>将XML序列化 合规水平.Fragment ,将有一个例外。

不能要求具有一致性的作者撰写书面文件。 分裂

In this answer to .net XmlSerialize throws "不能要求具有一致性的作者撰写书面文件。 分裂", Wim Reymen identifies two workarounds:

  • 在第一次要求序列化之前使用<代码>XmlWriter.Write Whitespace(")。

    不幸的是,正如你所注意到的那样,这种令人不快的指责。 出现这种情况的原因是:XmlWriter>/code>,以及要求书写白空间触发器对here

  • 在第一次序列化之前使用<代码>XmlWriter.WriteComment(")

    虽然这并不令人沮丧,但当然是写出你不希望的话。

因此,你们的工作选择是什么?

Firstly, 请注意,您可就每个项目分别设立XmlWriter, 在你写到的“涉及大量间接费用”的评论中,我需要撰写多达100克的内容,因此希望再利用作者<>/em>,但我建议你说明,因为与替代品相比,这项工作非常简单。

假设你写成<条码>Stream,你可制定一种推广方法,如:

public static partial class XmlExtensions
{
    static Encoding Utf8EncodingNoBom { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
    
    public static void SerializeFragmentsToXml<T>(this IEnumerable<T> enumerable, Stream stream, XmlSerializer? serializer = null, XmlSerializerNamespaces? ns = null)
    {
        var newLine = "
";
        var newLineBytes = Utf8EncodingNoBom.GetBytes(newLine+newLine);
        
        var settings = new XmlWriterSettings()
        {
            NamespaceHandling = NamespaceHandling.Default,
            NewLineChars = newLine,
            Encoding = Utf8EncodingNoBom, // supress BOM
            Indent = true,
            NewLineHandling = NewLineHandling.Replace,
            OmitXmlDeclaration = true,
            WriteEndDocumentOnClose = false,
            CloseOutput = false, // Required to prevent the stream from being closed between items
            CheckCharacters = false,
            NewLineOnAttributes = false,
        };
        
        serializer ??= new XmlSerializer(typeof(T));
        ns ??= new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

        bool first = true;
        foreach (var item in enumerable)
        {
            if (!first)
                stream.Write(newLineBytes);
            using (var xmlWriter = XmlWriter.Create(stream, settings))
                serializer.Serialize(xmlWriter, item, ns);
            first = false;
        }
    }
}

And use it e.g. as follows:

var items = new [] { "Koko", "POCO", "Loco" }.Select(n => FlyingMonkey.Create(n));

using var stream = new MemoryStream(); // Replace with some FileStream when serializing to disk

var serializer = new XmlSerializer(typeof(FlyingMonkey), defaultNamespace: null);
items.SerializeFragmentsToXml(stream, serializer : serializer);

Demo fiddle #1 here

<>Arternatively, 如果您<>真切需要<>/strong>再使用XmlWriter,出于执行原因,您需要打电话XmlWriter.WriteComment(,以防止例外情况从XmlSerial上删除,并在此后通过一些<代码>Texter/code>。 在飞机上写完时,删除这些货物。

下面的推广方法似乎就是这样:

public static partial class XmlExtensions
{
    const string FirstCommentText = "first";
    const string FirstComment = $"<!--{FirstCommentText}-->";
    const string SubsequentCommentText = "subsequent";
    const string SubsequentComment = $"<!--{SubsequentCommentText}-->";
    
    static Encoding Utf8EncodingNoBom { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
    
    public static void SerializeFragmentsToXml<T>(this IEnumerable<T> enumerable, Stream stream, XmlSerializer? serializer = null, XmlSerializerNamespaces? ns = null)
    {
        string newLine = "
";
        
        var settings = new XmlWriterSettings()
        {
            ConformanceLevel = ConformanceLevel.Fragment,
            NamespaceHandling = NamespaceHandling.Default,
            NewLineChars = newLine,
            Encoding = Utf8EncodingNoBom, // supress BOM
            Indent = true,
            NewLineHandling = NewLineHandling.Replace,
            OmitXmlDeclaration = true,
            WriteEndDocumentOnClose = false,
            CloseOutput = false, 
            CheckCharacters = false,
            NewLineOnAttributes = false,
        };
        
        serializer ??= new XmlSerializer(typeof(T));
        ns ??= new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

        using var innerTextWriter = new StreamWriter(stream, encoding : Utf8EncodingNoBom, leaveOpen  : true) { NewLine = newLine };
        using var textWriter = new FakeCommentRemovingTextWriter(innerTextWriter, new(FirstComment, ""), new(SubsequentComment, newLine)) { NewLine = newLine };
        using var xmlWriter = XmlWriter.Create(textWriter, settings);

        bool first = true;
        foreach (var item in enumerable)
        {
            xmlWriter.WriteComment(first ? FirstCommentText : SubsequentCommentText);
            serializer.Serialize(xmlWriter, item, ns);
            // XmlWriter buffers its output, so Flush() is required  to ensure that the fake comments are not split across calls to Write().
            xmlWriter.Flush(); 
            first = false;
        }
    }
    
    private class FakeCommentRemovingTextWriter : TextWriterDecorator
    {
        readonly KeyValuePair<string, string> [] replacements;
        
        public FakeCommentRemovingTextWriter(TextWriter baseWriter, params KeyValuePair<string, string> [] replacements) : base(baseWriter, true) => this.replacements = replacements;
        
        public override void Write(ReadOnlySpan<char> buffer)
        {
            foreach (var replacement in replacements)
            {
                int index;
                if ((index = StartsWithIgnoringWhitespace(buffer, replacement.Key)) >= 0)
                {
                    if (index > 0)
                        base.Write(buffer.Slice(0, index));
                    buffer = buffer.Slice(index).Slice(replacement.Key.Length);
                    if (buffer.StartsWith(NewLine))
                        buffer = buffer.Slice(NewLine.Length);
                    if (!string.IsNullOrEmpty(replacement.Value))
                        base.Write(replacement.Value);
                }
            }
            base.Write(buffer);
        }
        
        static int StartsWithIgnoringWhitespace(ReadOnlySpan<char> buffer, ReadOnlySpan<char> value)
        {
            for (int index = 0; index < buffer.Length; index++)
            {
                if (buffer.Slice(index).StartsWith(value))
                    return index;
                if (!XmlConvert.IsWhitespaceChar(buffer[index]) || index >= buffer.Length - value.Length)
                    break;
            }
            return -1;
        }
    }
}

public class TextWriterDecorator : TextWriter
{
    // Override the same methods that are overridden in https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IO/StringWriter.cs.
    TextWriter? baseWriter; // null when disposed
    readonly bool disposeBase;
    readonly Encoding baseEncoding;

    public TextWriterDecorator(TextWriter baseWriter, bool disposeBase = true) => 
        (this.baseWriter, this.disposeBase, this.baseEncoding) = (baseWriter ?? throw new ArgumentNullException(nameof(baseWriter)), disposeBase, baseWriter.Encoding);

    protected TextWriter BaseWriter => baseWriter == null ? throw new ObjectDisposedException(GetType().Name) : baseWriter;
    public override Encoding Encoding => baseEncoding;
    public override IFormatProvider FormatProvider => baseWriter?.FormatProvider ?? base.FormatProvider;
    [AllowNull] public override string NewLine 
    { 
        get => baseWriter?.NewLine ?? base.NewLine; 
        set
        {   
            if (baseWriter != null)
                baseWriter.NewLine = value;
            base.NewLine = value;
        }
    }

    public override void Flush() => BaseWriter.Flush();
    public sealed override void Close() => Dispose(true);
    public override void Write(char value) => BaseWriter.Write(value);
    public sealed override void Write(char[] buffer, int index, int count) => this.Write(buffer.AsSpan(index, count));
    public override void Write(ReadOnlySpan<char> buffer) => BaseWriter.Write(buffer);
    public sealed override void Write(string? value) => Write(value.AsSpan());

    public override Task WriteAsync(char value) => BaseWriter.WriteAsync(value);
    public sealed override Task WriteAsync(string? value) => WriteAsync(value.AsMemory());
    public sealed override Task WriteAsync(char[] buffer, int index, int count) => WriteAsync(buffer.AsMemory(index, count));
    public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => BaseWriter.WriteAsync(buffer, cancellationToken);
    //public virtual Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) - no need to override

    public override Task WriteLineAsync(char value) => BaseWriter.WriteLineAsync(value);
    public sealed override Task WriteLineAsync(string? value) => WriteLineAsync(value.AsMemory());
    public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) => BaseWriter.WriteLineAsync(value, cancellationToken);
    public sealed override Task WriteLineAsync(char[] buffer, int index, int count) => WriteLineAsync(buffer.AsMemory(index, count));
    public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => BaseWriter.WriteLineAsync(buffer, cancellationToken);
    
    public override Task FlushAsync() => BaseWriter.FlushAsync();
    public override Task FlushAsync(CancellationToken cancellationToken) => BaseWriter.FlushAsync(cancellationToken);

    protected override void Dispose(bool disposing)
    {
        try
        {
            if (disposing)
            {
                if (Interlocked.Exchange(ref this.baseWriter, null) is {} writer)
                    if (disposeBase)
                        writer.Dispose();
                    else
                        writer.Flush();
            }
        }
        finally
        {
            base.Dispose(disposing);
        }
    }

    public override async ValueTask DisposeAsync()
    {
        try
        {
            if (Interlocked.Exchange(ref this.baseWriter, null) is {} writer)
                if (disposeBase)
                    await writer.DisposeAsync().ConfigureAwait(false);
                else
                    await writer.FlushAsync().ConfigureAwait(false);
        }
        finally
        {
            await base.DisposeAsync().ConfigureAwait(false);
        }
    }
    
    public override string ToString() => string.Format("{0}: {1}", GetType().Name, baseWriter?.ToString() ?? "disposed");
}

但坦率地说,我怀疑它值得麻烦。 Demo fiddle #2 here

无论是哪种做法,产出都看上去。

<flyingMonkey name="Koko">
  <limbs>
    <limb name="leg" />
    <limb name="arm" />
    <limb name="tail" />
    <limb name="wing" />
  </limbs>
</flyingMonkey>

<flyingMonkey name="POCO">
  <limbs>
    <limb name="leg" />
    <limb name="arm" />
    <limb name="tail" />
    <limb name="wing" />
  </limbs>
</flyingMonkey>

<flyingMonkey name="Loco">
  <limbs>
    <limb name="leg" />
    <limb name="arm" />
    <limb name="tail" />
    <limb name="wing" />
  </limbs>
</flyingMonkey>
问题回答

暂无回答




相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...