/*
* $HeadURL$
* $Id$
*
* Copyright (c) 2006-2011 by Public Library of Science
* http://plos.org
* http://ambraproject.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License. |
*/
package org.ambraproject.util;
import org.ambraproject.rhino.shared.XPathExtractor;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Convenience utility bean for querying xml using XPath expressions.
*
* @author Alex Kudlick Date: 6/6/11
* <p/>
* org.ambraproject.util
*/
public class XPathUtil implements XPathExtractor {
/**
* The XPath instance used to create expressions
*/
private XPath xPath = XPathFactory.newInstance().newXPath();
/**
* Set the namespace context to be used by this instance of Xpath. This enables the selection of namespaced
* attributes and nodes (i.e. nodes and attributes with a colon in them)
*
* @param pairs - a String array where each entry is of the form prefix=namespaceURI. In an XML document, the
* prefix is the part that comes just after the xmlns part, and the namespaceURI is the value of
* that attribute. E.g. to use the namespaces from the document below
* <pre>
* <rootNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
* ...
* </rootNode>
* </pre>
* you would pass in an array like <pre>{"xsi=http://www.w3.org/2001/XMLSchema-instance","web:http=//java.sun.com/xml/ns/javaee/web-app_2_5.xsd"}</pre>
*/
public void setNamespaceContext(final String[] pairs) {
xPath.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
for (String namespace : pairs) {
if (namespace.startsWith(prefix + "=")) {
return namespace.substring(namespace.indexOf("=") + 1);
}
}
return null;
}
@Override
public String getPrefix(String namespaceURI) {
for (String namespace : pairs) {
if (namespace.endsWith(namespaceURI)) {
return namespace.substring(0, namespace.indexOf("="));
}
}
return null;
}
@Override
public Iterator getPrefixes(String namespaceURI) {
return null;
}
});
}
/**
* Stores compiled XPath expressions for performance. To ensure thread safety, this is only accessed from {@link
* XPathUtil#getXPathExpression(String)}
*/
private Map<String, XPathExpression> storedExpressions = new HashMap<String, XPathExpression>();
private synchronized XPathExpression getXPathExpression(String xpath) throws XPathExpressionException {
if (storedExpressions.containsKey(xpath)) {
return storedExpressions.get(xpath);
} else {
XPathExpression expression = xPath.compile(xpath);
storedExpressions.put(xpath, expression);
return expression;
}
}
/**
* Select a single node from the document
*
* @param document - the document to select from
* @param expression - the xpath expression to use to get the node
* @return - the node described by the XPath expression, or null if there is none
* @throws XPathExpressionException - if there's a problem parsing the expression
*/
public Node selectSingleNode(Node document, String expression) throws XPathExpressionException {
return (Node) getXPathExpression(expression).evaluate(document, XPathConstants.NODE);
}
/**
* {@inheritDoc}
*/
@Override
public Node selectNode(Node node, String expression) throws XPathException {
return selectSingleNode(node, expression);
}
/**
* Select a single node from the input source.
*
* @param inputSource - the input source to select from
* @param expression - the xpath expression to use to get the node
* @return - the node described by the XPath expression, or null if there is none
* @throws XPathExpressionException - if there's a problem parsing the expression
*/
public Node selectSingleNode(InputSource inputSource, String expression) throws XPathExpressionException {
return (Node) getXPathExpression(expression).evaluate(inputSource, XPathConstants.NODE);
}
/**
* Select multiple nodes from the document
*
* @param document - the document to select from
* @param expression - the xpath expression to use to get the node
* @return - a NodeList containing the nodes described by the XPath expression, or null if there are none
* @throws XPathExpressionException - if there's a problem parsing the expression
*/
@Override
public NodeList selectNodes(Node document, String expression) throws XPathExpressionException {
return (NodeList) getXPathExpression(expression).evaluate(document, XPathConstants.NODESET);
}
/**
* Select multiple nodes from the input source
*
* @param inputSource - the input source to select from
* @param expression - the xpath expression to use to get the node
* @return - a NodeList containing the nodes described by the XPath expression, or null if there are none
* @throws XPathExpressionException - if there's a problem parsing the expression
*/
public NodeList selectNodes(InputSource inputSource, String expression) throws XPathExpressionException {
return (NodeList) getXPathExpression(expression).evaluate(inputSource, XPathConstants.NODESET);
}
/**
* Evaluate the given XPath expression, trimming the result
*
* @param document - the document in which to evaluate the expression
* @param expression - the expression to evaluate
* @return - the result of evaluation, as a string
* @throws XPathExpressionException - if there's a problem parsing the given expression
*/
public String evaluate(Node document, String expression) throws XPathExpressionException {
return getXPathExpression(expression).evaluate(document).trim();
}
/**
* Evaluate the given XPath expression, trimming the result
*
* @param inputSource - the input source in which to evaluate the expression
* @param expression - the expression to evaluate
* @return - the result of evaluation, as a string
* @throws XPathExpressionException - if there's a problem parsing the given expression
*/
public String evaluate(InputSource inputSource, String expression) throws XPathExpressionException {
return getXPathExpression(expression).evaluate(inputSource).trim();
}
/**
* Evaluate the give expression with the given return type
* <p/>
* The return types are determined from the given QName as follows: <ul> <li>XPathConstants.NODE ->
* org.w3c.dom.Node</li> <li>XPathConstants.NODESET -> org.w3c.dom.NodeList</li> <li>XPathConstants.BOOLEAN ->
* java.lang.Boolean</li> <li>XPathConstants.NUMBER -> java.lang.Integer</li> <li>XPathConstants.STRING ->
* java.lang.String</li> </ul>
* <p/>
* The return value of this method will be automatically cast to the type of a variable declared, so be sure that
* variables are of types described above.
*
* @param document - the context in which to evaluate the xpath expression
* @param expression - the xpath expression to evaluate
* @param returnType - an xpath constant denoting the return type.
* @return - the result of the evaluation, of the given type
* @throws XPathExpressionException - if there is a problem evaluating the expression
*/
public Object evaluate(Node document, String expression, QName returnType) throws XPathExpressionException {
return getXPathExpression(expression).evaluate(document, returnType);
}
/**
* Evaluate the give expression with the given return type
* <p/>
* The return types are determined from the given QName as follows: <ul> <li>XPathConstants.NODE ->
* org.w3c.dom.Node</li> <li>XPathConstants.NODESET -> org.w3c.dom.NodeList</li> <li>XPathConstants.BOOLEAN ->
* java.lang.Boolean</li> <li>XPathConstants.NUMBER -> java.lang.Integer</li> <li>XPathConstants.STRING ->
* java.lang.String</li> </ul>
* <p/>
* The return value of this method will be automatically cast to the type of a variable declared, so be sure that
* variables are of types described above.
*
* @param inputSource - the context in which to evaluate the xpath expression
* @param expression - the xpath expression to evaluate
* @param returnType - an xpath constant denoting the return type.
* @return - the result of the evaluation, of the given type
* @throws XPathExpressionException - if there is a problem evaluating the expression
*/
public Object evaluate(InputSource inputSource, String expression, QName returnType) throws XPathExpressionException {
return getXPathExpression(expression).evaluate(inputSource, returnType);
}
@Override
public String selectString(Node node, String xpath) throws XPathException {
return evaluate(node, xpath);
}
}