Sammenlign to XML-filer og se, hvad der er ændret

Den hurtigste måde at sammenligne to XML-filer på er at indsætte begge i et side-om-side diff-værktøj, formatere dem ens og læse de linjer, det fremhæver. Selve sammenligningen er den nemme del. Støjen er det, der driller folk: omarrangerede attributter, mellemrum mellem tags og namespace-præfikser kan få to filer, der betyder det samme, til at se ud, som om de slet ikke har noget til fælles.

Denne guide gennemgår, hvordan man får en ren og troværdig XML-diff. Vi ser på, hvorfor to ækvivalente dokumenter driver fra hinanden på papiret, hvilke metoder der er værd at kende, og et gennemarbejdet eksempel, du kan følge. Vil du bare have værktøjet, klarer vores XML-sammenligningsside det hele i browseren.

Hvorfor XML-filer er bedragerisk svære at sammenligne

XML har en streng grammatik (se W3C's XML-specifikation), men giver skribenter stor frihed i, hvordan teksten er sat op. To dokumenter kan beskrive præcis de samme data og stadig være forskellige byte for byte. En almindelig tekstdiff forstår intet af dette og flagger derfor det hele.

Her er det vigtigste at holde fast i: i XML er rækkefølgen af attributter på et element ikke betydningsfuld. Det XML Information Set behandler attributter som en uordnet mængde. Så <user id="7" role="admin"/> og <user role="admin" id="7"/> bærer den samme information, selv om en linjediff maler dem røde og grønne. Elementrækkefølge derimod betyder som regel noget.

Ligner en ændring, men er det som regel ikke
Hvad du ser i diffenEr det en reel ændring?Hvad du skal gøre
Attributter i en anden rækkefølgeNej, attributrækkefølge er ikke betydningsfuldKanoniser begge sider
2-mellemrums vs 4-mellemrums indrykningNejFormater begge ens
Mellemrum mellem elementerSom regel ikkeFormater, eller fjern ubetydelige mellemrum
<br/> vs <br></br>Nej, samme tomme elementKanoniser begge sider
Et andet namespace-præfiks for samme URINej, præfikser er vilkårlige etiketterSammenlign efter namespace-URI, ikke præfiks
Underelementer i en anden rækkefølgeSom regel ja, elementrækkefølge betyder nogetUndersøg, dette er sandsynligvis reelt

Den sidste række er den, man skal holde øje med. Attributrækkefølge er fri, men rækkefølgen af underelementer er en del af dokumentet i de fleste skemaer. Vil du have detaljerne om, hvordan en parser ser alt dette, har MDN en solid reference om at parse XML med DOMParser.

Fire måder at sammenligne XML, og hvornår man griber til hver

Der er ingen enkelt bedste metode. Det afhænger af, hvor filerne ligger, og hvad du forsøger at finde ud af. Sådan klarer de gængse muligheder sig.

MetodeBedst tilIndsatsForstår XML?
At læse det igennem selvBittesmå filer, et eller to elementerLavNej, du er parseren
Online diff-værktøjHurtige tjek, indsætning fra hvor som helstLavMed formatering, ja
Kommandolinje (xmllint)Filer på disk, scripting, kanonisk formMellemJa, med --c14n
IDE eller git diffFiler der allerede er i et repoLav ved commitLinjebaseret som standard

For de fleste vinder et browserværktøj på hastighed: intet at installere, og du kan indsætte et uddrag direkte fra en konfigurationsfil eller et SOAP-svar. Hagen er formateringsstøj, som vi tager fat på nu. Lever du i terminalen, er xmllint fra libxml2 værktøjet at kende.

Den hurtigste rene sammenligning, trin for trin

Det er rutinen, jeg bruger, når nogen rækker mig to konfigurationsfiler og spørger "hvad er forskellen?" Det tager omkring femten sekunder.

  1. Åbn XML-sammenligningsværktøjet.
  2. Indsæt originalen til venstre, den nye version til højre.
  3. Klik på Formater på begge sider, så de bruger samme indrykning.
  4. Søg efter reelle forskelle. Grøn er tilføjet, rød er fjernet, og en ændret værdi vises som en af hver.
  5. Ignorer rækkerne, der kun er attributomarrangering eller mellemrum.

Trin tre er det meste af tricket. Når begge dokumenter bruger samme indrykning, er det eneste, der er tilbage at fremhæve, hvad der faktisk er ændret. Vores diff-motor bygger på Googles diff-match-patch, som sammenligner linje for linje først, så den forbliver hurtig selv på lange filer.

Et gennemarbejdet eksempel

Sig, at du gennemgår en ændring i en tjenestekonfiguration. Her er før:

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

Og her er efter, som en kollega rakte den til dig:

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

Smid dem ind i en rå linjediff, og den allerførste linje ser ændret ud, fordi id og role har byttet plads. Formater begge, sammenlign efter betydning, og den reelle historie er kort:

Hvad der faktisk blev ændret
NodeFørEfterÆndring
@roleeditoradminÆndret
seats35Ændret
teamplatformTilføjet
@id77Ingen ændring (bare flyttet)
nameAda LovelaceAda LovelaceIngen ændring

Tre reelle ændringer: en rolleforfremmelse, et antal pladser og et nyt team-element. Attributbyttet var støj. Den forfremmelse fra editor til admin er præcis den slags, du vil fange i gennemgangen, og den er let at overse, når den er begravet under en linje, diffen fejlagtigt flaggede.

Kanonisk XML: den rigtige måde at ignorere støj på

At formatere begge sider håndterer indrykning, men der findes en standard bygget netop til dette problem. Kanonisk XML, defineret af W3C i Canonical XML 1.1, omskriver et dokument til én normaliseret form: attributter sorteret, tomme elementer udvidet, mellemrum i tags normaliseret og standardattributter gjort eksplicitte. To ækvivalente dokumenter producerer identisk kanonisk output. Det er XML-ækvivalenten til at sortere JSON-nøgler.

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

Nu rapporterer diff kun indhold, der virkelig er ændret, fordi begge filer er normaliseret på samme måde. Vil du bare have læsbar indrykning i stedet for streng kanonisk form, formaterer xmllint --format file.xml den, hvilket er terminal-ækvivalenten til at klikke på Formater i browseren.

Namespaces: den del, der forvirrer alle

XML-namespaces lader to dokumenter bruge det samme vokabular med forskellige præfiks-etiketter. <ns1:user> bundet til en URI og <u:user> bundet til samme URI er det samme element; præfikset er bare et lokalt kælenavn. En tekstdiff ser ns1 mod u og flagger en ændring, der ikke er en. Løsningen er at sammenligne efter namespace-URI frem for præfikset, hvilket er præcis, hvad kanonisering gør. Specifikationen Namespaces in XML er referencen, hvis du skal afgøre en diskussion om det.

Almindelige faldgruber at holde øje med

FaldgrubeHvorfor den biderLøsning
TegnkodningEn UTF-8- og en UTF-16-fil kan indeholde samme tekst, men være forskellige byte for byteNormaliser kodningen; XML-deklarationen angiver den
Entitetsreferencer&amp; og et bogstaveligt & kan begge optræde for samme tegnKanoniser, hvilket opløser entiteter konsekvent
CDATA vs escaped tekst<![CDATA[a<b]]> og a&lt;b er det samme tekstindholdSammenlign den parsede værdi, ikke de rå bytes
Betydningsfulde mellemrumInden i xml:space="preserve" betyder mellemrum noget og må ikke fjernesTrim ikke blindt; respekter xml:space
Selvlukkende tags<x/> og <x></x> er identiskeKanoniser, så begge gengives ens

Tekstdiff vs strukturel diff

Alt ovenstående er en tekst-diff: hurtig, visuel og perfekt for en person, der læser en ændring. En strukturel diff går videre og beskriver ændringen i form af XML-træet: dette attribut blev ændret, det underelement blev indsat ved denne sti. Du vil have en strukturel diff, når et program skal anvende ændringen, eller når elementrækkefølge virkelig ikke betyder noget, og du vil have den ignoreret. Til daglig gennemgang er en tekstdiff af to formaterede dokumenter rigeligt.

Relaterede værktøjer

XML er sjældent det eneste format, du har med at gøre. Sammenligner du API-payloads, anvender JSON-sammenligning den samme idé på JSON. Opmærkede sider er lettere at læse på HTML-sammenligningssiden, og miljøindstillinger passer godt på config-sammenligningsværktøjet.

Ofte stillede spørgsmål

Uploader online-sammenligning af XML-filer dem nogen steder?
På comparetext.org kører diffen i din browser. De to XML-filer sammenlignes af JavaScript på din egen maskine, så intet sendes til en server, medmindre du udtrykkeligt klikker på Gem eller Del. Det gør det sikkert for konfigurationsfiler, SOAP-beskeder og andre data, du ikke vil indsætte på et site, der uploader ved hvert tastetryk.
Hvorfor vises hver linje som forskellig mellem mine to XML-filer?
Næsten altid er det formatering, ikke reelle ændringer. Den ene fil er minificeret eller indrykket med tabs, den anden med to mellemrum, eller attributterne er i en anden rækkefølge. Klik på Formater på begge sider, så de bruger samme indrykning. Derefter skrumper diffen som regel til de få værdier, der virkelig er ændret. For strengere normalisering, kanoniser begge filer med xmllint --c14n først.
Betyder attributrækkefølge noget, når man sammenligner XML?
Nej. I XML er attributterne på et element en uordnet mængde, så <a x="1" y="2"/> og <a y="2" x="1"/> er ækvivalente. En almindelig tekstdiff ved ikke dette og flagger omarrangeringen som en ændring. Kanonisk XML sorterer attributter i en fast rækkefølge, så når man kanoniserer begge sider før sammenligning, forsvinder den falske alarm. Elementrækkefølge derimod er som regel betydningsfuld.
Hvordan sammenligner jeg XML og ignorerer namespace-præfikser?
Namespace-præfikser er lokale etiketter for en namespace-URI, så ns1:user og u:user bundet til samme URI er det samme element. For at sammenligne korrekt normaliserer du efter URI frem for præfiks. Den nemmeste måde er at kanonisere begge dokumenter med xmllint --c14n, som omskriver namespace-bindinger konsekvent, og derefter diffe resultaterne. En rå tekstdiff kan ikke gøre dette på egen hånd.
Kan jeg sammenligne store XML-filer uden at siden fryser?
Ja, op til et vist punkt. En linjebaseret diff forbliver hurtig på filer med tusindvis af linjer, fordi den sammenligner hele linjer først i stedet for hvert tegn. Meget store filer (flere megabyte) håndteres bedre med et kommandolinjeværktøj som xmllint eller git diff, der streamer dataene. For alt, du komfortabelt kan scrolle igennem i en browser, er en online-diff den hurtigere mulighed.
Hvad er forskellen mellem en tekstdiff og en strukturel diff af XML?
En tekstdiff sammenligner filerne linje for linje, på samme måde som den ville sammenligne to essays. En strukturel diff forstår XML-træet, så den ved, at et omarrangeret attribut ikke er en ændring, og kan rapportere et indsat element via dets sti. Tekstdiffs er hurtigere og gode nok til de fleste gennemgange, når begge sider er formateret. Strukturelle diffs betyder noget, når et program skal anvende ændringen, eller når du vil have elementrækkefølge ignoreret.

Klar til at prøve det? Indsæt dine filer i XML-sammenligningsværktøjet og se, hvad der er ændret.