Лучший способ сравнить 2 XML-документа на Java

Я пытаюсь написать автоматический тест приложения, которое в основном переводит пользовательский формат сообщения в сообщение XML и отправляет его на другой конец. У меня есть хороший набор пар сообщений ввода / вывода, поэтому все, что мне нужно сделать, это отправить входные сообщения и прослушать XML-сообщение, которое выйдет на другой конец.

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

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

Итак, сводится к следующему:

Учитывая две строки Java, каждая из которых содержит допустимый XML, как бы вы определить, являются ли они семантически эквивалентными? Бонусные баллы, если у вас есть способ определить, в чем разница.

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
204
0
199 146
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

Xom имеет служебную программу Canonicalizer, которая превращает ваши модели DOM в обычную форму, которую вы затем можете преобразовывать в строки и сравнивать. Таким образом, независимо от неправильных пробелов или порядка атрибутов, вы можете получать регулярные предсказуемые сравнения ваших документов.

Это особенно хорошо работает в IDE, в которых есть специальные визуальные компараторы String, например Eclipse. Вы получаете наглядное представление о семантических различиях между документами.

Ответ принят как подходящий

Похоже на работу для XMLUnit

Пример:

public class SomeTest extends XMLTestCase {
  @Test
  public void test() {
    String xml1 = ...
    String xml2 = ...

    XMLUnit.setIgnoreWhitespace(true); // ignore whitespace differences

    // can also compare xml Documents, InputSources, Readers, Diffs
    assertXMLEqual(xml1, xml2);  // assertXMLEquals comes from XMLTestCase
  }
}

Я знал, что должно быть что-то подобное. Не могу поверить, что Google не нашел его для меня. Спасибо.

Mike Deck 27.09.2008 01:57

У меня были проблемы с XMLUNit в прошлом, он был очень дерганым с версиями XML API и не оказался надежным. Однако прошло некоторое время с тех пор, как я отказался от него для XOM, так что, возможно, с тех пор он улучшился.

skaffman 27.09.2008 20:41

Для новичков в XMLUnit обратите внимание, что по умолчанию myDiff.similar () будет возвращать ложный, если контрольный и тестовый документы отличаются отступом / новой строкой. Я ожидал такого поведения от myDiff.identical (), а не от myDiff.similar (). Включить XMLUnit.setIgnoreWhitespace (true); в вашем методе setUp, чтобы изменить поведение для всех тестов в вашем тестовом классе, или использовать его в отдельном методе тестирования, чтобы изменить поведение только для этого теста.

Stew 07.12.2012 00:43

@Stew спасибо за ваш комментарий, только начинаю с XMLUnit и я уверен, что столкнулся бы с этой проблемой. +1

Jay 11.08.2014 23:53

Если вы пытаетесь это сделать с XMLUnit 2 на github, версия 2 - это полная переработка, поэтому этот пример предназначен для XMLUnit 1 на SourceForge. Кроме того, на странице sourceforge указано, что «XMLUnit для Java 1.x по-прежнему будет поддерживаться».

Yngvar Kristiansen 25.02.2016 11:00

В этом примере Junit 3, который использует extends TestCase, действительно олдскульный. Было бы очень полезно использовать пример Junit 4.

Yngvar Kristiansen 25.02.2016 12:27

метод assertXMLEqual как из XMLAssert.java.

user2818782 16.01.2018 09:06

@skaffman, по общему признанию, 10 лет спустя .... но xmlunit очень хорошо работает для меня в тестах JUnit 4.

user3456014 12.04.2018 22:54

Скаффман, кажется, дает хороший ответ.

