XmlElementのラッパークラス

# ノード作成時のXmlDocument.CreateElementが面倒
XMLファイルをXmlDocumentで作成する場合、testAノードの下にtestBノードを作成したい時に、testA.AppendChild(testB)としたいのに、毎回XmlDocument.CreateElement(新しいノード)をする必要があるため、より直感的にAppendできように簡単なラッパークラスを考えてみました。


#環境
**IDE**：VisualStudio2019
**アプリケーション**：コンソールアプリ
**フレームワーク**：.NET Core 3.1



# XmlDocumentを持ちまわる
XmlElementをラップし、さらにXmlDocumentを持ちまわって、Append時にそのXmlDocumentでCreateElementさせます。

```C#
using System.Xml;

namespace TestProject
{
    /// <summary>
    /// XmlDocument拡張クラス
    /// </summary>
    public class XmlDocumentExtension : XmlDocument
    {
        /// <summary>
        /// ルート要素を作成
        /// ※既存の子ノードはすべて削除します
        /// </summary>
        /// <param name="el">要素</param>
        /// <param name="version">バージョン</param>
        /// <param name="encoding">エンコーディング</param>
        /// <param name="standalone">外部依存</param>
        /// <returns>XmlElementWrapper</returns>
        public XmlElementWrapper CreateRootOfElementWrapper(string el, string version = "1.0", string encoding = "utf-8", string standalone = null)
        {
            this.RemoveAll();
            this.AppendChild(this.CreateXmlDeclaration(version, encoding, standalone));
            var createEl = this.CreateElement(el);
            return new XmlElementWrapper(this.AppendChild(createEl));
        }
    }

    /// <summary>
    /// XmlElementラッパークラス
    /// </summary>
    public class XmlElementWrapper
    {
        /// <summary>
        /// XmlElement
        /// </summary>
        private XmlElement _el;

        /// <summary>
        /// XmlDocument
        /// </summary>
        private XmlDocument _doc;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="node">XmlNode</param>
        public XmlElementWrapper(XmlNode node)
        {
            // 自身のXmlElementを保持
            this._el = node as XmlElement;

            // XmlDocumentを保持
            this._doc = this._el.OwnerDocument;
        }

        /// <summary>
        /// 子要素を追加
        /// </summary>
        /// <param name="el">要素</param>
        /// <returns>追加した子要素</returns>
        public XmlElementWrapper AppendChild(string el)
        {
            var addEl = this._el.AppendChild(this._doc.CreateElement(el));
            return new XmlElementWrapper(addEl);
        }
    }
}
```
AppendChildで追加した子要素を新たにXmlElementWrapperでラップすることで、後続のエレメントでもAppendが楽になります。
※AppendChildの引数はstringですがXmlNode版もあるといいのかもしれません。

また、最初のrootエレメントを起点とすることにしたため、XmlDocumentの拡張クラスを用意しています。


このクラスの使用イメージは以下です。

```C#
using System;
using System.IO;
using TestProject.Extension;

namespace TestProject
{
    /// <summary>
    /// メインクラス
    /// </summary>
    public class Program
    {
        /// <summary>
        /// メインエントリ
        /// </summary>
        /// <param name="args">実行時引数</param>
        public static void Main(string[] args)
        {
            // XmlDocument拡張クラス
            var doc = new XmlDocumentExtension();

            // root要素作成
            var root = doc.CreateRootOfElementWrapper("root");

            // rootの直下にtestAを作成
            var testA = root.AppendChild("testA");

            // testAの下にtestBを作成
            var testB = testA.AppendChild("testB");

            // xml保存
            doc.Save(Path.Combine(Environment.CurrentDirectory, "test.xml"));
        }
    }
}
```

以下のxmlが作成されます。

```XML
<?xml version="1.0" encoding="utf-8"?>
<root>
  <testA>
    <testB />
  </testA>
</root>
```

# 必要なメソッドを実装していく
値設定／値取得や、XPath、属性設定等　必要に応じてメソッドを用意し、ラップしたXElementの各処理を実装します。

```C#
        /// <summary>
        /// 値を設定
        /// </summary>
        /// <param name="value">値</param>
        public void SetValue(string value)
        {
            this._el.InnerText = value;
        }

        /// <summary>
        /// 値を取得
        /// </summary>
        /// <returns>値</returns>
        public string GetValue()
        {
            return this._el.InnerText;
        }

        /// <summary>
        /// 要素検索
        /// </summary>
        /// <param name="xPath">XPath</param>
        /// <returns>XmlElementWrapper</returns>
        public XmlElementWrapper SelectSingleNode(string xPath)
        {
            var node = this._el.SelectSingleNode(xPath);
            return node == null ? null : new XmlElementWrapper(node);
        }
```

# サンプル全文

