İki XML dosyası nasıl karşılaştırılır ve değişiklik görülür

İki XML dosyasını karşılaştırmanın en hızlı yolu, ikisini de yan yana bir diff aracına yapıştırmak, aynı şekilde biçimlendirmek ve vurguladığı satırları okumaktır. Karşılaştırmanın kendisi kolay kısımdır. İnsanları yanıltan şey gürültüdür: yeniden sıralanmış öznitelikler, etiketler arasındaki boşluklar ve namespace önekleri, aynı anlama gelen iki dosyanın hiç ortak yanı yokmuş gibi görünmesine neden olabilir.

Bu rehber temiz, güvenilir bir XML diff'in nasıl elde edileceğini anlatıyor. Eşdeğer iki belgenin kâğıt üzerinde neden ayrıştığına, bilmeye değer yöntemlere ve takip edebileceğiniz işlenmiş bir örneğe bakacağız. Yalnızca aracı istiyorsanız, XML karşılaştırma sayfamız bunların hepsini tarayıcıda yapar.

XML dosyaları neden aldatıcı şekilde karşılaştırması zordur

XML'in katı bir grameri vardır (W3C XML spesifikasyonuna bakın), ancak yazarlara metni nasıl yerleştireceklerine dair büyük bir serbestlik tanır. İki belge tam olarak aynı verileri tanımlayabilir ve yine de bayt bayt farklı olabilir. Düz metin diff'i bunların hiçbirini anlamaz, bu yüzden hepsini işaretler.

Akılda tutulması gereken kilit gerçek şu: XML'de bir öğenin özniteliklerinin sırası anlamlı değildir. XML Information Set öznitelikleri sırasız bir küme olarak ele alır. Yani <user id="7" role="admin"/> ve <user role="admin" id="7"/> aynı bilgiyi taşır; bir satır diff'i onları kırmızı ve yeşile boyasa bile. Öğe sırası ise genellikle önemlidir.

Değişiklik gibi görünür, ama genellikle değildir
Diff'te gördüğünüzGerçek bir değişiklik mi?Ne yapmalı
Farklı sırada özniteliklerHayır, öznitelik sırası anlamlı değildirHer iki tarafı canonicalize edin
2 boşluk vs 4 boşluk girintiHayırHer iki tarafı aynı şekilde biçimlendirin
Öğeler arasındaki boşlukGenellikle hayırBiçimlendirin veya önemsiz boşluğu çıkarın
<br/> vs <br></br>Hayır, aynı boş öğeHer iki tarafı canonicalize edin
Aynı URI için farklı bir namespace önekiHayır, önekler keyfi etiketlerdirÖnek yerine namespace URI'sine göre karşılaştırın
Farklı sırada alt öğelerGenellikle evet, öğe sırası önemlidirİnceleyin, bu büyük olasılıkla gerçek

O son satır, dikkat edilmesi gerekendir. Öznitelik sırası serbesttir, ancak alt öğelerin sırası çoğu şemada belgenin bir parçasıdır. Bir parser'ın tüm bunları nasıl gördüğüne dair ayrıntıyı istiyorsanız, MDN'de DOMParser ile XML ayrıştırma konusunda sağlam bir kaynak var.

XML karşılaştırmanın dört yolu ve her birine ne zaman başvurulur

Tek bir en iyi yöntem yoktur. Dosyaların nerede olduğuna ve ne öğrenmeye çalıştığınıza bağlıdır. Yaygın seçeneklerin nasıl kıyaslandığı şöyle.

YöntemEn uygunÇabaXML'i anlar mı?
Gözle incelemeMinik dosyalar, bir iki öğeDüşükHayır, parser sizsiniz
Çevrimiçi diff aracıHızlı kontroller, her yerden yapıştırmaDüşükBiçimlendirmeyle evet
Komut satırı (xmllint)Diskteki dosyalar, betikleme, kanonik biçimOrtaEvet, --c14n ile
IDE veya git diffZaten bir depoda olan dosyalarCommit edilmişse düşükVarsayılan olarak satır tabanlı

Çoğu kişi için bir tarayıcı aracı hızda kazanır: kurulacak bir şey yok ve bir yapılandırma dosyasından ya da bir SOAP yanıtından doğrudan bir parça yapıştırabilirsiniz. İşin püf noktası, sıradaki konumuz olan biçimlendirme gürültüsüdür. Terminalde yaşıyorsanız, libxml2'nin xmllint aracı bilinmesi gereken araçtır.

En hızlı temiz karşılaştırma, adım adım

