System.Yaml.YamlNode

English version is here.

YamlNode は YAML のデータノードを表すための抽象クラスです。

実際のデータノードは YamlScalar YamlSequenceYamlMapping で表されます。

YAML のデータモデル

完全な記述については http://yaml.org/ を見て下さい。

YAML のデータ構造は次のようになっています。この表式は実際の文法を表す物では無く、抽象的なデータ構造を簡略化して書いた物ですので注意して下さい。
  • yaml-stream ::= yaml-document*
  • yaml-document ::= yaml-directive* yaml-node
  • yaml-directive ::= YAML-directive | TAG-directive | user-defined-directive
  • yaml-node ::= yaml-scalar | yaml-sequence | yaml-mapping
  • yaml-scalar ::= yaml-tag yaml-value
  • yaml-sequence ::= yaml-tag yaml-node*
  • yaml-mapping ::= yaml-tag ( yaml-node yaml-node )*
  • yaml-tag ::= yaml-global-tag yaml-local-tag
  • yaml-global-tag ::= "tag:" taggingEntity ":" specific [ "#" fragment ]
  • yaml-local-tag ::= "!" yaml-local-tag-name

つまり、
  • YAML ストリームは0個以上の YAML ドキュメント から構成されます
  • YAML ドキュメントは0個以上の YAML ディレクティブと、ただ1つのルート YAML ノード から構成されます
  • YAML ディレクティブは %YAML-ディレクティブ か %TAG-ディレクティブ です
  • YAML ノードは YAML スカラ か YAML シーケンス か YAML マッピング です
  • YAML スカラは YAML タグ と スカラ値 から構成されます
  • YAML シーケンス は YAML タグ と0個以上の子 YAML ノードから構成されます
  • YAML マッピング は YAML タグ と0個以上の キー/値 ペア から構成されます。 キーも値も YAML ノード です
  • YAML タグ は YAML グローバルタグ または YAML ローカルタグです
  • YAML グローバルタグ は "tag:" から始まるもので、"tag:" URI スキーマとして RFC4151 に定義されている形式を持ちます
  • YAML ローカルタグは "!" から始まる任意形式のタグです

// YAML ノードツリーを作ります
YamlNode node = 
    new YamlSequence(                           // !!seq ノード
        new YamlScalar("abc"),                  // !!str ノード
        new YamlScalar("!!int", "123"),         // !!int ノード
        new YamlScalar("!!float", "1.23"),      // !!float ノード
        new YamlSequence(                       // ネストした !!seq ノード
            new YamlScalar("def"),
            new YamlScalar("ghi")
        ),
        new YamlMapping(                        // !!map ノード
            new YamlScalar("key1"), new YamlScalar("value1"),
            new YamlScalar("key2"), new YamlScalar("value2"),
            new YamlScalar("key3"), new YamlMapping(    // ネストした !!map ノード
                new YamlScalar("value3key1"), new YamlScalar("value3value1")
            ),
            new YamlScalar("key4"), new YamlScalar("value4")
        )
    );

// YAML ストリームに変換します
string yaml = node.ToYaml();

// %YAML 1.2
// ---
// - abc
// - 123
// - 1.23
// - - def
//   - ghi
// - key1: value1
//   key2: value2
//   key3:
//     value3key1: value3value1
//   key4: value4
// ...

// YAML ストリームから YAML ノードを 取り出します
// 一般に YAML ストリームは複数の YAML ドキュメントを含み、
// そのそれぞれがルート YAML ノードを持つため、返り値は配列になります
YamlNode[] nodes = YamlNode.FromYaml(yaml);

// ストリームに含まれるただ1つのノードが上で我々が保存した物です
Assert.AreEqual(1, nodes.Length);
YamlNode resotred = nodes[0];

// 等しいことを確認します
Assert.AreEquel(node, restored);

// サブノードを取り出します
var seq = (YamlSequence)restored;
var map = (YamlMapping)seq[4];
var map2 = (YamlMapping)map[new YamlScalar("key3")];

// 読み出されたノードツリーの一部を変更します
map2[new YamlScalar("value3key1")] = new YamlScalar("value3value1 modified");

// 2つのノードは等しくなくなります
Assert.AreNotEquel(node, restored);

YamlNode クラス

YamlNode は YAML ノードを表す抽象クラスです。

実際のデータノードは YamlScalar YamlSequenceYamlMapping で表されます。

