倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

XMLEventReader

XMLEventReader は XML 文書を解析して XML イベントを返すクラスです。 XMLEventReader を用いた XML 文書の解析の手順は、概ね以下のようになります:

  1. XMLInputFactory のインスタンスを取得する
  2. (必要なら)XMLInputFactory の設定をする
  3. XMLEventReader のインスタンスを取得する
  4. XMLEventReader のインスタンスを用いて XML 文書の解析を行う
  5. XMLEventReader を閉じる

簡単なサンプルは次のようになります(引数の java.io.Reader オブジェクトから解析対象の XML 文書を読み込みます):

public void analyze(Reader input)throws XMLStreamException{
    // 1. XMLInputFactory のインスタンスを取得する
    XMLInputFactory factory = XMLInputFactory.newInstance();
        
    // 2. (必要なら)XMLInputFactory の設定をする
    factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);
        
    XMLEventReader reader = null; 
    try{
        // 3. XMLEventReader のインスタンスを取得する
        reader = factory.createXMLEventReader(input);
            
        // 4.  XML 文書の解析を行う
        while(reader.hasNext()){
            XMLEvent event = reader.nextEvent();
            
            // ここで各 XML イベントに対する処理を行う
            System.out.println(event.toString());
        }
            
    }finally{
        // 5. XMLEventReader を閉じる
        if(reader != null)
            reader.close();
    }
}

各ステップについて少し詳しく見ていきましょう。

XMLInputFactory のインスタンスを取得する、設定をする


これは SAXParserFactoryDomcumentBuilderFactory を扱う際に行った手順と同じです。 XMLInputFactory が XMLEventReader(と XMLStreamReader)のファクトリ・クラスとなります。 XMLInputFactory のインスタンスは static メソッド XMLInputFactory#newInstance() から取得し、設定が必要なら、この XMLInputFactory オブジェクトに設定を行います。

XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.IS_NAMESPACE_AWARE, true);

生成された XMLEventReader (と XMLStreamReader)に対しては設定を変更できない点に注意しましょう(設定された値を取得することはできます)。 設定できる項目(プロパティ)はそのうちに。

XMLEventReader のインスタンスを取得する


XMLEventReader のインスタンスは XMLInputFactory のメソッド XMLInputFactory#createXMLEventReader() によって生成します。

XMLEventReader reader = factory.createXMLEventReader(input);

createXMLEventReader() メソッドに渡せるオブジェクトのうち、解析対象の XML 文書を指定するものは次の4つです:

  • java.io.InputStream
  • java.io.Reader
  • javax.xml.transform.Source
  • javax.xml.stream.XMLStreamReader

これらのオブジェクトに加えて、エンコーディング(InputStream の場合)やシステム識別子(InputStream, Reader の場合)を指定する文字列も渡すことができます。

XML 文書の解析を行う


XMLEventReader は、java.util.Iterator を実装しているように、使い方は Iterator と同様です。 ただし、Iterator#next() メソッドは返り値が Object 型のため、XMLEvent オブジェクトなどを返す next() メソッド様のメソッドが定義されています:

メソッド名 返り値 返り値の型 備考
nextEvent() 次の XML イベント XMLEvent
nextTag() 次の StartElement または EndElement XMLEvent 次の StartElement または EndElement までに空白しかない場合はそれらを飛ばしますが、テキストなどがある場合は例外を投げます。
getElementText() 次のテキスト String 次のテキストが、子テキストのみを持つ要素の子テキストでなければ例外を投げます。 また、テキストを返した後に nextEvent() を呼び出すと、終了タグの次のイベントが返されます。
peek() 次の XML イベント XMLEvent XMLEventReader はその XML イベントを保持したままです。

nextTag() や getElementText() は、次のようなプロパティ風の XML ファイルに対して用いると便利です:

<book>
  <title>憲法への招待</title>
  <author>渋谷秀樹</author>
  <price>740</price>
  <isbn>4-00-430758-9</isbn>
</book>

この XML ファイルを解析して、プロパティを表示する簡単なサンプルコードは例えば次のようになります:

public void iterateProperties()throws Exception{

    XMLInputFactory factory = XMLInputFactory.newInstance();
    XMLEventReader reader = factory.createXMLEventReader(new FileReader("book.xml"));

    // <book> 要素まで処理を飛ばす
    while(true){
        if(reader.nextEvent().isStartElement())
            break;
    }
        
    while(true){
        XMLEvent next = reader.nextTag();

        // 次のプロパティがあるかどうかの判定
        if(next.isStartElement()){
            // プロパティがあった場合の処理
            StartElement start = next.asStartElement();

            // 要素名と子テキストを表示
            System.out.println(start.getName()+"="+reader.getElementText());
        }else{
            // 子要素がない場合はループから抜ける
            break;
        }
    }
            
    reader.close();
}

これを実行すると次のように表示されます:

title=憲法への招待
author=渋谷秀樹
price=740
isbn=4-00-430758-9

個々の XMLEvent の扱い方は以前の記事(こちらこちら)を参照。 次回に簡単なサンプルを書く予定。

★注意★

Attribute や Namespace などの XMLEvent がありますが、これは XMLEventReader の nextEvent() メソッドなどでは返されません。 属性や名前空間宣言を処理したい場合は、それぞれ StartElement の getAttributes(), getNamespaces() からそれらに対する Iterator を取得します*1

XMLEventReader を閉じる


java.io.InputStream, java.io.Reader のように、XMLEventReader も読み込みが終了した時点で関連するリソースを解放するために close() メソッドを呼び出す必要があります。 ただし、JavaDoc には「このメソッドは基本となる入力ソース*2を閉じません。」と書いてあるので、「基本となる入力ソース」はそのインスタンスを生成した側が責任もって閉じましょう。

上記の analyze(Reader) メソッドに対しては次のようにするのが望ましい方法です:

public void analyzeFile(String fileName)throws IOException, XMLStreamException{
    Reader reader = null;
    try{
        reader = new FileReader(fileName);
        analyze(reader);

    }finally{
        if(reader != null)
            reader.close();
    }
}

次のようなコードはテストコードなどだけにしておきましょう*3

public void analyzeFile(String fileName)throws Exception{
    analyze(new FileReader(fileName));
}

*1:SAX 解析では、属性は ContentHandler#startElement() の引数に渡される一方、名前空間宣言は ContentHandler#startPrefixMapping(), ContentHandler#endPrefixMapping() で通知されるのでした。

*2:XMLEventReader のインスタンスを生成する際に XMLInputFactory に渡した InputStream や Reader のことだと思います。

*3:FileReader のデフォルト実装では、読み込みが終了した時点で勝手に Reader が閉じられるようなので、このようなコードでも一応動きます。