SSブログ

[Perl] Perl の Unicode 対応について [Perl]

# さらに、内部形式について詳しいことを
# [Perl] Perl の内部形式に関する調査
# Perl の内部形式に関する調査
# に書きました。参照してくださいませ。

Perl は ver.5.8 ぐらいからちゃんと Unicode をサポートしてたようで(実は最近になって初めて知った)、最近いろいろ調べてたんですが、なかなか難しい・・・! 文字列の Perl 内部的な表現があって、入出力用に内部的な表現と通常の文字コードセットの変換をする PerlIO レイヤがあって、今までとの互換性を保つために、内部的な表現を使う場合は UTF8 フラグを立てる、などなど、なんとなくはわかったのですが、細かい部分で期待通りの動きをしてくれませんでした;
特に UTF8 フラグについてが難しかった部分なのですが・・・今日ようやくわかったような気がします。

そんなわけで自分が躓いた部分なんかを書き残しておきます。


まずは基本事項。
Unicode 対応、とは言いますが Unicode はあくまで文字セットなので、何のコードを使うかという問題があります。Perl では、UTF-8 が基本になっているようです。

プログラムは基本的に UTF-8 で書くことになります。そして、Perl に UTF-8 で書いてあることを知らせるために utf8 プラグマを使用します。
 # utf8 プラグマ
 use utf8;
これでプログラム中に書かれてある文字列が、自動的に内部表現に変換されます。
プログラムの Unicode 対応はできました。

次に入出力です。Perl 内部で Unicode に対応した内部表現が使われるようになったとしても、そのまま内部表現を外部に出力したり外部から入力したりできるわけではありません。内部表現と通常の文字コードセットを変換する必要があります。それが PerlIO レイヤと呼ばれるもので、open プラグマを使用して、入出力に使用する文字コードセットのデフォルト値を指定できます。
 # open プラグマで入出力のデフォルトを指定する
 use open ":utf8"; # UTF-8 で入出力する場合
 use open ":encoding(xxx)"; # 文字コードセット xxx で入出力する場合 "shiftjis", "euc-jp" など。
 use open ":bytes"; # バイト列として入出力する場合
また、上記の指定では標準入出力に適用されないため、open ":std" で標準入出力にも適用します。
 # 入出力のデフォルトを標準入出力にも適用する。
 use open ":std";
また、デフォルト以外の指定をしたい場合は、ファイルオープン時に指定したり、binmode を使用していつでも変更できます。
 # open 時に 3 つの引数を指定することで PerlIO レイヤを指定する。
 open $handle, "<:utf8", $file;
 # binmode を使って、PerlIO レイヤを変更する。
 binmode($handle,":encoding(shiftjis)");

そして、これまでとの互換性を保つために、Unicode に対応した内部表現を使用している場合は、UTF-8 フラグというものが付与される。
この UTF-8 フラグというものがなかなか厄介でわかりにくかったものです。。
今日 daily dayflower さんとこで UTF-8 フラグの記事を読んで自分の実感と違うとこがあったんでいろいろ試してみたんですが、ようやくわかんなかったとこがわかりました。ということで自分が躓いたとこを書いておきます。

daily dayflower さんとこの表現に合わせて書いていくので、先にあちらを読んでいただけるとわかりやすいかと思います。
読み進めるためにこれだけは知っておくべきこと。
 Perl の文字列リテラル中に \x{XXXX} などと書くことで Unicode のコードポイント XXXX の文字を表現できる。(XXXX は 16 進数字)
 Unicode はあくまで文字セットであり文字コードセットではない。UTF-8 は文字コードセット。
# 追記: ここから
以下の内容は誤りです。
dayflower さんに指摘していただきました。ありがとうございます。
# 追記: ここまで
daily dayflower さんとこの文書を読んだらほとんどわかっちゃうと思うんですが、私が気になったのは 「Unicode 文字列の透過性 (1)」 に書かれている内容。
Latin-1 の範囲(U+0000 ~ U+00FF)に収まる Character で構成される文字列は、内部 Latin-1 でも内部 UTF-8 でも表象でき、eq オペレータで比較すると,(内部表象はどうであれ)等価な文字列かどうかを比較する、という内容です。
内部 Latin-1 (UTF8 フラグなし)と内部 UTF-8 (UTF8 フラグあり)の 2 つを比較した時、フラグの有無は関係なく比較される、という部分は私の実感と同じだったのですが、その時に内部表現が異なっている、という点が私の実感とは異なっていました。
そこで確認を行ってみたところ、U+0000 ~ U+00FF の範囲の文字は、内部 UTF8 でも内部 Latin-1 でもコードは一緒である、という結論に達しました。つまり、U+0080 から U+00FF までの文字は、Perl の内部的 UTF8 表象では UTF-8 コードではなく Latin-1 コードに一致する、ということです。
内部 UTF-8 も 内部 Laitn-1 も U+0000 から U+00FF まではコードが一緒なのです。違いはただ UTF8 フラグが付いてるかどうかだけ。そして比較するときも UTF8 フラグは関係ないですし、出力時も関係ありません。UTF8 フラグすらも気にする必要はない、ということです。
(ただ UTF8 フラグを使って判定をするときに問題になる、というのが先の話だったりするのですが)

