DOM Level 3 XPath

The Saxon API only works with Saxon. The Xalan API only works with Xalan. Both only work with Java. The W3C DOM Working Group is attempting to define a standard, cross-engine XPath API that can be used with many different XPath engines (as of summer 2002, this effort is just beginning and is not yet supported by any implementations ). DOM Level 3 includes an optional XPath module in the org.w3c.dom.xpath package. The feature string "XPath" with the version "3.0" tests for the presence of this module. For example,

 if (!impl.hasFeature("XPath", "3.0")) {   System.err.println    ("This DOM implementation does not support XPath");   return; } 


This section is based on the March 28, 2002, working draft of the Document Object Model (DOM) Level 3 XPath Specification []. The details are still subject to change, however.

The XPath module has two main interfaces, XPathEvaluator and XPathResult . XPathEvaluator , shown in Example 16.6, searches an XML document for the objects identified by an XPath expression, such as /book/chapter/section[ starts-with (@title, 'DOM')] . The XPath expression is passed as a Java String , and the context node is passed as a DOM Node object. The result of evaluating the expression is returned as an XPathResult , a wrapper interface for the four standard XPath data types: node-set, string, boolean, and number.

Example 16.6 The XPathEvaluator Interface
 package org.w3c.dom.xpath; public interface XPathEvaluator {   public XPathResult evaluate(String expression,    Node contextNode, XPathNSResolver resolver, short type,    XPathResult result) throws XPathException, DOMException;   public XPathExpression createExpression(String expression,    XPathNSResolver resolver) throws XPathException, DOMException;   public XPathNSResolver createNSResolver(Node nodeResolver); } 

In DOM implementations that support XPath, the same classes that implement org.w3c.dom.Document implement XPathEvaluator . Thus no special constructor or factory class is required. Just cast the Document object that encapsulates the document you want to query to XPathEvaluator (after checking to make sure that the implementation supports XPath with hasFeature() , of course). For example, in Chapter 5 you saw an XML-RPC server that returned Fibonacci numbers . The documents that server returned looked like this:

 <?xml version="1.0"?>  <methodResponse>   <params>     <param>       <value><double>28657</double></value>     </param>   </params> </methodResponse> 

A client for this program needs to extract the content of the double element, and you can use XPath to simplify this task. There are numerous XPath expressions that will retrieve the relevant node. These include

  • /methodResponse/params/param/value/double

  • /child::methodResponse/child::params/child::param/child::value/ child::double[1]

  • /methodResponse/params/param/value/double[1]

  • //double[1]

  • /descendant::double[1]

