PerlのYAML::SyckとJSON::XSでutf-8ファイルを取り扱うポイント

考えてみると当たり前なんだけど、毎回考えるのが面倒なので動くコードをまとめておく。

Perlunicode周りの挙動が面倒なんだよなあ。Python(3)の方がシンプルでよいね。モダンな言語はやっぱりこの辺の取り扱いがスマートだわ。

YAML::Syck

UTF-8 エンコーディングの差異まとめ YAML::XS、YAML::Syck、YAML::Old、YAML、YAML::Tiny - Tociyuki::Diary

普通の用途としてはLoadFile/DumpFileでファイルI/Oをする時に使うだけでしょう。
$YAML::Syck::ImplicitUnicode = 1 を指定すると、Load/Dumpの際にutf-8 decode/encodeをしてくれるようになります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use YAML::Syck;
$YAML::Syck::ImplicitUnicode = 1;

my $names   = LoadFile(shift);
my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}
$ perl a.pl data.yaml
立花りく_//ネコプラ// is ネコプラ
坂下雅_なんキニ! is not ネコプラ
成瀬かおり_//ネコプラ// is ネコプラ
双葉ゆり(ラルムーン) is not ネコプラ
大島遥華_KATA☆CHU is not ネコプラ
高宮さくら_//ネコプラ// is ネコプラ
藍川みり_//ネコプラ// is ネコプラ
佐藤まりん_chuLa is not ネコプラ
もも is not ネコプラ

JSON::XS

[Perl] JSON モジュールの utf8 フラグ周りの仕様 tips 注意点: Kawanet Blog II

ややこしいですね。

JSON::XSは直接ファイルI/Oをしてくれるメソッドが用意されていないので、

  1. バイト列として読み込んでutf8(1)経由でデコード
  2. UTF-8文字列として読み込んでutf8(0)(デフォルト)でデコード

のどちらかで処理する必要があります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use Path::Class qw/file/;
use JSON::XS;

my $content = file(shift)->slurp;
my $names   = JSON::XS->new->utf8->decode($content);

# あるいはこちらでも可
# my $content = file(shift)->slurp(iomode => '<:encoding(utf-8)');
# my $names   = JSON::XS->new->decode($content);

my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}

YAMLと違ってJSONはファイルだけでなくWeb APIでも使われることが多いので、JSON::XSには常にUTF-8 encode済みの文字列を渡す(utf8-flag disabledとして使う)という運用のほうが分かりやすいかもしれません。

まあ、「入口でdecode、出口でencode」の原則さえ分かっていればやりやすい方法でいいかなと思いますが。