Added XPath template.

This commit is contained in:
Arjen Poutsma
2007-04-18 13:43:58 +00:00
parent f99573afe7
commit 9511de6cc7
14 changed files with 877 additions and 23 deletions

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import java.util.Properties;
import javax.xml.transform.Source;
import org.springframework.xml.transform.TransformerObjectSupport;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
/**
* Abstract base class for implementations of {@link XPathOperations}. Contains a namespaces property.
*
* @author Arjen Poutsma
*/
public abstract class AbstractXPathTemplate extends TransformerObjectSupport implements XPathOperations {
private Properties namespaces;
/** Returns namespaces used in the XPath expression. */
public Properties getNamespaces() {
return namespaces;
}
/** Sets namespaces used in the XPath expression. Maps prefixes to namespaces. */
public void setNamespaces(Properties namespaces) {
this.namespaces = namespaces;
}
public final void evaluate(String expression, Source context, NodeCallbackHandler callbackHandler)
throws XPathException {
evaluate(expression, context, new NodeCallbackHandlerNodeMapper(callbackHandler));
}
/** Static inner class that adapts a {@link NodeCallbackHandler} to the interface of {@link NodeMapper}. */
private static class NodeCallbackHandlerNodeMapper implements NodeMapper {
private final NodeCallbackHandler callbackHandler;
public NodeCallbackHandlerNodeMapper(NodeCallbackHandler callbackHandler) {
this.callbackHandler = callbackHandler;
}
public Object mapNode(Node node, int nodeNum) throws DOMException {
callbackHandler.processNode(node);
return null;
}
}
}

View File

