BMills
BMills

Reputation: 901

Get last child element using XmlReader

Say I have this XML:

 <fields>
        <field fieldid="fdtElem3Group">
          <value actionid="1" actiontype="review">123456789</value>
          <value actionid="2" actiontype="review">123456789</value>
          <value actionid="3" actiontype="review">123456789</value>
          <value actionid="4" actiontype="review">123456789</value>
          <value actionid="5" actiontype="review">123456789</value>
        </field>
        <field fieldid="fdtElem7Group">
          <value actionid="1" actiontype="review">29/10/75</value>
          <value actionid="2" actiontype="review">29/10/74</value>
          <value actionid="3" actiontype="review">29/10/74</value>
          <value actionid="4" actiontype="review">29/10/76</value>
          <value actionid="5" actiontype="review">29/10/74</value>
        </field>
      </fields>

I'm trying to get the value of the last 'value' element of each respective 'field' element using XmlReader. How would I do that please? This doesn't work:

while (xmlReader.Read())
{
    if ((xmlReader.NodeType == System.Xml.XmlNodeType.Element) && (xmlReader.Name == "field"))
    {
        xmlReader.ReadToDescendant("value");
        while (xmlReader.ReadToNextSibling("value"))
        {
            //just iterate over until it reaches the end
        }
        xmlReader.Read();
        Console.WriteLine(xmlReader.Value);
    }
}

Upvotes: 0

Views: 1579

Answers (3)

dbc
dbc

Reputation: 116805

If you don't want to load your entire XML into an XDocument (because, e.g., it is very large), you can adopt a hybrid approach where you use an XmlReader to iterate through the <field> and <value> elements in your XML file, load each <value> element into an XElement, then select the last one of each parent <field>. This keeps the memory footprint small and constant no matter how large the XML file grows.

First introduce the following two extension methods:

public static class XmlReaderExtensions
{
    public static IEnumerable<IEnumerable<XElement>> ReadNestedElements(this XmlReader xmlReader, string outerName, string innerName)
    {
        while (!xmlReader.EOF)
        {
            if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == outerName)
            {
                using (var subReader = xmlReader.ReadSubtree())
                {
                    yield return subReader.ReadElements(innerName);
                }
            }
            xmlReader.Read();
        }
    }

    public static IEnumerable<XElement> ReadElements(this XmlReader xmlReader, string name)
    {
        while (!xmlReader.EOF)
        {
            if (xmlReader.NodeType == System.Xml.XmlNodeType.Element && xmlReader.Name == name)
            {
                var element = (XElement)XNode.ReadFrom(xmlReader);
                yield return element;
            }
            else
            {
                xmlReader.Read();
            }
        }
    }
}

Then your algorithm to get the last 'value' element of each respective 'field' element becomes very simple:

public static List<string> LastFieldValues(XmlReader reader)
{
    var query = reader.ReadNestedElements("field", "value")
        .Select(l => l.LastOrDefault())
        .Where(v => v != null)
        .Select(v => (string)v);

    return query.ToList();
}

Notes:

  • XmlReaderExtensions.ReadNestedElements() returns an enumerable of enumerables of <value> elements belonging to <field> elements. Enumerable.LastOrDefault() is then used to select the last <value> belonging to each <field>.

  • Whenever working directly with XmlReader, be sure to test XML both with and without indentation, for reasons explained here and here.

  • XmlReader.ReadSubtree() leaves the XmlReader positioned on the EndElement node of the element being read, while XNode.ReadFrom() leaves the reader positioned immediately after the EndElement node of the element being read. Just an annoying inconsistency to watch out for.

On the other hand, if you are willing to load the entire XML into memory as an XDocument, this can be done very simply using XPathSelectElements():

// Parse the XML into an XDocument.
// You can use use XDocument.Load(fileName) to load from a file
var doc = XDocument.Parse(xmlString); 
var xpathLastValues = doc
    .XPathSelectElements(@"//fields/field/value[last()]")
    .Select(e => e.Value)
    .ToList();

Sample fiddle.

Upvotes: 1

Venu Amudala
Venu Amudala

Reputation: 29

Hope this helps !

static void Main(string[] args)
        {
            string xml = @"<fields><field fieldid='fdtElem3Group'><value actionid='1' actiontype='review'>123456789</value><value actionid='2' actiontype='review'>123456789</value><value actionid='3' actiontype='review'>123456789</value><value actionid='4' actiontype='review'>123456789</value><value actionid='5' actiontype='review'>123456789</value></field><field fieldid='fdtElem7Group'><value actionid='1' actiontype='review'>29/10/75</value>          <value actionid='2' actiontype='review'>29/10/74</value><value actionid='3' actiontype='review'>29/10/74</value><value actionid='4' actiontype='review'>29/10/76</value><value actionid='5' actiontype='review'>29/10/74</value></field></fields>";
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(xml);
            foreach (XmlNode item in xmlDocument.DocumentElement.ChildNodes)
            {
                Console.WriteLine(item.LastChild.InnerXml);
            }            
            Console.ReadKey();
        }

Upvotes: 1

Riv
Riv

Reputation: 1859

Sorry, just read now you're looking for an xmReader solution. But using XDocument and Linq, you could do the following:

string xml = @"<fields>
        <field fieldid=""fdtElem3Group"">
          <value actionid=""1"" actiontype=""review"">123456789</value>
          <value actionid=""2"" actiontype=""review"">123456789</value>
          <value actionid=""3"" actiontype=""review"">123456789</value>
          <value actionid=""4"" actiontype=""review"">123456789</value>
          <value actionid=""5"" actiontype=""review"">123456789</value>
        </field>
        <field fieldid=""fdtElem7Group"">
          <value actionid=""1"" actiontype=""review"">29/10/75</value>
          <value actionid=""2"" actiontype=""review"">29/10/74</value>
          <value actionid=""3"" actiontype=""review"">29/10/74</value>
          <value actionid=""4"" actiontype=""review"">29/10/76</value>
          <value actionid=""5"" actiontype=""review"">29/10/74</value>
        </field>
      </fields>";



var xmlDoc = XDocument.Parse(xml).Root;
var lastElements = xmlDoc.Descendants("field").Select(x => x.LastNode);

Upvotes: 1

Related Questions