monthly gimite

試験運用中。

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はXPathCSSセレクタのどっちも受け付ける)ので、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になるようになっていたので、本文を修正しました。