站内搜索: 请输入搜索关键词
当前页面: 图书首页 > XML and Java: Developing Web Applications, Second Edition

XML and Java: Developing Web Applications, Second Edition

[ directory ] Previous Section Next Section

7.1 XPath

The hyperlink concept of HTML made a significant impact on the Internet. People created a huge number of hyperlinks from their HTML files to others, even to those they did not know. It worked unexpectedly well. People finally created the largest distributed database ever seen. It is now called the Web.

XML was designed for the Web by nature. It has a hyperlink concept to point to another XML document. A URL is useful to specify the physical location of an XML document the way HTML does; however, it is not sufficient to specify the location of elements or attributes in an XML document. For example, if you want to specify a link to select Section 7.1 of this book, how can you do it?[1]

[1] The source of this book is written as an XML document.

The answer is XPath. XPath provides a mechanism for specifying part of an XML document.

7.1.1 What Is XPath?

XPath is a W3C Recommendation used to specify part of an XML document. It also has useful functions to handle various data types, such as strings, numbers, and booleans. Unlike other XML-related languages (for example, XML Schema), XPath does not have a syntax defined by XML. Instead, XPath can be written in a very short form in a single line. You can embed an XPath in a Uniform Resource Identifier (URI) or an attribute in an XML document thanks to the design of XPath.[2]

[2] Embedding XPath into a URI is an extension to XPath defined by XPointer, which is based on XPath.

XPath does not handle an XML document as a string form but as an abstract tree generated by an XML parser. XPath can specify a set of nodes (a node set) in an XML document, in which element nodes, attribute nodes, and text nodes are included. Thanks to this design decision, XPath is easy for humans to understand. Also, there is a possibility that we can formalize the nature of XPath from a mathematical point of view.

Even though XPath can be used as an independent tool, it was originally designed for use in another language, such as XSLT and XPointer. XSLT, described in Section 7.2, uses XPath not only to specify a node set in a target XML document but also to check whether a specified node set is empty or not. XPointer is built on top of XPath and defines some extensions to XPath. For example, XPointer defines the way to embed XPath into URI references. We do not describe XPointer in this book, however.

7.1.2 Syntax and Semantics of XPath

Let's look at the details of XPath. In this section, we give an overview of the syntax and semantics of XPath.

Overview of the Syntax

UNIX and Windows have the notion of a file path to specify a file or a directory in a tree-structured file system. XPath also defines path notations to specify a node set in an XML document the way the file system does. Paths in XPath are called location paths. A parent element and its child element are separated using a slash (/) as in the UNIX file system. For example, the following XPath selects Signature elements of child elements of Header elements of child elements of the document element Envelope in an XML document.

/Envelope/Header/Signature

Each element separated by a slash is called a location step in XPath. In this example, Envelope, Header, and Signature are location steps.

There is a difference between a file path and an XPath. An XPath specifies not a single node but a node set. Even if an XPath selects only one node, the result is regarded as a set that has only one node. Similarly, if an XPath does not select any node, the result is regarded as an empty set. The previous example may not uniquely specify an element; that is, there may be more than one Signature element under a Header element. In such a case, XPath selects a set of Signature elements.

You can specify a file path in two ways: as an absolute path and as a relative path. Similarly, an XPath can be written in either an absolute or a relative way, as an absolute location path or as a relative location path. The origin node of an absolute location path is the document root of an XML document. The origin node of a relative location path is called the context node, just like the concept of the current working directory in UNIX and Windows. The context node is determined when an XPath is evaluated.

XPath provides an abbreviated notation for convenience in addition to a general notation for exactness. The previous XPath example is an abbreviated notation of the following general notation:

/child::Envelope/child::Header/child::Signature

The general notation of an XPath explicitly specifies the relationship between the context node and the nodes selected by the location step by using an axis, such as child::element-name and attribute::attribute-name.

