本文介绍如何在序列化 XML 树时控制空白。
一种常见情形是先读取已经缩进的 XML,创建内存中的 XML 树,不保留任何空格文本节点,然后对 XML 执行一些操作,最后以缩进方式保存 XML。 使用格式序列化 XML 时,只会保留 XML 树中的显著空白。 这是 LINQ to XML 的默认行为。
另一个常见方案是读取和修改经过有意缩进的 XML 文档。 您可能不想以任何方式更改这种缩进。 若要在 LINQ to XML 中执行此作,请在加载或分析 XML 时保留空白,并在序列化 XML 时禁用格式设置。
序列化 XML 树的方法的空白行为
类中的XElementXDocument以下方法序列化 XML 树。 可以将 XML 树序列化为文件、TextReader 或 XmlReader。 该方法 ToString 序列化为字符串。
如果 SaveOptions 不是该方法的参数,那么该方法将对序列化的 XML 进行格式化(缩进)。 在这种情况下,将丢弃 XML 树中的所有微不足道的空格。
如果该方法确实采用 SaveOptions 参数,则可以指定该方法不设置序列化 XML 的格式(缩进)。 在这种情况下,将保留 XML 树中的所有空白。
使用回车实体往返 XML
本文中讨论的空白保留与 XML 往返不同。 当 XML 包含回车实体 (
) 时,LINQ to XML 的标准序列化可能无法以允许完美往返的方式保留它们。
考虑以下包含回车实体的 XML 示例:
<x xml:space="preserve">a
b
c
</x>
使用此 XML XDocument.Parse()分析时,根元素的值将变为 "a\r\nb\nc\r"。 但是,如果使用 LINQ to XML 方法重新序列化它,回车不会被实体化:
string xmlWithCR = """
<x xml:space="preserve">a
b
c
</x>
""";
XDocument doc = XDocument.Parse(xmlWithCR);
Console.WriteLine($"Original parsed value: {string.Join("", doc.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
// Output: a\r\nb\nc\r
string reserialized = doc.ToString(SaveOptions.DisableFormatting);
Console.WriteLine($"Reserialized XML: {reserialized}");
// Output: <x xml:space="preserve">a
// b
// c</x>
XDocument reparsed = XDocument.Parse(reserialized);
Console.WriteLine($"Reparsed value: {string.Join("", reparsed.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
// Output: a\nb\nc\n
值是不同的:原始值是 "a\r\nb\nc\r" ,但在往返处理后,它变成了 "a\nb\nc\n"。
解决方案:将 XmlWriter 与 NewLineHandling.Entitize 配合使用
要实现保留回车实体的真正 XML 往返,请使用 XmlWriter 并将 NewLineHandling 设置为 Entitize:
string xmlWithCR = """
<x xml:space="preserve">a
b
c
</x>
""";
XDocument doc = XDocument.Parse(xmlWithCR);
// Create XmlWriter settings with NewLineHandling.Entitize
XmlWriterSettings settings = new XmlWriterSettings
{
NewLineHandling = NewLineHandling.Entitize,
OmitXmlDeclaration = true
};
// Serialize using XmlWriter
using StringWriter stringWriter = new StringWriter();
using (XmlWriter writer = XmlWriter.Create(stringWriter, settings))
{
doc.WriteTo(writer);
}
string roundtrippedXml = stringWriter.ToString();
Console.WriteLine($"Roundtripped XML: {roundtrippedXml}");
// Output: <x xml:space="preserve">a
// b
// c
</x>
// Verify roundtripping preserves the original value
XDocument roundtrippedDoc = XDocument.Parse(roundtrippedXml);
bool valuesMatch = doc.Root!.Value == roundtrippedDoc.Root!.Value;
Console.WriteLine($"Values match after roundtripping: {valuesMatch}");
当需要为 XML 往返保留回车实体时,请使用 XmlWriter 和适当的 XmlWriterSettings,而不是 LINQ to XML 的内置序列化方法。
有关XmlWriter及其设置的详细信息,请参阅System.Xml.XmlWriter。