XPath: How to implement XpathResolver correctly
I want to use XPath to filter information from an XML.
XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
Object result = null;
// set the custom resolver to compares dates
xpath.setXPathFunctionResolver(new DateValidatorContext(PubUtils.getInstance().parseDate(date), operator));
try {
PubUtils.getInstance().printDOM(feed, System.out);
XPathExpression expr = xpath.compile("//entry/content/artifact/resourceUri/text()");
result = expr.evaluate(feed, XPathConstants.NODESET);
} catch (XPathExpressionException e) {
PublishService.logger.debug(e.getMessage());
return null;
}
NodeList nodes = (NodeList) result;
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getNodeValue());
}
I implemented the XPathResolver that checks if the date is (EQUALS/GREATER/LOWER) than an given date.
import java.util.Date;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathFunctionException;
import javax.xml.xpath.XPathFunctionResolver;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import ....Operator;
/**
* XPathFunctionResolver to compare dates
*
*/
public class DateValidatorContext implements XPathFunctionResolver {
private static final QName name = new QName(null, "compare-date");
private Date referenceDate = null;
Operator operator = Operator.EQUALS;
/**
* Sets the variables for comparison. Default comparison is equals
*
* @param referenceDate
* date to compare to
*/
public DateValidatorContext (Date referenceDate){
this.referenceDate = referenceDate;
}
/**
* Sets the variables for comparison
*
* @param referenceDate
* date to com开发者_如何学Gopare to
* @param operator
* type of operation to execute (EQUALS, GREATER, LOWER)
*/
public DateValidatorContext (Date referenceDate, Operator operator){
this.referenceDate = referenceDate;
this.operator = operator;
}
public XPathFunction resolveFunction(QName name, int arity) {
if (name.equals(DateValidatorContext.name) && arity == 1) {
return new DateValidator(referenceDate, operator);
}
return null;
}
/**
* XPathFunction to compare dates
*
*/
public class DateValidator implements XPathFunction{
private Date referenceDate = null;
Operator operator = Operator.EQUALS;
/**
* Set the values for comparison
*
* @param referenceDate
* date to compare to
* @param operator
* type of operation to execute (EQUALS, GREATER, LOWER)
*/
public DateValidator (Date referenceDate, Operator operator){
this.referenceDate = referenceDate;
this.operator = operator;
}
public Object evaluate(List args) throws XPathFunctionException {
if(args.size()!= 1){
throw new XPathFunctionException("Wrong number of arguments to compare-date()");
}
Date feedItemDate;
Object o = args.get(0);
// perform conversions
if (o instanceof String) feedItemDate = PubUtils.getInstance().parseDate((String) args.get(0));
else if (o instanceof Boolean) feedItemDate = null;
else if (o instanceof Double) feedItemDate = null;
else if (o instanceof NodeList) {
NodeList list = (NodeList) o;
Node node = list.item(0);
feedItemDate= PubUtils.getInstance().parseDate(node.getTextContent());
}
else {
throw new XPathFunctionException("Could not convert argument type");
}
if(referenceDate != null && feedItemDate != null){
int dateCompare = feedItemDate.compareTo(referenceDate);
switch(operator){
case EQUALS:
if(dateCompare == 0){
return Boolean.TRUE;
}
break;
case GREATER:
if(dateCompare > 0){
return Boolean.TRUE;
}
break;
case LOWER:
if(dateCompare < 0){
return Boolean.TRUE;
}
break;
default:
break;
}
}
return Boolean.FALSE;
}
}
}
The problem is that with the current expression
"//entry/content/artifact/resourceUri/text()"
I'm getting all the content from the XML. How can I fix this to get only the information I need from the XML?
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<recentArtifactsFeed>
<entry>
<updated>2010-07-08T00:54:22.859Z</updated>
<content lang="english" type="application/xml">
<artifact><resourceUri>../types/_WDKRIYorEd-hG9AeAoU8_g</resourceUri></artifact>
</content>
</entry>
</recentArtifactsFeed>
i just tested your xpathExpression ("//entry/content/artifact/resourceUri/text()"), without the XPathFunctionResolver, this gives the expected result.
I cannot test with the XPathFunctionResolver, because the code is not complete (Operator, PubUtils, ...), but i checked the javadoc:
In particular, the resolver is only called for functions in an another namespace (functions with an explicit prefix). This means that you cannot use the
XPathFunctionResolver
to implement specifications like XML-Signature Syntax and Processing which extend the function library of XPath 1.0 in the same namespace. This is a consequence of the design of the resolver.
Following gives also a good start: http://xml.apache.org/xalan-j/xpath_apis.html#functionresolver
In short, it takes three steps to use functionresolvers:
- create XPathFunction
- create XPathFunctionResolver
- create NamespaceContext
精彩评论