連載 XMLツールでプログラミング(3)
SAXによるXML文書の操作

DOMとSAXは、XML文書を操作するもっともポピュラーなAPIだ。前回紹介したDOMはDOMツリーを柔軟に操作可能な一方で、SAXは大規模なXML文書を高速に処理できる特長がある。目的に合わせてこの2つを使い分けるべきだが、今回はそのSAXについて解説する。

赤木伸
日本オラクル株式会社
2000/10/5

 前回はXML ParserのDOM APIを利用して下記の「Book.xml」から特定の要素の内容を取り出す方法について説明した。今回はXML文書を操作するためのもうひとつのAPIであるSAXについて説明する。

 SAXでは、DOMのようにXML文書をまるごとメモリに読み込んだあと処理するのではなく、XML文書の先頭から一行ずつ順番に処理をして行く。そのため、どんなに大きなXML文書を処理するときでも、メモリの使用量はそれほど負担にならず、処理も一般に高速だという利点がある。今回も下記の「Book.xml」をサンプルにして、前回のDOMによるプログラムと同じ結果を得るプログラムを紹介しよう。

Book.xml

<?xml version="1.0" encoding="Shift_JIS" ?>
<ブックリスト>
  <アイテム id="11111">
    <タイトル>鹿子木といえばXML</タイトル>
    <筆者>鹿子木亨紀</筆者>
    <カテゴリ>1</カテゴリ>
  </アイテム>
  <アイテム id="22222">
    <タイトル>丸橋によるXML講座</タイトル>
    <筆者>丸橋玲奈</筆者>
    <カテゴリ>2</カテゴリ>
  </アイテム>
  <アイテム id="33333">
    <タイトル>倉田とXMLの出会い</タイトル>
    <筆者>倉田雅友</筆者>
    <カテゴリ>2</カテゴリ>
  </アイテム>
  <アイテム id="44444">
    <タイトル>XMLと有川の世界</タイトル>
    <筆者>有川樹一郎</筆者>
    <カテゴリ>3</カテゴリ>
  </アイテム>
</ブックリスト>

   SAX(Simple API for XML)を使う

 SAXでは、要素の開始などのイベントが起こるたびに決まったメソッドが呼ばれるので、そのメソッドを実装することによって、目的の処理を行う。例えば、パース(解析)し始めて、要素の開始というイベントが起きると、startElement()というメソッドが呼ばれる。そのメソッドに、処理したい内容を記述するわけだ。

 例として、先ほどの「Book.xml」をSAX Parserで解析したとき、どのようなイベントが起きるかを以下に示す。

SetDocumentLocator
StartDocument
StartElement :ブックリスト
StartElement :アイテム (id=11111)
StartElement :タイトル
Characters :鹿子木といえばXML
EndElement :タイトル
StartElement :筆者
Characters :鹿子木亨紀
EndElement :筆者
StartElement :カテゴリ
Characters :1
EndElement :カテゴリ
EndElement :アイテム
StartElement :アイテム (id=22222)
StartElement :タイトル
Characters :丸橋によるXML講座
EndElement :タイトル
StartElement :筆者
Characters :丸橋玲奈
EndElement :筆者
StartElement :カテゴリ
Characters :2
EndElement :カテゴリ
EndElement :アイテム
StartElement :アイテム (id=33333)

……中略……

EndElement :アイテム
EndElement :ブックリスト
EndDocument

 ほとんどのイベントは“StartElement”と“EndElement”であり、しかも読み込んだXML文書のエレメントの順番にイベントが発生していることが分かるだろう。

 では、このイベントをふまえて、先ほどDOMを利用した例とまったく同じ結果を得る、SAXを利用したプログラムの例を見てみる。

SAXを利用したプログラム(抜粋)