другой способ, вероятно, - отформатировать XML с помощью утилиты командной строки, такой как xmlstarlet (http://xmlstar.sourceforge.net/), а затем отформатировать обе строки, а затем использовать любую утилиту (библиотеку) diff для сравнения полученных выходных файлов. Я не знаю, хорошее ли это решение, когда возникают проблемы с пространствами имен.

Поскольку вы говорите «семантически эквивалентный», я предполагаю, что вы имеете в виду, что хотите сделать больше, чем просто проверить, что выходные данные xml равны (строка), и что вам нужно что-то вроде

<foo> кое-что здесь </foo> </code>

и

<foo> кое-что здесь </foo> </code>

читать как эквивалент. В конечном итоге будет иметь значение, как вы определяете «семантически эквивалент» для любого объекта, из которого вы воссоздаете сообщение. Просто создайте этот объект из сообщений и используйте специальный метод equals (), чтобы определить, что вы ищете.

Не ответ, а вопрос.

Kartoch 08.07.2011 13:32

Я использую Альтова ДиффДог, у которого есть параметры для структурного сравнения файлов XML (игнорируя строковые данные).

Это означает, что (если установлен флажок «игнорировать текст»):

<foo a = "xxx" b = "xxx">xxx</foo>

и

<foo b = "yyy" a = "yyy">yyy</foo> 

равны в том смысле, что имеют структурное равенство. Это удобно, если у вас есть файлы примеров, которые отличаются данными, но не структурой!

Единственный минус в том, что это не бесплатно (99 евро за профессиональную лицензию) с 30-дневной пробной версией.

Pimin Konstantin Kefaloukos 26.10.2010 13:36

Нашел только утилиту (altova.com/diffdog/diff-merge-tool.html); приятно иметь библиотеку.

dma_k 27.10.2010 13:42

Следующее будет проверять, равны ли документы, используя стандартные библиотеки JDK.

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setCoalescing(true);
dbf.setIgnoringElementContentWhitespace(true);
dbf.setIgnoringComments(true);
DocumentBuilder db = dbf.newDocumentBuilder();

Document doc1 = db.parse(new File("file1.xml"));
doc1.normalizeDocument();

Document doc2 = db.parse(new File("file2.xml"));
doc2.normalizeDocument();

Assert.assertTrue(doc1.isEqualNode(doc2));

normalize () нужен для того, чтобы убедиться в отсутствии циклов (технически их не было бы)

Приведенный выше код потребует, чтобы пробелы были одинаковыми внутри элементов, потому что он сохраняет и оценивает его. Стандартный синтаксический анализатор XML, поставляемый с Java, не позволяет вам установить функцию для предоставления канонической версии или понимания xml:space, если это будет проблемой, тогда вам может потребоваться замена синтаксического анализатора XML, например xerces, или использовать JDOM.

Это отлично работает для XML без пространств имен или с «нормализованными» префиксами пространств имен. Я сомневаюсь, что это работает, если один XML - это <ns1: a xmlns: ns1 = "ns" />, а другой - <ns2: a xmlns: ns2 = "ns" />

koppor 06.01.2013 06:43

dbf.setIgnoringElementContentWhitespace (true) не дает результата, я бы ожидал, что <root> name </root> не равно <root> name </name> с этим решением (с двумя пробелами), но XMLUnit дает тот же результат в этом случае (JDK8)

Miklos Krivan 07.09.2016 15:03

Для меня он не игнорирует разрывы строк, что является проблемой.

Flyout91 11.12.2017 21:05

setIgnoringElementContentWhitespace(false)

Archimedes Trajano 11.12.2017 21:06

Спасибо, я продлил это, попробуйте это ...

import java.io.ByteArrayInputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

public class XmlDiff 
{
    private boolean nodeTypeDiff = true;
    private boolean nodeValueDiff = true;

    public boolean diff( String xml1, String xml2, List<String> diffs ) throws Exception
    {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setCoalescing(true);
        dbf.setIgnoringElementContentWhitespace(true);
        dbf.setIgnoringComments(true);
        DocumentBuilder db = dbf.newDocumentBuilder();


        Document doc1 = db.parse(new ByteArrayInputStream(xml1.getBytes()));
        Document doc2 = db.parse(new ByteArrayInputStream(xml2.getBytes()));

        doc1.normalizeDocument();
        doc2.normalizeDocument();

        return diff( doc1, doc2, diffs );

    }

    /**
     * Diff 2 nodes and put the diffs in the list 
     */
    public boolean diff( Node node1, Node node2, List<String> diffs ) throws Exception
    {
        if ( diffNodeExists( node1, node2, diffs ) )
        {
            return true;
        }

        if ( nodeTypeDiff )
        {
            diffNodeType(node1, node2, diffs );
        }

        if ( nodeValueDiff )
        {
            diffNodeValue(node1, node2, diffs );
        }


        System.out.println(node1.getNodeName() + "/" + node2.getNodeName());

        diffAttributes( node1, node2, diffs );
        diffNodes( node1, node2, diffs );

        return diffs.size() > 0;
    }

    /**
     * Diff the nodes
     */
    public boolean diffNodes( Node node1, Node node2, List<String> diffs ) throws Exception
    {
        //Sort by Name
        Map<String,Node> children1 = new LinkedHashMap<String,Node>();      
        for( Node child1 = node1.getFirstChild(); child1 != null; child1 = child1.getNextSibling() )
        {
            children1.put( child1.getNodeName(), child1 );
        }

        //Sort by Name
        Map<String,Node> children2 = new LinkedHashMap<String,Node>();      
        for( Node child2 = node2.getFirstChild(); child2!= null; child2 = child2.getNextSibling() )
        {
            children2.put( child2.getNodeName(), child2 );
        }

        //Diff all the children1
        for( Node child1 : children1.values() )
        {
            Node child2 = children2.remove( child1.getNodeName() );
            diff( child1, child2, diffs );
        }

        //Diff all the children2 left over
        for( Node child2 : children2.values() )
        {
            Node child1 = children1.get( child2.getNodeName() );
            diff( child1, child2, diffs );
        }

        return diffs.size() > 0;
    }


    /**
     * Diff the nodes
     */
    public boolean diffAttributes( Node node1, Node node2, List<String> diffs ) throws Exception
    {        
        //Sort by Name
        NamedNodeMap nodeMap1 = node1.getAttributes();
        Map<String,Node> attributes1 = new LinkedHashMap<String,Node>();        
        for( int index = 0; nodeMap1 != null && index < nodeMap1.getLength(); index++ )
        {
            attributes1.put( nodeMap1.item(index).getNodeName(), nodeMap1.item(index) );
        }

        //Sort by Name
        NamedNodeMap nodeMap2 = node2.getAttributes();
        Map<String,Node> attributes2 = new LinkedHashMap<String,Node>();        
        for( int index = 0; nodeMap2 != null && index < nodeMap2.getLength(); index++ )
        {
            attributes2.put( nodeMap2.item(index).getNodeName(), nodeMap2.item(index) );

        }

        //Diff all the attributes1
        for( Node attribute1 : attributes1.values() )
        {
            Node attribute2 = attributes2.remove( attribute1.getNodeName() );
            diff( attribute1, attribute2, diffs );
        }

        //Diff all the attributes2 left over
        for( Node attribute2 : attributes2.values() )
        {
            Node attribute1 = attributes1.get( attribute2.getNodeName() );
            diff( attribute1, attribute2, diffs );
        }

        return diffs.size() > 0;
    }
    /**
     * Check that the nodes exist
     */
    public boolean diffNodeExists( Node node1, Node node2, List<String> diffs ) throws Exception
    {
        if ( node1 == null && node2 == null )
        {
            diffs.add( getPath(node2) + ":node " + node1 + "! = " + node2 + "\n" );
            return true;
        }

        if ( node1 == null && node2 != null )
        {
            diffs.add( getPath(node2) + ":node " + node1 + "! = " + node2.getNodeName() );
            return true;
        }

        if ( node1 != null && node2 == null )
        {
            diffs.add( getPath(node1) + ":node " + node1.getNodeName() + "! = " + node2 );
            return true;
        }

        return false;
    }

    /**
     * Diff the Node Type
     */
    public boolean diffNodeType( Node node1, Node node2, List<String> diffs ) throws Exception
    {       
        if ( node1.getNodeType() != node2.getNodeType() ) 
        {
            diffs.add( getPath(node1) + ":type " + node1.getNodeType() + "! = " + node2.getNodeType() );
            return true;
        }

        return false;
    }

    /**
     * Diff the Node Value
     */
    public boolean diffNodeValue( Node node1, Node node2, List<String> diffs ) throws Exception
    {       
        if ( node1.getNodeValue() == null && node2.getNodeValue() == null )
        {
            return false;
        }

        if ( node1.getNodeValue() == null && node2.getNodeValue() != null )
        {
            diffs.add( getPath(node1) + ":type " + node1 + "! = " + node2.getNodeValue() );
            return true;
        }

        if ( node1.getNodeValue() != null && node2.getNodeValue() == null )
        {
            diffs.add( getPath(node1) + ":type " + node1.getNodeValue() + "! = " + node2 );
            return true;
        }

        if ( !node1.getNodeValue().equals( node2.getNodeValue() ) )
        {
            diffs.add( getPath(node1) + ":type " + node1.getNodeValue() + "! = " + node2.getNodeValue() );
            return true;
        }

        return false;
    }


    /**
     * Get the node path
     */
    public String getPath( Node node )
    {
        StringBuilder path = new StringBuilder();

        do
        {           
            path.insert(0, node.getNodeName() );
            path.insert( 0, "/" );
        }
        while( ( node = node.getParentNode() ) != null );

        return path.toString();
    }
}

Довольно поздно, но просто хотел отметить, что в этом фрагменте кода есть ошибка: в diffNodes () на node2 нет ссылки - второй цикл повторно использует node1 неправильно (я отредактировал код, чтобы исправить это). Кроме того, у него есть одно ограничение: из-за того, что дочерние карты связаны с ключами, этот diff не поддерживает случай, когда имена элементов не уникальны, то есть элементы, содержащие повторяющиеся дочерние элементы.

aberrant80 08.11.2012 08:06

Использование JExamXML с приложением Java

    import com.a7soft.examxml.ExamXML;
    import com.a7soft.examxml.Options;

       .................

       // Reads two XML files into two strings
       String s1 = readFile("orders1.xml");
       String s2 = readFile("orders.xml");

       // Loads options saved in a property file
       Options.loadOptions("options");

       // Compares two Strings representing XML entities
       System.out.println( ExamXML.compareXMLString( s1, s2 ) );

Последняя версия XMLUnit может помочь в утверждении равенства двух XML. Также для рассматриваемого случая могут потребоваться XMLUnit.setIgnoreWhitespace() и XMLUnit.setIgnoreAttributeOrder().

См. Рабочий код простого примера использования модуля XML ниже.

import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Assert;

public class TestXml {

    public static void main(String[] args) throws Exception {
        String result = "<abc             attr=\"value1\"                title=\"something\">            </abc>";
        // will be ok
        assertXMLEquals("<abc attr=\"value1\" title=\"something\"></abc>", result);
    }

    public static void assertXMLEquals(String expectedXML, String actualXML) throws Exception {
        XMLUnit.setIgnoreWhitespace(true);
        XMLUnit.setIgnoreAttributeOrder(true);

        DetailedDiff diff = new DetailedDiff(XMLUnit.compareXML(expectedXML, actualXML));

        List<?> allDifferences = diff.getAllDifferences();
        Assert.assertEquals("Differences found: "+ diff.toString(), 0, allDifferences.size());
    }

}

Если вы используете Maven, добавьте это в свой pom.xml:

<dependency>
    <groupId>xmlunit</groupId>
    <artifactId>xmlunit</artifactId>
    <version>1.4</version>
</dependency>

Это идеально подходит для людей, которым нужно сравнивать статическим методом.

Andy B 02.07.2014 18:07

Это прекрасный ответ. Спасибо .. Однако мне нужно игнорировать несуществующие узлы. Поскольку я не хочу видеть в выводе результата такой вывод: Ожидаемое присутствие дочернего узла "null", но было ...... Как я могу это сделать? С уважением. @acdcjunior

limonik 20.12.2016 13:38

XMLUnit.setIgnoreAttributeOrder (истина); не работает. Если некоторые узлы имеют другой порядок, сравнение не удастся.

Bevor 22.05.2017 17:34

[ОБНОВЛЕНИЕ] это решение работает: stackoverflow.com/questions/33695041/…

Bevor 22.05.2017 18:06

Вы ведь понимаете, что «IgnoreAttributeOrder» означает игнорировать порядок атрибутов и не игнорировать порядок узлов, верно?

acdcjunior 22.05.2017 18:16

@acdcjunior Если узлы находятся в разных местах ... это сработает?

Abhijit Bashetti 18.09.2019 08:17

@AbhijitBashetti Видимо только когда еще добавляешь .withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byName)).

