|
|
「Java SE 6完全攻略」第69回 第三のパーサ - StAX その1今月から,XMLとWebサービスに関するJava SE 6の新機能を紹介していきます。 今まで,WebサービスはJava EEだけで扱われていました。Java EEではWebサービスを提供する側の機能が中心になっています。しかし,提供するだけでなく,Webサービスを使う側の機能も重要です。 Java SE 6では,この使う側,つまりWebサービスのクライアント機能が取りいれられました。また,それに応じて,XMLを扱う機能も強化されています。 そこで,本連載ではWebサービスの基幹となる,XMLを扱う機能から紹介していくことにしましょう。 DOM,SAX,そしてStAXJava SE 6では,新しいXMLパーサが仲間入りすることになりました。その名はStreaming API for XML,通称StAXです。 StAXはJCPのJSR 173で標準策定が行なわれました。Java SEに取りいれられたJSRのスペックリードは圧倒的にSun Microsystemsの人が多いのですが,JSR 173は元BEA SystemsのChristopher Fry氏が行なっています。このため,Reference ImplementationもBEAで作成されたという,変り種のJSRです。 StAXは新しい標準であるにも関わらず,すでに多くのプロジェクトで使用されています。たとえば,JAXB 2.0などがStAXを使用しています。 そのStAXとはどのようなパーサなのでしょうか。 今まで,JavaでXMLをパースするには,DOMもしくはSAXが使われていました。 DOMはXMLのツリー構造を,そのままオブジェクト構造であらわすパーサです。一方のSAXはイベント駆動型のパーサになります。 StAXも,SAX同様,イベント駆動型のパーサになります。SAXとStAXは名前も似ていますが,処理手法も似ているのです。 それでは,何が違うかといういうと,SAXがプッシュ型,StAXがプル型という点が異なります。プルとかプッシュとかいってもよく分からないですね。 SAXではイベントリスナに相当するorg.xml.sax.ContentHandlerオブジェクトをパーサに登録します。パーサはXMLをパースしながら,要素が開始された時などに,ContentHandleオブジェクトのstartElementなどのコールバックメソッドをコールします。 つまり,イベントを起こすか起こさないかはパーサに依存しており,アプリケーションはイベントを押しつけられている(つまりプッシュです)だけです。 それに対して,StAXではイベントを投げるのはパーサではありません。アプリケーションがパーサに対してイベントがあるかどうか伺いをたてます。イベントがあれば,それをパーサから取得します。アプリケーションがパーサからイベントを引っ張り上げる(つまりプルです)するのです。 StAXを使った場合,イテレータのようにイベントを扱うことになります。つまり,パースの制御はアプリケーション側にあります。 たとえば,SAXはパースがはじまってしまうと,アプリケーション側から制御することができません。それに対し,StAXではパースの制御ができるので,たとえばパースの途中で中止するなどといったことが容易に実現できるのです。 カーソルAPIでは,さっそくStAXの使い方を見ていきましょう。 StAXには2種類のパース手法があります。一方がカーソルAPI,もう一方がイベントイテレータAPIです。 カーソルAPIもイベントイテレータAPIもイテレータのようにパーサを扱うことは同じです。イベントイテレータAPIの方が,名前が示すとおり,イベントが強調されたAPIになっています。 そこで,より基本的なカーソルAPIを今週,イベントイテレータAPIを来週解説することにします。 まずは単純なサンプルを使って,カーソルAPIの使い方を見ていくことにしましょう。
このサンプルはXMLファイルを読み込み,要素が開始されると,その要素名を標準出力に出力するというプログラムです。 public CursorSample1(String xmlfile) {
// 1. パーサ用ファクトリの生成
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLStreamReader reader = null;
BufferedInputStream stream = null;
try {
// 2. 入力に使用するファイルの設定
stream = new BufferedInputStream(
new FileInputStream(xmlfile));
// 3. パーサの生成
reader = factory.createXMLStreamReader(stream);
// 4. イベントループ
while (reader.hasNext()) {
// 4.1 次のイベントを取得
int eventType = reader.next();
// 4.2 イベントが要素の開始であれば,名前を出力する
if (eventType == XMLStreamReader.START_ELEMENT) {
System.out.println("Name: " + reader.getName());
}
}
} catch (FileNotFoundException ex) {
System.err.println(xmlfile + " が見つかりません");
} catch (XMLStreamException ex) {
System.err.println(xmlfile + " の読み込みに失敗しました");
} finally {
// 5. パーサ,ストリームのクローズ
if (reader != null) {
try {
reader.close();
} catch (XMLStreamException ex) {}
}
if (stream != null) {
try {
stream.close();
} catch (IOException ex) {}
}
}
}
StAXを使用するには,まずパーサを生成しなくてはなりません。 カーソルAPIを使用する場合,パーサはjavax.xml.stream.XMLStreamReaderインタフェースであらわされます。また,XMLStreamReaderオブジェクトを生成には,ファクトリクラスであるjavax.xml.stream.XMLInputFactoryクラスが使用されます。 XMLInputFactoryクラスのオブジェクトは上記コードの1に示しているようにgetInstanceメソッドをコールして,取得します。 XMLStreamReaderオブジェクトを生成するには,XMLInputFactoryクラスのcreateXMLStreamReaderメソッドをコールします。このメソッドは引数にストリーム,リーダ,もしくはXSLTで用いるソースを使用できます。 ここでは,2に示したようにストリームを使用し,3でストリームを引数にしてcreateXMLStreamReaderメソッドをコールしています。 これでパーサの準備は完了しました。 4からがXMLのパース処理になります。 前述したようにパースはイテレータを用いた処理と同様に行ないます。 XMLStreamReaderオブジェクトに対し,イベントがあるかどうかを確認するのがhasNextメソッドです。hasNextメソッドの戻り値はイベントがあればtrue,なければfalseになります。 イベントがある場合,4.1に示すように,nextメソッドをコールします。 イテレータではnextメソッドの戻り値として値を取得できますが,XMLStreamReaderクラスではnextメソッドの戻り値はイベントタイプを表すintの値です。 このイベントタイプはjavax.xml.stream.XMLStreamConstatntsインタフェースで定義されています。XMLStreamReaderインタフェースはXMLStreamConstantsインタフェースの派生インタフェースなので,XMLStreamConstantsインタフェースで定義された定数をそのまま使用することができます。 個人的には,このようなインタフェースで定数を定義するよりenumを使って定義してもらいたいところです。とはいうものの,StAXが策定されていた頃は,まだJ2SE 5.0がリリースされる前なのでしかたないのですが。 イベントタイプが取得できたら,そのタイプに応じた処理を行ないます。ここでは4.2に示したように,イベントタイプが要素の開始を示すSTART_ELEMENTの場合,getNameメソッドを使用して要素の名前を出力しています。 最後にXMLStreamReaderオブジェクトとストリームをクローズします。 XMLStreamReaderオブジェクトをクローズすると,ストリームも一緒にクローズするような気がしますが,実際にはストリームのクローズは行ないません。このため,別途ストリームのクローズを行なう必要があります。 これはXMLStreamReaderオブジェクトにリーダやソースを用いたときも同様です。 さて,このサンプルを実行してみましょう。例として,以下の簡単なXMLファイル(names.xml)をパースしてみます。 <?xml version="1.0" encoding="utf-8"?>
<names>
<name>
<first>Bob</first>
<last>Dylan<last>
</name>
<name>
<first>Paul</first>
<last>Simon</last>
</name>
<name>
<first>Pete</first>
<last>Seeger</last>
</name>
</names>
実行結果は次のようになりました。 C:\stax>java CursorSample1 names.xml Name: names Name: name Name: first Name: last Name: name Name: first Name: last Name: name Name: first Name: last このXMLファイルは名前空間を設定していないため,単にタグ名だけが出力されました。適切な名前空間が設定されていれば,それも合わせて出力されます。 たとえば,この連載の原稿をパースしてみましょう。本連載の原稿はxhtmlで記述してあるので,パースできるはずです。 C:\stax>java CursorSample1 stax1.xml
Name: {http://www.w3.org/1999/xhtml}html
Name: {http://www.w3.org/1999/xhtml}head
Name: {http://www.w3.org/1999/xhtml}meta
Name: {http://www.w3.org/1999/xhtml}title
Name: {http://www.w3.org/1999/xhtml}link
Name: {http://www.w3.org/1999/xhtml}style
Name: {http://www.w3.org/1999/xhtml}body
Name: {http://www.w3.org/1999/xhtml}div
Name: {http://www.w3.org/1999/xhtml}h1
Name: {http://www.w3.org/1999/xhtml}p
Name: {http://www.w3.org/1999/xhtml}h2
Name: {http://www.w3.org/1999/xhtml}p
Name: {http://www.w3.org/1999/xhtml}p
Name: {http://www.w3.org/1999/xhtml}p
Name: {http://www.w3.org/1999/xhtml}p
Name: {http://www.w3.org/1999/xhtml}h3
Name: {http://www.w3.org/1999/xhtml}p
<<以下,省略>>
このように要素の名前が取得できましたが,これ以外にも取得できる情報があります。
>>次のサンプル
|