Jämför två XML-filer och se vad som ändrats
Det snabbaste sättet att jämföra två XML-filer är att klistra in båda i ett diff-verktyg sida vid sida, formatera dem på samma sätt och läsa de rader som markeras. Själva jämförandet är den enkla delen. Bruset är det som ställer till det: omordnade attribut, blanksteg mellan taggar och namnrymdsprefix kan få två filer som betyder samma sak att se ut som om de inte har något gemensamt alls.
Den här guiden går igenom hur man får en ren och pålitlig XML-diff. Vi tittar på varför två likvärdiga dokument glider isär på pappret, vilka metoder som är värda att kunna och ett genomarbetat exempel att följa. Vill du bara ha verktyget gör vår XML-jämförelsesida allt detta i webbläsaren.
Varför XML-filer är bedrägligt svåra att jämföra
XML har en strikt grammatik (se W3C:s XML-specifikation), men ger skribenter stor frihet i hur texten läggs ut. Två dokument kan beskriva exakt samma data och ändå skilja sig byte för byte. En vanlig textdiff förstår inget av detta och flaggar därför allt.
Här är det viktigaste att hålla fast vid: i XML är ordningen på attributen
i ett element inte betydelsefull. Det
XML Information Set
behandlar attribut som en oordnad mängd. Så
<user id="7" role="admin"/> och
<user role="admin" id="7"/> bär samma information, även
om en raddiff målar dem röda och gröna. Elementordning, å andra sidan,
spelar oftast roll.
| Vad du ser i diffen | Är det en verklig ändring? | Vad du ska göra |
|---|---|---|
| Attribut i en annan ordning | Nej, attributordning är inte betydelsefull | Kanonisera båda sidor |
| 2-stegs vs 4-stegs indrag | Nej | Formatera båda på samma sätt |
| Blanksteg mellan element | Oftast inte | Formatera, eller ta bort obetydliga blanksteg |
<br/> vs <br></br> | Nej, samma tomma element | Kanonisera båda sidor |
| Ett annat namnrymdsprefix för samma URI | Nej, prefix är godtyckliga etiketter | Jämför efter namnrymds-URI, inte prefix |
| Barnelement i en annan ordning | Oftast ja, elementordning spelar roll | Undersök, detta är troligen verkligt |
Den sista raden är den att hålla koll på. Attributordning är fri, men ordningen på barnelement är en del av dokumentet i de flesta scheman. Vill du ha detaljerna kring hur en parser ser allt detta har MDN en gedigen referens om att tolka XML med DOMParser.
Fyra sätt att jämföra XML, och när man väljer vilket
Det finns ingen enskilt bästa metod. Det beror på var filerna ligger och vad du försöker ta reda på. Så här står sig de vanliga alternativen.
| Metod | Bäst för | Insats | Förstår XML? |
|---|---|---|---|
| Att läsa igenom det själv | Pyttesmå filer, ett eller två element | Låg | Nej, du är parsern |
| Diff-verktyg online | Snabba kontroller, inklistring var som helst | Låg | Med formatering, ja |
Kommandorad (xmllint) | Filer på disk, skript, kanonisk form | Medel | Ja, med --c14n |
IDE eller git diff | Filer som redan finns i ett repo | Låg vid commit | Radbaserad som standard |
För de flesta vinner ett webbläsarverktyg på snabbhet: inget att
installera, och du kan klistra in ett utdrag direkt från en
konfigurationsfil eller ett SOAP-svar. Haken är formateringsbrus, som vi
tar itu med härnäst. Lever du i terminalen är
xmllint från libxml2
verktyget att känna till.
Den snabbaste rena jämförelsen, steg för steg
Det här är rutinen jag använder när någon räcker mig två konfigurationsfiler och frågar "vad skiljer?" Det tar ungefär femton sekunder.
- Öppna XML-jämförelseverktyget.
- Klistra in originalet till vänster, den nya versionen till höger.
- Klicka på Formatera på båda sidor så att de använder samma indrag.
- Sök efter verkliga skillnader. Grönt är tillagt, rött är borttaget, och ett ändrat värde visas som ett av vardera.
- Ignorera raderna som bara är attributomordning eller blanksteg.
Steg tre är det mesta av tricket. När båda dokumenten använder samma indrag är det enda som återstår att markera vad som faktiskt ändrats. Vår diff-motor bygger på Googles diff-match-patch, som jämför rad för rad först så att den förblir snabb även på långa filer.
Ett genomarbetat exempel
Säg att du granskar en ändring i en tjänstekonfiguration. Här är före:
<user id="7" role="editor">
<name>Ada Lovelace</name>
<active>true</active>
<seats>3</seats>
</user>
Och här är efter, som en kollega räckte över den till dig:
<user role="admin" id="7">
<name>Ada Lovelace</name>
<active>true</active>
<seats>5</seats>
<team>platform</team>
</user>
Släng in dem i en rå raddiff och allra första raden ser ändrad ut,
eftersom id och role bytt plats. Formatera båda,
jämför efter betydelse, och den verkliga historien är kort:
| Nod | Före | Efter | Ändring |
|---|---|---|---|
@role | editor | admin | Ändrad |
seats | 3 | 5 | Ändrad |
team | — | platform | Tillagd |
@id | 7 | 7 | Ingen ändring (bara flyttad) |
name | Ada Lovelace | Ada Lovelace | Ingen ändring |
Tre verkliga ändringar: en rollhöjning, ett antal platser och ett nytt
team-element. Attributbytet var brus. Den befordran från
editor till admin är precis den sortens sak du
vill fånga i granskningen, och den är lätt att missa när den är begravd
under en rad som diffen felaktigt flaggat.
Kanonisk XML: det rätta sättet att ignorera brus
Att formatera båda sidor hanterar indrag, men det finns en standard byggd just för det här problemet. Kanonisk XML, definierad av W3C i Canonical XML 1.1, skriver om ett dokument till en enda normaliserad form: attribut sorterade, tomma element expanderade, blanksteg i taggar normaliserade och standardattribut gjorda explicita. Två likvärdiga dokument ger identisk kanonisk utdata. Det är XML-motsvarigheten till att sortera JSON-nycklar.
xmllint --c14n old.xml > old.c14n.xml
xmllint --c14n new.xml > new.c14n.xml
diff old.c14n.xml new.c14n.xml
Nu rapporterar diff bara innehåll som verkligen ändrats,
eftersom båda filerna normaliserats på samma sätt. Vill du bara ha läsbart
indrag i stället för strikt kanonisk form formaterar
xmllint --format file.xml den, vilket är terminalmotsvarigheten
till att klicka på Formatera i webbläsaren.
Namnrymder: delen som förvirrar alla
XML-namnrymder låter två dokument använda samma vokabulär med olika
prefixetiketter. <ns1:user> bunden till en URI och
<u:user> bunden till samma URI är samma
element; prefixet är bara ett lokalt smeknamn. En textdiff ser
ns1 mot u och flaggar en ändring som inte är en.
Lösningen är att jämföra efter namnrymds-URI snarare än prefixet, vilket är
precis vad kanonisering gör. Specifikationen
Namespaces in XML
är referensen om du behöver avgöra ett gräl om det.
Vanliga fallgropar att hålla utkik efter
| Fallgrop | Varför den biter | Lösning |
|---|---|---|
| Teckenkodning | En UTF-8- och en UTF-16-fil kan innehålla samma text men skilja sig byte för byte | Normalisera kodningen; XML-deklarationen anger den |
| Entitetsreferenser | & och ett bokstavligt & kan båda förekomma för samma tecken | Kanonisera, vilket löser upp entiteter konsekvent |
| CDATA vs escapad text | <![CDATA[a<b]]> och a<b är samma textinnehåll | Jämför det tolkade värdet, inte de råa byten |
| Betydelsefulla blanksteg | Inuti xml:space="preserve" spelar blanksteg roll och får inte tas bort | Trimma inte blint; respektera xml:space |
| Självstängande taggar | <x/> och <x></x> är identiska | Kanonisera så att båda återges likadant |
Textdiff vs strukturell diff
Allt ovan är en text-diff: snabb, visuell och perfekt för en person som läser en ändring. En strukturell diff går längre och beskriver ändringen i termer av XML-trädet: detta attribut ändrades, det barnelementet infogades vid den här sökvägen. Du vill ha en strukturell diff när ett program behöver tillämpa ändringen eller när elementordning verkligen inte spelar roll och du vill ignorera den. För daglig granskning räcker en textdiff av två formaterade dokument gott och väl.
Relaterade verktyg
XML är sällan det enda formatet du har att göra med. Jämför du API-payloads tillämpar JSON-jämförelse samma idé på JSON. Uppmärkta sidor är lättare att läsa på HTML-jämförelsesidan, och miljöinställningar passar bra på config-jämförelseverktyget.
Vanliga frågor
- Laddar onlinejämförelse av XML-filer upp dem någonstans?
- På comparetext.org körs diffen i din webbläsare. De två XML-filerna jämförs av JavaScript på din egen dator, så inget skickas till en server om du inte uttryckligen klickar på Spara eller Dela. Det gör det säkert för konfigurationsfiler, SOAP-meddelanden och annan data du inte vill klistra in på en webbplats som laddar upp vid varje tangenttryckning.
- Varför visas varje rad som olik mellan mina två XML-filer?
- Nästan alltid handlar det om formatering, inte verkliga ändringar. En fil är minifierad eller indragen med tabbar, den andra med två blanksteg, eller så är attributen i en annan ordning. Klicka på Formatera på båda sidor så att de använder samma indrag. Därefter krymper diffen oftast till de handfull värden som verkligen ändrats. För striktare normalisering, kanonisera båda filerna med xmllint --c14n först.
- Spelar attributordning roll när man jämför XML?
- Nej. I XML är attributen i ett element en oordnad mängd, så
<a x="1" y="2"/>och<a y="2" x="1"/>är likvärdiga. En vanlig textdiff vet inte detta och flaggar omordningen som en ändring. Kanonisk XML sorterar attribut i en fast ordning, så att kanonisera båda sidor före jämförelse får falsklarmet att försvinna. Elementordning, däremot, är oftast betydelsefull. - Hur jämför jag XML och ignorerar namnrymdsprefix?
- Namnrymdsprefix är lokala etiketter för en namnrymds-URI, så
ns1:userochu:userbundna till samma URI är samma element. För att jämföra korrekt normaliserar du efter URI snarare än prefix. Enklaste sättet är att kanonisera båda dokumenten med xmllint --c14n, som skriver om namnrymdsbindningar konsekvent, och sedan diffa resultaten. En rå textdiff kan inte göra detta på egen hand. - Kan jag jämföra stora XML-filer utan att sidan fryser?
- Ja, upp till en viss gräns. En radbaserad diff förblir snabb på filer med tusentals rader eftersom den jämför hela rader först i stället för varje tecken. Mycket stora filer (flera megabyte) hanteras bättre med ett kommandoradsverktyg som xmllint eller git diff, som strömmar data. För allt du bekvämt kan skrolla igenom i en webbläsare är en onlinediff det snabbare alternativet.
- Vad är skillnaden mellan en textdiff och en strukturell diff av XML?
- En textdiff jämför filerna rad för rad, på samma sätt som den skulle jämföra två uppsatser. En strukturell diff förstår XML-trädet, så den vet att ett omordnat attribut inte är en ändring och kan rapportera ett infogat element via dess sökväg. Textdiffar är snabbare och tillräckligt bra för de flesta granskningar när båda sidor är formaterade. Strukturella diffar spelar roll när ett program behöver tillämpa ändringen eller när du vill att elementordning ignoreras.
Redo att testa? Klistra in dina filer i XML-jämförelseverktyget och se vad som ändrats.