Skip to content

Commit

Permalink
add byNameAndAllAttributes that uses an attrbute filter
Browse files Browse the repository at this point in the history
inspired by #259
  • Loading branch information
bodewig committed Mar 22, 2023
1 parent a7ca5be commit 5141915
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 4 deletions.
30 changes: 27 additions & 3 deletions xmlunit-core/src/main/java/org/xmlunit/diff/ElementSelectors.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.xmlunit.util.Predicate;
import org.xmlunit.xpath.JAXPXPathEngine;
import org.xmlunit.xpath.XPathEngine;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

Expand Down Expand Up @@ -84,23 +85,46 @@ && bothNullOrEqual(Nodes.getMergedNestedText(controlElement),
/**
* Elements with the same local name (and namespace URI - if any)
* and attribute values for all attributes can be compared.
*
* <p>This {@code ElementSelector} doesn't know anything about a potentially configured attribute filter so may also
* compare attributes that are excluded from comparison by the filter. Use {@link
* #byNameAndAllAttributes(Predicate)} passing in your attribute filter if this causes problems.</p>
*/
public static final ElementSelector byNameAndAllAttributes =
new ElementSelector() {
byNameAndAllAttributes(new Predicate<Attr>() {
@Override
public boolean test(Attr a) {
return true;
}
});

/**
* Elements with the same local name (and namespace URI - if any)
* and attribute values for all attributes can be compared.
*
* @param attributeFilter filter to use when comparing attributes. Only attributes where the filter returns {@code
* true} are considered.
*
* @since XMLUnit 2.9.2
*/
public static final ElementSelector byNameAndAllAttributes(final Predicate<Attr> attributeFilter) {
return new ElementSelector() {
@Override
public boolean canBeCompared(Element controlElement,
Element testElement) {
if (!byName.canBeCompared(controlElement, testElement)) {
return false;
}
Map<QName, String> cAttrs = Nodes.getAttributes(controlElement);
Map<QName, String> tAttrs = Nodes.getAttributes(testElement);
Map<QName, String> cAttrs = Nodes.getAttributes(controlElement, attributeFilter);
Map<QName, String> tAttrs = Nodes.getAttributes(testElement, attributeFilter);
if (cAttrs.size() != tAttrs.size()) {
return false;
}
return mapsEqualForKeys(cAttrs, tAttrs, cAttrs.keySet());
}
};
}

/**
* String Constants.
*/
Expand Down
21 changes: 20 additions & 1 deletion xmlunit-core/src/main/java/org/xmlunit/util/Nodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,32 @@ public static String getMergedNestedText(Node n) {
* @return attributes
*/
public static Map<QName, String> getAttributes(Node n) {
return getAttributes(n, new Predicate<Attr>() {
@Override
public boolean test(Attr a) {
return true;
}
});
}

/**
* Obtains an element's attributes as Map.
* @param n the node
* @param attributeFilter is used to suppress unwanted attributes. Only attributes where the filter's test returns
* {@code true} are returned
* @return attributes
* @since XMLUnit 2.9.2
*/
public static Map<QName, String> getAttributes(Node n, Predicate<Attr> attributeFilter) {
Map<QName, String> map = new LinkedHashMap<QName, String>();
NamedNodeMap m = n.getAttributes();
if (m != null) {
final int len = m.getLength();
for (int i = 0; i < len; i++) {
Attr a = (Attr) m.item(i);
map.put(getQName(a), a.getValue());
if (attributeFilter.test(a)) {
map.put(getQName(a), a.getValue());
}
}
}
return map;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
Expand Down Expand Up @@ -128,6 +129,35 @@ static void byNameAndText_SingleLevel(ElementSelector s, Document doc) {
.canBeCompared(control, differentNS));
}

@Test public void byNameAndAllAttributesWithFilter() {
Element control = doc.createElement(FOO);
control.setAttribute(BAR, BAR);
Element equal = doc.createElement(FOO);
equal.setAttribute(BAR, BAR);
equal.setAttribute("x", "y");
Element noAttributes = doc.createElement(FOO);
Element differentValue = doc.createElement(FOO);
differentValue.setAttribute(BAR, FOO);
Element differentName = doc.createElement(FOO);
differentName.setAttribute(FOO, FOO);
Element differentNS = doc.createElement(FOO);
differentNS.setAttributeNS(SOME_URI, BAR, BAR);
Predicate<Attr> filter = new Predicate<Attr>() {
@Override
public boolean test(Attr a) {
return BAR.equals(a.getName());
}
};
ElementSelector es = ElementSelectors.byNameAndAllAttributes(filter);

assertTrue(es.canBeCompared(control, equal));
assertFalse(es.canBeCompared(control, noAttributes));
assertFalse(es.canBeCompared(noAttributes, control));
assertFalse(es.canBeCompared(control, differentValue));
assertFalse(es.canBeCompared(control, differentName));
assertFalse(es.canBeCompared(control, differentNS));
}

@Test public void byNameAndAttributes_NamePart() {
pureElementNameComparisons(ElementSelectors
.byNameAndAttributes(new String[] {}));
Expand Down
14 changes: 14 additions & 0 deletions xmlunit-core/src/test/java/org/xmlunit/util/NodesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,20 @@ public class NodesTest {
assertEquals(BAR, m.get(new QName(SOME_URI, FOO, BAR)));
}

@Test public void attributeMapWithFilter() {
Element e = doc.createElement(FOO);
e.setAttribute(FOO, BAR);
e.setAttribute("x", "y");
Map<QName, String> m = Nodes.getAttributes(e, new Predicate<Attr>() {
@Override
public boolean test(Attr a) {
return FOO.equals(a.getName());
}
});
assertEquals(1, m.size());
assertEquals(BAR, m.get(new QName(FOO)));
}

private Document handleWsSetup() {
return Convert.toDocument(Input.fromString(
"<root>\n"
Expand Down

0 comments on commit 5141915

Please sign in to comment.