[C#] XElement とは違う LINQ できる XmlNode を作成する | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3628
なところで、自前の XmlNode を使って LINQ 操作を実現した訳ですが、XNode との重複が激しいので、試しに System.Xml.Linq.XNode http://msdn.microsoft.com/ja-jp/library/system.xml.linq.xnode.aspx を使って書き直してみます、っていう実験を。
■XNavigatorを実装。
自前の XmlNavigator をそのままコピペして実装し直します。
いくつかのメソッド名を変更しますが、そのままですんなりコンパイルが通ります。
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 | public class XNavigator : IEnumerable<XNode> { XNode _root; public XNavigator(XNode root) { _root = root; } public IEnumerator<XNode> GetEnumerator() { return new Enumerator(_root); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return new Enumerator(_root); } protected class Enumerator : IEnumerator<XNode> { XNode _root; XNode _cur; public Enumerator(XNode root) { _root = root; } public XNode Current { get { return _cur; } } public void Dispose() { } object System.Collections.IEnumerator.Current { get { return _cur; } } public bool MoveNext() { if (_cur == null ) { _cur = _root; return true ; } if (_cur.NodeType == System.Xml.XmlNodeType.Element && ((XElement)_cur).Nodes().Count() > 0) { _cur = ((XElement)_cur).FirstNode; return true ; } if (_cur.NextNode != null ) { _cur = _cur.NextNode; return true ; } XNode cur = _cur; while ( true ) { XElement pa1 = cur.Parent; if (pa1 == null ) { _cur = null ; return false ; } if (pa1.NextNode != null ) { _cur = pa1.NextNode; return true ; } cur = pa1; } } public void Reset() { _cur = null ; } } } |
■テストコードを書く
XmlNavigator 用に書いたテストコードを XNavigator 用に直します。
- XML 構築のところは、XDocument を使って書き換え。
- XNode のままでは Where メソッドでうまく動かないので、ちょっと思案
という具合です。
XNode のままだと、実は Nodes コレクションとかが無いんですよね…このあたり、DOM の頃からそうなのですが、インターフェースを重視するあまり、利用する時に XElement にキャストをしないといけないという罠があります。
XNavigator 自体は、XElement, XText などのオブジェクトも返すので、この TagName や Value 当たりが鬼門となります。
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 | [TestClass] public class TestXNavi { [TestMethod] public void TestNormal1() { XDocument doc = new XDocument( new XElement( "root" , new XElement( "person" , new XElement( "name" , "masuda" )))); var q = new XNavigator(doc.FirstNode) .Where(n => n.TagName() == "name" ) .FirstOrDefault(); Assert.AreEqual( "masuda" , q.Value()); } [TestMethod] public void TestNormal2() { XDocument doc = new XDocument( new XElement( "root" , new XElement( "person" , new XElement( "name" , "masuda" ), new XElement( "name" , "yamada" ), new XElement( "name" , "yamasaki" )))); // タグが検索できた場合 var q = new XNavigator(doc.FirstNode) .Where(n => n.TagName() == "name" ) .Select( n => n ); Assert.AreEqual(3, q.Count()); Assert.AreEqual( "masuda" , q.First().Value()); } [TestMethod] public void TestNormal3() { XDocument doc = new XDocument( new XElement( "root" , new XElement( "person" , new XAttribute( "id" , "1" ), new XElement( "name" , "masuda" ), new XElement( "age" , "44" )), new XElement( "person" , new XAttribute( "id" , "2" ), new XElement( "name" , "yamada" ), new XElement( "age" , "20" )), new XElement( "person" , new XAttribute( "id" , "3" ), new XElement( "name" , "tanaka" ), new XElement( "age" , "10" )))); // タグが検索できた場合 var q = new XNavigator(doc.FirstNode) .Where(n => n.Attrs( "id" ) == "2" ) .FirstOrDefault(); Assert.AreEqual( "person" , q.TagName()); // 拡張メソッドを利用 Assert.AreEqual( "yamada" , q.Child( "name" ).Value()); Assert.AreEqual( "20" , q.Child( "age" ).Value()); } [TestMethod] public void TestNormal4() { XDocument doc = new XDocument( new XElement( "root" , new XElement( "person" , new XAttribute( "id" , "1" ), new XElement( "name" , "masuda" ), new XElement( "age" , "44" )), new XElement( "person" , new XAttribute( "id" , "2" ), new XElement( "name" , "yamada" ), new XElement( "age" , "20" )), new XElement( "person" , new XAttribute( "id" , "3" ), new XElement( "name" , "tanaka" ), new XElement( "age" , "10" )))); // クエリ文にしてみる var q = from n in new XNavigator(doc.FirstNode) where n.Attrs( "id" ) == "2" select n; Assert.AreEqual( "person" , q.First().TagName()); // 拡張メソッドを利用 Assert.AreEqual( "yamada" , q.First().Child( "name" ).Value()); Assert.AreEqual( "20" , q.First().Child( "age" ).Value()); } } |
■拡張メソッドを追加する
で、普通にやると断念してしまうので…無理矢理、XNode に拡張メソッドを追加します。
本来は、TagName プロパティ、Value プロパティのように「プロパティ」にしたいのですが、プロパティの拡張メソッドはできないので TagName() メソッド、Value() メソッドにします。
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 | /// <summary> /// XNodeをXElement風に使う拡張クラス /// </summary> public static class XNodeExtentions { public static string TagName( this XNode n) { if (n.NodeType == System.Xml.XmlNodeType.Element) { return ((XElement)n).Name.ToString(); } else { return "" ; } } public static string Value( this XNode n) { if (n.NodeType == System.Xml.XmlNodeType.Element) { return ((XElement)n).Value; } else { return "" ; } } public static string Attrs( this XNode n, string key) { if (n.NodeType == System.Xml.XmlNodeType.Element) { var attr = ((XElement)n).Attribute(key); if ( attr != null ) { return attr.Value; } } return "" ; } public static XNode Child( this XNode nd, string tag) { if (nd.NodeType == System.Xml.XmlNodeType.Element) { return ((XElement)nd).Nodes().Where(n => n.TagName() == tag).FirstOrDefault(); } else { return null ; } } } |
すると、上記のテストコードが通るようになります。
1 2 3 4 | var q = new XNavigator(doc.FirstNode) .Where(n => n.TagName() == "name" ) .FirstOrDefault(); Assert.AreEqual( "masuda" , q.Value()); |
ラムダ式の中の n が XNode なので、TagName() メソッドを使います、ってな具合。XElement の場合は Value プロパティになる訳で、ここのところ、
- XNode.Value() メソッド
- XElement.Value プロパティ
という「メソッド名とプロパティ名が同じ」という(多分)VB では使えないメソッドが作られていますが、まあ良しとしましょう(後で確認してみますか)。
こうすると、XDocument もツリー構造でサーチができるのでコードが簡単になりますね。って話で。
自前の XmlNode でもいいんだけど、node / “tag” のような書き方ができないから、どうしようかね、って具合です。