6

Please note this question is specific to XmlReader and not whether to use XDocument or XmlReader.

I have an XML fragment as:

private string GetXmlFragment()
{
    return @"<bookstore>
          <book genre='novel' ISBN='10-861003-324'>
            <title>The Handmaid's Tale</title>
            <price>19.95</price>
          </book>
          <book genre='novel' ISBN='1-861001-57-5'>
            <title>Pride And Prejudice</title>
            <price>24.95</price>
          </book>
        </bookstore>";
}

I also have an extension method as:

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    reader.MoveToElement();

    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element 
            && reader.Name.Equals(elementName, StringComparison.InvariantCulture))
        {
            yield return XNode.ReadFrom(reader) as XElement;
        }
    }
}

I then try to get the two book elements by doing:

var xmlReaderSettings = new XmlReaderSettings
{
    CheckCharacters = false,
    ConformanceLevel = ConformanceLevel.Fragment,
    IgnoreComments = true,
    IgnoreWhitespace = true,
    IgnoreProcessingInstructions = true
};

using (var stringReader = new StringReader(this.GetXmlFragment()))
using (var xmlReader = XmlReader.Create(stringReader, xmlReaderSettings))
{
    xmlReader.GetElement("book").Count().ShouldBe(2);
}

However I only get the first element, debugging shows that as soon as I get the first element the reader jumps to the title of the second book element.

The solution is inspired from HERE

Any help is much appreciated.

babayi
  • 85
  • 4

4 Answers4

5

The problem is that, if there is no intervening whitespace, the call to XNode.ReadFrom() will leave the XML reader positioned right at the next element. The while condition then immediately consumes this element before we can check it. The fix is to not call XmlReader.Read() immediately afterwards, but to continue checking for nodes (as the read has been done implicitly):

while (reader.Read()) {
    while (reader.NodeType == XmlNodeType.Element 
           && reader.Name.Equals(elementName, StringComparison.InvariantCulture)) {
        yield return XNode.ReadFrom(reader) as XElement;
    }
}

(In case it's not clear, the if in the loop has been changed to a while.)

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85
2
public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    while (!reader.EOF)
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "book")
            yield return XNode.ReadFrom(reader) as XElement;
        else
            reader.Read();
}
Denis Fedak
  • 161
  • 12
0

The code is skipping every other book tag because the book tags immediately follow each other . The read method leave the reader at the next book tag and then the read method moves before reading the 2nd book tag skipping the element. Try the code below which I developed and always works.

        public static IEnumerable<XElement> GetElement(XmlReader reader, string elementName)
        {
            List<XElement> books = new List<XElement>();


            while (!reader.EOF)
            {
                if(reader.Name != "book")
                {
                    reader.ReadToFollowing("book");
                }
                if(!reader.EOF)
                {
                    books.Add((XElement)XElement.ReadFrom(reader));
                }
            }
            return books;
        }
jdweng
  • 33,250
  • 2
  • 15
  • 20
0

Like others have said, XNode.ReadFrom is advancing your reader to the next book open tag (if there is no whitespace between them) then reader.Read will advance to the inner text of that tag.

See here for more information:

https://stackoverflow.com/a/26788230/3850405

Fix for your extension method:

public static IEnumerable<XElement> GetElement(this XmlReader reader, string elementName)
{
    reader.MoveToElement();

    reader.Read();
    while (!reader.EOF)
    {
        if (reader.NodeType == XmlNodeType.Element 
            && reader.Name.Equals(elementName, StringComparison.InvariantCulture))
        {
            yield return XNode.ReadFrom(reader) as XElement;
        }
        else
        {
            reader.Read();
        }
    }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418