キーワード

プロフィール

深沢千尋

Author:深沢千尋
みなさんこんにちは、深沢千尋です。(公式ページ
文字コード【超】研究 改訂第2版NEW!」「すぐわかるPerl」「すぐわかる オブジェクト指向 Perl」の著者です。
ここでは、多くは技術的でないこと、ごくまれに技術的なことをなげやりに書いていきます。
メールは suguwakaruPerl@gmail.com まで。(アットマークは ASCII に)
Twitterはじめました。@query1000です。よろしく~

最新記事

最新コメント

最新トラックバック

月別アーカイブ

カテゴリ

検索フォーム

RSSリンクの表示

リンク

ブロとも申請フォーム

QRコード

QRコード

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

「まるごとEncode」解題(5)今さら decode() の解説されても・・・

つづき。

以下のプログラムを実行する。

#! perl
# decodeExp.pl -- decode を使って UTF-8 文字を作る
use strict;
use warnings;
use Encode;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目のひから4文字、ひらがなと表示される

my $bytes = 'abc漢字ひらがなカタカナ';
print "bytes:", decode('utf8', substr($bytes, 9, 12)), "\n";

my $utf8 = decode('UTF-8', $bytes);
print "utf8:", substr($utf8, 5, 4), "\n";

__END__

以下のように出力される。

F:\Dropbox20091029\My Dropbox\Marugoto>decodeExp.pl
bytes:ひらがな
utf8:ひらがな

これは(もうさんざん出てきてしまったが)decode 関数を説明するプログラムである。

本プログラムは、use utf8; プラグマに入っていないので、文字列リテラルが Perl 内部表現である。
ために、

'abc漢字ひらがなカタカナ'

から「ひらがな」の部分を取り出すには、

substr($bytes, 9, 12)

と書く必要がある。
「abc」は ASCII だから 1 文字 1 バイトだから全部で 3 バイト。
「漢字」は CJK 統合漢字(基本)だから1文字3バイトだから全部で 6 バイト。
ここまでが9バイト。
だから「ひらがな」は 10 バイト目から始まる。
「ひらがな」はひらがなだけど CJK 統合漢字(基本)だから1文字3バイトだから全部で12バイト。
よって10バイト目から12バイト、と数える必要があった。

しかしながら、

my $utf8 = decode('UTF-8', $bytes);

と書くことによって、バイトストリーム文字列 $bytes が Perl 内部表現に変わって $utf8 になることで、

print "utf8:", substr($utf8, 5, 4), "\n";

で(6文字目から4文字で)「ひらがな」が取り出せる。
グッとカンタン。
JPerlみたいだ!←

しかしながら、その前に、

print "bytes:", decode('utf8', substr($bytes, 9, 12)), "\n";

でもう decode が出てきてしまっているではないか。
これは、binmode で cp932 を指定してるが、ここで encode されるのに備えて decode しているのだ。
(Perl 内部表現でないと binmode で cp932 に変更できないから。)

まとめると、
・原記事は UTF-8 出力前提だが、
・それを DOS 窓前提にすると、
・プログラム本体を UTF-8 で書くために、
・出力するときには binmode STDOUT, (:cp932); を使わざるを得なくなって、
・binmode は encode するから、
・プログラム本体が use utf8; の場合は、
・文字列がバイトストリームなので、
・print 前に decode が必要
という話だ。
ああめんどくさい。

decode の第1引数の UTF-8 と utf8 は、http://perldoc.jp/docs/perl/5.10.0/perlunifaq.pod ではこう述べられている。

What's the difference between UTF-8 and utf8?
(UTF-8 と utf8 の違いは?)

UTF-8 は公式な標準です。 utf8 は、何を受け入れるかに関して自由な Perl のやり方です。もしそれほど自由でないものと対話する必要があるなら、 UTF-8 を使うことを考えたくなるかもしれません。自由すぎるものと対話する必要があるなら、utf8 を使わなければならないかもしれません。完全な説明は Encode にあります。

UTF-8 は内部では utf-8-strict として知られます。チュートリアルでは、たとえ内部では実際には utf8 が使われる場合でも一貫して UTF-8 を使っています; なぜなら区別をつけるのは難しく、ほとんど無意味だからです。

例えば utf8 は、9999999 のような、Unicode に存在しない符号位置も使えますが、これを UTF-8 でエンコードすると、代替文字を得ることになります(これはデフォルトの場合です; これを扱う他の方法については Encode/"Handling Malformed Data" を参照してください。)

わかりました、どうしてもと言うのなら:「内部形式」は utf8 であって、 UTF-8 ではありません。 (もしその他のエンコーディングでないのなら。)


さて、$bytes と、それを decode した $utf8 についてさらに調べる。
Devel::Peek::Dump というメソッドを使うと、変数の UTF8 フラグを調べることができる。
Devel は developper(開発者用)、Peek は覗くという意味だ。

#! perl
# expPeek.pl -- Devel::Peek::Dump を使って UTF8 フラグを覗く
use strict;
use warnings;
use Encode;
use Devel::Peek;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目の「ひ」から4文字、ひらがなと表示される

my $bytes = 'abc漢字ひらがなカタカナ';
Dump $bytes;
print "bytes:", decode('utf8', substr($bytes, 9, 12)), "\n";

my $utf8 = decode('UTF-8', $bytes);
Dump $utf8;
print "utf8:", substr($utf8, 5, 4), "\n";

__END__

実行する。

C:\Marugoto>expPeek.pl
SV = PV(0x3d6a94) at 0x9d9cc4
REFCNT = 1
FLAGS = (PADMY,POK,pPOK)
PV = 0x9e0fbc "abc\346\274\242\345\255\227\343\201\262\343\202\211\343\201\214
\343\201\252\343\202\253\343\202\277\343\202\253\343\203\212"\0
CUR = 33
LEN = 36
bytes:ひらがな
SV = PV(0x3d6c0c) at 0x9ef264
REFCNT = 1
FLAGS = (PADMY,POK,pPOK,UTF8)
PV = 0xa6ee4c "abc\346\274\242\345\255\227\343\201\262\343\202\211\343\201\214
\343\201\252\343\202\253\343\202\277\343\202\253\343\203\212"\0 [UTF8 "abc\x{6f2
2}\x{5b57}\x{3072}\x{3089}\x{304c}\x{306a}\x{30ab}\x{30bf}\x{30ab}\x{30ca}"]
CUR = 33
LEN = 36
utf8:ひらがな

こういうの見るとお目目が痛くなるが、ポイントは、
最初の Dump $bytes; は
FLAGS = (PADMY,POK,pPOK)
と、表示し、2回目の Dump $utf8; は
FLAGS = (PADMY,POK,pPOK,UTF8)
と表示していること。
これにより、decode によって UTF8 フラグが付与されたことが分かる。
UTF8 フラグが付与されると、substr が文字単位で動作する。
(他にもいいことはいっぱいあると思うが~)

なお、UTF8 フラグは、別に中身が UTF-8 でエンコーディングされてるからそういう名前なのではない。

上のプログラムを以下のように変更する。

#! perl
# expPeek.pl -- Devel::Peek::Dump を使って UTF8 フラグを覗く
# Shift_JIS 版:CP932 で保存のこと

use strict;
use warnings;
use Encode;
use Devel::Peek;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目のひから4文字、ひらがなと表示される

my $bytes = 'abc漢字ひらがなカタカナ';
Dump $bytes;
print "bytes:", decode('shiftjis', substr($bytes, 7, 8)), "\n";

my $utf8 = decode('shiftjis', $bytes);
Dump $utf8;
print "utf8:", substr($utf8, 5, 4), "\n";

__END__

decode の第一引数を shiftjis に変えてみた。
プログラムも Shift_JIS(CP932)で保存した。
そうすると、1個目の substr の引数は8バイト目から8バイトになるから、そうした。
では実行。

C:\Documents and Settings\fuc\My Documents\My Dropbox\Marugoto>expPeek.pl
SV = PV(0x3d698c) at 0x9d9b84
REFCNT = 1
FLAGS = (PADMY,POK,pPOK)
PV = 0xaad824 "abc\212\277\216\232\202\320\202\347\202\252\202\310\203J\203^\2
03J\203i"\0
CUR = 23
LEN = 24
bytes:ひらがな
SV = PV(0xa73e64) at 0x9ef264
REFCNT = 1
FLAGS = (PADMY,POK,pPOK,UTF8)
PV = 0xa6ef5c "abc\346\274\242\345\255\227\343\201\262\343\202\211\343\201\214
\343\201\252\343\202\253\343\202\277\343\202\253\343\203\212"\0 [UTF8 "abc\x{6f2
2}\x{5b57}\x{3072}\x{3089}\x{304c}\x{306a}\x{30ab}\x{30bf}\x{30ab}\x{30ca}"]
CUR = 33
LEN = 36
utf8:ひらがな

この場合も、FLAGS についたフラグは UTF8 であって、SHIFTJIS とか CP932 ではないのがわかる。

しかしながら、CP932 でプログラムを書くとなにかと面倒が多い。
有名なのが表示の表やソ連のソの後半が(ってよく言っていたのだが、ソ連ってずいぶん前に崩壊したな・・・)「\」とカブる 0x5c 問題だが、Perl の場合は他にも @ とかがカブるので "" が怖くて使えなくなる。
昔は EUC-JP で作れと言われているが、今様は UTF-8 を使うようだ。

ということでプログラムを元に戻して UTF-8 で保存し直しておく。

さて、UTF8 フラグの立ち座りを調べるだけであれば、Devel::Peek::Dump を使わなくても utf8::is_utf8 というのを使えばよい。
これは真偽値を返す。

#! perl
# expIsUtf8.pll -- is_utf8 を使って UTF8 フラグを調べる
use strict;
use warnings;
use Encode;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目のひから4文字、ひらがなと表示される
my $bytes = 'abc漢字ひらがなカタカナ';
print "bytes:", decode('utf8', substr($bytes, 9, 12)), " is_utf:", utf8::is_utf8($bytes), "\n";

my $utf8 = decode('UTF-8', $bytes);
print "utf8:", substr($utf8, 5, 4), " is_utf:", utf8::is_utf8($utf8), "\n";

__END__

最初に use utf8; をするとプログラム全体の挙動が変わってしまうので、上では is_utf8 メソッドを呼ぶために

実行。

C:\Marugoto>expIsUtf8.pl
bytes:ひらがな is_utf:
utf8:ひらがな is_utf:1

上では、decode されていると 1 が、そうでないと NULL が返っているようだが、無論それを利用してプログラムを書くのではなく、

 decode 'utf8', $bytes if utf8::is_utf $bytes;

的に真偽値として使うべきだ。

なお、この場合 UTF8 フラグの有無しか気にしていないのだから、

・プログラムが UTF-8 で書かれているときは
  decode('UTF-8', $bytes)

・プログラムが Shift_JIS で書かれているときは
  decode('shiftjis', $bytes)

といちいち第1引数を渡すのは面倒だ。
省略する方法はないだろうか。

UTF-8 の場合は、decode_utf8 というのを使うと省略できる。
下のプログラムを UTF-8 で保存して動かすと動く。

#! perl
# expIsUtf8.pll -- is_utf8 を使って UTF8 フラグを調べる
use strict;
use warnings;
use Encode;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目のひから4文字、ひらがなと表示される
my $bytes = 'abc漢字ひらがなカタカナ';
print "bytes:", decode_utf8(substr($bytes, 9, 12)), " is_utf:", utf8::is_utf8($bytes), "\n";

my $utf8 = decode_utf8 $bytes;
print "utf8:", substr($utf8, 5, 4), " is_utf:", utf8::is_utf8($utf8), "\n";

__END__

F:\Dropbox20091029\My Dropbox\Marugoto>expIsUtf8.pl
bytes:ひらがな is_utf:
utf8:ひらがな is_utf:1

でも、次のようなプログラムを Shift_JIS で動かすと怒られる。

#! perl
# expIsUtf8.pll -- is_utf8 を使って UTF8 フラグを調べる
use strict;
use warnings;
use Encode;

binmode STDOUT, ':encoding(cp932)'; # 出力はCP932に変更

#6文字目のひから4文字、ひらがなと表示される
my $bytes = 'abc漢字ひらがなカタカナ';
print "bytes:", decode_utf8(substr($bytes, 7, 8)), " is_utf:", utf8::is_utf8($bytes), "\n";

my $utf8 = decode_utf8 $bytes;
print "utf8:", substr($utf8, 5, 4), " is_utf:", utf8::is_utf8($utf8), "\n";

__END__

F:\Dropbox20091029\My Dropbox\Marugoto>expIsUtf8_sjis.pl
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 11.
"\x{0402}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 11.
"\x{70aa}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 11.
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 11.
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 11.
bytes:\x{fffd}\x{0402}\x{70aa}\x{fffd}\x{fffd} is_utf:
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 14.
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 14.
"\x{fffd}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 14.
"\x{0402}" does not map to cp932 at F:\Dropbox20091029\My Dropbox\Marugoto\expIsUtf8_sjis.pl line 14.
utf8:\x{fffd}\x{fffd}\x{fffd}\x{0402} is_utf:1

perldoc Encode によると、

$string = decode_utf8($octets [, CHECK]);
equivalent to $string = decode("utf8", $octets [, CHECK]) . The sequence of octets represented by $octets is decoded from UTF-8 into a sequence of logical characters. Not all sequences of octets form valid UTF-8 encodings, so it is possible for this call to fail. For CHECK, see "Handling Malformed Data".

ということだから、文字列から UTF8 フラグを倒すだけではなく、UTF-8 に変換してしまう。
\x{fffd} というのは、存在しない UTF-8 文字で、不当な UTF-8 文字を変換しようとしたので入れたものである。

他にも、utf8::decode とか Encode::_utf8_on というのもあったが、文字列が Shift_JIS だとうまくいかない。
やっぱり Perl(5.8 以降)の文字列は UTF-8 で書いておけということか。

なお、utf8::decode や Encode::_utf8_on は、Encode::decode_utf8 と違って不当な UTF-8 文字列をチェックしない。

あと、Encode::_utf8_on は、use Encode; していても Encode:: を付けて完全修飾しないといけないようだ。
[INTERNAL] だからかしら。

ていうか、http://perldoc.jp/docs/perl/5.10.0/perlunifaq.pod では UTF8 フラグは気にするな、is_utf8 は使うなと書いてあるが、そう言われると気になるのが人間というものね。

参考:
 ・http://blog.livedoor.jp/dankogai/archives/51004472.html
 ・http://blog.livedoor.jp/dankogai/archives/51290188.html

http://blog.livedoor.jp/dankogai/archives/51184112.html は勉強になる。
よくある「不当な Unicode 文字列をわざと書いて XSS 攻撃をする」というのを防ぐ方法が書いてある。
スポンサーサイト

<< 「まるごとEncode」解題(6)そして encode() | ホーム | 「まるごとEncode」解題(4)use bytes; の迷宮 >>


コメント

コメントの投稿


管理者にだけ表示を許可する

 ホーム 


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。