Microsoft & .NET.NETUnderstanding XPath

Understanding XPath

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

This article is an excerpt from the book XML and ASP.NET (ISBN:073571200X), written by Kirk Allen Evans, Ashwin Kamanna, and Joel Mueller, published by New Riders Publishing.

What is XPath?

XML is simply markup for data. That's it. XML is not a magic wand; it
does not specify how data is transmitted over the wire, it does not specify how
data is stored. XML simply determines the format of the data: what you do with
the data is up to you. That said, the real power behind XML is not solely its
ability to represent data: XML's real power lies in ancillary technologies
that, when combined with XML, provide robust solutions, and XPath is one of
those ancillary technologies.

Version 1.0 of the XML Path Language became a World Wide Web Consortium (W3C)
recommendation on November 16th, 1999. You can view the W3C recommendation for
XPath 1.0 at
http://www.w3.org/TR/xpath.
This document shows all information relating to XPath including an overview of
XPath and a description of its components.

XPath grew out of efforts to share a common syntax between XSL
Transformations (XSLT) and XPointer. It allows for the search and retrieval of
information within an XML document structure. XPath is not an XML syntax:
rather, it uses a syntax that relates to the logical structure of an XML
document.

An Analogy to SQL

Consider a relational database. Is the real power of a database the ability
to simply store data, index the data, and specify relations between tables of
data? After all, a database is supposed to hold data, so is the capability of
persisting data the real advantage behind a relational database? If so, a simple
file would suffice for this. It is easy to see that the real power of a database
is the ability to use Structured Query Language (SQL) statements to retrieve
subsets of data. To take this example one step further, the fact that SQL is an
ANSI standard makes your knowledge of SQL applicable to different databases
running on different platforms.

Using this same logic, XML would simply be a format for data storage without
a prescribed way of retrieving that data. This is exactly what XPath is: XPath
is the query language for XML documents. XPath is the common name used for XML
Path Language. Using XPath statements, you can retrieve complex subsets of data
from XML documents using a syntax that is universal across implementations. The
same XPath statements that work within the System.Xml and
System.Xml.XPath namespaces should work exactly the same as XPath
statements in the MSXML Parser, and both should work exactly the same as other
parsers that implement the W3C XPath recommendation.

An Analogy to a File Path

Computers are built around files and the organization of those files. To
access files, you need to be able to navigate to different portions of the file
system. One way to navigate a file system is to use the Uniform Naming
Convention (UNC) for specifying the location of resources on a local area
network (LAN). UNC separates folders and files using a backslash ()
character. In the good ol' DOS days before point-and-click, file systems
were navigated using command-line syntax.

Go to the Start button on your computer; choose Run, and type cmd in
the text box to bring up a DOS command shell window. You will see the following
text:

Microsoft Windows 2000 [Version 5.00.2195]
(C) Copyright 1985-2000 Microsoft Corp.
C:>

At the command prompt, change directories from the C: root to the Program
FilesMicrosoft Visual studio directory.

C:>cd Program FilesMicrosoft Visual Studio

To change directories, you specified a path for the file system to navigate.
More to the point, you specified a series of location steps used to navigate to
a new folder based on the current folder. XPath uses a very similar syntax.
Imagine your file system as an XML document.

<?xml version="1.0" encoding="utf-8" ?>
<C>
   <INETPUB>
     <WWWROOT>
        <ASPNET_CLIENT/>
     </WWWROOT>
   </INETPUB>
<C>

We could easily represent this as an XPath statement:

C/INETPUB/WWWROOT/ASPNET_CLIENT

If we are currently positioned at the very beginning of the document there
are four location steps made. But what if we were currently positioned on the
WWWROOT element and wanted to reposition to the ASPNET_CLIENT
element? We would specify the following XPath statement:

./ASPNET_CLIENT

The period (.) at the beginning of the XPath statement represents
the expression "the context node", meaning the node that we originally
started from. Instead of specifying that we are navigating based on the context
node, we can also use a short form of XPath that specifies a path relative to
the context node:

ASPNET_CLIENT

A location path is composed of three parts: the axis, the node-test, and zero
or more predicates.

XPath Axis

The axis component of an XPath query determines the direction of the node
selection in relation to the context node. An axis can be thought of as a
directional query. The axes listed in Table 1 are provided in XPath.

