IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  XML  >

XMLの論考: REXMLライブラリー

Rubyプログラミング言語におけるXML処理

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 中級

David Mertz, Ph.D (mertz@gnosis.cx), Author, Gnosis Software, Inc.

2002年 3月 01日

XMLの処理に立ち向かうには、少なくとも2つの姿勢があります。1つは、多くのプログラミング言語から呼び出することのできる標準的なAPIを採用するという姿勢です。2つ目は、XMLアプリケーションを開発するのに使用するプログラミング言語の力を借りてXML処理ライブラリーを作り出すという姿勢です。この連載の最初の方の記事で、David Mertz氏は、彼独自のPythonxml_pickle およびxml_objectify や、HaskellHaXml ライブラリーで、この第2のアプローチのいくつかのバージョンを検討しました。かなり新しく、しかも急速に発展しつつあるRubyプログラミング言語用のよく利用されるライブラリーも、この第2のアプローチを採用しています。この記事では、David氏が、Ruby Electric XML (REXML) という、Rubyの機能を活用してXML処理を実行するライブラリーを紹介します。REXML は、ストリーム・スタイルのSAXにも、ツリー・スタイルのDOMにも似ていますが、どちらの種類のAPIにも直接には制約を受けていません。

まず最初に、Ruby言語について紹介させてください。とはいえ、Rubyをご存じでない読者に十分な予備知識を得ていただくほどのことを書くにはスペースが足りないので、参考文献に紹介した記事を読むようにお勧めします。ただ、Ruby言語を独学で習得したプログラマーの一人として、この言語がなぜ興味深いのかをお伝えすることができます。Rubyは、「Perlは正しかった」と説明されてきたスクリプト言語のひとつです。その点は、Pythonを含むすべての新しいスクリプト言語もおそらく同じです。Rubyに関して言えば、この説明はいっそう真実味を帯びています。それは、Perlは間違っていたという意味ではなく(そんな批判を浴びせるプログラミング言語はありませんが)、RubyはSmalltalk風のOOPというきれいな土台を出発点にしているにもかかわらず、Perlの簡潔さとその手際のよさを存分に身に付けているという意味においてです。さらに (少なくとも私にはそう思えるのですが)、Rubyは、一部のPerlコードに見られる「実行上は問題ないが奇々怪々なコード」という特質を回避しながらも、簡潔さを実現しています。それと同時に、Rubyの多くのコード構造は、Pythonの各バージョンよりも直接的な「感じ」がします (全体の長さを本当に節約するわけではありませんが)。

REXML は、Sean Russell氏が記述したライブラリーです。これはRuby用の唯一のXMLライブラリーというわけではありませんが、人気のあるライブラリーで、純粋なRubyで記述されています (したがって、NQXML であり、Cで記述されたJadeライブラリーを包み込むXMLParser ではない) 。Russell氏は、REXMLの概説の中で次のようにコメントしています。

私の抱えていた問題はこうでした。私はobscifucatedな (原文のまま) APIが嫌いです。Java用のXMLパーサーAPIは、いくつも出回っています。そのほとんどはDOMかSAXに従っており、考え方の面では、次々と世に出てくる多くのJava APIと似たり寄ったりです。つまり、開発したAPIを自分では決して使う必要のない理論家たちによって設計されたに違いないということです。既存のXML APIは、概してひどいものです。それらのAPIは、非常にシンプルかつエレガントで強力なものになるように特に設計されたマークアップ言語を対象にしており、不愉快なほど肥大化した巨大なAPIを身にまとっています。最も基本的なXMLツリーの操作を実行するときでさえ、私はAPIの資料を参照しなければならないのが常でした。どの機能も直感的なものではなく、ほとんどすべての操作が複雑なものでした。

私はここまで大げさに表現するつもりはありませんが、基本的にはRussell氏の意見に賛成です。XML APIは実に凡庸で、それを使って何かを実行しようとすると、必要な作業が多すぎる場合がほとんどです。

簡単なことは簡単に

XML文書を取り扱っているプログラマーの8割がたが本当に望んでいるものは、単にデータを取り込んで、それを構造化されたデータとして簡単に操作する手段ではないでしょうか。DOMでそれを行うのは難しい作業で、SAXではもっと難しい作業です。以前のいくつかの記事の中で、私の開発したPythonxml_objectifyの明快さと簡明さについて紹介しました。ここで、ファイルaddress.xml を使用する簡単な例をもう一度取り上げたいと思います。これは、アドレス帳を記述するファイルです。


ネストされたデータをxml_objectifyを使って参照する方法
>>> from xml_objectify import XML_Objectify
>>> addressbook = XML_Objectify('address.xml').make_instance()
>>> print addressbook.person[1].address.city
New York

