ノード作成時のXmlDocument.CreateElementが面倒
XMLファイルをXmlDocumentで作成する場合、testAノードの下にtestBノードを作成したい時に、testA.AppendChild(testB)としたいのに、毎回XmlDocument.CreateElement(新しいノード)をする必要があるため、より直感的にAppendできように簡単なラッパークラスを考えてみました。
環境
IDE:VisualStudio2019
アプリケーション:コンソールアプリ
フレームワーク:.NET Core 3.1
XmlDocumentを持ちまわる
XmlElementをラップし、さらにXmlDocumentを持ちまわって、Append時にそのXmlDocumentでCreateElementさせます。
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の拡張クラスを用意しています。
このクラスの使用イメージは以下です。
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 version="1.0" encoding="utf-8"?>
<root>
<testA>
<testB />
</testA>
</root>
必要なメソッドを実装していく
値設定/値取得や、XPath、属性設定等 必要に応じてメソッドを用意し、ラップしたXElementの各処理を実装します。
/// <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);
}
サンプル全文
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);
}
}
}
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();
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<testA>
<testB>
<testTag testAttr="testAttrValue">valueTag1</testTag>
<testTag>valueTag2</testTag>
</testB>
</testA>
</root>
おわりに
ラップせずともXmlElementを継承して実現できるのであれば、各メソッドやプロパティを実装しなくてよいのですが・・・
アドバイス等ありましたら、いただけると幸いです。