困ったときにそのまま使える LINQ to XML コードサンプル 18 パターン [1/2] – Web/DB プログラミング徹底解説
http://keicode.com/dotnet/linq-to-xml-101.php
XElement クラス (System.Xml.Linq)
http://msdn.microsoft.com/ja-jp/library/system.xml.linq.xelement.aspx
を参考にしながら、XML をメソッドチェーンを使って構築するクラスを作ってみます。まぁ、HtmlDom を作るときの副産物…と言うか、練習問題的に。
■使い方サンプルを作る
TDD と UIDD のハイブリッドプロセス(という命名をしておく)、ということで使い方のサンプルを先に作ります。
これを先に作っておくと、ある程度のマニュアルがなくても使える「感覚的に正しい」プログラミングコードができあがります。D.A.ノーマンの言う「ユーザーインターフェース」ってやつです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public void test1() { var root = new XmlNode( "root" ) .Node( "person" ) .Node( "name" , "masuda" ) .Root; var root2 = new XmlNode( "root" ) .Node( "person" ) .AddNode( "name" , "masuda" ) .AddNode( "age" , "44" ) .AddNode( "address" , "itabashi-ku" ) .Root; var root3 = new XmlNode( "root" ) .Node( "person" ) .AddAttr( "name" , "masuda" ) .AddAttr( "age" , "44" ) .SetValue( "masuda tomoaki" ) .Root; var root4 = new XmlNode( "persons" ) .Node( "person" ).AddAttr( "id" , "1" ) .AddNode( "name" , "masuda tomoaki" ) .AddNode( "age" , "44" ) .Parent .Node( "person" ).AddAttr( "id" , "2" ) .AddNode( "name" , "yamada taro" ) .AddNode( "age" , "20" ) .Root; } |
実は、.Parent と .Root は後付、SetValue はやむなくって感じですね。
最初に作ったのはこんな感じ。
1 2 3 4 5 | var root = new XmlNode( "root" ) .Node( "person" ) .Attr( "name" , "masuda" ) .Attr( "age" , "44" ) .Value( "masuda tomoaki" ); |
「動詞」を使わずに「名詞」だけで、繋げてみたかったのですが、Value プロパティと、Value メソッドが曖昧になってしまうのでパス。
あと、子ノードを連続追加する時に Node だけだと駄目で、Node.Parent.Node(“tag”) と変なことをしないといけないので、AddNode に変更。それに伴って、Attr も AddAttr に変更しています。
それと、最後に Root プロパティを入れたのは、テストコードを書いたときに、
1 2 3 4 5 6 | var root = new XmlNode( "root" ); root.Node( "person" ) .Attr( "name" , "masuda" ) .Attr( "age" , "44" ) .Value( "masuda tomoaki" ); Assert.AreEqual( "..." , root.Xml ); |
のように「わざわざ」ルート要素を保存しないといけないという面倒があるので、ルートに戻すプロパティを用意します。
1 2 3 4 5 6 7 | var root = new XmlNode( "root" ) .Node( "person" ) .Attr( "name" , "masuda" ) .Attr( "age" , "44" ) .Value( "masuda tomoaki" ) .Root ; Assert.AreEqual( "..." , root.Xml ); |
こっちのほうがすっきりするし、後から意味が分かり易いのです。
■インターフェースに従って、コンパイルが通るようにコーディング
テストコードを先に書いてもいいのですが、Visual Studio のインテリセンスを利用したいので、コンパイルが通るまでのコードを書きます。
素のエディタで書くときは、逆にテストコードから書いたりします。と言うのも、素のエディタの場合は、テストコードのコンパイルエラーからメソッド/プロパティを追加することになるので、テストコードが先のほうがやり易いのです。
この違いは(私にとって)重要です。
■テストコードを書く
下記では、一気に追加していますが、実際にはひとつひとつ追加しています。
簡単なテストから複雑なテストへというのはテストの定番です。
実は Xml プロパティは、テストコードを書くときに追加しています。こうするとテストがやりやすいですからね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | [TestMethod] public void TestNormal1() { var root = new XmlNode( "root" ) .Node( "person" ) .Node( "name" , "masuda" ) .Root; Assert.AreEqual( "<root><person><name>masuda</name></person></root>" , root.Xml); } [TestMethod] public void TestNormal2() { var root = new XmlNode( "root" ) .Node( "person" ) .AddNode( "name" , "masuda" ) .AddNode( "age" , "44" ) .AddNode( "address" , "itabashi-ku" ) .Root; Assert.AreEqual( "<root><person><name>masuda</name><age>44</age><address>itabashi-ku</address></person></root>" , root.Xml); } [TestMethod] public void TestNormal3() { var root = new XmlNode( "root" ) .Node( "person" ) .AddAttr( "name" , "masuda" ) .AddAttr( "age" , "44" ) .SetValue( "masuda tomoaki" ) .Root; Assert.AreEqual( "<root><person name=\"masuda\" age=\"44\">masuda tomoaki</person></root>" , root.Xml); } [TestMethod] public void TestNormal4() { var root = new XmlNode( "persons" ) .Node( "person" ).AddAttr( "id" , "1" ) .AddNode( "name" , "masuda tomoaki" ) .AddNode( "age" , "44" ) .Parent .Node( "person" ).AddAttr( "id" , "2" ) .AddNode( "name" , "yamada taro" ) .AddNode( "age" , "20" ) .Root; ; Assert.AreEqual( "<person>" + "<person id=\"1\"><name>masuda tomoaki</name><age>44</age></person>" + "<person id=\"2\"><name>tamada taro</name><age>20</age></person>" + "</persons>" , root.Xml); } |
■コードが完成品
でもって、テストを通るようにしたのが以下、150行程度のコードです。
Xml プロパティは、HtmlDom からコピペしているのでちょっと無駄なコードが入っていますが、まぁこの程度で書けるのだから C# って便利やなぁ、と。
これを HtmlDom に組み入れるかどうか思案中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /// <summary> /// XML node class /// </summary> public class XmlNode { #region property public string TagName { get ; set ; } public string Value { get ; set ; } public XmlNodeCollection Children { get ; set ; } public XmlAttrCollection Attrs { get ; set ; } public XmlNode Parent { get ; set ; } #endregion #region constractor public XmlNode( string tag, string value = "" ) { this .TagName = tag; this .Value = value; this .Children = new XmlNodeCollection(); this .Children.Parent = this ; this .Attrs = new XmlAttrCollection(); } #endregion #region creator for method chain public XmlNode Node( string tag, string value = "" ) { var nd = new XmlNode(tag, value); this .Children.Add(nd); return nd; } public XmlNode AddNode( string tag, string value = "" ) { this .Node(tag, value); return this ; } public XmlNode AddAttr( string key, string value) { this .Attrs.Add( new XmlAttr(key, value)); return this ; } public XmlNode SetValue( string value) { this .Value = value; return this ; } #endregion #region xml string serialize public string Xml { get { string s = "" ; if ( this .TagName == "" || this .TagName == "#text" || this .TagName == "#comment" ) { s = this .Value.Trim(); } else { s += string .Format( "<{0}" , this .TagName); foreach ( var at in this .Attrs) { s += string .Format( " {0}=\"{1}\"" , at.Key, at.Value); } if ( this .Children.Count > 0) { s += ">" ; s += this .Value; foreach ( var el in this .Children) { s += el.Xml; } s += string .Format( "</{0}>" , this .TagName); } else if ( this .Value != "" ) { s += ">" ; s += this .Value; s += string .Format( "</{0}>" , this .TagName); } else { s += "/>" ; } } return s; } } #endregion public XmlNode Root { get { XmlNode nd = this ; while (nd.Parent != null ) { nd = nd.Parent; } return nd; } } } /// <summary> /// XML node collection /// </summary> public class XmlNodeCollection : List<XmlNode> { public XmlNode Parent { get ; set ; } public new XmlNode Add(XmlNode nd) { base .Add(nd); nd.Parent = this .Parent; return nd; } } /// <summary> /// XML attribute class /// </summary> public class XmlAttr { public string Key { get ; set ; } public string Value { get ; set ; } public XmlAttr( string key, string value) { this .Key = key; this .Value = value; } } /// <summary> /// XML Attribute collection /// </summary> public class XmlAttrCollection : List<XmlAttr> { } |