アップデート:RSSからTwitterに自動投稿をしてくれるTwitterfeedなどのサービスがありますが、これらは下記の2.の「XMLはUTF8をそのままに残す」というのができなくて文字化けを発生させているようです。”日″などの記号はウェブブラウザだと正しく変換して画面に表示してくれますが、ほとんどのTwitterクライアントではこの変換をやらないためです。
丸一日、これで悩んでいました。なんとか解決したので、ここに記録します。
やりたかったこと
- UTF8化したデータをXMLに書き出す。
- XMLファイルはUTF8をそのままに残す。例えば”日本語” => “日本語”という変換はしない。
- 大きいXMLファイルを書き出したいので、XMLをすべてメモリに溜め込んでから書き出すのではなく、少しずつファイルに書き出す。
そんなに珍しいことをやろうという訳でもないので、簡単にできるかなと思ったのですが、これがなかなか大変でした。Ruby 1.9ではもう少し簡単になっているかもしれませんが、少なくともRuby 1.8では大変です。
その1:Ruby標準ライブラリのREXMLを使うという選択肢
採用せず
- まず、REXMLはバグが多い。例えばXMLをインデント整形するだけでバグ。
- 少しずつファイル書き出しはできない。
その2:Ruby on RailsのActiveSupportについてくるBuilder (version 2.1.2)
採用せず
- ファイルを少しずつ書き出すことができるのは大きなプラス。
- しかし”日本語” => “日本語”は起きる。
その3:Nokogiri
採用せず
- “日本語” => “日本語”は起きないというのは大きなプラス。
- しかしファイルを少しずつ書き出すことはできない。
その4:Builderの最新バージョン (Githubにある version 2.2.0以上)
採用。以下の感じで使いました。
[ruby]
$KCODE = ‘UTF8’
require ‘rubygems’
gem ‘bigfleet-builder’
require ‘builder’
x = Builder::XmlMarkup.new(File.open(“output_file.xml”, “w”), :indent => 1)
x.instruct!(:xml, :encoding => “UTF-8”)
1000.times do
x.product do
x.name(“日本語”)
end
end
[/ruby]
Railsでは処理速度を向上させるために、fast_xs gemがインストールされていればこれを読み込んでBuilderをパッチしています。しかしバージョン 2.2.0のBuilderはXmlBase#_escape内で、String#to_xsを引数付きで呼び出しているので、引数を取らないfast_xsのto_xsとコンパチではなくなっている感じです。
例えば
[ruby]
$KCODE = ‘UTF8’
require ‘rubygems’
gem ‘bigfleet-builder’
require ‘builder’
require ‘active_support’
x = Builder::XmlMarkup.new(File.open(“output_file.xml”, “w”), :indent => 1)
x.instruct!(:xml, :encoding => “UTF-8”)
1000.times do
x.product do
x.name(“日本語”)
end
end
[/ruby]
とすると、ArgumentError: wrong number of arguments (1 for 0) : method to_xs in xmlbase.rb at line 118
と怒られます。
これを解消するためには fast_xs を使わないようにmonkey patchします。
[ruby]
$KCODE = ‘UTF8’
require ‘rubygems’
gem ‘bigfleet-builder’
require ‘builder’
require ‘active_support’
class String
alias_method :to_xs, :original_xs if method_defined?(:original_xs)
end
x = Builder::XmlMarkup.new(File.open(“output_file.xml”, “w”), :indent => 1)
x.instruct!(:xml, :encoding => “UTF-8”)
1000.times do
x.product do
x.name(“日本語”)
end
end
[/ruby]
かなり美しくないのですが、これでようやくなんとかXMLがやりたいように書き出せるようになりました。