Comparer deux sorties XML sans se noyer dans le diff
Un diff de texte signale des centaines de différences là où il n'y en a qu'une. La comparaison utile porte sur l'arbre, pas sur les octets.
Deux sorties XML qui devraient être identiques ne le sont jamais tout à fait. Un outil de diff textuel signale alors des centaines de lignes là où le sens n'a changé qu'une fois, et la seule différence qui compte se perd dans le bruit.
La comparaison utile porte sur l'arbre. Encore faut-il décider ce qu'on accepte d'ignorer — et cette décision appartient au test, pas à l'outil.
Pourquoi le diff textuel échoue
Trois différences sans effet sur le sens produisent un diff illisible :
| Différence | Effet sur le document |
|---|---|
| ordre des attributs | aucun |
| indentation | aucun, sauf xml:space |
| préfixe d'espace de noms | aucun, si l'URI est la même |
Aucune des trois n'est un défaut, et un comparateur de texte les signale toutes. Elles apparaissent d'autant plus facilement que la sortie a été réécrite par un autre processeur, ou simplement relue et réenregistrée par un éditeur.
La forme canonique, et ce qu'elle ne fait pas
La réponse standard est la canonicalisation : réécrire les deux documents sous une forme unique avant de les comparer.
xmllint --c14n11 attendu.xml > attendu.c14n
xmllint --c14n11 obtenu.xml > obtenu.c14n
diff attendu.c14n obtenu.c14n
Deux des trois lignes du tableau disparaissent : l'ordre des attributs est fixé, les déclarations d'espaces de noms sont normalisées.
🔴 La troisième ne disparaît pas. La canonicalisation ne touche pas aux nœuds de texte : un document indenté et le même document sur une seule ligne restent différents après passage. C'est la surprise la plus fréquente, et elle envoie chercher un défaut de sérialisation là où il n'y en a pas — voir les espaces blancs en XSLT.
⚠️ Et elle ajoute quelque chose. La forme canonique se calcule sur le modèle de données, valeurs d'attributs par défaut comprises. Si l'un des deux documents a résolu sa DTD et l'autre non, ils divergeront sur des attributs que ni l'un ni l'autre n'écrit. C'est le mécanisme décrit dans résoudre une DTD hors ligne avec un catalogue, vu depuis l'autre bout.
Ce qu'aucun comparateur ne décide à votre place
L'ordre des éléments frères, lui, est significatif en XML :
<facture><ligne id="2"/><ligne id="1"/></facture>
⚠️ Aucun outil ne peut deviner si l'inversion de ces deux ligne est un défaut ou une variante
acceptable : cela dépend de votre modèle de données, pas du format. Un comparateur qui trie les
frères rend vertes des sorties fausses ; un comparateur qui ne trie jamais rend rouges des sorties
correctes. La décision se déclare, elle ne se devine pas.
Le même arbitrage revient pour les identifiants engendrés, les horodatages et les chemins absolus : trois choses qui diffèrent à chaque exécution sans que rien ne soit faux.
Ignorer trop est pire qu'ignorer rien
Devant un rouge permanent, la tentation est d'élargir la liste des différences ignorées jusqu'à ce que la comparaison passe. 🔴 Le point d'arrivée de cette pente est un test qui compare deux documents vides — vert, rapide, et sans aucune valeur.
Une règle simple tient le cap : on n'ignore que ce qu'on peut nommer et justifier en une phrase. « Les blancs entre éléments, parce que l'étape suivante réindente de toute façon » est une justification. « Les blancs, parce que ça échouait » n'en est pas une.
⚠️ Ignorer les blancs est d'ailleurs le choix le plus coûteux de la liste : c'est la classe de défauts que la comparaison d'arbres détecte le mieux, et que l'œil humain ne voit pas du tout.
Ce que cet article ne couvre pas
- la validation : un document peut être conforme à son schéma et faux, et l'inverse ;
- la comparaison de fragments ou de séquences, qui n'ont pas de racine unique ;
- les différences d'encodage, invisibles une fois le document lu — elles se cherchent sur les octets, comme le décrit l'encodage de sortie XSLT.
Le chapitre valider et comparer du manuel détaille les règles appliquées et la façon dont les écarts sont présentés.