All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.practicalxml.xpath.XPathWrapper Maven / Gradle / Ivy

// Copyright 2008-2014 severally by the contributors
//
// 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 net.sf.practicalxml.xpath;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFunction;
import javax.xml.xpath.XPathVariableResolver;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import net.sf.practicalxml.DomUtil;
import net.sf.practicalxml.XmlException;



/**
 *  This class simplifies the use of XPath expressions, hiding the factory and
 *  return types, and providing a simple builder-style interface for adding
 *  resolvers. It also maintains the expression in a compiled form, improving
 *  reuse performance.
 *  

* Warning: * XPathWrapper, like the underlying JDK XPath object, * is not thread-safe. However, separate instances may be created and evaluated * on separate threads without explicit synchronization. To make this work, we * create a new XPathFactory instance for each wrapper (with the * Sun JDK, this adds little overhead), synchronizing newInstance() * on XPathWrapper.class. */ public class XPathWrapper implements Cloneable { private final String _expr; private final NamespaceResolver _nsResolver = new NamespaceResolver(); private Map _variables = new HashMap(); private FunctionResolver _functions = new FunctionResolver(); private XPathExpression _compiled; /** * Creates a new instance, which may then be customized with various * resolvers, and used to evaluate expressions. */ public XPathWrapper(String expr) { _expr = expr; } //---------------------------------------------------------------------------- // Public methods //---------------------------------------------------------------------------- /** * Adds a namespace binding to this expression. All bindings must be * added prior to the first call to evaluate(). If you add * multiple bindings with the same namespace, only the last is retained. * * @param prefix The prefix used to reference this namespace in the * XPath expression. Note that this does not * need to be the same prefix used by the document. * @param nsURI The namespace URI to associate with this prefix. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindNamespace(String prefix, String nsURI) { _nsResolver.addNamespace(prefix, nsURI); return this; } /** * Sets the default namespace binding: this will be applied to all * expressions that do not explicitly specify a prefix (although * they must use the colon separating the non-existent prefix from * the element name). * * @param nsURI The default namespace for this document. * * @return The wrapper, so that calls may be chained. * * @deprecated In practice, this method isn't particularly useful: * if you're going to specify ":" on a term, you might * as well specify a single-character prefix. It won't * be removed, but a future implementation might parse * an expression sans colons, and insert references. */ @Deprecated public XPathWrapper bindDefaultNamespace(String nsURI) { _nsResolver.setDefaultNamespace(nsURI); return this; } /** * Binds a value to a variable, replacing any previous value for that * variable. Unlike other configuration methods, this may be called * after calling evaluate(); the new values will be used * for subsequent evaluations. * * @param name The name of the variable; this is turned into a * QName without namespace. * @param value The value of the variable; the XPath evaluator must * be able to convert this value into a type usable in * an XPath expression. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindVariable(String name, Object value) { return bindVariable(new QName(name), value); } /** * Binds a value to a variable, replacing any previous value for that * variable. Unlike other configuration methods, this may be called * after calling evaluate(); the new values will be used * for subsequent evaluations. * * @param name The fully-qualified name of the variable. * @param value The value of the variable; the XPath evaluator must * be able to convert this value into a type usable in * an XPath expression. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindVariable(QName name, Object value) { _variables.put(name, value); return this; } /** * Binds a self-describing function to this expression. Subsequent calls * with the same name/arity will silently replace the binding. *

* Per the JDK documentation, user-defined functions must occupy their * own namespace. You must bind a namespace for this function, and use * the bound prefix to reference it in your expression. Alternatively, * you can call the variant of bindFunction() that binds * a prefix to the function's namespace. * * @param func The function. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func) { _functions.addFunction(func); return this; } /** * Binds a self-describing function to this expression, along with the * prefix used to access that function. This also establishes a namespace * binding for that prefix. *

* Per the JDK documentation, user-defined functions must occupy their * own namespace. This method will retrieve the namespace from the * function, and bind it to the passed prefix. * * @param func The function. * @param prefix The prefix to bind to this function's namespace. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(FunctionResolver.SelfDescribingFunction func, String prefix) { _functions.addFunction(func); return bindNamespace(prefix, func.getNamespaceUri()); } /** * Binds a standard XPathFunction to this expression, * handling any number of arguments. Subsequent calls to this method * with the same name will silently replace the binding. *

* Per the JDK documentation, user-defined functions must occupy their * own namespace. If the qualified name that you pass to this method * includes a prefix, the associated namespace will be bound to that * prefix. If not, you must bind the namespace explicitly. In either * case, you must refer to the function in your expression using a * bound prefix. * * @param name The qualified name for this function. Must contain * a name and namespace, may contain a prefix. * @param func The function to bind to this name. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(QName name, XPathFunction func) { return bindFunction(name, func, 0, Integer.MAX_VALUE); } /** * Binds a standard XPathFunction to this expression, * handling a specific number of arguments. Subsequent calls to this * method with the same name and arity will silently replace the binding. *

* Per the JDK documentation, user-defined functions must occupy their * own namespace. If the qualified name that you pass to this method * includes a prefix, the associated namespace will be bound to that * prefix. If not, you must bind the namespace explicitly. In either * case, you must refer to the function in your expression using a * bound prefix. * * @param name The qualified name for this function. Must contain * a name and namespace, may contain a prefix. * @param func The function to bind to this name. * @param arity The number of arguments accepted by this function. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(QName name, XPathFunction func, int arity) { return bindFunction(name, func, arity, arity); } /** * Binds a standard XPathFunction to this expression, * handling a specific range of arguments. Subsequent calls to this * method with the same name and range of arguments will silently * replace the binding. *

* Per the JDK documentation, user-defined functions must occupy their * own namespace. If the qualified name that you pass to this method * includes a prefix, the associated namespace will be bound to that * prefix. If not, you must bind the namespace explicitly. In either * case, you must refer to the function in your expression using a * bound prefix. * * @param name The qualified name for this function. Must contain * a name and namespace, may contain a prefix. * @param func The function to bind to this name. * @param minArity The minimum number of arguments accepted by this * function. * @param maxArity The maximum number of arguments accepted by this * function. * * @return The wrapper, so that calls may be chained. */ public XPathWrapper bindFunction(QName name, XPathFunction func, int minArity, int maxArity) { _functions.addFunction(func, name, minArity, maxArity); if (!"".equals(name.getPrefix())) bindNamespace(name.getPrefix(), name.getNamespaceURI()); return this; } /** * Attaches a pre-build {@link FunctionResolver} to this wrapper, * replacing the existing resolver (and removing all bound functions). * This method is intended for use by {@link XPathWrapperFactory}, but * is exposed for applications that wish to manage their own XPaths. * * @since 1.1.12 */ public XPathWrapper setFunctionResolver(FunctionResolver resolver) { _functions = resolver; return this; } /** * Applies this expression to the the specified node, converting the * resulting NodeList into a java.util.List. */ public List evaluate(Node context) { return DomUtil.asList( evaluate(context, XPathConstants.NODESET, NodeList.class), Node.class); } /** * Applies this expression to the the specified node, converting the * resulting NodeList into a java.util.List * containing only nodes of the specified type. */ public List evaluate(Node context, Class klass) { return DomUtil.filter( evaluate(context, XPathConstants.NODESET, NodeList.class), klass); } /** * Applies this expression to the the specified node, and returning the * first Element from the resulting nodelist. Returns * null if there is no element in the list. * * @since 1.1.11 */ public Element evaluateAsElement(Node context) { NodeList nodelist = evaluate(context, XPathConstants.NODESET, NodeList.class); int size = nodelist.getLength(); for (int ii = 0 ; ii < size ; ii++) { Node node = nodelist.item(ii); if (node instanceof Element) return (Element)node; } return null; } /** * Applies this expression to the specified node, requesting the * STRING return type. */ public String evaluateAsString(Node context) { return evaluate(context, XPathConstants.STRING, String.class); } /** * Applies this expression to the specified node, requesting the * NODESET return type, and then retrieving the text * content for each returned node. *

* Note: this method calls Node.getTextContent() in * keeping with normal XPath behavior. If you want to get * only the immediate child text nodes, evaluate as a list * of elements and use DomUtil.getText(). * * @since 1.1.3 */ public List evaluateAsStringList(Node context) { List nodes = evaluate(context); List result = new ArrayList(nodes.size()); for (Node node : nodes) result.add(node.getTextContent()); return result; } /** * Applies this expression to the specified node, requesting the * NUMBER return type. */ public Number evaluateAsNumber(Node context) { return evaluate(context, XPathConstants.NUMBER, Number.class); } /** * Applies this expression to the the specified node, requesting the * BOOLEAN return type. */ public Boolean evaluateAsBoolean(Node context) { return evaluate(context, XPathConstants.BOOLEAN, Boolean.class); } //---------------------------------------------------------------------------- // Overrides of Object //---------------------------------------------------------------------------- /** * Two instances are considered equal if they have the same expression, * namespace mappings, variable mappings, and function mappings. Note * that instances with function mappings are all but guaranteed to be * not-equal, unless you override the equals() method on * the function implementation class. */ @Override public final boolean equals(Object obj) { if (obj instanceof XPathWrapper) { XPathWrapper that = (XPathWrapper)obj; return this._expr.equals(that._expr) && this._nsResolver.equals(that._nsResolver) && this._variables.equals(that._variables) && this._functions.equals(that._functions); } return false; } /** * Hash code is driven by the expression, ignoring differences between * namespaces, variables, and functions. */ @Override public int hashCode() { return _expr.hashCode(); } /** * The string value is the expression. */ @Override public String toString() { return _expr; } /** * Creates a shallow clone of this object, omitting the compiled * XPath expression. This method exists to support thread-local instances, * which will recompile the expression. */ @Override protected XPathWrapper clone() throws CloneNotSupportedException { XPathWrapper newInstance = (XPathWrapper)super.clone(); newInstance._compiled = null; return newInstance; } //---------------------------------------------------------------------------- // Internals //---------------------------------------------------------------------------- /** * Compiles the expression, if it has not already been compiled. This is * called from the various evaluate methods, and ensures * that the caller has completely configured the wrapper prior to use. */ private void compileIfNeeded() { if (_compiled != null) return; try { XPathFactory fact = null; synchronized (XPathFactory.class) { fact = XPathFactory.newInstance(); } XPath xpath = fact.newXPath(); xpath.setNamespaceContext(_nsResolver); xpath.setXPathVariableResolver(new MyVariableResolver()); xpath.setXPathFunctionResolver(_functions); _compiled = xpath.compile(_expr); } catch (XPathExpressionException ee) { throw new XmlException("unable to compile: " + _expr, ee); } } /** * The base evaluation method: compiles the XPath, applies it as instructed * by the caller, and casts the result (also as instructed). */ private T evaluate(Node context, QName returnType, Class castTo) { compileIfNeeded(); try { return castTo.cast(_compiled.evaluate(context, returnType)); } catch (Exception ee) { throw new XmlException("unable to evaluate: " + _expr, ee); } } /** * Resolver for variable references. */ private class MyVariableResolver implements XPathVariableResolver { public Object resolveVariable(QName name) { return _variables.get(name); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy