Come confrontare due file XML e vedere cosa è cambiato

Il modo più veloce per confrontare due file XML è incollarli entrambi in uno strumento di diff affiancato, formattarli allo stesso modo e leggere le righe che evidenzia. Il confronto è la parte facile. È il rumore a confondere le persone: attributi riordinati, spazi tra i tag e prefissi di namespace possono far sembrare due file che significano la stessa cosa come se non avessero nulla in comune.

Questa guida spiega come ottenere un diff XML pulito e affidabile. Vedremo perché due documenti equivalenti si allontanano sulla carta, quali metodi vale la pena conoscere, e un esempio pratico da seguire. Se vuoi solo lo strumento, la nostra pagina di confronto XML fa tutto questo nel browser.

Perché i file XML sono ingannevolmente difficili da confrontare

XML ha una grammatica rigorosa (vedi la specifica XML del W3C), ma lascia agli autori molta libertà su come disporre il testo. Due documenti possono descrivere esattamente gli stessi dati e differire byte per byte. Un diff di testo semplice non capisce nulla di tutto ciò, quindi segnala tutto.

Ecco il fatto chiave da tenere a mente: in XML, l'ordine degli attributi di un elemento non è significativo. L' XML Information Set tratta gli attributi come un insieme non ordinato. Quindi <user id="7" role="admin"/> e <user role="admin" id="7"/> contengono la stessa informazione, anche se un diff di righe li dipinge di rosso e verde. L'ordine degli elementi, invece, di solito conta.

Sembra una modifica, ma di solito non lo è
Cosa vedi nel diffÈ una modifica reale?Cosa fare
Attributi in ordine diversoNo, l'ordine degli attributi non è significativoCanonicalizza entrambi i lati
Indentazione di 2 vs 4 spaziNoFormatta entrambi i lati allo stesso modo
Spazi tra gli elementiDi solito noFormatta, o rimuovi gli spazi insignificanti
<br/> vs <br></br>No, stesso elemento vuotoCanonicalizza entrambi i lati
Un prefisso di namespace diverso per lo stesso URINo, i prefissi sono etichette arbitrarieConfronta per URI del namespace, non per prefisso
Elementi figli in ordine diversoDi solito sì, l'ordine degli elementi contaIndaga, è probabilmente reale

Quell'ultima riga è quella da tenere d'occhio. L'ordine degli attributi è libero, ma l'ordine degli elementi figli fa parte del documento nella maggior parte degli schemi. Se vuoi il dettaglio su come un parser vede tutto questo, MDN ha un solido riferimento sull'analisi di XML con DOMParser.

Quattro modi per confrontare XML e quando usare ciascuno

Non esiste un unico metodo migliore. Dipende da dove si trovano i file e da cosa stai cercando di scoprire. Ecco come si confrontano le opzioni comuni.

MetodoIdeale perSforzoCapisce l'XML?
Controllo visivoFile minuscoli, uno o due elementiBassoNo, il parser sei tu
Strumento di diff onlineControlli rapidi, incollare da qualsiasi postoBassoCon la formattazione, sì
Riga di comando (xmllint)File su disco, scripting, forma canonicaMedioSì, con --c14n
IDE o git diffFile già in un repositoryBasso se committatoBasato su righe per impostazione predefinita

Per la maggior parte delle persone uno strumento da browser vince in velocità: niente da installare, e puoi incollare un frammento direttamente da un file di configurazione o da una risposta SOAP. Il problema è il rumore di formattazione, di cui ci occupiamo dopo. Se vivi nel terminale, xmllint di libxml2 è lo strumento da conoscere.

Il confronto pulito più veloce, passo dopo passo

Questa è la routine che uso quando qualcuno mi passa due file di configurazione e chiede "cosa c'è di diverso?". Richiede circa quindici secondi.

  1. Apri lo strumento di confronto XML.
  2. Incolla l'originale a sinistra, la nuova versione a destra.
  3. Clicca su Formatta su entrambi i lati così da condividere la stessa indentazione.
  4. Cerca le differenze reali. Il verde è aggiunto, il rosso è rimosso, e un valore modificato appare come uno di ciascuno.
  5. Ignora le righe che sono solo riordino di attributi o spazi.