public class BookBySAX extends HandlerBase {
  static public void main(String[] argv) {

  // 「Book.xml」を読み込む
  theStreamToParse = new FileInputStream("Book.xml");

  // パーサー用に新しくハンドラーを生成
  BookBySAX sample = new BookBySAX();

  // SAXパーサーをインスタンス化
  SAXParser parser = new SAXParser(); (1)

  // 使用するハンドラーをパーサーに指定
  parser.setDocumentHandler(sample);
  parser.setErrorHandler(sample);

  // XML文書を解析
  parser.parse(inputsource);(2)

  // 各インスタンス変数を出力
  System.out.println(sample.targetid);
  System.out.println(sample.title);
  System.out.println(sample.author);
  System.out.println(sample.categid);
  }
}

/////////////////////////////////////////////
// DocumentHandlerインターフェースの実装
/////////////////////////////////////////////

public void setDocumentLocator (Locator locator)(3)
{ this.locator = locator; }

public void startDocument()(4)
{
  // スタックを生成
  elements = new Stack();
}

public void endDocument() throws SAXException(5)
{ }

public void startElement(String name, AttributeList atts)
throws SAXException(6)
{
  // 要素名をスタックに追加
  elements.push(name);
  if (name.equals("アイテム")) {
    for (int i=0;i<atts.getLength();i++) {
      String aname = atts.getName(i);
      if (aname.equals("id")) {
        // 「id」の値を保存
        bookid = Integer.parseInt(atts.getValue(i));
      }
    }
  }

}

public void endElement(String name) throws SAXException
{(7)
  // 要素をスタックから出す
  elements.pop();
  if (name.equals("アイテム")) {
    bookid = -1;
  }
}

public void characters(char[] cbuf, int start, int len)
{(8)
  // 指定の値の「id」のときだけの処理
  if (bookid == targetid)
  {
    // 要素ごとに要素の内容をインスタンス変数に格納
    String text = new String(cbuf,start,len);
    if (elements.peek().equals("タイトル"))
    { title = text; }
    else if (elements.peek().equals("筆者"))
    { author = text; }
    else if (elements.peek().equals("カテゴリ"))
    { categid = Integer.parseInt(text); }
  }
}

/////////////////////////////////////////////
// ErrorHandlerインターフェースの実装
/////////////////////////////////////////////

public void warning (SAXParseException e)(9)
throws SAXException
{……}

public void error (SAXParseException e)
throws SAXException
{……}

public void fatalError (SAXParseException e)
throws SAXException
{……}

 例によって、上記のリストも必要な部分以外は省略してある。リストの全体は、BookBySAX.javaを参照してほしい。

  1. まず、DOMのときと同じように、SAX Parserをインスタンス化する。ただし、SAXの場合はパース(解析)する前に、いくつかやっておくことがある。SAXでは、イベントのカテゴリごとにインターフェースが用意されているので、そのうち必要なものだけをイベントハンドラとしてSAX Parserに登録する必要がある。このインターフェイス(イベントハンドラ)には、DTDのイベントハンドラであるDTDHandlerなどといったものがあるが、今回のサンプルで必要なのは、XML文書のイベントハンドラであるDocumentHandlerとエラーのイベントハンドラであるErrorHandlerの2つである。そこで、それぞれsetDocumentHandler()とsetErrorHandler()メソッドを用いてSAX Parserに登録している。また、HandlerBaseというクラスを継承しているのがわかるが、このクラスは一通りのイベントハンドラのデフォルトの処理(基本的になにもしていない)を実装している。よって、このクラスを継承することで、必要なインタフェースの必要なメソッドだけをオーバーライドすれば済むようになっている。

  2. イベントハンドラの登録の後に、いよいよparse()メソッドを用いてパース(解析)する。このメソッドの中でイベントが起こるたびに、イベントハンドラが呼ばれるわけである。