acdcjunior 18.09.2019 09:06

DiffBuilder.compare (bufferedReaderExistingFile) .withTest (bufferedReaderNewFile) .ignoreComments () .ignoreWhitespace () .withNodeMatcher (новый DefaultNodeMatcher (ElementSelectors.byName)) .checkForSimilar () .build ();

Abhijit Bashetti 18.09.2019 09:17

@acdcjunior: у меня есть код выше, но он говорит, что файлы разные

Abhijit Bashetti 18.09.2019 09:17

@AbhijitBashetti хм ... это плохо ... извини, у меня больше ничего нет ?

acdcjunior 18.09.2019 09:40

Это позволит сравнить полные строковые XML-файлы (по ходу переформатируя их). Это упрощает работу с вашей IDE (IntelliJ, Eclipse), потому что вы просто нажимаете и визуально видите разницу в файлах XML.

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.w3c.dom.Element;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;

import static org.apache.xml.security.Init.init;
import static org.junit.Assert.assertEquals;

public class XmlUtils {
    static {
        init();
    }

    public static String toCanonicalXml(String xml) throws InvalidCanonicalizerException, ParserConfigurationException, SAXException, CanonicalizationException, IOException {
        Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
        byte canonXmlBytes[] = canon.canonicalize(xml.getBytes());
        return new String(canonXmlBytes);
    }