Figure 7.1 illustrates what axes mean for a given context node. The figure shows a subtree of an XML document. Here, the central node is the context node, and the arrows represent axes referring to the other nodes. For example, the parent axis refers to the parent node of the context node.

Figure 7.1. Axes and a context node in XPath

graphics/07fig01.gif

Figure 7.2 shows more details of axes. Note that we still omit some axes defined by the XPath specification. There is an ordering in XPath called document order. The document order is the order in which the start tag of each node appears in the literal XML document. The axes are defined based on the document order. For example, the preceding axis selects all the nodes appearing before the context node in the document order.

Figure 7.2. Typical axes in XPath

graphics/07fig02.gif

The abbreviation notations, such as element-name and @attribute-name, correspond to the general notations we mentioned earlier梩hat is, child::element-name and attribute::attribute-name. Both a general notation and its corresponding abbreviated notation have the same meaning.

Table 7.1 shows typical examples that are often used in XPath programming. The description of the detailed syntax of XPath is beyond the scope of this book. For more information, refer to the XPath specification.

Table 7.1. XPath Examples

XPATH EXAMPLE

ABBREVIATION

DESCRIPTION

/

/

Selects the document root. Note that this is not the document element

child::person

person

Selects the person element(s) among child elements under the context node.

child:person | /

person | /

Selects person element(s) or the document root. "|" stands for a union (logical OR).

/child::Address Book

/AddressBook

Selects the AddressBook element under the document root. That is, AddressBook is selected if it is the document element; otherwise, no element is selected.

child::*

*

Selects all the child elements under the context node. Note that this does not select any text nodes or attributes.

child::text()

text()

Selects all the text nodes under the context node. Note that if text nodes appear before and after an element, they are not concatenated but are selected as a set of text nodes.

attribute::type

@type

Selects the type attribute of the context node.

/descendant-or-self::node()/child::email

//email

Selects all the email elements in a document. Note that // is short for /descendant-or-self::node()/. The node () function selects all nodes, whatever their node type.

child::person/child::email[position()=1]

person/email[1]

Selects the first email element under all the person child elements in a context node. You can specify additional (or detail) conditions in "[ ]." This part is called the predicate in XPath.

child::person [child::email and attribute:: id="12345"]

person[email and@id="12345"]

Selects all the person elements in a context node that have at least one email element and an id attribute with the value 12345

child::name[attribute:: family="neyama"] [position()= last()]

name[@family="neyama"][last()]

Selects the last name element in a context node that has a family attribute and the value neyama

Objects and Types

The result of evaluating an XPath is called an object. An object has one of the following primitive types:

  • Node-set? an unordered collection of nodes without duplications

  • String? a sequence of characters

  • Boolean? true or false

  • Number? a floating-point number

Note that the tree representing a target XML document is processed as an ordered tree, but a node-set as a result object is unordered by definition.

XPath provides various functions that are used in location steps. The functions are categorized in four groups corresponding to the primitive types. Table 7.2 shows typical functions in XPath.

Table 7.2. Typical Functions in XPath

FUNCTION GROUP

CONVERSION FUNCTION

FUNCTIONS

DESCRIPTION

Node-set

None

number last()

Returns the number of the selected nodes (context size).

   

number position()

Returns the index of the selected node (context position).

   

number count(node-set)

Returns the number of nodes in the argument node-set.

String

string string(object?)

string local-name(node-set?)

Returns the local part of the node. The first node in the argument node-set in document order is used as the target node.

   

string namespace-uri(node-set?)

Returns the namespace URI of the node. The first node in the argument node-set in document order is used as the target node.

   

string concat(string, string, string*)

Returns the concatenation of its arguments.

Boolean

boolean boolean(object)

boolean starts-with(string, string)

Checks that the first argument string starts with the second argument string

   

boolean contains (string, string)

Checks that the first argument string contains the second argument string.

   

number string-length (string?)

Returns the length of the argument.

   

string normalize-space(string?)