データの形式について少しのことを知っておく必要がありますが、それほど多くはありません (この記事で使用しているサンプル文書の入手先は、参考文献を参照してください)。まず、文書のルートがアドレス帳であること (しかし、要素名は必ずしも<addressbook> ではない) を知っている必要があります。また、文書に複数の人物の情報を列挙できることも知っている必要があります (とはいえ、1人分の情報しか格納されていなくても、何も問題はありません。その情報は、addressbook.person またはaddressbook.person[0] として参照できます) 。そして最後に、概念として、人物は住所を持ち、住所は「市」を持っているということも知っている必要があります。これだけ知っていれば十分に機能する のです!

それに比べて、DOM (OOP化されたXMLであると宣伝されている) の場合は、数々の試練を経験します。まず最初の課題は、ルート要素を参照することです。それには、少なくとも5種類の方法が頭に浮かんできます。


DOMを使ってXML文書のルートを特定する
>>> from xml.dom import minidom
>>> dom = minidom.parse('address.xml')
>>> dom.firstChild
<DOM Element: addressbook at 1811436>
>>> dom._get_documentElement()
<DOM Element: addressbook at 1811436>
>>> dom._get_firstChild()
<DOM Element: addressbook at 1811436>
>>> dom.getElementsByTagName('addressbook')[0]
<DOM Element: addressbook at 1811436>
>>> dom.childNodes[0]
<DOM Element: addressbook at 1811436>

さらに、どれがメソッドでどれが属性かを何とかして見極める必要があります(あるいは、マニュアルを手もとに置く)。ルート要素を取得するのであれば、._get_documentElement() メソッドがおそらく最善の選択でしょう。では、xml_objectify の例と同じように、2番目の人の「市」を取得したい場合はどうでしょうか。

ネストされたデータをDOMを使って参照する方法
>>> addressbook = dom._get_documentElement()
>>> print addressbook.getElementsByTagName('person')[1].\
.. getElementsByTagName('address')[0].getAttribute('city')
New York

この書き方はかなり冗長ですが、前の例をDOMに置き換えるとしたらこれが最も近いでしょう。.childNodes 属性の配列をダイレクトに使用すれば文字数を減らせるかもしれませんが、たとえば、<addressbook><person> 以外の子要素がある場合には、その方法は危険です。さらに、city がサブタグの内容ではなく要素の属性であるという肝心の詳細も知っている必要があります (この例のような基本的なデータでは、どちらのケースもあり得ます)。




上に戻る


REXMLをツリー・モードで使用する

REXML の目標は、十分に機能する ということです。だいたいのところ、かなりうまく機能します。実際、REXML は、XML処理の2種類のスタイル、つまり「ツリー」と「ストリーム」をどちらもサポートしています。前者はDOMが成し遂げようとしているものの簡略版であり、後者はSAXが成し遂げようとしているものの簡略版です。ではまず、ツリー・スタイルの処理を調べてみましょう。ここでは、前の例で取り上げたのと同じアドレス帳のデータを取り込むことにします。この例は、私が作成した修正版のeval.rb から取ったものです。標準的なeval.rb (Rubyのチュートリアルで使われる) は、複雑なオブジェクトから成る式を評価すると非常に長い結果を表示することがありますが、私の修正版では、エラーがなければメッセージは表示されません。


ネストされたデータをREXMLを使って参照する方法
 ruby> require "rexml/document"
ruby> include REXML
ruby> addrbook = (Document.new File.new "address.xml").root
ruby> persons = addrbook.elements.to_a("//person")
ruby> puts persons[1].elements["address"].attributes["city"]
New York

この式は、かなり自然です。.to_a() メソッドは、文書内のすべての<person> 要素の配列を作成します。これは、その他の場面でも便利に利用できます。要素は、DOMのノードに少し似ていますが、実際にはXML自体にもっと近いものです (そして、操作も簡単)。.to_a() に対する引数はXPathです。この例の場合は、文書内の任意の位置にあるすべての<person> 要素を指定しています。もし、第1レベルの位置にある要素だけを指定するのであれば、次のように書きます。


マッチする要素の配列を作成する
ruby> persons = addrbook.elements.to_a("/addressbook/person")

.elements 属性に対してもっと限定的なXPath式を指定することもできます。たとえば、次のとおりです。


ネストされたデータをREXMLを使って参照する別の方法
ruby> puts addrbook.elements["//person[2]/address"].attributes["city"]
New York

ここで注意する必要があるのは、XPathでは1をベースとするインデックスが使用され、0をベースとするインデックスを使用するRubyやPythonの配列とは違うという点です。したがって、上のように書いた場合、「市」を取得しているのは以前の例と同じ人物についてです。さらに、REXML の要素そのものを利用することにより、この人物の情報をもっと見ることができます。


REXMLを使ってXMLソースの要素を表示する
ruby> puts addrbook.elements["//person[2]/address"]
<address city='New York' street='118 St.' number='344' state='NY'/>
ruby> puts addrbook.elements["//person[2]/contact-info"]
<contact-info>
  <email address='robb@iro.ibm.com'/>
  <home-phone number='03-3987873'/>
</contact-info>

さらに別の使用例として、XPath式が複数の要素にマッチしても構いません。その点はperson の配列を定義する記述で既に見ましたが、複数の要素にマッチすることを強調する例として、次の書き方をご覧ください。