```C#
using System.Xml;

namespace TestProject.Extension
{
    /// <summary>
    /// XmlDocument拡張クラス
    /// </summary>
    public class XmlDocumentExtension : XmlDocument
    {
        /// <summary>
        /// ルート要素を作成
        /// ※既存の子ノードはすべて削除します
        /// </summary>
        /// <param name="el">要素</param>
        /// <param name="version">バージョン</param>
        /// <param name="encoding">エンコーディング</param>
        /// <param name="standalone">外部依存</param>
        /// <returns>XmlElementWrapper</returns>
        public XmlElementWrapper CreateRootOfElementWrapper(string el, string version = "1.0", string encoding = "utf-8", string standalone = null)
        {
            this.RemoveAll();
            this.AppendChild(this.CreateXmlDeclaration(version, encoding, standalone));
            var createEl = this.CreateElement(el);
            return new XmlElementWrapper(this.AppendChild(createEl));
        }
    }

    /// <summary>
    /// XmlElementラッパークラス
    /// </summary>
    public class XmlElementWrapper
    {
        /// <summary>
        /// XmlElement
        /// </summary>
        private XmlElement _el;

        /// <summary>
        /// XmlDocument
        /// </summary>
        private XmlDocument _doc;

        /// <summary>
        /// タグ名
        /// </summary>
        public string Name => this._el.Name;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="node">XmlNode</param>
        public XmlElementWrapper(XmlNode node)
        {
            this._el = node as XmlElement;
            this._doc = this._el.OwnerDocument;
        }

        /// <summary>
        /// 子要素を追加
        /// </summary>
        /// <param name="el">要素</param>
        /// <returns>追加した子要素</returns>
        public XmlElementWrapper AppendChild(string el)
        {
            var addEl = this._el.AppendChild(this._doc.CreateElement(el));
            return new XmlElementWrapper(addEl);
        }

        /// <summary>
        /// 値を設定
        /// </summary>
        /// <param name="value">値</param>
        public void SetValue(string value)
        {
            this._el.InnerText = value;
        }

        /// <summary>
        /// 値を取得
        /// </summary>
        /// <returns>値</returns>
        public string GetValue()
        {
            return this._el.InnerText;
        }

        /// <summary>
        /// 属性を設定
        /// </summary>
        /// <param name="attrName">属性名</param>
        /// <param name="value">属性値</param>
        public void SetAttribute(string attrName, string attrValue)
        {
            this._el.SetAttribute(attrName, attrValue);
        }

        /// <summary>
        /// 属性を取得
        /// </summary>
        /// <param name="attrName">属性名</param>
        /// <returns>属性値</returns>
        public string GetAttribute(string attrName)
        {
            return this._el.GetAttribute(attrName);
        }

        /// <summary>
        /// 要素検索
        /// </summary>
        /// <param name="xPath">XPath</param>
        /// <returns>XmlElementWrapper</returns>
        public XmlElementWrapper SelectSingleNode(string xPath)
        {
            var node = this._el.SelectSingleNode(xPath);
            return node == null ? null : new XmlElementWrapper(node);
        }
    }
}
```

```C#
using System;
using System.IO;
using TestProject.Extension;

namespace TestProject
{
    /// <summary>
    /// メインクラス
    /// </summary>
    public class Program
    {
        /// <summary>
        /// メインエントリ
        /// </summary>
        /// <param name="args">実行時引数</param>
        public static void Main(string[] args)
        {
            try
            {
                // XmlDocument拡張クラス
                var doc = new XmlDocumentExtension();

                // root要素作成
                var root = doc.CreateRootOfElementWrapper("root");

                // rootの直下にtestAを作成
                var testA = root.AppendChild("testA");

                // testAの下にtestBを作成
                var testB = testA.AppendChild("testB");

                // testBの下に同じタグを追加
                var testTag1 = testB.AppendChild("testTag");
                var testTag2 = testB.AppendChild("testTag");

                // 値設定
                var value1 = "valueTag1";
                var value2 = "valueTag2";
                testTag1.SetValue(value1);
                testTag2.SetValue(value2);

                // 属性設定
                testTag1.SetAttribute("testAttr", "testAttrValue");

                // XPath(あえてrootから)
                var findEl = root.SelectSingleNode($"//testB/testTag[text()='{value1}']");

                Console.WriteLine("SelectSingleNode結果 XMLタグ名：" + findEl?.Name);
                Console.WriteLine("属性testAttr：" + findEl?.GetAttribute("testAttr"));

                // xml保存
                doc.Save(Path.Combine(Environment.CurrentDirectory, "test.xml"));
            }
            catch(Exception err)
            {
                Console.WriteLine(err.Message);                
            }
            finally
            {
                Console.Read();
            }
        }
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526982/b9d20df4-c992-7211-9efa-43d6e0791a10.png)

```XML
<?xml version="1.0" encoding="utf-8"?>
<root>
  <testA>
    <testB>
      <testTag testAttr="testAttrValue">valueTag1</testTag>
      <testTag>valueTag2</testTag>
    </testB>
  </testA>
</root>
```

# おわりに
ラップせずともXmlElementを継承して実現できるのであれば、各メソッドやプロパティを実装しなくてよいのですが・・・

アドバイス等ありましたら、いただけると幸いです。