3

I have an XML-Stream that contains the following XML content:

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

I can't find a way to get an XmlReader to read to the Key attribute, so that reader.NodeType is XmlNodeType.Attribute and reader.LocalName is "Key".

This is how I initialize my XmlReader:

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

This reader then is passed through several method levels, until it gets to my parser function.

Here is all the alternative code I tried to get the reader to read that element. Is stripped the control structures from the code, so you can only see the functions that are actually called.

First attempt, move to attributes by MoveToFirstAttribute() call:

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
}

So obviously, MoveToFirstAttribute does not move the reader. As a side effect subLevelReader that is usually used to parse inner XmlElement nodes now grabs the whole WebError node, and when subLevelReader is disposed, the whole WebError node is stepped over.

Second attempt, call MoveToContent() and search for attributes:

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"
  ...
}

Obviously I progressed too far already when I called MoveToContent() because it moved to the end of the WebError starting tag.

Third attempt, read attributes before calling 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"
  ...
}

This does not work either. So, how do I get to the WebError@Key node?

3
  • 1
    I'm not sure I understand but this may help you: stackoverflow.com/a/11105852/4499267
    – Phate01
    Commented Jun 16, 2016 at 15:33
  • Actually yes. Strange thing this post doesn't turn up when you search for "xmlreader c# attribute", but is the first result if you search for "xmlreader attribute", although the first tag is "c#"...
    – LWChris
    Commented Jun 16, 2016 at 16:03
  • 1
    To specify tags, put brackets around them (ie. "xmlreader [c#] attribute")
    – Pluto
    Commented Jun 16, 2016 at 16:45

2 Answers 2

9

This question (which unfortunately does not turn up in the search results for "xmlreader c# attribute") contains an answer which made me understand the problem: Read() does not position the reader at an attribute. You first move to an element, then move to its content, then move to its attributes. Only this order works.

Turns out all my approaches work if you call MoveToContent() before MoveToAttribute("Key"), MoveToNextAttribute() or MoveToFirstAttribute(), but not Read() yet because that reads to the Message node.

So this is the actual code:

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)
      }
    }
  }
}
2
  • So unlike the sax parser in MSXML2 the attribute node never gets hit on a std read. That somehow seems wrong to me. Eg why does the XmlNodeType.Attribute check even exist? I mean if the only way you can ever access attributes is by doing MoveToNextAttribute it would seem to be redundant. This (to me at least) little known feature also explains why the page learn.microsoft.com/en-us/dotnet/api/… did not have case XmlNodeType.Attribute. It would have been nice if MS had been a tad more explicit about this feature though!
    – walbury
    Commented Apr 16, 2022 at 0:18
  • I suppose you use Read to quickly skip over the XML nodes, and once you found the node you actually want to process, you use MoveToContent to make the reader analyze the current node, and from there use the attribute-related functions to actually navigate around. I agree that this "two level" approach is not very intuitive.
    – LWChris
    Commented Jul 13, 2023 at 12:24
1

The simplest way is to use ReadToFollowing and GetAttribute methods.

reader.ReadToFollowing("WebError");
string keyAttr = reader.GetAttribute("Key");

Not the answer you're looking for? Browse other questions tagged or ask your own question.