| [ directory ] |
|
7.1 XPathThe 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]
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]
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 XPathLet's look at the details of XPath. In this section, we give an overview of the syntax and semantics of XPath. Overview of the SyntaxUNIX 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
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
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.
Objects and TypesThe result of evaluating an XPath is called an object. An object has one of the following primitive types:
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.
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 NamespacesTo 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.
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]
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 JavaIn 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]
A Simple ExampleThe 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 TypesIn 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:
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 ] |
|