今月から,XMLとWebサービスに関するJava SE 6の新機能を紹介していきます。

今まで,WebサービスはJava EEだけで扱われていました。Java EEではWebサービスを提供する側の機能が中心になっています。しかし,提供するだけでなく,Webサービスを使う側の機能も重要です。

Java SE 6では,この使う側,つまりWebサービスのクライアント機能が取りいれられました。また,それに応じて,XMLを扱う機能も強化されています。

そこで,本連載ではWebサービスの基幹となる,XMLを扱う機能から紹介していくことにしましょう。

DOM,SAX,そしてStAX

Java 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の使い方を見ていくことにしましょう。

サンプルのソース CursorSample1.java

このサンプルは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
         <<以下,省略>> 

このように要素の名前が取得できましたが,これ以外にも取得できる情報があります。