次に、イベントハンドラとして定義されているメソッドをオーバーライドしていこう。

  1. setDocumentLocator()メソッドのLocatorオブジェクトは、現在パースしているソース内の位置(何行目の何文字目)を取得するためのものである。ここでLocatorオブジェクトを保存することによって、エラーなどが出たときに、そのエラーの発生したソースの位置を取得できる。ここでは、「locator」という名のインスタンス変数に保存している。

  2. XML文書が解析され最初に呼ばれるのが、startDocument()メソッドである。ここでは、各要素を保存するためにスタックを生成している。実は、このサンプルの場合はわざわざスタックを使う必要はないのだが、今後の応用を考慮してスタックを使用している。

  3. endDocument()は、一通りXML文書の解析が終わるタイミングで呼ばれる。今回は、特になにもしていない。

  4. 次のstartElement()は、新しい要素が見つかるたびに呼ばれる。まず、スタックに要素名をプッシュ(追加)する。次に要素名が「アイテム」であれば、「アイテム」要素の「id」属性の値を「bookid」インスタンス変数に保存しておく。

  5. ある要素が終了したときに呼ばれるのが、endElement()メソッドである。ここでは、スタックの一番上が最新の要素になるように、終わった要素名をスタックから取り出している。また、要素名が「アイテム」のときは、「bookid」をクリアする。

  6. characters()は、タグ以外の文字列が見つかるたびに呼ばれる、要素の内容の文字列を返すメソッドである。ここでは、保存しておいた「id」属性の値が指定の値と同値のときだけ、要素ごとに要素の内容を取り出している。それぞれの要素の判別はスタックの一番上にある要素名と比較して行っている。

  7. 最後にエラーを扱うErrorHandlerインターフェースのメソッドをオーバーライドする。error()とfatalError()は、それぞれW3CのXML 1.0の仕様で定義されている「Error」と「Fatal Error」が発生したときに呼ばれるメソッドであり、warning()は、そのどちらでもない場合に呼ばれる。warning()では、処理を中断させる必要はないので、「throw e」が抜けている(上記のリストではこの部分は省略されている。詳細はBookBySAX.java)。

このコードを実行した場合も、以下のようにDOMのサンプルと同様の結果が得られる。

F:\Oracle\JDeveloper 3.1.1\java1.2\jre\bin>java BookBySAX
22222
丸橋によるXML講座
丸橋玲奈
2

 今回のように、XML文書を一度たどれば済んでしまうような処理の場合は、ツリー構造をメモリ上に作成する必要のない高パフォーマンスのSAXを使うことによって、メモリの使用量をあまり気にする必要もなく、サイズの大きいXML文書を扱うことができる。ちなみに、オラクルのDOM Parserはメモリ上にDOMツリーを作成する際に、内部的にこのSAX Parserを利用している。

 DOMの場合は、XML文書がツリー構造としてメモリ上に保存されているので、それに対してトップダウンに処理をするために、自分がどの位置のノードを操作しているのかが把握しやすい。一方、SAXの場合は、現在どの位置を処理しているのかをつねに考えて、頭の中で把握していないと処理を書くことができない。ゆえに、いささかSAXのプログラムのほうが複雑に見えるのではないだろうか。

 最後に、オラクルのXML Parserはさまざまな仕様をサポートしているが、そのサポートする仕様とそのバージョンを以下にまとめておく。実装するときの参考にしてほしい。

■Oracle XML Parserが対応している仕様

  • W3CのXML1.0勧告に準拠
  • W3CのDOM Level1 1.0勧告に準拠
  • SAX1.0に準拠
  • W3CのNamespace in XML勧告に準拠
  • W3CのXSLT1.0勧告に準拠
  • W3CのXPath1.0勧告
Index
連載 XMLツールでプログラミング
  (1)Javaで利用するXML開発ツール
Oracle XML Developer's Kit(XDK)の紹介
  (2)DOMによるXML文書の操作
DOM APIを使ったプログラムの基礎
(3) SAXによるXML文書の操作
SAX APIを使ったXML文書操作の基礎と実践
  (4) Javaで文書作成クラスを生成する
DTDを元に、XML文書を作成するJavaクラスを作る
  (5) OracleからXML文書を出力する
データベースへの問い合わせ結果をXML文書にする



XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間