Perl で UTF-16 のテキストファイル"も"読めるようにする
Perl でテキストファイルを読んで処理するプログラムを書いて使ってもらっていたところ、ある日突然エラーになるという報告が。そこにあったのは、UTF-16 (BOM 付き) のテキストファイルでした。つらい。
Windows では UTF-16 が使われることが多く、また、UTF-8 だったとしても BOM が付いていて、UNIX なテキスト処理のプログラムに入れるとまともに処理されないことがあります。ついでに改行コードが CRLF だったりもします。
自分を含めプログラマだけが使うプログラムであれば、UTF-8 と LF の組み合わせを前提にしても問題ないでしょう。しかし、非プログラマが使うとなると、なかなかそうも言っていられません。ファイルを作る時に気をつけてもらうという手も無くはないですが、たくさんの人が関わるようになると限界があるというのも確かなので、読み取る側でうまいことやって使う側は何も気にしなくて済むようにしよう、となるわけです。
標準モジュールの Encode::Guess がデータから文字コードを推測してくれるとのことだったので、試してみたのですがどうもうまく動きませんでした。はじめに推測した結果で読み進めていくのではなく、行を読むたびに推測しているっぽく、1行が短いとうまく推測できないんだと思います。
先頭をざっくり見て、推測させてから読んでいけばうまくいくのかなと思い試したのが以下のコードです。割と期待していたのですが、BOM 付きの UTF-16 として推測されると、2行目を読むときにも BOM を読み取ろうとしてエラーになってしまいました。全部まとめて decode すれば解決しそうな気はするものの、ファイルサイズが非常に大きい想定なのでそれはできそうにありません。
結局、Windows 環境で生成される ASCII 非互換のファイルはだいたいが BOM 付きの Unicode であることに着目し、自分で BOM を見てエンコーディングを設定するようにしました。対応する文字コードは限られるものの、対応する範囲については推測するよりも確実に動いてくれて安心感があります。
これで平和な世の中になるといいですね。
追記 2016-03-12
tyru さんから、BOM の候補を長い順にソートしておかないと出現順によっては BOM の一部が短いパターンにマッチしてしまう場合があるとのご指摘をいただいたため、コードを修正しました。