Nokogiriに渡す前の文字コード判別
Nokogiriが確実に処理できるように、htmlを先に決め打ちでutf-8に変換する方法を模索してみた。
方針としては、
- httpヘッダのcharsetは中身と一致しているとは限らないため参照しない。
- metaタグのcharsetは信用する。
- metaタグが無かったら自動判定。
- 文字コードを変換したらcharsetが書いてあるmetaタグを抜く。
という方向で文字コード判定関数を書いた。判定は適当に試したところ特に問題はなかった。
次に、検出したコードを元にUTF8に変換するのだが、
Encoding::UndefinedConversionError
に遭遇。
http://charset.7jp.net/sjis.html
googleでsjisと検索すると先頭に来るサイトですが0x81ADのコードで死ぬ。
Encoding::Shift_JISをEncoding::CP932に変えてみたが無駄であった。
(そもそも文字が無いところか?)
で、調べてみると
http://d.hatena.ne.jp/kitamomonga/20090701
に
Ruby 1.8 の String#tosjis などは「変換先の文字エンコーディングにない文字は切り捨てるか適当に変換」という処理をしていましたが、Ruby 1.9 の String#encode は変換先にない文字があった場合とりあえず例外を出します。
という記述が。
まさに探していた情報が見つかったので解決。
ついでにString#encodingの:undefと:replaceの技をいただきました。
#!/usr/bin/env ruby # coding: utf-8 # 文字コードを判別してUTF-8に変換する require 'strscan' require 'open-uri' require 'kconv' # metaタグを探してcharsetを返す。見つかった場合はmetatagのバイト位置も返す。 def detect_encode(ascii8) s=StringScanner.new(ascii8) while true do # 捨て s.scan(/[^<]*/) # tag pos=s.pos tag=s.scan(/<([^>]*)>/i) if not tag then break end if tag[1, 4].downcase=='meta' then is_ready=false tag.scan(%r!([\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*))!i) do |m| key=m[0] value=m[1] or m[2] or m[3] if is_ready then charset=value.match(/charset=([\w-]*)/i)[1] # 後でmetaタグを捨てたり書き換えるために場所も返す return Encoding::find(charset), pos, tag.bytesize end if key.downcase=='http-equiv' and value.downcase=='content-type' then is_ready=true else break end end end end return Kconv.guess(ascii8), 0, 0 end if $0 == __FILE__ then if ARGV.empty? then html=open('sjis.html', 'r:ascii-8bit').read else html=open(ARGV[0], 'r:ascii-8bit').read end encoding, pos, len=detect_encode(html) p encoding puts html[pos, len] if encoding==Encoding::Shift_JIS then # 決め打ち encoding=Encoding::CP932 end utf8=html.encode(Encoding::UTF_8, encoding, #:undef => :replace, :replace => '・') # 全角はよろしくない :undef => :replace, :replace => '_') open('utf8.html', 'wb').write utf8 end