Scala は人気のある新しいプログラミング言語であり、JVM (Java™ Virtual Machine) 上で実行されます。Scala はバイトコードにコンパイルされるため、Java プログラミング言語を活用することができます。一方で Scala は、その構文を有効に活かせるシナリオでは Java コードに代わる強力な手段となります。そうしたシナリオの 1 つが XML の処理です。Scala を利用すると、構文解析された XML をいくつかの方法でナビゲートしたり処理したりすることができます。また Scala には XML に対するファーストクラスのサポートが最初から組み込まれているため、XML ストリングの作成や、プログラムによる DOM ツリーの作成が必要ありません。この記事では、こうした面での Scala の実際と、Scala によって XML の処理がいかに楽しいものになるかを説明します。

Michael Galpin (mike.sr@gmail.com), Developer, MichaelDKelly.com

Michael Galpin's photoMichael Galpin は 1998年から Web アプリケーションの開発に従事してきました。彼は、California Institute of Technology で数学の学位を取得しており、現在は、カリフォルニア州サンノゼにある eBay にアーキテクトとして勤務しています。



2008年 4月 22日

この記事では Scala プログラミング言語のバージョン 2.6.1 を使用しています。Scala は若い言語で変化が速いため、最新の情報をチェックしてください。この記事では Scala の構文とイディオムのいくつかを紹介しますが、読み進める上で Scala の知識は必要ありません。Scala には Java 仮想マシンが必要であり、ここでは JDK 1.6.0_04 を使用しますが、Scala には 1.5 以上であれば十分です。この記事では Java コードを作成するわけではありませんが、 Java 言語に慣れていることを前提とします。

