perl、CGI::Application、HTML::Template、postgresでUTF8を使う

前置き

本サイトでは、原則として文字コードにUTF8を使っています。具体的には…

  • perlのスクリプト
  • HTML::Templateのテンプレート
  • postgresのDB
  • apacheが出力するHTML/CSS

中でも、perlスクリプトの文字コード制御は難解です。perlらしいと言えばperlらしいですが…。現状の理解を書いておきます。

本文

エンコードとデコード

いきなり脇道に逸れます。

2つのレイヤ「人間」と「プログラム」を考えるとき、プログラム内では文字をコードで表現します。これを文字コードと呼びます。人間的表現(文字)からプログラム的表現(文字コード)へ変換する処理をエンコードと呼びます。逆の処理をデコードと呼びます。レイヤ間を文字データが行き来するときに、エンコードやデコードが発生します。

perlでは、3つ目のレイヤ「perlインタープリタ内部(略してperl内部)」が加わります。上から順に、「人間」→「プログラム」→「perl内部」となります。perl内部での文字表現のことを仮にrawと呼びましょう。perlでは、プログラム的表現(文字コード)からperl内部的表現(raw)へ変換する処理をデコードと呼びます。rawから文字コードへの変換がエンコードです。ショックですね。

図.レイヤとエンコード/デコード

UTF8フラグ

perl内部での文字表現(raw)には、UTF8フラグなしとUTF8フラグ付きの2種類があります。この用語は紛らわしいので、本稿では単にフラグ付きとかフラグなしと呼ぶことにします。

フラグなしのraw
これがデフォルト。1文字を1バイトで表現。 文字列を単なるバイト列として扱う。もし日本語文字列をフラグなしのrawで表現すると、例えばlength()は、文字数ではなくバイト数を返す。
フラグ付きのraw
1文字をマルチバイトで表現。日本語文字列に対して、length()は文字数を返す。

「プログラム」レイヤも「perl内部」レイヤも、文字をコードで表現する点では共通しており、「プログラム」レイヤでは、UTF8やEUCなど複数のコード体系が定義されています。一方「perl内部」レイヤでも、何らかのコード体系が定義されています。実はそれはUTF8なのですが、妙なフラグが付いたUTF8なので、個人的には、UTF8と考えない方が良いと思っています。そこで本稿では、perl内部の文字コードは独自コード(フラグなしrawかフラグ付きraw)と考えることにします。

図.perl内部コード

フラグを付けるには

たとえperlスクリプトをUTF8やEUCで書いたとしても、特に明示しない限りperlインタープリタは、スクリプトをフラグなしのrawで扱います。この場合、「プログラム」レイヤと「perl内部」レイヤ間のエンコード/デコード処理はnopです。

文字列を処理する関数(lengthとかsubstrとか)にマルチバイト文字を意識させるためには、フラグを付ければ良いのですが、話はそれだけでは済みません。なぜなら、フラグを付けた途端に「プログラム」レイヤ側の文字コードも意識する必要が出てくるからです。なのでperlには、単にフラグを付けるだけの命令は用意されていません。言い方を変えると、フラグを付ける全ての命令は、文字コード指定を伴います。

そんな命令の1つが、use utf8です。

 use utf8;

これは、「perlスクリプトはUTF8で書かれてるから、文字列リテラルにはフラグ付きrawを使え」をperlインタープリタへ伝えます。そうすると、「プログラム」レイヤから「perl内部」レイヤへデコードするときには、UTF8文字からフラグ付きrawへの変換が行われます。逆にperl内部からプログラムへエンコードするときには、フラグ付きrawがUTF8へ変換されます。

perlインタープリタへの出入口

さて、perlインタープリタへ入ってくる文字は、perlスクリプト上のリテラルだけではありません。以下のような経路が考えられます。

  • スクリプト上のリテラル
  • ファイルIOから読んだ文字
  • STDINから読んだ文字
  • DBから取得した文字
  • perlインタプリタの引数(@ARGV)

同様に、perlインタープリタから出て行く文字にも複数の経路があります。

  • ファイルIOへ書いた文字
  • STDOUT/STDERRへ書いた文字
  • DBへ出力した文字

ややこしいことに、経路によってフラグを付ける方法が異なります。

  • ファイルIOの場合は、use open
 use open ":encoding(utf8)";      # UTF8 <=> フラグ付きraw
  • STDIN/STDOUT/STDERRの場合は、binmode
 binmode STDIN, ':encoding(euc-jp)';   # EUC <=> フラグ付きraw
 binmode STDOUT, ':encoding(euc-jp)';
 binmode STDERR, ':encoding(euc-jp)';
  • DB(CGI::Applicationとpostgresの組み合わせの場合)の場合は、pg_enable_utf8属性
sub cgiapp_prerun {
  my $self = shift;
  $self->dbh_config(XXXXX, YYYYY, ZZZZZ);   # データソースやユーザ名などを指定。
  $self->dbh->{pg_enable_utf8} = 1;         # UTF8 => フラグ付きraw
}

DBの文字コードがUTF8以外の場合は、どうしたらいいんでしょうね。また、DBへ出力するときにフラグ付きrawからUTF8へエンコードする必要があるはずですが、そこまでpg_enable_utfが面倒を見てくれるかどうかは、perldoc DBD::Pg から読み取れませんでした(試した範囲では面倒見てるっぽいです)。

  • @ARGVの場合は、-CAオプションか、環境変数PERL_UNICODE
$ perl -e 'print $ARGV[0] . "\n";' 日本語
日本語
$ perl -CA -e 'print $ARGV[0] . "\n";' 日本語
Wide character in print at -e line 1.
日本語
$ PERL_UNICODE=A perl -e 'print $ARGV[0] . "\n";' 日本語
Wide character in print at -e line 1.
日本語
$

オプションの場合も環境変数の場合も、Aは「@ARGVの文字コードはUTF8」を意味します。その結果、perl内部では$ARGV[0]の「日本語」がフラグ付きrawで管理されます。そのため、フラグ付きの文字をSTDOUTへ出力することになり、warningが出ています。

フラグを付ける、別の方法

スクリプトの文字列リテラルをフラグ付きrawで扱うようにするには、前述の use utf8 の他にも、use encoding を使う方法があります。ただし use encoding は、文字列リテラルだけでなく、STDIN/STDOUTにも作用します。

 use encoding 'utf8';     # UTF8 <=> フラグ付きraw
                          # リテラルとSTDIN/STDOUTに作用する。

スクリプト内で、自由にきめ細かくフラグを制御したい場合は、Encodeモジュールを使います。

use Encode;

sub read_ascii {
  my $data = shift;
  return decode('iso-8859-1', $data);   # ISO 8859-1(Latin-1) => フラグ付きraw
}

sub out_utf8 {
  my $data = shift;
  return encode('utf8', $data);         # フラグ付きraw => UTF8
}

CGI::ApplicationとHTML::Templateを使ったアプリの場合、たとえファイルIOやSTDIN/STDOUTをフラグ付きで扱うようにしてあっても、テンプレートに書かれたテキストやTMPL_VARで埋め込んだテキストが文字化けします。そこで、テンプレートをロードする際に、フィルターを組み込みましょう。

sub load_tmpl {
  my ($self, $tmpl) = @_;

  $self->SUPER::load_tmpl(
    $tmpl,
    'filter' => sub {
      my $txref = shift;
      $$txref = decode('utf8', $$txref);
    }
  );
}

以上のような対応により、本サイトでは、今のところUTF8がハンドリングできていると思います。

Last modified:2010/05/17 21:47:53
Keyword(s):
References:[言語Tips]
This page is frozen.