Reading multiple child nodes of xml file

87,007

Solution 1

You could use an XPath approach like so:

XmlNodeList xnl = doc.SelectNodes(string.Format("/Periods/PeriodGroup[@name='{0}']/Period", PG));

Though prefer LINQ to XML for it's readability.

This will return Period node children based on the PeriodGroup name attribute supplied, e.g. HER:

XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));

var nodes = (from n in xml.Descendants("Periods")
            where n.Element("PeriodGroup").Attribute("name").Value == "HER"
            select n.Element("PeriodGroup").Descendants().Elements()).ToList();

Results:

<PeriodName>Prehistoric</PeriodName>
<StartDate>-500000</StartDate>
<EndDate>43</EndDate>

<PeriodName>Iron Age</PeriodName>
<StartDate>-800</StartDate>
<EndDate>43</EndDate>

<PeriodName>Roman</PeriodName>
<StartDate>43</StartDate>
<EndDate>410</EndDate>

The query is pretty straightforward

from n in xml.Descendants("Periods")

Will return a collection of the descendant elements for the element Periods. We then use where to filter this collection of nodes based on attribute value:

where n.Element("PeriodGroup").Attribute("name").Value == "HER"

Will then filter down the collection to PeriodGroup elements that have a name attribute with a value of HER

Finally, we select the PeriodGroup element and get it's descendant nodes

select n.Element("PeriodGroup").Descendants().Elements()

EDIT (See comments)

Since the result of this expression is just a query, we use .ToList() to enumerate the collection and return an object containing the values you need. You could also create anonymous types to store the element values for example:

var nodes = (from n in xml.Descendants("Period").
             Where(r => r.Parent.Attribute("name").Value == "HER")
             select new
             {
                  PeriodName = (string)n.Element("PeriodName").Value,
                  StartDate = (string)n.Element("StartDate").Value,
                  EndDate = (string)n.Element("EndDate").Value
             }).ToList();

//Crude demonstration of how you can reference each specific element in the result
//I would recommend using a stringbuilder here..
foreach (var n in nodes)
{
      text += "<br>" + n.PeriodName;
      text += "<br>" + n.StartDate;
      text += "<br>" + n.EndDate;
}

This is what the nodes object will look like after the query has run:

enter image description here

Solution 2

Since the XmlDocument.SelectNodes method actually accepts an XPath expression, you're free to go like this:

XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup[@name='" + PG + "']/Period");
foreach (XmlNode node in xnl) {
    // Every node here is a <Period> child of the relevant <PeriodGroup>.
}

You can learn more on XPath at w3schools.

Share:
87,007

Related videos on Youtube

Peter C
Author by

Peter C

I am retired now but was in public health working on metadata systems and standards plus some web development (ASP.Net and c#), mainly in data visualisation. I am by no means an expert but have usually managed to do everything I've needed to, albeit a bit slow at times, especially in the early days and with help from some real experts. Now-days I am mainly just keeping my hand in working on a few websites for voluntary organisations (using VS2010 in ASP.Net and c#) and have recently been working on some interactive mapping which is going really well. I have now started playing around with linq to xml and linq to sql which seems quite a different way to work from what I've been used to.

Updated on July 24, 2022

Comments

  • Peter C
    Peter C almost 2 years

    I have created an Xml file with example contents as follows:

    <?xml version="1.0" encoding="utf-8" ?>
    <Periods>
      <PeriodGroup name="HER">
        <Period>
          <PeriodName>Prehistoric</PeriodName>
          <StartDate>-500000</StartDate>
          <EndDate>43</EndDate>
        </Period>
        <Period>
          <PeriodName>Iron Age</PeriodName>
          <StartDate>-800</StartDate>
          <EndDate>43</EndDate>
        </Period>
        <Period>
          <PeriodName>Roman</PeriodName>
          <StartDate>43</StartDate>
          <EndDate>410</EndDate>
        </Period>
      </PeriodGroup>
      <PeriodGroup name="CAFG">
        <Period>
          <PeriodName>Prehistoric</PeriodName>
          <StartDate>-500000</StartDate>
          <EndDate>43</EndDate>
        </Period>
        <Period>
          <PeriodName>Roman</PeriodName>
          <StartDate>43</StartDate>
          <EndDate>410</EndDate>
        </Period>
        <Period>
          <PeriodName>Anglo-Saxon</PeriodName>
          <StartDate>410</StartDate>
          <EndDate>800</EndDate>
        </Period>   
      </PeriodGroup>
    </Periods>
    

    I need to be able to read the Period node children within a selected PeriodGroup. I guess the PeriodName could be an attribute of Period if that is more sensible.

    I have looked at loads of examples but none seem to be quite right and there seems to be dozens of different methods, some using XmlReader, some XmlTextReader and some not using either. As this is my first time reading an Xml file, I thought I'd ask if anyone could give me a pointer. I've got something working just to try things out, but it feels clunky. I'm using VS2010 and c#. Also, I see a lot of people are using LINQ-Xml, so I'd appreciate the pros and cons of using this method.

    string PG = "HER";
    XmlDocument doc = new XmlDocument();
    doc.Load(Server.MapPath("./Xml/XmlFile.xml"));
    string text = string.Empty;
    XmlNodeList xnl = doc.SelectNodes("/Periods/PeriodGroup");
    foreach (XmlNode node in xnl)
    {
        text = node.Attributes["name"].InnerText;
        if (text == PG)
        {
            XmlNodeList xnl2 = doc.SelectNodes("/Periods/PeriodGroup/Period");
            foreach (XmlNode node2 in xnl2)
            {
                text = text + "<br>" + node2["PeriodName"].InnerText;
                text = text + "<br>" + node2["StartDate"].InnerText;
                text = text + "<br>" + node2["EndDate"].InnerText;
            }
        }
        Response.Write(text);
    }
    
  • Peter C
    Peter C about 11 years
    Would you mind expanding your LINQ a little as I've never used it. Using XElement xml = XElement.Load(Server.MapPath("./Xml/XmlFile.xml")); the nodes are not returned in the var nodes.
  • Peter C
    Peter C about 11 years
    @DGibbs has expanded on your answer, but it did work anyway, so thanks.
  • DGibbs
    DGibbs about 11 years
    I think you mean XDocument for example: XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));
  • DGibbs
    DGibbs about 11 years
    Added a brief explanation of the query
  • Peter C
    Peter C about 11 years
    Ok, sorry to be thick here, I've now got some content in the nodes object, but I can't read it. How do I extract the results?
  • Peter C
    Peter C about 11 years
    BTW, I get an 'Object reference not set to an instance of an object' error when I use the 'select new {...}' method above before it gets to the foreach so I must be missing something.
  • DGibbs
    DGibbs about 11 years
    I will edit my post to update my LINQ query which will return anonymous types instead, and also demonstrate how to access these types
  • Peter C
    Peter C about 11 years
    Brilliant, thanks DGibbs for your help. It was the .ToList() I was missing. Quite happy with StringBuilder and use it all the time. This has got me to a point where I can go forward integrating it with the rest of the project.
  • Greg
    Greg almost 7 years
    There's no question here, we can't answer if you don't ask :)
  • Greg
    Greg almost 7 years
    ups, sorry ;) I got confused