Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixes #68 - ".." XPath does not work for Attributes.

Also move XPathHelper from contrib to core. Include battery of tests.
  • Loading branch information...
commit 59b49330b29f671ab3a45fe356cffabe55533930 1 parent 555d6a7
@rolfl rolfl authored
View
568 core/src/java/org/jdom2/xpath/XPathHelper.java
@@ -0,0 +1,568 @@
+/*--
+
+ Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions, and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions, and the disclaimer that follows
+ these conditions in the documentation and/or other materials
+ provided with the distribution.
+
+ 3. The name "JDOM" must not be used to endorse or promote products
+ derived from this software without prior written permission. For
+ written permission, please contact <request_AT_jdom_DOT_org>.
+
+ 4. Products derived from this software may not be called "JDOM", nor
+ may "JDOM" appear in their name, without prior written permission
+ from the JDOM Project Management <request_AT_jdom_DOT_org>.
+
+ In addition, we request (but do not require) that you include in the
+ end-user documentation provided with the redistribution and/or in the
+ software itself an acknowledgement equivalent to the following:
+ "This product includes software developed by the
+ JDOM Project (http://www.jdom.org/)."
+ Alternatively, the acknowledgment may be graphical using the logos
+ available at http://www.jdom.org/images/logos.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+
+ This software consists of voluntary contributions made by many
+ individuals on behalf of the JDOM Project and was originally
+ created by Jason Hunter <jhunter_AT_jdom_DOT_org> and
+ Brett McLaughlin <brett_AT_jdom_DOT_org>. For more information
+ on the JDOM Project, please see <http://www.jdom.org/>.
+
+ */
+
+package org.jdom2.xpath;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom2.Attribute;
+import org.jdom2.Comment;
+import org.jdom2.Content;
+import org.jdom2.Element;
+import org.jdom2.Namespace;
+import org.jdom2.NamespaceAware;
+import org.jdom2.ProcessingInstruction;
+import org.jdom2.Text;
+import org.jdom2.filter.Filters;
+
+/**
+ * Provides a set of utility methods to generate XPath expressions to select a
+ * given node in a document. You can generate absolute XPath expressions to the
+ * target node, or relative expressions from a specified start point. If you
+ * request a relative expression, the start and target nodes must share some
+ * common ancestry.
+ * <p>
+ * XPaths are required to be namespace-aware. Typically this is done by using
+ * a namespace-prefixed query, with the actual namespace URI being set in the
+ * context of the XPath expression. This is not possible to express using a
+ * simple String return value. As a work-around, this method uses a potentially
+ * slower, but more reliable, mechanism for ensuring the correct namespace
+ * context is selected. The mechanism will appear like (for Elements):
+ * <br>
+ * <code> .../*[local-name() = 'tag' and namespace-uri() = 'uri']</code>
+ * <br>
+ * Similarly, Attributes will have a syntx similar to:
+ * <br>
+ * <code> .../@*[local-name() = 'attname' and namespace-uri() = 'uri'] </code>
+ * <br>
+ * This mechanism makes it possible to have a simple namespace context, and a
+ * simple String value returned from the methods on this class.
+ * <p>
+ * This class does not provide ways to access document-level content. Nor does
+ * it provide ways to access data relative to the Document level. Use absolute
+ * methods to access data from the Document level.
+ * <p>
+ * The methods on this class assume that the Document is above the top-most
+ * Element in the XML tree. The top-most Element is the one that does not have
+ * a parent Element (although it may have a parent Document). As a result, you
+ * can use Element data that is not attached to a JDOM Document.
+ * <p>
+ * Detatched Attributes, and detached non-Element content are not treated the
+ * same. If you try to get an Absolute path to a detached Attribute or
+ * non-Element Content you will get an IllegalArgumentException. On the other
+ * hand it is legal to get the relative XPath for a detached node to itself (
+ * but to some other node will cause an IllegalArgumentException because the
+ * nodes do not share a common ancestor).
+ * <p>
+ * <strong>Note</strong>: As this class has no knowledge of the document
+ * content, the generated XPath expression rely on the document structure. Hence
+ * any modification of the structure of the document may invalidate the
+ * generated XPaths.
+ * </p>
+ *
+ * @author Laurent Bihanic
+ * @author Rolf Lear
+ */
+public final class XPathHelper {
+
+ /**
+ * Private constructor.
+ */
+ private XPathHelper () {
+ // make constructor inaccessible.
+ }
+
+ /**
+ * Appends the specified path token to the provided buffer followed by the
+ * position specification of the target node in its siblings list (if
+ * needed).
+ *
+ * @param node
+ * the target node for the XPath expression.
+ * @param siblings
+ * the siblings of the target node.
+ * @param pathToken
+ * the path token identifying the target node.
+ * @param buffer
+ * the buffer to which appending the XPath sub-expression or
+ * <code>null</code> if the method shall allocate a new buffer.
+ * @return the XPath sub-expression to select the target node among its
+ * siblings.
+ */
+ private static StringBuilder getPositionPath(Object node, List<?> siblings,
+ String pathToken, StringBuilder buffer) {
+
+ buffer.append(pathToken);
+
+ if (siblings != null) {
+ int position = 0;
+ final Iterator<?> i = siblings.iterator();
+ while (i.hasNext()) {
+ position++;
+ if (i.next() == node)
+ break;
+ }
+ if (position > 1 || i.hasNext()) {
+ // the item is not at the first location, ot there are more
+ // locations. in other words, indexing is required.
+ buffer.append('[').append(position).append(']');
+ }
+ }
+ return buffer;
+ }
+
+ /**
+ * Calculate a single stage of an XPath query.
+ *
+ * @param nsa
+ * The token to get the relative-to-parent XPath for
+ * @param buffer
+ * The buffer to append the relative stage to
+ * @return The same buffer as was input.
+ */
+ private static final StringBuilder getSingleStep(final NamespaceAware nsa,
+ final StringBuilder buffer) {
+ if (nsa instanceof Content) {
+
+ final Content content = (Content) nsa;
+
+ final Element pnt = content.getParentElement();
+
+ if (content instanceof Text) { // OR CDATA!
+
+ final List<?> sibs = pnt == null ? null :
+ pnt.getContent(Filters.text()); // CDATA
+ return getPositionPath(content, sibs, "text()", buffer);
+
+ } else if (content instanceof Comment) {
+
+ final List<?> sibs = pnt == null ? null :
+ pnt.getContent(Filters.comment());
+ return getPositionPath(content, sibs, "comment()", buffer);
+
+ } else if (content instanceof ProcessingInstruction) {
+
+ final List<?> sibs = pnt == null ? null :
+ pnt.getContent(Filters.processinginstruction());
+ return getPositionPath(content, sibs,
+ "processing-instruction()", buffer);
+
+ } else if (content instanceof Element &&
+ ((Element) content).getNamespace() == Namespace.NO_NAMESPACE) {
+
+ // simple XPath to a no-namespace Element.
+
+ final String ename = ((Element) content).getName();
+ final List<?> sibs = pnt == null ? null : pnt
+ .getChildren(ename);
+ return getPositionPath(content, sibs, ename, buffer);
+
+ } else if (content instanceof Element) {
+
+ // complex XPath to an Element with Namespace...
+ // we do not want to have to prefix namespaces because that is
+ // essentially impossible to get right with the new JDOM2 API.
+ final Element emt = (Element)content;
+
+ // Note, the getChildren compares only the URI (not the prefix)
+ // so the results are the same as an XPath would be.
+ final List<?> sibs = pnt == null ? null :
+ pnt.getChildren(emt.getName(), emt.getNamespace());
+ String xps = "*[local-name() = '" + emt.getName() +
+ "' and namespace-uri() = '" +
+ emt.getNamespaceURI() + "']";
+ return getPositionPath(content, sibs, xps, buffer);
+
+ } else {
+
+ final List<?> sibs = pnt == null ? Collections
+ .singletonList(nsa) : pnt.getContent();
+ return getPositionPath(content, sibs, "node()", buffer);
+
+ }
+ } else if (nsa instanceof Attribute) {
+ Attribute att = (Attribute) nsa;
+ if (att.getNamespace() == Namespace.NO_NAMESPACE) {
+ buffer.append("@").append(att.getName());
+ } else {
+ buffer.append("@*[local-name() = '").append(att.getName());
+ buffer.append("' and namespace-uri() = '");
+ buffer.append(att.getNamespaceURI()).append("']");
+ }
+ }
+
+ // do nothing...
+ return buffer;
+ }
+
+ /**
+ * Returns the path to the specified <code>to</code>Element from the
+ * specified <code>from</code> Element. The from Element must have a common
+ * ancestor Element with the to Element.
+ * <p>
+ *
+ * @param from
+ * the Element the generated path shall select relative to.
+ * @param to
+ * the Content the generated path shall select.
+ * @param sb
+ * the StringBuilder to append the path to.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if the from and to Elements have no common ancestor.
+ */
+ private static StringBuilder getRelativeElementPath(final Element from,
+ final Element to, final StringBuilder sb) {
+ if (from == to) {
+ sb.append(".");
+ return sb;
+ }
+
+ // ToStack will be a chain of Elements from the to element to the
+ // root element, but will be 'short' if it encounters the from Element
+ // itself.
+ final ArrayList<Element> tostack = new ArrayList<Element>();
+ Element e = to;
+ while (e != null && e != from) {
+ tostack.add(e);
+ e = e.getParentElement();
+ }
+
+ // the number of steps we will have in the resulting path (potentially)
+ int pos = tostack.size();
+
+ if (e != from) {
+ // the from is not a direct ancestor of the to.
+ // we need to find where the common ancestor is between from and to
+ // we use the 'pos' variable to locate the common ancestor.
+ // pos is a pointer in to the tostack.
+ Element f = from;
+ int fcnt = 0;
+ // note that we search for 'pos' here... it will be set.
+ while (f != null && (pos = locate(f, tostack)) < 0) {
+ // go up the from ELement's ancestry until we intersect with the
+ // to Element's Ancestry.
+ fcnt++;
+ f = f.getParentElement();
+ }
+ if (f == null) {
+ throw new IllegalArgumentException(
+ "The 'from' and 'to' Element have no common ancestor.");
+ }
+ // OK, we have counted how far up the ancestry we need to go, so
+ // add the steps to the XPath.
+ while (--fcnt >= 0) {
+ sb.append("../");
+ }
+ }
+ // we have the common point in the ancestry, indicated by 'pos'.
+ // we walk down the 'to' side of the tree until we get to the target.
+ while (--pos >= 0) {
+ getSingleStep(tostack.get(pos), sb);
+ sb.append("/");
+ }
+ // we automatically append '/' in the loop, so we remove the last '/'
+ sb.setLength(sb.length() - 1);
+ return sb;
+ }
+
+ /**
+ * Do an identity search in an array for a specific value.
+ *
+ * @param f
+ * The Element to search for.
+ * @param tostack
+ * The list to search in.
+ * @return the position of the f value in the tostack.
+ */
+ private static int locate(final Element f, final List<Element> tostack) {
+ // a somewhat naive search... ArrayList it is fast enough though.
+ int ret = tostack.size();
+ while (--ret >= 0) {
+ if (f == tostack.get(ret)) {
+ return ret;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the relative path from the given from Content to the specified to
+ * Content as an XPath expression.
+ *
+ * @param from
+ * the Content from which the the generated path shall be applied.
+ * @param to
+ * the Content the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> and <code>from</code> are not part of the same
+ * XML tree
+ */
+ public static String getRelativePath(final Content from, final Content to) {
+ if (from == null) {
+ throw new NullPointerException(
+ "Cannot create a path from a null target");
+ }
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null target");
+ }
+ StringBuilder sb = new StringBuilder();
+ if (from == to) {
+ return ".";
+ }
+ final Element efrom = (from instanceof Element) ? (Element) from : from
+ .getParentElement();
+ if (from != efrom) {
+ sb.append("../");
+ }
+ if (to instanceof Element) {
+ getRelativeElementPath(efrom, (Element) to, sb);
+ } else {
+ final Element telement = to.getParentElement();
+ if (telement == null) {
+ throw new IllegalArgumentException(
+ "Cannot get a relative XPath to detached content.");
+ }
+ getRelativeElementPath(efrom, telement, sb);
+ sb.append("/");
+ getSingleStep(to, sb);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the relative path from the given from Content to the specified to
+ * Attribute as an XPath expression.
+ *
+ * @param from
+ * the Content from which the the generated path shall be applied.
+ * @param to
+ * the Attribute the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> and <code>from</code> are not part of the same
+ * XML tree
+ */
+ public static String getRelativePath(final Content from, final Attribute to) {
+ if (from == null) {
+ throw new NullPointerException(
+ "Cannot create a path from a null Content");
+ }
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null Attribute");
+ }
+ final Element t = to.getParent();
+ if (t == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a path to detached Attribute");
+ }
+ StringBuilder sb = new StringBuilder(getRelativePath(from, t));
+ sb.append("/");
+ getSingleStep(to, sb);
+ return sb.toString();
+ }
+
+ /**
+ * Returns the relative path from the given from Attribute to the specified
+ * to Attribute as an XPath expression.
+ *
+ * @param from
+ * the Attribute from which the the generated path shall be applied.
+ * @param to
+ * the Attribute the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> and <code>from</code> are not part of the same
+ * XML tree
+ */
+ public static String getRelativePath(final Attribute from,
+ final Attribute to) {
+ if (from == null) {
+ throw new NullPointerException(
+ "Cannot create a path from a null 'from'");
+ }
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null target");
+ }
+ if (from == to) {
+ return ".";
+ }
+
+ final Element f = from.getParent();
+ if (f == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a path from a detached attrbibute");
+ }
+
+ return "../" + getRelativePath(f, to);
+ }
+
+ /**
+ * Returns the relative path from the given from Attribute to the specified
+ * to Content as an XPath expression.
+ *
+ * @param from
+ * the Attribute from which the the generated path shall be applied.
+ * @param to
+ * the Content the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> and <code>from</code> are not part of the same
+ * XML tree
+ */
+ public static String getRelativePath(final Attribute from, final Content to) {
+ if (from == null) {
+ throw new NullPointerException(
+ "Cannot create a path from a null 'from'");
+ }
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null target");
+ }
+ final Element f = from.getParent();
+ if (f == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a path from a detached attrbibute");
+ }
+ if (f == to) {
+ return "..";
+ }
+ return "../" + getRelativePath(f, to);
+ }
+
+ /**
+ * Returns the absolute path to the specified to Content.
+ *
+ * @param to
+ * the Content the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> is not an Element and it is detached.
+ */
+ public static String getAbsolutePath(final Content to) {
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null target");
+ }
+
+ final StringBuilder sb = new StringBuilder();
+
+ final Element t = (to instanceof Element) ? (Element) to : to
+ .getParentElement();
+ if (t == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a path to detached target");
+ }
+ Element r = t;
+ while (r.getParentElement() != null) {
+ r = r.getParentElement();
+ }
+ sb.append("/");
+ getSingleStep(r, sb);
+ if (r != t) {
+ sb.append("/");
+ getRelativeElementPath(r, t, sb);
+ }
+ if (t != to) {
+ sb.append("/");
+ getSingleStep(to, sb);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns the absolute path to the specified to Content.
+ *
+ * @param to
+ * the Content the generated path shall select.
+ * @return an XPath expression to select the specified node.
+ * @throws IllegalArgumentException
+ * if <code>to</code> is detached.
+ */
+ public static String getAbsolutePath(final Attribute to) {
+ if (to == null) {
+ throw new NullPointerException(
+ "Cannot create a path to a null target");
+ }
+
+ final Element t = to.getParent();
+ if (t == null) {
+ throw new IllegalArgumentException(
+ "Cannot create a path to detached target");
+ }
+ Element r = t;
+ while (r.getParentElement() != null) {
+ r = r.getParentElement();
+ }
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append("/");
+ getSingleStep(r, sb);
+ if (t != r) {
+ sb.append("/");
+ getRelativeElementPath(r, t, sb);
+ }
+ sb.append("/");
+ getSingleStep(to, sb);
+ return sb.toString();
+ }
+
+}
View
2  core/src/java/org/jdom2/xpath/jaxen/JDOMCoreNavigator.java
@@ -316,6 +316,8 @@ public final Object getParentNode(Object contextNode) throws UnsupportedAxisExce
p = ((Content)contextNode).getParent();
} else if (contextNode instanceof NamespaceContainer) {
p = ((NamespaceContainer)contextNode).getParentElement();
+ } else if (contextNode instanceof Attribute) {
+ p = ((Attribute)contextNode).getParent();
}
if (p != null) {
return new SingleObjectIterator(p);
View
3  core/src/java/org/jdom2/xpath/package.html
@@ -9,4 +9,7 @@
Please see the web page for the details on the
<a href="https://github.com/hunterhacker/jdom/wiki/JDOM2-Feature:-XPath-Upgrade">
JDOM2 XPath API change</a>.
+ <p>
+ The XPathHelper class provides static methods to create XPath queries
+ that identify specific JDOM nodes.
</body>
View
26 test/resources/complex.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE root [ <!ELEMENT root (#PCDATA)> <!ENTITY unres 'Expand Me!' > ] >
+<root att1="val1" att2="val2" >
+ text
+ <child att="child1" xml:space="preserve"> hello Frodo Baggins! </child>
+ <child att="child2" />
+ <?jdomtest processing instruction ?>
+ <child att="child3" />
+ <child xmlns="childns" att="childns 1">
+ <!-- ns comment 1 -->
+ </child>
+ <!-- comment -->
+ <child att="child4" unresolved="&amp;"/>
+ <ns:child xmlns:ns="childns" att="childns 2" ns:att="childns 2">
+ <!-- ns comment 2 -->
+ <?jdomtest processing instruction ?>
+ </ns:child>
+ &unres; some unresolved content.
+ <child att="child5" > <![CDATA[some cdata text ]]> </child>
+ <child att="child6" >
+ <leaf att="Leaf6" />
+ </child>
+</root>
+
+
+
View
5 test/src/java/org/jdom2/test/cases/xpath/AbstractTestXPathCompiled.java
@@ -817,6 +817,11 @@ public void testAttributesNamespace() {
}
@Test
+ public void testAttributeParent() {
+ checkXPath("..", mainatt, null, main);
+ }
+
+ @Test
public void testXPathDefaultNamespacesFromElement() {
// the significance here is that the c3nsb namespace should already be
// available because it is in scope on the 'context' element.
View
339 test/src/java/org/jdom2/test/cases/xpath/TestXPathHepler.java
@@ -0,0 +1,339 @@
+package org.jdom2.test.cases.xpath;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+import org.jdom2.Attribute;
+import org.jdom2.CDATA;
+import org.jdom2.Comment;
+import org.jdom2.Content;
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.EntityRef;
+import org.jdom2.JDOMException;
+import org.jdom2.NamespaceAware;
+import org.jdom2.ProcessingInstruction;
+import org.jdom2.Text;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.test.util.UnitTestUtil;
+import org.jdom2.xpath.XPathDiagnostic;
+import org.jdom2.xpath.XPathExpression;
+import org.jdom2.xpath.XPathFactory;
+import org.jdom2.xpath.XPathHelper;
+
+@SuppressWarnings("javadoc")
+public class TestXPathHepler {
+
+ @Test
+ public void testGetPathStringElement() {
+ Element emt = new Element("root");
+ assertEquals("/root", XPathHelper.getAbsolutePath(emt));
+ Element kid = new Element("kid");
+ assertEquals("/kid", XPathHelper.getAbsolutePath(kid));
+ emt.addContent(kid);
+ assertEquals("/root/kid", XPathHelper.getAbsolutePath(kid));
+ }
+
+ private static final void checkAbsolute(NamespaceAware nsa) {
+ final XPathFactory xfac = XPathFactory.instance();
+ String xq = null;
+ if (nsa instanceof Attribute) {
+ xq = XPathHelper.getAbsolutePath((Attribute)nsa);
+ } else if (nsa instanceof Content) {
+ xq = XPathHelper.getAbsolutePath((Content)nsa);
+ } else {
+ xq = "/";
+ }
+ System.out.println("Running XPath for " + nsa + ": " + xq);
+ try {
+ final XPathExpression<Object> xp = xfac.compile(xq);
+ final XPathDiagnostic<Object> xd = xp.diagnose(nsa, false);
+ if (xd.getResult().size() != 1) {
+ fail ("expected exactly one result, not " + xd.getResult().size());
+ }
+ if (nsa != xd.getResult().get(0)) {
+ fail ("Expect the only result to be " + nsa + " but it was " +
+ xd.getResult().get(0));
+ }
+ } catch (IllegalArgumentException e) {
+ String xxq = null;
+ if (nsa instanceof Attribute) {
+ xxq = XPathHelper.getAbsolutePath((Attribute)nsa);
+ } else if (nsa instanceof Content) {
+ xxq = XPathHelper.getAbsolutePath((Content)nsa);
+ }
+
+ AssertionError ae = new AssertionError("Unable to compile expression '" + xxq + "' to node " + nsa);
+ ae.initCause(e);
+ throw ae;
+ }
+
+
+ }
+
+ private static final void checkRelative(final NamespaceAware nsa, final NamespaceAware nsb) {
+ final XPathFactory xfac = XPathFactory.instance();
+ String xq = null;
+ if (nsa instanceof Attribute) {
+ if (nsb instanceof Attribute) {
+ xq = XPathHelper.getRelativePath((Attribute)nsa, (Attribute)nsb);
+ } else {
+ xq = XPathHelper.getRelativePath((Attribute)nsa, (Content)nsb);
+ }
+ } else if (nsa instanceof Content) {
+ if (nsb instanceof Attribute) {
+ xq = XPathHelper.getRelativePath((Content)nsa, (Attribute)nsb);
+ } else {
+ xq = XPathHelper.getRelativePath((Content)nsa, (Content)nsb);
+ }
+ } else {
+ xq = "/";
+ }
+ final XPathExpression<Object> xp = xfac.compile(xq);
+ final XPathDiagnostic<Object> xd = xp.diagnose(nsa, false);
+ if(xd.getResult().size() != 1) {
+ fail ("Expected single result from " + nsa + " to " + nsb + " but got " + xd);
+ }
+ if (nsb != xd.getResult().get(0)) {
+ fail ("Expected single result to be " + nsb + " not " + xd.getResult());
+ }
+
+ }
+
+ /**
+ * This test loads up an XML document and calculates the absolute expression
+ * for each node, and also the expression for each node relative to *every*
+ * other node. It then runs every expression and ensures that the exact
+ * right node is selected.
+ * The input XML document is designed to have all sorts of tricky nodes to
+ * process. This ensures that all (for a limited set of 'all') combinations
+ * of valid input data are tested.
+ *
+ * @throws JDOMException If the document fails to parse.
+ * @throws IOException
+ */
+ @Test
+ public void testComplex() throws JDOMException, IOException {
+ SAXBuilder sb = new SAXBuilder();
+ sb.setExpandEntities(false);
+ Document doc = sb.build(new File("test/resources/complex.xml"));
+ final Iterator<Content> des = doc.getDescendants();
+ final ArrayList<NamespaceAware> allc = new ArrayList<NamespaceAware>();
+ while (des.hasNext()) {
+ final Content c = des.next();
+ if (c.getParent() == doc && c != doc.getRootElement()) {
+ // ignore document level content (except root element.
+ continue;
+ }
+ checkAbsolute(c);
+ allc.add(c);
+ if (c instanceof Element) {
+ if (((Element) c).hasAttributes()) {
+ for (Attribute a : ((Element)c).getAttributes()) {
+ checkAbsolute(a);
+ allc.add(a);
+ }
+ }
+ }
+ }
+ for (NamespaceAware nsa : allc) {
+ for (NamespaceAware nsb : allc) {
+ checkRelative(nsa, nsb);
+ }
+ }
+ }
+
+ @Test
+ public void testGetAbsolutePathAttribute() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Attribute att = null;
+ XPathHelper.getAbsolutePath(att);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Attribute att = new Attribute("detached", "value");
+ XPathHelper.getAbsolutePath(att);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ }
+
+ @Test
+ public void testGetAbsolutePathContent() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Text att = null;
+ XPathHelper.getAbsolutePath(att);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Text att = new Text("detached");
+ XPathHelper.getAbsolutePath(att);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ }
+
+ @Test
+ public void testGetRelativePathAttributeAttribute() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Attribute atta = new Attribute("att", "value");
+ final Attribute attb = null;
+ XPathHelper.getRelativePath(atta, attb);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Attribute atta = new Attribute("att", "value");
+ final Attribute attb = new Attribute("detached", "value");
+ XPathHelper.getRelativePath(atta, attb);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ try {
+ final Attribute atta = null;
+ final Attribute attb = new Attribute("att", "value");
+ XPathHelper.getRelativePath(atta, attb);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ }
+
+ @Test
+ public void testGetRelativePathContentAttribute() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Element root = new Element("root");
+ final Attribute att = null;
+ XPathHelper.getRelativePath(root, att);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Element root = new Element("root");
+ final Attribute att = new Attribute("detached", "value");
+ XPathHelper.getRelativePath(root, att);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Element root = null;
+ final Attribute att = new Attribute("att", "value");
+ XPathHelper.getRelativePath(root, att);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ }
+
+ @Test
+ public void testGetRelativePathAttributeContent() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Attribute att = null;
+ final Element root = new Element("root");
+ XPathHelper.getRelativePath(att, root);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Attribute att = new Attribute("detached", "value");
+ final Element root = new Element("root");
+ XPathHelper.getRelativePath(att, root);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ try {
+ final Attribute att = new Attribute("detached", "value");
+ final Element root = null;
+ XPathHelper.getRelativePath(att, root);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ }
+
+ @Test
+ public void testGetRelativePathContentContent() {
+ // testComplex() covers working case. We need to do negative testing
+ try {
+ final Element root = new Element("root");
+ final Text att = null;
+ XPathHelper.getRelativePath(root, att);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ try {
+ final Element root = new Element("root");
+ final Text att = new Text("detached");
+ XPathHelper.getRelativePath(root, att);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ try {
+ // no common ancestor
+ final Element roota = new Element("root");
+ final Element rootb = new Element("root");
+ XPathHelper.getRelativePath(roota, rootb);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ try {
+ final Element roota = null;
+ final Element rootb = new Element("root");
+ XPathHelper.getRelativePath(roota, rootb);
+ UnitTestUtil.failNoException(NullPointerException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(NullPointerException.class, e);
+ }
+ }
+
+ private void checkDetached(final NamespaceAware nsa) {
+ try {
+ checkAbsolute(nsa);
+ UnitTestUtil.failNoException(IllegalArgumentException.class);
+ } catch (Exception e) {
+ UnitTestUtil.checkException(IllegalArgumentException.class, e);
+ }
+ checkRelative(nsa, nsa);
+ }
+
+ @Test
+ public void testDetached() {
+ // non-Element content...
+ checkDetached(new Text("detached"));
+ checkDetached(new CDATA("detached"));
+ checkDetached(new Attribute("detached", "value"));
+ checkDetached(new ProcessingInstruction("detached"));
+ checkDetached(new EntityRef("detached"));
+ checkDetached(new Comment("detached"));
+
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.