Il passo tre è gran parte del trucco. Una volta che entrambi i documenti usano la stessa indentazione, l'unica cosa che resta da evidenziare è ciò che è davvero cambiato. Il nostro motore di diff è costruito sul diff-match-patch di Google, che confronta prima riga per riga per restare veloce anche su file lunghi.

Un esempio pratico

Supponi di revisionare una modifica a una configurazione di servizio. Ecco il prima:

<user id="7" role="editor">
  <name>Ada Lovelace</name>
  <active>true</active>
  <seats>3</seats>
</user>

Ed ecco il dopo, così come te lo ha passato un collega:

<user role="admin" id="7">
  <name>Ada Lovelace</name>
  <active>true</active>
  <seats>5</seats>
  <team>platform</team>
</user>

Mettili in un diff di righe grezzo e la primissima riga sembra modificata, perché id e role si sono scambiati di posto. Formatta entrambi, confronta per significato, e la storia reale è breve:

Cosa è cambiato davvero
NodoPrimaDopoModifica
@roleeditoradminModificato
seats35Modificato
teamplatformAggiunto
@id77Nessuna modifica (solo spostato)
nameAda LovelaceAda LovelaceNessuna modifica

Tre modifiche reali: una promozione di ruolo, un conteggio di posti e un nuovo elemento team. Lo scambio di attributi era rumore. Quella promozione da editor ad admin è esattamente il tipo di cosa che vuoi cogliere in revisione, ed è facile da perdere quando è sepolta sotto una riga che il diff ha segnalato per errore.

XML canonico: il modo corretto di ignorare il rumore

Formattare entrambi i lati gestisce l'indentazione, ma esiste uno standard pensato esattamente per questo problema. L'XML canonico, definito dal W3C in Canonical XML 1.1, riscrive un documento in un'unica forma normalizzata: attributi ordinati, elementi vuoti espansi, spazi nei tag normalizzati e attributi predefiniti resi espliciti. Due documenti equivalenti producono un output canonico identico. È l'equivalente XML dell'ordinamento delle chiavi JSON.

xmllint --c14n old.xml > old.c14n.xml
xmllint --c14n new.xml > new.c14n.xml
diff old.c14n.xml new.c14n.xml

Ora diff segnala solo i contenuti che sono davvero cambiati, perché entrambi i file sono stati normalizzati allo stesso modo. Se vuoi solo un'indentazione leggibile invece della forma canonica rigorosa, xmllint --format file.xml la formatta, che è l'equivalente da terminale del clic su Formatta nel browser.

Namespace: la parte che confonde tutti

I namespace XML permettono a due documenti di usare lo stesso vocabolario con etichette di prefisso diverse. <ns1:user> associato a un URI e <u:user> associato allo stesso URI sono lo stesso elemento; il prefisso è solo un soprannome locale. Un diff di testo vede ns1 contro u e segnala una modifica che non c'è. La soluzione è confrontare per URI del namespace invece che per prefisso, che è esattamente ciò che fa la canonicalizzazione. La specifica Namespaces in XML è il riferimento se devi risolvere una discussione al riguardo.

Insidie comuni da tenere d'occhio

InsidiaPerché mordeSoluzione
Codifica dei caratteriUn file UTF-8 e uno UTF-16 possono contenere lo stesso testo ma differire byte per byteNormalizza la codifica; la dichiarazione XML la indica
Riferimenti a entità&amp; e un & letterale possono apparire entrambi per lo stesso carattereCanonicalizza, il che risolve le entità in modo coerente
CDATA vs testo con escape<![CDATA[a<b]]> e a&lt;b sono lo stesso contenuto testualeConfronta il valore analizzato, non i byte grezzi
Spazi significativiDentro xml:space="preserve", gli spazi contano e non vanno rimossiNon tagliare alla cieca; rispetta xml:space
Tag auto-chiudenti<x/> e <x></x> sono identiciCanonicalizza così che entrambi vengano resi allo stesso modo

