XmlDocument:空白处理和规范化

XmlDocument: whitespace handling and normalization

我有一堆关于用 XmlDocument 处理白色 space 的问题。请参阅下面示例中的编号注释。

  1. 全白space在混合模式下不应该很重要吗?为什么 a 标签之间的 space 不重要?

  2. 虽然我知道实际的白色space元素仍然是一个XmlWhitespace,但我如何将这些space标准化为XmlSignificantWhitespace节点? Normalize() 无效。

  3. 我唯一的选择是手动完成吗?

这是我的测试用例:

private static void Main()
{
    // 1. Shouldn't all whitespace be significant in mixed mode? Why the space between the a tags is not significant?
    var doc = new XmlDocument
    {
        InnerXml = "<root>test1 <a>test2</a> <a>test3</a></root>",
    };
    PrintDoc(doc);

    // 2.a. While I understand that the actual whitespace element is still XmlWhitespace, how do I normalize these spaces into XmlSignificantWhitespaces?
    doc.DocumentElement.RemoveAll();
    doc.DocumentElement.SetAttribute("xml:space", "preserve");
    var fragment = doc.CreateDocumentFragment();
    fragment.InnerXml = "test1 <a>test2</a> <a>test3</a>";
    doc.DocumentElement.PrependChild(fragment);
    PrintDoc(doc);

    // 2.b. Normalize doesn't work
    doc.Normalize();
    PrintDoc(doc);

    // 3.a. Manual normalization does work, is there a better way?
    doc.DocumentElement.RemoveAllAttributes();
    var whitespaces = doc.DocumentElement.ChildNodes.Cast<XmlNode>()
        .OfType<XmlWhitespace>()
        .ToList();
    foreach (var whitespace in whitespaces)
    {
        var significant = doc.CreateSignificantWhitespace(whitespace.Value);
        doc.DocumentElement.ReplaceChild(significant, whitespace);
    }
    PrintDoc(doc);

    // 3.b. Reading from string also works
    doc.InnerXml = "<root xml:space=\"preserve\">test1 <a>test2</a> <a>test3</a></root>";
    PrintDoc(doc);
}

private static void PrintDoc(XmlDocument doc)
{
    var nodes = doc.DocumentElement.ChildNodes.Cast<XmlNode>().ToList();
    var whitespace = nodes.OfType<XmlWhitespace>().Count();
    var significantWhitespace = nodes.OfType<XmlSignificantWhitespace>().Count();

    Console.WriteLine($"Xml: {doc.InnerXml}\nwhitespace: {whitespace}\nsignificant whitespace: {significantWhitespace}\n");
}

输出如下:

Xml: <root>test1 <a>test2</a><a>test3</a></root>
whitespace: 0
significant whitespace: 0

Xml: <root xml:space="preserve">test1 <a>test2</a> <a>test3</a></root>
whitespace: 1
significant whitespace: 0

Xml: <root xml:space="preserve">test1 <a>test2</a> <a>test3</a></root>
whitespace: 1
significant whitespace: 0

Xml: <root>test1 <a>test2</a> <a>test3</a></root>
whitespace: 0
significant whitespace: 1

Xml: <root xml:space="preserve">test1 <a>test2</a> <a>test3</a></root>
whitespace: 0
significant whitespace: 1

Microsoft 文档不清楚,至少部分不准确。虽然 Microsoft 文档为 XmlSignificantWhitespace Class says that "white space between markup in a mixed content node" is "significant whitespace," the actual XmlDocument loading and parsing behavior is not consistent with that. Related documentation is PreserveWhitespace and White Space and Significant White Space Handling when Loading the DOM,但是这些并没有提供足够具体的细节。

根据经验,正如您用测试用例和我自己的测试所证明的那样,行为如下:

  • 当加载 XmlDocument.PreserveWhitespace = true 并且在 xml:space="preserve" 范围内时,空格被保留。但是,对于前者,它保存在 Whitespace 个节点中,而不是 SignificantWhitespace 个节点中。
  • 如果XmlDocument.PreserveWhitespace = false,则混合内容节点中元素之间的空白被丢弃与[=26相反 =] 文档。
  • 空白 变成 xml:space="preserve" 范围内的 SignfiicantWhitespace 个节点。在这种情况下,无论 XmlDocument.PreserveWhitespace 设置如何,它总是 保留为 SignificantWhitespace

简而言之,将空白直接解析为 SignificantWhitespace 节点的唯一方法是在 xml:space="preserve" 范围内。一种可能对您有用的方法是将 XML 内容包装在具有 xml:space="preserve" 范围的新外部元素中。我不知道为什么你的 CreateDocumentFragment() 测试不起作用,但这里有一些代码确实有效:

// 4. Loading the XML within an xml:space="preserve" element works
doc.InnerXml = "<root xml:space=\"preserve\"></root>";
doc.FirstChild.InnerXml = "test1 <a>test2</a> <a>test3</a>";
PrintDoc(doc);

这导致:

Xml: <root xml:space="preserve">test1 <a>test2</a> <a>test3</a></root>
whitespace: 0
significant whitespace: 1

自己编写 XmlNodeReader 似乎可行,尽管它不是 "cleanest" 解决方案。

考虑当前的实现 here:

public virtual XmlNodeType MoveToContent() {
    do {
        switch (this.NodeType) {
            case XmlNodeType.Attribute:
                MoveToElement();
                goto case XmlNodeType.Element;
            case XmlNodeType.Element:
            case XmlNodeType.EndElement:
            case XmlNodeType.CDATA:
            case XmlNodeType.Text:
            case XmlNodeType.EntityReference:
            case XmlNodeType.EndEntity:
                return this.NodeType;
        }
    } while (Read());
    return this.NodeType;
}

要将标记 SignificantWhitespace 作为内容,您可以 return 当 XmlNodeType.SignificantWhitespace.

NodeType

这是我自己的完整实现 WhitespaceXmlNodeReader:

internal class WhitespaceXmlNodeReader : XmlNodeReader
{
    public WhitespaceXmlNodeReader(XmlNode node)
        : base(node)
    {
    }

    public override XmlNodeType MoveToContent()
    {
        do
        {
            switch (NodeType)
            {
                case XmlNodeType.Attribute:
                    MoveToElement();
                    goto case XmlNodeType.Element;
                case XmlNodeType.Element:
                case XmlNodeType.EndElement:
                case XmlNodeType.CDATA:
                case XmlNodeType.Text:
                case XmlNodeType.EntityReference:
                case XmlNodeType.EndEntity:
                // This was added:
                case XmlNodeType.SignificantWhitespace:
                    return NodeType;
            }
        } while (Read());
        return NodeType;
    }
}