XPathExpression dynamic return value
I need to evaluate an xpath expression without开发者_JS百科 knowing its return value in advance. I don't care about the distinction between a Node and a NodeSet, but I do care about NodeSet versus a String or a number.
For example, an xpath with a sum
or concat
function can return a number or a string (or boolean). An xpath with a regular xml path returns a nodeset, potentially with only one node.
How can I call the XPathExpression.evaluate()
function (javadoc here)? I don't know what QName to pass to it as the second parameter. The class XPathConstants
contains the closed list of constants allowed (these are Node, NodeSet, String, Number and Boolean).
Is it possible to specify an "all" or "don't care" value and then inspect the returned object for its type?
I thought maybe to use the overloaded version of this function, which always return a String. However, I don't see how this will work if the xpath expression is meant to return a node set.
Following Alejandro's hint about DOM Level 3 XPath, I found out that Xalan 2.7.1 already supports it.
I used the class XPathEvaluatorImpl
of Xalan. It works great, and I can specify ANY-TYPE
as the return value. The only downside is that this functionality is not exposed in the standard javax api, at least not in Java 6.
Re: getting a String - if the expression references a NodeSet you get its textual representation. It might be someling like this:
thisthat
reads as a String "thisthat" with or without whitespaces depending on the configuration. You will not know without trying out.
Personally I prefer Dom4J because you're in charge of what you receive using methods like booleanValueOf, numberValueOf, selectNodes, selectSingleNode. And the code gets clear: it says what you want to get so it's easy to understand.
You can first use xpathExpression.evaluateExpression(doc).type().name()
( java >=9
) which will return a value among: BOOLEAN
, NUMBER
, STRING
,NODESET
,NODE
. Then you can create a branch for each case to select the appropriate QName to pass in evaluate()
.
Note: it happens that the names of the enum
returned by xpathExpression.evaluateExpression(doc).type()
match exactly the values of XPathConstants.*.getLocalPart()
so you can use the code like below:
XPathEvaluationResult xpathEvalResult = xpathExpression.evaluateExpression(xmlDocument);
String xpathTypeName = xpathEvalResult.type().name();
if(xpathTypeName.equals(XPathConstants.NODESET.getLocalPart())){ //e.g. xpath: /elem1/*
NodeList nodeList = (NodeList)expr.evaluate(xmlDocument, XPathConstants.NODESET);
for (int i = 0; i < nodeList.getLength(); i++) {
retVal += convertNodeToString(nodeList.item(i))+"\n===\n";
}
}else if(xpathTypeName.equals(XPathConstants.STRING.getLocalPart())){ //e.g. xpath: name(/elem1/*)
retVal = (String)expr.evaluate(xmlDocument, XPathConstants.STRING);
}else if(xpathTypeName.equals(XPathConstants.NUMBER.getLocalPart())){ //e.g. xpath: count(/elem1/*)
retVal = ((Number)expr.evaluate(xmlDocument, XPathConstants.NUMBER)).toString();
}else if(xpathTypeName.equals(XPathConstants.BOOLEAN.getLocalPart())){ //e.g. xpath: contains(elem1, 'sth')
retVal = ((Boolean)expr.evaluate(xmlDocument, XPathConstants.BOOLEAN)).toString();
}else if(xpathTypeName.equals(XPathConstants.NODE.getLocalPart())){ //e.g. xpath: fixme: find one
retVal = convertNodeToString((Node)expr.evaluate(xmlDocument, XPathConstants.NODE));
}else{
throw new RuntimeException("Unexpected xpath type name: "+xpathTypeName+". This should normally not happen");
}
Note that the above works with standard java library (no need for extra artifacts). Tested with jdk17 but should work with any jdk >=9.
See also my related answer here
精彩评论