Diff di testo vs diff strutturale

Tutto quanto sopra è un diff di testo: veloce, visivo e perfetto per una persona che legge una modifica. Un diff strutturale va oltre e descrive la modifica in termini dell'albero XML: questo attributo è cambiato, quell'elemento figlio è stato inserito in questo percorso. Vuoi un diff strutturale quando un programma deve applicare la modifica o quando l'ordine degli elementi davvero non conta e vuoi ignorarlo. Per la revisione quotidiana, un diff di testo di due documenti formattati basta e avanza.

Strumenti correlati

L'XML è raramente l'unico formato con cui hai a che fare. Se stai confrontando payload di API, confrontare JSON applica la stessa idea al JSON. Le pagine con markup sono più facili da leggere nella pagina di confronto HTML, e le impostazioni di ambiente si allineano bene nello strumento di confronto config.

Domande frequenti

Confrontare file XML online li carica da qualche parte?
Su comparetext.org il diff viene eseguito nel tuo browser. I due file XML vengono confrontati da JavaScript sulla tua macchina, quindi nulla viene inviato a un server a meno che tu non clicchi esplicitamente su Salva o Condividi. Questo lo rende sicuro per file di configurazione, messaggi SOAP e altri dati che non vorresti incollare in un sito che carica tutto a ogni battitura.
Perché i miei due file XML mostrano ogni riga come diversa?
Quasi sempre è formattazione, non modifiche reali. Un file è minificato o indentato con tabulazioni, l'altro con due spazi, oppure gli attributi sono in un ordine diverso. Clicca su Formatta su entrambi i lati così da usare la stessa indentazione. Dopo di che il diff di solito si riduce alla manciata di valori che sono davvero cambiati. Per una normalizzazione più rigorosa, canonicalizza prima entrambi i file con xmllint --c14n.
L'ordine degli attributi conta quando si confronta XML?
No. In XML gli attributi di un elemento sono un insieme non ordinato, quindi <a x="1" y="2"/> e <a y="2" x="1"/> sono equivalenti. Un diff di testo semplice non lo sa e segnalerà il riordino come una modifica. L'XML canonico ordina gli attributi in un ordine fisso, quindi canonicalizzare entrambi i lati prima di confrontare fa sparire il falso positivo. L'ordine degli elementi, invece, di solito è significativo.
Come confronto XML ignorando i prefissi di namespace?
I prefissi di namespace sono etichette locali per un URI di namespace, quindi ns1:user e u:user associati allo stesso URI sono lo stesso elemento. Per confrontare correttamente, normalizza per URI invece che per prefisso. Il modo più semplice è canonicalizzare entrambi i documenti con xmllint --c14n, che riscrive le associazioni di namespace in modo coerente, e poi fare il diff dei risultati. Un diff di testo grezzo non può farlo da solo.
Posso confrontare file XML grandi senza che la pagina si blocchi?
Sì, fino a un certo punto. Un diff in modalità riga resta veloce su file con migliaia di righe perché confronta prima righe intere invece di ogni carattere. I file molto grandi (diversi megabyte) sono gestiti meglio con uno strumento da riga di comando come xmllint o git diff, che fa streaming dei dati. Per tutto ciò che puoi scorrere comodamente in un browser, un diff online è l'opzione più veloce.
Qual è la differenza tra un diff di testo e un diff strutturale di XML?
Un diff di testo confronta i file riga per riga, allo stesso modo in cui confronterebbe due temi. Un diff strutturale capisce l'albero XML, quindi sa che un attributo riordinato non è una modifica e può segnalare un elemento inserito tramite il suo percorso. I diff di testo sono più veloci e sufficienti per la maggior parte delle revisioni una volta formattati entrambi i lati. I diff strutturali contano quando un programma deve applicare la modifica o quando vuoi ignorare l'ordine degli elementi.

Pronto a provarlo? Incolla i tuoi file nello strumento di confronto XML e guarda cosa è cambiato.