@@ -0,0 +1,171 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import org.jaxen.JaxenException;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.XPath;
import org.jaxen.dom.DOMXPath;
import org.springframework.xml.dom.DomUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* Implementation of {@link XPathOperations} that uses Jaxen.
* <p/>
* Namespaces can be set using the <code>namespaces</code> property.
*
* @author Arjen Poutsma
* @see <a href="http://www.jaxen.org/">Jaxen</a>
*/
public class JaxenXPathTemplate extends AbstractXPathTemplate {
public boolean evaluateAsBoolean(String expression, Source context) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return xpath.booleanValueOf(element);
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public Node evaluateAsNode(String expression, Source context) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return (Node) xpath.selectSingleNode(element);
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public List evaluateAsNodeList(String expression, Source context) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return xpath.selectNodes(element);
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public double evaluateAsDouble(String expression, Source context) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return xpath.numberValueOf(element).doubleValue();
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public String evaluateAsString(String expression, Source context) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return xpath.stringValueOf(element);
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
Node node = (Node) xpath.selectSingleNode(element);
if (node != null) {
try {
return nodeMapper.mapNode(node, 0);
}
catch (DOMException ex) {
throw new XPathException("Mapping resulted in DOMException", ex);
}
}
else {
return null;
}
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
public List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException {
try {
XPath xpath = createXPath(expression);
Element element = DomUtils.getRootElement(context, getTransformerFactory());
List nodes = (List) xpath.selectNodes(element);
List results = new ArrayList(nodes.size());
for (int i = 0; i < nodes.size(); i++) {
Node node = (Node) nodes.get(i);
try {
results.add(nodeMapper.mapNode(node, i));
}
catch (DOMException ex) {
throw new XPathException("Mapping resulted in DOMException", ex);
}
}
return results;
}
catch (JaxenException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
private XPath createXPath(String expression) throws JaxenException {
XPath xpath = new DOMXPath(expression);
if (getNamespaces() != null && !getNamespaces().isEmpty()) {
xpath.setNamespaceContext(new SimpleNamespaceContext(getNamespaces()));
}
return xpath;
}
}

View File

@@ -28,7 +28,7 @@ import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* JAXP 1.3-specific factory creating <code>XPathExpression</code>s.
* JAXP 1.3-specific factory creating {@link XPathExpression} objects.
*
* @author Arjen Poutsma
* @see #createXPathExpression(String)

View File

@@ -0,0 +1,168 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFactoryConfigurationException;
import org.springframework.xml.dom.DomUtils;
import org.springframework.xml.namespace.SimpleNamespaceContext;
import org.springframework.xml.transform.StaxSource;
import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Implementation of {@link XPathOperations} that uses JAXP 1.3. JAXP 1.3 is part of Java SE since 1.5.
* <p/>
* Namespaces can be set using the <code>namespaces</code> property.
*
* @author Arjen Poutsma
* @see #setNamespaces(java.util.Properties)
*/
public class Jaxp13XPathTemplate extends AbstractXPathTemplate {
private XPathFactory xpathFactory;
public Jaxp13XPathTemplate() {
this(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
}
public Jaxp13XPathTemplate(String xpathFactoryUri) {
try {
xpathFactory = XPathFactory.newInstance(xpathFactoryUri);
}
catch (XPathFactoryConfigurationException ex) {
throw new XPathException("Could not create XPathFactory", ex);
}
}
public boolean evaluateAsBoolean(String expression, Source context) throws XPathException {
Boolean result = (Boolean) evaluate(expression, context, XPathConstants.BOOLEAN);
return result != null && result.booleanValue();
}
public Node evaluateAsNode(String expression, Source context) throws XPathException {
return (Node) evaluate(expression, context, XPathConstants.NODE);
}
public List evaluateAsNodeList(String expression, Source context) throws XPathException {
NodeList result = (NodeList) evaluate(expression, context, XPathConstants.NODESET);
List nodes = new ArrayList(result.getLength());
for (int i = 0; i < result.getLength(); i++) {
nodes.add(result.item(i));
}
return nodes;
}
public double evaluateAsDouble(String expression, Source context) throws XPathException {
Double result = (Double) evaluate(expression, context, XPathConstants.NUMBER);
return result != null ? result.doubleValue() : Double.NaN;
}
public String evaluateAsString(String expression, Source context) throws XPathException {
return (String) evaluate(expression, context, XPathConstants.STRING);
}
public Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException {
Node node = evaluateAsNode(expression, context);
if (node != null) {
try {
return nodeMapper.mapNode(node, 0);
}
catch (DOMException ex) {
throw new XPathException("Mapping resulted in DOMException", ex);
}
}
else {
return null;
}
}
public List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException {
NodeList nodes = (NodeList) evaluate(expression, context, XPathConstants.NODESET);
List results = new ArrayList(nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
try {
results.add(nodeMapper.mapNode(nodes.item(i), i));
}
catch (DOMException ex) {
throw new XPathException("Mapping resulted in DOMException", ex);
}
}
return results;
}
private Object evaluate(String expression, Source context, QName returnType) throws XPathException {
XPath xpath = xpathFactory.newXPath();
if (getNamespaces() != null && !getNamespaces().isEmpty()) {
SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext();
namespaceContext.setBindings(getNamespaces());
xpath.setNamespaceContext(namespaceContext);
}
try {
if (context instanceof StaxSource) {
Element element = DomUtils.getRootElement(context, getTransformerFactory());
return xpath.evaluate(expression, element, returnType);
}
else if (context instanceof SAXSource) {
SAXSource saxSource = (SAXSource) context;
return xpath.evaluate(expression, saxSource.getInputSource(), returnType);
}
else if (context instanceof DOMSource) {
DOMSource domSource = (DOMSource) context;
return xpath.evaluate(expression, domSource.getNode(), returnType);
}
else if (context instanceof StreamSource) {
StreamSource streamSource = (StreamSource) context;
InputSource inputSource = null;
if (streamSource.getInputStream() != null) {
inputSource = new InputSource(streamSource.getInputStream());
}
else if (streamSource.getReader() != null) {
inputSource = new InputSource(streamSource.getReader());
}
else {
throw new IllegalArgumentException("StreamSource contains neither InputStream nor Reader");
}
return xpath.evaluate(expression, inputSource, returnType);
}
else {
throw new IllegalArgumentException("context type unknown");
}
}
catch (javax.xml.xpath.XPathException ex) {
throw new XPathException("Could not evaluate XPath expression [" + expression + "]", ex);
}
catch (TransformerException ex) {
throw new XPathException("Could not transform context to DOM Node", ex);
}
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
/**
* An interface used by {@link XPathOperations} implementations for processing {@link Node} objects on a per-node basis.
* Implementations of this interface perform the actual work of processing nodes, but don't need to worry about
* exception handling.
* <p/>
* Consider using a {@link NodeMapper} instead if you need to map exactly result object per node, assembling them in a
* List.
*
* @author Arjen Poutsma
* @see XPathOperations#evaluate(String,javax.xml.transform.Source,NodeCallbackHandler)
*/
public interface NodeCallbackHandler {
/**
* Processed a single node.
*
* @param node the node to map
* @throws DOMException in case of DOM errors
*/
void processNode(Node node) throws DOMException;
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
/**
* An interface used by {@link XPathOperations} implementations for mapping {@link Node} objects on a per-node basis.
* Implementations of this interface perform the actual work of mapping each node to a result object, but don't need to
* worry about exception handling.
*
* @author Arjen Poutsma
* @see XPathOperations#evaluate(String,javax.xml.transform.Source,NodeMapper)
* @see XPathOperations#evaluateAsObject(String,javax.xml.transform.Source,NodeMapper)
*/
public interface NodeMapper {
/**
* Maps a single node to an arbitrary object.
*
* @param node the node to map
* @param nodeNum the number of the current node
* @return object for the current node
* @throws DOMException in case of DOM errors
*/
Object mapNode(Node node, int nodeNum) throws DOMException;
}

View File

@@ -19,32 +19,46 @@ package org.springframework.xml.xpath;
import org.w3c.dom.Node;
/**
* Defines the contract for a precompiled XPath expression. Concrete instances can be obtained through the
* <code>XPathExpressionFactory</code>.
* Defines the contract for a precompiled XPath expression. Concrete instances can be obtained through the {@link
* XPathExpressionFactory}.
* <p/>
* Implementations of this interface are precompiled, and thus faster, but less flexible, than the XPath expressions
* used by {@link XPathOperations} implementations.
*
* @author Arjen Poutsma
* @see XPathExpressionFactory
*/
public interface XPathExpression {
/**
* Evaluates the given expression as a <code>Node</code>. Returns the evaluation of the expression, or
* <code>null</code> if it is invalid.
*
* @param node the starting point
* @return the result of the evaluation
*/
Node evaluateAsNode(Node node);
/**
* Evaluates the given expression as a <code>boolean</code>. Returns the boolean evaluation of the expression, or
* <code>false</code> if it is invalid.
*
* @param node the starting point
* @return the result of the evaluation
* @see <a href="http://www.w3.org/TR/xpath#booleans">XPath specification</a>
*/
boolean evaluateAsBoolean(Node node);
/**
* Evaluates the given expression as a <code>Node</code>. Returns the evaluation of the expression, or
* <code>null</code> if it is invalid.
*
* @param node the starting point
* @return the result of the evaluation
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
Node evaluateAsNode(Node node);
/**
* Evaluates the given expression, and returns all <code>Node</code>s that conform to it. Returns and empty array if
* no result could be found.
*
* @param node the starting point
* @return the <code>Node</code>s that are selected by the expression
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
Node[] evaluateAsNodes(Node node);
/**
* Evaluates the given expression as a number (<code>double</code>). Returns the numeric evaluation of the
* expression, or <code>Double.NaN</code> if it is invalid.
@@ -52,6 +66,7 @@ public interface XPathExpression {
* @param node the starting point
* @return the result of the evaluation
* @see Double#NaN
* @see <a href="http://www.w3.org/TR/xpath#numbers">XPath specification</a>
*/
double evaluateAsNumber(Node node);
@@ -61,15 +76,7 @@ public interface XPathExpression {
*
* @param node the starting point
* @return the first <code>Node</code> that is selected by the expression
* @see <a href="http://www.w3.org/TR/xpath#strings">XPath specification</a>
*/
String evaluateAsString(Node node);
/**
* Evaluates the given expression, and returns all <code>Node</code>s that conform to it. Returns and empty array if
* no result could be found.
*
* @param node the starting point
* @return the <code>Node</code>s that are selected by the expression
*/
Node[] evaluateAsNodes(Node node);
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2007 the original author or authors.
*
* 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.springframework.xml.xpath;
import java.util.List;
import javax.xml.transform.Source;
import org.w3c.dom.Node;
/**
* Interface that specifies a basic set of XPath operations, implemented by various XPathTemplates. Contains numerous
* evaluation methods,
* <p/>
* The templates that implement this interface do not use precompiled XPath expressions. Consider using the {@link
* XPathExpressionFactory} or the {@link XPathExpressionFactoryBean} for optimal performance, but less flexibility.
*
* @author Arjen Poutsma
* @see Jaxp13XPathTemplate
* @see JaxenXPathTemplate
*/
public interface XPathOperations {
/**
* Evaluates the given expression as a <code>boolean</code>. Returns the boolean evaluation of the expression, or
* <code>false</code> if it is invalid.
*
* @param expression the XPath expression
* @param context the context starting point
* @return the result of the evaluation
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#booleans">XPath specification</a>
*/
boolean evaluateAsBoolean(String expression, Source context) throws XPathException;
/**
* Evaluates the given expression as a {@link Node}. Returns the evaluation of the expression, or <code>null</code>
* if it is invalid.
*
* @param expression the XPath expression
* @param context the context starting point
* @return the result of the evaluation
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
Node evaluateAsNode(String expression, Source context) throws XPathException;
/**
* Evaluates the given expression as a list of {@link Node} objects. Returns the evaluation of the expression, or an
* empty list if no results are found.
*
* @param expression the XPath expression
* @param context the context starting point
* @return the result of the evaluation
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
List evaluateAsNodeList(String expression, Source context) throws XPathException;
/**
* Evaluates the given expression as a <code>double</code>. Returns the evaluation of the expression, or {@link
* Double#NaN} if it is invalid.
*
* @param expression the XPath expression
* @param context the context starting point
* @return the result of the evaluation
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#numbers">XPath specification</a>
*/
double evaluateAsDouble(String expression, Source context) throws XPathException;
/**
* Evaluates the given expression as a {@link String}. Returns the evaluation of the expression, or
* <code>null</code> if it is invalid.
*
* @param expression the XPath expression
* @param context the context starting point
* @return the result of the evaluation
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#strings">XPath specification</a>
*/
String evaluateAsString(String expression, Source context) throws XPathException;
/**
* Evaluates the given expression, mapping a single {@link Node} result to a Java object via a {@link NodeMapper}.
*
* @param expression the XPath expression
* @param context the context starting point
* @param nodeMapper object that will map one object per node
* @return the single mapped object
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
Object evaluateAsObject(String expression, Source context, NodeMapper nodeMapper) throws XPathException;
/**
* Evaluates the given expression, mapping each result {@link Node} objects to a Java object via a {@link
* NodeMapper}.
*
* @param expression the XPath expression
* @param context the context starting point
* @param nodeMapper object that will map one object per node
* @return the result list, containing mapped objects
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
List evaluate(String expression, Source context, NodeMapper nodeMapper) throws XPathException;
/**
* Evaluates the given expression, handling the result {@link Node} objects on a per-node basis with a {@link
* NodeCallbackHandler}.
*
* @param expression the XPath expression
* @param context the context starting point
* @param callbackHandler object that will extract results, one row at a time
* @throws XPathException in case of XPath errors
* @see <a href="http://www.w3.org/TR/xpath#node-sets">XPath specification</a>
*/
void evaluate(String expression, Source context, NodeCallbackHandler callbackHandler) throws XPathException;
}

View File

@@ -1,5 +1,5 @@
<html>
<body>
Provides classes for XPath evaluation using JAXP 1.3, Jaxen, and Xalan. Mostly for internal use by the framework.
Provides XPathTemplate implementations, and various classes for XPath evaluation using JAXP 1.3, Jaxen, and Xalan.
</body>
</html>