今日のCPANモジュール(跡地)
このサイトが元になったCPANモジュールガイドという本を書きました。
本書でも取り上げています。
このページにあることは間違いではないのですが、「UTF8フラグ」「Unicode」「文字列」「バイト列」このあたりについて、より正確で簡潔な解説を心がけました。また、全編を通して日本語を扱う方法もふれています。
2008-06-11
use Encode;
日本語/フラグ周りでつまづくのはありがちなので落ち込む必要はないです。この連載も「そろそろ日本語の話題か・・」と思ったのですが、まとめるのがめんどうでテラ放置してました。
すでにたくさんの資料が出回っていることを踏まえ違う切り口でまとめてみます(少々乱暴な部分もあるので、後で ADVANCED も見て下さい)。
まず、以下はそれぞれだいたい同じものと考えていいです。
- A: 文字列、 ユニコード(Unicode)、 utf8フラグ付き、 wide character、
decode()されたもの、"x{3042}"表記、use utf8;されたスコープにUTF-8で書かれた'あ' - B: バイト列、 バイトストリーム、 オクテット、 フラグなし、 普通に書かれた(もしくは
use bytes;スコープでの)'あ'
混同しやすいのは、上記 A 郡と「UTF-8」が別物という点です。UTF-8 とは Shift_JIS や EUC-JP と同じ、バイトの並び順の仕様(エンコーディング)で、Unicode を表す方法の一つです。
Perl は Unicode を保持する際内部で付けているフラグを(UTF-8 を使っているため)「utf8」フラグと呼んでおり、一般向けの説明でもその表現なので少々ややこしいです。
文字処理の基本
Encode の decode()/encode() を使います。
use Encode;
# 入力はバイト列。以下は例で、euc-jp エンコーディングでの「ああ」
my $input = "xa4xa2xa4xa2";
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); # => xe3x81x82xe3x81x82
上記例の $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 に明記されていないので要確認なのですが、UNICODE と ENCODING を指定することで、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: xHHxHH 表記で書いて decode()
use Encode;
my $text = decode('euc-jp', "xbexd0"); # バイト部分は笑の euc-jp 表現
# D: B の UTF-8 版. perl にとってはほぼ B と同義
use Encode;
my $text = decode('utf-8', "xe7xacx91"); # バイト部分は笑の 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; # " " // 全角半角問わず空白にマッチ
}
半角スペースや数字のみにマッチさせたい場合、やり方はいろいろありますが
- 事前に Unicode::Normalize、Encode::JP::H2Z、
tr///などで半角にしておく。 - 正規表現の部分だけ(
use utf8;の逆である)use bytes;を使う 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); # => ㈱
print encode('shift_jis', "x{3231}", Encode::FB_XMLCREF); # => ㈱
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() と書く習慣を付けてると問題が防げるかもしれません。
ADVANCED
だいたい同じと乱暴にまとめた latin-1 の話
latin-1 とは俗称で、正式には ISO-8859-1 とか言う。おおざっぱに言うと ascii 127 文字の続きに1バイトに収まる範囲でアクセント記号付きアルファベットとかを付け加えたもの(表)。付け加えるもののバリエーションで latin-2(ISO-8859-2)とかあったりする。
Perl は utf8 フラグのなかった時代のコードもそのまま動くよう、latin-1 の範囲(1バイト = 10進で255以下、16進で xFF 以下)を優遇しその範囲は Encode::decode() なしにフラグが立たないようにしている。(例えば "x{UUUU}" や chr 0xUUUU 表記)
なので Data::Dumper で "x{UUUU}" 表記になるか見るだけで utf8 フラグが立っているかどうかを判断するのは実は正確ではない。日本語(非1バイト)を扱っている場合はだいたいそれでわかりますが。
この辺は主に入出力を担当するモジュールを書く人が気をつけるべき話題で、モジュールを作る人はできるだけ利用者が Unicode だけを使えばよいようにしてあげよう。ということで本編では流した。モジュールでの Unicode 利用の歴史: 1, 2, 3
自身でフラグを気にしなければいけない場合は Data::Visitor::Encode が便利。
まったく登場しない utf8:: の話
utf8::decode() は Encode::decode() と違い latin-1 の範囲はフラグを立てないのでその差に注意しなければならない。あと日本人はどうせ utf8::* だけでは済まないことが多い。なので utf8:: ではなく Encode::* 推奨とした。
同様の理由で Catalyst でエンコーディングが UTF-8 だけの場合は使えるのだけど C::P::Unicode は割愛した。
まったく登場しない encoding プラグマの話
非推奨であるのとソースは UTF-8 を使えるケースが多いと思うのでスルー。
まったく登場しない PerlIO の話
Perl には PerlIO というファイルハンドル動作のラッパー機能があり、これを使ってファイル open や print の時 自動で decode()/encode() させることができる。
PerlIO は便利だけどこれ専用の機能ではないし Encode::decode()/ Encode::encode() で済むといえば済むので、よくわからないうちは無理に使わなくても良いと思いスキップ。あくまでも便利機能という位置づけとした。
ここで簡単に触れておきます。PerlIO の使い方は3パターン。それぞれ例を示します。
open プラグマによる指定
use open qw/:utf8 :std/; open(my $fh, '<', 'file.txt') or die $!; while (my $line = <$fh>) { print $line; }これは以下とだいたい同じです。
use Encode; open(my $fh, '<', 'file.txt') or die $!; while (my $line = <$fh>) { $line = decode('utf-8', $line); print encode('utf-8', $line); }open()(3つ引数取る版)の第2引数に仕込むopen(my $fh, '<:encoding(euc-jp)', 'file.txt') or die $!; while (my $line = <$fh>) { print $line; }これは以下とだいたい同じです。
use Encode; open(my $fh, '<', 'file.txt') or die $!; while (my $line = <$fh>) { $line = decode('euc-jp', $line); print $line; }binmodebinmode STDOUT, ':utf8'; print "x{7B11}";これは以下とだいたい同じです。
print encode('utf-8', "x{7B11}");
と、decode()/encode() を書かずに透過的に decode()/encode() できます。PerlIO レイヤは :utf8 や :encoding() タグ以外にもいろいろあったりします。入出力のラッパーだと認識した上で使うと便利です。
cp932 の話
Encode の Shift_JIS エンコーディングは純粋な Shift_JIS。Microsoft が Shift_JIS を拡張して入れた ?? みたいな文字を含む Shift_JIS は cp932 というエンコーディングで別に入っている。機種依存文字も最近は Mac でも見れたり入力できたりするので、cp932 をつかっておけばよい、とした。
この話題についてはLegacy Encoding Project に詳しい。shift_jis に対する cp932 と同じようなポジションで Encode::EUCJPMS や Encode::ISO2022JPMS(開発中?)がある。
波ダッシュ/全角チルダ問題
charset=Shift_JIS な HTML からキーボードから普通に入力した「?」は、shift_jis で decode() すると U+301C に、cp932 では U+FF5E にマッピングされる。さらに、charset=UTF=8 な HTML からは Windows と Mac で違うのが来て、utf-8 で decode() すると Windows からのは U+FF5E に、Mac からのは U+301C にマッピングされる。
問題は encode() 時で、shift_jis には U+FF5E に対応するのがない。まあ shift_jis には機種依存文字もないので良いが、cp932 は機種依存はあるが U+301C に対応するのがないので ? になる。
といっても実際問題になるケースは少なく、Mac から decode('utf-8') したのを encode('utf-8') して保存しておいて、場合によっては decode('cp932') で出すというケースとかだが、Encode::FB_HTMLCREF を使うのは嫌でどうしても出したい時は
# cp932 を使って encode() する場合。shift_jis でやる場合は逆にする
$text =~ s/x{301C}/x{FF5E}/g; # WAVE DASH to FULLWIDTH TILDE
print encode('cp932', $text);
みたいに置換してから encode() という方法がある。
しかしこれって「PC/携帯 でデータを共有するサービス」が想定されるケースであり、携帯に特化したエンコーディングパックを追加する Encode::JP::Mobile の shift_jis 系エンコーディング(x-sjis-docomo)とかは cp932 ベースだが U+301C も特別に入っているので置換とかする必要がないので普通にこれを使えば良い。(今のところ波ダッシュ以外の違う奴はサポートしていないが)
その他、Unicode / Encode に関係あるけど割愛したもの
そのスカラに utf8 フラグが付いているかどうかの以下のようなチェックの件。よく考えると実際調べてどうするというケースが多いので
warn warn use
pack()の U- charnames の
N{名前}。便利そうだけど微妙に使う機会少ないんだよな - 当方 EBCDIC 環境(オフコン?)は知りませんのでよろしく
正規表現
- 正規表現の
p{プロパティ}は便利。だけど長くなるので Unicode::UCD, Unicode::Normalize と合わせて別エントリにしようか dが全角にもマッチするから FormValidator::Simple では'ASCII'とセットにすると良いよ、というのは FormValidator::Simple 取り上げる時書けば良いか
Encode
decode_utf8($text)はdecode('utf-8', $text)に同じ、
encode_utf8($text)はencode('utf-8', $text)に同じ。クオート符号が少なくという利点があるのでたまに使いますfrom_to()は、名前もイマイチだしバイト時代の人向け- 自前エンコーディングを作れる機能は、Encode::JP::Mobile とか作る人向け。
define_alias()は、sjis って書いちゃったやつを全部 cp932 にしたい時とか便利。でもまあ普通に cp932 って書けばいいかと- CHECK にコールバックを仕込むのは実は使いこなすのは大変なので詳しくはパス
- Encode::JP::H2Z については Unicode::Japanese の方がよいと思うので別でとりあげることにする。
