[C#] XElement 風に XML を構築するクラスを作ってみる

困ったときにそのまま使える 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> { }
カテゴリー: C# パーマリンク