本稿では、出版予定のXML by Example 第2版から1つの章に手を加え、SAXの入門記事 としました。SAXは、XMLを処理するためのイベント・ベースAPIであり、Document Object ModelすなわちDOMを補完するものです。DOMは、W3Cにより公開されたXMLパーサー用のオブジェクト・ベースAPIです。
SAXについては、次のようなことが言えます。
- イベント・ベースのAPIであること。
- DOMより下位のレベルで動作すること。
- DOMより細かい制御ができること。
- DOMより常に効率が良いこと。
- しかし残念ながらDOMより手が掛かること。
名前に惑わされないでください。SAXは、Simple API for XML (XML用の簡素なAPI) の略ですが、DOM以上の作業が必要となります。その代わり、努力するだけの価値のある、堅固なコードが得られます。
図1は、典型的なXMLプログラムの2つのコンポーネントを示しています。
- パーサーは、アプリケーションに代わって、XMLファイルをデコードするソフトウェア・コンポーネントです。パーサーは、XML構文の複雑さから開発者を効果的に保護します。
- アプリケーションは、ファイルの内容を使います。
図1. XMLプログラムのアーキテクチャー
言うまでもなく、アプリケーションは、ユーロとドルの間で通貨を換算するアプリケーションのように単純な場合も、インターネット上で商品を注文する分散型のe-commerceアプリケーションのように極めて複雑な場合もあります。
この章では、図1の点線、パーサーとアプリケーションとの間のインターフェース、すなわちAPI (アプリケーション・プログラミング・インターフェース) に焦点を当てます。
オブジェクト・ベースおよびイベント・ベースのインターフェース
ご存じのとおり、パーサー用インターフェースには2つの分類、すなわち、オブジェクト・ベースのインターフェースとイベント・ベースのインターフェースがあります。
DOMは、オブジェクト・ベースのパーサー用の標準APIであり、W3Cにより開発、公開されました。これについては、私の著作の他の章で細かく説明しています。ここでDOMの概要は、SAXが全体の中でどのように位置付けられているかを理解するのに役立ちます。
DOMは、オブジェクト・ベースのインターフェースとして、メモリー上にオブジェクトのツリーを明示的に構築することにより、アプリケーションと交信します。オブジェクトのツリーは、XMLファイルのエレメントのツリーを正確にマップしたものです。
DOMは、基礎となるXML文書に忠実に適合しているので、これを習得し、使用するのは簡単です。また、ブラウザーやエディターなど、私がXML中心のアプリケーションと呼んでいるものに対しても理想的です。XML中心のアプリケーションとは、XML文書を処理すること自体が目的でXML文書を処理するアプリケーションを言います。
しかし、ほとんどのアプリケーションにとって、XML文書の処理は、他の多くの作業の中の1つに過ぎません。たとえば、会計パッケージは、XMLの送り状をインポートしますが、それが主要な処理ではありません。勘定の決済、支出の追跡、送り状に対する支払いの照合などが主要な処理になります。会計パッケージの多くは、データベースのようなデータ構造を既に持っている可能性があります。DOMモデルは、このような会計アプリケーションには適していません。なぜなら、アプリケーションはメモリー上にデータのコピーを2つ (1つはDOMツリー内に、もう1つはアプリケーション自身の構造内に) 持たなければならないからです。少なくとも、メモリーにデータを二重に保持するのは、非効率的です。デスクトップ・アプリケーションには重要な問題にはならなくても、サーバーにとっては弱点となり得ます。
SAXは、「XML中心のアプリケーション」以外のアプリケーションにとって適切な選択肢です。実際、SAXは、文書ツリーをメモリー上に明示的には構築しません。SAXを用いればアプリケーションは、最も効率的な方法でデータを保管できるようになります。
図2では、アプリケーションがXMLツリーとそのデータ構造との間のマップをどのように実行するかを示しています。
図2. XML構造をアプリケーション構造にマップする
その名の示すとおり、イベント・ベースのパーサーは、アプリケーションにイベントを送ります。イベントは、
ONCLICK
イベント (ブラウザー) またはAWT/Swingイベント(Java)
などのユーザー・インターフェース・イベントに似ています。
またイベントは、何かが起こりアプリケーションが応答する必要があることを、アプリケーションに知らせます。ブラウザーでは、通常、イベントはユーザーのアクションに応じて生成されます。ユーザーがボタンをクリックすると、そのボタンは、
ONCLICK
イベントを発行します。
XMLパーサーにより、イベントは、ユーザー・アクションにではなく、読み込まれるXML文書のエレメントに関連付けられます。以下に対するイベントがあります。
- エレメントの開始タグおよび終了タグ
- エレメントの内容
- エンティティー
- 構文解析におけるエラー
図3では、パーサーが文書を読み込む際、イベントを生成する様子を示しています。
図3. パーサーがイベントを生成する
リスト1は、XMLで書かれた価格表を示します。ここでは、様々な企業により請求されたXMLの講習会の価格が詳しく記述されています。図4は、価格表文書の構造を示します。
リスト1. pricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.1">
<xbe:product>XML Training</xbe:product>
<xbe:price-quote price="999.00" vendor="Playfield Training"/>
<xbe:price-quote price="699.00" vendor="XMLi"/>
<xbe:price-quote price="799.00" vendor="WriteIT"/>
<xbe:price-quote price="1999.00" vendor="Emailaholic"/>
</xbe:price-list>
|
図4. 価格表の構造
XMLパーサーは、この文書を読み込んで解釈します。パーサーは、文書内で何かを認識するたびに、イベントを生成します。
リスト1
を読む際、パーサーは、まず、XML宣言部を読み込み、文書の開始のためのイベントを生成します。パーサーが最初の開始タグ、
<xbe:price-list>
に出合うと、2番目のイベントを生成し、
price-list
エレメントの開始タグに出合ったことをアプリケーションに通知します。
次に、パーサーは、
product
エレメントの開始タグを見て
(分かりやすくするため、ここから先はネームスペースと、空白インデントを無視します)、3番目のイベントを生成します。
開始タグの後で、パーサーは
product
エレメントである
XML Training
の内容を見ます。これによりもう1つのイベントが生成されます。
次のイベントは、
product
エレメントの終了タグを示します。パーサーは
product
エレメントの構文解析を完了しました。ここまでで、5つのイベントが発行されています。
product
エレメントに3つ、文書の開始に1つ、
price-list
の 開始タグに1つです。
ここでパーサーは、最初の
price-quote
エレメントに移ります。各
price-quote
エレメントに2つのイベントを生成します。1つは開始タグ、もう1つは終了タグのイベントです。
お分かりのとおり、終了タグが、開始タグ内の
/
文字に省略されていても、パーサーは、終了イベントを生成します。
price-quote
エレメントが4つあるので、パーサーは、それらを解析してイベントを8つ生成します。最後にパーサーは、
price-list
の終了タグに出合い、最後の2つのイベントを生成します。つまり
price-list
の終了のイベントと文書の終わりのイベントです。
図5で示しているように、総合すると、イベントはドキュメント・ツリーをアプリケーションに提示します。開始タグのイベントは、「ツリーで1つ段階を下げる」ことを意味するのに対し、終了タグのエレメントは、「ツリーの段階を1つ上げる」ことを意味します。
図5. パーサーがツリーを暗黙的に構築する方法
パーサーは、XML文書のドキュメント・ツリーを構築するのに充分な情報を渡しますが、DOMパーサーとは異なり、明示的にツリーを構築しないことに注目してください。
ここまで読んで、読者は混乱しているものと思います。SAXとDOMのどちらのタイプのAPIを使用すべきなのでしょうか、またどのような場合にこれらを使用すべきなのでしょうか。残念ながら、この疑問に明白な答えはありません。2つのAPIのいずれも本質的にどちらが優れているとは言えません。ニーズによって異るからです。
経験則から言えば、細かい制御を行いたい場合にはSAXを使用し、利便性が重要なときにはDOMを使用するのが良いでしょう。たとえば、DOMは、スクリプト言語でよく利用されます。
SAXを採用する主な理由は、効率が良いからです。SAXは、DOMと比較してできることは少ないのですが、構文解析する際には、より細かい制御が可能です。無論パーサーの能力が低ければ、開発者の作業は増えます。
また、既に述べたように、SAXは、ドキュメント・ツリーを構築する必要がないので、DOMほどリソースを消費しません。
XMLが登場した初期には、DOMは、W3Cの認めた公式APIであるという利点がありました。開発者は、利便性より性能を求めて、次第にSAXへ移っています。
SAXにおける主な制限は、文書内で逆方向のナビゲートができないことです。実際、イベントを出した後、パーサーはそのことを忘れてしまいます。お分かりのように、アプリケーションは、関連するイベントを明示的にバッファーしなければなりません。
もちろん、SAXとDOM APIのどちらをインプリメントしていようと、パーサーは、多くの役立つ処理を行います。パーサーは、文書を読み込み、XML構文を行い、エンティティーを解決するなど、多くのことをします。また、妥当性を検証パーサーは、文書スキーマを適用します。
パーサーを利用する理由は数多くあり、皆さんもSAX、DOMの両方のAPIを習得することをお勧めします。これにより、現在抱えている作業に必要な最善のAPIを選択できる柔軟性が得られます。幸い、最近のパーサーは、両方のAPIをサポートしています。
SAXは、イベント・ベースのパーサーに向けた標準的で簡素なAPIとして、XML-DEVメーリング・リストのメンバーにより開発されました。SAXは、Simple API for XMLの略です。
SAXは、元来Java向けに定義されたものですが、Python、Perl、C++ およびCOM(Windowsオブジェクト) でも利用できます。また、さらに多くの言語で利用できるようになるでしょう。さらに、SAXパーサーは、COMを通じてVisual BasicやDelphiなどWindows用プログラミング言語でも利用できます。
SAXは、DOMとは異なり、公式の標準化団体が認めたものではありませんが、広範囲に使用され、事実上の標準と見なされています。(現在SAXは、David Megginson氏が監修していますが、同氏は将来そのポストから退く意向を表明しています。)
これまで見てきた通り、DOMは、ブラウザーでは、好まれるAPIです。したがって、この章における例は、Javaで記述されています。(ここで読者が、Javaの速成学習コースを希望するなら、私の著作の付録Aまたは、DeveloperWorks、Java zoneのEducationのセクションをご覧ください。)
SAXをサポートするパーサーには、ApacheのパーサーであるXerces (以前はIBMパーサーと呼ばれていました)、MicrosoftのパーサーであるMSXML、Oracleのパーサー、XDKなどがあります。これらのパーサーは、DOMもサポートしているので非常に柔軟性に富んでいます。
SAXだけを提供するパーサーは少なく、James Clark氏のXP、AElig;lfred、Vivid Creations (参考文献 の項をご覧ください) のActiveSAXがあります。
リスト2 は、リスト1で最も低価格のオファーを検索するJavaアプリケーションです。このアプリケーションは、最低価格およびそのベンダー名を印刷します。
このアプリケーションをコンパイルするには、ご使用のプラットフォームに対応するJava DevelopmentKit (JDK) が必要です ( 参考文献 の項をご覧ください)。この例ではJavaランタイムだけでは不十分です。
著者のWebサイト のXBE2ページから本記事のリストをダウンロードしてください。ダウンロード・ファイルの中に、Xercesがあります。このリストに問題がある場合、著者のWebサイトを訪れ、最新版を入手してください。
Cheapest.java
ファイルの
リスト2
を保管してください。DOSプロンプトで、
Cheapest.java
を保管したディレクトリーに移動し、DOSプロンプトで以下のコマンドを発行し、コンパイルます。
mkdir classes set classpath=classes;lib\xerces.jar javac -d classes src\Cheapest.java |
コンパイルの結果、classディレクトリーにJavaプログラムがインストールされます。これらのコマンドは、Xercesが
lib
ディレクトリーに、リスト2が
src
ディレクトリーにあると想定しています。異なるディレクトリーに、パーサーをインストールしてある場合、
classpath
(2番目のコマンド) を変更する必要があります。
price listに対するアプリケーションを実行するには、以下のコマンドを発行します。
java com.psol.xbe2.Cheapest data\pricelist.xml |
結果は、次のようになるはずです。
The cheapest offer is from XMLi ($699.00) |
ここでは、data\pricelist.xmlのファイルに リスト1 があると想定しています。ここでも、ご自分のシステムのパスに適合するよう修正する必要があります。
SAXのイベントは、特定のJavaインターフェースに接続するメソッドとして定義されます。このセクションでは、リスト2をステップ・バイ・ステップに検討していきます。以下のセクションでは、SAXの主なインターフェースの情報について説明します。
イベント・ハンドラーを宣言するのに最も簡単な方法は、SAXが提供する
DefaultHandler
を継承することです。
public class Cheapest extends DefaultHandler |
このアプリケーションは、イベント・ハンドラー
startElement()
1つだけをインプリメントします。パーサーは、開始タグに出合ったとき、このイベント・ハンドラーを呼び出します。パーサーは、次の文書のすべての開始タグについて
startElement()
を呼び出します。すなわち、
<xbe:price-list>
、
<xbe:product>
および
<xbe:price-quote>
です。
リスト2では、イベント・ハンドラーは、
price-quote
だけに関係するので、これを検査します。ハンドラーは、他のエレメントのイベントに関しては何もしません。
if(uri.equals(NAMESPACE_URI) && name.equals("price-quote"))
{
// ...
}
|
イベント・ハンドラーは、
price-quote
エレメントを見付けると、属性のリストからベンダー名と価格を抽出します。この情報を武器にして、最低価格を探すのは簡単です。
String attribute =
attributes.getValue("","price");
if(null != attribute)
{
double price = toDouble(attribute);
if(min > price)
{
min = price;
vendor = attributes.getValue("","vendor");
}
}
|
イベント・ハンドラーは、エレメント名、ネームスペースおよび属性リストを、パーサーからパラメーターとして受け取ることに注目してください。
ここで、
main()
メソッドに目を向けましょう。このメソッドは、イベント・ハンドラー・オブジェクトとパーサー・オブジェクトを作成します。
Cheapest cheapest = new Cheapest(); XMLReader parser = XMLReaderFactory.createXMLReader(PARSER_NAME); |
XMLReader
および
XMLReaderFactory
は、SAXによって定義されます。
XMLReader
は、SAXパーサーです。factoryは、
XMLReaders
を作成するためのヘルパー・クラスです。
main()
は、パーサーのフィーチャーがネームスペースの処理を要求するよう設定し、イベント・ハンドラーをパーサーに登録します。最後に、main()
は、parse()メソッドを、XMLファイルへのURIを使用して呼び出します。
parser.setFeature("http://xml.org/sax/features/namespaces",true);
parser.setContentHandler(cheapest);
parser.parse(args[0]);
|
単純に見える
parse()
メソッドは、XML文書の解析をトリガーします。XML文書は、次にイベント・ハンドラーを呼び出します。
startElement()
メソッドが呼び出されるのは、このメソッドの実行中です。
parse()
を呼び出す陰では、様々なことが行われています。
最後になりましたが重要なのは、
main()
が、結果を印刷することです。
Object[] objects = new Object[]
{
cheapest.vendor,
new Double(cheapest.min)
};
System.out.println(MessageFormat.format(MESSAGE,objects));
|
ここで疑問が生じます。
Cheapest.vendor
と
Cheapest.min
は、その値を、いつ取得するのでしょう? ここでは、これらの値を
main()
に明示的に設定していません。そうです。これはイベント・ハンドラーの仕事です。イベント・ハンドラーは、最終的には
parse()
により呼び出されます。ここが、イベント処理の優れた点です。
注意
Java Development Kitがインストールされていないと、この例がコンパイルできないことを思い出してください。。
最後に、
src\Cheapest.java:7: Package org.xml.sax not found in import. import org.xml.sax.*; |
または
Can't find class com/psol/xbe2/Cheapest or something it requires |
のようなエラーが出された場合、原因は、以下のうちの1つであることがほとんどです。
-
classpath (2番目のコマンド、
classes;lib\xerces.jar)が間違っている。 -
最後のコマンド
(com.psol.xbe2.Cheapest)に誤ったクラス名が入力された。
ここまで、1つのイベント (
startElement()
) だけを見てきました。先に進む前に、SAXで定義される主なインターフェースについて検討しましょう。
SAXは、イベントを複数のインターフェースに分類しています。
-
ContentHandlerは、文書自身 (開始タグまたは終了タグなど) に関連するイベントを定義します。ほとんどのアプリケーションが、これらのイベントに対して登録を行います。 -
DTDHandlerは、DTDに関連するイベントを定義します。しかし、DTDに関して完全な情報をレポートできるだけのイベントは定義しません。DTDを解析する必要がある場合には、オプションのDeclHandlerを使用してください。DeclHandlerは、SAXの拡張で、これをサポートしないパーサーもあります。 -
EntityResolverは、エンティティーのロードに関連するイベントを定義します。このイベントに対する登録を行うアプリケーションは、ほとんどありません。 -
ErrorHandlerは、エラー・イベントを定義します。アプリケーションの多くは、独自の方法でエラーを報告するためにこれらのイベントに対する登録を行います。
作業を容易にするため、SAXでは、
DefaultHandler
クラスにおいてこれらのインターフェースに対するデフォルト・インプリメンテーションを提供しています。ほとんどの場合、インターフェースを直接インプリメントせずに、
DefaultHandler
を拡張して、アプリケーションに関連するメソッドをオーバーライドするほうが容易です。
イベント・ハンドラーを登録し解析を開始するために、アプリケーションは、
XMLReader
インターフェースを使用します。すでに説明したように、
XMLReader
のメソッド、
parse()
が解析を始めます。
parser.parse(args[0]); |
XMLReaderの主なメソッドには、以下ものがあります。
-
parse()は、XML文書を解析します。parse()には、2つのバージョンがあります。1つは、ファイル名またはURLを受け取ります。もう1つは、オブジェクトInputSource(「InputSource」の項を参照) を受け取ります。 -
setContentHandler()、setDTDHandler()、setEntityResolver()およびsetErrorHandler()を用いて、アプリケーションは、イベント・ハンドラーを登録します。 -
setFeature()およびsetProperty()は、パーサーの動作を制御します。これらは、プロパティーまたはフィーチャー識別子と値を取得します。この識別子は、ネームスペースに似たURIです。プロパティーは、オブジェクトを取るのに対し、フィーチャーは、ブール値を取ります。
XMLReaderFactoryで最も一般的に使用されるフィーチャーは、以下のとおりです。
-
http:// xml.org/sax/features/namespaces。これは、すべてのSAXパーサーが、認識します。trueに設定すると (デフォルト)、ContentHandlerのメソッドを呼び出す際に、パーサーは、ネームスペースを認識し、接頭部を解決します。 -
http://xml.org/sax/features/validation。これは、オプションです。trueに設定されていると、検証パーサーが、文書を検証します。非検証パーサーは、このフィーチャーを無視します。
XMLReaderFactory
は、パーサー・オブジェクトを生成します。これは、2つのバージョンの
createXMLReader()
を定義します。最初のバージョンは、パラメーターとしてパーサーに対するクラス名を取得します。2番目のバージョンは、
org.xml.sax.driver
システム・プロパティーからクラス名を取得します。
Xercesでは、クラスは、
org.apache.xerces.parsers.SAXParser
です。
XMLReaderFactory
を使用すると、他のパーサーに容易に変換できるので、これを使用すべきです。実際、ここで必要なのは、1行変更して再コンパイルすることだけです。
XMLReader parser = XMLReaderFactory.createXMLReader( "org.apache.xerces.parsers.SAXParser"); |
さらなる柔軟性を持たせるため、アプリケーションは、コマンド・ラインからクラス名を読み込むことも、パラメーター無しの
createXMLReader()
を使用することもできます。したがって、再コンパイルせずにパーサーを変更できます。
InputSource
は、パーサーが ファイルを読む方法を制御します。ファイルには、XML文書とエンティティーが含まれます。
ほとんどの場合、文書はURLからロードされます。しかし、特殊なニーズを持つアプリケーションは、
InputSource
をオーバーライドできます。たとえば、データベースから文書をロードする場合に使用されます。
ContentHandler
は、XML文書のイベントを定義するので、使用されることが最も多いSAXインターフェースです。
ここまで見てきたように、
リスト2
は、
ContentHandler
内で定義されるイベント、
startElement()
をインプリメントします。これは、パーサーに
ContentHandler
を登録します。
Cheapest cheapest = new Cheapest(); // ... parser.setContentHandler(cheapest); |
ContentHandler
は、以下のイベントを宣言します。
-
startDocument()/endDocument()は、アプリケーションに文書の先頭または末尾を通知します。 -
startElement()/endElement()は、アプリケーションに開始タグまたは終了タグを通知します。属性は、Attributesパラメーターとして渡されます (次の属性のセクションを参照)。空エレメント (たとえば<img href="logo.gif"/>) は、たとえ タグが1つしかなくてもstartElement()およびendElement()の両方を生成します。 -
startPrefixMapping()/endPrefixMapping()は、アプリケーションにネームスペースの範囲を通知します。http://xml.org/sax/features/namespacesがtrueである場合、パーサーは、既にネームスペースを解決しているので、この情報を必要とすることはほとんどありません。 -
characters()/ignorableWhitespace()は、エレメント内の最初のテキスト (解析済みの文字データ) を見つけると、アプリケーションにそのことを通知します。パーサーは、いくつものイベント間でテキストを展開することができます (そのバッファーを効率よく管理するため)。ignorableWhitespaceイベントは、XML標準により定義される、無視できるスペースに対して使用できます。 -
processingInstruction()は、処理命令をアプリケーションに通知します。 -
skippedEntity()は、アプリケーションにエンティティーがスキップされたこと(すなわち、パーサーが、DTD/スキーマのエンティティー宣言を認識しなかったとき) を通知します。 -
setDocumentLocator()は、Locatorオブジェクトをアプリケーションに渡します。後述のLocatorのセクションを参照してください。SAXパーサーは、必ずしもLocatorを持つ必要はありませんが、持っている場合、他のいかなるイベントにも先立ってこのイベントを起動しなければなりません。
属性
startElement()
イベント内で、アプリケーションは、
Attributes
パラメーター内の属性のリストを受け取ります。
String attribute = attributes.getValue("","price");
|
Attributes
は、以下のメソッドを定義します。
-
getValue(i)/getValue(qName)/getValue(uri,localName) は、i番目の属性の値、または名前が指定されている属性の値を戻します。 -
getLength()は、属性の数を戻します。 -
getQName(i)/getLocalName(i)/getURI(i) は、i番目の属性の修飾名 (接頭部を持つ)、ローカル名 (接頭部を持たない) およびネームスペースURIを戻します。 -
getType(i)/getType(qName)/getType(uri,localName) は、
i
番目の属性の型または、名前が指定されている属性の型を戻します。型は、DTDで使用されるような文字です。これには
"CDATA"、"ID"、"IDREF"、"IDREFS"、"NMTOKEN"、"NMTOKENS"、"ENTITY"、"ENTITIES"または"NOTATION"などがあります。.
Locator
によって、アプリケーションは行と列を使用できます。ただしパーサーは、
Locator
オブジェクトを提供する必要はありません。
Locator
は、以下のメソッドを定義します。
-
getColumnNumber()は、現在のイベントが終了した列を戻します。endElement()イベントでは、終了タグの最後の列を戻します。 -
getLineNumber()は、現在のイベントが終了した行を戻します。endElement()イベントでは、終了タグの行を戻します。 -
getPublicId()は、現在のdocumentイベントの公開識別子を戻します。 -
getSystemId()は、現在のdocumentイベントのシステム識別子を戻します。
DTDHandler
は、DTDの解析に関連する2つのイベントを宣言します。
-
notationDecl()は、アプリケーションに、表記法が宣言されたことを通知します。 -
nparsedEntityDecl()は、アプリケーションに未解析のエンティティー宣言が検出されたことを通知します。
EntityResolver
インターフェースは、1つのイベント、すなわち、
resolveEntity()
だけを定義します。これは、
InputSource
(別の章に説明があります) を戻します。
SAXパーサーは、ほとんどのURLを解決できるので、
EntityResolver
をインプリメントするアプリケーションは少ししかありません。例外は、カタログ・ファイル(別の章に説明があります)
です。これは、公開識別子をシステム識別子に変換します。アプリケーションがカタログ・ファイルを必要とする場合は、Norman
Walsh氏のカタログ・パッケージをダウンロードしてください (
参考文献
を参照)。
ErrorHandler
インターフェースは、エラーに関するイベントを定義します。これらのイベントを扱うアプリケーションは、独自のエラー処理を行うことができます。
独自のエラー・ハンドラーをインストールすると、パーサーは例外を生成しなくなります。例外をスローするのは、イベント・ハンドラーの役割になります。
このインターフェースは、エラーの3のレベル、すなわち重大度に対応する3つのメソッドを定義します。
-
warning()は、XMLの仕様で定義されているエラー以外の問題を知らせます。たとえば、パーサーによっては、XML宣言がない場合に警告を発行するものがあります。これはエラーではないが (宣言はオプションなので)、注意を払う価値はあります。 -
error()は、XMLの仕様により定義されているエラーがあることを知らせます。 -
fatalError()は、XMLの仕様により定義される致命的エラーを知らせます。
SAXにより定義されるほとんどのメソッドは、
SAXException
をスローできます。
SAXException
は、XML文書を解析している間エラーを知らせます。
エラーは、解析エラーまたはイベント・ハンドラーのエラーかのいずれかです。イベント・ハンドラーから、その他の例外を報告するには、
SAXException
の例外をラップすることによって可能になります。
例:
startElement
イベントを処理している間に、イベント・ハンドラーが
IndexOutOfBoundsException
をキャッチしたと仮定してください。イベント・ハンドラーは、
SAXException
内で
IndexOutOfBoundsException
をラップできます。
public void startElement(String uri,
String name,
String qualifiedName,
Attributes attributes)
{
try
{
// the code may throw an IndexOutOfBoundsException
}
catch(IndexOutOfBounds e)
{
throw new SAXException(e);
}
}
|
SAXException
は常に、それがキャッチされ、中断された
parse()
メソッドへ向かって流れます。
try
{
parser.parse(uri);
}
catch(SAXException e)
{
Exception x = e.getException();
if(null != x)
if(x instanceof IndexOutOfBoundsException)
// process the IndexOutOfBoundsException
}
|
リスト1
は、情報がpriceエレメントの属性として保管されるので、SAXパーサーには便利です。アプリケーションは、
startElement()
に対してのみ登録しなければなりません。
例 リスト3は、情報がいくつものエレメント間に分散しているので、さらに複雑です。特に、ベンダーは、配送の時期により異なる価格を持っています。ユーザーが待つことをいとわなければ、より適切な価格で購入できるかもしれません。図6は、文書の構造を示しています。
リスト3. xtpricelist.xml
<?xml version="1.0"?>
<xbe:price-list xmlns:xbe="http://www.psol.com/xbe2/listing8.3">
<xbe:name>XML Training</xbe:name>
<xbe:vendor>
<xbe:name>Playfield Training</xbe:name>
<xbe:price-quote delivery="5">999.00</xbe:price-quote>
<xbe:price-quote delivery="15">899.00</xbe:price-quote>
</xbe:vendor>
<xbe:vendor>
<xbe:name>XMLi</xbe:name>
<xbe:price-quote delivery="3">2999.00</xbe:price-quote>
<xbe:price-quote delivery="30">1499.00</xbe:price-quote>
<xbe:price-quote delivery="45">699.00</xbe:price-quote>
</xbe:vendor>
<xbe:vendor>
<xbe:name>WriteIT</xbe:name>
<xbe:price-quote delivery="5">799.00</xbe:price-quote>
<xbe:price-quote delivery="15">899.00</xbe:price-quote>
</xbe:vendor>
<xbe:vendor>
<xbe:name>Emailaholic</xbe:name>
<xbe:price-quote delivery="1">1999.00</xbe:price-quote>
</xbe:vendor>
</xbe:price-list>
|
図6. Price listの構造
最も有利な取り引きを探すには、アプリケーションは、いくつものエレメントから情報を集めなければなりません。しかしながら、パーサーは、
startElement()
、
characters()
および
endElement()
の各エレメントごとに3つのイベントを生成できます。アプリケーションは、どうしてもイベントおよびエレメントを関連付ける必要があります。
リスト4 は、price listから最も有利な取り引きを探す新たな Javaアプリケーションです。最も有利な取り引きを探す際には、緊急度も考慮します。実際、リスト3で、最も低価格のベンダー (SMLi) の配送は最も遅くなっています。他方、Emailaholicは、高価ではありますが配送期間は2日です。
先に紹介したCheapestアプリケーションと同様、このアプリケーションをコンパイルし、実行します。結果は、緊急度によって変わります。このプログラムが2つのパラメーターを取るのが分かるでしょう。ファイル名と、待つことが可能な日数を表す最大遅延日数です。
java com.psol.xbe2.BestDeal data/xtpricelist.xml 60 |
は、以下を戻します。
The best deal is proposed by XMLi. A(n) XML Training delivered[ccc] in 45 days for $699.00 |
ここで、
java com.psol.xbe2.BestDeal data/xtpricelist.xml 3 |
は、以下を戻します。
The best deal is proposed by Emailaholic. A(n) XML Training[ccc] delivered in 1 days for $1,999.00 |
リスト4は、これまでの中で最も複雑なアプリケーションです。ただし、異常なものではありません。このSAXパーサーは、非常に下位レベルのパーサーなので、アプリケーションは、DOMパーサーを使用する場合よりも多くの作業を肩代わりしなければなりません。
アプリケーションは、2つのクラス、
SAX2BestDeal
および
BestDeal
で編成されます。
SAX2BestDeal
は、SAXパーサーとのインターフェースを管理します。これは、統一の取れた方法で状態を管理し、イベントをグループ化します。
BestDeal
は、価格の比較を行うロジックを持っています。また、(XMLではなく)
アプリケーション用に最適化された構造を持つ情報を保持します。このアプリケーションのアーキテクチャーを、図7に示します。図8は、UMLで記述したクラス図です。
図7. アプリケーションのアーキテクチャー
図8. アプリケーションのクラス図
SAX2BestDeal
は、複数のイベント、
startElement()
、
endElement()
および
characters()
を扱います。
SAX2BestDeal
は、文書ツリーの中の位置を常に追跡します。
たとえば、
characters()
イベントにおいて、
SAX2BestDeal
は、テキストが名前なのか、価格なのか、または無視できる空白なのか、を知る必要があります。さらに、2つの
name
エレメントがあります。すなわち、
price-list
の
name
および
vendor
の
name
です。
SAXパーサーは、DOMパーサーと異なり、状態に関する情報を提供しません。そのためアプリケーションは自身の状態を追跡する必要があります。これには、いくつものオプションがあります。リスト4では、状態およびその遷移の中で重要なものを識別しました。図6の文書構造からこれらの情報を取り出すのは難しいことではありません。
アプリケーションがprice-listタグに最初に出合うのは明らかです。したがって、最初の状態は
price-list
の中に あるはずです。アプリケーションは、そこから
name
に到達します。2番目の状態は、
price-list
の
name
です。
次のエレメントは、
vendor
でなければならないので、3番目の状態は、
price-list
内の
vendor
の中に あります。nameはvendorの後にあるので、4番目の状態は、
price-list
内の
vendor
の中の
name
の中に あります。
name
の後には、
price-quote
エレメントが続き、対応する状態は、
price-list
内の
vendor
の中の
price
の中に あります。その後、パーサーは、すでに状態を持っている
price-quote
または
vendor
のいずれかに出合います。
この概念は、図9に見られるように状態と遷移を図上に視覚化したほうが分かりやすいでしょう。
price-list/name
または
price-list/vendor/name
を処理するかによって2つの異なるnameエレメントに関連する2つの異なる状態があることに注意してください。
図9. 状態遷移図
リスト4では、状態変数は、現在の状態を保管します。
final protected int START = 0,
PRICE_LIST = 1,
PRICE_LIST_NAME = 2,
VENDOR = 3,
VENDOR_NAME = 4,
VENDOR_PRICE_QUOTE = 5;
protected int state = START;
|
遷移1 状態変数の値は、イベントに応じて変化します。この例では、elementStart() が状態を更新します。
ifswitch(state)
{
case START:
if(name.equals("price-list"))
state = PRICE_LIST;
break;
case PRICE_LIST:
if(name.equals("name"))
state = PRICE_LIST_NAME;
// ...
if(name.equals("vendor"))
state = VENDOR;
break;
case VENDOR:
if(name.equals("name"))
state = VENDOR_NAME;
// ...
if(name.equals("price-quote"))
state = VENDOR_PRICE_QUOTE;
// ...
break;
}
|
SAX2BestDeal
は、現在の
name
および
price-quote
の内容を保管するためにインスタンス変数をいくつか持ちます。これは実際には、ツリーの小さなサブセットです。DOMとは異なり、ツリー全体を持つことは決してありません。アプリケーションが
name
および
price-quote
を使用し終わると、これらを破棄するからです。
これは、メモリー使用の観点から見ると非常に効率的です。実際、どの時点でもメモリーには小さなサブセットだけを配置するので、数ギガバイト単位のファイルを処理できるでしょう。
遷移2
パーサーは、文書の各文字データ (インデントを含む) に対して
characters()
を呼び出します。
name
および
price-quote
のテキストだけを記録することは意味があるのでイベント・ハンドラーは、状態を使用します。
switch(state)
{
case PRICE_LIST_NAME:
case VENDOR_NAME:
case VENDOR_PRICE_QUOTE:
buffer.append(chars,start,length);
break;
}
|
遷移3 endElement() のイベント・ハンドラーは、状態を更新し、現在のエレメントswitch(state) を処理するためにBestDealを呼び出します。
{
case PRICE_LIST_NAME:
if(name.equals("name"))
{
state = PRICE_LIST;
setProductName(buffer.toString());
// ...
}
break;
case VENDOR_NAME:
if(name.equals("name"))
state = VENDOR;
// ...
break;
case VENDOR_PRICE_QUOTE:
if(name.equals("price-quote"))
{
state = VENDOR;
// ...
compare(vendorName,price,delivery);
// ...
}
break;
case VENDOR:
if(name.equals("vendor"))
state = PRICE_LIST;
// ...
break;
case PRICE_LIST:
if(name.equals("price-list"))
state = START;
break;
}
|
リスト4は、SAXアプリケーションとしては典型的なものです。これには、SAXイベント・ハンドラー (
SAX2BestDeal
) があります。これは、アプリケーションに最も適した形式でイベントをまとめます。
アプリケーション・ロジック (
BestDeal
内の)
は、イベント・ハンドラーから独立しています。実際、多くの場合、アプリケーション・ロジックは、XMLとは別個に記述されます。
階層化アプローチは、アプリケーションと解析とを明確に分離します。
また、この例では、SAXがDOMより効率的なことが明確に示されていますが、プログラマーの負担は増えます。特にプログラマーは、状態および状態間の遷移を明示的に管理しなければなりません。(DOMでは、ツリーを再帰的にたどるので状態は暗黙的です。)
XMLは、非常に柔軟性に富んだ標準です。しかし、実際には、XMLアプリケーションは、プログラマーが柔軟にするものです。このセクションでは、アプリケーションがXMLの柔軟性を確実に活用するヒントを提供します。
BestDealアプリケーションは、XML文書の構造をほとんど制約しません。XML文書にエレメントを追加しても、それらは単に無視されます。たとえば、BestDealは、以下の
vendor
エレメントを受け入れます。
<xbe:vendor> <xbe:name>Playfield Training</xbe:name> <xbe:contact>John Doe</xbe:contact> <xbe:price-quote delivery="5">999.00</xbe:price-quote> <xbe:price-quote delivery="15">899.00</xbe:price-quote> </xbe:vendor> |
しかしcontact情報は無視されるでしょう。一般に、HTMLブラウザーがしているように、未知のエレメントを単に無視するというのは良い考えです。
イベント・ハンドラーから文書構造を検証するのは難しいことではありません。以下の短いコード (
startElement()
から適用しました) は、構造を検証し、vendorエレメントにnameまたはprice以外のものが格納されていると
SAXException
をスローします。
case VENDOR:
if(name.equals("name"))
{
state = VENDOR_NAME;
buffer = new StringBuffer();
}
else if(name.equals("price-quote"))
{
state = VENDOR_PRICE_QUOTE;
String st = attributes.getValue("","delivery");
delivery = Integer.parseInt(st);
buffer = new StringBuffer();
}
else
throw new SAXException("Expecting <xbe:name> or <xbe:price-quote>");
break;
|
contact
エレメントを伴うリストがあれば、ここには、以下のように記述されます。
org.xml.sax.SAXException: Expecting <xbe:name> or <xbe:price-quote> |
しかしながら、実際には、アプリケーションが本当に文書構造に依存していれば、スキーマを記述し検証パーサーを使うのが最善です。
XML by Example 第2版から抜粋したこの記事では、XML文書の読み方を学習しました。この本の残りの部分では、文書の記述方法とループの閉じ方に関するプロセス全体の学習を行うことができます。この学習を完了するためオンライン・チュートリアルやオンライン記事を参照できます。(もちろん、私の著作を選択してくれることを望みますが。)
-
著者のWebサイト
のXBE2ページから本抜粋記事のリストをダウンロードしてください。
-
出版社のページで
XML by Example
第2版の詳細をご覧ください。この本は、9月に出版の予定です。この書籍を注文できる書店サイトへもリンクしています。
-
XML-dev list
のコミュニティーが開発中の
SAXの公式ホーム・ページ
で、FAQ、歴史およびソフトウェア・サポートなどを調べ、SAXについて詳しく研究してください。
SoundForgeのSAXプロジェクト・ページ
からSAXをダウンロードしてください。
-
SAXをサポートするパーサー
- James Clark氏のXP
- Vivid CreationsのActiveSAX
- 以前、IBMのパーサーであった ApacheパーサーのXerces は、DOMとSAXの両方をサポートするパーサーのうちの1つです。PerlおよびCOMに対するバインディングを伴うJavaとC++ バージョンが利用できます。
- SAX2をサポートする、他のパーサー、XSLTエンジン、ドライバー、ユーティリティー、その他のソフトウェアを the SAX page の「Drivers and applications for SAX2」でご覧ください。
-
読者のアプリケーションでカタログ・ファイルが必要な場合、
Norman Walsh氏のカタログ・パッケージ
をダウンロードしてください。
-
最近developerWorks XML zoneで公開されたSAXのヒント (および他のXMLの話題)
をご覧ください。ここには、
妥当性検査およびSAX ErrorHandlerインターフェース
および
エンティティー・リゾルバーの使用
があります。
-
developerWorks XML
zoneには、著者による次の記事があります。この記事のXMLアプリケーション・サンプルをご覧ください。
実用的なXML: コンテンツ・マネージメントにXSLTを使用する
。
-
WAS Advanced Edition 3.5オンライン・ヘルプの
technical XMLの テクニカル・バックグラウンド情報
にXMLパーサーとXSLTエンジンを組み込むIBM WebSphere Application
ServerのXML処理方法が載っていますのでご覧ください。
-
SAXおよびDOMの入門用スライド・ショーは、Kelvin Lawrence (IBM CTO for
XML Technology) のページの次のプレゼンテーションからPDFを開いてください。"
A Detailed Introduction to Parsing and
Processing XML documents using Java(TM)
technology
"

Benoit Marchal氏は、ベルギーのナミュールを拠点にしたコンサルタントおよび著述家です。彼の著作には、 XML by Example(Que社、邦訳: インプレス社「実例で学ぶXML」。間もなく第2版が出版される予定です)、 Applied XML Solutions および XML and the Enterprise があります。また、Gamelanのコラムや、developerWorks XML zoneのコラムWorking XML の著者でもあります。最新プロジェクトの詳細については、www.marchal.com をご覧ください。