XPath is an expression language for addressing parts of an XML
document. You can think of XPath expressions as sort of like regular
expressions for XML. They let you pull out parts of an XML document based
on patterns. In the case of XPath, the patterns are more concerned with
structural information than with character content and the values returned
may be either simple text or “live” DOM nodes. With XPath, we can query an
XML document for all of the elements with a certain name or in a certain
parent-child relationship. We can also apply fairly sophisticated tests or
predicates to the nodes, which allows us to construct
complex queries such as this one: give me all of the Animals with a Weight greater than the number 400 and a
Temperament of irritable whose animalClass attribute is mammal.
The full XPath specification has many features and includes both a compact and more verbose syntax. We won’t try to cover it all here, but the basics are easy and it’s important to know them because XPath expressions are at the core of XSL transformations and other APIs that refer to parts of XML documents. The full specification does not make great bedtime reading, but can be found at http://www.w3.org/TR/xpath.
An XPath expression addresses a Node in an XML document tree. The node may be
an element (possibly with children) like <animal>...</animal> or it may be
a lower-level document node representing an attribute (e.g., animalClass="mammal"), a CDATA block, or even
a comment. All of the structure of an XML document is accessible through
the XPath syntax. Once we’ve addressed the node, we can either reduce
the content to a text string (as we might with a simple text content
element like name) or we can access
it as a proper DOM tree to further read or manipulate it.
Table 24-2 shows the most basic node-related syntax.
Table 24-2. Basic node-related syntax
Syntax | Example | Description |
|---|---|---|
| All animal nodes under | |
| | All animal nodes anywhere in document.
A |
| All child nodes of inventory ( | |
| All | |
| The current node (all | |
| | The parent node ( |
Nodes are addressed with a slash-separated path based on name. For
example, /Inventory/Animal refers to
the set of all Animal nodes under the
Inventory node. If we want to list
the names of all Animals, we would
use /Inventory/Animal/Name. The
// syntax matches a node anywhere in
a document, at any level of nesting, so //Name would match the name elements of
Animals, FoodRecipes, and possibly many other elements.
We could be more specific, using //Animal/Name to match only Name elements whose parent is an Animal element. The at sign (@) matches
attributes. This becomes much more useful with predicates, which we
describe next. Finally, the familiar . and ..
notation can be used to “move” relative to a node; read on to see how
this is used.
Predicates let us apply a test to a node. Nodes that pass the test are included in the result set or used to select other nodes (child or parent) relative to them. There are many types of tests available in XPath. Table 24-3 lists a few examples.
Table 24-3. Predicates
Syntax | Example | Description |
|---|---|---|
| Select the nth
element of a set. (Starts with 1 rather than 0.) For example,
select the first | |
| Match nodes with the specified
attribute value. For example, | |
| | Match nodes with a child node whose
text value is specified. For example, match the |
| Predicates may also test for inequality and numeric greater-/lesser-than value. | |
| Predicates may use logical AND and OR
to test. For example, |
Predicates can be compounded (AND’ed) using this syntax or simply by adding more predicates, like so:
//animal[@animalClass="mammal"][weight > 400]Here, we’ve asked for animals
with a class attribute of "mammal"
and a weight element containing a
number greater than 400.
We can now also see the usefulness of the .. operator. Suppose we want to find all of
the animals with a foodRecipe that uses Fruit as an ingredient:
//animal/foodRecipe[ingredient="Fruit"]/..The .. means that instead of
returning the matching foodRecipe
node itself, we return its parent—the animal element. The . (current node) operator is useful in other
cases where we use XPath functions to manipulate values in more refined
ways. We’ll say a few words about functions next.
The XPath specification includes not only the basic node traversal and predicate syntax we’ve shown, but also the ability to invoke more open-ended functions that operate on nodes and the node context. These XPath functions cover a wide range of duties and we’ll just give a couple of examples here. The functions fall into a few general categories.
Some functions select node types other than an element. For
example, there is no special syntax for selecting an XML comment.
Instead you invoke a special method called comment(), like
this:
/inventory/comment()
This expression returns any XML comment nodes that are children of
the inventory element. XPath also
offers functions that duplicate all of the (compact) syntax we’ve
discussed, including methods like child() and parent() (corresponding
to . and ..).
Other functions look at the context of nodes—for example,
last() and count().
/inventory/animal[last()]
This expression selects the last animal child element of inventory in the same way that [n] selects the
nth.
//foodRecipe[count(ingredient)>2]This expression matches all of the foodRecipe elements with more than two
ingredients. (Cool, eh?)
Finally, there are many string-related functions. Some are useful
for simple tests, but others are really useful only in the context of
XSL, where they help out the language (in an awkward way) with basic
formatting and string manipulation. For example, the contains() and
starts-with() methods
can be used to look at the text values inside XML documents:
//animal[starts-with(name,"S")]This expression matches animals
whose name starts with the character
S (e.g., Song Fang). The contains() method, similarly, can be used to
look for a substring in text.
Now that we’ve got a taste for the syntax, let’s look at
how to use the API. The procedure is similar to that of the Java regular
expression API for strings. We use a factory to create an XPath object.
We can then either evaluate expressions with it or “compile” an
expression down to an XPathExpression for
better performance if we’re going to use it more than once.
XPathxpath=XPathFactory.newInstance().newXPath();InputSourcesource=newInputSource(filename);Stringresult=xpath.evaluate("//animal/name",source);// Song Fang
Here we’ve used the simplest form of the evaluate() method,
which returns only the first match and takes the value as a string. This
method is useful for pulling simple text values from elements. However,
if we want the full set of values (e.g., the names of all the animals matched by this expression), we need
to return the results as a set of Node objects instead.
The return type of (the overloaded forms of) evaluate() is controlled by identifiers of the
XPathConstants class.
We can get the result as one of the following: STRING, BOOLEAN, NUMBER, NODE, or NODESET. The default is STRING, which strips out child element tags
and returns just the text of the matching nodes. BOOLEAN and NUMBER are conveniences for getting primitive
types. NODE and NODESET return org.w3c.dom.Node and NodeList objects, respectively. We need the
NodeList to get all the
values.
NodeListelements=(NodeList)xpath.evaluate(expression,inputSource,XPathConstants.NODESET);
This simple example can be used as a command-line utility,
such as grep, for testing XPath expressions
against a file. It applies an XPath expression and then prints the
resulting elements as XML text using the same technique we used in our
PrintDOM example. Nodes that are not
elements (e.g., attributes, comments, and so on) are simply printed with
their toString() method, which
normally serves well enough to identify them, but you can expand the
example to your taste. Here it is:
importorg.w3c.dom.*;importorg.xml.sax.InputSource;importjavax.xml.xpath.*;importjavax.xml.transform.*;importjavax.xml.transform.dom.DOMSource;importjavax.xml.transform.stream.StreamResult;publicclassXMLGrep{publicstaticvoidprintXML(Elementelement)throwsTransformerException{Transformertransformer=TransformerFactory.newInstance().newTransformer();transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");Sourcesource=newDOMSource(element);Resultoutput=newStreamResult(System.out);transformer.transform(source,output);System.out.println();}publicstaticvoidmain(String[]args)throwsException{if(args.length!=2){System.out.println("usage: PrintXPath expression file.xml");System.exit(1);}Stringexpression=args[0],filename=args[1];XPathxpath=XPathFactory.newInstance().newXPath();InputSourceinputSource=newInputSource(filename);NodeListelements=(NodeList)xpath.evaluate(expression,inputSource,XPathConstants.NODESET);for(inti=0;i<elements.getLength();i++)if(elements.item(i)instanceofElement){printXML((Element)elements.item(i));}elseSystem.out.println(elements.item(i));}}
There are again a lot of imports in this example. The transform code in
our printXML() method is drawn from
the PrintDOM example with one
addition. We’ve set a property on the transformer to omit the standard
XML declaration that would normally be output for us at the head of our
document. Since we may print more than one (root) element, the output is
not well formed XML anyway.
Run the example by passing an XPath expression and the name of an XML file as arguments:
%javaXMLGrep"//animal[starts-with(name,'C')]"zooinventory.xml
This example really is useful for trying out XPath. Please give it a whirl. Mastering these expressions (and learning more) will give you great power over XML documents and, again, form the basis for learning about XSL transformations.