Duke Cyrillus
Duke Cyrillus

Reputation: 1237

XPath expression for finding an element without a particular attribute

I have an xml file with the following format

<someXML>
    <a>
        <b></b>
        <b test='someValue'></b>
    </a>

    <c>
        <d test='someOtherValue'></d>
        <d></d>
    </c>
</someXML>

I'm trying to write C# code that will get me a node set containing only those nodes with the test attribute. I am trying to use an XPathExpression in this manner:

XDocument document = XDocument.Load("someXML");
XPathNavigator navigator = document.CreateNavigator();
XPathExpression expression = XPathExpression.Compile("//*[@test]");
object nodeSet = navigator.Evaluate(expression);

This doesn't seem to be working. When I debug, the nodeSet always contains the Root element. Is there something wrong with the expression or is this working correctly?

Thanks!

Upvotes: 2

Views: 5888

Answers (6)

Sean H
Sean H

Reputation: 746

Your XPath query is correct. The Current member will be the Root element (I'm not an XPathIterator expert, so I don't really know the rationale behind it). If you iterate through your nodeSet, you will only get the matches to your XPath query. So I guess the answer is to iterate the set and put each item into a collection.

Upvotes: 2

Erik Dietrich
Erik Dietrich

Reputation: 6090

Why not just do this, assuming that you just want the nodes and don't have some specific reason to use the navigator (null checking elided):

var document = XDocument.Load("someXML");
var myNodes = document.Descendants().Where(node => node.Attribute(myAttributeName) != null);

It doesn't use the navigator, but it's pretty simple and straightforward if you just want the nodes.

Upvotes: 0

Oleks
Oleks

Reputation: 32343

Your expression is seems to be correct. But you could use XPathSelectElements extension method to do it simplier:

var nodes = document.XPathSelectElements("//*[@test]");

Upvotes: 3

Brad Falk
Brad Falk

Reputation: 196

I believe you'll want to use the Select method of the XPathNavigator object, instead of Evaluate, which will return an XPathNodeIterator object.

http://msdn.microsoft.com/en-us/library/3tk3f03k.aspx

or you could use the XmlDocument object and XmlNodeList:

        XmlDocument document = new XmlDocument();
        document.Load("somexml.xml");

        XmlNodeList nodeList = document.SelectNodes("//*[@test]");

        foreach (XmlNode Node in nodeList) { 
            // do something.            
        }

Upvotes: 1

k.m
k.m

Reputation: 31464

Your XPath does work, it's just you need to retrieve nodes a bit differently:

XPathNavigator navigator = document.CreateNavigator();
XPathExpression expression = XPathExpression.Compile("//*[@test]");
var matchedNodes = navigator.Select(expression);
foreach (XPathNavigator node in matchedNodes)
{
    Console.WriteLine(node.UnderlyingObject);
}

Snippet above will print the following:

<b test='someValue'></b>
<d test='someOtherValue'></d>

Upvotes: 4

Douglas
Douglas

Reputation: 54897

Do you assume that the nodeSet contains the “Root” element because the debugger displays its string representation as Position=0, Current={Root}? That may be misleading; the reason that the “Root” element appears is because the XPathNodeIterator (which is the actual type returned by Evaluate in your case) has not yet been positioned on the first node resulting from the query. Per the MSDN documentation for XPathNodeIterator:

An XPathNodeIterator object returned by the XPathNavigator class is not positioned on the first node in a selected set of nodes. A call to the MoveNext method of the XPathNodeIterator class must be made to position the XPathNodeIterator object on the first node in the selected set of nodes.

You can retrieve your path items by iterating over your nodeSet:

XDocument document = XDocument.Load("someXML");
XPathNavigator navigator = document.CreateNavigator();
XPathExpression expression = XPathExpression.Compile("//*[@test]");
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(expression);
foreach (XPathItem item in nodeSet)
{
    // Process item here
}

Or, if you want them converted to an array:

XDocument document = XDocument.Load("someXML");
XPathNavigator navigator = document.CreateNavigator();
XPathExpression expression = XPathExpression.Compile("//*[@test]");
XPathNodeIterator nodeSet = (XPathNodeIterator)navigator.Evaluate(expression);
XPathItem[] items = nodeSet.Cast<XPathItem>().ToArray();

Upvotes: 4

Related Questions