C# Advent Calendar 2012 9日目の記事になります。
テキストでデータをやりとりする方法としては、CSV. TSV, もう少しプログラミングの処理として気が利くようににマークアップされたものとしてXML, JSONなどありますが、それら以外にも主にRuby方面で人気のあるフォーマットとしてYAML(YAML Ain’t a Markup Language)があります。
C#なら素直にXML使っとけよって事なんですが、XMLは正直人間にキツイ。カッコ大杉で見にくい。など、いろいろ(と非プログラマに)評判が悪い。ということで、人間に優しいという評判のYAMLを使えないものかと調べてみることにしました。
で、実際に調べてみると、何種類も公開されているライブラリがあるのですが、今回はNuGet様の人気順に素直に従い、YamlDotNetを使用することにします。
YamlDotNet
YamlDotNetはAntoine Aubryさんにより開発されているYAMLを取り扱うための.NET Libraryです。GitHubでソースコードが公開されています。
また、バイナリの入手にはNuGetを使用することが出来ます。NuGetは3つのパッケージに分かれていますが、基本的には全部インストールしてしまいましょう。

YAMLの書き出し
ふつうに出来ます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using YamlDotNet;
using YamlDotNet.RepresentationModel;
using YamlDotNet.RepresentationModel.Serialization;
namespace CsAdventCalendarDec9
{
    class Program
    {
        static void Main(string[] args) {
            //シリアライズするオブジェクトを定義する
            var calendar = new AdventCalendar();
            calendar.Year = new DateTime(2012, 12, 1);
            calendar.Description = "C# Advent Calender 2012";
            calendar.Contents.Add(new Content()  {
                Day = 1,
                Author = "gushwell",
                Description = ".NET Framework4.5 での非同期I/O処理 事始め",
                URL = @"http://gushwell.ldblog.jp/archives/52290230.html"
            });
            calendar.Contents.Add(new Content() {
                Day = 2,
                Author = "KTZ",
                Description = "年末の大掃除",
                URL = @"http://ritalin.github.com/2012/12-02/csharp-advent-2012/"
            });
            calendar.Contents.Add(new Content() {
                Day = 3,
                Author = "neuecc",
                Description = "MemcachedTranscoder – C#のMemcached用シリアライザライブラリ",
                URL = @"http://neue.cc/2012/12/03_389.html"
            });
            //YAMLにシリアライズしてコンソールに表示
            var serializer = new Serializer();
            serializer.Serialize(Console.Out, calendar);
            Console.Read();
        }
    }
    public class Content
    {
        public int Day { get; set; }
        public string Author { get; set; }
        public string Description { get; set; }
        public string URL { get; set;}   
    }
    public class AdventCalendar
    {
        public DateTime Year { get; set; }
        public string Description { get; set; }
        public List Contents { get; set; }
        public AdventCalendar() {
            this.Contents = new List();
        }
        public AdventCalendar(List contents) {
            this.Contents = contents;
        }
    }
}
39行目でシリアライザー作って、40行目でシリアライズさせるだけ。簡単です。
YAMLの読み込み
こちらもそれほど難しくはありません。
読み込み側は2種類の方法のサンプルを作りました。一つ目はファイルを読み込んでYAMLのノードを解析していく方法、もう一つは直接C#のオブジェクトに変換してしまう方法です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using YamlDotNet;
using YamlDotNet.RepresentationModel;
using YamlDotNet.RepresentationModel.Serialization;
namespace CsAdventCalendarDec9DeSerialize
{
    class Program
    {
        static void Main(string[] args) {
            // YAMLのファイルをUTF-8エンコーディングされたファイルとして読み込む
            var input = new StreamReader("AdventCalendar.yaml", Encoding.UTF8);
            // YAMLのストリームを解析する
            var yaml = new YamlStream();
            yaml.Load(input);
            // ストリームを解析する
            var mapping = (YamlMappingNode)yaml.Documents[0].RootNode;
            //Yearノードの値を取得する
            var Year = (YamlScalarNode)mapping.Children[new YamlScalarNode("Year")];
            Console.WriteLine("Year\t{0}", Year.Value);
            //Descriptionノードの値を表示する
            var Description = (YamlScalarNode)mapping.Children[new YamlScalarNode("Description")];
            Console.WriteLine("Desciption\t{0}", Description.Value);
            //Contents以下の内容表示
            Console.WriteLine("Contents:");
            //シーケンス(配列)のノードとしてContentsノードの内容を取り出す
            var items = (YamlSequenceNode)mapping.Children[new YamlScalarNode("Contents")];
            //Contentsの各Contentをマッピングノードとして取り出す
            foreach (YamlMappingNode item in items) {
                //Contentの個別要素を表示させる
                foreach (var child in item) {
                    Console.WriteLine(
                        "{0}\t{1}", 
                        ((YamlScalarNode)child.Key).Value, 
                        ((YamlScalarNode)child.Value).Value);
                }
            }
            // クラスに直接デシリアライズする。
            //YAMLファイルを開き直す
            input.Close();
            input = new StreamReader("AdventCalendar.yaml", Encoding.UTF8);
            //YAMLファイルからContentsオブジェクトに復元するためのYamlSerializer(T)クラスのインスタンスを作る
            //Genericで復元先のクラスを指定する
            var yamlSerializer = new YamlSerializer();
            //C#nのオブジェクトに復元を行う
            var calendar = yamlSerializer.Deserialize(input);
            //復元した結果を表示する。
            Console.WriteLine("Year:{0}", calendar.Year);
            Console.WriteLine("Description:{0}", calendar.Description);
            foreach (var content in calendar.Contents) {
                Console.WriteLine("\tDay:{0}", content.Day);
                Console.WriteLine("\tAuthor:{0}", content.Author);
                Console.WriteLine("\tDescription:{0}", content.Description);
                Console.WriteLine("\tURL:{0}", content.URL);
            }
            input.Close();
            Console.Read();
        }
    }
    public class Content
    {
        public int Day { get; set; }
        public string Author { get; set; }
        public string Description { get; set; }
        public string URL { get; set; }
    }
    public class AdventCalendar
    {
        public DateTime Year { get; set; }
        public string Description { get; set; }
        public List Contents { get; set; }
        public AdventCalendar() {
            this.Contents = new List();
        }
        public AdventCalendar(List contents) {
            this.Contents = contents;
        }
    }
}
18行目から44行目がノードを解析していく方法です。
まずは読み込むYAMLのファイルを見てみます。
Year: 2012-12-01T00:00:00.0000000 Description: C# Advent Calender 2012 Contents: - Day: 1 Author: gushwell Description: ".NET Framework4.5 での非同期I/O処理 事始め" URL: http://gushwell.ldblog.jp/archives/52290230.html - Day: 2 Author: KTZ Description: "年末の大掃除" URL: http://ritalin.github.com/2012/12-02/csharp-advent-2012/ - Day: 3 Author: neuecc Description: "MemcachedTranscoder ? C#のMemcached用シリアライザライブラリ" URL: http://neue.cc/2012/12/03_389.html
YAMLの構造としては二つの値を持ったノードの後にContentsと言うシーケンスノード(リスト)があり、そこに子供のYAMLのデータがある構造になっています。
ポイントしては、YAMLのオブジェクトをとるときにはYamlMappingNodeにキャストする、シーケンスのルートをとるときにはYamlSequenceNodeにキャストする、値を持ったノードをとるときにはYamlScalarNodeをとると覚えておけばいいのでは無いかと思います。
後は、コード上のコメントを読んでいただければ何となくわかるんじゃ無いかと思います。
53行目からがC#のオブジェクトに直接変換してしまう方法で、GenericなYanlSerializerで復元先となるオブジェクトのクラスを指定してインスタンスを作り、SerializeメソッドにYAMLファイルのテキストストリームを渡してシリアライズさせます。こちらはあまり複雑じゃありません。
まとめ
だいたいXML Serializerなどと同じように作ってくれているので、様子がわかると思います。もうこれでC#でYAML使えないみたいなことは言わせませんよ。
今回のコードはgithubにありますので、気が向いたらForkでもしてやって下さい。
さて、明日はまさる(高野将)さんです。ではハリキッテどうぞ!


コメント
RT @ishisaka: C#でYAMLを使えるか試してみた: C# Advent Calendar 2012 9日目の記事になります。 … http://t.co/7hL5Gmdn
RT @ishisaka: C#でYAMLを使えるか試してみた: C# Advent Calendar 2012 9日目の記事になります。 … http://t.co/7hL5Gmdn
RT @ishisaka: C#でYAMLを使えるか試してみた: C# Advent Calendar 2012 9日目の記事になります。 … http://t.co/7hL5Gmdn
[…] 昨日は@ishisakaさんの「C#でYAMLを使えるか試してみた | OPC Diary – No Code, No Life.」でした。 […]