※訳注: 「JDK 1.6.0_04」のリンク先 (http://java.sun.com/javase/downloads/index.jsp) からは、もう JDK 1.6.0_04 は入手できません。同バージョンが入手できるのは下記 URL になります。

http://java.sun.com/products/archive/j2se/6u4/index.html

XML を構文解析する

頻繁に使用する頭字語

  • API: application programming interface
  • DOM: Document Object Model
  • HTTP: Hypertext Transfer Protocol
  • JSON: JavaScript Object Notation
  • SAX: Simple API for XML
  • StAX: Streaming API For XML
  • XML: Extensible Markup Language

まず、Scala を利用して XML を構文解析する方法を調べましょう。大部分のプログラミング言語と同様、Scala にも XML を構文解析するための選択肢が複数あります。以下はどれも基本的な方法ですが、InfoSet/DOM ベースで表現する方法、イベントをプッシュ (SAX) またはプル (StAX) する方法、あるいは JAXB (Java Architecture for XML Binding) と同じようなデータ・バインディングによる方法などがあります。ここでは Scala の構文のさまざまなメリットを活用できることから、DOM ベースの操作を説明します。この説明に入る前に、どのような XML を構文解析するのか、その XML に対して何をするのかを理解する必要があります。そのためにはサンプル・アプリケーションを用意する必要があります。


サンプル・アプリケーション: FriendFeed

FriendFeed は人気の高い、2008年の新しい Web サービスの 1 つです。FriendFeed を利用すると、他のサービスに関するユーザーのアクティビティーを集約することができます (他のサービスの例としては、さまざまなブログ・サービスやインスタント・メッセージ・サービス、YouTube、Flickr、Twitter などがあります)。そして FriendFeed は、このようにして集約したものから 1 つのデータ・フィードを作成します。この集約は、個別に行うことができます。つまり指定した人に関するアクティビティーを集約することができます。さらに興味深いものとして (あまり使い道はないかもしれませんが)、FriendFeed の公開フィードがあります。この機能を利用すると、全 FriendFeed ユーザーのすべての公開アクティビティーを集約することができます。FriendFeed には個々のフィードと公開フィードの両方にアクセスするための API が用意されています。ここでは公開フィードへのアクセスと構文解析を行うアプリケーションを作成します。


Java ライブラリーを活用する

最初に必要なことは、FriendFeed の公開フィードにアクセスすることです。そのための URL は http://friendfeed.com/api/feed/public です。このフィードはデフォルトで最新の 30 エントリーのデータを JSON フォーマットで表示します。フォーマットを XML に変更するためには、format=xml というクエリー・ストリング・パラメーターを追加します。エントリーの数を (例えば) 100 に変更するためには、num=100 というクエリー・ストリング・パラメーターを追加します。すると、あとはこの URL にアクセスすればよいだけです。これを Java コードで行うのは簡単です。ということは Scala コードでも簡単に行えるということです。FriendFeed の公開フィードにアクセスするためのコードを見てください (リスト 1)。

リスト 1. FriendFeed にアクセスする
object FriendFeed {
  import java.net.{URLConnection, URL}
  import scala.xml._ 
  def friendFeed():Elem = {
    val url = new URL("http://friendfeed.com/api/feed/public?format=xml&num=100")
    val conn = url.openConnection
    XML.load(conn.getInputStream)
  }
}

ここでは最初に 2 つのコアとなる Java クラスをインポートしていることに注意してください。Scala は、HTTP 接続を開くような作業には独自の API を使って苦労するようなことはしません。そのような場合には、Scala は単純に Java の API を利用できるからです。Scala には同じパッケージから複数のクラスをインポートするためにいくつかのショートカットが用意されていることに注目してください。その次の行は Scala のコアの XML クラスをインポートしています。アンダーバーは Java でのアスタリスクのようなものであり、scala.xml パッケージの中にあるすべてのクラスをインポートします。

したがって、FriendFeed に対する HTTP 接続を開く際には Java の API を利用します。今度は Scala の XML オブジェクトを使って構文解析を行います。ここで、いくつか興味深いことに気付きます。第 1 に、XML が Scala のオブジェクトであることです。つまり XML はシングルトン・オブジェクトです。Scala には静的なメソッドやフィールド、初期化子がありません。その代わり何かを (クラスではなく) オブジェクトとして定義することができ、それがクラスのシングルトン・インスタンスになります。シングルトン・オブジェクトのメソッドにアクセスするための方法は、静的なメソッドを呼び出す方法と似ています。これが、XML.load の中で行われていることです。XML.load が Scala オブジェクトのメソッドであるにもかかわらず、Java オブジェクト (java.io.InputStream) をパラメーターに取っていることに注目してください。これを見るだけでも、いかに Scala と Java との関係が近いかがわかります。最後に、return 文がないことに注意してください。Scala では return 文はオプションです。return 文がない場合には、そのメソッドの中の最後の文の評価結果が返されます (ただし評価結果があり、Scala がコンパイル・エラーを返さない場合です)。これで、リスト 1 のメソッドには簡単にアクセスすることができます (リスト 2)。

リスト2. friendFeed メソッドにアクセスする
val feedXml = friendFeed

friendFeed メソッドへの呼び出しに対して括弧を使う必要がないことに注意してください。また、ここでは Scala の型推論も利用しています。feedXml の型が何であるかを宣言する必要はありません。friendFeed メソッドの戻り型から型を推論できるからです。リスト 1 を見直してみると、こうした構文ショートカットも使われていることがわかります。気が付く点の最後として、構文解析された XML オブジェクトが val として宣言されています。これによって、この XML オブジェクトは (Java コードでのストリングのように) 不変オブジェクトになりますが、これは Scala では一般的なことです。XML を不変オブジェクトとして持つことには無数の利点がありますが、DOM の appendChild API を使うことに慣れていると、Scala での方法に慣れるまで時間がかかるかもしれません。これで FriendFeed からの XML を構文解析できたので、この XML を Scala を使って自由自在に加工することができます。


ナビゲーションとパターン・マッチング

多くのプログラミング言語では XML を DOM ツリーとして表現します。この方法には多くの利点がありますが、プログラムでツリーをトラバースして XML 文書からデータを抽出しようとすると、非常に手間がかかります。Java 技術には XPath 構文を活用するライブラリーがあります。Scala でも同じような方法を使いますが、Scala での方法にはいくつかの利点があります。Scala には関数型言語の側面が数多くあります。Scala には (+ や * のような) 演算子はなく、+ や * のような記号は、通常の数値加算や減算などを行う関数を定義するために使われます。これはまた、任意の型に対して演算子を定義できるということでもあります (演算子は実際には単なる関数にすぎないため)。そして C++ などの言語での演算子のオーバーロードよりも、はるかに強力です。XPath に関して言えば、XPath 構文の一部を直接 Scala の中で使うことができます。XPath は単に関数の呼び出しに変換されるだけだからです。

こうしたことを念頭に、FriendFeed から得られる XML がどのようなものかを調べてみましょう。リスト 3 はその一例を示しています。

リスト 3. FriendFeed の XML の例
<feed>
    <entry>
        <updated>2008-03-26T05:06:36Z</updated>
        <service>
            <profileUrl>http://twitter.com/karlerikson</profileUrl>
            <id>twitter</id>
            <name>Twitter</name>
        </service>
        <title>Listening to Panic at the Disco on Kimmel</title>
        <link>http://twitter.com/karlerikson/statuses/777188586</link>
        <published>2008-03-26T05:06:36Z</published>
        <id>f18ebf10-06be-98e2-6059-fa78fa44584b</id>
        <user>
            <profileUrl>http://friendfeed.com/karlerikson</profileUrl>
            <nickname>karlerikson</nickname>
            <id>f294a86c-e6f3-11dc-8203-003048343a40</id>
            <name>Karl Erikson</name>
        </user>
    </entry>
    <entry>
        <updated>2008-03-26T05:06:35Z</updated>
        <service>
            <profileUrl>http://twitter.com/asfaq</profileUrl>
            <id>twitter</id>
            <name>Twitter</name>
        </service>
        <title>@ceetee lol</title>
        <link>http://twitter.com/asfaq/statuses/777188582</link>
        <published>2008-03-26T05:06:35Z</published>
        <id>d4099bb0-8186-5aa1-ce1f-672246c0fe9c</id>
        <user>
            <profileUrl>http://friendfeed.com/asfaq</profileUrl>
            <nickname>asfaq</nickname>
            <id>41e24568-ee6b-11dc-a88d-003048343a40</id>
            <name>Asfaq</name>
        </user>
    </entry>
    <entry>
        <updated>2008-03-26T05:06:31Z</updated>
        <service>
            <profileUrl>http://twitter.com/chrisjlee</profileUrl>
            <id>twitter</id>
            <name>Twitter</name>
        </service>
        <title>sleep..</title>
        <link>http://twitter.com/chrisjlee/statuses/777188561</link>
        <published>2008-03-26T05:06:31Z</published>
        <id>8c4ec232-3ad5-28e1-16c0-00a428294c9c</id>
        <user>
            <profileUrl>http://friendfeed.com/chrisjlee</profileUrl>
            <nickname>chrisjlee</nickname>
            <id>5af39ad4-53b6-45d8-ae25-ef7c50fe9568</id>
            <name>Chris</name>
        </user>
    </entry>
    <entry>
        <updated>2008-03-26T05:06:49Z</updated>
        <service>
            <profileUrl>
                http://www.google.com/reader/shared/09566745492004297397
            </profileUrl>
            <id>googlereader</id>
            <name>Google Reader</name>
        </service>
        <title>Poketo First Editions Show!!</title>
        <link>
            http://www.poketo.com/blog/2008/03/24/poketo-first-editions-show/
        </link>
        <published>2008-03-26T05:06:49Z</published>
        <id>4caefceb-d71c-59c9-8199-45c5adbc60f2</id>
        <user>
            <profileUrl>http://friendfeed.com/misterjt</profileUrl>
            <nickname>misterjt</nickname>
            <id>e745cc8a-f9e4-11dc-a477-003048343a40</id>
            <name>Jason Toney</name>
        </user>
    </entry>
</feed>

このアプリケーションの場合、最初にサービスごとにユーザーのリストを取得します。そこでまず、関心対象のサービスのみにフィードをフィルタリングします。これを Scala で行うための方法をリスト 4 で見てみましょう。

リスト 4. サービスを元にフィードをフィルタリングする
def filterFeed(feed:Elem, feedId:String):Seq[Node] = {
   var results = new Queue[Node]()
   feed\"entry" foreach{(entry) =>
     if (search(entry\"service"\"id" last, feedId)){
       results += (entry\"user"\"nickname").last
     }
   }
   return results
 }
 
 def search(p:Node, Name:String):Boolean = p match {
   case <id>{Text(Name)}</id> => true
   case _ => false
 }

filterFeed という関数は、サービスの XML 要素 (フィード) と ID を引数として取り込み、最初に results という、XML Node の Queue を作成します。Queue は Java での List や Map のように、パラメーター化されています。Scala では (Java プログラミングで使われる不等号括弧の代わりに) 大括弧を使ってジェネリック型を定義します。feed\"entry" の行は XPath 風の式です。バックスラッシュは、実は scala.xml.Elem クラスのメソッドです。この行によって、指定された名前を持つすべての子ノード (つまりフィードの中にあるすべての <entry> 要素) が scala.xml.NodeSeq クラスのインスタンスとして返されます。このクラスは Seq[Node] を継承していて、Seq であるため、クロージャーをパラメーターに取る foreach メソッドを持っています。

(entry) => ... という表記は、エントリーとして表現される 1 つのパラメーターを取るクロージャーを示します。このクロージャーでは、再度 XPath 風の式、entry\"service"\"id" を使って、Node というエントリーからサービスの ID を抽出します。これを search という関数に渡し、このメソッドに渡されたフィードの ID と比較します。この関数の本体は、この後すぐに説明します。比較の結果一致した場合には、そのエントリーを作成したユーザーのニックネームを results キューに追加します。このキュー・オブジェクトが演算子のような += を持っていることに注目してください。この += も実際にはこのキュー・オブジェクトの関数にすぎません。また、Scala の XPath 風の構文をもう一度使って、このユーザーのニックネームである Node を抽出しています。

今度は search 関数を見てみましょう。この関数は、Scala の最も強力な機能の 1 つである、パターン・マッチングを使っています。この場合には入力ノードを id という名前のノードと比較しています (このノードは search 関数に渡される Name ストリングで構成される子テキスト・ノードを持っています)。マッチしたものが見つかると、search 関数は真を返します。case _ 構文はそれ以外のすべての突き合わせを行います。ここでも _ が Scala のワイルドカードとして使われています。case _ のような文は、Java や C++ のコードでの case 文でのデフォルト節に似ています。これは Scala でのパターン・マッチングの強力さを示す、非常に簡単な例です。次に、XML 構造を作成するための方法を調べてみましょう。


パターン・マッチングを使って XML を作成する

このアプリケーションでは、FriendFeed の公開フィードから抽出したすべてのユーザーのニックネームのための新しい XML 構造を作成する必要があります。そのための方法はいくつかありますが、ここでも再度パターン・マッチングを利用する方法を説明します。リスト 5 に示す関数を見てください。

リスト 5. パターン・マッチングを使うビルダー関数
def add(p:Node, newEntry:Node ):Node = p match {
   case <UserList>{ ch @ _* }</UserList> => 
     <UserList>{ ch }{ newEntry }</UserList>
}

このパターンによって、どのような子を持つ UserList 要素に対しても突き合わせが行われます。そして同じ子を持つ新しい UserList 要素と、既存の子に続く追加の子が返されます。これは機能的には DOM 仕様の appendChild イディオムと同じです。しかし appendChild イディオムとは根本的に異なり、オリジナルの Node は変更されません (オリジナルの Node は不変であるため、変更することはできません)。その代わりに新しい Node が作成されて返されます。この方法は、DOM を使って行う等価な操作よりも大量にメモリーを使用する可能性があります。では Scala を使って XML 構造を作成するための、他の方法をいくつか調べてみましょう。


XML を作成する

Scala の自然な XML 構文は、新しい XML 文書を作成する際に最も威力を発揮します。その最初の一例として、先ほど作成した UserList の構造を、関連するサービスのノードの中にラップします。リスト 6 はそのコードを示しています。

リスト 6. Results サービスを作成する
def results(name:String, cnt:Int, elements:NodeSeq):Any = {
   if (cnt > 0){
     return <Service id={name}>{elements}</Service>
   } 
 }

Scala はネイティブで XML をサポートしているため、テンプレート型の構文を使って XML 構造の中に動的にデータを挿入することができます。この場合は渡される name ストリングを使って id 属性を設定します。渡される要素のシーケンスを使って作成対象の Service 要素の子ノードにします。ただし、これを行うのは cnt パラメーターが 0 よりも大きい場合のみであることに注意してください。cnt==0 の場合には、この関数は何も返しません。Scala では、この関数が Any を返すように指定することで、この問題を回避することができます。Scala での Any クラスは java.lang.Object と同じように、最初から存在しているクラスです。Scala には void 型がありませんが、void 型と等価な Unit という型があります。便利なことに Unit は Any を継承するため、ある関数が、ある時にはオブジェクトを返し、またある時には何も返さないようにすることができます。

これを見るとわかるように、Scala の XML 構文の中に動的データを混在させると非常に強力です。別の例として、stats という XML 文書を作成しましょう。この例はフィードの中での各サービスの発生回数を記述する XML を示しています。このコードをリスト 7 に示します。

リスト 7. Stats XML を作成する
def stats(map:HashMap[String,Int]):Node = {
   var nodes = new Queue[Node]()
   map.foreach{(nvPair) =>
     nodes += <Service id={nvPair._1} cnt={nvPair._2.toString}/>
   }
   return <Stats>{nodes}</Stats>
}

この関数は、サービスの名前をキーとし、FriendFeed の中でサービスが発生する回数を値とする HashMap を想定しています。この関数は、おなじみの foreach によるクロージャー・スタイルを使って HashMap に対して繰り返しの処理を行います。次に HashMap から得られる名前と値のペアを使って新しいノードを作成し、このノードを Node の Queue に追加します。次に Stats 構造を作成し、動的データとして単純に Node の Queue の中に入れると、この構造が評価されて XML 構造になります。これですべての関数を用意できたので、あとはこのプログラムをテストできるように、プログラムを駆動するためのものが必要なだけです。


実行とテスト

プログラムを実行する前に、このプログラムを駆動するためのコードを追加する必要があります。そのために、Java プログラミングの場合と同じように main メソッドを作成します (リスト 8)。

リスト 8. FriendFeed の main
def main(args:Array[String]) = {
    val feedXml = friendFeed
    var map = new HashMap[String,Int]
    args.foreach{(serviceName) =>
      val filteredEntries = filterFeed(feedXml, serviceName)
      var users:Node = <UserList/>
      filteredEntries.foreach{(user) =>
        users = add(users, user)
      }
      map += serviceName -> filteredEntries.length
      println(results(serviceName,filteredEntries.length,users))
    }
    println(stats(map))
}

このメソッドによって FriendFeed が作成されます。このメソッドはコマドライン・パラメーターを取り込んで、どのサービスを使ってユーザーの発見と stats の計算を行うのかを判断します。この構文が Java の構文にとてもよく似ていることに注目してください。main 関数は、args という String 配列を取り込みます。このプログラムは stats 文書に対する HashMap と各サービスに対する UserList 文書を作成します。次に、各 UserList と stats 文書を出力します。このプログラムを実行するためには、scalac FriendFeed.scala を実行してこのプログラムをコンパイルし、scala FriendFeed によりプログラムを実行します (リスト 9)。

リスト 9. プログラムを実行する
$ scalac FriendFeed.scala
$ scala FriendFeed googlereader flickr delicious twitter blog
<Service id="twitter"><UserList><nickname>ntamaoki</nickname>
<nickname>terrazi</nickname><nickname>ntamaoki</nickname>
<nickname>terrazi</nickname><nickname>ntamaoki</nickname>
<nickname>parodi</nickname><nickname>trevor</nickname>
<nickname>cindy</nickname><nickname>christinelu</nickname>
<nickname>clint</nickname><nickname>savvyauntie</nickname>
<nickname>44gi</nickname></UserList></Service>
<Serviceid="blog"><UserList><nickname>nechipor</nickname>
<nickname>mdolla</nickname><nickname>kyhpudding</nickname>
<nickname>hanayuu</nickname><nickname>hanayuu</nickname>
</UserList></Service><Stats><Service cnt="12" id="twitter">
</Service><Service cnt="0" id="delicious"></Service><Service 
cnt="0" id="flickr"></Service><Service cnt="0" id="googlereader">
</Service><Service cnt="5" id="blog"></Service></Stats>

もちろん、コマンドライン引数などに別のサービス名を選択することもできます。Scala には、XML を適切な空白、タブ、フォーマットで出力するための pretty-printer クラスもいくつか用意されています。また、ファイルなどのストリームに書き戻すための XML ライターもあります。Scala を使って行いたいとユーザーが思うような一般的な機能はすべてサポートされており、また Scala 特有の風変わりな機能もサポートされています。


まとめ

Scala は多くの人から、Java プログラミングの進化のステップと見なされています。XML は既に非常に重要な技術要素となっているため、XML のサポートが構文の中に組み込まれているプログラミング言語を使うのはごく自然なことです。それを実現するものがまさに Scala です。Scala を利用することで、多くの複雑な作業を容易に行えるようになります。この記事で行ったさまざまなことを調べ、同じことをするために Java コードではどれほど多くのなコード行が必要になるかを考えてみてください。


ダウンロード

内容ファイル名サイズ
Article example codefriendfeed.example.zip1KB

参考文献

学ぶために

製品や技術を入手するために

  • FriendFeed の公開フィードにアクセスしてください。この URL にアクセスすると、JSON フォーマットでデータを表示することができ、また最新の 30 エントリーを表示することができます。
  • Scala のサイトから Scala をダウンロードしてください。
  • 皆さんの次期開発プロジェクトを IBM trial software で構築してください。developerWorks から直接ダウンロードすることができます。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Java technology
ArticleID=311778
ArticleTitle=Scala と XML
publish-date=04222008