Depending on what you intend to do with the node once you have it, you might want to use one of the functions that returns the string value of the node instead. In that case, these expressions would be appropriate:

  • normalize-space(/methodResponse/params/param/value/double)

  • normalize-space(//double[1])

  • string(//double)

  • normalize-space(/methodResponse)

  • normalize-space(/)

These are all absolute expressions that do not depend on the context node, but there are many more depending on what the context node is. For example, if the context node were set to the methodResponse document element, then these relative location paths and function calls would also work:

  • params/param/value/double

  • child::params/child::param/child::value/child::double[1]

  • .//double

  • normalize-space(.//double[1])

  • normalize-space(params)

  • normalize-space(/)

Assuming that the relevant server response has already been parsed into a DOM Document object named response , the following code will extract the desired element into an XPathResult object:

 Document response;  // Initialize response object by parsing request... String query = "/methodResponse/params/param/value/double"; if (impl.hasFeature("XPath", "3.0")) {   XPathEvaluator evaluator = (XPathEvaluator) response;   try {     XPathResult index = evaluator.evaluate(query, response,      null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)     // work with the result...   }   catch (XPathException e) {     System.err.println(query      + " is not a correct XPath expression");   }   catch (DOMException e) {     System.err.println(e);   } } 

What this builds is an XPathResult object, which is one step removed from the string you actually want. The XPathResult interface is a wrapper for the four things an XPath expression might evaluate to (double, string, boolean, or node-set). Getter methods are provided to return the relevant type from the XPathResult . Example 16.7 shows this interface.

Example 16.7 The XPathResult Interface
 package org.w3c.dom.xpath; public interface XPathResult {   public static final short ANY_TYPE                     = 0;   public static final short NUMBER_TYPE                  = 1;   public static final short STRING_TYPE                  = 2;   public static final short BOOLEAN_TYPE                 = 3;   public static final short UNORDERED_NODE_ITERATOR_TYPE = 4;   public static final short ORDERED_NODE_ITERATOR_TYPE   = 5;   public static final short UNORDERED_NODE_SNAPSHOT_TYPE = 6;   public static final short ORDERED_NODE_SNAPSHOT_TYPE   = 7;   public static final short ANY_UNORDERED_NODE_TYPE      = 8;   public static final short FIRST_ORDERED_NODE_TYPE      = 9;   public short   getResultType();   public double  getNumberValue() throws XPathException;   public String  getStringValue() throws XPathException;   public boolean getBooleanValue() throws XPathException;   public Node    getSingleNodeValue() throws XPathException;   public boolean getInvalidIteratorState();   public int     getSnapshotLength() throws XPathException;   public Node    iterateNext()    throws XPathException, DOMException;   public Node    snapshotItem(int index) throws XPathException; } 

Of the four get XXX Value() methods, only one of them will return a sensible result for any given XPath expression. The other three will throw an XPathException with the error code XPathException.TYPE_ERR . The preceding example expected only a single node as a result of evaluating the XPath location path /methodResponse/params/param/value/double . Consequently, the getSingleNodeValue() method can retrieve it:

 Element doubleNode = (Element) index.getSingleNodeValue(); 

That this expression returns a single value is indicated by foreknowledge of the input format, not by anything intrinsic to the XPath expression. If there were more than one double element in the client request, then the location path would find them all.

Now we have an Element node, but what we really need is the complete text of that node, after accounting for possible if unlikely comments, CDATA sections, processing instructions, and other detritus that DOM presents to us. In Chapter 10, I developed a getFullText() utility method to account for this, and I could use it again here. But DOM XPath offers a simpler solution. The getStringValue() method returns the XPath value of the node-set. The XPath value of an element node is defined as the complete text of the node after all character references, entity references, and CDATA sections are resolved and all other markup is stripped. Thus instead of requesting a Node , you can ask for a String :

 String value = index.getStringValue(); 

Or maybe it's not a String you want but a number. In this case, use getNumberValue() , which returns a double :

 double value = index.getNumberValue(); 

The DOM3 XPath methods getStringValue() , getNumberValue() , and getBooleanValue() correspond to the XPath casting functions string() , number() , and boolean() . XPath has a number of other useful functions. For example, normalize-space() first converts its argument to a string as if by the string() function, and then strips all leading and trailing white space and converts all other runs of white space to a single space. With this function, you can use a simpler location path:

 XPathResult index = evaluator.evaluate("normalize-space(/)",   response, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null) String value = index.getStringValue(); 

In the case of an XPath expression that evaluates to a node-set, getSingleNodeValue() returns only the first node in the set. You can invoke iterateNext() and then call getSingleNodeValue() again to get the second node in the set. Repeat this procedure for the third node, the fourth node, and so on until getSingleNodeValue() returns null, indicating that there are no more nodes in the set. If the set is empty to begin with, then getSingleNodeValue() returns null immediately. This is how you would handle a case such as the SOAP response that returns multiple int elements.

Namespace Bindings

Because the SOAP request document uses namespace-qualified elements, we first need to provide some namespace bindings to use when evaluating the XPath expression. The XPathNSResolver interface provides the namespace bindings. Although you can implement this in any convenient class, an instance is normally created by passing a Node with all of the necessary bindings to the createNSResolver() method of the XPathEvaluator interface. For example, this code uses JAXP to build a very simple document whose document element binds the prefix SOAP to the URI and the prefix f to the URI . Then that document element is passed to the XPathEvaluator 's createNSResolver() method to create an XPathNSResolver object that has the same namespace bindings as the synthetic node we created.

 // Load JAXP  DocumentBuilderFactory factory  = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); // Build the document DOMImplementation impl = builder.getDOMImplementation(); Document namespaceHolder = impl.createDocument(  "",  "f:namespaceMapping", null); // Attach the namespace declaration attributes Element root = namespaceHolder.getDocumentElement(); root.setAttributeNS("",  "xmlns:SOAP", ""); root.setAttributeNS("",  "xmlns:f", ""); // Create the resolver XPathNSResolver namespaces = evaluator.createNSResolver(root); 

Now we're ready to repeat the earlier example, but this time using the DOM XPath API instead of the processor-specific Xalan or Saxon APIs. To relieve the tedium, I'm going to make a small shift in the pattern of the readResponse() method. Rather than storing the XPath search string and the namespace bindings in the source code, I'm going to move them to the separate XML document shown in Example 16.8, which can be bundled with the application and is assumed to live at the relative URL config.xml . (A more realistic example might store this document as a resource in the application's JAR archive.)

Example 16.8 An XML Document That Contains Namespace Bindings and an XPath Search Expression
 <?xml version="1.0"?> <config search="/SOAP:Envelope/SOAP:Body/f:Fibonacci_Numbers/f:fibonacci"  xmlns:f=""  xmlns:SOAP="" /> 

The program both reads the XPath expression from the search attribute of the document element and uses that element for the namespace bindings. This enables the XPath string to change independently of the source code.

Following is the configurable, DOM-XPath-based readResponse() method. Because the iterator always returns a DOM node, we have to use a second XPath evaluation on each node to take the element node's string value.

 public static void readResponse(InputStream in)   throws IOException, SAXException, DOMException,  XPathException, ParserConfigurationException {   DocumentBuilderFactory factory    = DocumentBuilderFactory.newInstance();   factory.setNamespaceAware(true);   DocumentBuilder builder = factory.newDocumentBuilder();   // Parse the server response   InputSource data = new InputSource(in);   Node doc = builder.parse(data);   // Check to see that XPath is supported   if (!impl.hasFeature("XPath", "3.0")) {     throw new XPathException(      "Implementation does not support XPath");   }   XPathEvaluator evaluator = (XPathEvaluator) doc;   // Parse the config file   Document config = builder.parse("config.xml");   Element root    = config.getDocumentElement();   String query    = root.getAttributeValue("search");   XPathNSResolver namespaces    = evaluator.createNSResolver(root);   // Evaluate the expression   XPathResult nodes = evaluator.evaluate(    "/SOAP:Envelope/SOAP:Body/f:Fibonacci_Numbers/f:fibonacci",    doc, namespaces, XPathResult.ORDERED_NODE_ITERATOR_TYPE,    null);   // work with the result...   Node next;   while (next = nodes.iterateNext()) {     XPathResult stringValue = evaluator.evaluate("string()",      next, namespaces, XPathResult.STRING_TYPE, null);     System.out.println(stringValue.getStringValue());   } } 


Iterators like this one are good only for a single pass. You cannot reuse them or back up in them. Furthermore, if the Document object over which the iterator is traversing changes before you have finished with the iterator (for example, if a node in the iterator is deleted from the Document object), then iterateNext() throws a DOMException with the code INVALID_STATE_ERR.

To hold on to a more stable list that can be reused and survives document edits, request a snapshot of the node-set to be returned rather than an iterator. A snapshot is reusable and features random access through indexing. For example, using a snapshot the above code would become

 // Evaluate the expression    XPathResult nodes = evaluator.evaluate(    "/SOAP:Envelope/SOAP:Body/f:Fibonacci_Numbers/f:fibonacci",    doc, namespaces, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,    null);   for (int i = 0; i < nodes.getSnapshotLength(); i++) {     Node next = nodes.snapshotItem(i);     XPathResult stringValue = evaluator.evaluate("string()",      next, namespaces, XPathResult.STRING_TYPE, null);     System.out.println(stringValue.getStringValue());   } } 

Of course, snapshots have the opposite problem: there is no guarantee that the nodes in the snapshot reflect the current state of the Document .

Compiled Expressions

An XPath engine that implements the DOM XPath API may need to compile the expression into some internal form rather than simply keeping it as a generic String . The XPathExpression interface, shown in Example 16.9, represents such a compiled expression.

Example 16.9 The DOM3 XPathExpression Interface
 package org.w3c.dom.xpath; public interface XPathExpression {   public XPathResult evaluate(Node contextNode, short type,    XPathResult result) throws XPathException, DOMException; } 

You can use the createExpression() method in the XPathEvaluator interface to compile a String into an XPathExpression :

 public XPathExpression  createExpression  (String  expression,  XPathNSResolver  resolver  ) throws XPathException 

Then you can repeatedly invoke the same expression on different documents without needing to compile the XPath expression from a string each time. For example,

 XPathExpression expression = evaluator.createExpression(  "/SOAP:Envelope/SOAP:Body/f:Fibonacci_Numbers/f:fibonacci",  namespaces);  XPathResult nodes = expression.evaluate(doc, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 

This isn't very important for an expression that's only going to be used once or twice, but in a program that will process many documents in the same way, it can be a significant savings. Imagine, for example, an XML-RPC or SOAP server that receives thousands of requests per hour and needs to apply the same XPath expression to each request document. The exact speed that you'll gain by compiling your expressions will of course vary from implementation to implementation.

Processing XML with Java. A Guide to SAX, DOM, JDOM, JAXP, and TrAX
Processing XML with Javaв„ў: A Guide to SAX, DOM, JDOM, JAXP, and TrAX
ISBN: 0201771861
EAN: 2147483647
Year: 2001
Pages: 191

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: