Jak porównać dwa pliki XML i zobaczyć, co się zmieniło
Najszybszy sposób porównania dwóch plików XML to wklejenie obu do narzędzia diff wyświetlającego je obok siebie, sformatowanie ich tak samo i odczytanie podświetlonych wierszy. Samo porównywanie to łatwa część. To szum wprowadza ludzi w błąd: przestawione atrybuty, białe znaki między tagami i prefiksy namespace'ów mogą sprawić, że dwa pliki znaczące to samo wyglądają, jakby nie miały ze sobą nic wspólnego.
Ten przewodnik pokazuje, jak uzyskać czysty, wiarygodny diff XML. Przyjrzymy się, dlaczego dwa równoważne dokumenty rozjeżdżają się na papierze, które metody warto znać, oraz opracowanemu przykładowi, który możesz prześledzić. Jeśli chcesz tylko narzędzia, nasza strona porównywania XML robi to wszystko w przeglądarce.
Dlaczego pliki XML są zwodniczo trudne do porównania
XML ma ścisłą gramatykę (zobacz specyfikację XML W3C), ale daje autorom dużą swobodę w rozmieszczaniu tekstu. Dwa dokumenty mogą opisywać dokładnie te same dane, a mimo to różnić się bajt po bajcie. Zwykły diff tekstowy nic z tego nie rozumie, więc oznacza wszystko.
Oto kluczowy fakt do zapamiętania: w XML kolejność atrybutów elementu nie jest
znacząca.
XML Information Set
traktuje atrybuty jako zbiór nieuporządkowany. Zatem
<user id="7" role="admin"/> i
<user role="admin" id="7"/> niosą tę samą informację, nawet jeśli
diff wierszowy maluje je na czerwono i zielono. Kolejność elementów z kolei zwykle ma znaczenie.
| Co widzisz w diffie | Czy to prawdziwa zmiana? | Co zrobić |
|---|---|---|
| Atrybuty w innej kolejności | Nie, kolejność atrybutów nie jest znacząca | Skanonikalizuj obie strony |
| Wcięcie 2 vs 4 spacje | Nie | Sformatuj obie strony tak samo |
| Białe znaki między elementami | Zwykle nie | Sformatuj lub usuń nieznaczące białe znaki |
<br/> vs <br></br> | Nie, ten sam pusty element | Skanonikalizuj obie strony |
| Inny prefiks namespace'u dla tego samego URI | Nie, prefiksy to dowolne etykiety | Porównuj według URI namespace'u, nie prefiksu |
| Elementy potomne w innej kolejności | Zwykle tak, kolejność elementów ma znaczenie | Zbadaj, to prawdopodobnie prawdziwe |
Ten ostatni wiersz to ten, na który trzeba uważać. Kolejność atrybutów jest dowolna, ale kolejność elementów potomnych jest częścią dokumentu w większości schematów. Jeśli chcesz szczegółów na temat tego, jak parser widzi to wszystko, MDN ma solidne źródło o parsowaniu XML za pomocą DOMParser.
Cztery sposoby porównywania XML i kiedy po który sięgnąć
Nie ma jednej najlepszej metody. Zależy to od tego, gdzie są pliki i czego próbujesz się dowiedzieć. Oto jak wypadają popularne opcje.
| Metoda | Najlepsza do | Wysiłek | Rozumie XML? |
|---|---|---|---|
| Oględziny | Maleńkie pliki, jeden lub dwa elementy | Niski | Nie, to ty jesteś parserem |
| Narzędzie diff online | Szybkie sprawdzenia, wklejanie skądkolwiek | Niski | Z formatowaniem tak |
Wiersz poleceń (xmllint) | Pliki na dysku, skrypty, postać kanoniczna | Średni | Tak, z --c14n |
IDE lub git diff | Pliki już w repozytorium | Niski, jeśli zacommitowane | Domyślnie oparty na wierszach |
Dla większości ludzi narzędzie w przeglądarce wygrywa szybkością: nic do
zainstalowania, a fragment można wkleić wprost z pliku konfiguracyjnego lub
odpowiedzi SOAP. Haczykiem jest szum formatowania, którym zajmiemy się dalej. Jeśli
żyjesz w terminalu, xmllint z biblioteki
libxml2
to narzędzie, które warto znać.
Najszybsze czyste porównanie, krok po kroku
Oto rutyna, której używam, gdy ktoś podaje mi dwa pliki konfiguracyjne i pyta „co jest inne?”. Zajmuje to około piętnastu sekund.
- Otwórz narzędzie porównywania XML.
- Wklej oryginał po lewej, nową wersję po prawej.
- Kliknij Formatuj po obu stronach, aby miały to samo wcięcie.
- Szukaj prawdziwych różnic. Zielony to dodane, czerwony to usunięte, a zmieniona wartość pojawia się jako po jednej z każdego.
- Ignoruj wiersze, które są jedynie przestawieniem atrybutów lub białymi znakami.
Krok trzeci to większość sztuczki. Gdy oba dokumenty używają tego samego wcięcia, jedyne, co pozostaje do podświetlenia, to to, co naprawdę się zmieniło. Nasz silnik diff opiera się na diff-match-patch od Google, który najpierw porównuje wiersz po wierszu, dzięki czemu pozostaje szybki nawet przy długich plikach.
Opracowany przykład
Załóżmy, że przeglądasz zmianę w konfiguracji usługi. Oto wersja przed:
<user id="7" role="editor">
<name>Ada Lovelace</name>
<active>true</active>
<seats>3</seats>
</user>
A oto wersja po, tak jak podał ci ją kolega z zespołu:
<user role="admin" id="7">
<name>Ada Lovelace</name>
<active>true</active>
<seats>5</seats>
<team>platform</team>
</user>
Wrzuć je do surowego diffa wierszowego, a sam pierwszy wiersz wygląda na zmieniony,
ponieważ id i role zamieniły się miejscami. Sformatuj oba,
porównaj według znaczenia, a prawdziwa historia jest krótka:
| Węzeł | Przed | Po | Zmiana |
|---|---|---|---|
@role | editor | admin | Zmodyfikowano |
seats | 3 | 5 | Zmodyfikowano |
team | — | platform | Dodano |
@id | 7 | 7 | Bez zmian (tylko przeniesiono) |
name | Ada Lovelace | Ada Lovelace | Bez zmian |
Trzy prawdziwe edycje: awans roli, liczba miejsc i nowy element team. Zamiana
atrybutów była szumem. Ten awans z editor na admin to
dokładnie taka rzecz, którą chcesz wychwycić podczas przeglądu, a łatwo ją przeoczyć,
gdy jest pogrzebana pod wierszem, który diff błędnie oznaczył.
Kanoniczny XML: właściwy sposób ignorowania szumu
Formatowanie obu stron załatwia wcięcie, ale istnieje standard stworzony dokładnie do tego problemu. Kanoniczny XML, zdefiniowany przez W3C w Canonical XML 1.1, przepisuje dokument do jednej znormalizowanej postaci: atrybuty posortowane, puste elementy rozwinięte, białe znaki w tagach znormalizowane, a atrybuty domyślne uczynione jawnymi. Dwa równoważne dokumenty dają identyczny wynik kanoniczny. To odpowiednik sortowania kluczy JSON w świecie XML.
xmllint --c14n old.xml > old.c14n.xml
xmllint --c14n new.xml > new.c14n.xml
diff old.c14n.xml new.c14n.xml
Teraz diff zgłasza tylko treść, która naprawdę się zmieniła, ponieważ oba
pliki zostały znormalizowane w ten sam sposób. Jeśli chcesz tylko czytelnego wcięcia
zamiast ścisłej postaci kanonicznej, xmllint --format file.xml ładnie je
formatuje, co jest terminalowym odpowiednikiem kliknięcia Formatuj w przeglądarce.
Namespace'y: część, która wszystkich myli
Namespace'y XML pozwalają dwóm dokumentom używać tego samego słownictwa z różnymi
etykietami prefiksów. <ns1:user> powiązany z URI i
<u:user> powiązany z tym samym URI to ten sam element;
prefiks to tylko lokalna ksywka. Diff tekstowy widzi ns1 kontra
u i oznacza zmianę, której nie ma. Rozwiązaniem jest porównywanie według
URI namespace'u zamiast prefiksu, co jest dokładnie tym, co robi kanonikalizacja.
Specyfikacja
Namespaces in XML
to punkt odniesienia, jeśli musisz rozstrzygnąć spór na ten temat.
Częste pułapki, na które warto uważać
| Pułapka | Dlaczego gryzie | Rozwiązanie |
|---|---|---|
| Kodowanie znaków | Plik UTF-8 i UTF-16 mogą zawierać ten sam tekst, ale różnić się bajt po bajcie | Znormalizuj kodowanie; deklaracja XML je podaje |
| Odwołania do encji | & i dosłowne & mogą wystąpić oba dla tego samego znaku | Skanonikalizuj, co rozwiązuje encje spójnie |
| CDATA vs tekst z escape'ami | <![CDATA[a<b]]> i a<b to ta sama treść tekstowa | Porównuj wartość sparsowaną, nie surowe bajty |
| Znaczące białe znaki | Wewnątrz xml:space="preserve" spacje mają znaczenie i nie wolno ich usuwać | Nie przycinaj na ślepo; uszanuj xml:space |
| Tagi samozamykające | <x/> i <x></x> są identyczne | Skanonikalizuj, aby oba renderowały się tak samo |
Diff tekstowy vs diff strukturalny
Wszystko powyżej to diff tekstowy: szybki, wizualny i idealny dla osoby czytającej zmianę. Diff strukturalny idzie dalej i opisuje zmianę w kategoriach drzewa XML: ten atrybut się zmienił, ten element potomny wstawiono w tej ścieżce. Diffa strukturalnego chcesz, gdy program musi zastosować zmianę albo gdy kolejność elementów naprawdę nie ma znaczenia i chcesz ją zignorować. Do codziennego przeglądu diff tekstowy dwóch sformatowanych dokumentów w zupełności wystarcza.
Powiązane narzędzia
XML rzadko jest jedynym formatem, z którym masz do czynienia. Jeśli porównujesz ładunki API, porównywanie JSON stosuje ten sam pomysł do JSON. Strony ze znacznikami łatwiej czyta się na stronie porównywania HTML, a ustawienia środowiska ładnie się układają w narzędziu porównywania config.
Najczęściej zadawane pytania
- Czy porównywanie plików XML online przesyła je gdzieś?
- Na comparetext.org diff działa w twojej przeglądarce. Dwa pliki XML są porównywane przez JavaScript na twojej własnej maszynie, więc nic nie jest wysyłane na serwer, chyba że wyraźnie klikniesz Zapisz lub Udostępnij. To czyni go bezpiecznym dla plików konfiguracyjnych, wiadomości SOAP i innych danych, których nie chciałbyś wklejać na stronę przesyłającą wszystko przy każdym naciśnięciu klawisza.
- Dlaczego moje dwa pliki XML pokazują każdy wiersz jako różny?
- Prawie zawsze to formatowanie, nie prawdziwe zmiany. Jeden plik jest zminifikowany lub wcięty tabulatorami, drugi dwiema spacjami, albo atrybuty są w innej kolejności. Kliknij Formatuj po obu stronach, aby używały tego samego wcięcia. Potem diff zwykle kurczy się do garstki wartości, które naprawdę się zmieniły. Dla ściślejszej normalizacji najpierw skanonikalizuj oba pliki za pomocą xmllint --c14n.
- Czy kolejność atrybutów ma znaczenie przy porównywaniu XML?
- Nie. W XML atrybuty elementu to zbiór nieuporządkowany, więc
<a x="1" y="2"/>i<a y="2" x="1"/>są równoważne. Zwykły diff tekstowy o tym nie wie i oznaczy przestawienie jako zmianę. Kanoniczny XML sortuje atrybuty w stałej kolejności, więc skanonikalizowanie obu stron przed porównaniem sprawia, że fałszywy alarm znika. Kolejność elementów z kolei zwykle jest znacząca. - Jak porównać XML, ignorując prefiksy namespace'ów?
- Prefiksy namespace'ów to lokalne etykiety dla URI namespace'u, więc
ns1:useriu:userpowiązane z tym samym URI to ten sam element. Aby porównać poprawnie, normalizuj według URI, a nie prefiksu. Najprostszy sposób to skanonikalizować oba dokumenty za pomocą xmllint --c14n, co spójnie przepisuje powiązania namespace'ów, a następnie zdiffować wyniki. Surowy diff tekstowy nie zrobi tego sam. - Czy mogę porównać duże pliki XML bez zawieszania strony?
- Tak, do pewnego stopnia. Diff w trybie wierszowym pozostaje szybki na plikach z tysiącami wierszy, ponieważ najpierw porównuje całe wiersze zamiast każdego znaku. Bardzo duże pliki (kilka megabajtów) lepiej obsłużyć narzędziem wiersza poleceń, jak xmllint lub git diff, które strumieniuje dane. Dla wszystkiego, co da się wygodnie przewinąć w przeglądarce, diff online jest szybszą opcją.
- Jaka jest różnica między diffem tekstowym a strukturalnym XML?
- Diff tekstowy porównuje pliki wiersz po wierszu, tak samo jak porównałby dwa wypracowania. Diff strukturalny rozumie drzewo XML, więc wie, że przestawiony atrybut to nie zmiana, i potrafi zgłosić wstawiony element po jego ścieżce. Diffy tekstowe są szybsze i wystarczające do większości przeglądów, gdy obie strony są sformatowane. Diffy strukturalne mają znaczenie, gdy program musi zastosować zmianę lub gdy chcesz zignorować kolejność elementów.
Gotowy, żeby spróbować? Wklej swoje pliki do narzędzia porównywania XML i zobacz, co się zmieniło.