RubyでXMLデータ処理ライブラリREXMLを使うための手引き
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" ) )
サポートされているどのエンコーディングも、出力に渡すことができます。
関連
-
Railsにフィールド暗号化ストレージを実装
-
Mac OS XにRuby実行環境をインストールするための詳細な手順
-
Rubyにおけるフックメソッドとメソッド呼び出しへのフックの追加例
-
bundlerを使ったRuby環境のインストールと複数バージョンのgemの管理
-
Windowsでrubyとrailsをインストールする際に発生する問題点まとめ
-
Rubyのgemパッケージ管理およびgemソース構築のチュートリアル
-
Rubyの文字列と配列の最大化問題の考察
-
Rubyのプライベートとプロテクトを簡単にご紹介します。
-
Ruby on Railsにおける国際化の簡単な紹介
-
Rubyメタプログラミングの注目すべき点
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
Rubyの文法と言語機能のまとめ
-
Rubyのinstance_evalメソッドとclass_evalとの比較
-
Rubyのgemパッケージマネージャの使い方とbundlerによる複数バージョンのgemの管理
-
Ruby は REXML ライブラリを使って xml 形式のデータをパースする
-
RubyのTimeオブジェクトの共通機能まとめ
-
Rubyのデザインパターン。プログラミングにおけるアピアランスパターンの応用
-
Ruby+Watirの自動テスト環境とWindowsでのデータ読み込みについて
-
Rubyで配列とハッシュテーブルを使う
-
Rubyにおける継承とメッセージング
-
Rubyのモジュールに関する基礎知識