tags:

views:

45

answers:

3

In the following code I am using XPath to find all of the matching nodes using XPath, and appending the values to a StringBuilder.

StringBuilder sb = new StringBuilder();
foreach (XmlNode node in this.Data.SelectNodes("ID/item[@id=200]/DAT[1]/line[position()>1]/data[1]/text()"))
{
    sb.Append(node.Value);
}
return sb.ToString();

How do I do the same thing, except using Linq to XML instead? Assume that in the new version, this.Data is an XElement object.

+1  A: 

You can still use XPath with a XElement. You have to include the System.Xml.XPath namespace since its an extension method

var xml = XDocument.Parse("<xml></xml>");
var elements = xml.XPathSelectElements("ID/item[@id=200]/DAT[1]/line[position()>1]/data[1]/text()");
Pierre-Alain Vigeant
Thanks Pierre. I gave the answer to Matthew because that was what I was looking for, but I'm curious about this. Would you say there is an advantage of XPath vs Linq to XML, other than familiarity?
John Hansen
Beside helping against the null reference problem that Matthew commented about, not really. Linq-to-xml can help you do strong typed comparaison (where clause) and it is very good against "xml-injection" if that even exists :P
Pierre-Alain Vigeant
A: 

Query syntax would look something like this.

var nodes = from item in Data.Elements("item")
            where item.Attribute("id").Value == "200"
            let dat = item.Element("DAT")
            from line in dat.Elements("line").Skip(1)
            let data = line.Element("data")
            select data;

var sb = new StringBuilder();
foreach (var node in nodes)
    sb.Append(node.Value);

... this would get rid of the possible nullref exceptions and would get rid of the foreach loop.

var nodes = from item in Data.Elements("item")
            let id = item.Attribute("id")
            where id != null && id.Value == "200"
            let dat = item.Element("DAT")
            where dat != null
            from line in dat.Elements("line").Skip(1)
            let data = line.Element("data")
            where data != null
            select data.Value;
return nodes.Aggregate(new StringBuilder(), (sb, r) => sb.Append(r))
            .ToString();
Matthew Whited
There are several points in this query where you could get nullref exceptions... depending on how well structured your XML is.
Matthew Whited
ID was the root node, so I removed that line, then it worked perfectly. Thanks!
John Hansen
Matthew, can you explain a scenerio where that might happen?
John Hansen
If there was no `@id`, no `DAT` element, or no `data` element.
Matthew Whited
If you have generated XML that is back with something like XSD (or you just trust it will be structured correctly) you can skip the null checks. The XPath will pretty much do this for you.
Matthew Whited
Very elegent, works great. Thanks!
John Hansen
I do plan to validate the XML with a schema eventually, but I will need the null checks until then, so that was useful.
John Hansen
Pretty much any of the method names that are `singular` could return `null`. ... just something to think about
Matthew Whited
A: 

You can try something like that :

var query = 
    from id in this.Data.Elements("ID")                      // ID
    from item in id.Elements("item")                         // /item
    where item.Attribute("id").Value == "200"                // [@id=200]
    let dat in item.Elements("DAT").ElementAtOrDefault(1)    // /DAT[1]
    where dat != null                                        // (ensure there is an element at index 1)
    from line in dat.Elements("line").Skip(1)                // /line[position()>1]
    let data in line.Elements("data").ElementAtOrDefault(1)  // /data[1]
    where data != null                                       // (ensure there is an element at index 1)
    select data                                              // /text()

StringBuilder sb = new StringBuilder();
foreach(XElement data in query)
{
    sb.Append(data.Value);
}
return sb.ToString();

As you can see, it's not really more convenient than XPath, so you should probably keep using XPath (as explained in Pierre-Alain's answer)

Thomas Levesque
the `[1]` in XPath is the first element in the set (It's 1's based instead of 0 based) You could just use `.Element` as in my example.
Matthew Whited
oh, that's right... it's been a while since I last used XPath, I'm a bit rusty ;)
Thomas Levesque