| package org.hamcrest.xml; |
| |
| import org.hamcrest.Condition; |
| import org.hamcrest.Description; |
| import org.hamcrest.Matcher; |
| import org.hamcrest.TypeSafeDiagnosingMatcher; |
| import org.hamcrest.core.IsAnything; |
| import org.w3c.dom.Node; |
| |
| import javax.xml.namespace.NamespaceContext; |
| import javax.xml.namespace.QName; |
| import javax.xml.xpath.*; |
| |
| import static javax.xml.xpath.XPathConstants.STRING; |
| import static org.hamcrest.Condition.matched; |
| import static org.hamcrest.Condition.notMatched; |
| |
| /** |
| * Applies a Matcher to a given XML Node in an existing XML Node tree, specified by an XPath expression. |
| * |
| * @author Joe Walnes |
| * @author Steve Freeman |
| */ |
| public class HasXPath extends TypeSafeDiagnosingMatcher<Node> { |
| public static final NamespaceContext NO_NAMESPACE_CONTEXT = null; |
| private static final IsAnything<String> WITH_ANY_CONTENT = new IsAnything<String>(""); |
| private static final Condition.Step<Object,String> NODE_EXISTS = nodeExists(); |
| private final Matcher<String> valueMatcher; |
| private final XPathExpression compiledXPath; |
| private final String xpathString; |
| private final QName evaluationMode; |
| |
| /** |
| * @param xPathExpression XPath expression. |
| * @param valueMatcher Matcher to use at given XPath. |
| * May be null to specify that the XPath must exist but the value is irrelevant. |
| */ |
| public HasXPath(String xPathExpression, Matcher<String> valueMatcher) { |
| this(xPathExpression, NO_NAMESPACE_CONTEXT, valueMatcher); |
| } |
| |
| /** |
| * @param xPathExpression XPath expression. |
| * @param namespaceContext Resolves XML namespace prefixes in the XPath expression |
| * @param valueMatcher Matcher to use at given XPath. |
| * May be null to specify that the XPath must exist but the value is irrelevant. |
| */ |
| public HasXPath(String xPathExpression, NamespaceContext namespaceContext, Matcher<String> valueMatcher) { |
| this(xPathExpression, namespaceContext, valueMatcher, STRING); |
| } |
| |
| private HasXPath(String xPathExpression, NamespaceContext namespaceContext, Matcher<String> valueMatcher, QName mode) { |
| this.compiledXPath = compiledXPath(xPathExpression, namespaceContext); |
| this.xpathString = xPathExpression; |
| this.valueMatcher = valueMatcher; |
| this.evaluationMode = mode; |
| } |
| |
| @Override |
| public boolean matchesSafely(Node item, Description mismatch) { |
| return evaluated(item, mismatch) |
| .and(NODE_EXISTS) |
| .matching(valueMatcher); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| description.appendText("an XML document with XPath ").appendText(xpathString); |
| if (valueMatcher != null) { |
| description.appendText(" ").appendDescriptionOf(valueMatcher); |
| } |
| } |
| |
| private Condition<Object> evaluated(Node item, Description mismatch) { |
| try { |
| return matched(compiledXPath.evaluate(item, evaluationMode), mismatch); |
| } catch (XPathExpressionException e) { |
| mismatch.appendText(e.getMessage()); |
| } |
| return notMatched(); |
| } |
| |
| private static Condition.Step<Object, String> nodeExists() { |
| return new Condition.Step<Object, String>() { |
| @Override |
| public Condition<String> apply(Object value, Description mismatch) { |
| if (value == null) { |
| mismatch.appendText("xpath returned no results."); |
| return notMatched(); |
| } |
| return matched(String.valueOf(value), mismatch); |
| } |
| }; |
| } |
| |
| private static XPathExpression compiledXPath(String xPathExpression, NamespaceContext namespaceContext) { |
| try { |
| final XPath xPath = XPathFactory.newInstance().newXPath(); |
| if (namespaceContext != null) { |
| xPath.setNamespaceContext(namespaceContext); |
| } |
| return xPath.compile(xPathExpression); |
| } catch (XPathExpressionException e) { |
| throw new IllegalArgumentException("Invalid XPath : " + xPathExpression, e); |
| } |
| } |
| |
| |
| /** |
| * Creates a matcher of {@link org.w3c.dom.Node}s that matches when the examined node has a value at the |
| * specified <code>xPath</code> that satisfies the specified <code>valueMatcher</code>. |
| * For example: |
| * <pre>assertThat(xml, hasXPath("/root/something[2]/cheese", equalTo("Cheddar")))</pre> |
| * |
| * @param xPath |
| * the target xpath |
| * @param valueMatcher |
| * matcher for the value at the specified xpath |
| */ |
| public static Matcher<Node> hasXPath(String xPath, Matcher<String> valueMatcher) { |
| return hasXPath(xPath, NO_NAMESPACE_CONTEXT, valueMatcher); |
| } |
| |
| /** |
| * Creates a matcher of {@link org.w3c.dom.Node}s that matches when the examined node has a value at the |
| * specified <code>xPath</code>, within the specified <code>namespaceContext</code>, that satisfies |
| * the specified <code>valueMatcher</code>. |
| * For example: |
| * <pre>assertThat(xml, hasXPath("/root/something[2]/cheese", myNs, equalTo("Cheddar")))</pre> |
| * |
| * @param xPath |
| * the target xpath |
| * @param namespaceContext |
| * the namespace for matching nodes |
| * @param valueMatcher |
| * matcher for the value at the specified xpath |
| */ |
| public static Matcher<Node> hasXPath(String xPath, NamespaceContext namespaceContext, Matcher<String> valueMatcher) { |
| return new HasXPath(xPath, namespaceContext, valueMatcher, STRING); |
| } |
| |
| /** |
| * Creates a matcher of {@link org.w3c.dom.Node}s that matches when the examined node contains a node |
| * at the specified <code>xPath</code>, with any content. |
| * For example: |
| * <pre>assertThat(xml, hasXPath("/root/something[2]/cheese"))</pre> |
| * |
| * @param xPath |
| * the target xpath |
| */ |
| public static Matcher<Node> hasXPath(String xPath) { |
| return hasXPath(xPath, NO_NAMESPACE_CONTEXT); |
| } |
| |
| /** |
| * Creates a matcher of {@link org.w3c.dom.Node}s that matches when the examined node contains a node |
| * at the specified <code>xPath</code> within the specified namespace context, with any content. |
| * For example: |
| * <pre>assertThat(xml, hasXPath("/root/something[2]/cheese", myNs))</pre> |
| * |
| * @param xPath |
| * the target xpath |
| * @param namespaceContext |
| * the namespace for matching nodes |
| */ |
| public static Matcher<Node> hasXPath(String xPath, NamespaceContext namespaceContext) { |
| return new HasXPath(xPath, namespaceContext, WITH_ANY_CONTENT, XPathConstants.NODE); |
| } |
| } |