今日のCPANモジュール

2008-06-11 16:35  

use Encode;

日本語/フラグ周りでつまづくのはありがちなので落ち込む必要はないです。この連載も「そろそろ日本語の話題か・・」と思ったのですが、まとめるのがめんどうでテラ放置してました。

すでにたくさんの資料が出回っていることを踏まえ違う切り口でまとめてみます(少々乱暴な部分もあるので、後で ADVANCED も見て下さい)。

まず、以下はそれぞれだいたい同じものと考えていいです。

混同しやすいのは、上記 A 郡と「UTF-8」が別物という点です。UTF-8 とは Shift_JISEUC-JP と同じ、バイトの並び順の仕様(エンコーディング)で、Unicode を表す方法の一つです。

Perl は Unicode を保持する際内部で付けているフラグを(UTF-8 を使っているため)「utf8」フラグと呼んでおり、一般向けの説明でもその表現なので少々ややこしいです。

文字処理の基本

Encodedecode()/encode() を使います。

use Encode;

# 入力はバイト列。以下は例で、euc-jp エンコーディングでの「ああ」 
my $input = "\xa4\xa2\xa4\xa2";

warn length $text; # => 4 (バイト長。2バイト×2文字で4バイト)

# (1) まず該当エンコーディングで decode() する
my $text = decode('euc-jp', $input);

# (2) Unicode になったものを文字として好きに処理する
warn length $text; # => 2 (文字長。2文字)

# (3) 外部へは好きなエンコーディングで encode() して出す
print encode('utf-8', $text); # => \xe3\x81\x82\xe3\x81\x82

上記例の $input$text は意味的には同じ「ああ」ですが、前者はバイト列・後者は文字列です。最後 print() で出す時にまたバイト列にしています。

たとえ入力が UTF-8 な文字だったとしても、そのままでは「UTF-8 で並んだバイト列」にすぎませんので、decode('utf-8', $input); して Unicode にする必要があります。

よくある入出力

具体的な入出力の場面でどう decode()/encode() するのか見てみます。

ファイル読み書き

use Encode;
open(my $fh, '<', 'text.txt') or die $!;
while (<$fh>) {
    my $line = decode('euc-jp', $_); # text.txt は euc-jp の場合
    $line =~ s/^\s*//; # とかなんとか
    print encode('utf-8', $line); # 例えば utf-8 で出力
}

CGI.pm

#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use CGI::Carp qw(fatalsToBrowser);
use Encode;
use utf8;

my $cgi = CGI->new;

# charset=Shift_JIS な HTML からのフォーム送信を想定
my $name = decode('cp932', $cgi->param('name')); # 入力を decode()

if ($name eq 'とみた') { # この cgi スクリプト自体は utf-8 で保存すること
    die "「とみた」は私です";
}

my $message = "$nameさん こんちは";

print $cgi->header('-charset' => 'Shift_JIS');
print encode('cp932', $message); # ブラウザに返すものは encode()

Catalyst

C::P::Unicode::Encoding を使うと、入力($c->req->params)の decode()、最終出力($c->res->body)の encode() を config で指定したエンコーディングで自動にやってくれます。

use Catalyst qw(
    ConfigLoader
    ...
    Unicode::Encoding
);

__PACKAGE__->config(encoding => 'euc-jp');

decode()/encode() 専業ですので、必要に応じて Content-Type ヘッダを指定する必要もあります。特に RenderView を使ってる場合は指定しないとデフォルトの 'text/html; charset=utf-8' になるので必須です。

$c->res->content_type('text/html; charset=EUC-JP');

DBIx::Class

DBIx::Class::UTF8Columns を使って ResultSource のクラスで

__PACKAGE__->utf8_columns(qw( name kana ));

とか指定すると、そのフィールドへの出し入れの際に自動で decode()/encode() してくれます。使われるエンコーディング(つまり DB の文字コード)は UTF-8 前提です。それ以外の場合は自分で inflate/deflate を書くです。

