Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saxon XPath API returns TinyElementImpl instead of org.w3c.dom.Node

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.

like image 600
Eran Medan Avatar asked Dec 31 '09 13:12

Eran Medan


4 Answers

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...

like image 196
Eran Medan Avatar answered Nov 03 '22 18:11

Eran Medan


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());
like image 26
Hua Zhang Avatar answered Nov 03 '22 16:11

Hua Zhang


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.

like image 2
skaffman Avatar answered Nov 03 '22 16:11

skaffman


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.

like image 2
kdgregory Avatar answered Nov 03 '22 18:11

kdgregory