すべてのl YamlNodeTag プロパティを持ちます。このプロパティはそのノードが保持するデータの型に関する情報を保持します。

YamlScalarYamlSequenceYamlMapping に対するデフォルトの Tag 値はそれぞれ "tag:yaml.org,2002:str"、 "tag:yaml.org,2002:seq"、 "tag:yaml.org,2002:map" です。 このような "tag:yaml.org,2002:" ( = YamlNode.DefaultTagPrefix) で始まるグローバルタグは YAML タグリポジトリ http://yaml.org/type/ で定義されています。

このライブラリでは、そのようなタグを "!!" で始まる短縮形、すなわち "!!str"、 "!!seq"、 "!!map" のように表すこともできます。 タグの正規形と短縮形とは ExpandTag(string) ShorthandTag(string) の2つのスタティックメソッドを使って相互に変換することができます。 これらの3つの基本的なタグに加え、このライブラリは "!!null", "!!bool", "!!int", "!!float", "!!timestamp" および "!!merge" をデフォルトでサポートしています。

YamlNodes を YAML ストリームから読み出すには FromYaml(string), FromYaml(Stream), FromYaml(TextReader) および FromYamlFile(string) のスタティックメソッドを用います。一般に YAML ストリームは複数の YAML ドキュメントを含んでおり、そのそれぞれが1つのルートノードを持っているため、これらのメソッドは YamlNode の配列を返すよう定義されています。

YamlNodes を YAML ストリームに書き出すには ToYaml(), ToYaml(Stream), ToYaml(TextWriter) および ToYamlFile(string) を用います。

シリアライゼーションの方法はある程度カスタマイズすることができます。そのようなカスタマイズされた設定は YamlConfig クラスのインスタンスを使って指定されます。上で紹介したシリアライゼーション用のメソッドは、YamlConfig を受け付けるオーバーロードされた形式をそれぞれ持っています。また、スタティックプロパティ YamlNode.DefaultConfig を変更して、デフォルトの動作を変更することもできます。

YamlScalarValue プロパティを持っており、ノード値の文字列表現が保持されます。

YamlSequence は子ノードにアクセスするための IList<YamlNode> インターフェースを実装しています。

YamlMapping はキー/値ペアにアクセスするための IDictionary<YamlNode,YamlNode> インターフェースを実装しています。

.Net のネイティブオブジェクトから YamlScalar への暗黙的な変換

String, Boolean, Int32, Double および DateTime から YamlNode への暗黙的な型変換が定義されています。

したがって、コード中で YamlNode が必要となる部分にはいつでも .Net のオブジェクトをそのままの形で書くことができます。例えば YamlSequenceYamlMapping のメソッドに .Net のオブジェクトをそのまま渡すことができることになります。

var map = new YamlMapping();
map["Time"] = DateTime.Now;                 // YamlScalar への暗黙的な変換
Assert.IsTrue(map.ContainsKey(new YamlScalar("Time")));
Assert.IsTrue(map.ContainsKey("Time"));     // YamlScalar への暗黙的な変換

YamlScalar から .Net オブジェクトを取り出す

YamlScalar が YAML の標準的なタグ、例えば !!int や !!bool を持っており、その型に対する有効な値が Value プロパティに与えられると、 NativeObjectAvailable プロパティに true が設定されます。
このとき、NativeObject を使って対応する .Net オブジェクトを取り出すことができます。

YamlNodes の同値性

2つの YamlNodes が等しいかどうかの判定は、可能な限りその内容から判断されます。すなわち、異なる YamlNode オブジェクトが同じ内容を含んでいれば、それらは等しい物として扱われます。そのような同値性の判定には Equals(object) を用います。

YamlNodes が YAML の標準的なタグではない、何らかのタグを持っている場合には、ライブラリはその内容の同値性を判定することができません。そこで、そのようなノードの同値性は、比較対象のノードが同一かどうか(まったく同じオブジェクトを参照しているかどうか)で判定されます。

2つの YamlNodes が YAML の標準的なタグを持つ場合、それらはその YamlNode およびその子ノードが同じ (Tag および Value) を持っており、なおかつノードツリーのトポロジーが一致する場合に限り同値であると判定されます。

YamlNode a1 = "a";  // 暗黙的な変換
YamlNode a2 = "a";  // 暗黙的な変換
YamlNode a3 = new YamlNode("!char", "a");
YamlNode b  = "b";  // 暗黙的な変換

