[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.
|