[C#] XDocument から XmlNavigater を使えるように拡張

巷では、Windows 8 RTM, Visual Studio 2012 RTM で盛り上がっていますが、しばらくは平常運転で。
ちなみに 90日試用版は以下からダウンロードできます。

Download Windows 8 Enterprise Evaluation
http://msdn.microsoft.com/ja-jp/evalcenter/jj554510.aspx

# 英語のページだけど、きちんと日本語版がダウンロードできます。2.4GB 程度です。
# MSDN のほうも Japanese 版が用意されています。

さて、本題の XmlDom のほうはテストルーチンから作っていきます。
内部的には XML文字列 -> XDocument -> XmlDocument -> XmlNavigator の経由で渡しています。
ちまちま XML文字列をパースするコードを書いてもいいのですが、パース自体はあまりスピードを問わないので。
# 実は、System.Xml.Linq は Silverlight に含まれていない(今は含まれている?)ので避けていたのですが、
# Siverlight 自体が終了しそうな雰囲気なので、XDocument を使うことに決定

■テストコードを書く

以前に書いた XNode, XmlNode 構築のコードを XML文字列に直します。
Assert.AreEqual 部分はそのまま動くはず…というか、動かないと駄目です。

namespace TestXmlDom
{
	[TestClass]
	public class TestXmlDocument
	{
		[TestMethod]
		public void TestParse1()
		{
			string xml = "<root><name>masuda tomoaki</name></root>";
			var doc = new XmlDocument(xml);

			var q = new XmlNavigator(doc)
				.Where(n => n.TagName == "name")
				.FirstOrDefault();

			Assert.AreEqual("masuda tomoaki", q.Value);
		}

		[TestMethod]
		public void TestParse2()
		{
			string xml = @"
<root>
 <name>masuda</name>
 <name>yamada</name>
 <name>yamasaki</name>
</root>";
			var doc = new XmlDocument(xml);
			var q = new XmlNavigator(doc)
				.Where(n => n.TagName == "name")
				;
			
			Assert.AreEqual(3, q.Count());
			Assert.AreEqual("masuda", q.First().Value);
		}

		[TestMethod]
		public void TestParse3()
		{
			string xml = @"
<root>
 <person id='1'>
  <name>masuda</name>
  <age>44</age>
 </person>
 <person id='2'>
  <name>yamada</name>
  <age>20</age>
 </person>
 <person id='3'>
  <name>tanaka</name>
  <age>10</age>
 </person>
</root>";
			var doc = new XmlDocument(xml);
			var q = new XmlNavigator(doc)
				.Where(n => n.Attrs["id"] == "2")
				.FirstOrDefault();
			Assert.AreEqual("person", q.TagName);
			// ExDoc記述
			Assert.AreEqual("yamada", q / "name");
			Assert.AreEqual("20", q / "age");
		}
	}
}

XNode じゃなくて、XmlNode を使う理由としては「q / “name”」という書き方ができるからです。
この部分はXElemnetを使って「q.Element(“name”).Value」と書いても同じです。

ただし、q 自体は XNode を返すので Element() を含んでいなくていまいち不便なんですよね。

■XDocument から XmlDoucmnet を構築する

インターフェースとして、XmlDocument を用意します。
単純に、XDocument の中身を XmlDocument に移し替えているだけです。

ざっとインターフェースを合わせていきます。

namespace Moonmile.XmlDom
{
	public class XmlDocument : XmlNode
	{
		public XmlNode documentElement { 
			get
			{
				if (this.Children.Count() > 0)
				{
					return Children[0];
				}
				else
				{
					return XmlNode.Empty;
				}
			}
		}

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public XmlDocument() 
		{
		}

		/// <summary>
		/// コンストラクタ(XML文字列で初期化)
		/// </summary>
		/// <param name="xml"></param>
		public XmlDocument( string xml)
		{
			this.LoadXml(xml);
		}

		/// <summary>
		/// ファイルから構築する
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		public XmlDocument Load(string path)
		{
			return this.Load(XDocument.Load(path));
		}

		/// <summary>
		/// XML文字列から構築する
		/// </summary>
		/// <param name="xml"></param>
		/// <returns></returns>
		public XmlDocument LoadXml(string xml)
		{
			StringReader sr = new StringReader(xml);
			return this.Load(XDocument.Load(sr));
		}

		/// <summary>
		/// XDocumentオブジェクトから構築する
		/// </summary>
		/// <param name="doc"></param>
		/// <returns></returns>
		public XmlDocument Load(XDocument doc)
		{
			var root = LoadXNode(doc.FirstNode);
			this.Children.Add(root);
			return this;
		}

		protected XmlNode LoadXNode(XNode node)
		{
			var nn = new XmlNode(node.TagName(), node.Value());
			if (node.NodeType == System.Xml.XmlNodeType.Element)
			{
				var el = node as XElement;
				foreach (var at in el.Attributes())
				{
					nn.Attrs.Add(new XmlAttr(at.Name.ToString(), at.Value));
				}
				foreach (var n in el.Nodes())
				{
					nn.Children.Add(LoadXNode(n));
				}
			}
			return nn;
		}
	}
}

という訳で、これで XML を自由に比較的自由に扱うことができました。
今度は HTML 文字列を扱えるように、HtmlNode, HtmlDocument へ移していきます。

カテゴリー: C#, XmlDom パーマリンク