    public static String prettyFormat(String input) throws TransformerException, ParserConfigurationException, IOException, SAXException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        InputSource src = new InputSource(new StringReader(input));
        Element document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src).getDocumentElement();
        Boolean keepDeclaration = input.startsWith("<?xml");
        DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
        DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
        LSSerializer writer = impl.createLSSerializer();
        writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
        writer.getDomConfig().setParameter("xml-declaration", keepDeclaration);
        return writer.writeToString(document);
    }

    public static void assertXMLEqual(String expected, String actual) throws ParserConfigurationException, IOException, SAXException, CanonicalizationException, InvalidCanonicalizerException, TransformerException, IllegalAccessException, ClassNotFoundException, InstantiationException {
        String canonicalExpected = prettyFormat(toCanonicalXml(expected));
        String canonicalActual = prettyFormat(toCanonicalXml(actual));
        assertEquals(canonicalExpected, canonicalActual);
    }
}

Я предпочитаю это XmlUnit, потому что клиентский код (тестовый код) чище.

Это отлично работает в двух тестах, которые я сделал сейчас, с тем же XML и с другим XML. С IntelliJ diff легко заметить различия в сравниваемых XML.

Yngvar Kristiansen 25.02.2016 14:24

Кстати, эта зависимость вам понадобится, если вы используете Maven: <dependency> <groupId> org.apache.santuario </groupId> <artifactId> xmlsec </artifactId> <version> 2.0.6 </version> </ зависимость>

