Сравните две строки XML, игнорируя порядок элементов - программирование
Подтвердить что ты не робот

Сравните две строки XML, игнорируя порядок элементов

Предположим, у меня есть две строки 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);
4b9b3361

Ответ 1

Мой оригинальный ответ устарел. Если бы мне пришлось построить его снова, я бы использовал 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()));
}

Ответ 2

XMLUnit будет делать то, что вы хотите, но вы должны указать elementQualifier. Если спецификатор elementQualifier не указан, он будет сравнивать только узлы в той же позиции.

Для вашего примера вы хотите ElementNameAndTextQualifer, он считает, что node аналогичен, если существует, который соответствует имени элемента и его текстовому значению, например:

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

Ответ 3

Для xmlunit 2.0 (я искал это) теперь это делается с помощью DefaultNodeMatcher

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

Надеюсь, что это помогает, это помогает другим людям гуглить...

Ответ 4

Перекрестная проводка из Сравнение XML, игнорирующего порядок дочерних элементов

У меня была такая же потребность сегодня вечером, и я не мог найти то, что соответствовало моим требованиям.

Мое обходное решение состояло в сортировке двух файлов XML, которые я хотел разделить, сортируя по алфавиту по имени элемента. Как только они были в одинаковом порядке, я мог бы разграничить два отсортированных файла, используя обычный инструмент визуального разграничения.

Если этот подход полезен для кого-то еще, я поделился с python script, который я написал, чтобы сортировать по http://dalelane.co.uk/blog/?p=3225

Ответ 5

ВАРИАНТ 1
Если XML-код прост, попробуйте следующее:

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


ВАРИАНТ 2
Если XML более сложный, загрузите его парсером XML и сравните фактические узлы, найденные с вами ссылочными узлами.

Ответ 6

Как пример того, как сравнивать более сложные сочетания xml-элементов на основе равенства атрибута name. Например:

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

Ответ 7

Для меня мне также необходимо добавить метод checkForSimilar() в DiffBuilder. Без него утверждение было ошибочным, говоря, что последовательность узлов была не одинаковой (позиция в дочернем списке не была одинаковой)

Мой код:

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