camel

すびばせん。それ、ドキュメントバグです。

PerlIO の encoding layer の fallback ではまった - daily dayflower
Encode - character encodings - search.cpan.org をみるとわかるように,FB_XMLCREF は XMLCREF | LEAVE_SRC なんだけど,いろいろ試行錯誤してるとどうやら LEAVE_SRC が悪さをするらしい。

$PerlIO::encoding::fallbackを指定するときは、FB_*を使ってはいけません

今からその理由を説明します。

Encode::LEAVE_SRCって何さ?

まずは、Encode::FB_(PERLQQ|HTMLCREF|XMLCREF)Encode::(PERLQQ|HTMLCREF|XMLCREF)の違い、すなわちFB_がついたフラグと、そうでないフラグの違いを見てみましょう。

#!/usr/bin/perl
use strict;
use warnings;
use Encode;
use utf8; # for literals;

binmode STDOUT, ':utf8';
{
    my $utf8  = 'dan小飼';
    my $ascii = encode 'ascii', $utf8, Encode::FB_XMLCREF;
    print '$utf8  = ', $utf8, "\n", '$ascii = ', $ascii, "\n";
}
{
    my $utf8  = 'dan小飼';
    my $ascii = encode 'ascii', $utf8, Encode::XMLCREF;
    print '$utf8  = ', $utf8, "\n", '$ascii = ', $ascii, "\n";
}

なんと、FB_なしの方では、元のデータが消えてしまいました。

実は、CHECKフラグがある時のEncodeの振る舞いは、元のデータを消すのがデフォルトなのです。なぜそうなっているかは後述します。それを防ぐのがEncode::LEAVE_SRCで、以下のようにFB_*はこのEncode::LEAVE_SRCがコミになっています。

is(Encode::FB_PERLQQ,   Encode::PERLQQ   | Encode::LEAVE_SRC);
is(Encode::FB_HTMLCREF, Encode::HTMLCREF | Encode::LEAVE_SRC);
is(Encode::FB_XMLCREF,  Encode::XMLCREF  | Encode::LEAVE_SRC);

通常の使用の場合、元データを「消して」しまうのはたいていの場合望ましくないので、通常はFB_*をつけるわけですが、それでは困る場合があるのです。

それが、PerlIOです。

PerlIOとバッファー

tie()でもしていない限り、中味が100%メモリーにあることが保証されているscalarと異なり、Filehandleの中味というのは、ファイル(ストリーム)の一部のみがバッファーの中にあります。そのおかげで、何GBもあるログファイルを、数MBのメモリーで処理できるわけですが、マルチバイト処理の場合、これは困った自体を引き起こす原因となります。

文字がバッファーのおかげで「ちぎれて」しまう可能性があるのです。

たとえば、バッファーが1024byteの時、小飼弾がずらーっと並んだファイルを読むとしましょう。この時、114個目の小飼弾はどうなるか? bytes::length('小飼弾') * 114 = 1026 なので、2byte余ることになります。つまり、こういうことです。

小         |飼         |弾         |
\xe5\xb0\x8f\xe9\xa3\xbc\xe5\xbc\xbe
== buffer==================]

何も考えずに処理していたら、あわれ弾は"\xe5"と"\xbc\xbe"にギロチンされてしまうのです。

そうならないように、PerlIOから呼び出されたEncodeは、decode()ないしencode()できるところまで処理した後、処理しきれなかった部分を、引数に書き込む形でPerlIOに戻します。上の例で元データが「消えていた」のは、実は「全て処理済み」だったのですね。PerlIOは、バッファーを空にするのではなく、処理しきれなかった分に追加する形でバッファーをまた埋め、埋まったら、あるいはeofが来たらまたEncodeを呼ぶというわけです。

その時にEncodeにどう振る舞うかを教えてあげるのが、$PerlIO::encoding::fallbackだったのです。

実はこのあたりの事情は、以下に書いてはあります。

こういうのもなんですが、きめの細かいコントロールが欲しい場合はPerlIOを経由するのはあまり望ましいとは言えないでしょう。PerlIOでサポートできないencodingも存在しますし(例えばISO-2022-KR)、fallbackの指定もごらんの通りパッケージ変数経由でやる必要がある。どうしてもという場合には、

$PerlIO::encoding::fallback &= ~Encode::LEAVE_SRC; # LEAVE_SRCを必ず落とす!

ようにしてください。

Dan the Encode Maintainer