Reputation: 8582
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
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 < 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
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
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
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