web-dev-qa-db-ja.com

要素の順序を無視して2つのXML文字列を比較する

2つのxml文字列があるとします

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

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

これらの2つの文字列を比較し、要素の順序を無視するテストを作成する方法は?

テストをできるだけ短くし、10行のXML解析などを行う場所がないようにしたいのですが、単純なアサーションまたは類似のものを探しています。

私はこれを持っています(動作しません)

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

私の元の答えは時代遅れです。もう一度ビルドする必要がある場合は、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()));
}
10
user1121883

Xmlunit 2.0(これを探していました)の場合、 DefaultNodeMatcher を使用して実行されます

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

これが他の人がグーグルするのに役立つことを願っています...

21
Akzidenzgrotesk

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

18

からのクロスポスト 子要素の順序を無視してXMLを比較

今晩も同様のニーズがあり、自分の要件に合うものを見つけることができませんでした。

私の回避策は、比較したい2つのXMLファイルを要素名でアルファベット順に並べ替えることでした。両方が一貫した順序になったら、通常の視覚的差分ツールを使用して、2つのソートされたファイルを比較できます。

このアプローチが他のユーザーにとって有用であると思われる場合は、 http://dalelane.co.uk/blog/?pでソートを行うために書いたpythonスクリプトを共有しました= 3225

1
dalelane

オプション1
XMLコードが単純な場合は、次のことを試してください。

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


オプション2
XMLがさらに複雑な場合は、XMLパーサーを使用してロードし、参照ノードと実際のノードを比較します。

0
Stephan

私にとっては、DiffBuilderにメソッドcheckForSimilar()も追加する必要がありました。これがないと、ノードのシーケンスが同じではない(子リスト内の位置が同じではなかった)とアサートは誤って言っていました

私のコードは:

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

属性nameの等価性に基づいて一致するより複雑なxml要素を比較する方法の例と同じです。例えば:

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

次のカスタム要素修飾子:

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);
0
Stepan Vavra