Yngvar Kristiansen 25.02.2016 16:23

Основываясь на ответе Том, вот пример использования XMLUnit v2.

Он использует эти зависимости maven

    <dependency>
        <groupId>org.xmlunit</groupId>
        <artifactId>xmlunit-core</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.xmlunit</groupId>
        <artifactId>xmlunit-matchers</artifactId>
        <version>2.0.0</version>
        <scope>test</scope>
    </dependency>

..и вот тестовый код

import static org.junit.Assert.assertThat;
import static org.xmlunit.matchers.CompareMatcher.isIdenticalTo;
import org.xmlunit.builder.Input;
import org.xmlunit.input.WhitespaceStrippedSource;

public class SomeTest extends XMLTestCase {
    @Test
    public void test() {
        String result = "<root></root>";
        String expected = "<root>  </root>";

        // ignore whitespace differences
        // https://github.com/xmlunit/user-guide/wiki/Providing-Input-to-XMLUnit#whitespacestrippedsource
        assertThat(result, isIdenticalTo(new WhitespaceStrippedSource(Input.from(expected).build())));

        assertThat(result, isIdenticalTo(Input.from(expected).build())); // will fail due to whitespace differences
    }
}

Документация, описывающая это, - https://github.com/xmlunit/xmlunit#comparing-two-documents

AssertJ 1.4+ имеет определенные утверждения для сравнения содержимого XML:

String expectedXml = "<foo />";
String actualXml = "<bar />";
assertThat(actualXml).isXmlEqualTo(expectedXml);

Вот Документация

Однако тривиальная разница в префиксе пространства имен между двумя документами приводит к сбою AssertJ. AssertJ - отличный инструмент, но на самом деле он предназначен для XMLUnit.

Alexander Vasiljev 15.09.2020 13:57

