XPath

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.

Nodes

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

/Name

/inventory/animal

All animal nodes under /inventory.

//Name

//animal

All animal nodes anywhere in document. A foodRecipe/animal would also match.

Name/*

/inventory/*

All child nodes of inventory (animals and any other elements directly under inventory).

@Name

//animal/@animalClass

All animalClass attributes of animals.

.

/inventory/animal/.

The current node (all animals).

..

/inventory/animal/..

The parent node (inventory).

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

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

[n]

/inventory/animal[1]

Select the nth element of a set. (Starts with 1 rather than 0.) For example, select the first animal in the inventory.

[@name=value]

//animal[@animalClass="mammal"]

Match nodes with the specified attribute value. For example, animals with the animalClass attribute "mammal".

[element=value]

//animal[name="Cocoa"]

Match nodes with a child node whose text value is specified. For example, match the animal with a name element containing the simple text "Cocoa".

=!=><

//animal[weight > 400]

Predicates may also test for inequality and numeric greater-/lesser-than value.

and, or

//animal[@animalClass= "mammal" or @class="reptile"]]

Predicates may use logical AND and OR to test. For example, animals whose animalClass is mammal or reptile.

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.

Functions

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.

The XPath API

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.

XPath xpath = XPathFactory.newInstance().newXPath();
InputSource source = new InputSource( filename );
         
String result = 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.

NodeList elements = (NodeList)xpath.evaluate(
    expression, inputSource, XPathConstants.NODESET );

Next, let’s put this together in a useful example.

XMLGrep

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:

    import org.w3c.dom.*;
    import org.xml.sax.InputSource;
    import javax.xml.xpath.*;
    import javax.xml.transform.*;
    import javax.xml.transform.dom.DOMSource;
    import javax.xml.transform.stream.StreamResult;
     
    public class XMLGrep {
     
        public static void printXML( Element element )
            throws TransformerException {
            
            Transformer transformer =
                TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty( OutputKeys.OMIT_XML_DECLARATION,
                "yes" );
            Source source = new DOMSource( element );
            Result output = new StreamResult( System.out );
            transformer.transform( source, output );
            System.out.println();
        }
         
        public static void main( String [] args ) throws Exception {
            if ( args.length != 2 ) {
                System.out.println( "usage: PrintXPath expression file.xml" );
                System.exit(1);
            }
            String expression = args[0], filename = args[1];
             
            XPath xpath = XPathFactory.newInstance().newXPath();
            InputSource inputSource = new InputSource( filename );
             
            NodeList elements = (NodeList)xpath.evaluate(
            expression, inputSource, XPathConstants.NODESET );
             
            for( int i=0; i<elements.getLength(); i++ )
                if ( elements.item(i) instanceof Element ) {
                    printXML( (Element)elements.item(i) );
                } else
                    System.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:

% java XMLGrep "//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.

Get Learning Java, 4th Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.