Template-Toolkit

POD に明記されていないので要確認なのですが、UNICODEENCODING を指定することで、TT にテンプレートファイルを decode() して読むよう指示できます。

use Template;

my $tt = Template->new({
    UNICODE  => 1,       # テンプレートファイルを Unicode として扱う宣言
    ENCODING => 'utf-8', # テンプレートの文字コード。これで decode() される
});

もしくは Template::Provider::Encoding を使い、以下のようにもできます。この場合 Template::Provider::Encoding の他の機能も使うことができます。

use Template;
use Template::Provider::Encoding;

my $tt = Template->new({
    LOAD_TEMPLATES   => [ Template::Provider::Encoding->new ],
    DEFAULT_ENCODING => 'utf-8', 
});

$tt->process() の結果は Unicode のままですので、出力する時には以下のように encode() します。(process()binmode オプション$Template::BINMODE を使うことも可能)

# 上の続き

use utf8;
my $vars = { name => "とみた" }; # 変数は Unicode

$tt->process('template.tt', $vars, \my $text) # $text に入れて
    or die $tt->error;

print encode('euc-jp', $text); # 出す時に encode()

その他のモジュールと Unicode

最近は Unicode を渡し Unicode を受け取るモジュールがほとんどであり、そうでないものも移行しつつあります。ですので、今でも文字をむりやり全部バイトのまま扱うことも可能ですが、Unicode で扱ったほうが何かと楽になってきました。

各モジュールの Unicode 受け入れ体制についてはそれぞれの POD や Changes をチェックして欲しいのですが、少なくとも今後この連載で取り上げるモジュールについては日本語まわりの話題に触れるようかと。

Unicode を表記する

上の例ですでに使っていますが、use utf8; するとそのスコープ(レキシカル)において、UTF-8 で書いたリテラルがそのまま Unicode として扱われます。これ以外にも Unicode を表記する方法がいくつかあります。以下の例はどれもだいたい同義で最終的に $text は Unicode です。

# A: utf8 プラグマのスコープ内に utf-8 で書く
use utf8;
my $text = "笑"; # この時点でもう Unicode(ソースは UTF-8 で保存する)

# B: utf-8 で書いて decode()
use Encode;
my $text = "笑"; # この時点ではバイト(ソースは UTF-8 で保存した場合)
   $text = decode('utf-8', $text); # ここで Unicode

# C: \xHH\xHH 表記で書いて decode()
use Encode;
my $text = decode('euc-jp', "\xbe\xd0"); # バイト部分は笑の euc-jp 表現

# D: B の UTF-8 版. perl にとってはほぼ B と同義
use Encode;
my $text = decode('utf-8', "\xe7\xac\x91"); # バイト部分は笑の utf-8 表現

# E: \x{UUUU} 表記. UUUU は 16進表記の Unicode コードポイント
my $text = "\x{7B11}";

# F: chr() に Unicode コードポイントの10進を渡す
my $text = chr 31505;  # または、
my $text = chr 0x7B11; # コードポイントは16進表記が多いので 0x 表記が楽かも

特定の文字の Unicode コードポイントや、いろんなエンコーディングでどう表現されるかは Unicode Character Search が超便利です。(例: 「笑」のページ や More.. からたどる 各種エンコーディングでの表記)。最近落ちてることが多くて悲しいです。

Unicode を Data::Dumper した場合、E の "\x{UUUU}" 表現になりバイト列と区別が付きます。(ADVANCED も見てください)

正規表現

Perl は Unicode サポートを実直に実装しており、正規表現もサポートしています。具体的には、Unicode に対しては . が「1文字」に対応します。これは良いですが、\s\d\w なども意味的なマッチになるので要注意です。

use utf8;
if ("あ12  " =~ /^(.)(\d+)(\s+)$/) {
    warn $1; # あ
    warn $2; # 12   // 全角半角問わず数字にマッチ
    warn $3; # "  " // 全角半角問わず空白にマッチ
}