Мне требовались те же функции, что и в основном вопросе. Поскольку мне не разрешалось использовать какие-либо сторонние библиотеки, я создал собственное решение на основе решения @Archimedes Trajano.

Вот мое решение.

import java.io.ByteArrayInputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.junit.Assert;
import org.w3c.dom.Document;

/**
 * Asserts for asserting XML strings.
 */
public final class AssertXml {

    private AssertXml() {
    }

    private static Pattern NAMESPACE_PATTERN = Pattern.compile("xmlns:(ns\d+)=\"(.*?)\"");

    /**
     * Asserts that two XML are of identical content (namespace aliases are ignored).
     * 
     * @param expectedXml expected XML
     * @param actualXml actual XML
     * @throws Exception thrown if XML parsing fails
     */
    public static void assertEqualXmls(String expectedXml, String actualXml) throws Exception {
        // Find all namespace mappings
        Map<String, String> fullnamespace2newAlias = new HashMap<String, String>();
        generateNewAliasesForNamespacesFromXml(expectedXml, fullnamespace2newAlias);
        generateNewAliasesForNamespacesFromXml(actualXml, fullnamespace2newAlias);

        for (Entry<String, String> entry : fullnamespace2newAlias.entrySet()) {
            String newAlias = entry.getValue();
            String namespace = entry.getKey();
            Pattern nsReplacePattern = Pattern.compile("xmlns:(ns\d+)=\"" + namespace + "\"");
            expectedXml = transletaNamespaceAliasesToNewAlias(expectedXml, newAlias, nsReplacePattern);
            actualXml = transletaNamespaceAliasesToNewAlias(actualXml, newAlias, nsReplacePattern);
        }

        // nomralize namespaces accoring to given mapping

        DocumentBuilder db = initDocumentParserFactory();

        Document expectedDocuemnt = db.parse(new ByteArrayInputStream(expectedXml.getBytes(Charset.forName("UTF-8"))));
        expectedDocuemnt.normalizeDocument();

        Document actualDocument = db.parse(new ByteArrayInputStream(actualXml.getBytes(Charset.forName("UTF-8"))));
        actualDocument.normalizeDocument();

        if (!expectedDocuemnt.isEqualNode(actualDocument)) {
            Assert.assertEquals(expectedXml, actualXml); //just to better visualize the diffeences i.e. in eclipse
        }
    }


    private static DocumentBuilder initDocumentParserFactory() throws ParserConfigurationException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(false);
        dbf.setCoalescing(true);
        dbf.setIgnoringElementContentWhitespace(true);
        dbf.setIgnoringComments(true);
        DocumentBuilder db = dbf.newDocumentBuilder();
        return db;
    }

    private static String transletaNamespaceAliasesToNewAlias(String xml, String newAlias, Pattern namespacePattern) {
        Matcher nsMatcherExp = namespacePattern.matcher(xml);
        if (nsMatcherExp.find()) {
            xml = xml.replaceAll(nsMatcherExp.group(1) + "[:]", newAlias + ":");
            xml = xml.replaceAll(nsMatcherExp.group(1) + " = ", newAlias + " = ");
        }
        return xml;
    }

    private static void generateNewAliasesForNamespacesFromXml(String xml, Map<String, String> fullnamespace2newAlias) {
        Matcher nsMatcher = NAMESPACE_PATTERN.matcher(xml);
        while (nsMatcher.find()) {
            if (!fullnamespace2newAlias.containsKey(nsMatcher.group(2))) {
                fullnamespace2newAlias.put(nsMatcher.group(2), "nsTr" + (fullnamespace2newAlias.size() + 1));
            }
        }
    }

}

Он сравнивает две строки XML и заботится о любых несоответствующих сопоставлениях пространств имен, переводя их в уникальные значения в обеих входных строках.

Возможна точная настройка, например, в случае перевода пространств имен. Но для моих требований просто выполняет свою работу.

Ниже код работает для меня

String xml1 = ...
String xml2 = ...
XMLUnit.setIgnoreWhitespace(true);
XMLUnit.setIgnoreAttributeOrder(true);
XMLAssert.assertXMLEqual(actualxml, xmlInDb);

Любой контекст? Ссылка на библиотеку?

Ben 17.07.2019 17:00

Другие вопросы по теме