XmlDocument:空白处理和规范化

时间:2015-09-24 07:33:56

标签: c# .net xmldocument system.xml

我有一堆与XmlDocument的空格处理有关的问题。请参阅以下示例中的编号注释。

  1. 在混合模式下,所有空白都不应该显着吗?为什么a标记之间的空格不重要?

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

  3. 我是唯一可以手动选择的吗?

  4. 这是我的测试用例:

    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
    

2 个答案:

答案 0 :(得分:3)

Microsoft文档不清楚,至少部分不准确。虽然XmlSignificantWhitespace Class的Microsoft文档说“混合内容节点中的标记之间的空格” “重要的空白”,但实际的XmlDocument加载和解析行为与此不一致。相关文档为PreserveWhitespaceWhite Space and Significant White Space Handling when Loading the DOM,但这些文档未提供足够的具体细节。

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

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

简而言之,将空格直接解析为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

答案 1 :(得分:1)

编写自己的XmlNodeReader似乎有效,尽管它不是&#34;最干净的&#34;溶液

考虑当前的实施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作为内容,您可以在NodeType时返回XmlNodeType.SignificantWhitespace

这是我自己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;
    }
}