プログラマーのための YAML 入門 (初級編)

はじめに

YAML とは、構造化されたデータを表現するためのフォーマットです。 目的は XML と似ていますが、XML と比べて「読みやすい」「書きやすい」「わかりやすい」という利点があります。

また YAML はあくまで「仕様」であり、それを処理するライブラリの「実装」が必要です。 Ruby 1.8 では Syck というライブラリが標準で含まれています。

本稿では 3 回に渡り、YAML および Ruby のライブラリである Syck について解説します。

プログラマーのための YAML 入門 (初級編) − YAML の書き方と、XML との比較について
YAML は構造化されたデータを表現するためのフォーマットであり、主にインデントを使って構造を表現します。初級編では、YAML フォーマットの書き方を中心に説明します。また XML と YAML の比較も行います。
プログラマーのための YAML 入門 (中級編) − Syck の機能について
Syck には、YAML フォーマットをツリー構造に変換して XPath のようにトラバースする機能や、トランザクションを使ってデータをファイルに保存する機能などがあります。中級編では、このような Syck の機能について解説します。
プログラマーのための YAML 入門 (実践編) − 実用的なアプリケーションの作成例
YAML はアプリケーションのデータストア層に適していますが、ビュー層には eRuby が適しています。実践編では、YAML データを読み込んで加工し、eRuby で出力する実用的なアプリケーションを作成します。

本稿では Ruby 1.8 以上を前提にします。Ruby 1.6 系をお使いの方は、Syck をインストールしてください。

なお XML と YAML の比較についてだけ興味のある方は、セクション『XML との比較』をご覧ください。

YAML について

まず、YAML について簡単に説明します。