Normalizes the whitespace in the argument string by stripping leading and trailing whitespace characters and by replacing a sequence of white space characters with a single space.

   

boolean not(boolean)

Negates the argument boolean.

   

boolean true()

Always returns true.

   

boolean false()

Always returns false.

Number

number number(object?)

number sum(node-set)

Returns the sum of the argument node-converted to a number through its string representation.

In general, a function in a function group takes the arguments of the type that corresponds to the primitive type of the group梖or example, boolean type for the boolean function group. If a function is called with arguments of types that do not match the function, the arguments are converted to the appropriate types by calling the conversion functions implicitly. For example, a nonempty node-set is converted to true of boolean type by the boolean() function. In this sense, the conversion functions are regarded as defining the conversion rules in XPath. Note that the conversion from string, boolean, and number to node-set is not supported.

7.1.3 XPath and Namespaces

To make the explanation easy in Section 7.1.2, we intentionally did not cover an important relationship between XPath and XML Namespaces (hereafter referred to as "namespaces"). Namespaces are becoming very important and are already used widely in many XML documents. For example, every element must be qualified by a namespace in a SOAP message (described in Chapter 12). In this section, we describe how to specify an XPath if the target XML document uses namespaces.

XPath supports namespaces if an XML processor used with an XPath processor supports namespaces. If the XML processor does not support namespaces, we do not need to care about the issues described in this section.[3] Hereafter, we assume that namespaces are supported.

[3] If the processor does not support namespaces, it recognizes the SOAP-ENV:Envelope as a whole as an element name because it does not recognize the SOAP-ENV as a namespace prefix. Therefore, this works only when a target document always uses the same namespace prefixes as specified in an XPath.

The following example, shown previously in Section 7.1.2, specifies elements with an unqualified namespace.

/Envelope/Header/Signature

How can we specify namespace-qualified elements? The simplest way to do so is as follows:

/SOAP-ENV:Envelope/SOAP-ENV:Header/dsig:Signature

This example tries to select the Signature element qualified by the dsig namespace prefix by navigating first an Envelope element and then a Header element qualified by the SOAP-ENV namespace prefix from the document root.

The previous XPath works only when a target document always uses the same namespace prefixes (SOAP-ENV and dsig in this example). However, namespace prefixes are not always the same in general, and we should not assume it. Therefore, we do not recommend this kind of fixed prefix use for a general XPath notation.[4]

[4] This works if an XPath is used in an XSLT stylesheet. As described in Section 7.2, namespaces declared in an XSLT stylesheet affect the evaluation of XPaths.

As described in the next section, Xalan provides an XPath API that resolves application-specific namespaces by creating an instance of the NamespaceResolver interface. This is useful but not sufficient because an application must provide a fixed set of namespace prefixes beforehand.

Furthermore, this simplest way has a problem in that the namespace scope depends on the context node. For example, as shown in the following example, the scope of the dsig namespace is limited under the Signature element. If the context node is the SOAP-ENV:Envelope element, the previous XPath cannot select the Signature element.

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://...">
  <SOAP-ENV:Header>
    <dsig:Signature xmlns:dsig="http://...">
    ... omitted ...
    </dsig:Signature>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
  ... omitted ...
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This causes an XPath parsing error because the prefix, dsig, cannot be resolved within the scope of the SOAP-ENV:Envelope element. It works only if the dsig prefix is declared within the scope of the SOAP-ENV:Envelope element.

Finally, we show the correct use of an XPath that can handle namespaces without fixing namespace prefixes. For example, if you want to select all the Signature elements associated with the namespace http://www.w3.org/2000/09/xmldsig# (in fact, these are the Signature elements in the XML Digital Signature specification), you can specify an XPath as follows:

//*[namespace-uri()="http://www.w3.org/2000/09/xmldsig#" and
    local-name()="Signature"]

Note that the following two XPaths do not select the Signature elements as expected. These are typical mistakes that many people tend to make.

