【问题标题】:Compare two XML strings ignoring element order比较两个忽略元素顺序的 XML 字符串
【发布时间】:2013-05-08 13:33:50
【问题描述】:

假设我有两个 xml 字符串

<test>
  <elem>a</elem>
  <elem>b</elem>
</test>

<test>
  <elem>b</elem>
  <elem>a</elem>
</test>

如何编写一个比较这两个字符串并忽略元素顺序的测试?

我希望测试尽可能短,没有地方进行 10 行 XML 解析等。我正在寻找一个简单的断言或类似的东西。

我有这个(不起作用)

   Diff diff = XMLUnit.compareXML(expectedString, actualString);   
   XMLAssert.assertXMLEqual("meh", diff, true);

【问题讨论】:

  • 解析xml并将值添加到集合中
  • 有什么方法可以在 XMLUnit 中只用几行就完成吗?
  • 显然你应该根据你的情况来写。没有现成的解决方案,因为这是不寻常的要求。
  • 解析 XML 并将文本节点列表与其他列表进行比较。
  • 也许更容易获取 xpath (XPathExpression) 的所有值以构建该集合

标签: java unit-testing junit xmlunit


【解决方案1】:

对于 xmlunit 2.0(我一直在寻找这个),现在使用 DefaultNodeMatcher 完成了

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

希望这有助于其他人在谷歌上搜索...

【讨论】:

  • 是的,它有帮助,而且它是一个完美、纯粹的解决方案。
  • 2.4.0 版本是 DiffBuilder.compare... 而不是 Diffbuilder.compare...。
【解决方案2】:

XMLUnit 会做你想做的事,但你必须指定 elementQualifier。如果没有指定 elementQualifier,它只会比较相同位置的节点。

对于您的示例,您需要一个 ElementNameAndTextQualifer,如果存在与元素名称和它的文本值匹配的节点,这会考虑一个相似的节点,例如:

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

您可以在此处阅读更多信息:http://xmlunit.sourceforge.net/userguide/html/ar01s03.html#ElementQualifier

【讨论】:

  • 在我的例子中,XMLUnit 1.6 需要将 false 传递给第二个参数,以实现相似的比较而不是相同的比较。
【解决方案3】:

我原来的答案已经过时了。如果我必须再次构建它,我会使用 xmlunit 2 和 xmlunit-matchers。请注意,对于 xml 单元,不同的顺序总是“相似”不等于。

@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)));
}  

如果有人仍然不想在这里使用纯 java 实现,那就是。此实现从 xml 中提取内容并比较列表忽略顺序。

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()));
}

【讨论】:

  • xml dom;我加载它使用:DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xml)); return builder.parse(is);
  • 没关系,把set改成list就好了,我没说是set
  • 你说忽略元素顺序:D
  • +1:无论如何,加载 XML 然后在其上运行一些 XPath明确在 Java 中不是直接的...
【解决方案4】:

来自Compare XML ignoring order of child elements的交叉发帖

今天晚上我也有类似的需求,但找不到符合我要求的东西。

我的解决方法是对我想要比较的两个 XML 文件进行排序,按元素名称的字母顺序排序。一旦它们的顺序一致,我就可以使用常规的可视差异工具来区分这两个排序的文件。

如果这种方法对其他人有用,我已经在http://dalelane.co.uk/blog/?p=3225分享了我编写的用于排序的python脚本

【讨论】:

    【解决方案5】:

    仅作为一个示例,说明如何基于属性name 的相等性来比较更复杂的 xml 元素匹配。例如:

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

    对比

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

    使用以下自定义元素限定符:

    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);
    

    【讨论】:

      【解决方案6】:

      对我来说,我还需要在DiffBuilder 上添加方法:checkForSimilar()。 没有它,断言是错误的,说节点的顺序不一样(子列表中的位置不一样)

      我的代码是:

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

      【讨论】:

        【解决方案7】:

        我不知道他们为解决方案采用了哪些版本,但没有任何效果(或者至少很简单)所以这是我的解决方案,适用于有同样痛苦的人。

        附注我讨厌人们错过静态方法的导入或 FQN 类名

            @Test
        void given2XMLS_are_different_elements_sequence_with_whitespaces(){
            String testXml = "<struct><int>3</int>  <boolean>false</boolean> </struct>";
            String expected = "<struct><boolean>false</boolean><int>3</int></struct>";
        
            XmlAssert.assertThat(testXml).and(expected)
                    .ignoreWhitespace()
                    .normalizeWhitespace()
                    .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
                    .areSimilar();
        }
        

        【讨论】:

        • 我完全同意你在 FQN 上的看法。我在任何地方都找不到“isSimilarTo()”。顺便说一句,您的解决方案对我有用。
        【解决方案8】:

        选项 1
        如果 XML 代码很简单,试试这个:

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


        选项 2
        如果 XML 更复杂,请使用 XML 解析器加载它,并将找到的实际节点与您的参考节点进行比较。

        【讨论】:

        • @option2 : 有什么方法可以自动完成吗?
        【解决方案9】:

        我最终重写了 xml 并将其比较。让我知道它是否对您遇到类似问题的任何人有所帮助。

        import org.apache.commons.lang3.StringUtils;
        import org.jdom2.Attribute;
        import org.jdom2.Document;
        import org.jdom2.Element;
        import org.jdom2.input.SAXBuilder;
        import org.jdom2.output.Format;
        import org.jdom2.output.XMLOutputter;
        
        import java.io.ByteArrayInputStream;
        import java.nio.charset.StandardCharsets;
        import java.util.List;
        import java.util.stream.Collectors;
        
        public class XmlRewriter {
            private static String rewriteXml(String xml) throws Exception {
                SAXBuilder builder = new SAXBuilder();
                Document document = builder.build(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8)));
                Element root = document.getRootElement();
        
                XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat());
        
                root.sortChildren((o1, o2) -> {
                    if(!StringUtils.equals(o1.getName(), o2.getName())){
                        return o1.getName().compareTo(o2.getName());
                    }
                    // get attributes
                    int attrCompare = transformToStr(o1.getAttributes()).compareTo(transformToStr(o2.getAttributes()));
                    if(attrCompare!=0){
                        return attrCompare;
                    }
                    if(o1.getValue()!=null && o2.getValue()!=null){
                        return o1.getValue().compareTo(o2.getValue());
                    }
                    return 0;
                });
                return xmlOutputter.outputString(root);
            }
        
            private static String transformToStr(List<Attribute> attributes){
                return attributes.stream().map(e-> e.getName()+":"+e.getValue()).sorted().collect(Collectors.joining(","));
            }
        
            public static boolean areXmlSimilar(String xml1, String xml2) throws Exception {
                Diff diff = DiffBuilder.compare(rewriteXml(xml1)).withTest(rewriteXml(xml2))
                        .normalizeWhitespace()
                        .ignoreWhitespace()
                        .ignoreComments()
                        .checkForSimilar()
                        .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndText))
                        .build();
        
                return !diff.hasDifferences();
            }
        
        // move below into another test class.. 
            @Test
            public void compareXml() throws Exception {
                String xml1 = "<<your first XML str here>>";
                String xml2 = "<<another XML str here>>";
                assertTrue(XmlUtil.areXmlSimilar(xml1, xml2));
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2023-03-21
          • 1970-01-01
          • 2018-08-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-01
          • 2012-05-29
          • 1970-01-01
          相关资源
          最近更新 更多