YAML (YAML Ain't Markup Language) とは、構造化されたデータを表現するためのフォーマットです。YAML は次のような用途に向いています。

  • 各種設定ファイル
  • データ保存用 (シリアライゼーション)
  • データ交換用フォーマット
  • ログファイル

YAML の目的は XML と似ていますが、XML と比べて次のような利点があります (YAML と XML との詳しい比較はセクション『XML との比較』をご覧ください)。

  • 読みやすい ―― YAML ではインデントを使ってデータの階層構造を表すため、人間にとって非常に読みやすいです。
  • 書きやすい ―― YAML では XML のような終了タグが必要ないので、人間にとって非常に書きやすいです。
  • わかりやすい ―― YAML ではデータを「配列」「ハッシュ」「スカラー (数値や文字列や真偽値)」だけで表すため、人間にとって非常に理解しやすく、またプログラミングも容易です。

YAML の仕様では、正式には配列ではなく「シーケンス (Sequence)」、ハッシュではなく「マッピング (Mapping)」という用語を使用しています。これは、「配列」や「ハッシュ」という用語がデータ構造の具体的な実装を表すため、仕様書で使うのはふさわしくないと判断されたのだと思われます。しかし本稿ではわかりやすさを優先して、Ruby ユーザになじみの深い「配列」や「ハッシュ」を用語として使用します。正式な用語については、YAML の仕様書である『YAML Ain't Markup Language Version 1.1』をご覧ください。

また YAML はあくまで「仕様」であり、それを処理するライブラリの「実装」が必要です。Ruby 1.8 では Syck というライブラリが標準で含まれています。Syck の本体は C 言語で書かれており、それを Ruby などのスクリプト言語から使えるようになっています。現時点では Syck は以下の言語に対応しています (対応状況については、Ruby 以外の言語ではまだ使えない機能も多いみたいです)。

  • Ruby
  • Python
  • PHP
  • OCaml

〔追記 (2005-09-28) OCamlのサポートはなくなりました。また新たに Lua と Cocoa がサポートされたようです。〕

そのほかの実装については YAML ホームページのダウンロードページをご覧ください。

基本的な書き方

YAML では、主に次の 3 つの組み合わせでデータを表現します。

  • 配列
  • ハッシュ
  • スカラー(文字列、数値、真偽値など)

このセクションではまず配列とハッシュの書き方を説明し、そのあとにスカラーについて説明します。

配列

YAML では、行頭に「-」をつけることで配列を表現します。「-」のあとには半角スペースを入れてください。

- aaa
- bbb
- ccc

また YAML ファイルを読み込んで表示する Ruby スクリプト「print-yaml.rb」は次のようになります。

#!/usr/bin/ruby

###
### print-yaml.rb
###
### 使い方: ruby print-yaml.rb file.yaml [file2.yaml ...]
###
require 'yaml'
require 'pp'

str  = ARGF.read()      # 入力をすべて読み込む
data = YAML.load(str)   # パースする
pp data                 # データを表示する

「YAML.load()」は YAML データを Ruby オブジェクトに変換するメソッドであり、引数として文字列または IO オブジェクトが指定できます。

実行するには次のようにします。

$ ruby print-yaml.rb example01.yaml

なお上記のスクリプトを使わなくても、次のように 1 行で済ませることもできます。

$ ruby -r yaml -r pp -e 'pp YAML.load(ARGF.read())' example01.yaml

実行結果は次のようになります。 YAML のデータが配列になっていることがわかります。

["aaa", "bbb", "ccc"]

半角スペースでインデントすると、配列をネストさせることができます。タブ文字は使えませんので注意してください。

- aaa
-
  - b1
  - b2
  -
    - b3.1
    - b3.2
- ccc

実行結果は次のようになります。

["aaa", ["b1", "b2", ["b3.1", "b3.2"]], "ccc"]

なお箇条書きのように書くことはできません。例えば次の例では、配列の要素が「"A"」なのか「["a1","a2","a3"]」なのか決められません (実際にはすべて文字列として解釈されます)。

- A
  - a1
  - a2
  - a3

実行結果は次のようになります。

["A - a1 - a2 - a3"]

ハッシュ

ハッシュは「キー: 値」の形式で表します。コロン「:」のあとに半角スペースを 1 つ以上入れてください (タブ文字は使えません)。

A: aaa
B: bbb
C: ccc

実行結果:

{"A"=>"aaa", "B"=>"bbb", "C"=>"ccc"}

半角スペースでインデントすることで、ハッシュをネストさせることができます。

A: aaa
B:
  B1: bbb1
  B2: bbb2
C: ccc

実行結果:

{"A"=>"aaa", "B"=>{"B1"=>"bbb1", "B2"=>"bbb2"}, "C"=>"ccc"}

また次のように書くことはできません。なぜなら、キー「"A"」に対応する値が「"foo"」なのか「{"a1"=>"bar","a2"=>"baz"}」なのか分からないからです。

A: foo
  a1: bar
  a2: baz

実行結果:

$ ruby print-yaml.rb exampe13.yaml
/usr/lib/ruby/1.8/yaml.rb:39:in `load': parse error on line 1, col 5: `  a1: bar
' (ArgumentError)
        from /usr/lib/ruby/1.8/yaml.rb:39:in `load'
        from print-yaml.rb:11

配列とハッシュのネスト

配列とハッシュはお互いにネストさせることができます。

次の例は、配列の中にハッシュをネストさせる例です。

- name:  Hanako
  email: flower@mail.com
- name:  Sumire
  email: garnet@mail.net
- name:  Momoko
  email: peach@mail.org

実行結果:

[{"name"=>"Hanako", "email"=>"flower@mail.com"},
 {"name"=>"Sumire", "email"=>"garnet@mail.net"},
 {"name"=>"Momoko", "email"=>"peach@mail.org"}]

次はハッシュの中に配列をネストさせる例です。

names:
  - Hanako
  - Sumire
  - Momoko
emails:
  - flower@mail.com
  - garnet@mail.net
  - peach@mail.org

実行結果:

{"emails"=>["flower@mail.com", "garnet@mail.net", "peach@mail.org"],
 "names"=>["Hanako", "Sumire", "Momoko"]}

ネストはいくらでも深くできます。次の例は、「ハッシュをもつ配列をもつハッシュをもつ配列をもつハッシュ」の例です。

database:
  host:     localhost
  port:     5432
  user:     dbuser
  passwd:   dbpasswd

tables:

  - name:   users
    desc:   User master table
    columns:
      - name:     id
        type:     integer
        primary-key:  true
      - name:     user
        type:     varchar(32)
        not-null: true
        unique:   true
      - name:     password
        type:     varchar(32)
        not-null: true
      - name:     group_id
        type:     integer
        not-null: true

  - name:   groups
    desc:   Group master table
    columns:
      - name:     id
        type:     integer
        primary-key:  true
      - name:     group
        type:     varchar(32)
        not-null: true
        unique:   true

実行結果:

{"tables"=>
  [{"columns"=>
     [{"name"=>"id", "type"=>"integer", "primary-key"=>true},
      {"name"=>"user",
       "not-null"=>true,
       "type"=>"varchar(32)",
       "unique"=>true},
      {"name"=>"password", "not-null"=>true, "type"=>"varchar(32)"},
      {"name"=>"group_id", "not-null"=>true, "type"=>"integer"}],
    "name"=>"users",
    "desc"=>"User master table"},
   {"columns"=>
     [{"name"=>"id", "type"=>"integer", "primary-key"=>true},
      {"name"=>"group",
       "not-null"=>true,
       "type"=>"varchar(32)",
       "unique"=>true}],
    "name"=>"groups",
    "desc"=>"Group master table"}],
 "database"=>
  {"user"=>"dbuser", "passwd"=>"dbpasswd", "port"=>5432, "host"=>"localhost"}}

ブロックスタイルとフロースタイル

今まで見てきたような、インデントを使って構造を表す書き方を「ブロックスタイル」といいます。ブロックスタイルではデータを複数行にわたって記述します。

これに対し、「{}」や「[]」を使って構造を表すこともできます。この書き方を「フロースタイル」といいます。フロースタイルではデータを 1 行にまとめて記述できます。

次の例はフロースタイルによる配列の例です。コンマ「,」のあとには半角空白を入れてください。またエラーになる場合は Ruby のバージョンを 1.8.2 にあげてみてください。

[aaa, bbb, ccc]

実行結果:

["aaa", "bbb", "ccc"]

次の例はフロースタイルによるハッシュの例です。コロン「:」のあとには半角空白を入れてください。

{ A: aaa, B: bbb, C: ccc }

実行結果:

{"A"=>"aaa", "B"=>"bbb", "C"=>"ccc"}

フロースタイルでもネストさせることができます。またブロックスタイルとの併用もできます。

route1: [ {x: 0, y: 0}, {x: 0, y: 5}, {x: 5, y: 5} ]
route2:
  - {x: 0, y: 0}
  - {x: 5, y: 0}
  - {x: 5, y: 5}

実行結果:

{"route1"=>[{"x"=>0, "y"=>0}, {"x"=>0, "y"=>5}, {"x"=>5, "y"=>5}],
 "route2"=>[{"x"=>0, "y"=>0}, {"x"=>5, "y"=>0}, {"x"=>5, "y"=>5}]}

なおフロースタイルを使うと、JSON と同じにすることができます。つまり JSON を YAML としてみなすことができます。 詳しくは YAML is JSON をご覧ください。

{ "menu": {
    "id": "file",
    "value": "File:",
    "popup": {
      "menuitem": [
        {"value": "New", "onclick": "CreateNewDoc()"},
        {"value": "Open", "onclick": "OpenDoc()"},
        {"value": "Close", "onclick": "CloseDoc()"}
      ]
    }
  }
}

実行結果:

{"menu"=>
  {"popup"=>
    {"menuitem"=>
      [{"value"=>"New", "onclick"=>"CreateNewDoc()"},
       {"value"=>"Open", "onclick"=>"OpenDoc()"},
       {"value"=>"Close", "onclick"=>"CloseDoc()"}]},
   "id"=>"file",
   "value"=>"File:"}}

コメント

「#」から行末までがコメントになります。範囲コメントはありません。

# コメント行
# コメント行
- aaa
- bbb        # これもコメント行
- ccc

なお「\#」のようにバックスラッシュでエスケープできます。

スカラーとデータ型

〔追記 (2005-10-01) : 真偽値のパターンとして "on/off" を追加。Symbol のパターンを追加。Syck のソースである lib/impplicit.re についての言及を追加。 〕

配列やハッシュのような、他のデータを要素としてもつデータは「コレクション (Collection)」といいます。それ以外のデータ (数値や文字列など) は「スカラー (Scalar)」といいます。

YAML では、以下のデータ型を自動的に判別します。

  • 整数
  • 浮動小数点
  • 真偽値 (true, yes, false, no)
  • Null値 (null, ~)
  • 日付 (yyyy-mm-dd)
  • タイムスタンプ (yyyy-mm-dd hh:mm:ss [+-]hh:mm)

これら以外は文字列として認識されます。また引用符「'」や二重引用符「"」で囲むと、強制的に文字列として認識されます。

decimal1:  123                           # 整数 (10 進数)
decimal2:  1,234,567,890                 # 整数 (10 進数)
octal:     0644                          # 整数 (8 進数)
hexa:      0xFF                          # 整数 (16 進数)
float1:    0.05                          # 浮動小数点
bool1:     true                          # 真
bool2:     yes                           # 真
bool3:     on                            # 真
bool4:     false                         # 偽
bool5:     no                            # 偽
bool6:     off                           # 偽
null1:     ~                             # Null 値
null2:     null                          # Null 値
date:      2005-01-01                    # 日付
stamp:     2005-01-01 00:00:00 +09:00    # タイムスタンプ
str1:      'true'                        # 文字列
str2:      "2005"                        # 文字列
symbol:    :foo                          # シンボル (Syck の独自機能)

実行結果 (順番は変更しています) :

{"decimal1"=>123,
 "decimal2"=>1234567890,
 "octal"=>420,
 "hexa"=>255,
 "float1"=>0.05,
 "bool1"=>true,
 "bool2"=>true,
 "bool4"=>false,
 "bool5"=>false,
 "bool6"=>false,
 "null1"=>nil,
 "null2"=>nil,
 "date"=>#<Date: 4906743/2,0,2299161>,
 "stamp"=>Sat Jan 01 00:00:00 GMT+9:00 2005,
 "str1"=>"true",
 "str2"=>"2005",
 "symbol"=>:foo}

また「!」を使うと、データの型を明示的に指定できます。これを使うと、Ruby における Regexp や Symbol のように処理系の言語に依存した型も指定できます。

- !str 123            # 文字列
- !pairs              # 組 (要素が 2 つの配列)
  - A: aaa
  - B: bbb
- !ruby/sym  foo      # Ruby の Symbol
- !ruby/regexp /^$/   # Ruby の Regexp

実行結果:

["123", [["A", "aaa"], ["B", "bbb"]], :foo, /^$/]

どのようなデータ型が指定できるかは『Language-Independent Types for YAML』をご覧ください。ただし、そこで説明されたすべてのデータ型に Syck が対応しているわけではないようです。

またデータ型のパターンは、Syck のソースでは lib/implicit.re で定義されています。独自のパターンを追加するには、このファイルを変更して Syck を最コンパイルする必要があり、Ruby スクリプトから追加する方法はありません。

複数行の文字列

文字列も、フロースタイルとブロックスタイルがあります。今まで見てきたのはフロースタイルであり、 1 行に収まる文字列の場合に適しています。 ブロックスタイルは複数行にわたる文字列に適しており、次のようになります。

## 各行の改行を保存する
text1: |
  aaa
  bbb
  ccc


## 各行の改行と、最終行に続く改行を保存する
text2: |+
  aaa
  bbb
  ccc


## 各行の改行は保存するが、最終行の改行は取り除く
text3: |-
  aaa
  bbb
  ccc


## 改行を半角スペースに置き換える、ただし最終行の改行は保存される
text4: >
  aaa
  bbb
  ccc


## 改行を半角スペースに置き換え、最終行に続く改行を保存する
text5: >+
  aaa
  bbb
  ccc


## 改行を半角スペースに置き換え、最終行の改行を取り除く
text6: >-
  aaa
  bbb
  ccc

実行結果:

{"text1"=>"aaa\nbbb\nccc\n",
 "text2"=>"aaa\nbbb\nccc\n\n\n",
 "text3"=>"aaa\nbbb\nccc",
 "text4"=>"aaa bbb ccc\n",
 "text5"=>"aaa bbb ccc\n\n\n",
 "text6"=>"aaa bbb ccc"}

各方法において、空行も保存されます。

またインデント幅を明示することもできます。ただし0は指定できないようです。

- |2
     foo
     bar
     baz

- |0
     foo
     bar
     baz

- >2
     foo
     bar
     baz

- >0
     foo
     bar
     baz

実行結果:

["   foo\n   bar\n   baz\n",
 "foo\nbar\nbaz\n",
 "   foo\n   bar\n   baz\n",
 "foo bar baz\n"]

ブロックスタイルを使うと、YAML ドキュメントの中に別の YAML ドキュメントを埋め込むことが簡単にできます。このとき、XML と違って記号をエスケープする必要はありません。

アンカーとエイリアス

YAML では、データに「&name」で印をつけ、「*name」で参照することができます (C 言語におけるアドレスやポインタと同じ表記です)。前者をアンカー (Anchor)、後者をエイリアス (Alias) といいます。

配列の要素にアンカーをつけるには、次のようにします。

- &mark foo
- bar
- *mark
- *mark

これは次の Ruby スクリプトと同じです。

mark = "foo"
[mark, "bar", mark, mark]

ハッシュの要素にアンカーをつけるには、次のようにします。

A: &mark
   foo
B: bar
C: *mark

これは次の Ruby スクリプトと同じです。

mark = foo
{ "A"=>mark, "B"=>"bar", "C"=>mark }

より複雑な例を次に示します。

## 役職
posts:
  - &p91
    id:     91
    name:   重役
  - &p92
    id:     92
    name:   管理職
  - &p93
    id:     93
    name:   平社員


## 社員
employees:

  ## 山田は重役、上司はなし
  - &e1001
    id:     1001
    name:   山田○○
    post:   *p91
    supervisor: ~

  ## 田中は管理職、上司は山田
  - &e1002
    id:     1002
    name:   田中○○
    post:   *p92
    supervisor: *e1001

  ## 中村は平社員、上司は田中
  - &e1003
    id:     1003
    name:   中村○○
    post:   *p93
    supervisor: *e1002

アンカーとエイリアスを使うことで、木構造だけでなく、DAG やネットワーク構造を直接表現できます。これは、基本的に木構造しか表現できない XML と比べて、YAML の利点です。

タブ文字について

YAML では、半角空白のかわりにタブ文字を使うことはできません。タブ文字の表示幅は何かの規格で決まっているわけではなく、環境によって異なります (8 文字が多いのはあくまで習慣にすぎません)。そのため、YAML ではタブ文字を使わないようになっています。

しかし、タブ文字が使えたほうが便利だという人もいるでしょう。その場合は、次のようにするとタブ文字を半角スペース 8 個に展開することができます。

require 'yaml'

## データを読み込む
str = ARGF.read()

## インデントが 8 文字単位になるように空白に置き換える
s = ''
str.each_line do |line|
   s << line.gsub(/([^\t]{8})|([^\t]*)\t/n) { [$+].pack("A8") }
end

## パースする
yaml = YAML.load(s)

その他

  • 本稿では「配列」「ハッシュ」という用語を使用していますが、YAML の用語ではそれぞれ「シーケンス (Sequence)」、「マッピング (Mapping)」といいます。またシーケンスやマッピングのように、他のデータを入れるようなデータ型を「コレクション (Collection)」といい、そうではないデータ型を「スカラー (Scalar)」といいます。
  • コレクションには他に次のようなものがあります。ただし、シーケンスとマッピング以外はあまり使わないうえに、Syck できちんと解釈できないようなので、本稿では説明を割愛します。興味のある方は『Language-Independent Types for YAML Version 1.1』をご覧ください。
    • シーケンス (Sequence)
    • マッピング (Mapping)
    • 順序つきマッピング (Ordered Mapping)
    • 組 (Pairs)
    • 集合 (Set)
  • 「---」で区切ることで、ひとつのファイルに複数の YAML ドキュメントを含めることができます。またひとつのファイルから複数の YAML ドキュメントを読み込むには、YAML.load_stream() を使います。これについては次回に説明します。
---
name: foo
email: foo@mail.com
---
name: bar
email: bar@mail.org
---
name: baz
email: baz@mail.net
  • 「...」だけの行があると、それ以降は読み込まれません。これは Ruby における「__END__」と同じようなものです。この機能は、エラーがあった箇所を絞り込むときに便利です。
- aaa
- bbb
...
- ccc      # 読み込まれない
  • 先頭に「%YAML 1.1」と書くことで、YAML のバージョンを指定できます。これをディレクティブといいます。ディレクティブは省略可能であり、また今のところ特に役にたつわけではないので、通常は省略します。
--- %YAML 1.1
- aaa
- bbb
  • YAML 仕様書によると、ドキュメントの文字コードはユニコードのみが使えるとされていますが、EUC などを使っても特に問題はないようです。また文字コードを明示的に指定する方法はありません。
  • ドメインを指定できます。ドメインとは名前空間のようなものです。データ型は必ず何らかのドメインに所属します。必要度は低いので本稿では割愛します。
  • 配列やハッシュを、ハッシュのキーとして指定することができます。有用性は低いので本稿では割愛します。

これ以外にも、YAML の仕様では様々な記述方法が定められています。 YAML はシンプルなようでいて、実は思ったよりも仕様は複雑です。 今回はよく使われる機能だけを紹介し細かいところは省略していますが、興味のある方は『YAML Ain't Markup Language (YAML) Version 1.1』をご覧ください。

XML との比較

YAML も XML も、どちらも構造化されたデータを表現するフォーマットです。しかし両者には歴然とした違いがあります。

このセクションでは、YAML と XML との比較を行い、両者の利点と欠点をみていきます。客観的に比較したつもりですが、おかしな点があればご指摘ください。

表記法について

今まで見てきたように、YAML は終了タグを使うかわりにインデントを使うことで、データ構造のネストを表現しています。これは人間にとって読みやすく、かつ書きやすい表記法です。これだけでも XML から乗り換える価値があります。

XML はご存知のように、書きやすくも読みやすくもありません。終了タグを必要とするため、XML データを自動生成するような場面では都合がいいのですが、人間が直接読んだり書いたりするには向きません。

これは、なにやら Ruby と Java の関係に似ています。やたら簡潔な表記を好む Ruby 派と、長くても正確な名前を好む Java 派は、そのまま YAML 派と XML 派にきれいに分かれるんじゃないでしょうか。XML の冗長な表記に耐えられる人は Java と XML を使い、耐えられない人は Ruby と YAML を好んでいるような気がします (煽っているわけじゃないので怒らないでね)。

なお XML でも、空要素タグと属性をうまく使えば、そこそこ簡潔な表記は可能です。 また現在では Groovy MarkupSXML のように XML と等価な簡易表記法があるので、「表記法が簡単だから」という理由で YAML へ乗り換える必要性は薄れています。

筆者は、表記法が簡単なことは YAML の利点ではあるが本質ではないと考えています。

概念について

XML では「エレメント」やら「ノード」やら「属性」やら「テキストノード」やら、いろんな面倒くさい概念を導入しています。これらを理解していないと XML を扱うことができないため、習得が困難です。また DOM にしろ SAX にしろ、プログラミングが非常に面倒くさいものになります。

これに対し YAML では、データを配列とハッシュとスカラーの組み合わせで表現します。新しい概念を導入していないので、非常にわかりやすく、習得が容易です。また扱うものが配列とハッシュとスカラーだけですので、プログラミングも簡単です。

つまり YAML は、表記法が簡単なだけでなく、概念も簡単なのです。XML でも簡易記法を用いることで、読みやすさと書きやすさは改善されます。しかし XML の概念はどうやっても簡単になるわけではありません。

XML に毒されている人はよく考えてみて下さい:たかが設定ファイルを読み込むだけのことに、DOM プログラミングが本当に必要でしょうか?簡単なアドレス帳を作りたいだけなのに、XML の難しい概念を覚えなきゃいけないのでしょうか?あなたが必要としているデータは、配列とハッシュでは表現できないのでしょうか?

筆者は、データを配列とハッシュとスカラーだけで表現する簡易性こそが YAML の本質であると考えています。

インライン要素について

XML では、「文章の途中で<strong>強調</strong>する」というようなインライン要素を自然に表現できます。これは開始タグと終了タグを使っているおかげです。そのため、記事や論文のような文章、もっといえば非定型的なデータを表現するのに適しています。DocBook などは最もよい例でしょう。

YAML では、インライン要素をうまく表現できません。表現できないわけではないのですが、うまくは表現できません。そのため、文章のような非定型的なデータよりは、アドレス帳や設定ファイルのように定型化されたデータを表現するのに適しています。

筆者は、「猫も杓子も XML」という現状に辟易しています。が、「猫も杓子も YAML」という世界もどうかと思います。文章のような非定型的なデータを扱うなら XML、定型化されたデータを扱うなら YAML、という世界が望ましいと考えています。

なお文章を記述するなら、XML よりも各種 Wiki 文法や RDreStructuredText などをお勧めします。

データ型について

XML では、データ型はスキーマ定義で指定します。つまり、XML ドキュメント自身はデータ型の情報を持たず、別ファイルにしています。

またデータ型の指定はタグや属性に対して行います。そのためデータ型はタグや属性によって決まり、データ自身で決まるわけではありません。 例えば「<age>タグは整数をとる」というスキーマ定義があったとします。このとき「<age>19</age>」というエレメントがあると「<age>タグだから19は整数」と決まるのであり、「19 だから整数」と決まるわけではありません。 そのため、<age>タグの値をとってくると必ず整数であることが期待できます。

YAML では、YAML ドキュメント中にデータ型の情報を埋め込みます。別ファイルにすることはありませんし、スキーマ定義も必要としません。

またデータ型はデータによって決まります。例えば「age: 19」とあった場合、「19 が整数である」ということが「19」というデータによって決まるのであり、「age:」というキーによって決まるわけではありません。 そのため age: キーの値をとってきても、必ず整数であるという保証はありません (文字列や真偽値なども指定できてしまうため)。

どちらのアプローチがいいかは好みによります。タグや属性によってデータ型が決まり、その型の値しかとらないという XML のアプローチは、なにやら静的な型の言語とよく似ています。逆に YAML のように、データ自身によって型が決まり、どこにどんな型のデータがくるかわからないというのは、まさに動的な型の言語をみているようです。Java では XML が流行って、Ruby では YAML が流行るのは、こんなところにも原因の一端があるのでしょう。

改行や半角スペースの扱いについて

XML では、見やすくするために改行やインデントを入れると、それがそのままテキストノードとなってしまいます。これが結構悩みの種で、この余計なテキストノードを読み飛ばすためにプログラミングが (若干ですが) 複雑になります。しかしプログラミングを楽にしようとして改行やインデントを取り除けば、今度は XML ドキュメントが非常に読みづらくなります。

YAML はインデントがそのまま構造を表現し、かつ改行がデータの終わりを表すため、このような問題はありません。

データバインディングについて

XML では、スキーマ定義を読み込んで、その XML ドキュメントを処理するようなクラスを自動生成するツールが存在します。これをデータバインディングツールというそうです。Java でいうと DigesterBetwixtRelaxer というツールがあり、また JAXB という Java の標準規格もあります (developerWorks の記事をご覧ください)。これらを用いると、複雑な DOM プログラミングをしなくて済みます (だれだって DOM プログラミングはいやですからね)。

YAML は標準でバインディング機能を持ちます。これは、YAML ではデータ型の情報をドキュメント中に埋め込めるためであり、YAML の利点です。具体的な方法は次回で紹介しますが、XML と違ってスキーマ定義なしで利用できるので便利です。

ツールについて

XML ではツールやライブラリのサポートが発達しています。パーサだけで DOM パーサ、SAX パーサ、StAX パーサの 3 種類ありますし、スキーマバリデータも主なものだけでDTD、XML Schema、RelaxNG の 3 つあります。他にも XSLT プロセッサ、データバインディングなど数も種類も豊富です。しかし裏を返せば、これらはすべて、扱いにくい XML をなんとか扱いやすくしようとする努力の成果でもあり、また XML をまともに扱おうと思えばこれらのツールを習得しなければならずコストがかかるということでもあります。

YAML のツールとしては、Syck が事実上の標準実装です。作者である why the lucky stiff の尽力によりメジャーなスクリプト言語 (Ruby, Python, PHP, OCaml) で利用できるようになっていますが、他のツールはあまり見かけません。というのも、Syck があればたいがいにおいて事足りるため、他のツールを必要とすることがないからです。 ただし本格的なスキーマバリデータがないことは大きな弱点です。簡単なものなら存在しますが、DTD や RelaxNG や XML Schema に比べてかなり低機能です。

その他

  • XML ではドキュメントの文字コードを指定できます。YAML ではユニコードを前提にしており、文字コードを指定できません。
  • XML ではデータは木構造となります。YAML ではアンカーとエイリアスを使うことで、ネットワーク構造も表現できます。
  • XML では実体参照 (Entity Reference) があります。YAML には相当する機能はありません。
  • XML を DOM ツリーに変換すると、子ノードから親ノードをたどることができます。YAML は配列とハッシュを使うため、子から親をたどることはできません。

終わりに

本稿では YAML の書き方を中心に説明し、また XML との比較を行いました。YAML は文章を表現するのにはあまり向きませんが、定型化されたデータは簡潔に表現できます。 また配列とハッシュとスカラー (文字列や数値など) でデータを表現するため、わかりやすく習得も容易です。

構造化されたデータを表現するものとしては、XML や YAML 以外にも、JSON などがあります。用途によってはそれらのほうがふさわしい場合もありますので、ご覧ください。

次回の中級編では、Syck の機能を掘り下げて紹介します。

参考文献

YAML Ain't Markup Language
YAML のホームページです。リファレンス仕様書サンプルなどがあります。
Yaml Cookbook
Syck の作者である why the lucky stiff による、たいへんわかりやすい YAML の解説です。
XML の論考: YAML は XML に改良を加える
developerWorks による YAML の紹介記事です。
Matz 日記:XML と YAML
まつもとさんが YAML と XML とを比較しています。
Plain Text and XML
達人プログラマー Andy Hunt と Dave Thomas が XML を批判しています。
XML Alternatives
XML の代替となるマークアップのリストです。数多すぎ。

著者について

名前:kwatch。三流プログラマー。いろんな言語に手を出したあげくRubyに行き着き、「これ以上のものは自分には作られへん」と悟ってオレ言語の作成をあきらめてからはや数年。最近のお気に入りは「きっこの日記」。