Kwalifyの3つのライブラリを紹介します。
Syck
Syckは、Cで書かれたYAML用ライブラリです。YAML用ライブラリとしては世界で最も広く使われており、YAMLの普及に大きく貢献しました。
Syckについて
インストール
Ruby 1.8以上であれば標準で付属しているため、インストールの必要はありません。
使い方
Syckの使い方はリスト1のとおりです。詳細はリファレンスマニュアル[1]をご覧ください。
リスト1 Syckの使い方(ex-rbsyck1.rb)
require 'yaml'
str = File.read('example.yaml')
data = YAML.load(str)
data = YAML.load_file('example.yaml')
File.open('example.yaml') do |f|
YAML.load_documents(f) do |data|
end
end
data = [ {'x'=>10, 'y'=>20}, {'x'=>15, 'y'=>25} ]
str = data.to_yaml
タグを変更する
yaml.dump()でインスタンスオブジェクトをYAML文字列に変換した場合、たとえば「!ruby/object:Color」のようなタグがつきます。しかしこれだとRuby限定になり、ほかの言語のYAMLパーサで読み込むとエラーになるため、データ交換する場合には都合が悪いです。
これを回避するには、以下のメソッドを使ってクラスに対応するタグを指定します(リスト2)。
- Object#taguri()
- オブジェクトを表すタグを返します。
- YAML.add_builtin_type(ytag) {|type, val| ...}
- データからオブジェクトを復元するブロックを登録します。
またObject#to_yaml_style()を再定義することで、フロースタイルで出力させることができます。
リスト2 クラスに対応するタグを指定する(ex-rbsyck1.rb)
require 'yaml'
class Color
def initialize(r, g, b)
@r = r; @g = g; @b = b
end
attr_accessor :r, :g, :b
def taguri
return 'tag:yaml.org,2002:color'
end
def to_yaml_style
return :inline
end
end
obj = Color.new(255, 128, 0)
puts YAML.dump(obj)
YAML.add_builtin_type('color') do |type, val|
YAML.object_maker(Color, val)
end
str = '!color {r: 255, g: 128, b: 0}'
p YAML.load(str)
日本語の扱い
Rubyの文字列に日本語が含まれる場合、YAMLドキュメントに変換すると、たとえば「'いろは'」が「!binary 44GE44KN44Gv」のようにバイナリデータとして変換されてしまいます。この問題は、根本的にはRubyにおいて文字列とバイナリデータの区別がないことが原因であり、そのためにSyckは安全を期して「ASCII以外の文字を(ある一定の比率で)含むような文字列はバイナリデータ」とみなしています。
しかしこれは不便である人も多いでしょう。この回避策としては、2つあります。1つ目はSyckのソースコードにパッチを当てることです。Rubyのソースコードをダウンロードし、リスト3のパッチを適用してコンパイルします。そしてリスト4のようString#is_binary_data?()を上書きしてバイナリとみなされないようにすると、日本語文字列が二重引用符で囲まれて出力されるようになります。
リスト3 日本語をエスケープしないようにするパッチ
--- ruby-1.8.6-p111/ext/syck/emitter.c Wed Jan 09 12:57:49 2008 +0900
+++ ruby-1.8.6-p111/ext/syck/emitter.c Wed Jan 09 14:07:38 2008 +0900
@@ -778,7 +778,8 @@ syck_emitter_escape( SyckEmitter *e, cha
int i;
for( i = 0; i < len; i++ )
{
- if( (src[i] < 0x20) || (0x7E < src[i]) )
+
+ if( 0 )
{
syck_emitter_write( e, "\\", 1 );
if( '\0' == src[i] )
.--------------------
リスト4 YAML.dump()で日本語を出力する
require 'yaml'
class String
def is_binary_data?
false
end
end
puts YAML.dump(['alphabet', '日本語'])
もう1つの方法は、Ya2YAMLを使うことです。Ya2YAMLをインストールして$KCODEを'utf8'に設定すると、文字列をUTF8として扱ってくれます(リスト5)。Ya2YAMLはRubyGemsを使って「gem install ya2yaml」とすればインストールできます。
ただし、循環参照したデータには対応していません。
リスト5 ya2yamlを使うと、日本語がUTF8として出力される(ex-ya2yaml1.rb)
str = 'いろは'
require 'yaml'
p str.to_yaml
require 'rubygems'
require 'ya2yaml'
$KCODE = 'utf8'
p str.ya2yaml
[Object, String, Array, Hash].each do |klass|
klass.class_eval { alias to_yaml ya2yaml }
end
p str.to_yaml
不具合
筆者が試したところ、以下のような不具合がありました(Ruby 1.8.6で確認)。
日付(タイムスタンプ)の解析に不具合があり、「2008-01-01T12:34:56Z」という形式しか解釈されない(リスト6)。
対応するアンカーのないエイリアスがあっても、エラーにならない。
フロースタイルのデータにエイリアスをつけるとエラーになる場合がある(リスト7)。
リスト6 仕様上は正しいがSyckでは文字列と解釈される形式
- 2008-01-01T12:34:56Z
- 2008-01-01 12:34:56
- 2008-01-01 12:34:56Z
- 2008-01-01T12:34:56
- 2008-01-01 12:34:56 +9
- 2008-01-01 12:34:56.0
リスト7 Syckで読み込むとエラーになる例
- &m1
{a: 10}
- &s1
Foo
その他
Syckは、Ruby向けに次のような独自機能を実装しています。
- 「:foo」のようなデータは、文字列ではなくRubyのSymbolオブジェクトに変換されます。
RbYAML
RbYAMLは、pure Rubyで実装されたYAMLライブラリです。もともとは、Syckが拡張ライブラリであるためJRubyで使えないので、JRubyでも使えるようなPure RubyのYAMLライブラリが必要だったために開発されました[3]。またRbYAMLは、Python用のライブラリであるPyYAMLをRubyに移植したものです。
Syckとの最大の違いは、SyckがYAML 1.0準拠なのに対し、RbYAMLはYAML 1.1準拠だという点です。そのため、SyckとRbYAMLとでは仕様が異なる点があります。
ただし、循環したデータ構造が扱えないなど、Syckと比べて不具合が多いです。
RbYAMLについて
インストール
RubyGemsをインストールしてから「gem install rbyaml」でインストールしてください。
使い方
使い方は、クラス名がYAMLでなくRbYAMLとなっている点以外はSyckとほぼ同じです(リスト8)。
リスト8 RbYAMLの使い方(ex-rbyaml1.rb)
require 'rubygems'
require 'rbyaml'
str = File.read('example.yaml')
data = RbYAML.load(str)
data = RbYAML.load_file('example.yaml')
File.open('example.yaml') do |f|
RbYAML.load_documents(f) do |data|
end
end
data = [ {'x'=>10, 'y'=>20}, {'x'=>15, 'y'=>25} ]
str = data.to_yaml
タグを変更する
RbYAML.dump()でインスタンスオブジェクトをYAML文字列に変換した場合、たとえば「!ruby/object:Color」のようなタグがつきます。しかしこれだとRuby限定になり、ほかの言語のYAMLパーサで読み込むとエラーになるため、データ交換する場合には都合が悪いです。
これを回避するには、以下のメソッドを使ってクラスに対応するタグを指定します(リスト9)。
- Object.to_yaml_node(representer)
- オブジェクトをノード(RbYAML::Node)に変換します。
- RbYAML._add_constructor(ytag, symbol)
- ノード(RbYAML::Node)からオブジェクトを復元するメソッドをRbYAML::Constructorクラスに定義し、そのメソッド名を登録します。
リスト9 タグを変更する(ex-rbyaml2.rb)
require 'rubygems'
require 'rbyaml'
class Color
def initialize(r, g, b)
@r = r; @g = g; @b = b
end
attr_accessor :r, :g, :b
def to_yaml_node(representer)
node = super
node.tag = '!color'
node.flow_style = true
node
end
end
color = Color.new(255, 128, 0)
puts RbYAML.dump(color)
class RbYAML::Constructor
def construct_ruby_color(node)
return construct_ruby('Color', node)
end
end
RbYAML._add_constructor('!color', :construct_ruby_color)
obj = RbYAML.load('!color {r: 255, g: 128, b: 0}')
p obj
日本語の扱い
日本語を含むYAMLドキュメントを読み込む場合、RbYAML.load()の引数に文字列を指定した場合は問題なく読み込めますが、FileオブジェクトやIOオブジェクトを指定するとエラーになります。
書き出しについては、たとえば「'いろは'」が「!!binary 44GE44KN44Gv」のようにバイナリデータとして出力されてしまいます。これを回避する方法は、現状では提供されていません。
不具合
RbYAMLは古いバージョンのPyYAMLをもとにしているためか、いろいろとバグや制限が多いです。筆者が試したところ、以下のようなバグや制限が見つかりました。
- YAML.load()
- 循環構造を持つデータが解釈できない
- 値がない場合にエラーが発生する(リスト10)
- 文字列をブロックスタイルで記述したとき、最終行の改行が取り除かれる(リスト11)
- マッピングのデフォルト値を認識しない
- 「'foo\n'」が「"foo\n"」ではなく「"foo92n"」になる
- YAML.dump()
リスト10 値がないとエラー
- abc
-
- 123
リスト11 ブロックスタイルだと最終行の改行が取り除かれる
- |
text
その他
作者のOla Biniは、現在はJRubyで忙しく、RbYAMLは放置気味です。そのため、RbYAMLの不具合が修正される可能性は低いでしょう。
Kwalify
Kwalifyは、YAML用のパーサ、スキーマバリデータ、データバインディングが統合されたライブラリです。
YAMLドキュメントのデータ構造を形式的に記述したものをスキーマといいます。そのスキーマ定義に従って、YAMLドキュメントのデータが正しい構造かどうかをチェックすることをスキーマバリデーションといい、それを実行するツールをスキーマバリデータといいます。またYAMLドキュメントのデータは、通常は(Rubyであれば)HashやArrayなどに変換されますが、それらのかわりにユーザが指定したクラスのオブジェクトに変換するのをデータバインディングといいます。
Kwalifyは、これらが統合されたライブラリです。Kwalifyを使うと、YAMLドキュメントをパースするときに、スキーマバリデーションとデータバインディングを同時に行うことができます。
Kwalifyについて
インストール
RubyGemsをインストールしている場合は、「gem install kwalify」でインストールできます。RubyGemsをインストールしていない場合は、以下の手順でインストールしてください。
$ tar xjf kwalify_0.7.0.tar.bz2
$ cd kwalify_0.7.0
$ sudo ruby setup.rb install
使い方
KwalifyのYAMLパーサは、スキーマ定義を与えることで、パースしながらスキーマバリデーションとデータバインディングを同時に行うことができます(リスト12)。スキーマ定義などの詳細についてはドキュメントを参照してください。
リスト12 パースと同時にスキーマバリデーションとデータバインディングを行う
require 'kwalify'
schema_def = <<END
type: map
class: Config
mapping:
"host": {type: str, default: localhost}
"port": {type: int, default: 80}
"user": {type: str, required: true}
"pass": {type: str, required: true}
END
require 'kwalify/util/hashlike'
class Config
include Kwalify::Util::HashLike
def initialize
@host = 'localhost'
@port = 8080
end
attr_accessor :host, :port, :user, :pass
end
schema = Kwalify::Yaml.load(schema_def)
validator = Kwalify::Validator.new(schema)
parser = Kwalify::Yaml::Parser.new(validator)
parser.data_binding = true
str = <<END
port: 8080
user: user1
pass: password1
END
ydoc = parser.parse(str)
p ydoc
errors = parser.errors
if errors && !errors.empty?
for e in errors
puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
end
end
Kwalifyでは、スキーマ定義からRubyのクラス定義を生成する機能もあります。詳しくはドキュメントを参照してください。
またそのほかの機能として、KwalifyのYAMLパーサではアンカーより先にエイリアスを書くことができます(リスト13)。これは、参照構造が複雑なデータを記述するときに大変便利です。
リスト13 KwailfyでのYAMLパーサの使い方
str = <<END
- &a1
val: 10
next: *a2
- &a2
val: 20
next: *a3
- &a3
val: 30
next: *a1
END
require 'kwalify'
ydoc = Kwalify::Yaml.load(str, :preceding_alias=>true)
require 'pp'
pp ydoc
タグを変更する
KwalifyのYAMLパーサは、タグに対応していません。
日本語の扱い
日本語を含むYAMLドキュメントの読み込みについては、問題ありません。書き出しについては、Kwalifyはデータをdumpする機能はありません。
不具合
以下のような不具合があります。
- タグに未対応
- オブジェクトをdumpする機能がない
- キーと同じインデント幅でシーケンスが続くとパースできない(リスト14)
リスト14 Kwalifyでパースできない例
key:
- 10
- 20
- 30
その他
KwalifyのYAMLパーサは、よく使われる機能だけを実装しており、あまり使われない機能は実装されていません。よってデータ交換を目的とするなら、あまり使うべきではないでしょう。
しかしスキーマバリデーションやデータバインディングは大変便利な機能です。またアンカーより先にエイリアスを書ける機能も、複雑なデータ構造を記述する場合には実に重宝します。このため、データ交換には向きませんが、設定ファイルやデータファイルを読み込むときに使うとよいでしょう。
またKwalifyのスキーマバリデーションやデータバインディングは、YAMLだけでなくJSONにも活用できます。これは、JSONがYAMLのサブセットと見なせるためです。興味のある人は試してみてください。