[Perl] 最近話題になっている、UTF8の話はついていけん

こんんばんは。
なんだか、久しぶりにブログを書いておりますが。
なんとか生きています。
マジ忙しい。
いい加減、YAPC::ASIAの動画を見たいのですが、見るヒマもないです。
どこもかしこも火を噴いてる。
連休は最悪一日休みとかって状態になりかねません。
それはともかく、最近話題になっているUTF8の件はうぉっちしなければならないので、観察しに行っているのですが・・・わからないんですよ。

そもそもの発端というよりも、まとめは以下のサイトですね。

 PHP以外では、既にあたり前になりつつある文字エンコーディングバリデーション
  http://www.tokumaru.org/d/20090914.html

まあ、言わんとする所は大賛成で、そもそも異常な文字列はエラーにすべきなのですが、おじいさんはメール関係のシステムを触っているので、思考回路が違うのです。
 
 「UTF8のスパムメールで、UTF8の文字列が壊れているのはあたりまえ」

そういう状態でも、できる限り表示してやらねばなりません。
で、今回の主題は、文字エンコーディングのバリデーションちゃんとやれよな、という点にしぼられるかと思います。

で、文字エンコーディングのバリデーションは誤動作を起こさないために必須なのですが、誤動作と言われてもピンと来ません。
おじいさんとしては、なぜ必要か具体的に書けば、

 ・1byteが壊れているだけで、残りの文字すべてが読めなくなるのはまずい
  →ムリな場合もありますが、できるだけ救う必要があります。

 ・データを格納する際に色々な特殊文字をエスケープしたりしますが、そのエスケープをくぐり抜けてしまって、
  データ破壊が引き起こされる可能性がある。
  端的な例として言えば、ヘンなUTF8が入っていると、XMLパーサがwell formedではないと言って、エラーとか。

そんな所ですねえ。

では、具体的にどーせいというかというと、結局Perlの場合はEncodeにお任せになるわけですが。
その辺は、Dan先生の記事などを参照してくださいませ。

 http://blog.livedoor.jp/dankogai/archives/51290188.html
 http://blog.livedoor.jp/dankogai/archives/51184112.html


まあ、それで本題に入っていくわけですよ。

UTF8の非最短文字について


Encode::decode_utf8すれば、U+FFFDに変換される。
Encode::decodeでは、エラー、適当な文字に変換にできる。

適当な文字にはこんな感じ。

print encode( 'ascii', $utf8, sub {"〓"} ), "\n";



ところで、ソフトバンクマークが文字化けに見えるのは職業病ですか、そうですか。


半端な先行バイトで後続文字が巻き込まれる


たとえばですね、「あいうえお」というUTF8の文字列がありまして。
その時のコードは、

 "\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\x0a"
 →あいうえお

なるわけですね。
が、

 "\xe3\x81\x82\xe3\x81\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\x0a";

の時は、「い」がぶっ壊れているだけにもかかわらず、表示させると

 →「あ縺繧ヲお」

えーっと(^-^;)
これは、後ろの「う」まで破壊されているわけです。もしここに、終端文字などがあれば、それも巻き添えになってしまって、とんでもない事になります。
たとえば、フィールドが一個抜けるとか、オーバーランしてしまったりとか。
どうしようもない場合もあるのですが、UTF8の場合どうしようもない事はないわけですよ。先頭バイトを検出できるわけですから。

なので、正規表現でUTF8だけマッチさせて、その文字列だけを取り出すという事をやります。

 Unicodeをあつかうのは難しいなあ
  http://cast-a-spell.at.webry.info/200907/article_3.html

で、それをEncodeでできねぇかと延々と悩んでおりまして。
まあ、これもですね。
decode→encodeしておけば、\xfffdに変換はされます。

あー、そうか。
こうでございますね。


my $bytes2 = "\xe3\x81\x82\xe3\x81\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\x0a"; # あ??うえお

my $utf8 = decode( 'utf8', $bytes2 );
$utf8 =~ s/\x{fffd}//g; # 不正なバイトを削除
print encode( 'utf8', $utf8 ), "\n";


本来は、decodeのcheckにcoderefを入れたいのですけれど、なんだか上手く入りません。
Encodeのバージョンが低いせいかしらん。

まあ、エレガントではありませんが。

で、速度比較です。

#!/usr/local/bin/perl
use strict;
use Benchmark qw(:all);
use warnings;
use Encode;

my $str = "\xe3\x81\x82\xe3\x81\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\x0a" x 1000;  # あ??うえお

cmpthese (1000,{
    enc => sub {
        my $str2 = decode( 'utf8', $str );
        $str2 =~ s/\x{fffd}//g;
        $str2 = encode( 'utf8', $str2  );
    },
    reg  => sub{
        my $str2 = $str;
        my $str3 = "";
        $str2 =~ s/([\x00-\x7f]
                 |[\xc0-\xdf][\x80-\xbf]
                 |[\xe0-\xef][\x80-\xbf][\x80-\xbf]
                 |[\xf0-\xf3][\x80-\xbf][\x80-\xbf][\x80-\xbf]
                 |\xf4[\x80-\x8f][\x80-\xbf][\x80-\xbf])/ $str3 = $str3."$1"  ; "$1";/egx;
    },
});



result

     Rate  reg  enc
reg 135/s   -- -79%
enc 637/s 373%   --


それなりに効果があるようで(^-^;)

ブログ気持玉

クリックして気持ちを伝えよう!

ログインしてクリックすれば、自分のブログへのリンクが付きます。

→ログインへ

なるほど(納得、参考になった、ヘー)
驚いた
面白い
ナイス
ガッツ(がんばれ!)
かわいい

気持玉数 : 2

なるほど(納得、参考になった、ヘー) なるほど(納得、参考になった、ヘー)

この記事へのコメント

この記事へのトラックバック