Yaml を利用して C# でヒアドキュメントを考える | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6560
これの実装編です。Yaml をパースするためのライブラリが SharpYaml にあるので、これを使います。最初は、YamlDotNet を利用して作っていたのですが、 PCL 版がないので SharpYaml を使っています。SharpYaml 自体が YamlDotNet の派生ということなので、中身は同じクラスが使われています。
github
moonmile/ExDoc
https://github.com/moonmile/ExDoc
コードは github で公開していて ExDoc の下にあります。ついでに、HTML と Json 版も作りました。
- ExDoc.Html – HTML から XML へ
- ExDoc.Yaml – Yaml から XML へ
- ExDoc.Json – Json から XML へ
拡張メソッドを使って、XDocument を生成します。そのまま XDocument で LINQ しても良いし、ExDoc に読み込ませて検索してもよい、というスタイルです。ExDoc 自体は XElement に依存しているので、検索結果を XElmenet に戻すことも可能です。まだ実装してはいませんが、そのうち属性や要素の値を変更して出力するようにもしていきます。
NuGet
nuget は、それぞれのアセンブリを別々に用意しています。
Moonile.ExDoc
https://www.nuget.org/packages/Moonmile.ExDoc/
Moonmile.ExDoc.Html
https://www.nuget.org/packages/Moonmile.ExDoc.Html/
Moonmile.ExDoc.Yaml
https://www.nuget.org/packages/Moonmile.ExDoc.Yaml/
Moonmile.ExDoc.Json
https://www.nuget.org/packages/Moonmile.ExDoc.Json/
もともと、Yaml や Json は XML にコンバートするメソッドを持っているのですが、いまひとつ使いづらい XML を吐き出すので、コンバートライブラリはそれを補足する感じです。設定やヒアドキュメントは人間に書きやすく、内部処理するときは XML 形式にしてプログラミングでやり易く、という主旨ですね。
ExDoc.Yaml の使い方
[TestMethod] public void demo1() { var yaml = @" app: - user: masuda - apikey: key-xxxx - apipass: password "; var ys = new YamlStream(); ys.Load(new StringReader(yaml)); var xdoc = ys.Documents[0].ToXDocument(); var doc = ExDocument.Load(xdoc); string apikey = doc * "apikey"; string apipass = doc * "apipass"; Debug.WriteLine("key:{0} pass:{1}", apikey, apipass); }
たとえば、こんな感じで、yaml をヒアドキュメントで定義しておきます。このぐらいだったら、フィールドを使った方がいいし、そもそもアプリケーション設定を使えってのもあるでしょうが、まあ、テストの時とかちょっとした設定を書いておくときに便利でしょう。Yaml 自体はファイルから読み込みもできるので、外だしにしておくこともできます。
YamlDotNet/SharpYaml で作成したオブジェクトを直接参照してもよいのですが、KeyValue の羅列があったり、階層構造が面倒だったりして解析部分が結構面倒です。そういう場合は、クラスにマッピングするのがよいのですが、Yaml 自体の構造と同じでないといけないのが面倒です。
そのあたりを、なんかいい感じに ExDoc を使って検索します。
上記のコードを動かすと以下のような結果が取れます。
key:key-xxxx pass:password
このあたりの暗黙のキャストと要素名の使い方は、調べてみると Json.NET でも似たものがありました。
Json.NET – Modifying JSON
http://james.newtonking.com/json/help/?topic=html/ModifyJson.htm
JObject に変換した後に、名前付きインデックスで検索ができます。
string rssTitle = (string)rss["channel"]["title"];
ExDoc の場合は、/ 演算子や % 演算子をオーバーライドすることによって、doc * “apikey” のような構文を実現していますが、ああ、なるほど名前付き配列でも十分ですよね。dynamic 版もあって、PHP の XML 風に要素名を連ねることもできます。
内部構造的には、同じことをやっているので、同じ構文が使えても良いかなと思っています。
ExDoc.Json も同じ
ExDoc.Json も作ってみましたが、内部的には ExDoc.Yaml と変わりません。Yaml も Json もハッシュとリストでできているので、似た感じの XML が出来上がります。ただし、Json の場合、長いデータもハッシュに置くことが多いので、ハッシュ値を属性に置くか、要素に置くかの選択ができるようにしました。
public void demo1() { string json = @" { 'CPU': 'Intel', 'PSU': '500W', 'Drives': { set1: 'DVD read/writer', set2: '500 gigabyte hard drive', set3: '200 gigabype hard drive' }, 'Languages': [ 'C#', 'F#', 'Visual Basic' ] }"; var jdoc = JToken.Parse(json); var xdoc = jdoc.ToXDocument(); Debug.WriteLine(xdoc.ToString()); var doc = ExDocument.Load(xdoc); var lang = doc * "Languages"; foreach (var it in lang) { Debug.WriteLine("lang" + (string)it); } }
こんな風に入れ子になった JSON の場合、Drives のハッシュをそのまま属性にしてしまうと、ちょっと見づらくなります。
<root CPU="Intel" PSU="500W"> <Drives set1="DVD read/writer" set2="500 gigabyte hard drive" set3="200 gigabype hard drive" /> <Languages>C#</Languages> <Languages>F#</Languages> <Languages>Visual Basic</Languages> </root>
XML に変換するときに要素出力を優先(XmlPriority.Element)にしておくと、
var xdoc = jdoc.ToXDocument(XmlPriority.Element);
下記のように、すべてのデータが要素になりようにします。XML 形式にしてしまうと冗長ですが、XElement で LINQ したり、ExDoc で検索したりするときには便利でしょう。
<root> <CPU>Intel</CPU> <PSU>500W</PSU> <Drives> <set1>DVD read/writer</set1> <set2>500 gigabyte hard drive</set2> <set3>200 gigabype hard drive</set3> </Drives> <Languages>C#</Languages> <Languages>F#</Languages> <Languages>Visual Basic</Languages> </root>
ちなみに、Json.NET の内部構造を探っているときに分かったのですが、XPath のような仕組みを作るためにパス部分(ハッシュ名)をすべて持っているんですよね。ハイパフォーマンスな分、メモリは食うかもしれません….が、Web API でやり取りするぐらいのでデータならば別にどうということはないのですが。
追記:2014/10/29
Languages のところが、リストっぽくなくてアクセスしにくいので item タグを使うように変更しよう。
<Languages> <item>C#</item> <item>F#</item> <item>Visual Basic</item> </Languages>
今後の予定
だいたいは思う通りに動いたので、今度は値を変更するところを実装しようかなと思ってます。以前、少し実装はしてみたもの、ExDoc の / 演算子と相性が悪くて、どうもうまくいかないのですが、Json.NET のように KeyValue を使って添え字に名前を使えば、書式が簡単になりそうです。
doc._["Languages"][2] = "Visual Basic.NET" ;
な感じで変更できればよいかと。
あと、XLTS か Xml schema な感じで、元の XDocument 構造を他の XDocument にマッピングする写像っぽい使い方ができると良いと思ってます。複雑な XML や乱雑な HTML データから、目的の XML データを取り出すみたいな使い方です。クエリを使って、直接 XML フォーマット(この場合は XElement ツリー)を作るところまでできれば、後は Json や Yaml に再フォーマットするのは難しくはないかと思ってます。