Table 1 XPath Axes

Axis

Description

ancestor

The context node's parent, the parent's parent, and so on.

ancestor-or-self

The context node as well as its ancestors.

attribute

The attributes of the context node.

child

All children of the context element (attributes cannot have children).

descendant

All descendants of the context: children, children's children, and so
on.

descendant-or-self

All descendants as well as the context node.

following

All nodes in the same document as the context node that are after the context
node. This does not include descendants, attribute nodes, or namespace
nodes.

following-sibling

All the following siblings of the context node. A sibling is an element
occurring at the same level in the tree.

namespace

The namespace nodes of the context node.

parent

The parent of the context node.

preceding

All nodes in the same document as the context node that are immediately
before the context node.

preceding-sibling

Contains the preceding siblings. If the context node is either an attribute
or a name-space node, the preceding-sibling axis is empty

The examples so far have used forward axes: that is, we have
only navigated to nodes that are descendants of the context node. Let's
look at some examples of XPath statements using reverse axes, or axes that
navigate up the document hierarchy. Consider the following representation of a
file system, with drives A, C, and D, and D has a backup copy of the contents of
the C drive. The context node is highlighted. This document is represented in
Listing 1. Note that the line numbers are represented only for explanation and
are not actually part of the XML document.

Listing 1—An XML Representation of a File System

1 <?xml version="1.0" encoding="utf-8" ?>
2 <FILESYSTEM>
3    <DRIVE LETTER="A"/>
4    <DRIVE LETTER="C">
5     <FOLDER NAME="INETPUB">
6        <FOLDER NAME="WWWROOT">
7          <FOLDER NAME="ASPNET_CLIENT" />
8        </FOLDER>
9     </FOLDER>
10     <FOLDER NAME="Program Files">
11        <FOLDER NAME="Microsoft Visual Studio .NET">
12          <FOLDER NAME="Framework SDK">
13             <FOLDER NAME="BIN"></FOLDER>
14          </FOLDER>
15        </FOLDER>
16     </FOLDER>
17   </DRIVE>
18   <DRIVE LETTER="D">
19     <FOLDER NAME="INETPUB">
20        <FOLDER NAME="WWWROOT">
21          <FOLDER NAME="ASPNET_CLIENT" />
22        </FOLDER>
23     </FOLDER>
24     <FOLDER NAME="Program Files">
25        <FOLDER NAME="Microsoft Visual Studio .NET"/>
26     </FOLDER>
27   </DRIVE>
28 </FILESYSTEM>

Working with the preceding XML structure, we introduce the following XPath
statement:

parent::*

This XPath query translates to "retrieve all parent nodes of the context
node", which would return the element "FOLDER" on line 10.

ancestor-or-self::*

This query would return a more complex structure, which is depicted in
Listing 2. The returned nodes are highlighted.

Listing 2—An XML Representation of a File System

<?xml version="1.0" encoding="utf-8" ?>
<FILESYSTEM>
   <DRIVE LETTER="A"/>
   <DRIVE LETTER="C">
     <FOLDER NAME="INETPUB">
        <FOLDER NAME="WWWROOT">
          <FOLDER NAME="ASPNET_CLIENT" />
        </FOLDER>
     </FOLDER>
     <FOLDER NAME="Program Files">
        <FOLDER NAME="Microsoft Visual Studio .NET">
          <FOLDER NAME="Framework SDK">
             <FOLDER NAME="BIN"></FOLDER>
          </FOLDER>
        </FOLDER>
     </FOLDER>
   </DRIVE>
   <DRIVE LETTER="D">
     <FOLDER NAME="INETPUB">
        <FOLDER NAME="WWWROOT">
          <FOLDER NAME="ASPNET_CLIENT" />
        </FOLDER>
     </FOLDER>
     <FOLDER NAME="Program Files">
        <FOLDER NAME="Microsoft Visual Studio .NET"/>
     </FOLDER>
   </DRIVE>
</FILESYSTEM>

As you can see in Listing 2, a path is depicted from the context node
directly to the root node.

Our examples of axes used an axis with an accompanying asterisk. The asterisk
is considered a wildcard that translates to "all nodes within the specified
path". This identifier is known as the node-test.

