Java에서 2개의 XML 문서를 비교하는 가장 좋은 방법
기본적으로 커스텀 메시지 형식을 XML 메시지로 변환하여 다른 쪽 끝으로 전송하는 애플리케이션의 자동 테스트를 작성하려고 합니다.입력/출력 메시지 쌍이 잘 갖추어져 있기 때문에 입력 메시지를 보내고 XML 메시지가 상대편에서 나올 때까지 기다리기만 하면 됩니다.
실제 출력과 예상 출력을 비교할 때 몇 가지 문제가 있습니다.처음에 생각한 건 예상된 메시지와 실제 메시지를 문자열로 비교하는 거였어요.예제 데이터의 형식이 항상 일관되지 않고 XML 네임스페이스에 사용되는 별칭이 여러 번 다르기 때문에 이 방법이 제대로 작동하지 않습니다(또한 네임스페이스가 전혀 사용되지 않을 수도 있습니다.
두 문자열을 해석하고 각 요소를 직접 비교해서 쉽게 비교할 수 있다는 것은 알지만, 더 나은 방법이나 라이브러리를 활용할 수 있을 것 같습니다.
요약하자면, 질문은 다음과 같습니다.
둘 다 유효한 XML을 포함하는 2개의 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.set Namespace Aware(true); dbf.set Coalescing(true); dbf.setIgnoringElement 내용공백(참); dbf.set Ignoring Comments(true); DocumentBuilder db = dbf.newDocumentBuilder(); 문서 doc1 = db.db(새 파일("file1.xml"));doc1.normalize Document(); 문서 doc2 = db.db(새 파일("file2.xml"));doc2.normalize Document(); Assert.assertTrue(doc1.isEqualNode(doc2));
normalize()는 사이클이 없는 것을 확인하기 위한 것입니다(기술적으로는 없습니다).
위의 코드는 요소를 보존하고 평가하기 때문에 요소 내에서 공백이 동일해야 합니다.되어 있는 XML 하거나 Java를 할 수 .xml:space
문제가 되는 경우는, xerces등의 대체 XML 파서가 필요하거나 JDOM 를 사용할 필요가 있습니다.
Xom에는 DOM을 정규 형식으로 변환하는 Canonicalizer 유틸리티가 있습니다.이 유틸리티는 DOM을 문자열화하고 비교할 수 있습니다.따라서 공백의 불규칙함이나 속성 순서에 관계없이 문서를 정기적으로 예측 가능한 비교 결과를 얻을 수 있습니다.
이 기능은 이클립스와 같이 전용 시각적 문자열 비교기가 있는 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>
Tom의 답변을 바탕으로 XMLUnit v2를 사용하는 예를 보여 줍니다.
이러한 의존성을 사용합니다.
<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 입니다.
고마워, 이거 연장했어, 이거 해봐...
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();
}
}
AssertJ 1.4+에는 XML 콘텐츠를 비교하는 특정 어사션이 있습니다.
String expectedXml = "<foo />";
String actualXml = "<bar />";
assertThat(actualXml).isXmlEqualTo(expectedXml);
여기 매뉴얼이 있습니다.
아래 코드가 도움이 됩니다.
String xml1 = ...
String xml2 = ...
XMLUnit.setIgnoreWhitespace(true);
XMLUnit.setIgnoreAttributeOrder(true);
XMLAssert.assertXMLEqual(actualxml, xmlInDb);
skaffman이 좋은 대답을 하는 것 같다.
다른 방법으로는 XML을 xmlstarlet(http://xmlstar.sourceforge.net/))과 같은 Commmand line 유틸리티를 사용하여 포맷한 후 두 문자열을 모두 포맷한 후 임의의 diff 유틸리티(diff 유틸리티)를 사용하여 출력 파일을 디프하는 방법이 있습니다.네임스페이스에 문제가 있을 때 이것이 좋은 해결책인지 모르겠습니다.
XML 파일을 구조적으로 비교할 수 있는 옵션이 있는 Altova DiffDog를 사용하고 있습니다(문자열 데이터를 무시).
즉, ('텍스트 무시' 옵션을 선택한 경우)
<foo a="xxx" b="xxx">xxx</foo>
그리고.
<foo b="yyy" a="yyy">yyy</foo>
구조적으로 평등하다는 점에서 평등하다는 것입니다.데이터는 다르지만 구조는 다른 예제 파일이 있는 경우 유용합니다.
저는 메인 질문에서 요청한 것과 같은 기능을 요구했습니다.서드파티 라이브러리를 사용할 수 없었기 때문에 @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 문자열을 비교하여 두 입력 문자열의 고유한 값으로 변환하여 일치하지 않는 네임스페이스 매핑을 처리합니다.
네임스페이스 번역의 경우 등 미세 조정이 가능합니다.하지만 제 요구 사항이라면 그냥 그 일을 할 수 있어요.
이렇게 하면 전체 문자열 XML이 비교됩니다(가다가 다시 포맷).클릭하기만 하면 XML 파일의 차이를 볼 수 있기 때문에 IDE(IntelliJ, Eclipse)로 작업하기 쉬워집니다.
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);
}
}
클라이언트 코드(테스트 코드)가 깨끗하기 때문에 Xml Unit보다 선호합니다.
JEXamX 사용Java 응용 프로그램을 사용하는 ML
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 2.x 사용
에서pom.xml
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-assertj3</artifactId>
<version>2.9.0</version>
</dependency>
테스트 구현(junit 5 사용):
import org.junit.jupiter.api.Test;
import org.xmlunit.assertj3.XmlAssert;
public class FooTest {
@Test
public void compareXml() {
//
String xmlContentA = "<foo></foo>";
String xmlContentB = "<foo></foo>";
//
XmlAssert.assertThat(xmlContentA).and(xmlContentB).areSimilar();
}
}
기타 방법:areIdentical()
,areNotIdentical()
,areNotSimilar()
상세(설정)assertThat(~).and(~)
및 예)를 참조하십시오.
XMLUnit에는 (다른 기능 중에서도) 보다 정밀한 비교를 하기 위한 가 있습니다.
「의미적으로 동등」하다고 하는 것은, 문자 그대로 xml 출력이 같은 것을 확인하는 것 이상의 것을 실시하는 것을 의미한다고 생각합니다.
<foo> 여기 몇 가지 </foo> </code>
그리고.
<foo> 여기 몇 가지 </foo> </code>
동등하게 읽는다.궁극적으로 메시지를 재구성하는 객체에 대해 "의미적으로 동등한"을 어떻게 정의하느냐가 중요합니다.메시지에서 오브젝트를 작성하고 커스텀 등호()를 사용하여 원하는 것을 정의하기만 하면 됩니다.
언급URL : https://stackoverflow.com/questions/141993/best-way-to-compare-2-xml-documents-in-java
'programing' 카테고리의 다른 글
C의 정수 자릿수를 판별하려면 어떻게 해야 하나요? (0) | 2022.07.19 |
---|---|
Vuejs js는 1페이지 어플리케이션이 아닌 여러 페이지에 대응합니다. (0) | 2022.07.19 |
다형성:"ArrayList list = new ArrayList" 대신 "List list = new ArrayList"를 사용하는 이유는 무엇입니까? (0) | 2022.07.19 |
vuex 스토어에서 vue-logger를 사용하려면 어떻게 해야 합니까? (0) | 2022.07.19 |
Java: 지도 기능이 있나요? (0) | 2022.07.19 |