複数の要素にマッチするXPath式
ruby> puts addrbook.elements.to_a("//person/address[@state='CA']")
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/>
<address city='Los Angeles' street='Pine Rd.' number='1234' state='CA'/>

これとは対照的に、.elements 属性にインデックスを指定した場合は、マッチした最初の 要素だけが取得されます。


XPath式が最初の出現箇所にのみマッチするケース
ruby> puts addrbook.elements["//person/address[@state='CA']"]
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/>
ruby> puts addrbook.elements.to_a("//person/address[@state='CA']")[0]
<address city='Sacramento' street='Spruce Rd.' number='99' state='CA'/>

XPath式は、REXMLXPath クラスでも使用できます。このクラスには、.first().each().match() などのメソッドがあります。

REXML の要素に特有のメソッドの1つに、.eachイテレーターがあります。Rubyにはコレクションを操作するためのループ構造としてfor がありますが、Rubyプログラマーは概して、コード・ブロックに制御を渡してしまうイテレーター・メソッドの方を好みます。この後に示す2つのコード構造はどちらも等価ですが、2番目の構造の方が、Rubyではより自然なコードに見えるわけです。


REXMLにおいてXPath式にマッチする要素を反復する
                
ruby> for addr in addrbook.elements.to_a("//address[@state='CA']")
    |    puts addr.attributes["city"]
    | end
Sacramento
Los Angeles
ruby> addrbook.elements.each("//address[@state='CA']") {
    |    |addr| puts addr.attributes["city"]
    | }
Sacramento
Los Angeles




上に戻る


REXMLをストリーム・モードで使用する

「十分に機能する」という目的のためには、ツリー・モードのREXML が、おそらくRuby言語における最も簡単なアプローチでしょう。しかし、REXML は、SAXのさらに軽量な変種であるストリーム・モードも提供しています。SAXの場合、REXML は、XML文書に由来するデフォルトのデータ構造をアプリケーション・プログラマーに提供しません。その代わりに、「リスナー」または「ハンドラー」クラスが、文書ストリームのさまざまなイベントに応答する一式のメソッドを提供する役割を果たします。イベントには通常、タグの開始、タグの終了、要素テキストに遭遇、などがあります。

ストリーム・モードは、ツリー・モードで操作するよりもはるかに労力を要しますが、はるかに高速であるのが普通です。REXML のチュートリアルによると、ストリーム・モードは1500倍 高速であるとのことです。この件についてベンチマーク・テストは試みていませんが、この数字が当てはまるのは限られたケースだけではないかと思います (この記事で使用している小さな例では、ツリー・モードでも瞬時に処理されます)。いずれにしても、速度が問題になるケースでは、速度の違いが重要になるようです。

ここでは、「カリフォルニア州のデータを列挙する」という前と同じ例を実行する、ごく簡単な例を見てみましょう。この例を、もっと複雑な文書処理に拡張するのは、比較的簡単な作業です。


REXMLにおけるXML文書のストリーム処理
ruby> require "rexml/document"
ruby> require "rexml/streamlistener"
ruby> include REXML
ruby> class Handler
    |    include StreamListener
    |    def tag_start name, attrs
    |       if name=="address" and attrs.assoc("state")[1]=="CA"
    |          puts attrs.assoc("city")[1]
    |       end
    |    end
    | end
ruby> Document.parse_stream((File.new "address.xml"), Handler.new)
Sacramento
Los Angeles

ストリーム処理の例で注目するべき1つの点は、タグ属性が配列の配列として渡されるという点です。これは、ハッシュに比べると取り扱いが若干複雑になります (しかし、おそらくライブラリー内での作成はより高速です)。




上に戻る


結論

今回の記事では、DOM、SAX、およびXSLTの扱いにくいAPIに代わるもう1つの代替ツールを概観しました。以前のいくつかの記事で考察したxml_objectify、PYX、およびHaXml という選択肢と並んで、Rubyプログラマーは、困難さを示す急勾配の学習曲線を体験せず、XMLを手早く処理する方法をもう1つ手にしたことになります。



参考文献

  • 幸いにも、RubyのWebサイトに優れたチュートリアルが提供されています。このサイトにある言語リファレンスやその他の文書にも、一読の価値があります。

  • 私は、Rubyの作者まつもと ゆきひろ氏の書籍の英訳 『Ruby in a Nutshell』にも目を通しました。Rubyについて学習するプログラマーから見ると、この書籍は私のような初心者よりも、もう少し経験を積んだRubyユーザーに適しているように思います。また、日本語から英語への翻訳があまり上手ではないという印象を受けました。この書籍はリファレンス書としてたいへんよく編成されていますが、言語の微妙な部分について不明確に思える説明がかなりありました。

  • RubyとXMLに関するニュースとディスカッションのWebサイトをご覧ください。

  • この記事で使用したアドレス帳のサンプル・ファイルは、http://gnosis.cx/download/address.xmlにあります。


著者について

Photo of David Mertz

David Mertz氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