[java] Java에서 2 개의 XML 문서를 비교하는 가장 좋은 방법

기본적으로 사용자 정의 메시지 형식을 XML 메시지로 변환하고 다른 쪽 끝으로 보내는 응용 프로그램의 자동화 된 테스트를 작성하려고합니다. 입력 / 출력 메시지 쌍이 잘 갖추어져 있으므로 입력 메시지를 보내고 XML 메시지를 듣고 다른 쪽 끝을 듣기 만하면됩니다.

실제 출력을 예상 출력과 비교할 때가되었지만 몇 가지 문제가 있습니다. 내 첫 번째 생각은 예상 된 메시지와 실제 메시지를 문자열 비교하는 것입니다. 우리가 가지고있는 예제 데이터가 항상 일관되게 형식화되지는 않으며 XML 네임 스페이스에 다른 별칭이 사용되는 경우가 종종 있으며 때로는 네임 스페이스가 전혀 사용되지 않기 때문에 이것은 잘 작동하지 않습니다.

두 문자열을 구문 분석 한 다음 각 요소를 살펴보고 직접 비교할 수 있으며 그렇게하기가 어렵지 않지만 더 좋은 방법이나 라이브러리를 활용할 수 있다는 느낌이 들었습니다.

따라서 삶은 문제는 다음과 같습니다.

둘 다 유효한 XML을 포함하는 두 개의 Java 문자열이 주어지면 의미 적으로 동등한 지 여부를 어떻게 결정합니까? 차이점을 확인할 수있는 방법이 있다면 보너스 포인트입니다.



답변

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


답변

다음은 표준 JDK 라이브러리를 사용하여 문서가 동일한 지 확인합니다.

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

문서 doc1 = db.parse (새 파일 ( "file1.xml"));
doc1.normalizeDocument ();

문서 doc2 = db.parse (새 파일 ( "file2.xml"));
doc2.normalizeDocument ();

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

normalize () 사이클이 없는지 확인하기 위해 존재합니다 (기술적으로는 없을 것입니다)

위의 코드는 공백을 유지하고 평가하기 때문에 요소 내에서 공백이 동일해야합니다. Java와 함께 제공되는 표준 XML 파서는 표준 버전을 제공하는 기능을 설정하거나 xml:space이것이 문제가 될지 이해하지 못 하므로 xerces와 같은 대체 XML 파서가 필요하거나 JDOM을 사용해야합니다.


답변

Xom 에는 Canonicalizer 유틸리티가있어 DOM을 일반 형식으로 변환하여 문자열 화하고 비교할 수 있습니다. 따라서 공백의 불규칙성 또는 속성 순서에 관계없이 문서를 정기적으로 예측 가능한 비교할 수 있습니다.

이것은 Eclipse와 같은 전용 시각적 문자열 비교기가있는 IDE에서 특히 잘 작동합니다. 문서 간의 시맨틱 차이를 시각적으로 표시합니다.


답변

최신 버전의 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>


답변

고마워, 나는 이것을 확장, 이것을 시도 …

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


답변

바탕 의 대답은 여기에 위해 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입니다.


답변

skaffman이 좋은 대답을하고있는 것 같습니다.

다른 방법은 xmlstarlet ( http://xmlstar.sourceforge.net/ )과 같은 명령 줄 유틸리티를 사용하여 XML을 포맷 한 다음 두 문자열을 모두 포맷 한 다음 diff 유틸리티 (라이브러리)를 사용하여 결과 출력 파일을 비교하는 것입니다. 네임 스페이스에 문제가있을 때 이것이 좋은 해결책인지는 모르겠습니다.