File::BOM via PerlIO::via

いまいじっているウェブアプリの設定は YAML 形式にしています。デザイナさんに設定ファイルをいじってもらったら「コンテンツがそっくり消えてしまいした〜(泣)」と言われてしまいました。ぱっとみ確認したところ形式が壊れたわけじゃなさそうだし…とよくよくみてみたら先頭に BOM がついてました*1。調べてみたところ,Plagger の謎 と同じ現象じゃないですか。

ということで,File::BOM なるモジュールと PerlIO::via インタフェースがあることを知りました。
(毎度毎度周回遅れな状況でため息がでてしまいます)


File::BOM はファイルの BOM からエンコーディングを判別して自動変換してくれるもので,PerlIO::via による ':via' インタフェースを使うとエンコーディングにかかわらず透過的に読み書きできます。といっても

  • BOM があるのは UTF-8/16/32(BE/LE) あたりなのでそれら以外は読み書きできない(色々自動判別したいなら,Encode::Guess なり Encode-Detect*2 を使う),
  • BOM がないとなにもしない(UTF-8 とみなされる)

んですが。

$buf は utf-8 bytes であって UTF-8 flagged 文字列ではないことに注意。

とのことなので,

use utf8;
use File::BOM;

open my $fh, '<:via(File::BOM)', $file
    or die "$file: $!";
my $str = join '', <$fh>;
close($fh);

print utf8::is_utf8($str), "\n";
print $str, "\n";

のように,同じようなスクリプトを書いてみたところ,

1
Wide character in print at test.pl line 20.
ファイルの中身

あらら UTF-8 flagged な文字列みたいですよ。


んーなんでだ,とちょっともぐって調べてみました*3

  • ドキュメントを読むと PerlIO::via の限界で read() を使うとバイトストリームのままらしい
  • で read に書き換えてやってみたけどやっぱり UTF-8 flagged な文字列だった
  • File::BOM::UTF という関数(see PerlIO::via)が,
    • Perl 5.8.7 以降だと 1 を 返す
    • 以前だと 0 を返す(バグ回避?)
  • ドキュメント古い?

結局 Perl 5.8.7 以降だと UTF-8 flagged で正解のようです。

Perl 5.8.6 以前だとレイヤ2段重ねで

open my $fh, '<:via(File::BOM):utf8', $file

ってすれば同じく UTF-8 flagged な文字列をとれるのではないかと。

*1:WinNT 系のメモ帳で UTF のファイルをいじると BOM を勝手につけるみたい

*2:残念ながら私の環境で Encode-Detect がインストールできたことがないですが

*3:File::BOM は入力ストリームが seekable か否かで判定ルーチン変えてて涙ぐましい