2005-06-15  URLエンコードされた日本語文字列のデコード

<お急ぎの方は後半の「まとめ」をご覧ください>

概要

Perl5.8のEncodeモジュールを使って,URLエンコードされた日本語文字列をデコードする方法をお話します。

<対象>

  • HTTPのGET要求に含まれるURLエンコーディングをデコードしたい人
  • サーチエンジンの検索文字列をデコードしたい人

あらまし

最初にバージョン5.8より古いPerlのお話です。URLエンコードされた日本語文字列をデコードするにはどうしたらよいのでしょうか。私が調べたいくつかの書籍では,

$str =~ tr/+/ /;
$str =~ s/%([a-fA-F0-9]{2})/pack( 'C', hex($1) )/eg ;  

というコードで最後に文字コードを合わせればよい,なんて記述をよく見かけました。文字コードの変換にはJcode.plがよく使われたようです。

Perl5.8では文字コードの変換に標準対応しており,Jcode.plは不要だそうです。そこで何の気なしに

use Encode ;

my $str ;
$str = '%A4%A2%A4%D9+%A4%DE%A4%EA%A4%A2' ;

$str =~ tr/+/ /;
$str =~ s/%([a-fA-F0-9]{2})/pack( 'C', hex($1) )/eg ;  

my $result ;
$result = decode( 'euc-jp', $str ) ;
print $result ;

とするだけで動いてしまうように思えたのですが,実行すると「Wide character in subroutine entry at C:/Perl/lib/Encode.pm line 164.」と怒られてしまいました。

いったいどうすればよいのでしょうか。

解決方法

Perl5.8からUTF-8フラグというものが導入されており,Perlインタプリタ内部では文字列の文字コードをUTF-8として扱っているそうです。

「UTF-8フラグが立っていれば,そのバイト列はUTF-8文字列だけど,フラグが立っていないときは,なんだか知らない」ということなのでしょう。たかが文字列だと思っていたら,どうやらPerlインタプリタ内部では文字列を表すバイト列以外にも情報を持っているようなのです。

結局どうすればよいのかというと,URLエンコードされた文字列をデコードする前に,まずUTF-8フラグを外します。URLデコードが済んだらEncode::decodeで,エンコード前の文字コードを指定します。Encode::decodeが成功すると暗黙でUTF-8フラグが立ちます。

騙されないために

私が「クセモノ」だと思ったのは,UTF-8フラグの挙動です。予期しないところでUTF-8フラグが立ってしまうことがあるのです。例えばUTF-8フラグを外した文字列にtr/+/ /すると,UTF-8フラグが立ってしまいます。

「Wide character in subroutine entry at...」でお悩みの方は,確実にUTF-8フラグの罠に嵌っています。フラグが外れているか,予期しないところでフラグが立っていないか,トレース文を挿入して確認するとよいでしょう。UTF-8フラグの騙しの被害に遭わないように気をつけましょう。

まとめ

以上を踏まえて「%A4%A2%A4%D9+%A4%DE%A4%EA%A4%A2」(エンコード前の文字コードは日本語EUC)をデコードする例を示します。

use Encode ;

my $str ;
$str = '%A4%A2%A4%D9+%A4%DE%A4%EA%A4%A2' ;

$str =~ tr/+/ / ;
$str = encode_utf8( $str ) ; # tr/+/ /後にUTF-8フラグを外すこと
$str =~ s/%([a-fA-F0-9]{2})/pack( 'C', hex($1) )/eg ;  

my $result ;
$result = decode( 'euc-jp', $str ) ;
print $result ;

実行すると標準出力に「あべ まりあ」が出力されます。

関連リンク