HpricotからNokogiriに移行するときの罠(特にXML名前空間)
HpricotからNokogiriに移行しようとしていくつか罠にはまったのでメモしておきます。
基本的には
- require "hpricot" → require "nokogiri"
- Hpricot(html) → Nokogiri::HTML(html)
- Hpricot::XML(xml) → Nokogiri::XML(xml)
と書き換えるだけで、運が良ければそのまま動くと思います。
Nokogiri(text)というのもあるのですが、これはXMLかHTMLかを自動判定するらしく、失敗することもあるのでお勧めしません。
NokogiriはHpricotと違ってXML名前空間をきちんと解釈するので、XML名前空間を使ったXMLを解析する場合には注意が必要です。XML名前空間を使ったXMLというのは、以下のようにxmlnsなんとかというのが入っているやつです。
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:gs="http://schemas.google.com/spreadsheets/2006"> <entry> <gs:cell>hoge</gs:cell> </entry> </feed>
この例だとdoc.search("entry")とかdoc.search("gs:cell")では何も引っかかりません。それぞれ以下のように書く必要があります。
namespaces = { "atom" => "http://www.w3.org/2005/Atom", "gs" => "http://schemas.google.com/spreadsheets/2006", } doc.xpath(".//atom:entry", namespaces) doc.xpath(".//gs:cell", namespaces)
いくつか注意点。
- doc.search("gs:cell", namespaces)だとCSSセレクタだと思われてしまう(searchはXPathとCSSセレクタのどっちも受け付ける)ので、xpathメソッドを使う必要があります。
- XPathの文法だと単に"gs:cell"だと現在のノードの直接の子ノードしか対象にならないので、子孫ノードも対象にするには.//を付けます。
要するに面倒くさいです。こっちの方がXML的に正しい実装ではあるんでしょうけど…。XML名前空間って、やりたい事はわかるし、便利なときは便利なんでしょうけど(XHTMLの中に直接SVGを書くとか)、大抵のときは話を面倒くさくしているだけのような…。
という点が主な原因で、実はgoogle-spreadsheet-rubyはとりあえずNokogiriへの移行を見送りました。
- NokogiriはRuby 1.9のString#encodingをちゃんと設定してくれる(Encoding::UTF_8になる)
- Nokogiriの方が速いらしい
- MechanizeもNokogiriに移行したらしい
- Hpricotは作者(_why氏)が行方不明?
というあたりから、Nokogiriに移行した方がいいような気はするのですが。
2010/9/24追記: CSSセレクタ(cssメソッド)を使うと名前空間のゴチャゴチャがなくなる(見た目通りの名前空間を書けばいい)と教えてもらいました。上の例ではそれぞれdoc.css("entry"), doc.css("gs|cell")になります。名前空間の区切りが|になるのはちょっと慣れないですけど。google-spreadsheet-rubyは現在はNokogiriに移行し、この方法を使っています。あと、NokogiriのString#encodingはちゃんと何も指定しなくてもEncoding::UTF_8になるようになっていたので、本文を修正しました。