LINQ学习笔记:导航和查询X-DOM
导航/查询X-DOM
就像你可能期望的那样, XNode和XContainer类定义了方法和属性来测量X-DOM树. 与常规的DOM不同的是, 这些函数并不会返回实现了IList<T>的集合, 而是返回了一个单一值或者一个实现了IEnumerable<T>的序列. 基于这个你可以执行一个LINQ查询或者使用foreach来做枚举. 这同时也允许你使用熟悉的LINQ查询语法来执行简单的导航任务或者高级查询.
在X-DOM中, 元素和属性名都是大小写敏感的, 这与XML是一致的.
FirstNode, LastNode与Nodes
FirstNode与LastNode允许你直接访问第一个或者最后一个子节点; Nodes返回所有的子节点并形成一个序列. 这三个方法只用于直系的后代节点.
检索元素
Elements方法返回类型为XElement的子节点. 例如:
1: var bench = new XElement ("bench",
2: new XElement ("toolbox",
3: new XElement ("handtool", "Hammer"),
4: new XElement ("handtool", "Rasp")
5: ),
6: new XElement ("toolbox",
7: new XElement ("handtool", "Saw"),
8: new XElement ("powertool", "Nailgun")
9: ),
10: new XComment ("Careful with the nailgun")
11: );
12:
13: foreach (XElement e in bench.Elements( ))
14: Console.WriteLine (e.Name + "=" + e.Value);
15:
16: // 结果: toolbox=HammerRasp
17: toolbox=SawNailgun
以下的LINQ查询用于查询包含nail gun的toolbox:
1: IEnumerable<string> query =
2: from toolbox in bench.Elements( )
3: where toolbox.Elements( ).Any
4: (tool => tool.Value == "Nailgun")
5: select toolbox.Value;
6:
7: RESULT: { "SawNailgun" }
Elements等价于Nodes上面的LINQ查询, 我们之前的查询也可以被写为:
1: from toolbox in bench.Nodes().OfType<XElement>()
2: where ...
接下来的例子使用一个SelectMany查询检索hand tools:
1: IEnumerable<string> query =
2: from toolbox in bench.Elements( )
3: from tool in toolbox.Elements( )
4: where tool.Name == "handtool"
5: select tool.Value;
6:
7: RESULT: { "Hammer", "Rasp", "Saw" }
Elements也可以只返回给定名称的元素, 例如:
1: int x = bench.Elements ("toolbox").Count(); // 2
这等价于:
1: int x = bench.Elements()
2: .Where (e => e.Name == "toolbox")
3: .Count(); // 2
4:
Elements还定义了一个扩展方法接受IEnumerable<XContainer>参数. 更精确的说, 它接受了此种类型的参数:
1: IEnumerable<T> where T : XContainer
这让其可以和元素序列一起工作, 使用这个方法我们可以重写查找hand tools的查询:
1: from tool in bench.Elements ("toolbox")
2: .Elements ("handtool")
3: select tool.Value.ToUpper( );
第一个Elements绑定到XContainer上, 第二个则绑定到扩展方法上.
读取一个单一的元素
方法Element(单数)返回匹配给定名称的第一个元素. Element对于简单的导航是非常有用的, 例如:
1: var settings = XElement.Load ("databaseSettings.xml");
2:
3: string cx = settings.Element ("database")
4: .Element ("connectString")
5: .Value;
Element的作用相当于调用Elements然后再应用LINQ的FirstOrDefault并给定一个名称作为匹配断言. 如果没有任何元素匹配到, 则Element返回null.
如果元素xyz不存在, 那么Element(”xyz”).Value将会抛出一个NullReferenceException异常. 如果你倾向于使用null代替异常, 可以将XElement转换成string而不是调用它的Value属性, 如下:
1: string xyz =
2: (string) settings.Element("xyz");
XElement定义了一个显示的string转换正式为了这个目的.
递归功能
XContainer同时也定义了Descendants和DescendantNodes方法, 它们递归地返回子元素或者子节点.Descendant接受一个可选的元素名, 会到我们之前的例子, 我们可以使用Descendants去查找所有的hand tools:
1: Console.WriteLine
2: (bench.Descendants ("handtool").Count( )); // 3
不管是父节点还是叶节点都包含在整体横切中. 以下的查询取得所有包含单词”careful”, 存在于X-DOM任何地方的注释节点:
1: IEnumerable<string> query =
2: from c in bench.DescendantNodes( ).OfType<XComment>( )
3: where c.Value.Contains ("careful")
4: orderby c.Value
5: select c.Value;
查询父节点
所有的XNodes都有一个Parent属性和AncestorXX的方法用于父节点导航. 一个父亲节点永远是一个XElement.
如果x是一个XElement, 以下代码打印true:
1: foreach (XNode child in x.Nodes( ))
2: Console.WriteLine (child.Parent == x);
如果x是一个XDocument的话, XDocument有点独特, 它永远不能作为任何节点的父亲节点. 为了访问XDocument,应该使用Document属性, 这只争对X-DOM树上的任意对象可以工作.
Ancestors返回一个序列其第一个元素是Parent, 下一个元素则是Parent.Parent, 依次类推直到根元素.
你还可以使用LINQ查询AncestorsAndSelf().Last()来取得输入序列的根元素. 另外一种方法是调用Document.Root, 但只有当XDocument呈现的时候才能工作.
对等节点导航
使用PreviousNode和NextNode(FirstNode / LastNode),我们可以使用一个linked list横贯所有的节点. 这并不是巧合, 在内部, Nodes实际上就是存储在一个linked list.
属性导航
XAttributes定义了PreviousAttribute和NextAttribute, Parent也是一样.
Attributes方法接受了一个名称并返回包含0或1个元素的序列; 在XML中, 一个元素不能包含重复的属性名.
待续!