Encode.pmでエンコードできなかった文字の扱い

例えばこういうの*1

use strict;
use warnings;
use Encode;

my $utf8str = "あいらとふれあ、コラボっちゃお(ハート)";
Encode::_utf8_off($utf8str);
my $sjisstr = encode('cp932', decode('utf8', $utf8str));

print $sjisstr;
$ perl a.pl
あいらとふれあ、コラボっちゃお?

デフォルトの挙動としては、encodeの際に変換出来なかった文字は"?"になります。

If CHECK is 0, encoding and decoding replace any malformed character with a substitution character. When you encode, SUBCHAR is used.

Encode - character encodings in Perl - metacpan.org

SUBCHARというのが"?"になってるみたいですね。

文字として扱う分には"?"で問題ないのですが、これをファイル名として使っている場合は困る。Windowsでは"?"をファイル名にできないので、Perlからファイルは作れるものの、オープンしようとするとエラーになってしまいます。

で。調べてみて初めて知ったのですが、encode/decode系の関数には第三引数で上手く変換できなかった(malformed)文字に対するハンドラーを定義できるみたいです。

そしてEncode 2.12以降のバージョンではcode referenceも指定できるみたいです。これは便利すぎる。

ということで、encodeの第三引数をこんな感じにしてみます。

my $sjisstr = encode('cp932', decode('utf8', $utf8str), sub{sprintf "x%04X", shift});
$ perl a.pl
あいらとふれあ、コラボっちゃおx2661

おおー。これはすごい。

*1:コード中の"(ハート)"は中抜きハート"♡"が入ります