Search code examples
c#xmlxmlreader

XMLReader.ReadToNextSibling jumps to end of file


I am using an XMLReader in C# to obtain data from an XML document received as part of a web request. The XML is generated based on query parameters passed to the service and can include a myriad of elements. The example in this question is based on an inventory query.

Here is a stripped down version of the XML:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfQMXINVQ_INVENTORYType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<QMXINVQ_INVENTORYType>
    <ABCTYPE>C</ABCTYPE>
    <BINNUM>A-5-2</BINNUM>
    <FREQUNIT />
    <GLACCOUNT>
        <VALUE>????-???-200</VALUE>
        <GLCOMP
            glorder="2">200</GLCOMP>
    </GLACCOUNT>
    <INVENTORYID>140</INVENTORYID>
    <INVGENTYPE />
    <ISSUEUNIT>EACH</ISSUEUNIT>
    <VENDOR>ATI</VENDOR>
    <INVBALANCES>
        <BINNUM>A-5-2</BINNUM>
                    <CURBAL>6</CURBAL>
        <STAGEDCURBAL>0</STAGEDCURBAL>
        <STAGINGBIN>false</STAGINGBIN>
    </INVBALANCES>
    <ITEM>
        <DESCRIPTION>Connecting Link - Repair</DESCRIPTION>
        <EXTERNALREFID />
        <GROUPNAME />
        <ISSUEUNIT />
        <ITEMID>175</ITEMID>
        <ITEMTYPE>ITEM</ITEMTYPE>
        <LOTTYPE
            maxvalue="NOLOT">NOLOT</LOTTYPE>
    </ITEM>
</QMXINVQ_INVENTORYType>
<QMXINVQ_INVENTORYType>
    <ABCTYPE>C</ABCTYPE>
    <BINNUM>B-8-1</BINNUM>
    <FREQUNIT />
    <GLACCOUNT>
        <VALUE>????-???-300</VALUE>
        <GLCOMP
            glorder="2">300</GLCOMP>
    </GLACCOUNT>
    <INVENTORYID>142</INVENTORYID>
    <INVGENTYPE />
    <ISSUEUNIT>EACH</ISSUEUNIT>
    <VENDOR>ATI</VENDOR>
    <INVBALANCES>
        <BINNUM>B-8-1</BINNUM>
                    <CURBAL>5</CURBAL>
        <STAGEDCURBAL>0</STAGEDCURBAL>
        <STAGINGBIN>false</STAGINGBIN>
    </INVBALANCES>
    <ITEM>
        <DESCRIPTION>Fence Stretcher</DESCRIPTION>
        <EXTERNALREFID />
        <GROUPNAME />
        <ISSUEUNIT />
        <ITEMID>105</ITEMID>
        <ITEMTYPE>ITEM</ITEMTYPE>
        <LOTTYPE
            maxvalue="NOLOT">NOLOT</LOTTYPE>
    </ITEM>
  </QMXINVQ_INVENTORYType>

Here is the code block in question:

     XmlTextReader reader = new XmlTextReader(strRespFile);
     reader.MoveToContent();
     string topLevelElementName = reader.Name;
     // We need to advance "i" rows here.
     for (int k = 0; k < i; k++)
     {
        reader.ReadToNextSibling(topLevelElementName);
     }

     // Now, read the element's value for the column
     while (reader.Read())
     {
        if (reader.NodeType == XmlNodeType.Element)
        {
           if (reader.Name == c)
           {
              reader.Read();
              dr[c] = reader.Value;
              break;
           }
        }
     }
     reader.Close();

The top level code (not shown) reads the child elements to a datatable. If a subchild is encountered, the code included is executed. The subchildren of the first child element is found and successfully added to the datatable.

<INVBALANCES>
    <CURBAL>6</CURBAL>
...
<ITEM>
    <DESCRIPTION>Connecting Link - Repair</DESCRIPTION>

Subsequent executions result in the reader jumping straight to the end of the file. Any ideas on why it is going to EOF instead of reading the next child?


Solution

  • You are positioning the reader on the root element "ArrayOfQMXINVQ_INVENTORYType", then trying to read its next sibling. But the root element cannot have a sibling, because there can be only one. What you want to do is:

    • Move to the root element
    • Read to its first child.
    • Skip to the Kth child element of the root named topLevelElementName.
    • Search its child elements.

    For instance:

            var xml = GetXml(); // Your sample XML as a string
    
            int topLevelElementIndex = 1;
            var topLevelElementName = "QMXINVQ_INVENTORYType";
    
            try
            {
                using (var sr = new StringReader(xml)) // You would use e.g. var sr = new StreamReader(strRespFile, Encoding.Encoding.UTF8)
                using (var reader = XmlReader.Create(sr))
                {
                    // Move to ROOT Element
                    reader.MoveToContent();
                    Debug.Assert(reader.Name == "ArrayOfQMXINVQ_INVENTORYType"); // No assert.
    
                    // Read to first CHILD Element of the root
                    reader.Read(); // Read to the first child NODE of the root element
                    reader.MoveToContent(); // If that node is whitespace, skip to content.
    
                    // Read to the FIRST array element of the desired name.
                    if (reader.Name != topLevelElementName)
                        if (!reader.ReadToNextSibling(topLevelElementName))
                            throw new InvalidOperationException("Not enough elements named " + topLevelElementName);
                    // Now read to the Nth element of the desired name.
                    for (int i = topLevelElementIndex; i > 0; i--)
                        if (!reader.ReadToNextSibling(topLevelElementName))
                            throw new InvalidOperationException("Not enough elements named " + topLevelElementName);
    
                    // Process the Nth element as desired.
                    Debug.Assert(reader.NodeType == XmlNodeType.Element && reader.Name == topLevelElementName);
                    using (var subReader = reader.ReadSubtree())
                    {
                        var element = XElement.Load(subReader);
                        Debug.WriteLine(element.ToString());
                        // If each individual array element has manageable size, the easiest way to parse it is to load it into
                        // an XElement with a nested reader, then use Linq to XML
                        Debug.Assert(element.Descendants("BINNUM").Select(e => (string)e).FirstOrDefault() == "B-8-1"); // No assert.
                    }
                }
            }
            catch (Exception ex)
            {
                // Handle errors in the file however you like.
                Debug.WriteLine(ex);
                throw;
            }
    

    Example fiddle.