根据我调用 reader 方法的方式,C# XmlReader 读取 XML 错误且不同

C# XmlReader reads XML wrong and different based on how I invoke the reader's methods

所以我目前对 C# XmlReader 工作原理的理解是,当我将其包装在以下构造中时,它采用给定的 XML 文件并逐个节点地读取它:

using System.Xml;
using System;
using System.Diagnostics;
...
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreComments = true;
settings.IgnoreWhitespace = true;
settings.IgnoreProcessingInstructions = true;
using (XmlReader reader = XmlReader.Create(path, settings))
{
    while (reader.Read())
    {
        // All reader methods I call here will reference the current node
        // until I move the pointer to some further node by calling methods like
        // reader.Read(), reader.MoveToContent(), reader.MoveToElement() etc
    }
}

为什么以下两个片段(在上述构造中)会产生两个截然不同的结果,即使它们都调用相同的方法?

I used this example file for testing.

Debug.WriteLine(new string(' ', reader.Depth * 2) + "<" + reader.NodeType.ToString() + "|" + reader.Name + ">" + reader.ReadString() + "</>");

(片段 1)
对比
(片段 2)

string xmlcontent = reader.ReadString();
string xmlname = reader.Name.ToString();
string xmltype = reader.NodeType.ToString();
int xmldepth = reader.Depth;
Debug.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");

代码段 1 的输出:

<XmlDeclaration|xml></>
<Element|rss></>
    <Element|head></>
        <Text|>Test Xml File</>
      <Element|description>This will test my xml reader</>
    <EndElement|head></>
    <Element|body></>
        <Element|g:id>1QBX23</>
        <Element|g:title>Example Title</>
        <Element|g:description>Example Description</>
      <EndElement|item></>
      <Element|item></>
          <Text|>2QXB32</>
        <Element|g:title>Example Title</>
        <Element|g:description>Example Description</>
      <EndElement|item></>
    <EndElement|body></>
  <EndElement|xml></>
<EndElement|rss></>

是的,它的格式与我输出中的一样 window。可以看出,它跳过了某些元素并为其他一些元素输出了错误的深度。因此,NodeTypes 是正确的,与代码片段 2 不同,它输出:

<XmlDeclaration|xml></>
  <Element|xml></>
      <Element|title></>
      <EndElement|title>Test Xml File</>
      <EndElement|description>This will test my xml reader</>
    <EndElement|head></>
      <Element|item></>
        <EndElement|g:id>1QBX23</>
        <EndElement|g:title>Example Title</>
        <EndElement|g:description>Example Description</>
      <EndElement|item></>
        <Element|g:id></>
        <EndElement|g:id>2QXB32</>
        <EndElement|g:title>Example Title</>
        <EndElement|g:description>Example Description</>
      <EndElement|item></>
    <EndElement|body></>
  <EndElement|xml></>
<EndElement|rss></>

再一次,深度搞砸了,但它不像代码片段 1 那样严重。它还跳过了一些元素并分配了错误的节点类型。

为什么不能输出预期的结果?为什么这两个片段会产生两个完全不同的输出,具有不同的深度、NodeTypes 和跳过的节点?[​​=35=] 我很感激这方面的任何帮助。我搜索了很多关于此的任何答案,但似乎我是唯一遇到这些问题的人。我在 Visual Studio 2017 年将 .NET Framework 4.6.2 与 Asp.net Web Forms 一起使用。

首先,您使用的方法XmlReader.ReadString() 已弃用:

XmlReader.ReadString Method

... reads the contents of an element or text node as a string. However, we recommend that you use the ReadElementContentAsString method instead, because it provides a more straightforward way to handle this operation.

但是,除了警告我们关闭该方法之外,该文档并未准确说明它的实际作用。要确定这一点,我们需要转到 reference source:

public virtual  string  ReadString() {
    if (this.ReadState != ReadState.Interactive) {
        return string.Empty;
    }
    this.MoveToElement();
    if (this.NodeType == XmlNodeType.Element) {
        if (this.IsEmptyElement) {
            return string.Empty;
        }
        else if (!this.Read()) {
            throw new InvalidOperationException(Res.GetString(Res.Xml_InvalidOperation));
        }
        if (this.NodeType == XmlNodeType.EndElement) {
            return string.Empty;
        }
    }
    string result = string.Empty;
    while (IsTextualNode(this.NodeType)) {
        result += this.Value;
        if (!this.Read()) {
            break;
        }
    }
    return result;
}

此方法执行以下操作:

  1. 若当前节点为空元素节点,return空字符串

  2. 如果当前节点是非空元素,前进reader

    如果当前节点是元素的末尾,return 一个空字符串。

  3. 当当前节点是文本节点时,将文本添加到字符串中并推进reader。只要当前节点不是文本节点,return 累积的字符串。

由此可见,这个方法是为了推进reader而设计的。我们还可以看到,给定混合内容 XML,如 <head>text <b>BOLD</b> more text</head>ReadString() 将仅部分读取 <head> 元素,而 reader 位于 <b>。这种奇怪的现象可能是 Microsoft 弃用该方法的原因。

我们还可以了解为什么您的两个代码段的功能不同。首先,在调用 ReadString() 并推进 reader 之前,您会得到 reader.Depthreader.NodeType。在推进 reader.

之后,您将获得这些属性。

由于您的目的是遍历节点并获取每个节点的值,而不是 ReadString()ReadElementContentAsString(),您应该只使用 XmlReader.Value:

gets the text value of the current node.

因此,您更正后的代码应如下所示:

 string xmlcontent = reader.Value;
 string xmlname = reader.Name.ToString();
 string xmltype = reader.NodeType.ToString();
 int xmldepth = reader.Depth;
 Console.WriteLine(new string(' ', xmldepth * 2) + "<" + xmltype + "|" + xmlname + ">" + xmlcontent + "</>");

XmlReader 很难使用。您始终需要检查文档以确定给定方法将 reader 定位的确切位置。例如,XmlReader.ReadElementContentAsString() moves the reader past the end of the element, whereas XmlReader.ReadSubtree() 将 reader 移动到 元素的末尾。但作为一般规则,任何名为 Read 的方法都会推进 reader,因此在外部 while (reader.Read()) 循环中使用 Read 方法时需要小心。

演示 fiddle here.