よく utf8::upgrade の説明で 「\x90 は \xC2\x90 の UTF8 文字列に変換されます」 みたいなことが書かれているのが混乱の元だと思うのですが、utf8::upgrade は文字コードを変換することなく単に UTF8 フラグをつけるだけ、ということになります。

最後に utf8::upgrade と utf8::decode について。
例として文字 é を使います。この文字のコードポイントは U+00E9 で、UTF-8 で表現すると C3-A9 という 2 bytes になります。
UTF8 フラグなしの \xE9 を utf8::upgrade すると、内部 UTF8 で U+00E9 を表します。Latin-1 octet stream に
使うのが utf8::upgrade です。
UTF8 フラグなしの \xC3\xE9 を utf8::decode すると、内部 UTF8 で U+00E9 を表します。UTF-8 octet stream に使うのが utf8::decodeです。

あと、コード指定での文字入力で、\x{XXXX} とするとコードポイント XXXX の Unicode を表すことになる、と上でも書きましたが、これは必ずしもそうはならない、というのがポイントです。コードポイント U+0080 から U+00FF までを表す時にはどうもややこしい動作をするようです。これはまた次の記事ででも書こうと思います。

この文書は趣味で Perl 使ってるような人に向けて書いてるので(自分もそうだし・・・)ばりばり Perl してる人からすると変な表現があるかもしれません。間違いもあるかもしれません。ご指摘頂けると幸いです。
なお、試験は ActivePerl v5.10.0 で行いました。

タグ:perl UTF-8 Unicode
nice!(0)  コメント(4)  トラックバック(1) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 4

Akira

さらにちょこっと動作を見てると、どうやら必ずしも U+0080 ~ U+00FF の文字が Perl の内部表現で Latin-1 コードになっているとは限らないようですね。。

"\xC3\x97" という octet stream を格納した utf8::decode すると、U+00D7 を表す内部表現になります。
"\x{00D7}" という octet stream だけを格納した変数を utf8::upgrade しても、同様に U+00D7 を表す内部表現になります。
ここまでは期待通りの動作で、さらにこの 2 つはともに、記事で述べたように内部に保持しているコードとしては D7 という 1 byte コードになっているようです。(それは unpack したり、PerlIO レイヤを指定せずに出力してみたりすることで確認しました。)
ちなみに use utf8; して '×' という文字を直接変数に代入した場合も、内部的には D7 という 1 byte コードになっていました。もちろん 3 つとも UTF8 フラグはついていました。
('×' という文字が U+00D7 で、UTF-8 コードでは C3-97 という 2 bytes コードである。)

ここまでは期待通りの動作なのですが、変数に "\x{3042}\x{00D7} " と代入した時の動作がどうも変です。このとき、utf8::upgrade や utf8::decode しなくても \x{3042} があるので UTF-8 フラグ付の内部表現に自動的になるようなのですが、この変数から 2 文字目(つまり \x{00D7})を substr で切り出してコードを見ると、なんと C3-97 という 2 bytes コードになっていました。
C3-97 を表す 2 bytes octet stream を utf8::decode しても、D7 という 1 byte の octet stream を utf8::upgrade しても、さらには use utf8; して UTF-8 コードで書いた '×' を直接代入しても、全て内部表現としてのコードは D7 という 1 byte であったのに、なぜか、自動的に UTF-8 フラグが付く状況で \x{00D7} と書くと内部的に C3-97 という 2 bytes コードになるようです・・・。なぜ??

ちなみに PerlIO レイヤを指定して出力した場合、内部的なコードが D7 であっても C3-97 であっても、ともに '×' が出力されました。
あと eq 演算子で比較すると、等価であることがわかりました。
daily dayflower さんとこの等価性に関しては確かってことですね。
けど、やはり utf8::upgrade は内部的なコードは変えないようです。
by Akira (2008-03-02 01:25) 

Akira

借りてるサーバの方でテストしてみたら daily dayflower さんとことおんなじ結果になりました。
ローカルの環境が ActivePerl だから違うんでしょうか? バージョンが違うから?

わかんないですけどこの記事はあんまり役に立たないかもですね。一応環境によってはこうなる、って感じで読んでください;
by Akira (2008-03-02 01:39) 

Akira

コメント欄の内容は Perl 5.10 と Perl 5.8 の 2 つの環境でテストした結果が混同したための混乱です。すみません。
記事本文には誤りはないと思われます。

なお、内部コードに関してさらに詳しいことを
http://scape.blog.so-net.ne.jp/2008-03-11
http://www.r-definition.com/program/perl/internalformat.htm
に書きましたので、ご覧ください。
by Akira (2008-03-11 20:53) 

Akira

うん、やはり誤りでしたね。すみません。
by Akira (2008-03-14 00:31) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 1

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。