XML Diff: 2つのXMLファイルをオンラインで比較
2つのXMLドキュメントを貼り付け、整形し、並べて比較。Pretty-print、検証、namespace対応のハイライトを内蔵。
XML diffツールとは?
2つのXMLドキュメントを比較するための、ブラウザ内で動作する無料ツールです。古いpom.xmlを左に、新しいものを右に貼り付けると、変更が要素ごとに浮かび上がります。データはマシンの外には出ません。
diff自体は文字レベルで動作し、検証はブラウザのDOMParserを通します。各ペインのFormatボタンはXMLを一貫したインデントで整形し直します。ライブ検証は、不正な記法、閉じていないタグ、壊れたエンティティ参照を入力中に指摘します。
コードレビューで4,000行のSpring application contextを開き、class属性が変わった唯一のbeanを探した経験があるなら、これはそこに数秒で到達するためのツールです。普通の文章なら、テキストdiffツールが適切です。キー/値ペアを持つ構造化データなら、JSON diffのほうがオブジェクトの並び替えをXMLよりきれいに扱えます。
diffの実際の仕組み
diffは文字レベルで動作し、その後の意味的な後処理パスがハイライトをずらして、無作為な記号ではなくタグ名・属性・テキスト内容に乗るようにします。挿入は右ペインに緑、削除は左ペインに赤で表示されます。
XMLにはspecが明記しているクセがいくつかあり、2つのファイルをテキストとして比較するときにすべて引っかかります。XML 1.0仕様は、要素上の属性の順序は意味を持たないと述べているため、パーサにとって<img src="a.png" alt="x"/>と<img alt="x" src="a.png"/>は同じです。テキストdiffはそれでも入れ替えを変更として表示します。構造比較なしには根本的な解決策はありません。実用的な回避策は、両側を同じツールで整形して属性順を安定させることです。
namespaceがもう一段階を加えます。同じURIを異なるプレフィックスにバインドした2つのドキュメントはNamespaces in XML 1.0のもとで等価ですが、テキストdiffはすべてのns1:とns2:の入れ替えを変更として塗ります。両側を整形し、片方のプレフィックスを揃えて、もう一度diffを取り直します。namespaceや属性順をdiff前に正規化するパイプラインなら、XSLT 3.0か、Canonical XMLに従った正準化ステップを検討してください。
3ステップでXMLを比較
テキストペイン2つ、diff1つ。登録もアップロードもサーバーへの往復もありません。
- 1
XMLを貼り付けるかアップロード
古いXMLを左に、新しいXMLを右に貼り付けます。あるいは、どちらかのアップロードから.xml、.pom、.config、.svgファイルを直接読み込みます。サンプルボタンは小さなpom.xml例で両ペインを埋めます。先にツールの動きを見たいときに使ってください。
- 2
公平な比較のため両側を整形
各ペインのFormatを押すと、一貫したインデントと改行で整形されます。これでホワイトスペースが正規化され、Windows CRLFファイル対Unix LFファイルのような書式ノイズではなく、本当の内容変更にdiffが集中できます。DOMParserがドキュメントをwell-formedとして受け入れると、検証バッジが緑になります。
- 3
diffを読む
削除は左に赤いハイライトで、挿入は右に緑のハイライトで表示されます。どちらかをスクロールするともう一方も連動します。各ヘッダーの変更カウントは、要素名・属性値・テキスト内容にわたってdiffが見つけた異なる編集の数を示します。
XML diffが正解になる場面
pom.xmlの依存関係アップグレードを確認
DependabotのPRがMavenの座標を一度に15個跳ね上げます。古いpom.xmlを新しいものと突き合わせて、実際のアップグレードを確認します: spring-boot-starter-webが3.1.5から3.2.1へ、jackson-databindが2.15.3から2.16.0へ、そして新しいmicrometer-registry-prometheus依存が追加されたといった具合に。diffがバージョン跳躍を明確にするので、承認前にchangelogと突き合わせて妥当性を確認できます。
Spring application context XMLのdiff
リファクタ後にサービスが落ち始めるとき、原因はしばしばapplicationContext.xmlの中でclass属性かコンストラクタ引数が変わった単一のbeanです。動いていたリビジョンをHEADと突き合わせて貼り付けると、class="com.acme.OldDataSource"対class="com.acme.HikariDataSource"の入れ替わりが瞬時に表面化し、周囲の<property>タグからどの設定が一緒に移ったかも分かります。
SOAPのリクエスト/レスポンスボディを比較
昨日まで動いていたSOAP統合が今日はFaultを返します。両方のエンベロープをパケットロガーやWireMockの記録から取り、diffに投入すると、問題の要素が浮かび上がります: <currencyCode>がUSDから要素ごと消えていた、あるいは上流サービスがsoap:Envelopeのnamespace宣言をひそかに変えていた、というように。並列ビューなしに800行のXMLからこれを見つけるのは絶望的です。
AndroidManifest.xmlの権限を監査
リリース前に、AndroidManifest.xmlを前回タグとdiffして権限の肥大を捕まえます。サードパーティSDKのアップデートと一緒に紛れ込んだ新しい<uses-permission android:name="android.permission.READ_CONTACTS"/>はまさにPlay Storeの審査で引っかかる類のものです。diffは<intent-filter>要素やandroid:exportedフラグの変更も浮かび上がらせます。これらはセキュリティレビューの典型的な注目点です。
RSSやAtomフィードのスキーマ変更を追う
フィードリーダーがRSS 2.0からAtom 1.0に切り替わって壊れた、あるいはパブリッシャーが新しい<media:thumbnail> namespaceを追加した。動いていたフィードのスナップショットを保存し、稼働中のフィードと差分を取れば、構造的な変更が数秒で見えます。OPMLインポートやpodcastフィードでchannelレベルのメタデータが要素間を移動した場合も同じワークフローです。
OOXMLのdocument.xmlのdiff
.docxはXMLが入ったzipに過ぎません。契約書の両方のバージョンを展開してword/document.xmlでdiffを走らせれば、Wordの変更追跡マークアップに邪魔されることなく実際の文章編集が見えます。パラリーガルがredlineに一致するという「クリーン」コピーを返してきたときに有用です。XMLがどの段落要素が動き、どのrunレベルの書式が変わったかを教えてくれます。
XMLクイックリファレンス
このツールが最も頻繁に表面化させるパース上のエッジケースの短いチートシートです。すべてW3CのXML仕様に基づいています。
| Topic | What this tool does |
|---|
| 属性の順序 | XML 1.0仕様により意味を持ちません。<a x="1" y="2"/>はパーサにとって<a y="2" x="1"/>と等しいです。テキストdiffは入れ替えを印を付けるので、両側を整形して順序を安定させてください。 |
|---|
| Namespace | URIにバインドされ、プレフィックスでエイリアスされます。同じURIを異なるプレフィックスにバインドした2つのドキュメントは等価です。Namespaces in XML 1.0を参照。 |
|---|
| CDATAセクション | <![CDATA[ ... ]]>で囲まれたリテラルテキスト。パーサは中のタグやエンティティを解釈しません。]]>シーケンスはCDATAブロック内に出現できません。 |
|---|
| 混合内容 | 要素はテキスト、子要素、ホワイトスペースを任意の順で含むことができます。<p>こんにちは <b>世界</b>!</p>は混合内容で、そこのホワイトスペースは意味を持ちます。 |
|---|
| コメント | <!-- コメント -->。内部に--を含むことはできません。多くのプロセッサで除去されますが、このdiffではテキストとして保持されます。 |
|---|
| エンコーディングとBOM | <?xml version="1.0" encoding="UTF-8"?>で宣言されます。UTF-8 BOMは隠れた最初の文字で、行1で1文字分のファントムdiffの定番原因です。 |
|---|
| XML 1.0対1.1 | ほぼ全員がXML 1.0を使います。バージョン1.1は要素内容で追加のUnicode制御文字をサポートしますが、実務ではほとんど見かけません。 |
|---|
| エンティティ参照 | 5つの組み込み: & < > ' "。アクセント付き文字に対するéのような数値文字参照も有効です。自己終了の<br/>と明示的な<br></br>は等価です。 |
|---|
XML diff: よくある質問
XML属性の並び替えやホワイトスペースはdiffに出ますか?
はい、どちらも出ます。テキストdiffは文字を行ごとに比較するので、再フォーマット、属性の並び替え、要素内のホワイトスペースは、ドキュメントが論理的に同一でもすべて差分として現れます。両ペインで先にFormatをクリックすれば、diffは本当の内容変更に集中します。深くネストした子要素を持つ要素ツリーには、XSLTやCanonical XMLによる構造比較が次のステップですが、このツールはその複雑さなしで実用的なXMLレビューの95%をカバーします。
要素の属性の順序は重要ですか?
いいえ、XMLパーサにとっては重要ではありません。XML 1.0仕様は属性の順序は意味を持たないと述べているので、<img src="a.png" alt="x"/>と<img alt="x" src="a.png"/>は同じ要素を表します。文字diffは生のテキストを見るので並び替えを変更として印を付けます。対処法は、diff前に同じツールで両側を整形して属性順を一貫させること、あるいはパイプラインを管理しているならCanonical XMLの正規化を適用することです。
XMLのnamespaceはdiffにどう影響しますか?
namespaceはURIベースですが、ドキュメント内では短いプレフィックスにバインドします。http://maven.apache.org/POM/4.0.0を異なるプレフィックスにバインドした2つのファイルはNamespaces in XML仕様のもとで等価ですが、テキストdiffはあらゆるプレフィックス入れ替えを変更として印を付けます。実用的な対処法は、両ファイルを整形し、両側で一致するプレフィックスを使うことです。自動化されたパイプラインでは、Canonical XMLのパスでこの差を消せます。
CDATAセクションを含むXMLファイルをdiffできますか?
はい。CDATAセクションは、パーサに解釈しないよう指示したテキスト内容に過ぎないので、<![CDATA[<b>raw</b>]]>は中の文字どおりに比較されます。長いCDATAブロック(scriptタグ、埋め込みHTML、SQL)もきれいにdiffできます。唯一の注意点は、CDATAセクション内に]]>を持てないことです。データにそのシーケンスが含まれる場合、ソースは2つのCDATAブロックに分割しなければならず、diffは書かれたとおりに表示します。
diffの代わりにXSLTを使うべきですか?
XSLTは、XMLを変換したり、比較前に正規化したい(子要素をソート、コメントを削除、namespaceを正準化する)ときに使います。このdiffは、特定の2ファイル間で何が変わったかを見たいときに使います。両者は補完的です: XSLTの前段パスとこのdiffの組み合わせは、ノイズの多いマシン生成XMLに対して強力なワークフローです。ほとんどのコードレビュー用途(pom.xml、AndroidManifest、application context)では、diff単体で十分です。
エンコーディング宣言やBOMは比較に影響しますか?
少し影響します。<?xml version="1.0" encoding="UTF-8"?>宣言はドキュメントテキストの一部なので、UTF-8をUTF-16に変えれば1行のdiffとして現れます。ファイル先頭のUTF-8 byte order mark(BOM)は、エディタによって取り除かれたり保持されたりする1つの隠し文字で、ファントムdiffの定番の原因です。2つのファイルが同じに見えるのにdiffが行1の文字0で変更を示すなら、BOMを疑い、既知のエンコーディング設定で保存し直してみてください。
プライバシーと仕組み
あなたのXMLはブラウザから出ません。パーサ、フォーマッタ、diffはすべてあなたのマシン上、ローカルで動作します。入力に対する解析、ログ、「親切な」クラウドへの往復はありません。パースと検証はブラウザのDOMParserを使い、準拠しているのはW3C XML 1.0です。フォーマット自体の背景はWikipediaにあります。