Standard XPath API for Java
Oversight? I suppose. But XPath has long been a standard way of querying
XML documents and is an integral part of XSL, however, for all of the
interface work for having a standard way to parse XML documents from Java
(without worrying about the actual XML parser available), there hasn't been
a standard way to issue XPath expressions without doing a parser-specific
implementation.
A Brief History of XPath and Java
I guess a little history may be in order. Once upon a time, Sun and the JCP
community decided that given all of the competing implementations of APIs
that parse XML documents, there should be a standard interface to all of
these disparaging parser variations.
So, in order to parse an XML document into a DOM, you first instantiate an
interface into a parser (and it doesn't matter which one), via the factory
pattern, as in this code example:
DocumentBuilderFactory docfactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder docbuilder =
docfactory.newDocumentBuilder();
With a DocumentBuilder
available, you can parse an XML document supplied
via an java.io.InputStream
like:
Document docroot = docbuilder.parse(filestream);
However, traversing a DOM using the getter methods of the Document/Node
API is cumbersome at best. Often, what you want, is to jump to a
particular node. Well, that is what XPath does. It allows you to specify an
"expression" that specifies one or more nodes, and the result is either a
Node
or NodeList
.
However, there is not standard Java way for doing this. Keep in mind,
Standard Java does specify how to transform a document using XSL, and XSL
requires XPath support to work, but there is not Java API for it. Each XML
parser has supplied their own variations on this theme.
For instance, Oracle's XDK (xmlparserv2) has the following interface:
Node nodeResult =
docroot.selectSingleNode( xpathExpression );
// Or to get multiple nodes:
NodeList nodeResults =
docroot.selectNodes( xpathExpression );
Apache's Xalan/Xerces XML parser, on the other hand does this:
import org.apache.xpath.XPathAPI;
Node nodeResults =
selectSingleNode ( docroot, xpathExpression );
// Or to get multiple nodes:
NodeList nodeResults =
selectNodeList ( docroot, xpathExpression );
// It also has a node iterator:
NodeIterator nodeResultSet =
selectNodeIterator ( docroot, xpathExpression );
Yes, the are both similar, but unique enough that you have to code for a
particular library. Not a great choice given the goals of creating the
general Java-to-XML interface.
XPath and Java 5
The new JAXP API associated with Java 5 now has added an XPath interface,
and I'm sure that all of the XML parsers will soon be compliant …
assuming you've upgraded to this version of the JDK. According to the
online API documentation, all you need to do is:
XPath xpath = XPathFactory.newInstance().newXPath();
Node resultNode =
(Node) xpath.evaluate (xpathExpression, docroot,
XPathConstants.NODE);
The two most fascinating parts of this new change is the fact that instead
of having two (or more) separate methods for getting a single node or a
multiple nodes, you pass in a XPathConstants
value to specify what you
want.
The nifty-swell aspect of this, is that instead of just asking for one or
more nodes, you can now ask for a boolean, numeric value and a string…
and the results is what you would expect.
If you ask for a boolean, the boolean that is returned is true if the XPath
expression match something or not. The numeric value tries to coerce the
results into a double
. The string is the value of of the node (which for
everything but an Element, turns out to be a call to Node.getValue()
.
However, if the results is an Element, then asking for a string, gets the
value of the child text nodes!
For instance, given the following XML document:
<a>
<b>
Howard Abrams
</b>
</a>
If you were to use the XPath expression, //b
to get that "b" element, you
could either issue a call to getName()
which would have returned "b" (not
very useful, since you knew that), and a call to getValue()
would have
returned null
.
However, with this new XPath Java API, specifying the following:
String result =
(String) xpath.evaluate("//b", docroot,
XPathConstants.STRING);
Would have returned "Howard Abrams" … which is what all of us
non-XML-purists would have expected. Yes, it does make the work I did on my
XML Query Java Bean somewhat less important.
XPath and Performance
If you were planning on getting a large amount of data from an XML
document, I wouldn't suggest a series of XPath expression queries… you
should probably parse it with SAX and store all the information you want.
However, let me bring out two interesting points about this new API:
First, XPath expressions can be compiled into an internal format and
reused. For instance:
String xexpr1 = "//b";
XPathExpression xpath1 =
xpathprocessor.compile ( xexpr1 );
. . .
NodeList nodes =
(NodeList) xpath1.evaluate ( doc,
XPathConstants.NODESET);
Percompiling XPath expressions may be a huge boon now that the XPath 2.0
specification allows for large and complex expressions.
The second point I'd like to make here is that you'll notice that an XML
document is always passed to the evaluate method. Essentially this means
that the XPath API is stateless, and the XML document is queried from the
top. This nice thing about Oracle's XPath methods, selectNodes()
and
selectSingleNode()
is they are associated with a Node
and not a
top-level document.
Of course, you can always come up with an XPath expression that can refer
to any subnode of any node, however, when you start to combine XML
documents, but an XPath expression was oriented for a particular embedded
document, this becomes much trickier.
Tell others about this article: