書いた人:kwatch
YAML(YAML Ain’t Markup Language)とは、構造化されたデータを表現するためのフォーマットです。XML と目的は同じですが、XML と比べて「読みやすい」「書きやすい」「わかりやすい」という特徴があります。
また YAML では、データを以下の 3 つで表現します。XML と違って特別な概念が必要ないので、データの操作が非常に簡単です。
前回の初級編では、YAML の書き方を中心に説明し、XML との比較も行いました。今回の中級編では、YAML を扱うライブラリ Syck の機能を紹介します。また Syck を PHP と Python から使うための方法も説明します。
Syck とは、YAML を扱うためのライブラリです。作者である why the lucky stiff および協力者の尽力により、現在では Ruby、Python、PHP、Cocoa、Lua で使用できます(前回、OCaml もサポートしていると紹介しましたが、今はやめたそうです)。
Syck では、以下のことが行えます。ただし Ruby 以外の言語ではサポートしていない機能のほうが多いです。
why the lucky stiff は、はじめは YAML4R という pure Ruby な YAML ライブラリを作成しました。そのあと、高速化と他言語への移植を考えて、C 言語で書き直しました。それが Syck です。現在、YAML4R は Syck の一部となっています。
Ruby では 1.8 から Syck が標準ライブラリに含まれており、「requre ‘yaml’」とするだけで、Syck が使用できます。 Ruby 1.6 には含まれていませんので、ダウンロードページからダウンロードし、インストールしてください。
YAML ドキュメントを Ruby オブジェクトに変換するには、YAML.load() または YAML.load_file() を使います。
example01.rb : YAML ドキュメントをオブジェクトに変換する
## YAML ドキュメント
str = <<END
- name: Shiina
birth: 1998-01-01
age: 6
favorite:
- Thomas
- Pokemon
- name: Sumire
smoker: false
birth: 2000-02-02
age: 4
END
## オブジェクトに変換する
require 'yaml'
require 'pp'
yaml = YAML.load(str)
pp yaml
実行結果:
[{"name"=>"Shiina",
"age"=>6,
"birth"=>#<Date: 4901629/2,0,2299161>,
"favorite"=>["Thomas", "Pokemon"]},
{"name"=>"Sumire",
"smoker"=>false,
"age"=>4,
"birth"=>#<Date: 4903153/2,0,2299161>}]
Ruby オブジェクトを YAML 形式の文字列に変換するには、Object#to_yaml() または YAML.dump() を使います。
example02.rb : Ruby オブジェクトを YAML 形式に変換する
require 'yaml'
## ハッシュの配列を作成
hashlist = [
{
'name' => 'Shiina',
'age' => 6,
'birth' => Date.new(1999, 1, 1),
'favorite' => ['Thomas', 'Pokemon'],
},
{
'name' => 'Sumire',
'age' => 4,
'birth' => Date.new(2001, 2, 2),
'smoker' => false,
},
]
## YAML 形式の文字列に変換
str = YAML.dump(hashlist) # or hashlist.to_yaml()
puts str
実行結果:
---
-
name: Shiina
birth: 1999-01-01
age: 6
favorite:
- Thomas
- Pokemon
-
name: Sumire
smoker: false
birth: 2001-02-02
age: 4
本稿では、YAML を「構造化されたデータを表現するためのフォーマット」と紹介していますが、YAML は本来「データをシリアライズするためのフォーマット」です(YAML がわざわざ「マークアップ言語ではない(YAML Ain’t Markup Language)」と強調しているのは、このためです)。つまり Ruby オブジェクトを文字列へ変換することは、YAML の本来の使い方といえます。
オブジェクトを YAML 形式の文字列に変換するときには、以下の点に注意してください。
YAML.dump() では、ユーザ定義クラスの Ruby オブジェクトも YAML 形式に変換できます。
example03.rb : ユーザ定義クラスの Ruby オブジェクトを YAML 形式に変換
## クラスを定義
class Foo
def initialize(a, b, c)
@a = a
@b = b
@c = c
end
end
## Ruby オブジェクトを作成
child = Foo.new(100, Time.new, {'x'=>1, 'y'=>2})
parent = Foo.new(:foo, [10, 20, 30], child)
## YAML 形式に変換
require 'yaml'
str = YAML.dump(parent) ## or parent.to_yaml()
puts str
実行結果:
--- !ruby/object:Foo
a: :foo
b:
- 10
- 20
- 30
c: !ruby/object:Foo
a: 100
b: 2005-09-06 12:11:29.-12775448 +09:00
c:
x: 1
y: 2
この出力ファイルから Ruby オブジェクトを復元することもできます(これはシリアライズとは逆の、デシリアライズになります)。上の出力を「data.yaml」というファイル名で保存し、次のプログラムを実行してみてください。
example04.rb : YAML ドキュメントをユーザ定義クラスのオブジェクトに変換
## クラスを定義
class Foo
def initialize(a, b, c)
@a = a
@b = b
@c = c
end
end
## YAML ドキュメントを読み込む
require 'yaml'
obj = File.load_document('data.yaml')
## オブジェクトを表示 (「p」のかわりに「pp」を使う)
require 'pp'
pp obj
実行結果:
#<Foo:0x402d3b84
@a=:foo,
@b=[10, 20, 30],
@c=
#<Foo:0x402d3f94
@a=100,
@b="2005-09-06 12:11:29.-12775448 +09:00",
@c={"x"=>1, "y"=>2}>>
YAML や XML のようなデータをユーザ定義のオブジェクトに変換する機能は、XML の世界ではデータバインディングと呼ばれます。XML ではデータバインディング用のスキーマやツールを別途用意しなくてはいけませんが、YAML ではその必要はありません。ただし、ドキュメントの中にデータ型を明示しなければなりません。
YAML では、ひとつのファイルに複数の YAML ドキュメントを格納することができます。個々の YAML ドキュメントは「—」で区切ります。複数の YAML ドキュメントを含むデータを、YAML ではストリームといいます。
データをストリームとして読み込む、つまり複数のYAML ドキュメントを読み込むには、YAML.load_documents() または YAML.load_stream() を使います。引数 input には文字列または IO オブジェクト(File オブジェクトを含む)が指定できます。
example05.rb : ストリームから複数の YAML ドキュメントを読み込む
## YAML データ (「---」で区切って複数のデータを記述している)
str = <<END
---
name: Ruby
url: http://www.ruby-lang.org
---
name: Python
url: http://www.python.org
---
name: PHP
url: http://www.php.net
END
## YAML ドキュメントをひとつずつ読み込む
require 'yaml'
YAML.load_documents(str) do |doc|
p doc
end
## または複数の YAML ドキュメントをまとめて読み込む
#stream = YAML.load_stream(str) ## stream は YAML::Stream オブジェクト
#stream.documents.each do |doc|
# p doc
#end
実行結果:
{"name"=>"Ruby", "url"=>"http://www.ruby-lang.org"}
{"name"=>"Python", "url"=>"http://www.python.org"}
{"name"=>"PHP", "url"=>"http://www.php.net"}
ストリームを使う利点は次の2つです。
例えば、次の例ではデータ全体を読み込まないと YAML ドキュメントを得ることができません。また一度に多くのメモリを消費します。
- name: Foo
mail: foo@example.com
- name: Bar
mail: bar@example.com
- name: Baz
mail: baz@example.com
これに対し、次の例では各データを個別の YAML ドキュメントとしています。そのため、すべてを読み込まなくても YAML ドキュメントを得ることができ、かつメモリも何回かに分けて少しずつ消費するようになります。
---
name: Foo
mail: foo@example.com
---
name: Bar
mail: bar@example.com
---
name: Baz
mail: baz@example.com
またストリームを使って、ひとつのファイルに複数の YAML ドキュメントを書き込むには、YAML.dump_stream() または YAML::Stream#emit() を使います。
example06.rb : ストリームに複数の YAML ドキュメントを出力する
## 複数のデータ
hash_list = [
{ "lang"=>"Ruby", "url"=>"http://www.ruby-lang.org" },
{ "lang"=>"Python", "url"=>"http://www.python.org" },
{ "lang"=>"PHP", "url"=>"http://www.php.net" },
]
## ひとつのファイルに複数の YAML ドキュメントを出力する
require 'yaml'
str = YAML.dump_stream(*hash_list)
print str
## または YAML::Stream を使って次のようにする
#stream = YAML::Stream.new()
#hash_list.each { |hash| stream.add(hash) }
#str = stream.emit()
#print str
実行結果:
---
url: http://www.ruby-lang.org
lang: Ruby
---
url: http://www.python.org
lang: Python
---
url: http://www.php.net
lang: PHP
Syck には、YAML ドキュメントをパースしツリー構造のデータに変換する機能があります。そのためには、YAML.parse() または YAML.parse_file() を使います。これらはツリーを返します(具体的には YAML::Syck::Node オブジェクトを返します)。
複数の YAML ドキュメントを読み込むには、YAML.parse_documents() を使います。YAML.parse_stream() は定義されていないようです。
またツリーに対して、パスを指定してノードにアクセスすることができます。この機能は YPath といい、XML における XPath に相当します。パスにはメタキャラクタが指定できます。
example07.rb : ツリーと YPath
## YAML テキスト
str = <<END
langs:
- name: Ruby
url: http://www.ruby-lang.org
- name: Python
url: http://www.python.org
- name: PHP
url: http://www.php.net
END
## パースしてツリーを作成
require 'yaml'
tree = YAML.parse(str) # tree は YAML::Syck::Node オブジェクト
## パスを指定して検索
path_list = tree.search("/langs/*/name") # 戻り値はパスの配列
p path_list #=> ["/langs/0/name", "/langs/1/name", "/langs/2/name"]
## 特定のノードだけを取り出す
if RUBY_VERSION >= "1.8.3"
array = tree.select("/langs/*/name") # 戻り値はノードの配列
array.each do |node|
p node.transform #=> "Ruby" "Python" "PHP"
end
else
node = tree.select("/langs/*/name" ) # 戻り値はノードの配列を表すノード
name_list = node.transform # データに変換
p name_list #=> ["Ruby", "Python", "PHP"]
end
実行結果(Ruby 1.8.3):
["/langs/0/name", "/langs/1/name", "/langs/2/name"]
"Ruby"
"Python"
"PHP"
実行結果(Ruby 1.8.2):
["/langs/0/name", "/langs/1/name", "/langs/2/name"]
["Ruby", "Python", "PHP"]
なお YPath はまだ仕様が決まっているわけではないので、将来変更される可能性があります。また XPath ほど高度な条件は今のところサポートしていません。
Syck では、トランザクションを使ってデータをファイルに保存することができます。トランザクション機能では、エラーがなかった場合(=例外が発生しなかった場合)にのみデータをファイルに書き込みます。エラーがあった場合(=例外が発生した場合)はファイルへの書き込みを行いません。
トランザクション機能を使うには、YAML::Store クラスを使います。
YAML::Store クラスは PStore クラスを継承しています。実際のプログラムでは、PStore クラスから継承した以下のようなメソッドを使います。
次の例は、エラーがない場合です。
example09.rb : トランザクション処理
require 'yaml'
require 'yaml/store'
## YAML::Store オブジェクトを作成
filename = "store.yaml"
store = YAML::Store.new(filename)
## データの読み出しと格納
store.transaction do |hash|
## データの読み出し
count = hash["count"]
count = 0 if count == nil
puts "count = #{count}"
## データの格納
hash["count"] = count + 1
end
このスクリプトを何度か実行すると、データが保存されていることがわかります。
実行例:データが保存されている
$ ruby example09.rb
count = 0
$ ruby example09.rb
count = 1
$ ruby example09.rb
count = 2
次の例は、トランザクション中に例外を発生させた場合の例です。
example10.rb : トランザクション中に例外が発生した場合
require 'yaml'
require 'yaml/store'
## YAML::Store オブジェクトを作成
filename = "store.yaml"
store = YAML::Store.new(filename)
## トランザクション中に例外を発生
begin
store.transaction do |hash|
## データの読み出し
count = hash["count"]
count = 0 if count == nil
puts "count = #{count}"
## データの格納
hash["count"] = count + 1
## 例外を発生
raise "Transaction error!"
end
rescue => ex
# nothing
end
このスクリプトを何度か実行すると、データを格納したはずのにファイルには保存されていないことがわかります。これは、トランザクション中に例外が発生したためです。
実行例:データが保存されていない
$ ruby example10.rb
count = 0
$ ruby example10.rb
count = 0
$ ruby example10.rb
count = 0
「require ‘yaml’」を実行すると、Kernel.y() メソッドが追加されます。これはちょうと Kernel.p() と同じで、デバッグ用に使います。Kernel.p() が Object#inspect() を呼び出すのに対し、Kernel.y() は Object#to_yaml() を呼び出します。
example12.rb : オブジェクトを YAML 形式に変換して表示
obj1 = { 'A'=> 'aaa', 'B'=>[1,2,3], 'C'=>true }
obj2 = [ nil, false, '' ]
## オブジェクトを YAML 形式で表示
require 'yaml'
y obj1, obj2
実行結果:
---
A: aaa
B:
- 1
- 2
- 3
C: true
---
-
- false
- ''
実行結果における先頭の「—」は、Ruby 1.8.2 では出力されますが、1.8.3 だと出力されません。
Syck には標準で Ruby、PHP、Python、Cocoa 用のバインディングが付属します。CVS には Lua 用のバインディングもあります(OCaml のサポートはあきらめたそうです)。ここでは、PHP と Python のバインディングを紹介します。少し Ruby から離れますが、ご了承ください。
なお Syck のダウンロードは RubyForge から行ってください。CVSでのバージョンは 0.60 ですが、一般向けにダウンロード可能になっているのは原稿執筆時(2005年9月)で 0.55 ですので、以降では 0.55 を使って説明します。
PHPユーザのみなさん、こんにちは。ここでは PHP 用バインディングについて説明します。
Syck の PHP 用バインディングは、Syck を PHP で使えるようにするための拡張モジュールです。これをインストールすると、PHP から Syck が使えるようになります。 ただし Syck の全機能が使えるわけではなく、Syck 0.55 でサポートされているのは YAML ドキュメントの読み込みを行う関数「syck_load()」だけです。
今回は次のような環境で試しました。Fedora Core や Debian をお使いの方は、Apache と PHP の開発用パッケージもインストールするのを忘れないでください。
なお Fedora Core 4 には専用の rpm パッケージがあるようです。Fedora Core 4 をお使いの方は、こちらをお試してみるのもいいでしょう。
Syck のコンパイルとインストールの手順は次の通りです。README に記載の手順ではうまくいきませんので、下記の手順に従ってください。
### Syck のコンパイル
$ tar xzf syck-0.55.tar.gz
$ cd syck-0.55/
$ ./configure
$ make
$ make check
### PHP extension のコンパイルとインストール
$ ln -s lib include # or cp -r lib include
$ cd ext/php/
$ phpize
$ ./configure --with-syck=../..
$ make
$ sudo make install
make install を実行すると、拡張モジュールが例えば /usr/local/lib/php/extensions/no-debug-non-zts-20020429/syck.so にコピーされます(ディレクトリは環境によって異なります)。
このあと、PHP の設定ファイル「php.ini」の extension_dir に拡張モジュールが置かれているディレクトリを指定してください。なお拡張モジュールをシステムにインストールできない場合でも、拡張モジュールのパスを明示的に指定すれば使用できます。
/usr/local/lib/php.ini :
;; 拡張モジュールが置かれているディレクトリ
extension_dir = "/usr/local/php4/lib/php/extensions/no-debug-non-zts-20020429"
サンプルプログラムと実行例は次の通りです。
サンプルプログラム(syck-test.php):
<?php
// 拡張モジュールsyck.soを読み込む
if (! extension_loaded('syck')) {
if (! dl('syck.so')) { // or dl('/some/where/to/syck.so')
die('cannot load syck extension.');
}
}
// YAML 形式の文字列をデータに変換する
$str = <<<END
A: aaa
B:
B1: 123
B2: 456
C: true
END;
$doc = syck_load($str);
var_dump($doc);
?>
実行例 :
$ php syck-test.php
array(3) {
["A"]=>
string(3) "aaa"
["B"]=>
array(2) {
["B1"]=>
int(123)
["B2"]=>
int(456)
}
["C"]=>
bool(true)
}
Python ユーザのみなさん、こんにちは。ここでは Syck の Python バインディングについて説明します。
Python バインディングは、Syck を Python から使えるようにする拡張モジュールです。これをインストールすると、Python から Syck が使えるようになります。 ただし Syck 標準の Python バインディングだと YAML ドキュメントの読み込みしかできません。最近、読み込みと書き込みが行える「PySyck」という新しいバインディングが発表されましたので、こちらを使って見ましょう。
ダウンロード: http://xitology.org/pysyck/PySyck-0.55.1.tar.gz
インストール:
### Python と開発環境をインストール
$ apt-get install python python-dev
### Syck のコンパイル
$ tar xzf syck-0.55.tar.gz
$ cd syck-0.55/
$ ./configure
$ make
$ make check
### PySyck のコンパイルとインストール
$ cd ext/
$ tar xzf /tmp/PySyck-0.55.1.tar.gz
$ cd PySyck-0.55.1/
$ python setup.py build
$ ls -F build/lib.linux-i686-2.3/
_syck.so* syck/
$ sudo python setup.py install
python setup.py install を実行すると、/usr/lib/python2.3/site-packages/ 以下に Syck がインストールされます(Debian/GNU Linux の場合)。
サンプルプログラムと実行例は次の通りです。
サンプルプログラム(syck-test.py):
### YAML 形式の文字列からデータへ変換
import syck
str = """
one: 1
two: 2
three: 3
"""
print syck.load(str)
### データから YAML 形式の文字列へ変換
hash = {
'A': 'aaa',
'B': { 'B1': 'bbb1', 'B2': 'bbb2' },
'C': 'ccc',
}
print syck.dump(hash)
実行例 :
$ python syck-test.py
{'one': 1, 'three': 3, 'two': 2}
---
B:
B2: bbb2
B1: bbb1
A: aaa
C: ccc
ついでに、Syck 以外の YAML パーサも紹介してみます。どれも Ruby 以外の言語による実装です。どんどん Ruby から離れていくんですけど、るびまでこんな記事書いていいんでしょうか。編集長ごめんなさい。
PyYaml は、Python による実装です。かなり本格的に作られており、Python で使うなら Syck より高機能です。
インストール:
$ tar xzf PyYaml_0.32_MONEW.tar.gz
$ cd PyYaml/
$ sudo make install
サンプルプログラム(pyyaml-test.py):
import yaml
str = """
one: 1
two: 2
three: 3
"""
## YAML 形式の文字列をデータに変換
hash = yaml.load(str).next() # or yaml.l(str)
print hash
## ファイルから読み込む場合は次のようにする
#hash = yaml.loadFile('filename.yaml').next()
## データを YAML 形式の文字列に変換
print yaml.dump(hash)
実行例 :
$ python pyyaml-test.py
{'three': 3, 'two': 2, 'one': 1}
---
one: 1
three: 3
two: 2
Spyc は、PHP による実装です。YAML ファイルの読み込みと書き込みができます。
ダウンロード:spyc-0.1.1.tar.gz
インストール :
$ tar xzf spyc-0.1.1.tar.gz
$ cd spyc-0.1.1/
$ sudo cp spyc.php /usr/local/php4/lib/php/ # 適当なディレクトリにコピー
サンプルプログラム(spyc-test.php):
<?php
// spyc.php を読み込む
require_once('spyc.php');
// YAML 形式の文字列をデータに変換する
$str = <<<END
A: aaa
B:
- 123
- 456
- 789
C: true
END;
$doc = Spyc::YAMLLoad($str);
var_dump($doc);
// データを YAML 形式の文字列に変換する
$doc = array();
$doc["A"] = "aaa";
$doc["B"] = array(123, 456, 789);
$doc["C"] = TRUE;
$str = Spyc::YAMLDump($doc);
echo $str;
?>
実行結果 :
$ php spyc-test.php
array(3) {
["A"]=>
string(3) "aaa"
["B"]=>
array(3) {
[0]=>
int(123)
[1]=>
int(456)
[2]=>
int(789)
}
["C"]=>
bool(true)
}
---
A: aaa
B:
- 123
- 456
- 789
C: 1
よくみたら、TRUE が 1 になっています。たぶんバグですが、大目にみてあげてください。
なお Spyc を PHP5 で使う場合は、『SpycをPHP5で使う』(BMediaNode) もご覧ください。
A YAML parser written in Java は、Java による実装です。 他の実装と違い、SAX のようなイベント駆動型のパーサです。また型の判別は行っていません。現在は開発が中止されています。
YAML JavaScript は、JavaScriptによる実装です。 2003年2月以降、更新されていないようです。
dyayaml は、D 言語で書かれた実装です。D 言語、Delphi、Ada で使えるそうです。詳しくは Web ページをご覧ください。
ここでもう一度、YAMLの用語について説明しておきます。詳しくは YAML の仕様書をご覧ください。
本稿では、YAML という「仕様」を扱うための「実装」である Syck について説明しました。Syck を使うと、YAML ドキュメントをオブジェクトに変換したり、その逆にオブジェクトを YAML ドキュメントに変換できます。また YAML ドキュメントをツリーに変換して操作することもできます。
Syck の機能をまとめると次のようになります。
クラスとメソッド | 説明 |
---|---|
Object#to_yaml | オブジェクトを YAML 形式の文字列に変換する |
YAML.dump(object) | オブジェクトを YAML 形式の文字列に変換する |
YAML.load(input) | YAML ドキュメントをひとつだけ読み込んでオブジェクトに変換する |
YAML.load_file(filename) | ファイル名を指定して YAML ドキュメントを読み込み、変換する |
YAML.load_documents(input, &block) | YAML ドキュメントをひとつずつ読み込み、ブロックを実行する |
YAML.load_stream(input) | YAML ドキュメントを複数読み込んでオブジェクトに変換する |
YAML.parse(input) | YAML ドキュメントをひとつだけ読み込んでツリーに変換する |
YAML.parse_stream(input) | YAML ドキュメントを複数読み込んでツリーに変換する |
YAML::Syck::Node#search(path) | パスを検索し、マッチしたパスの文字列を配列で返す。 |
YAML::Syck::Node#select(path) | パスを検索し、特定のノードを取り出す |
YAML::Syck::Node#transform() | ノードをデータに変換する。 |
YAML::Store.new(filename) | ファイル名を指定して Store オブジェクトを作成する |
YAML::Store#transaction(block) | ブロックを実行し、例外がなければ YAML ドキュメントをファイルに保存する |
また、Syck の PHP バインディングと Python バインディングについて説明しました。これらをインストールすると、PHP や Python から Syck を利用することができます。ただし、機能は限定されます。
次回は実践編と題して、YAML を使ったアプリケーションを作成します。
名前:kwatch。三流プログラマー。猛虎優勝はうれしいはずなのに、強い阪神になぜか違和感を感じる今日この頃。「ダメ虎」という言葉に愛着を感じるのは自分がダメ人間だからか。最近のお気に入りは「萌えたコピペ」。