217 votes

Meilleure façon de comparer 2 documents XML en Java

J'essaie d'écrire un test automatisé d'une application qui traduit essentiellement un format de message personnalisé en un message XML et l'envoie à l'autre bout. J'ai un bon ensemble de paires de messages d'entrée/sortie, donc tout ce que j'ai à faire est d'envoyer les messages d'entrée et d'écouter le message XML qui sort à l'autre bout.

Lorsqu'il s'agit de comparer le résultat réel au résultat attendu, je rencontre quelques problèmes. Ma première idée était de faire des comparaisons de chaînes de caractères sur les messages attendus et réels. Cela ne fonctionne pas très bien car les données d'exemple que nous avons ne sont pas toujours formatées de manière cohérente et il y a souvent différents alias utilisés pour l'espace de noms XML (et parfois les espaces de noms ne sont pas utilisés du tout).

Je sais que je peux analyser les deux chaînes, puis parcourir chaque élément et les comparer moi-même, ce qui ne serait pas trop difficile à faire, mais j'ai l'impression qu'il existe une meilleure méthode ou une bibliothèque que je pourrais exploiter.

Donc, en résumé, la question est :

Étant donné deux chaînes Java qui contiennent toutes deux du XML valide, comment allez-vous déterminer si elles sont sémantiquement équivalentes ? Des points bonus si vous avez un moyen de déterminer les différences.

210voto

Tom Points 15618

Cela ressemble à un travail pour XMLUnit

Exemple :

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

0 votes

Je savais que quelque chose comme ça devait exister. Je ne peux pas croire que Google ne l'ait pas trouvé pour moi. Merci.

1 votes

J'ai eu des problèmes avec XMLUNit dans le passé, il était très instable avec les versions de l'API XML et ne s'est pas avéré fiable. Mais cela fait un moment que je l'ai abandonné pour XOM, alors peut-être que cela s'est amélioré depuis.

67 votes

Pour les débutants de XMLUnit, notez que, par défaut, myDiff.similar() retournera falso si les documents de contrôle et de test diffèrent au niveau de l'indentation/des nouvelles lignes. J'attendais ce comportement de la part de myDiff.identical(), et non de myDiff.similar(). Incluez XMLUnit.setIgnoreWhitespace(true) ; dans votre méthode setUp pour modifier le comportement de tous les tests de votre classe de test, ou utilisez-le dans une méthode de test individuelle pour modifier le comportement de ce test uniquement.

40voto

Archimedes Trajano Points 2729

Ce qui suit vérifiera si les documents sont égaux en utilisant les bibliothèques standard du 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() est là pour s'assurer qu'il n'y a pas de cycles (il n'y en aurait techniquement pas)

Le code ci-dessus exigera cependant que les espaces blancs soient les mêmes dans les éléments, car il les conserve et les évalue. L'analyseur XML standard fourni avec Java ne vous permet pas de définir une fonction permettant de fournir une version canonique ou de comprendre les éléments suivants xml:space Si cela pose problème, il vous faudra peut-être utiliser un analyseur XML de remplacement tel que xerces ou utiliser JDOM.

4 votes

Cela fonctionne parfaitement pour les XML sans espaces de noms ou avec des préfixes d'espaces de noms "normalisés". Je doute que cela fonctionne si un XML est <ns1:a xmlns:ns1="ns" /> et l'autre est <ns2:a xmlns:ns2="ns" />.

0 votes

Dbf.setIgnoringElementContentWhitespace(true) n'a pas le résultat auquel je m'attendais, le <Root>name</Root> n'est pas égal à <Root>name </name> avec cette solution (padded with two space) mais XMLUnit donne le même résultat dans ce cas (JDK8)

0 votes

Pour moi, il n'ignore pas les sauts de ligne, ce qui est un problème.

28voto

skaffman Points 197885

Xom dispose d'un utilitaire Canonicalizer qui transforme vos DOM en une forme régulière, que vous pouvez ensuite stringifier et comparer. Ainsi, indépendamment des irrégularités de l'espace blanc ou de l'ordre des attributs, vous pouvez obtenir des comparaisons régulières et prévisibles de vos documents.

Cela fonctionne particulièrement bien dans les IDE qui ont des comparateurs visuels de String dédiés, comme Eclipse. Vous obtenez une représentation visuelle des différences sémantiques entre les documents.

28voto

acdcjunior Points 19898

La dernière version de XMLUnit peut aider à affirmer que deux XML sont égaux. Voir aussi XMLUnit.setIgnoreWhitespace() y XMLUnit.setIgnoreAttributeOrder() peut être nécessaire pour le cas en question.

Voir le code de travail d'un exemple simple d'utilisation de l'unité XML ci-dessous.

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

}

Si vous utilisez Maven, ajoutez ceci à votre pom.xml :

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

0 votes

C'est parfait pour les personnes qui ont besoin de comparer à partir d'une méthode statique.

0 votes

C'est la réponse parfaite. Merci. Cependant, j'ai besoin d'ignorer les nœuds qui n'existent pas. Puisque je ne veux pas voir dans la sortie de résultat une telle sortie : Expected presence of child node "null" but was ......Comment puis-je faire cela ? Merci. @acdcjunior

1 votes

XMLUnit.setIgnoreAttributeOrder(true) ; ne fonctionne pas. Si certains nœuds ont un ordre différent, la comparaison échouera.

7voto

Javelin Points 31

Merci, j'ai étendu ceci, essayez ceci ...

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

3 votes

Assez tard, mais je voulais juste noter que ce morceau de code a un bug : Dans diffNodes(), le nœud 2 n'est pas référencé - la deuxième boucle réutilise le nœud 1 de manière incorrecte (j'ai modifié le code pour corriger cela). Aussi, il a une limitation : En raison de la façon dont les maps to child sont clées, ce diff ne supporte pas le cas où les noms des éléments ne sont pas uniques, c'est à dire les éléments contenant des éléments enfants répétables.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X