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

PerlYAML::Any が対象にしている題に並べた 5 つの YAML モジュールの UTF-8 エンコーディング の扱いに、登場した当初から現在にいたるまで差異があり、単純に切り替えて使えないのが現状です。差があるのは仕方がないことで、それぞれの個性と割り切って差をまとめてみることにします。なお、UTF-8 の扱いはバージョンが上がると手が入ることがあるので、あくまで 2012 年 10 月現在のバージョンを対象にします。

YAML::XS    0.38
YAML::Syck  1.21
YAML::Old   0.81
YAML        0.84
YAML::Tiny  1.51

それから、YAML::Syck では ImplicitUnicode を真にしている場合だけを調べています。

$YAML::Syck::ImplicitUnicode = 1;

以下、Encode モジュールの POD の表記にならって、Perl が内部エンコーディングUTF-8 とみなしているときを string、ISO-8859-1 とみなしているときを octets とかき分けることにします。挙動チェックにもちいたテスト記述から抜き出すと次の部分が該当しています。YAML::Syck は他と違い、先頭のマイナス記号 3 つの直後にブランク文字を置くので、別に分けています。

# -*- coding: utf-8 -*-
use Test::More;
use utf8;

my $yaml_string = "---\n- こんにちわ\n";
my $syck_string = "--- \n- こんにちわ\n";
my $yaml_octets = encode_utf8("---\n- こんにちわ\n");
my $syck_octets = encode_utf8("--- \n- こんにちわ\n");

my $object_string = 'こんにちわ';
my $object_octets = encode_utf8('こんにちわ');

ok   utf8::is_utf8($yaml_string);
ok ! utf8::is_utf8($yaml_octets);
ok   utf8::is_utf8($object_string);
ok ! utf8::is_utf8($object_octets);

1. Load

Load は YAML が記述されたスカラを受け取り、Perl のオブジェクトへ変換するのに使います。YAML::XS は octets を受け取って、string へ変換します。string を渡すと die で例外を生じます。YAML::Tiny と、ImplicitUnicode オン時の YAML::Syck は、入力 YAML エンコーディング指定に関係なく、出力は必ず UTF-8 エンコーディングになります。YAML::Old と YAML は入力エンコーディングを出力も受け継ぎます。

YAML記述 octets string
YAML::XS string エラー
YAML::Syck string string
YAML::Old octets string
YAML octets string
YAML::Tiny string string

UTF-8 エンコーディングの日本語記述を含む YAML を Load 手続きで処理するときは、出力も UTF-8 エンコーディング扱いになってないと面倒なだけですので、YAML::XS 以外では、Encode::decode してから Load することにします。YAML::XS では、さらに encode しなおします。

use Encode;
use YAML::Any ();
use File::Slurp;

#- my $yaml_string = decode('UTF-8', read_file('sample.yml'), Encode::FB_CROAK|Encode::LEAVE_SRC);
my $yaml_string = decode('UTF-8', scalar read_file('sample.yml'), Encode::FB_CROAK|Encode::LEAVE_SRC);
my $obj = YAML::Any->implementation ne 'YAML::XS' ? YAML::Any::Load($yaml_string)
    : YAML::Any::Load(encode_utf8($yaml_string));

2. Dump

Dump は Perl のオブジェクトを受け取り、YAML で記述されたスカラへ変換します。Perl オブジェクトに入っているスカラが UTF-8 エンコーディング扱いのときは、シンプルな結果になります。YAML::XS だけが octets で、他は string で素直に YAML 記述へ変換されます。Perl オブジェクトが ISO-8859-1 扱いのとき、YAML::Syck は UTF-8エンコーディングされたオクテット列を 16 進エスケープした文字列に変換します。YAML::XS もバックスラッシュを使ってエスケープしているのではないかと思われますが、それにしてはオクテット数が合わず不思議です。YAML::Tiny は何がどうなっているのか理解できませんでした。

オブジェクト記述 octets string
YAML::XS ? octets
YAML::Syck 16進エスケープ string
YAML::Old octets string
YAML octets string
YAML::Tiny ? string

幸い、日本語の場合は、Perl オブジェクトでは UTF-8 エンコーディング扱いになっているでしょうから、文字列がどのようにエスケープされているか考えずに済みます。

use Encode;
use YAML::Any ();
use File::Slurp;

if (YAML::Any->implementation eq 'YAML::XS') {
    write_file('sample.yml', YAML::Any::Dump($obj));
}
else {
    write_file('sample.yml', encode_utf8(YAML::Any::Dump($obj)));
}

3. LoadFile

LoadFile は、ファイルから YAML 記述を読み込んで、Perl のオブジェクトへ変換します。引数にファイル名かオープン済みのファイルハンドルのいずれかを渡せます。ただし、YAML::Tiny はファイル名しか受け付けません。YAML::Old を除いて、UTF-8 エンコーディングで記述された YAML ファイルを変換すると、Perl オブジェクトのスカラも UTF-8 エンコーディング扱いになります。YAML::XS は、IO レイヤに UTF-8 エンコーディングを指定するとエラーになります。

YAML から変換された Perl オブジェクト内のスカラーエンコーディングの扱いを比較してみました。

  ファイル名 rawなファイルハンドル utf8なファイルハンドル
YAML::XS string string エラー
YAML::Syck string string string
YAML::Old octets octets string
YAML string string string
YAML::Tiny string 不可 不可

YAML::Tiny がファイル名しか受け付けないので、ファイル名から YAML をロードすることにするならば、YAML::Old だけ特別扱いします。

use YAML::Any ();

if (YAML::Any->implementation eq 'YAML::Old') {
    open mh($fh), '<:encoding(UTF-8)', $yaml_file_name or die "cannot open: $yaml_file_name: $!";
    $obj = YAML::Any::LoadFile($fh);
    close $fh;
}
else {
    $obj = YAML::Any::LoadFile($yaml_file_name);
}

4. DumpFile

DumpFile は、Perl オブジェクトを YAML に変換してファイルへ書き込みます。これも LoadFile 同様に、YAML::Tiny を除いて引数にファイル名かオープン済みのファイルハンドルのいずれかを渡せます。

ここでは、Perl オブジェクトの記述が string の場合に限って、比較します。この表で、出力が string になっているのは、Wide character in a print 警告が生じることを意味しています。YAML::Tiny を除いて、警告が出なくなっています。

  ファイル名 rawなファイルハンドル utf8なファイルハンドル
YAML::XS octets octets 内容破壊
YAML::Syck octets octets octets
YAML::Old octets octets octets
YAML octets octets octets
YAML::Tiny string 不可 不可

ファイルハンドルを渡すときは raw にしておいて、ファイル名を渡すとき含めて YAML::Tiny だけ特別扱いすることにします。

use Encode;
use File::Slurp;
use YAML::Any ();

if (YAML::Any->implementation eq 'YAML::Tiny') {
    write_file($yaml_file_name, encode_utf8(YAML::Any::Dump($obj)));
}
else {
    YAML::Any::DumpFile($yaml_file_name, $obj);
}