Ruby 1.9.1とREXMLとXML宣言のエンコーディング - 2009-05-11 - ククログ

ククログ

株式会社クリアコード > ククログ > Ruby 1.9.1とREXMLとXML宣言のエンコーディング

Ruby 1.9.1とREXMLとXML宣言のエンコーディング

Ruby 1.9.1付属のREXMLではXML宣言のエンコーディングの扱いに問題があるためvalidなXMLでもパースできない場合があるという話です。

問題

Ruby 1.9では文字列や正規表現がエンコーディング情報を持つため、REXMLのように正規表現ベースでXMLをパースしている場合は、エンコーディングを適切に設定しないとパースに失敗することがあります。

例えば、tDiaryのseach-yahoo.rbプラグインがこの問題に遭遇しています。

原因

REXMLは内部でUTF-8を用いています。そのため、パース対象のXMLのエンコーディングをUTF-8に変換しながらパースします。この処理はREXML::SourceまたはREXML::IOSourceで行われます。

しかし、REXML::IOSourceに問題があり、UTF-8に変換しないままパースしてしまう場合があります。これは、入力XMLのエンコーディングがUTF-8に設定されていない、かつ、XML宣言のエンコーディングがUTF-8になっている場合です。ちなみに、REXML::Sourceではこの問題は起きません。

tDiaryのsearch-yahoo.rbでは入力XMLのエンコーディングがASCII-8BITでXML宣言のエンコーディングがUTF-8になっていたため問題に遭遇しました。

search-yahoo.rbではopen-uriを使って入力XMLをHTTP経由で取得しています。open-uriはContent-Typeを見て適切なエンコーディングを設定してくれますが、今回はcharsetが指定されていなかったとのことです。このため、open-uriで取得した入力XMLがASCII-8BITになっていました。

xml = open("http://.../xxx.xml") {|f| f.read}
xml.encoding # => ASCII-8BIT
document = REXML::Document.new(xml) # => パースエラー

解決法

この問題に遭遇してしまった場合は、以下のような解決法があります。

  • 入力XMLのエンコーディングをUTF-8に設定する。
  • REXML::IOSourceの代わりにREXML::Sourceを使う。
  • パッチ付きでバグ報告済みなので修正されるのを待つ。

入力XMLのエンコーディングをUTF-8に設定する場合は以下のようになります。

xml = open("http://.../xxx.xml") {|f| f.read}
xml.force_encoding("utf-8")
document = REXML::Document.new(xml)

REXML::Sourceを使う場合は以下のようになります。

xml = open("http://.../xxx.xml") {|f| f.read}
document = REXML::Document.new(REXML::Source.new(xml))

修正されるのを待つ場合は、修正されるまで待ってください。

まとめ

Ruby 1.9で正規表現ベースのコードがうまく動かない場合はマッチ対象の文字列のエンコーディングを確認しましょう。

ちなみに、REXML::IOSource#matchではエンコーディング関係のエラーを握りつぶしているため、実際に発生するREXML::ParseExceptionだけ見てもエンコーディングミスマッチがどこで起こっているかはわかりません。問題が発生したときは問題解決につながるエラーメッセージを提供したいものですね。