Location paths can be relative or absolute. Relative location paths consist
of one or more location paths separated by backslashes. Absolute location paths
consist of a backslash optionally followed by a relative location path. In other
words, relative location paths navigate relative to the context node. Absolute
paths specify the absolute position within the document. An absolute
location path would then be:

/FILESYSTEM/DRIVE[@LETTER='C']/FOLDER[@NAME='Program Files']

Using an absolute location path, the current context node is ignored when
evaluating the XPath query, except for the fact that the path being searched
exists in the same document.

XPath Node Test

The XPath node test does just what its name implies: it tests nodes to
determine if they meet a condition. We already used one test, the asterisk
character, which specified all nodes should be returned. We can limit the nodes
that are returned by specifying names. Using the document in Listing 1 again, we
want to retrieve all ancestor elements that are named "DRIVE".

ancestor::DRIVE

By specifying the node name in the node test component of the XPath
statement, we limit the results so that only a single node is returned, the
DRIVE element on line four.

Besides using names for node-tests, we can also use node types. In Table 1,
we saw that one of the axes is an attribute axis, which retrieves an attribute
based on the specified node test. Again, using the document in Listing 1, the
following node test would return the attribute NAME for the context
node (highlighted in Listing 1):

attribute::NAME

If we wanted to select all attributes for the context node, we could also
issue a wildcard node test:

attribute::*

So, the type of node returned depends partially on the axes specified.
Attributes are not children of elements, so using the following XPath statement
would not return any nodes:

child::NAME

This is because there is no child element of the context node that is named
"NAME". We can also use XPath functions as node tests to return
certain nodes. The available node tests are listed in Table 2.

Table 2 Available XPath Function Node Tests

Axis

Description

comment()

Returns True if the matched node is a comment node.

node()

Returns True for any matched node, or False if no match was
found.

processing-instruction()

Returns True if the matched node is a processing-instruction.

text()

Returns True if the matched node is a text node.

Considering the analogy of an XPath statement to a SQL
statement, we have looked at the equivalent in XPath to a SQL SELECT
statement. Now, let's look at the equivalent to a SQL WHERE clause
in XPath.

XPath Predicates

Predicates filter the resulting node sets of an XPath query, producing a new
node set. A predicate can be evaluated as a Boolean or a number. When evaluated
as a number, nodes matching the positional number are returned, where the index
of nodes is 1-based. Listing 3 shows the same document as in Listing 1, but
highlights a new context node on line 18.

Listing 3—An XML Representation of a File System

1 <?xml version="1.0" encoding="utf-8" ?>
2 <FILESYSTEM>
3    <DRIVE LETTER="A"/>
4    <DRIVE LETTER="C">
5     <FOLDER NAME="INETPUB">
6        <FOLDER NAME="WWWROOT">
7          <FOLDER NAME="ASPNET_CLIENT" />
8        </FOLDER>
9     </FOLDER>
10     <FOLDER NAME="Program Files">
11        <FOLDER NAME="Microsoft Visual Studio .NET">
12          <FOLDER NAME="Framework SDK">
13             <FOLDER NAME="BIN"></FOLDER>
14          </FOLDER>
15        </FOLDER>
16     </FOLDER>
17   </DRIVE>
18   <DRIVE LETTER="D">
19     <FOLDER NAME="INETPUB">
20        <FOLDER NAME="WWWROOT">
21          <FOLDER NAME="ASPNET_CLIENT" />
22        </FOLDER>
23     </FOLDER>
24     <FOLDER NAME="Program Files">
25        <FOLDER NAME="Microsoft Visual Studio .NET"/>
26     </FOLDER>
27   </DRIVE>
28 </FILESYSTEM>

Position and Predicates

Using a predicate, we can return the FOLDER element on line 19 using
the following XPath:

child::*[position() = 1]

We can also use an abbreviated syntax to specify the same result:

child[1]

Besides using numeric position related to the context node, we can also use
compound predicates to express complex Boolean results. The modulus operator is
a common mechanism to test a value to see if it is even or odd. We can retrieve
the even numbered child elements of the context node:

child::*[position() mod 2 = 0]

If we wanted to return only the last node, we can use the XPath function
last() to test if the position of a node is the same as the position of
the last node, returning the last node:

child::*[position()=last()]