Assert.IsTrue(a1 != a2);        // 異なるオブジェクト
Assert.IsTrue(a1.Equals(a2));   // 等しい内容を持つ異なるオブジェクト

Assert.IsFalse(a1.Equals(a3));  // Tag が異なる
Assert.IsFalse(a1.Equals(b));   // Value が異なる

var s1 = new YamlMapping(a1, new YamlSequence(a1, a2));
var s2 = new YamlMapping(a1, new YamlSequence(a2, a1));
var s3 = new YamlMapping(a2, new YamlSequence(a1, a2));

Assert.IsFalse(s1.Equals(s2)); // ノードツリーのトポロジーが異なる
Assert.IsFalse(s1.Equals(s3)); // ノードツリーのトポロジーが異なる
Assert.IsTrue(s2.Equals(s3));  // 等しい内容とツリーのトポロジーを持つ異なるオブジェクト

YamlScalar が YAML の標準的なタグ、つまり "!!int" や "!!float" などを持つ場合、同値性はYAML ドキュメント中での文字列形式に依らず、ネイティブオブジェクトの同値性に従って判別されます。

var seq = (YamlSequence) YamlNode.FromYaml(
  "- !!float 10\n" +
  "- 10.0\n" +
  "- 1E1\n" +
  "- +.1E2\n"
)[0];

Assert.AreEqual(seq[0], seq[1]);  // "!!float 10" == "10.0"
Assert.AreEqual(seq[0], seq[2]);  // "!!float 10" == "1E1"
Assert.AreEqual(seq[0], seq[3]);  // "!!float 10" == "10.0"
Assert.AreEqual(seq[0], seq[3]);  // "!!float 10" == "+.1E2"

現実的な例

この例は YAML1.2 スペシフィケーションの Example 2.27 から取りました。

// %YAML 1.2
// --- 
// !<tag:clarkevans.com,2002:invoice>
// invoice: 34843
// date   : 2001-01-23
// bill-to: &id001
//     given  : Chris
//     family : Dumars
//     address:
//         lines: |
//             458 Walkman Dr.
//             Suite #292
//         city    : Royal Oak
//         state   : MI
//         postal  : 48046
// ship-to: *id001
// product:
//     - sku         : BL394D
//       quantity    : 4
//       description : Basketball
//       price       : 450.00
//     - sku         : BL4438H
//       quantity    : 1
//       description : Super Hoop
//       price       : 2392.00
// tax  : 251.42
// total: 4443.52
// comments:
//     Late afternoon is best.
//     Backup contact is Nancy
//     Billsmer @ 338-4338.
// ...

var invoice = new YamlMapping(
    "invoice", 34843,
    "date", new DateTime(2001, 01, 23),
    "bill-to", new YamlMapping(
        "given", "Chris",
        "family", "Dumars",
        "address", new YamlMapping(
            "lines", "458 Walkman Dr.\nSuite #292\n",
            "city", "Royal Oak",
            "state", "MI",
            "postal", 48046
            )
        ),
    "product", new YamlSequence(
        new YamlMapping(
            "sku",         "BL394D",
            "quantity",    4,
            "description", "Basketball",
            "price",       450.00
            ),
        new YamlMapping(
            "sku",         "BL4438H",
            "quantity",    1,
            "description", "Super Hoop",
            "price",       2392.00
            )
        ),
    "tax", 251.42,
    "total", 4443.52,
    "comments", "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338."
    );
invoice["ship-to"] = invoice["bill-to"];
invoice.Tag = "tag:clarkevans.com,2002:invoice";

invoice.ToYamlFile("invoice.yaml");
// %YAML 1.2
// ---
// !<tag:clarkevans.com,2002:invoice>
// invoice: 34843
// date: 2001-01-23
// bill-to: &A 
//   given: Chris
//   family: Dumars
//   address: 
//     lines: "458 Walkman Dr.\n\
//       Suite #292\n"
//     city: Royal Oak
//     state: MI
//     postal: 48046
// product: 
//   - sku: BL394D
//     quantity: 4
//     description: Basketball
//     price: !!float 450
//   - sku: BL4438H
//     quantity: 1
//     description: Super Hoop
//     price: !!float 2392
// tax: 251.42
// total: 4443.52
// comments: Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.
// ship-to: *A
// ...

Last edited Oct 4, 2009 at 10:51 AM by osamu, version 2

Comments

No comments yet.