//Signature
//Signature[namespace-uri()="http://www.w3.org/2000/09/xmldsig#"]

In the first XPath, no elements will be selected because it selects all the Signature elements that are namespace-unqualified, but the Signature element of the XML Signature is namespace-qualified. In the second XPath, no elements will be selected because it selects all the Signature elements that are not only namespace-unqualified but also namespace-qualified by http://www.w3.org/2000/09/xmldsig#. Of course, no element can be both namespace-qualified and namespace-unqualified.

We want to emphasize again that once the namespace is introduced in an XML document, the simple notation of an XPath explained in Section 7.1.2 (for example, separating element names by a "/") is no longer valid in general. You should use the complicated notation just described.

7.1.4 XPath Programming in Java

In this section, we explain how to write a Java program using XPath. We use Apache Xalan-Java 2.1.0 (hereafter referred to as Xalan) as an XPath processor. Xalan is a popular XPath processor from the Apache Software Foundation.[5]

[5] The API of Xalan version 2.x has been drastically changed from that of Xalan version 1.x. Therefore, a program using the Xalan 1.x API will not work with Xalan 2.x.

A Simple Example

The source code for an example, XPathTest, is shown in Listing 7.1. This program outputs the selected nodes and their appearance count by applying the XPath given as the second argument to the target XML document given as the first argument.

Listing 7.1 A simple Java program using XPath, chap07/XPathTest.java
package chap07;

// For JAXP
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.xml.sax.InputSource;

// For Xalan XPath API
import org.apache.xpath.XPathAPI;
import org.w3c.dom.traversal.NodeIterator;
import org.w3c.dom.Node;
import org.w3c.dom.Document;
      public class XPathTest {
         public static void main(String[] args) throws Exception {
[16]        String xmlFilePath = args[0];
[17]        String xPath = args[1];
[18]
[19]        System.out.println("Input XML File: " + xmlFilePath);
[20]        System.out.println("XPath: " + xPath);
[21]
[22]        DocumentBuilderFactory factory =
[23]           DocumentBuilderFactory.newInstance();
[24]        factory.setNamespaceAware(true);
[25]        DocumentBuilder parser = factory.newDocumentBuilder();
[26]
[27]        InputSource in =
[28]           new InputSource(xmlFilePath);
            Document doc = parser.parse(in);
[30]
[31]        Node contextNode = doc.getDocumentElement();
[32]        NodeIterator i =
[33]           XPathAPI.selectNodeIterator(contextNode, xPath);
[34]        int count = 0;
[35]        Node node;
[36]        // For each node
[37]        while ((node = i.nextNode()) != null) {
[38]            // Outputs the node to System.out
[39]            System.out.println(node.toString());
[40]            count++;
[41]        }
            System.out.println("" + count + " match(es)");
         }
      }

The first half of the program (lines 17?0) creates a DOM tree from the target XML document by using an XML parser, and the last half (lines 32?3) invokes the XPath API provided by Xalan.

The most important part of this program is XPathAPI.selectNodeIterator (Node, String) (line 34). This method takes two arguments: a node as the beginning context node and an XPath string. The result has the type NodeIterator, which is a collection of the selected nodes. Each invocation of nextNode() returns the next selected element in the result list. This program simply outputs the selected nodes by using the toString() method. You can replace this portion with application-specific code as appropriate.

Let's run XPathTest using the sample XML document shown in Listing 7.2.

Listing 7.2 A sample XML document for XPathTest, chap07/data/sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<W3Cspecs xmlns="http://www.example.com/xmlbook2/chap07/">
  <spec title="XML Path Language (XPath) Version 1.0"
        url="http://www.w3.org/TR/xpath">
    <date type="REC">16 November 1999</date>
    <editors>
      <editor>
        <name>James Clark</name>
        <email>jjc@jclark.com</email>
      </editor>
      <editor>
        <name>Steve DeRose</name>
        <email>Steven_DeRose@Brown.edu</email>
      </editor>
    </editors>
  </spec>
  <spec title="XSL Transformations (XSLT) Version 1.0"
        url="http://www.w3.org/TR/xslt">
    <date type="REC">16 November 1999</date>
    <editors>
      <editor>
        <name>James Clark</name>
        <email>jjc@jclark.com</email>
      </editor>
    </editors>
  </spec>