Besides complex predicates, we can also specify complex location steps using
axes, node tests, and predicates. If we wanted to find out all the drives on the
current machine using the document in Listing 3, we could issue the
following:

parent::FILESYSTEM/child::DRIVE

Abbreviated Location Path Syntax

Because XPath statements can become quite verbose, there also exists an
abbreviated version of XPath statements. Using abbreviated syntax, the preceding
XPath query is equivalent to:

parent::FILESYSTEM/DRIVE

Another abbreviation uses the backslash character to notate the root node of
the document containing the context node. This was previously explained as an
absolute location path. As an example, this XPath statement returns the
FOLDER element on line 19.

/FILESYSTEM/DRIVE[@LETTER='D']/FOLDER[@NAME='INETPUB']

Using two backslashes successively indicates that the entire document should
be searched recursively. This is a common misconception for developers used to
UNC notation for working with directory paths. While useful in certain
situations where an element pattern may occur anywhere in the current document,
it is rarely used in this context.

//FOLDER[@NAME="INETPUB"]/FOLDER[@NAME="WWWROOT"]/FOLDER

This notation, while seemingly simple, becomes very complex when dissected.
We begin by searching the entire document for an element called FOLDER
with a child named FOLDER and a grandchild named FOLDER. We
further limit the location paths by specifying the value of the NAME
attribute for each FOLDER element. Finally, we return all matching
grandchild FOLDER elements. This example would return the
FOLDER elements on lines 7 and 21. Note that this is not the same as
parent::FILESYSTEM/DRIVE, where we limit the search to a specified path
and not the entire document.

Attributes and Predicates

We have seen examples of using attributes as predicates, but have not
formally addressed attributes. Attributes can be retrieved using the attribute
axis or by using the abbreviated syntax, an at(@) symbol. Referring to
Listing 3 again, where the context node is represented on line 18, we can
retrieve all attributes where the name of the attribute is LETTER:

attribute::*[name()='LETTER']

This syntax can be abbreviated to specify searching only the LETTER
attribute and no other attributes:

attribute::LETTER

This syntax can be abbreviated further using the at symbol:

@LETTER

XPath Functions

We have mentioned Boolean expressions in the context of predicates, but
let's take a look how we can leverage Boolean expressions in predicates.
There are 29 different XPath functions relating to strings, numbers, node-sets,
and Booleans. Without listing them all here, we will focus on the Boolean
function not(). The not() function returns true if
the argument is false, false if the argument is true. Let's take a
look at what this really means by looking at an example. Here, we will select
ourselves only if we contain an attribute named LETTER.

self::*[@LETTER]

What if we wanted to select ourselves only if we did not contain an
attribute named LETTER? One way is to use the XPath function
not().

self::*[not(@LETTER)]

This statement can be misleading, so let's think about what is really
being queried. It would be easy to misinterpret this statement as " return
the context node's children that are not an attribute named
LETTER." Recall from Table 1 that the self-axis returns the
context node. So, we actually return the DRIVE element if the predicate
matches. The not() function tests to see if a LETTER attribute
is present. If the LETTER attribute is present, the node-test returns
false, and the context node is not selected.

XPath functions cannot be used as statements themselves. For instance, the
following XPath statement is not legal:

not(@LETTER)

This is because the statement must evaluate as a node-set. In other words, we
omitted two parts of the location step: the axis and the node test, we skipped
right to the predicate.

Why Use XPath?

XPath is used in conjunction with XPointer and XSLT for searching documents.
XPath statements can also be used individually using a Document Object Model
(DOM) representation of an XML document. Chapter 6 of XML and ASP.NET,
"Exploring the System.Xml Namespace", delves deeper into
XPath in the .NET Framework and looks at how XPath statements can be issued
using the System.Xml.XPath namespace. Chapter 5 of XML and ASP.NET,
"
MSXML Parser", also shows how to use the MSXML Parser to
query using XPath statements.

This article is an excerpt from the book XML and ASP.NET (ISBN:073571200X), written by Kirk Allen Evans, Ashwin Kamanna, and Joel Mueller, published by New Riders Publishing.

Kirk Allen Evans has been developing applications for over 10 years. His focus remains developing complex, distributed systems using Microsoft technologies. Kirk also developed and maintains vbdna.net, a website devoted to demonstrating techniques for developing distributed systems using Visual Basic.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories