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?
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:
topLevelElementName
.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.