[Practicalxml-commits] SF.net SVN: practicalxml:[36] trunk/src
Brought to you by:
kdgregory
From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-01 00:09:32
|
Revision: 36 http://practicalxml.svn.sourceforge.net/practicalxml/?rev=36&view=rev Author: kdgregory Date: 2008-12-01 00:09:30 +0000 (Mon, 01 Dec 2008) Log Message: ----------- add XPathWrapper Added Paths: ----------- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java Added: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java =================================================================== --- trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java (rev 0) +++ trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java 2008-12-01 00:09:30 UTC (rev 36) @@ -0,0 +1,373 @@ +package net.sf.practicalxml; + +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.XPathFunctionResolver; +import javax.xml.xpath.XPathVariableResolver; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import net.sf.practicalxml.misc.NamespaceResolver; + + + +/** + * 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. + */ +public class XPathWrapper +{ + private final String _expr; + private final NamespaceResolver _nsResolver = new NamespaceResolver(); + private Map<QName,Object> _variables = new HashMap<QName,Object>(); + private Map<QName,XPathFunction> _functions = new HashMap<QName,XPathFunction>(); + + 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 <code>evaluate()</code>. + * + * @param prefix The prefix used to reference this namespace in the + * XPath expression. Note that this does <em>not</em> + * 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. + */ + 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 <code>evaluate()</code>; the new values will be used + * for subsequent evaluations. + * + * @param name The name of the variable; this is turned into a + * <code>QName</code> 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 <code>evaluate()</code>; 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 function to this expression, replacing any previous binding + * for the specified name. Note that <code>XPathFunctionResolver</code> + * can support multiple functions with the same name; we don't. + * <p> + * Per the JDK documentation, user-defined functions must occupy their + * own namespace. You will need to add a namespace binding along with + * your function binding. + * + * @param name The fully-qualified name for this binding. + * @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) + { + _functions.put(name, func); + return this; + } + + + /** + * Applies this expression to the root of the specified document, + * converting the resulting NodeList into a java.util.List for ease + * in iteration. + */ + public List<Node> evaluate(Document context) + { + return evaluate(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, converting the + * resulting NodeList into a java.util.List for ease in iteration. + */ + public List<Node> evaluate(Element context) + { + compileIfNeeded(); + try + { + NodeList result = (NodeList)_compiled.evaluate(context, XPathConstants.NODESET); + List<Node> ret = new ArrayList<Node>(result.getLength()); + for (int ii = 0 ; ii < result.getLength() ; ii++) + { + ret.add(result.item(ii)); + } + return ret; + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>STRING</code> return type. + */ + public String evaluateAsString(Document context) + { + return evaluateAsString(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>STRING</code> return type. + */ + public String evaluateAsString(Element context) + { + compileIfNeeded(); + try + { + return _compiled.evaluate(context); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Document context) + { + return evaluateAsNumber(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>NUMBER</code> return type. + */ + public Double evaluateAsNumber(Element context) + { + compileIfNeeded(); + try + { + return (Double)_compiled.evaluate(context, XPathConstants.NUMBER); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + + /** + * Applies this expression to the root of the specified document, + * requesting the <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Document context) + { + return evaluateAsBoolean(context.getDocumentElement()); + } + + + /** + * Applies this expression to the specified element, requesting the + * <code>BOOLEAN</code> return type. + */ + public Boolean evaluateAsBoolean(Element context) + { + compileIfNeeded(); + try + { + return (Boolean)_compiled.evaluate(context, XPathConstants.BOOLEAN); + } + catch (Exception ee) + { + throw new XmlException("unable to evaluate: " + _expr, ee); + } + } + + +//---------------------------------------------------------------------------- +// 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 <code>equals()</code> 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; + } + + +//---------------------------------------------------------------------------- +// Internals +//---------------------------------------------------------------------------- + + /** + * Compiles the expression, if it has not already been compiled. This is + * called from the various <code>evaluate</code> methods, and ensures + * that the caller has completely configured the wrapper prior to use. + */ + private void compileIfNeeded() + { + if (_compiled != null) + return; + + try + { + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(_nsResolver); + xpath.setXPathVariableResolver(new MyVariableResolver()); + xpath.setXPathFunctionResolver(new MyFunctionResolver()); + _compiled = xpath.compile(_expr); + } + catch (XPathExpressionException ee) + { + throw new XmlException("unable to compile: " + _expr, ee); + } + } + + + /** + * Resolver for variable references. + */ + private class MyVariableResolver + implements XPathVariableResolver + { + public Object resolveVariable(QName name) + { + return _variables.get(name); + } + } + + + /** + * Resolver for function references. Note that we ignore the "arity" + * argument, meaning that there's a single binding per name. + */ + private class MyFunctionResolver + implements XPathFunctionResolver + { + public XPathFunction resolveFunction(QName name, int arity) + { + return _functions.get(name); + } + } +} Property changes on: trunk/src/main/java/net/sf/practicalxml/XPathWrapper.java ___________________________________________________________________ Added: svn:executable + * Added: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java =================================================================== --- trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java (rev 0) +++ trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java 2008-12-01 00:09:30 UTC (rev 36) @@ -0,0 +1,235 @@ +package net.sf.practicalxml; + +import java.util.List; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +public class TestXPathWrapper +extends AbstractTestCase +{ + public TestXPathWrapper(String name) + { + super(name); + } + + +//---------------------------------------------------------------------------- +// Test data +//---------------------------------------------------------------------------- + + public final static String EL_ROOT = "root"; + public final static String EL_CHILD = "child"; + public final static String NS1 = "ns1"; + public final static String NS2 = "ns2"; + + Document _dom; + Element _root; + Element _child1; + Element _child2; + Element _child3; + + @Override + protected void setUp() + { + _root = DomUtil.newDocument(EL_ROOT); + _child1 = DomUtil.appendChild(_root, EL_CHILD); + _child2 = DomUtil.appendChild(_root, NS1, EL_CHILD); + _child3 = DomUtil.appendChild(_root, NS2, EL_CHILD); + _dom = _root.getOwnerDocument(); + } + + +//---------------------------------------------------------------------------- +// Support Code +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +// Test Cases +//---------------------------------------------------------------------------- + + // the basic test to verify we can compile and execute + public void testCurrentElement() + throws Exception + { + XPathWrapper xpath = new XPathWrapper("."); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_root, result1.get(0)); + + List<Node> result2 = xpath.evaluate(_root); + assertEquals(1, result2.size()); + assertSame(_root, result2.get(0)); + + List<Node> result3 = xpath.evaluate(_child1); + assertEquals(1, result3.size()); + assertSame(_child1, result3.get(0)); + } + + + public void testEvalAsString() + throws Exception + { + _root.setAttribute("foo", "bar"); + _root.setAttribute("argle", "bargle"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals("bar", xpath.evaluateAsString(_root)); + assertEquals("bar", xpath.evaluateAsString(_dom)); + } + + + public void testEvalAsNumber() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath = new XPathWrapper("@foo"); + + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_root)); + assertEquals(Double.valueOf(10.0), xpath.evaluateAsNumber(_dom)); + } + + + public void testEvalAsBoolean() + throws Exception + { + _root.setAttribute("foo", "10"); + + XPathWrapper xpath1 = new XPathWrapper("@foo=10"); + + assertTrue(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertTrue(xpath1.evaluateAsBoolean(_dom).booleanValue()); + + _root.setAttribute("foo", "20"); + + assertFalse(xpath1.evaluateAsBoolean(_root).booleanValue()); + assertFalse(xpath1.evaluateAsBoolean(_dom).booleanValue()); + } + + + public void testNamespaces() throws Exception + { + XPathWrapper xpath1 = new XPathWrapper("//child"); + List<Node> result1 = xpath1.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + XPathWrapper xpath2 = new XPathWrapper("//ns:child") + .bindNamespace("ns", NS1); + List<Node> result2 = xpath2.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + + XPathWrapper xpath3 = new XPathWrapper("//:child") + .bindDefaultNamespace(NS2); + List<Node> result3 = xpath3.evaluate(_dom); + assertEquals(1, result3.size()); + assertSame(_child3, result3.get(0)); + } + + + public void testVariables() throws Exception + { + _child1.setAttribute("bar", "baz"); + _child2.setAttribute("bar", "bargle"); + + XPathWrapper xpath = new XPathWrapper("//*[@bar=$test]") + .bindVariable(new QName("test"), "baz"); + + List<Node> result1 = xpath.evaluate(_dom); + assertEquals(1, result1.size()); + assertSame(_child1, result1.get(0)); + + xpath.bindVariable("test", "bargle"); + + List<Node> result2 = xpath.evaluate(_dom); + assertEquals(1, result2.size()); + assertSame(_child2, result2.get(0)); + } + + + public void testFunctions() throws Exception + { + _child1.setAttribute("bar", "baz"); + _child2.setAttribute("bar", "bargle"); + + XPathFunction myfunc = new XPathFunction() { + public Object evaluate(List args) + throws XPathFunctionException + { + NodeList arg = (NodeList)args.get(0); + Element asElem = (Element)arg.item(0); + return asElem.getAttribute("bar"); + } + }; + + XPathWrapper xpath = new XPathWrapper("ns:myfunc(.)") + .bindNamespace("ns", NS1) + .bindFunction(new QName(NS1, "myfunc"), myfunc); + + assertEquals("baz", xpath.evaluateAsString(_child1)); + assertEquals("bargle", xpath.evaluateAsString(_child2)); + } + + + + public void testEqualsAndHashCode() throws Exception + { + Object obj1a = new XPathWrapper("//foo"); + Object obj1b = new XPathWrapper("//foo"); + Object obj2a = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj2b = new XPathWrapper("//foo") + .bindDefaultNamespace("zippy"); + Object obj3a = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj3b = new XPathWrapper("//foo") + .bindNamespace("argle", "bargle"); + Object obj4a = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj4b = new XPathWrapper("//foo") + .bindVariable("argle", "bargle"); + Object obj5a = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + Object obj5b = new XPathWrapper("//foo") + .bindFunction(new QName("foo"), null); + + assertTrue(obj1a.equals(obj1b)); + assertTrue(obj1b.equals(obj1a)); + assertEquals(obj1a.hashCode(), obj1b.hashCode()); + + assertFalse(obj1a.equals(obj2a)); + assertTrue(obj2a.equals(obj2b)); + assertTrue(obj2b.equals(obj2a)); + assertEquals(obj1a.hashCode(), obj2a.hashCode()); + assertEquals(obj2a.hashCode(), obj2b.hashCode()); + + assertFalse(obj1a.equals(obj3a)); + assertTrue(obj3a.equals(obj3b)); + assertTrue(obj3b.equals(obj3a)); + assertEquals(obj1a.hashCode(), obj3a.hashCode()); + assertEquals(obj3a.hashCode(), obj3b.hashCode()); + + assertFalse(obj1a.equals(obj4a)); + assertTrue(obj4a.equals(obj4b)); + assertTrue(obj4b.equals(obj4a)); + assertEquals(obj1a.hashCode(), obj4a.hashCode()); + assertEquals(obj4a.hashCode(), obj4b.hashCode()); + + assertFalse(obj1a.equals(obj5a)); + assertTrue(obj5a.equals(obj5b)); + assertTrue(obj5b.equals(obj5a)); + assertEquals(obj1a.hashCode(), obj5a.hashCode()); + assertEquals(obj5a.hashCode(), obj5b.hashCode()); + } +} Property changes on: trunk/src/test/java/net/sf/practicalxml/TestXPathWrapper.java ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |