Pablojim
Pablojim

Reputation: 8582

What's a simple way in java to evaluate an xpath on a string and return a result string

A simple answer needed to a simple question.

For example:

String xml = "<car><manufacturer>toyota</manufacturer></car>";
String xpath = "/car/manufacturer";
assertEquals("toyota",evaluate(xml, xpath));

How can I write the evaluate method in simple and readable way that will work for any given well-formed xml and xpath.

Obviously there are loads of ways this can be achieved but the majority seem very verbose.

Any simple ways I'm missing/libraries that can achieve this?

For cases where multiple nodes are returned I just want the string representation of this.

Upvotes: 20

Views: 20609

Answers (4)

Phlip
Phlip

Reputation: 5343

I have written assertXPath() in three languages so far. Ruby and Python are the best because they can also parse HTML with its idiosyncrasies via libxml2 and then run XPaths on them. For XML, or for carefully controlled HTML that doesn't have glitches like < for a JavaScript "less than", here's my assertion suite:

private static final XPathFactory xpathFactory = XPathFactory.newInstance();
private static final XPath xpath = xpathFactory.newXPath();

private static @NonNull Document assertHtml(@NonNull String xml) {
    try {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(false);
            DocumentBuilder builder = factory.newDocumentBuilder();
            ByteArrayInputStream stream = new ByteArrayInputStream(xml.replaceAll("i < len;", "i &lt; len;").getBytes());  //  Because JavaScript ruined HTML's ability to someday be real XML...
            return builder.parse(stream);
        } catch (SAXParseException e) {
            if (e.getLocalizedMessage().startsWith("Unexpected token") && !xml.startsWith("<xml>"))
                return assertHtml("<xml>" + xml + "</xml>");

            throw e;  //  a GOTO to 2 lines down...
        }
    } catch (Throwable e) {
        fail(e.getLocalizedMessage());
    }
    return null;
}

private static @NonNull List<String> assertXPaths(@NonNull Node node, @NonNull String xpathExpression)
{
    NodeList nodes = evaluateXPath(node, xpathExpression);
    List<String> values = new ArrayList<>();

    if (nodes != null)
        for (int i = 0; i < nodes.getLength(); i++) {
            Node item = nodes.item(i);
              //  item.getTextContent();
              //  item.getNodeName();
            values.add(item.getNodeValue());
        }

    if (values.size() == 0)
        fail("XPath not found: " + xpathExpression + "\n\nin: " + nodeToString(node) + "\n");

    return values;
}

private static @NonNull Node assertXPath(@NonNull Node node, @NonNull String xpathExpression)
{
    NodeList nodes = evaluateXPath(node, xpathExpression);

    if (nodes != null && nodes.getLength() > 0)
        return nodes.item(0);

    fail("XPath not found: " + xpathExpression + "\n\nin: " + nodeToString(node) + "\n");
    return null;  //  this can't happen
}

private static NodeList evaluateXPath(@NonNull Node node, @NonNull String xpathExpression) {
    NodeList nodes = null;

    try {
        XPathExpression expr = xpath.compile(xpathExpression);
        nodes = (NodeList) expr.evaluate(node, XPathConstants.NODESET);
    } catch (XPathExpressionException e) {
        fail(e.getLocalizedMessage());
    }
    return nodes;
}

private static void assertXPath(Node node, String xpathExpression, String reference) {
    List<String> nodes = assertXPaths(node, xpathExpression);
    assertEquals(1, nodes.size());  // CONSIDER  decorate these assertion diagnostics with nodeToString(). And don't check for one text() - join them all together
    assertEquals(reference, nodes.get(0).trim());  // CONSIDER  same complaint:  We need to see the nodeToString() here
}

private static void refuteXPath(@NonNull Node node, @NonNull String xpathExpression) {
    NodeList nodes = evaluateXPath(node, xpathExpression);

    if (nodes.getLength() != 0)
        fail("XPath should not be found: " + xpathExpression);  // CONSIDER  decorate this with the contents of the node
}

private static @NonNull String nodeToString(@NonNull Node node) {
    StringWriter sw = new StringWriter();
    Transformer t = null;

    try {
        t = TransformerFactory.newInstance().newTransformer();
    } catch (TransformerConfigurationException e) {
        fail(e.getLocalizedMessage());
    }

    t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    t.setOutputProperty(OutputKeys.INDENT, "yes");

    try {
        t.transform(new DOMSource(node), new StreamResult(sw));
    } catch (TransformerException e) {
        fail(e.getLocalizedMessage());
    }

    return sw.toString();
}

Use those recursively, like this:

    Document doc = assertHtml(myHtml);
    Node blockquote = assertXPath(doc, "//blockquote[ 'summary_7' = @id ]");

    assertXPath(blockquote, ".//span[ contains(., 'Mammal') and strong/text() = 'anteater' ]");

The benefit of finding a node, then asserting a path relative to that node (via .//) is at failure time nodeToString() will only report the node contents, such as my <blockquote>. The assertion diagnostic message won't contain the entire document, making it very easy to read.

Upvotes: 0

oldo
oldo

Reputation: 2202

Using the Xml class from https://github.com/guppy4j/libraries/tree/master/messaging-impl :

Xml xml = new Xml("<car><manufacturer>toyota</manufacturer></car>");
assertEquals("toyota", xml.get("/car/manufacturer"));

Upvotes: 0

vanje
vanje

Reputation: 10383

For this use case the XMLUnit library may be a perfect fit: http://xmlunit.sourceforge.net/userguide/html/index.html#Xpath%20Tests

It provides some additional assert methods.

For example:

assertXpathEvaluatesTo("toyota", "/car/manufacturer",
    "<car><manufacturer>toyota</manufacturer></car>");

Upvotes: 7

bdoughan
bdoughan

Reputation: 149047

Here you go, the following can be done with Java SE:

import java.io.StringReader;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import org.xml.sax.InputSource;

public class Demo {

    public static void main(String[] args) throws Exception {
        String xml = "<car><manufacturer>toyota</manufacturer></car>";
        String xpath = "/car/manufacturer";
        XPath xPath = XPathFactory.newInstance().newXPath();
        assertEquals("toyota",xPath.evaluate(xpath, new InputSource(new StringReader(xml))));
    }

}

Upvotes: 38

Related Questions