Compare two XML strings ignoring element order

给你一囗甜甜゛ 提交于 2019-11-29 05:32:50
user1121883

My original answer is outdated. If I would have to build it again i would use xmlunit 2 and xmlunit-matchers. Please note that for xml unit a different order is always 'similar' not equals.

@Test
public void testXmlUnit() {
    String myControlXML = "<test><elem>a</elem><elem>b</elem></test>";
    String expected = "<test><elem>b</elem><elem>a</elem></test>";
    assertThat(myControlXML, isSimilarTo(expected)
            .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
    //In case you wan't to ignore whitespaces add ignoreWhitespace().normalizeWhitespace()
    assertThat(myControlXML, isSimilarTo(expected)
        .ignoreWhitespace()
        .normalizeWhitespace()
        .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText)));
}  

If somebody still want't to use a pure java implementation here it is. This implementation extracts the content from xml and compares the list ignoring order.

public static Document loadXMLFromString(String xml) throws Exception {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    DocumentBuilder builder = factory.newDocumentBuilder();
    InputSource is = new InputSource(new StringReader(xml));
    return builder.parse(is);
}

@Test
public void test() throws Exception {
    Document doc = loadXMLFromString("<test>\n" +
            "  <elem>b</elem>\n" +
            "  <elem>a</elem>\n" +
            "</test>");
    XPathFactory xPathfactory = XPathFactory.newInstance();
    XPath xpath = xPathfactory.newXPath();
    XPathExpression expr = xpath.compile("//test//elem");
    NodeList all = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
    List<String> values = new ArrayList<>();
    if (all != null && all.getLength() > 0) {
        for (int i = 0; i < all.getLength(); i++) {
            values.add(all.item(i).getTextContent());
        }
    }
    Set<String> expected = new HashSet<>(Arrays.asList("a", "b"));
    assertThat("List equality without order",
            values, containsInAnyOrder(expected.toArray()));
}
Akzidenzgrotesk

For xmlunit 2.0 (I was looking for this) it is now done, by using DefaultNodeMatcher

Diff diff = Diffbuilder.compare(Input.fromFile(control))
   .withTest(Input.fromFile(test))
   .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
   .build()

Hope this helps this helps other people googling...

XMLUnit will do what you want, but you have to specify the elementQualifier. With no elementQualifier specified it will only compare the nodes in the same position.

For your example you want an ElementNameAndTextQualifer, this considers a node similar if one exists that matches the element name and it's text value, something like :

Diff diff = new Diff(control, toTest);
// we don't care about ordering
diff.overrideElementQualifier(new ElementNameAndTextQualifier());
XMLAssert.assertXMLEqual(diff, true);

You can read more about it here: http://xmlunit.sourceforge.net/userguide/html/ar01s03.html#ElementQualifier

dalelane

Cross-posting from Compare XML ignoring order of child elements

I had a similar need this evening, and couldn't find something that fit my requirements.

My workaround was to sort the two XML files I wanted to diff, sorting alphabetically by the element name. Once they were both in a consistent order, I could diff the two sorted files using a regular visual diff tool.

If this approach sounds useful to anyone else, I've shared the python script I wrote to do the sorting at http://dalelane.co.uk/blog/?p=3225

OPTION 1
If the XML code is simple, try this:

 String testString = ...
 assertTrue(testString.matches("(?m)^<test>(\\s*<elem>(a|b)</elem>\\s*){2}</test>$"));


OPTION 2
If the XML is more elaborate, load it with an XML parser and compare the actual nodes found with you reference nodes.

Stepan Vavra

Just as an example of how to compare more complex xml elements matching based on equality of attribute name. For instance:

<request>
     <param name="foo" style="" type="xs:int"/>
     <param name="Cookie" path="cookie" style="header" type="xs:string" />
</request>

vs.

<request>
     <param name="Cookie" path="cookie" style="header" type="xs:string" />
     <param name="foo" style="query" type="xs:int"/>
</request>

With following custom element qualifier:

final Diff diff = XMLUnit.compareXML(controlXml, testXml);
diff.overrideElementQualifier(new ElementNameAndTextQualifier() {

    @Override
    public boolean qualifyForComparison(final Element control, final Element test) {
        // this condition is copied from super.super class
        if (!(control != null && test != null
                      && equalsNamespace(control, test)
                      && getNonNamespacedNodeName(control).equals(getNonNamespacedNodeName(test)))) {
            return false;
        }

        // matching based on 'name' attribute
        if (control.hasAttribute("name") && test.hasAttribute("name")) {
            if (control.getAttribute("name").equals(test.getAttribute("name"))) {
                return true;
            }
        }
        return false;
    }
});
XMLAssert.assertXMLEqual(diff, true);
pierre

For me, I also needed to add the method : checkForSimilar() on the DiffBuilder. Without it, the assert was in error saying that the sequence of the nodes was not the same (the position in the child list was not the same)

My code was :

 Diff diff = Diffbuilder.compare(Input.fromFile(control))
   .withTest(Input.fromFile(test))
   .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
   .checkForSimilar()
   .build()
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!