半角スペースや数字のみにマッチさせたい場合、やり方はいろいろありますが

  1. 事前に Unicode::NormalizeEncode::JP::H2Ztr/// などで半角にしておく。
  2. 正規表現の部分だけ(use utf8; の逆である)use bytes; を使う
  3. \s の代わりに [ ]\d の代わりに [0-9] を使う

3番でいいと思います。

文字化け・エラー

「化けました」(ãããã みたいなアクセント付きアルファベット多数散見)

フラグ付きのものとそうでないものをくっつけるとこう化けます。よく TT を、上で示した方法を取らずに使い、テンプレートファイルと挿入する変数のどちらかがバイト列のまま(= decode() 忘れ)だったりとかで起こります。

「Wide character in print ...」

print "\x{7B11}";

フラグ付きのまま print() すると出ます(= encode() 忘れ)。この際、文字そのものは UTF-8 で出てくるので読めちゃうかもしれませんが、print() 前に encode() すべきです。ちなみに warn() だとこの warning は出ないですので warning を当てにしている場合は注意です。

「機種依存文字が出ません」/「波ダッシュが出ません」

'shift_jis' ではなく 'cp932'decode()/encode() するようにすればたいていうまくやってくれます。

「?」になる

ある Unicode を encode() した時、指定したエンコーディングでは対応する文字がないよ、という場合、デフォルトでは ? になります。この動作を、encode() の第三引数で変更してやることができます。(詳細

print encode('shift_jis', "\x{3231}"); # => ? (株) が Shift_JIS にないので
print encode('shift_jis', "\x{3231}", Encode::FB_HTMLCREF); # => &#12849;
print encode('shift_jis', "\x{3231}", Encode::FB_XMLCREF);  # => &#x3231;
print encode('shift_jis', "\x{3231}", Encode::FB_PERLQQ);   # => \x{3231}

なおブラウザは10進の文字参照(Encode::FB_HTMLCREF)もしくは16進の文字参照(Encode::FB_XMLCREF)でそのまま見えたりしますので使えます(10進の方がより安全と記憶)。

Encode のその他の機能

エンコーディング名の表記ゆれ

Encode はたくさんのエンコーディングを知っていますEncode::JP あたりに日本語で使いそうなエンコーディングが書いてあります。表にあるようにエンコーディング名は表記ゆれをある程度吸収してくれます。(euc-jp の場合 EUC-JP でも ujis でも OK)

Encode::Encoding オブジェクト

Encode::find_encoding() で、あるエンコーディング名がサポートされているかのチェック、そしてサポートされている場合は Encode::Encoding オブジェクトを取得できます。

my $encoding = Encode::find_encoding('cp932')
    or die "non supported";

my $text = $encoding->encode($input); # encode('cp932', $input); と同じ
my $out  = $encoding->decode($text);  # decode('cp932', $text); と同じ

機能というわけじゃないですが

Encode 以外で decode()/encode() というサブルーチンを持つモジュールがけっこうあるので、Encode::decode()/Encode::encode() と書く習慣を付けてると問題が防げるかもしれません。

SEE ALSO

perlunitute和訳), perlunicode和訳), perlunicode, Encode和訳

Unicode::Japanese

ADVANCED

こちらもご覧下さい:このエントリに関する補足事項

WRITTEN BY

冨田 尚樹 <tomita@cpan.org>

Feedback

はてなブックマーク livedoorクリップ POOKMARK Buzzurl Yahoo!ブックマーク del.ico.us

Trackback

url: http://e8y.net/mt/mt-tb.cgi/229

Comments

June 23, 2008 12:48 AM

advancedページに#mobilejpでもらった内容を更新。encodingプラグマは非推奨と明記。cp932の話題にLegacy Encoding ProjectのページとEUCJPMS/ISO2022JPMSを追記しておきました。

- 冨田

読者登録

最近の号