Ruby1.9のCGI.unescapeHTMLにて実体参照の戻しを行う場合には変換元の文字列のエンコーディングを考慮する必要がある
Ruby1.9で遊び始めた。WebのAPI叩いていたら、実体参照が含まれる文字列をunescapeHTMLしようとしても上手くいかない。例えば、""のような形式の実体参照。
Ruby1.8の場合には問題とならなかったケースだった。cgi.rbのソースコードを読んで、ようやく原因が分かった。
cgi/util.rb@1.9.2p180に含まれる、一節を抜粋する。
def CGI::unescapeHTML(string) enc = string.encoding #(中略) string.gsub(/&(amp|quot|gt|lt|\#[0-9]+|\#x[0-9A-Fa-f]+);/) do match = $1.dup case match #(中略) when /\A#x([0-9a-f]+)\z/i n = $1.hex if enc == Encoding::UTF_8 or enc == Encoding::ISO_8859_1 && n < 256 or asciicompat && n < 128 n.chr(enc) else "&#x#{$1};" end else
上記のwhen節は正規表現で"�"形式を抜き出したあとの処理になる。encとは、引数stringのencodingを示し、この入ってくるstringのエンコーディングがUTF-8か、それ以外で処理が異なる。
Ruby1.9ではそれぞれの文字列にそれぞれのエンコーディングが関連付けられる。よって引数としてのstringにASCIIがエンコディングとなっている文字列が入っているのではないか、と考えられる。
#!/usr/bin/env ruby require 'cgi' print "RUBY_VERSION ", RUBY_VERSION,"\n\n" word = "通常" print "encoding: ", word.encoding, "\n" print "word: ", CGI.unescapeHTML(word), "\n\n" word_utf8 = word.encode("UTF-8") print "encoding: ", word_utf8.encoding, "\n" print "word: ", CGI.unescapeHTML(word_utf8), "\n\n"
上記に検証コードを示す。下記に実行結果を示す。
$ ruby unescape.rb
RUBY_VERSION 1.9.2
encoding: US-ASCII
word: 通常
encoding: UTF-8
word: 通常
推測のとおり、US-ASCIIを入れてしまっていたようだ。文字コードを利用して整数から文字に変換するためには、当然、変換先のエンコーディングが必要になる。US-ASCIIのようなエンコーディング指定では、無理に変換しようとせずに、無視される。
Ruby1.9ではプログラムの冒頭にてリテラル文字列のエンコーディングに何を使うかの指定が行える。そのため通常は問題にならなそうだ。
# -*- encoding: utf-8 -*-
今回のケースでは、WebのAPIを叩く過程のどこかでASCIIエンコーディングの文字列が返ってきたようだ。どこで入っていたのかについては確認をしていない。
あまりにもアホなミスなのか、ネット上にunescapeHTMLについての事例の情報がなかった。恥ずかしいが、書いておこうと思う。