</W3Cspecs>

To run the program, type the following command:

R:\samples\>java chap07.XPathTest
  file:./chap07/data/sample.xml "//*[local-name()='email']/text()"

In this example, the XPath selects all text nodes whose parent node's local name is email. In short, it collects all e-mail addresses in the XML document. The result appears as follows:

[#text: jjc@jclark.com]
[#text: Steven_DeRose@Brown.edu]
[#text: jjc@jclark.com]
3 match(es)

Three e-mail addresses are found.

Handling Objects and Types

In Section 7.1.2, we explained that the result of evaluating an XPath is an object and that objects have these primitive types: node-set, string, boolean, and number. If you just want to select a set of DOM nodes with an XPath, the XPathAPI.selectNodeIterator() method, whose return type is the Node Iterator class, is powerful enough. Unfortunately, the NodeIterator class does not represent objects in XPath exactly. In other words, it does not contain XPath type information.

Type information provides the polymorphism for the objects. We can convert an object that has a specific type to another object that has another type according to the conversion rule defined in XPath. This functionality is useful when we reuse the semantics of XPath in applications of XPath. One of the most famous applications of XPath is XSLT, which is described in Section 7.2. In XSLT, XPath is used to test whether the specified nodes actually exist in the input XML document. In this case, XSLT reuses the result from converting the selected node-set to boolean梩hat is, if the XPath selects a nonempty node-set, the result is true; otherwise, it is false.

Accordingly, when we need to exactly handle objects and types as the result of evaluating an XPath, the XPathAPI.selectNodeIterator() method is not sufficient. Xalan provides an API for handling objects and types: the XPathAPI.eval() method. The method returns the instances of the following classes:

  • org.apache.xpath.objects.XObject

  • org.apache.xpath.objects.XNodeSet

  • org.apache.xpath.objects.XString

  • org.apache.xpath.objects.XBoolean

  • org.apache.xpath.objects.XNumber

XObject is a common superclass for the other four classes corresponding to each type in XPath: node-set, string, boolean, and number. You can get an XObject object as a result of evaluating an XPath by calling the XPathAPI.eval() method. It is actually an instance of the subclasses of the XObject class: XNodeSet, XString, XBoolean, and XNumber.

Listing 7.3 shows part of XObjectTest.java, which is basically a variation of XPathTest.java. XObjectTest calls the XPathAPI.eval() method instead of the XPathAPI.selectNodeIterator() method. The Java object returned by the method, xobject, has the bool() method, which converts the object into boolean. The program finally prints the result of whether matching nodes are found.

Listing 7.3 Part of the program chap07/XObjectTest.java
[31]       XObject xobject = XPathAPI.eval(contextNode, xPath);
[32]       boolean match = xobject.bool();
[33]       System.out.println("match found?: " + match);

For more details about these classes, please consult the Xalan API document.

In this section, we introduced XPath and its programming in Java. We hope you understand the powerful and useful capability of XPath. You will see another use of XPath in Chapter 11, Section 11.5.2, in retrieving XML documents stored into relational databases. You can write a program with XPath much more simply than with DOM or SAX. Also with XPath, you can easily and flexibly protect your program against small changes in the target XML document structure (DTD) or node selection logic. You can just modify the XPath in that case, while you need to modify the application logic when you are using DOM or SAX.

In Section 7.2, we introduce XSLT. Then we compare the pros and cons of DOM, SAX, XPath, and XSLT in Section 7.3 and discuss which one is better for what type of application.

    [ directory ] Previous Section Next Section