I have the following code:
// xpath evaluates to net.sf.saxon.xpath.XPathEvaluator
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression expression = xpath.compile("/foo/bar");
Object evaluate = expression.evaluate(someXML, XPathConstants.NODE);
Object evaluate2 = expression.evaluate(someXML, XPathConstants.NODESET);
System.out.println(evaluate!=null?evaluate.getClass():"null");
System.out.println(evaluate2!=null?evaluate2.getClass():"null2");
System.out.println(evaluate instanceof Node);
System.out.println(evaluate2 instanceof NodeList);
and this is the result...
class net.sf.saxon.tinytree.TinyElementImpl class java.util.ArrayList false false
Just to clarify, if I do this:
org.w3c.dom.Node node = (org.w3c.dom.Node)evaluate;
or
org.w3c.dom.NodeList node = (org.w3c.dom.NodeList)evaluate2;
I get a ClassCastException
How can that be? according to Suns Java 1.5 API NODE and NODESET should map to org.w3c.dom.Node
and org.w3c.dom.NodeList
respectively
Just to clarify2 yes I know Node is an iterface, that getClass() returns a concrete class.
Ok I figured it out!
If the evaluate method receives an InputSource the above error occurs.
e.g.
InputSource someXML = new InputSource(new StringReader("<someXML>...</someXML>)");
Object result = expression.evaluate(someXML, XPathConstants.NODE);
Node node = (Node) result; // ClassCastException
Then result is not implementing org.w3c.dom.Node
(TinyElementImpl
)
But if evaluate receives a Node
(or a Document
):
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document someXML = documentBuilder.parse(new InputSource(new StringReader("<someXML>...</someXML>)"));
Object result = expression.evaluate(someXML, XPathConstants.NODE);
Node node = (Node) result; // works
It works, but still, this is weird...
Try this code:
Object evaluate = expression.evaluate(someXML, XPathConstants.NODE);
System.out.println(evaluate instanceof Node);
System.out.println(NodeOverNodeInfo.wrap((NodeInfo) evaluate) instanceof Node);
It prints:
false
true
The returned object is of type NodeInfo
, so you need wrap it as a real Node
, so you can access its methods:
Node n = NodeOverNodeInfo.wrap((NodeInfo) evaluate);
System.out.println(n.getNodeName());
System.out.println(n.getTextContent());
It's a bit odd, this one. The Saxon javadoc says that TinyElementImpl
doesn't implement any of the org.w3c.dom
interfaces, and yet you're getting them back from the XPath evaluation.
My guess is that Saxon eschews the standard DOM model in favour of its own one. I suspect that the XPathConstants.NODE
that you pass to evaluate
is really just a hint. It's permitted for XPath expressions to return any old thing (for example, Apache JXPath uses XPath expressions to query java objects graphs), so it's permitted for Saxon to return its own DOM types rather than org.w3c
standard ones.
Solution: either use the Saxon DOM types as returned, or don't use Saxon.
Node
is an interface. You have to have a concrete class for implementation. And getClass()
returns that concrete class.
Edit in response to comment:
Sorry, I didn't pay attention to the instanceof. Looking at the source code, it appears that TinyNodeImpl
doesn't implement org.w3c.dom.Node
. And looking at the JDK docs, it appears that it doesn't have to: the doc for javax.xml.XPath refers you to XPathConstants for the result type, and it refers to the "The XPath 1.0 NodeSet data type" (which, if you look at the XPath 1.0 spec, is not defined).
So, it seems that returns from the XPath API are only required to be consistent when used within that API. Not exactly what you wanted to hear, I'm sure. Can you use the built-in JDK implementation? I know that it returns org.w3c.dom
objects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With