文字コードの変換

今まで文字コードを意識しなくても問題なかったんで、Encode.pmってのをあまり使ってなかったんですが、
WEBからコンテンツを拾ってきて、その文字コードを判別して、一意の文字コードに変換しないといけない場面が出てきたので、
Encode.pmとかその辺について色々調べてます。そのメモ書き程度に。

WEBでの文字コード

WEB上のコンテンツは、コンテンツ毎に異なる文字コードを使う事もできます。
文字コードの宣言を明示的にしないと、文字コードの特定が難しいです。
ブラウザ見た時に文字化けをしている時は、使用している文字コードの宣言を行っていない場合や、宣言した文字コードと異なる文字コードを使っている場合などです。
文字コードの宣言がない場合に使う文字コードは、ブラウザに依存します。

プログラムでの文字コード

例えば、RSSなどで配信しているデータを取得して一覧にする、というプログラムを作るとします。
最近はどこもメジャーなBlogであればだいたいUTF-8で書かれていますが、
コレは別にRSSで配信する上で仕様で決まってるわけではなく、文字コードは任意で選べます。
一覧にするのであれば、全て同じ文字コードでないと、文字化けが発生します。

文字コードの判別方法

幸い、文字コードを宣言する仕組みがあります。

  • HTTPでデータを要求した際に、ヘッダ情報にContent-Typeがあり、ここにcharsetを含める事ができます。
  • HTMLであれば、<meta>タグにContent-Typeを宣言できる部分があり、ここにもcharsetを含める事ができます。

相手を信頼して、ここから文字コードを読むと、一番ラクかもしれません。

宣言がない場合での文字コードの判別

意地悪な人も世の中にはいます。その場合はどうするか。
日本語の文字コードEUC-JP, Shift_JIS, UTF-8などが今のところメジャーですが、これらを完璧に識別する方法はありません。
でも出来る限り、識別しようとするモジュールはPerlにはあります。
Enocde::Guessです。

use Encode::Guess;

my $string = 'あいうえお';
my $code = '';
my $enc = guess_encoding($string, qw/euc-jp shiftjis 7bit-jis utf8/);
if(ref $enc){
    $code = $enc->name;
}
print "$code ($enc)\n";

guess_encodingに、対象になる文字列と候補となる文字コードのリストを与えると、文字列が大体どの文字コードが使われているかがわかります。
保存する文字コードによって、得られる結果が変わります。
ref $encとするのは、guess_encodingの結果が、
文字コードを絞り込めたら、オブジェクトが、
絞り込めなかったら、文字コードの候補を文字列で返すからです。
refすると、オブジェクトなら何らかのリファレンスを示す文字が帰ってきて、
文字列なら、リファレンスではないので、偽となります。
また、ASCII文字のみだと、判別ができません(EUC-JP, Shift_JIS, UTF-8がそれぞれ扱うASCII文字のコードは全て同じだから)
その場合は別に変換しなくてもいいんですが、
日本語らしい文字が入っていても、サンプルが少ないと絞り込めない事もあります。
上の例ですと、おそらく、UTF-8, ShiftJISの時は絞り込めますが、
EUC-JPの場合は、曖昧となり絞り込めなくなるでしょう。
その場合は、コンテンツ全体を渡して意地でも特定させる等の方法があります。
ちなみに、ここで絞り込めなかったら、文字コードを判別する手段はありません。
僕は文字コードに詳しくないのですが、数百バイトものデータがあれば、大体絞り込めると思います。
絞り込めなかったらそれはASCIIコードだと決めうちすればいいんじゃないかなと思います。ASCIIコードなら変換はいりません。

文字コードがわかったら変換

Encode.pmを使って、一意の文字コードに変換できます。Encode::from_toが簡単です。

use Encode;
use Encode::Guess;

# 文字コードを特定し、$codeに格納する
# ...

Encode::from_to($string, $code => 'utf8');
print "$string\n"

Encode::from_toの挙動の注意点として、
$stringに直接変換した文字を上書きするので、
Encode::from_toを呼び出す前の$stringと後の$stringの内容は違います(変換が正常に終われば)。
Encode::encodeとEncode::decodeを使って同じ処理を書く事もできます。

$string = Encode::encode('utf8', Encode::decode($code, $string));

速度はfrom_to > decode&encodeです。

          Rate encode fromto
encode 62735/s     --   -16%
fromto 74405/s    19%     --

ただ入力する文字コードが一定だとわかっている場合は、オブジェクトを作って、変換するほうが早いです。