如何在C#中使XmlReader读取属性?

3

我有一个XML流,其中包含以下XML内容:

<WebError Key="A">
  <Message>B</Message>
  <Parameters>
    <Parameter name="C">D</Parameter>
  </Parameters>
</WebError>

我找不到一种方法使XmlReader读取Key属性,以便reader.NodeTypeXmlNodeType.Attribute,而reader.LocalName"Key"。以下是如何初始化XmlReader

XmlReader.Create(stream, new XmlReaderSettings { CloseInput = true, IgnoreWhitespace = true });

这个读取器经过多个方法级别的处理,最终到达我的解析器函数。

这里是我尝试获取读取器读取该元素的所有替代代码。为了让您只看到实际调用的函数,我从代码中剥离了控制结构。

第一次尝试,通过调用MoveToFirstAttribute()移动到属性:

reader.Read(); // true
reader.IsStartElement("WebError"); // true
using (var nodeReader = reader.ReadSubtree()) {
  nodeReader.HasAttributes; // true
  nodeReader.MoveToFirstAttribute(); // false
  nodeReader.Read(); // true
  nodeReader.NodeType; // XmlNodeType.Element
  nodeReader.LocalName; // "WebError"
  using (var subLevelReader = nodeReader.ReadSubtree()) {
  }
  nodeReader.Read(); // false
}

显然,MoveToFirstAttribute 并不移动读取器。作为副作用,通常用于解析内部 XmlElement 节点的 subLevelReader 现在抓取整个 WebError 节点,并在 subLevelReader 被处理时,整个 WebError 节点被跳过。
第二次尝试,请调用 MoveToContent() 并搜索属性:
reader.Read(); // true
reader.IsStartElement("WebError"); // true
using (var nodeReader = reader.ReadSubtree()) {
  nodeReader.MoveToContent(); // XmlNodeType.Element
  nodeReader.LocalName; // "WebError"
  nodeReader.Read(); // true
  nodeReader.NodeType; // XmlNodeType.Element
  nodeReader.LocalName; // "Message"
  ...
}

显然,当我调用MoveToContent()时,我已经进展太远了,因为它移动到了WebError开始标签的末尾。

第三次尝试,在调用MoveToContent()之前读取属性:

reader.Read(); // true
reader.IsStartElement("WebError"); // true
using (var nodeReader = reader.ReadSubtree()) {
  nodeReader.MoveToAttribute("Key"); // false
  nodeReader.MoveToContent(); // XmlNodeType.Element
  nodeReader.LocalName; // "WebError"
  nodeReader.Read(); // true
  nodeReader.NodeType; // XmlNodeType.Element
  nodeReader.LocalName; // "Message"
  ...
}

这也不起作用。那么,我该如何进入WebError@Key节点?


1
我不确定我理解了,但这可能会对你有所帮助:https://dev59.com/Xmgu5IYBdhLWcg3wv5im#11105852 - Phate01
确实是这样。奇怪的是,当你搜索“xmlreader c# attribute”时,这篇文章并没有出现,但如果你搜索“xmlreader attribute”,它却是第一个结果,尽管第一个标签是“c#”... - LWChris
1
要指定标签,请在它们周围加上方括号(例如:"xmlreader [c#] attribute")。 - Pluto
2个回答

9
这个问题(很不幸在搜索“xmlreader c# attribute”时没有找到)包含一个答案让我理解了这个问题: Read()不能将读者定位在属性上。你首先要移动到元素,然后移动到它的内容,最后移动到它的属性。只有这样才能正常工作。

结果证明,如果在调用MoveToAttribute("Key")MoveToNextAttribute()MoveToFirstAttribute()之前调用MoveToContent(),则我所有的方法都可以正常工作,但是还不能使用Read(),因为它会读取到Message节点。

所以这是实际的代码:

while (reader.Read()) {
  if (!reader.IsStartElement("WebError")) { continue; }

  // We found the WebError node
  using (var nodeReader = reader.ReadSubtree()) {
    nodeReader.MoveToContent();

    // Read the attributes
    while (nodeReader.MoveToNextAttribute()) {
      var nodeName = nodeReader.LocalName;
      if (nodeName == "Key") {
        m_Key = nodeReader.Value; // "A"
        break;
      }
    }

    // Read the XML sub nodes
    while (nodeReader.Read()) {
      if (nodeReader.NodeType != XmlNodeType.Element) { continue; }

      using (var subLevelReader = nodeReader.ReadSubtree()) {
        // Parse sub levels of XML (Message, Parameters)
      }
    }
  }
}

与MSXML2中的sax解析器不同,属性节点在标准读取时永远不会被命中。这对我来说似乎是错误的。例如,为什么XmlNodeType.Attribute检查甚至存在?我的意思是,如果您唯一能够访问属性的方式是通过MoveToNextAttribute,那么它似乎是多余的。这个(至少对我来说)鲜为人知的特性也解释了为什么页面https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlnodereader.read?view=net-6.0没有包括XmlNodeType.Attribute。不过,如果微软公司能够更明确地说明这个特性就好了! - walbury
我猜你使用Read来快速跳过XML节点,一旦找到你真正想处理的节点,你使用MoveToContent让读取器分析当前节点,然后使用与属性相关的函数来实际导航。我同意这种“两级”方法并不直观。 - LWChris

1
最简单的方法是使用ReadToFollowingGetAttribute方法。
reader.ReadToFollowing("WebError");
string keyAttr = reader.GetAttribute("Key");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接