最近、XML文書が同じか否かを判別する方法に関して簡単な質問を受け、回答にとりかかりました。しかし、この回答は、そう簡単なものではありません。というのは、回答しようとすると、セマンティック同等性というあいまいな領域に足を踏み入れるからです。
XMLには柔軟性が備わっているため (だから「extensible (拡張可能)」のX です。覚えていますか?)、同じデータを多くの方法で表すことができます。したがって、2つの文書が同じことを「意味している」かどうか、つまり、それらの文書がセマンティック上 同等であるかどうかを判別するような場合は、話がややこしくなります。この記事では、この問題を掘り下げ、比較を行うためのテクニックをいくつか紹介し、XML同等性についての一般的な概要を示します。では、シート・ベルトを締めてください。多少がたがた揺れますよ!
ここで、問題を手短に説明します。2つ (またはそれより多くの) XML文書があり、それらが同じであるかどうかを知りたい場合を想定します。あなたはどうしますか?
XMLの比較でだれでも最初に試みることは、diff (ほとんどすべての *NIXシステムで使用可能) のような標準のユーティリティーを使用することです。そこであなたは2つの文書を取り出し、それらをdiff処理します。めったに起こらないことですが、もしもこのユーティリティーが違いを報告しなかった場合 (通常、このことは、コマンド行またはシェル・プロンプトに何も エコー・バックされないことを意味します) は、それらの文書は同じです。それに対して、こちらのほうがよく起こることですが、XML文書のペアをdiff処理すると、数行 (または数十行、または数百 行) の応答が出されます。「異なる」内容の行が無数にあるような文書では、これで答えが出ました。それらの文書は同じではないのです。そうですね? ウーム ... あなたには、まだ事の本筋が見えていないようです。これらのdiff応答行こそが、実際にあなたをXML比較の最初のステップへ導くのです。
なぜdiffのようなツールが不十分であるか知りたい場合は、簡単なXML文書を示すリスト1を見てください。
リスト1. document1.xml
<?xml version="1.0"?>
<!DOCTYPE hockeyTeam SYSTEM "hockeyTeam.DTD">
<hockeyTeam>
<city>Dallas</city>
<state>Texas</state>
<mascot>Stars</mascot>
<arena name="Reunion Arena">
<ice quality="poor" />
<location city="Dallas" />
</arena>
<conference>Western</conference>
<division>Pacific</division>
<nhlCopyright>&NHLCopyright;</nhlCopyright>
</hockeyTeam>
|
シンプルなリストです。では、リスト2のdocument2.xmlを見てください。
リスト2. document2.xml
<?xml version="1.0"?>
<!DOCTYPE hockeyTeam SYSTEM "hockeyTeam.DTD">
<hockeyTeam>
<city>Dallas</city><state>Texas</state><mascot>Stars</mascot>
<arena name="Reunion Arena">
<ice quality="poor" />
<location city="Dallas" />
</arena>
<conference>Western</conference><division>Pacific</division>
<nhlCopyright>&NHLCopyright;</nhlCopyright>
</hockeyTeam>
|
この2つの短いXML文書をよく見ると、リスト1とリスト2の間に意味の違いがほとんどないことが分かるでしょう。それぞれのエレメントのテキスト・データはすべて同じです。SAX、DOM、またはJDOMを使って両方の文書を1つのアプリケーションに読み込んだ場合は、その結果まったく同じデータを入手することになります。では、これらのシンプルなほとんど同等な文書に
diff
コマンドを使用すると、なぜ膨大な数の応答が出るのでしょうか?
それは、2つのXML文書間には、重要な違いもあれば重要でない違いもあり、またdiffユーティリティーだけでは重要な違いを識別するのに十分でないからです。
この問題が、日頃使用しないdiffユーティリティーを持ち出せば済むというレベルの問題ではないのが分かったならば、この記事の残りの部分を最大活用するために、関連知識を集めて整理する必要があるかもしれません。
まず、リスト1、2、3のXML文書をローカルに保管して、それらを構文解析することができます。Javaコンパイラー、XMLパーサー、それに場合によってはXMLエディターも役に立ちます。
ところでわたしは、あなたがXMLについてのいくらかの基礎知識を持っているものと想定しています。エレメントやDTD、エンティティー参照などについては、この記事であまり状況設定を行わないで説明しています。(あなたがXMLについてまったく知識がない場合は、 参考文献 に示されている背景資料や紹介資料を調べてください。) XMLの予備知識があれば、空白文字やエンティティー解決の問題を理解するのに役立ちます。XMLの基本知識のほかに、少なくとも、この記事で提案することを利用するためにSAX概念に関する理解が必要になります。ここでは、SAXの達人である必要はまったくありませんが、たとえば、「SAX Javadoc」( 参考文献 を参照) を熟読しておけば大いに役立つでしょう。また他の2つのXML API、つまりDOMとJDOMにも少し触れておきたいと思います。みなさんはこれらのAPIに精通している必要はありません。しかし、繰り返しになりますが、多少でも一般的な知識があれば理解を容易にします。
まず、すべての準備を済ませましょう。次にまた戻ってきて、あなたがXML文書の比較について知る必要があると考えている以上の内容を学ぶ準備をしましょう。
では、重要なことから始めましょう。XML文書を比較する際の最も大きな問題は、空白文字の処理です。なぜならば、実際の空白文字はまったく変更していないのに、その空白文字の意味 が変わる可能性があるからです。わけが分からなくなりましたか。わたしもそれを始めて聞いたときわけが分からなくなりましたが、これからそれを分かりやすく説明します。まず、無視可能な空白文字とはどういうものなのか、また空白文字があなたの人生 ... ええと ... そう、あなたのXMLをどのように変えることができるかについて説明します。その後で、どのようにしてDTDが文書の意味を変えることができるのか、またあなたは、それをXML文書の比較にどのように使用できるのかについて説明します。
無視可能な空白文字は、実際に無視することができる空白文字です。言い換えると、無視可能な空白文字とは、ある文書から削除しても、その文書の意味を変えない空白文字です。たとえば、XML文書の中の
arena
エレメントが、2つの子
ice
および
location
を持っています。リスト3は、document1.xmlの一部を抜粋したものです。
リスト3. リスト1の抜粋部分における空白文字の検出
<arena name="Reunion Arena">
<ice quality="poor" />
<location city="Dallas" />
</arena>
|
しかし問題は、
arena
開始タグと
ice
エレメントの開始タグの間の空白文字をどうするかということです。そこには改行が1つあり、いくつかの末尾スペースがある可能性があります。したがって、
arena
の右大括弧と
ice
の左大括弧の間の実際の内容は "
\n
" になります。同じような空白文字が後続の2行の末尾にも現れています。
すべてを完全に明らかにするために、リスト3Aでは、arena定義の中で空白文字が入っている場所を下線で示してあります (もちろん、実際に改行に下線を引くことはできませんが、これらの行の間の見分けは付くと思います)。
リスト3A. 空白文字 (このリストでは、強調のために下線で示されています)
<arena name="Reunion Arena">_
__<ice quality="poor" />_
__<location city="Dallas" />__
</arena>
|
では、エレメントが同じで、空白文字が異なる
リスト2
をバックアップするdocument2.xmlに類似した文書を考えてみます。この文書の場合、空白文字は、
arena
の開始タグと
ice
の開始タグを分離する"
\n
" ではなく、"\n"
です。この一見なんでもないような違いが、XML比較に大混乱をもたらすことがあります。それは違いではありますが、重要な違いでしょうか。この質問に対する答えは、残念ながら、多分
です。
XMLにDTDが指定されていない場合は、空白文字は無視できません。繰り返します。DTDが指定されていない場合は、空白文字は無視できません。つまり、パーサーは2つの文書を異なるものと考えるのです。それは、XMLパーサーには、そのテキスト・データ (つまり、その厄介な空白文字) が重要な意味を持つものであるかどうかの判別がつかないのです。あなたには、それが単なる無意味な空白文字のように見えます。しかし、文書の作成者にとっては、それはXSL変換で使用するために意図的に入れたフォーマット設定であるのかもしれません。それはちょっと信じがたいように思われるかもしれませんが、リスト4のXMLサンプルはどうでしょうか?
リスト4. フォーマット設定に使用される空白文字
<signature>
---
Brett McLaughlin
Enhydra Strategist
http://www.enhydra.org
</signature>
|
リスト4では、明らかに空白文字は文書の一部であると見なされ、しかもこの文書の作成者にとって、それは意味のあるものです。こういう理由から、DTDがなければ、パーサーが空白文字の意味を想定することは安全ではありません。したがって、2つのXML文書を比較する際に、最初に行うべきステップは、両者に対してDTDを明確化することです。そうすることにより、パーサーはどの空白文字が無視できるものであり、どれが意味を持つものであるかを指定できるようになります。
これで、DTDが必要であることが分かりました。しかし、もっと面倒なことに、どのDTDでもよいというわけではありません。たとえば、リスト5に示されているDTDはまったく役に立ちません。
リスト5. 任意の内容を使用できるDTD
<!ELEMENT hockeyTeam ANY> |
簡単なリスト5は (明らかに) DTDですが、これはルート・エレメント
hockeyTeam
内の任意の 内容を使用することができます。このDTDはあまり役立ちません。なぜならば、
hockeyTeam
の子に含まれる任意の内容に、意味のある空白文字が含まれている可能性がまだあるからです。次に、DTDが指定しなければならない内容は、ある特定のエレメントには、他のエレメントのみをその中に含めることができるということです。このことは、実際上は、「許可された唯一の内容は他のエレメントであるから、どの空白文字も無視できる」と言っています。したがって、リスト6のDTDは、
arena
エレメント内の空白文字が無視可能であることを明示しています。
リスト6. document1.xmlおよびdocument2.xmlのDTD
<!ELEMENT hockeyTeam (city, state, mascot,
logo, arena, conference)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT mascot (#PCDATA)>
<!ELEMENT arena (ice, location)>
<!ATTLIST arena
name CDATA #REQUIRED>
<!ELEMENT ice EMPTY>
<!ATTLIST ice
quality CDATA #REQUIRED>
<!ELEMENT location EMPTY>
<!ATTLIST location
city CDATA #REQUIRED>
<!ELEMENT conference (#PCDATA)>
<!ELEMENT division (#PCDATA)>
<!ELEMENT nhlCopyright ANY>
<!ENTITY NHLCopyright SYSTEM "http://www.nhl.com/nhlCopyright.xml">
|
XMLを比較する場合、比較する文書をできる限り厳密に規定するためのDTDを定式化することができます。特に、あるエレメントに他のエレメントのみを含めることができる場合は、それをDTD内に確実に示してください。この厳密性により、SAX、DOM、JDOMなどのAPIで作業する場合に、文書内の任意の空白文字が間違いなく無視されることになります。SAXの場合、エレメント内の空白文字は
characters()
コールバック (テキスト・エレメント・コンテンツを報告するために使用するメソッド)
には報告されません。その代わり、そのエレメント内の任意の空白文字が
ignorableWhitespace()
コールバック に報告されます
(通常、ユーザーはこれについて考える必要はありません)。もちろん、これは歓迎すべきことです。
これで、2つの「同様な」XML文書を比較する際に実行する最初のステップが「DTDの定義」であることが分かりました。次に、両方のXML文書にDTDを参照させると、可能なかぎり多くの空白文字を分離することができます。しかし、この時点で (たとえば、SAX、DOM、またはJDOMで文書を読み取るとき) まだ空白文字に違いがあれば、2つのXML文書は同じ文書ではありません。DTDを使用しないで、他のどの空白文字も重要でないと宣言することはできません。したがって、それを使用した後で、空白文字に違いがあった場合は、2つの文書は同じ文書ではありません。
空白文字の最初の問題を解決し、その観点から文書が同じであると分かったら、外部エンティティーの解決を処理しなければなりません。もう1度、
リスト1
のdocument1.xmlと
リスト2
のdocument2.xmlを見てください。これらはどちらも、外部エンティティー参照
NHLCopyright
を持っています。このエンティティー参照がDTD参照を介して解決され、また実行時には、このエンティティー参照がテキスト内容、より多くのXML内容、またはその他任意の内容に変換されるということを思い出してください。これによりまた問題が発生します。なぜならば、文書を比較するときに、ユーザーがこれらのエンティティーの解決が同じであってほしいと思うからです。この2つの文書は異なるDTDを使用できるため、同じエンティティー参照が異なった方法で解決されることがあります。このセクションでは、このような問題をどのようにして解決するかについて説明します。
明確なソリューションは、両方の文書が同じDTDを参照するようにすることです。しかし、これはいつも可能であるとは限りません。たとえば、ユーザーはXML文書に読み取り専用アクセスをするかもしれないし、プログラムを用いて文書の比較を行うかもしれません
(この場合、
DOCTYPE
参照の変更は、現行APIでは標準化されていません)。これらの場合は、解決を「ショートさせる」方法が必要になります。言い換えれば、エンティティー参照を、DTD内の値ではなく、ユーザーが決めた値に解決させるようにすることです。
これを行うためには、SAX
EntityResolver
インプリメンテーションを使用することができます。このインターフェースは
org.xml.sax.EntityResolver
に定義されていて、単一のメソッド
resolveEntity()
を提供します。このメソッドを使用すれば、ユーザーは独自のエンティティー解決を入手することができるため、パーサーはこのタスクにDTDを使用する必要がありません。したがって、2つの文書の場合は、同じエンティティー解決を行う
EntityResolver
インプリメンテーションを登録することができます。こうすることで、ユーザーが望むとおりに、もう1つの比較を式から除去することができます。リスト7は、常に
NHLCopyright
の同じ値を戻すサンプル・インプリメンテーション、つまり、両方のXML文書サンプルのエンティティー参照を示しています。そのエンティティーのシステムおよびパブリックIDのDTDに指定された値を調べれば、すべての文書で同じ値が戻されていることが確認できます。
リスト7. すべてのNHLCopyrightエンティティーの解決
package com.developerWorks.xml.util;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
public class CommonResolver implements EntityResolver {
public void resolveEntity(String publicID, String systemID)
throws SAXException {
// Look for the NHLCopyright system ID
if (systemID.equals("http://www.nhl.com/nhlCopyright.xml")) {
return new InputSource("myLocalCopyright.xml");
}
// In all other cases, return null
return null;
}
}
|
2つのDTDが同じ外部エンティティー参照について異なるパブリックおよびシステムIDを指定できる、ということも考慮する必要があります。たとえば、リスト8は、リスト6と非常に類似しているDTDではあるが、
NHLCopyright
参照について異なるシステムIDが指定されていることを示しています。
リスト8. 外部エンティティー参照について異なるIDが指定されているdocument1.xmlおよびdocument2.xmlのDTD
<!ELEMENT hockeyTeam (city, state, mascot,
logo, arena, conference)>
<!ELEMENT city (#PCDATA)>
<!ELEMENT state (#PCDATA)>
<!ELEMENT mascot (#PCDATA)>
<!ELEMENT arena (ice, location)>
<!ATTLIST arena
name CDATA #REQUIRED
>
<!ELEMENT ice EMPTY>
<!ATTLIST ice
quality CDATA #REQUIRED
>
<!ELEMENT location EMPTY>
<!ATTLIST location
city CDATA #REQUIRED
>
<!ELEMENT conference (#PCDATA)>
<!ELEMENT division (#PCDATA)>
<!ELEMENT nhlCopyright ANY>
<!ENTITY NHLCopyright SYSTEM "http://www.dallasStars.com/nhl/copyright.xml">
|
リスト8の場合、外部エンティティー参照のURLは異なっており、これは当然、問題を起こす可能性があります。DTDの残りの部分はリスト6と同じ
(このため、同じ空白文字が比較される)
ですが、外部エンティティー参照は異なった方法で解決され、2つの文書は異なって示されることがあります。これを回避するには、この新規システムIDをリスト7の
CommonResolver
クラスに追加します。
resolveEntity()
メソッドを変更する
リスト9
は、2つのエンティティー間の違いを実質的に式から除去しますので、再度有効な比較が可能になります。
この時点で、XML 1.0で発生する可能性のあるすべての空白文字の問題が取り除かれ、エンティティー解決も分離されました。これで大部分の文書を比較でき、それらの文書が同等であるかどうかを判別できるようになりました。しかし、結論を出す前に、もう1つ検討しておきたい概念上の問題があります。
これまで、技術上の細部について詳しく説明してきました。たとえば、パーサーによるデータ解釈の方法、テキストに空白文字や改行が含まれているときのその処理方法、解決対象のエンティティー、などがそれです。しかし、もう1つのセマンティクスの層がXMLに作用します。それは主として哲学に関する検討になりますが、XMLの比較に非技術的な問題が絡んでいるということを指摘する価値はあります。
この種の違いの最適な例は、従来から行われている属性とエレメントについての討論です。言い換えれば、データはエレメントとして文書に保管されるのですか、それとも属性として保管されるのですか、ということです。2つの文書で、同じデータが異なって保管されている場合は、それらの文書は同じなのでしょうか、あるいは異なっているのでしょうか。リスト10を見てください。
リスト10. エレメントではなく、属性を使用するXML文書
<?xml version="1.0"?>
<!DOCTYPE hockeyTeam SYSTEM "hockeyTeam.DTD">
<hockeyTeam>
<city value="Dallas"/>
<state value="Texas"/>
<mascot value="Stars"/>
<arena name="Reunion Arena">
<ice quality="poor" />
<location city="Dallas" />
</arena>
<conference value="Western"/>
<division value="Pacific"/>
<nhlCopyright>&NHLCopyright;</nhlCopyright>
</hockeyTeam>
|
これは リスト1 と同じデータ ですが、このデータは、主として、エレメントとしてではなく、属性として表されます。2つの文書は技術的に同じでしょうか。いいえ、まったく違います。しかし、あなたは両方のデータが同じであるというかなり納得のいく論拠を示すことができます。それが真実であれば、多分、それらの文書それ自体が同じ意味を持っていると論じることができるでしょう。
さて、読者を混乱させないために申しあげますが、この記事では純粋に理論上の検討をしました。また、これらの2つの文書を同等であると解釈するAPIは存在しないのです。ユーザーは両方の文書を処理するためのコードを作成するかどうかを自分で決めなければなりませんが、これらの違いが存在していることと、さらにいつかはそれらを自分で処理しなければならないということを認識する必要があります。
ここでユーザーは、2つのXML文書が「同じ」であるということがどういう意味であるかを明確に理解する必要があります。diffのような単純なプログラムが、単独ではXML文書の比較を行うのには十分でないことはすでにご存知です。ここに示したコードの一部を使用してXML文書内の比較点を分離することで、XML比較をやりやすくしてください。
では楽しんでください。いつものように、オンラインでお会いしましょう!
-
W3Cの
XML Activity Page
に示されている各種のXML仕様の状況をしらべてください。
-
さらに詳しいバックグラウンド情報については、完全な
XML Specification
をお読みください。
-
XML Diff and Merge
を調べてください。このソフトウェアは、alphaWorksから無償でダウンロードできるテクノロジーで、90日間の試用ライセンスがあります。
-
オンライン文書
を読んで、IBM WebSphere Application
Serverにおける構文解析の機能を調べてください。
-
IBM WebSphere Application Serverアドバンスト版での
XML開発に対するサポート
を調べてください。
Brett McLaughlin (brett@newInstance.com) は、Lutris TechnologiesでEnhydraストラテジストを務めており、分散システム・アーキテクチャーを専門としています。また、Java and XML (O'Reilly社) の著者でもあります。Javaサーブレット、Enterprise JavaBeansテクノロジー、XMLおよびビジネス・ツー・ビジネス・アプリケーションなどの技術にかかわっています。氏は、Jason Hunter氏と一緒に、JDOMプロジェクトを設立しました。このプロジェクトは、JavaアプリケーションからXMLを扱う、簡潔なAPIを提供しています。また、Apache CocoonプロジェクトとEJBoss EJBサーバーの開発に積極的に取り組んでおり、Apache Turbineアクティブの共同設立者でもあります。