Reading multiple child nodes of xml file
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:
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.
Related videos on Youtube
![Peter C](https://i.stack.imgur.com/wT3u1.jpg?s=256&g=1)
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, 2022Comments
-
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 about 11 yearsWould 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 about 11 years@DGibbs has expanded on your answer, but it did work anyway, so thanks.
-
DGibbs about 11 yearsI think you mean
XDocument
for example:XDocument xml = XDocument.Load(HttpContext.Current.Server.MapPath(FileLoc));
-
DGibbs about 11 yearsAdded a brief explanation of the query
-
Peter C about 11 yearsOk, 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 about 11 yearsBTW, 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 about 11 yearsI 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 about 11 yearsBrilliant, thanks DGibbs for your help. It was the
.ToList()
I was missing. Quite happy withStringBuilder
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 almost 7 yearsThere's no question here, we can't answer if you don't ask :)
-
Greg almost 7 yearsups, sorry ;) I got confused