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.
해결책
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.
kdgregory is correct that Node
is just an interface, and TinyElementImpl
implements that interface. expression.evaluate()
can't return an instance of Node
, it has to return a concrete class which implements node.
It might be useful to point out that you can use an instance of TinyElementImpl
as as Node
, and you can easily cast instances of TinyElementImp
to Node
.
For example, this should work just fine:
Node result = (Node) expression.evaluate(someXML, XPathConstants.NODE);
You can then use result
by calling any of the methods of Node
, and by passing it to any method which accepts a Node
.