ついにベールを脱いだJavaFX

第11回 通信と非同期処理

この記事を読むのに必要な時間:およそ 12 分

パーサを使って解析

今までのサンプルでドキュメントを取得できることがわかりました。しかし,ドキュメントを取得するだけでは,その情報を活用することができません。

そこで,取得したドキュメントをパースして必要な情報を取り出してみましょう。JavaFXではXMLとJSONをパースできるjava.data.pull.PullParserクラスが提供されています。ここでは,このクラスを使用してFlickrの更新情報を表すAtomをパースし,リストにして表示してみます。

FlickrのAtomはリスト3のようになっています。

リスト3 Flickr Atomの例

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
      xmlns:flickr="urn:flickr:"
      xmlns:media="http://search.yahoo.com/mrss/">

	<title>Uploads from yuichi.sakuraba</title>
	
        <<省略>>
    
	<entry>
		<title>Entremet Montebello, Pierre Hermé, Nihonbashi Mitsukoshi</title>
		<link rel="alternate" type="text/html" href="http://www.flickr.com/photos/skrb/3224086417/"/>
		<id>tag:flickr.com,2005:/photo/3224086417</id>
		<published>2009-01-25T07:08:47Z</published>
		<updated>2009-01-25T07:08:47Z</updated>
      <dc:date.Taken>2008-12-22T20:39:03-08:00</dc:date.Taken>
		<content type="html"> ... &lt;img src=&quot;http://farm4.static.flickr.com/3531/3224086417_896092041b_m.jpg&quot; ... </content>
        
        <<省略>>
        
        
	</entry>
 
        <<省略>>
 
</feed>

<feed>タグの中に複数の<entry>タグがあり,そこにアップロードされたイメージの情報が記述されています。<content>タグのテキストは長いため一部省略してしまいましたが,htmlでイメージのタイトルなどが表記されており,イメージのURLも含まれています。

ここでは,イメージのタイトルとイメージのURLを抽出して表示するアプリケーションを作成します。アプリケーションの名前はFlickrAtomReaderとしました。

以下にAtomの読み込みとパースを行うAtomReaderクラスのAtom読み込み部分を示します。

リスト4

public class AtomReader {
    public-init var url: String;
    
    public function read(): Void {
        var request = HttpRequest {
            location: url

            // 入力の解析はparse関数で行う
            onInput: parse

            // エラー処理
            onException: function(ex: Exception) {
                println("exception: {ex.getMessage()}");
            }
        };
        request.enqueue();
    }

read関数では,HttpRequestオブジェクトを生成し,読み込みを行うためenqueue関数をコールしています。onInputアトリビュートではparse関数に処理を委譲しています。

では,そのparse関数を見てみましょう。

リスト5

    // 処理が終了した後にコールするコールバック関数
    public var onDone: function(:PhotoInfo[]): Void;
 
    // サムネイルのURLを取り出すための正規表現
    def pattern = java.util.regex.Pattern.compile("http://farm.*_m.jpg");
 
    // Atomのパース
    function parse(stream: InputStream) {
        var infos: PhotoInfo[];
        var info: PhotoInfo;
        var inEntry: Boolean;
 
        var parser = PullParser {
            documentType: PullParser.XML;
            input: stream
            
            onEvent: function(event: Event) {
                if (event.type == PullParser.START_ELEMENT) {
                    if (event.qname.name == "entry") {
                        // <entry>の開始
                        // 新たなPhotoInfoオブジェクトを生成する
                        inEntry = true;
                        info = PhotoInfo {};
                    }
                }
                if (event.type == PullParser.END_ELEMENT) {
                    if (event.qname.name == "entry") {
                        // </entry>の場合
                        // 作成したPhotoInfoオブジェクトをシーケンスに追加
                        inEntry = false;
                        insert info into infos;
                    } else if (inEntry and event.qname.name == "title") {
                        // </title>の場合
                        // PhotoInfoオブジェクトのtitleを設定
                        info.title = event.text;
                    } else if (inEntry and event.qname.name == "content") {
                        // </content>の場合
                        // 正規表現を用いて,サムネイルのURLを取り出し,
                        // PhotoInfoオブジェクトのthumbnailUrlに設定
                        var matcher = pattern.matcher(event.text);
                        if (matcher.find()) {
                            var url = matcher.group();
                            info.thumbnailUrl = url.replaceAll("_m", "_t");
                        }
                    }
                }
            }
        }
        // パースの開始
        parser.parse();
        
        if (onDone != null) {
            // パースが終了したら,コールバック関数をコールする
            onDone(infos);
        }
        
        stream.close();
    }

PullParserクラスはイベント駆動型のパーサで,SAXと同じと考えることができます。

PullServerオブジェクトがパースするドキュメントの種類はdocumentTypeアトリビュートで指定します。ここには,XMLもしくはJSONを指定できます。デフォルトはXMLです。そして,inputアトリビュートに読み込みを行うInputStreamオブジェクトを指定します。

パース時のイベントはonEventアトリビュートに設定した関数で行われます。引数としてjavafx.data.pull.Eventオブジェクトが与えられます。パース処理はEventオブジェクトのtypeアトリビュートによって振り分けます。

ここでは,要素の開始を示すSTART_ELEMENTと要素の終わりを示すEND_ELEMENTを扱いました。START_ELEMENTでは,要素名がentryの場合,新たにPhotoInfoオブジェクトを生成し,entry要素のパース中であることを示すフラグinEntryをtrueにします。PhotoInfoクラスはtitleアトリビュートとthumbnailUrlアトリビュートだけを定義したクラスです。

そして,<title>要素のEND_ELEMENTで,内容をPhotoInfoオブジェクトのtitleアトリビュートに代入します。同じようにthumbnailUrlアトリビュートも設定しますが,<content>要素をそのままthumbnailUrlアトリビュートに代入することはできません。<content>要素で表されるHTMLの中で,イメージのURLはhttp://farmで始まる部分に相当します。そこで,正規表現を用いてこの部分を抽出しています。

ここで取得できるURLはhttp://farm4.static.flickr.com/3531/3224086417_896092041b_m.jpgのように,拡張子の前の部分が_mとなっています。Flickrでは,この部分がイメージのサイズを表しています。

_mの場合イメージの長辺が240ピクセルと少し大きいので,長辺が100ピクセルのイメージを表す_tに変更しています。

<entry>要素のEND_ELEMENTに達したときには,titleアトリビュートもthumbnailUrlアトリビュートも設定されているはずなのでPhotoInfoクラスのシーケンスに作成したPhotoInfoオブジェクトを追加します。

パースが終了したら,コールバック関数であるonDoneをコールします。

著者プロフィール

櫻庭祐一(さくらばゆういち)

横河電機に勤務するかたわらJava in the Boxにて新しい技術を追い続けています。JavaOneは今年で11年目。名実共にJavaOneフリークと化しています。