Xpath Expressions and XPathNavigator to the rescue

I would like to admit that I have, many a times, taken XML handling for granted and have come with ghastly pieces of code like the one shown below, where all I need is to find out the number of nodes in an Xml document. What I am doing here, is loading the Xml data it to a DOM, creating a node list and then accessing its Count property – all in all, an extremely expensive and needless process.

booksDoc.LoadXml(booksXml);

int numBooks = booksDoc.SelectNodes("bookstore/book").Count;
for (int i = 0; i < numBooks; i++)
{
    
// Some code
}

A much better and more efficient way to solve this problem is to use XPath Expressions and the XPathNavigator class. The XPathNavigator provides a method called Evaluate which can evaluate Xpath expressions to return boolean, string, int values accordingly. As an alternative to the above code, we can just use the count Xpath function to return the number of nodes in the node-set.

XPathDocument xpathDoc = new XPathDocument(reader);
XPathNavigator navigator = xpathDoc.CreateNavigator();

int numBooks = Convert.ToInt32(navigator.Evaluate("count(bookstore/book)"));

for (int i = 0; i < numBooks; i++)
{
    
//Some code
}

The reason why the latter approach is efficient is that XPathDocument creates a read-only in-memory tree that is optimized for Xpath and Xslt. On the other hand, XmlNode is a modifiable DOM representation, which could make it inefficient for Xpath-like scenarios.

An example from Aaron Skonnard's Xml Files column illustrates this better. What the code sample is trying to do is find the sum price of invoice line items from the given Xml document.

The XmlDocument approach:

const string EXPRESSION = "/Invoices/Invoice/LineItems/LineItem/Price/text()";

static void UseDOM()
{
    XmlDocument doc = 
new XmlDocument();
    doc.Load("invoices.xml");

    XmlNodeList selection = doc.SelectNodes(EXPRESSION);
    
double total = 0;

    
foreach (XmlNode n in selection)
    {
        total += XmlConvert.ToDouble(n.InnerText);
    }

    Console.WriteLine("sum: {0}", total);
}

The better approach. Note the usage of the sum Xpath expression.

const string SUMEXPRESSION = "sum(/Invoices/Invoice/LineItems/LineItem/Price/text())";

static void UseNavigatorEvaluate()
{
    XPathDocument doc = 
new XPathDocument("invoices.xml");

    XPathNavigator nav = doc.CreateNavigator();

    Console.WriteLine("sum: {0}", nav.Evaluate(SUMEXPRESSION));

}

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>