Birisi bana iki yapılandırma dosyası verip "ne farklı?" diye sorduğunda kullandığım rutin bu. Yaklaşık on beş saniye sürer.

  1. XML karşılaştırma aracını açın.
  2. Orijinali sola, yeni sürümü sağa yapıştırın.
  3. Her iki tarafta Biçimlendir'e tıklayın, böylece aynı girintiyi paylaşsınlar.
  4. Gerçek farkları arayın. Yeşil eklenen, kırmızı çıkarılandır; değişen bir değer her renkten biri olarak görünür.
  5. Yalnızca öznitelik yeniden sıralaması veya boşluk olan satırları göz ardı edin.

Üçüncü adım, işin büyük kısmıdır. Her iki belge aynı girintiyi kullandığında, vurgulanacak tek şey gerçekten değişen şeydir. Diff motorumuz Google'ın diff-match-patch üzerine kurulmuştur; uzun dosyalarda bile hızlı kalmak için önce satır satır karşılaştırır.

İşlenmiş bir örnek

Bir servis yapılandırmasındaki bir değişikliği incelediğinizi varsayın. İşte öncesi:

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

Ve işte bir takım arkadaşının size verdiği haliyle sonrası:

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

Bunları ham bir satır diff'ine atın ve ilk satır değişmiş gibi görünür, çünkü id ve role yer değiştirdi. İkisini de biçimlendirin, anlama göre karşılaştırın, ve gerçek hikâye kısa:

Gerçekte değişen şey
DüğümÖnceSonraDeğişiklik
@roleeditoradminDeğiştirildi
seats35Değiştirildi
teamplatformEklendi
@id77Değişiklik yok (yalnızca taşındı)
nameAda LovelaceAda LovelaceDeğişiklik yok

Üç gerçek düzenleme: bir rol yükseltmesi, bir koltuk sayısı ve yeni bir team öğesi. Öznitelik takası gürültüydü. editor'dan admin'e bu terfi, incelemede yakalamak istediğiniz türden bir şeydir ve diff'in yanlışlıkla işaretlediği bir satırın altına gömülünce gözden kaçırması kolaydır.

Kanonik XML: gürültüyü göz ardı etmenin doğru yolu

Her iki tarafı biçimlendirmek girintiyi halleder, ancak tam da bu sorun için tasarlanmış bir standart var. W3C tarafından Canonical XML 1.1'de tanımlanan kanonik XML, bir belgeyi tek bir normalize edilmiş biçime yeniden yazar: öznitelikler sıralanır, boş öğeler genişletilir, etiketlerdeki boşluk normalize edilir ve varsayılan öznitelikler açık hale getirilir. Eşdeğer iki belge aynı kanonik çıktıyı üretir. Bu, JSON anahtarlarını sıralamanın XML karşılığıdır.

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

Artık diff yalnızca gerçekten değişen içeriği bildirir, çünkü her iki dosya da aynı şekilde normalize edilmiştir. Katı kanonik biçim yerine yalnızca okunabilir bir girinti istiyorsanız, xmllint --format file.xml onu biçimli yazdırır; bu, tarayıcıda Biçimlendir'e tıklamanın terminal karşılığıdır.

Namespace'ler: herkesin kafasını karıştıran kısım

XML namespace'leri, iki belgenin farklı önek etiketleriyle aynı sözcük dağarcığını kullanmasına izin verir. Bir URI'ye bağlı <ns1:user> ile aynı URI'ye bağlı <u:user> aynı öğedir; önek yalnızca yerel bir takma addır. Bir metin diff'i ns1'e karşı u'yu görür ve olmayan bir değişikliği işaretler. Çözüm, önek yerine namespace URI'sine göre karşılaştırmaktır ki bu tam olarak canonicalization'ın yaptığı şeydir. Bu konuda bir tartışmayı sonlandırmanız gerekirse, Namespaces in XML spesifikasyonu başvuru kaynağıdır.

Dikkat edilmesi gereken yaygın tuzaklar

TuzakNeden ısırırÇözüm
Karakter kodlamasıBir UTF-8 ve bir UTF-16 dosyası aynı metni tutabilir ama bayt bayt farklı olabilirKodlamayı normalize edin; XML bildirimi bunu belirtir
Varlık referansları&amp; ve düz bir & aynı karakter için ikisi de görünebilirCanonicalize edin, bu varlıkları tutarlı şekilde çözer
CDATA vs kaçışlı metin<![CDATA[a<b]]> ve a&lt;b aynı metin içeriğidirHam baytları değil, ayrıştırılmış değeri karşılaştırın
Anlamlı boşlukxml:space="preserve" içinde boşluklar önemlidir ve çıkarılmamalıdırKörü körüne kırpmayın; xml:space'e saygı gösterin
Kendiliğinden kapanan etiketler<x/> ve <x></x> aynıdırİkisinin de aynı şekilde işlenmesi için canonicalize edin

Metin diff vs yapısal diff

