1. ホーム
  2. スクリプト・コラム
  3. ルビートピックス

RubyでXMLデータ処理ライブラリREXMLを使うための手引き

2022-01-31 11:12:20

REXMLをツリーとして使用する
REXMLは十分であることを意図しています。可能な限りの最大限の範囲において、うまく仕事をこなします。実際、REXMLは2つの異なるスタイルのXML処理、すなわち"tree"と"stream"をサポートしています。最初のスタイルはDOMが行おうとしていることをより単純化したものであり、2番目のスタイルはSAXが行おうとしていることをより単純化したものである。まず、ツリー型について見てみよう。先ほどの例と同じアドレス帳の文書を抽出したいとする。次の例は、私が作成した改良版 eval.rb のものです。標準の eval.rb (Ruby チュートリアルのリンク先) は、複雑なオブジェクトに対する式の評価に基づいて非常に長い計算を表示します - 私の eval.rb はエラーが発生しなければ応答しません。
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> 要素を識別します。もし、最初のレベルの要素だけが必要であれば、これを使うことができます。
一致する要素の配列を作成する

ruby> persons = addrbook.elements.to_a("/addressbook/person")


.elementsプロパティのオーバーロードされたインデックスとして、XPathをより直接的に使用することもできます。例えば
REXML を使ってネストされたデータを参照するもう一つの方法

ruby> puts addrbook.elements["//person[2]/address"].attributes["city"]
New York


XPathは、0ベースのインデックスを使用するRubyやPythonの配列と異なり、1ベースのインデックスを使用することに注意してください。つまり、都市を調べているのは同じ人であることに変わりはありません。REXMLを見て、XPathは0ベースのインデックスを使用するRubyやPythonの配列とは異なり、1ベースのインデックスを使用していることに注意してください。つまり、同じ人の都市を調べていることに変わりはないのです。次に
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='[email protected]'/>
 <home-phone number='03-3987873'/>
</contact-info>


さらに、XPathは1つの要素だけにマッチする必要はない。これは既にpersons配列の定義で見てきましたが、別の例でこの点を強調します。
複数の要素を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.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'/>


REXMLのXPathクラスには、.first(), .each(), .match()といったメソッドがあり、XPathアドレスを使用することもできます。
REXMLの要素に関するユニークなイディオムとして、.eachイテレータがあります。Rubyにはコレクションを操作するループ構造がありますが、Rubyプログラマはしばしばイテレータメソッドを使用してコードのブロックに制御を渡すことを好みます。次の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倍速いと言っています。ベンチマークは試していませんが、これは限定的なケースだと思われます(ツリーアプローチの私の小さな例も瞬時に実行されます)。いずれにせよ、速度がタイトになるのであれば、その差は大きくなりそうです。
上記の"List California Cities"の例と同じことをする非常にシンプルな例を見てみましょう。複雑な文書処理のためにこれを拡張するのは比較的簡単です。
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


ストリーム処理の例で注意すべき点は、タグのプロパティが配列のセットとして渡されることです。これは、ハッシュよりも処理に若干手間がかかります(しかし、おそらくライブラリでの作成はより高速です)。

コーディングの問題点
REXMLのすべてのテキストノードはUTF-8でエンコードされており、すべての呼び出しコードはこれを認識し、プログラム内でREXMLに渡される文字列はUTF-8でエンコードされていなければならない。

REXML は、テキストがどのようにエンコードされているかを常に正しく推測することはできないので、常に UTF-8 エンコードを仮定しています。また、REXMLは他のエンコーディングのテキストを追加しようとしても、警告を発しない。加筆者は、UTF-8のテキストを加筆していることを確認しなければなりません。標準的なASCIIの7ビットエンコーディングを追加しても問題ありません。ISO8859-1 テキストが使用されている場合、追加する前に UTF-8 に変換する必要があります。これは text.unpack("C").pack("U") を使って行うことができます。出力時のエンコーディングの変更は、Document.write() と Document.to_s() でのみサポートされています。特定のエンコーディングでノードを出力する必要がある場合、出力オブジェクトをOutputでラップする必要があります。

e = Element.new "<a/>"
e.text = "f\xfcr" # ISO-8859-1 '???'
o = ''
e.write( Output.new( o, "ISO-8859-1" ) )


サポートされているどのエンコーディングも、出力に渡すことができます。