Yukarıdakilerin hepsi bir metin diff'idir: hızlı, görsel ve bir değişikliği okuyan bir kişi için mükemmel. Bir yapısal diff daha ileri gider ve değişikliği XML ağacı açısından tanımlar: bu öznitelik değişti, şu alt öğe bu yola eklendi. Bir programın değişikliği uygulaması gerektiğinde ya da öğe sırası gerçekten önemli olmadığında ve göz ardı edilmesini istediğinizde yapısal bir diff istersiniz. Günlük inceleme için, biçimlendirilmiş iki belgenin metin diff'i fazlasıyla yeterlidir.

İlgili araçlar

XML nadiren uğraştığınız tek formattır. API yüklerini karşılaştırıyorsanız, JSON karşılaştırma aynı fikri JSON'a uygular. İşaretli sayfalar HTML karşılaştırma sayfasında daha kolay okunur ve ortam ayarları config karşılaştırma aracında güzel hizalanır.

Sıkça sorulan sorular

XML dosyalarını çevrimiçi karşılaştırmak onları bir yere yükler mi?
comparetext.org'da diff tarayıcınızda çalışır. İki XML dosyası kendi makinenizdeki JavaScript tarafından karşılaştırılır, bu yüzden açıkça Kaydet veya Paylaş'a tıklamadığınız sürece sunucuya hiçbir şey gönderilmez. Bu da onu yapılandırma dosyaları, SOAP mesajları ve her tuş vuruşunda yükleme yapan bir siteye yapıştırmak istemeyeceğiniz diğer veriler için güvenli kılar.
İki XML dosyam neden her satırı farklı gösteriyor?
Neredeyse her zaman biçimlendirmedir, gerçek değişiklikler değil. Bir dosya minified veya sekmeyle girintilenmiş, diğeri iki boşlukla, ya da öznitelikler farklı bir sırada. Her iki tarafta Biçimlendir'e tıklayın ki aynı girintiyi kullansınlar. Bundan sonra diff genellikle gerçekten değişen birkaç değere iner. Daha katı bir normalizasyon için önce her iki dosyayı xmllint --c14n ile canonicalize edin.
XML karşılaştırırken öznitelik sırası önemli mi?
Hayır. XML'de bir öğenin öznitelikleri sırasız bir kümedir, bu yüzden <a x="1" y="2"/> ve <a y="2" x="1"/> eşdeğerdir. Düz metin diff'i bunu bilmez ve yeniden sıralamayı bir değişiklik olarak işaretler. Kanonik XML öznitelikleri sabit bir sıraya dizer, bu yüzden karşılaştırmadan önce her iki tarafı canonicalize etmek yanlış pozitifi ortadan kaldırır. Öğe sırası ise genellikle anlamlıdır.
Namespace öneklerini göz ardı ederek XML'i nasıl karşılaştırırım?
Namespace önekleri bir namespace URI'sinin yerel etiketleridir, bu yüzden aynı URI'ye bağlı ns1:user ve u:user aynı öğedir. Doğru karşılaştırmak için önek yerine URI'ye göre normalize edin. En basit yol her iki belgeyi xmllint --c14n ile canonicalize etmektir; bu, namespace bağlamalarını tutarlı şekilde yeniden yazar, ardından sonuçları diff'lersiniz. Ham bir metin diff'i bunu tek başına yapamaz.
Sayfa donmadan büyük XML dosyalarını karşılaştırabilir miyim?
Evet, bir noktaya kadar. Satır modlu bir diff, her karakter yerine önce tam satırları karşılaştırdığı için binlerce satırlık dosyalarda hızlı kalır. Çok büyük dosyalar (birkaç megabayt) verileri akıtan xmllint veya git diff gibi bir komut satırı aracıyla daha iyi işlenir. Bir tarayıcıda rahatça kaydırabileceğiniz her şey için çevrimiçi bir diff daha hızlı seçenektir.
XML'in metin diff'i ile yapısal diff'i arasındaki fark nedir?
Bir metin diff'i dosyaları satır satır karşılaştırır, tıpkı iki kompozisyonu karşılaştırdığı gibi. Bir yapısal diff XML ağacını anlar, bu yüzden yeniden sıralanmış bir özniteliğin bir değişiklik olmadığını bilir ve eklenen bir öğeyi yoluyla bildirebilir. Metin diff'leri daha hızlıdır ve her iki taraf biçimlendirildikten sonra çoğu inceleme için yeterlidir. Yapısal diff'ler, bir programın değişikliği uygulaması gerektiğinde veya öğe sırasını göz ardı etmek istediğinizde önemlidir.

Denemeye hazır mısınız? Dosyalarınızı XML karşılaştırma aracına yapıştırın ve neyin değiştiğini görün.