本章包括
n XML LINQ 查询轴方法
n 使用 XML LINQ 查询 XML 文档
n 转换 XML
10. 1 XML LINQ 轴方法
XML LINQ 提供了一些可以使用 LINQ 的轴方法。本章的目的就是介绍这些轴方法,然后将它们与查询操作符联合使用。开始之前,让我们先浏览一遍示例 XML 文件,列表 10.1 展示了书籍及其目录信息的 XML 文件内容。
列表 10.1 示例 XML 文件
<category name="Technical">
<category name=".NET">
<books>
<book>CLR via C#</book>
<book>Essential .NET</book>
</books>
</category>
<category name="Design">
<books>
<book>Refactoring</book>
<book>Domain Driven Design</book>
<book>Patterns of Enterprise Application Architecture</book>
</books>
</category>
<books>
<book>Extreme Programming Explained</book>
<book>Pragmatic Unit Testing with C#</book>
<book>Head First Design Patterns</book>
</books>
</category>
这个 XML 的层次很简单。它包含一个 parent <category> 元素,该元素又包含了一系列其它的子节点( <category> 元素或者 <books> 元素),这些子节点又有自己的子节点。使用 XML LINQ 轴方法,我们可以选择我们感兴趣的元素和属性。在继续之前,不得不强调一下,对于这些轴方法,上下文很重要。为了理解查询的结果,需要知道 XML 树中的当前位置。
现在我们需要轴方法的帮助,产生如下结果:
.NET
- CLR via C#
- Essential .NET
为了产生这种结果,需要先熟悉一下 Element, Attribute 和 Elements 的轴方法。
10.1. 1 Element
要产生以上结果,第一件要做的事就是选择 . NET category 元素。元素轴方法允许我们通过名称选择单个 XML 元素。如列表 10.2 所示:
列表 10.2 使用元素的轴方法选择一个元素
XElement root = XElement.Load("categorizedBooks.xml");
XElement dotNetCategory = root.Element("category");
Console.WriteLine(dotNetCategory);
需要阐明的一点是, Element 轴方法接受一个 XName 参数,并返回第一个匹配此 XName 的子元素。以上代码将会在控制台上输出以下内容:
<category name=".NET">
<books>
<book>CLR via C#</book>
<book>Essential .NET</book>
</books>
</category>
如果 Element 轴方法没有找到指定名称的元素,那么将返回一个 null 值。下面要做的就是获取 .NET category XElement 对象的 name 属性。
10.1. 2 Attribute
使用 Attribute 轴方法,可以获得 name 属性,跟 Element 轴方法一样, Attribute 也返回第一个匹配 XName 参数的属性,列表 10.3 显示了此轴方法的用法。
列表 10.3 使用 Attribute 获取一个 XML 元素上的属性
XElement root = XElement.Load("categorizedBooks.xml");
XElement dotNetCategory = root.Element("category");
XAttribute name = dotNetCategory.Attribute("name");
如果没有找到匹配的属性, Attribute 轴方法将会返回一个 null 。现在我们已经获得了 XAttribute 对象,可以通过将其转换为一个 string ,然后打印到控制台上,如下:
Console.WriteLine((string) name);
结果如下
.NET
接下来的工作,就是获取所有当前 catalog 元素包含的 book 内容。因为这是一个集合,所以我们需要使用 Elements 轴方法。
10.1. 3 Elements
Elements 方法和 Element 方法的主要区别是, Elements 方法返回所有匹配的元素。如你所料, Elements 方法返回一个 IEnumerable<XElement> 对象。列表 10.4 显示了如何使用 Elements 轴方法获取所有 .Net catalog 元素下 book 元素。
列表 10.4 使用 Elements 方法选择所有 book 字节点
XElement root = XElement.Load("categorizedBooks.xml");
XElement dotNetCategory = root.Element("category");
XAttribute name = dotNetCategory.Attribute("name");
XElement books = dotNetCategory.Element("books");
IEnumerable<XElement> bookElements = books.Elements("book");
Console.WriteLine((string) dotNetcategory);
foreach(XElement bookElement in bookElements)
{
Console.WriteLine(" - " + (string)bookElement);
}
运行以上代码,得到如下结果:
.NET
- CLR via C#
- Essential .NET
此外, Elements 方法还提供了一个无参数的重载,它会返回所有子节点。至此为止,我们已经实现了预定的目标。不过还有一些其它重要的轴方法也是我们应该学习的。
10.1. 4 Descendants
Descendants 轴方法与 Elements 方法类似,但是它只是在字节点中查找, Descendants 将会在所有的后代节点中查找。当你不确定要查找元素的层次的时候,使用 Descendants 方法非常有用。 Descendants 有一个接受 XName 的重载和一个无参数的重载,概念上跟 Elements 方法类似。还是使用 10.1 中 XML 数据,这次我们要获取所有的 book 元素,而不管它在哪个目录下。因为 book 元素在不同的层次下,所以我们不能使用 Elements 轴方法,使用 Descendants 方法可以很好的完成这个工作,如列表 10.5 所示:
列表 10.5 使用 Descendants 方法获取 book 元素
XElement books = XElement.Load("categorizedBooks.xml");
foreach(XElement bookElement in books.Descendants("book"))
{
Console.WriteLine((string)bookElement);
}
输出如下:
CLR via C# Essential .NET Refactoring
Domain Driven Design
Patterns of Enterprise Application Architecture
Extreme Programming
Explained Pragmatic Unit Testing with C#
Head First Design Patterns
还有一个 DescedantNodes 轴方法,它与 Descendants 方法类似。区别是, DescedantNodes 方法的返回结果还包含那些非元素节点(如 XComment 和 XProcessingInstruction )。它返回一个 IEnumerable< XNode > 对象,而不是 IEnumerable< XElement >.
注意到 Descendants 方法并不会把当前节点也包括在搜索上下文中。如果你希望搜索包含当前节点的功能,可以使用 DescendantsAndSelf 方法。再次返回 10.1 引入的 XML ,如下:
<category name="Technical">
<category name=".NET">
<books>
<book>CLR via C#</book>
<book>Essential .NET</book>
</books>
</category>
<category name="Design">
<books>
<book>Refactoring</book>
<book>Domain Driven Design</book>
<book>Patterns of Enterprise Application Architecture</book>
</books>
</category>
<books>
<book>Extreme Programming Explained</book>
<book>Pragmatic Unit Testing with C#</book>
<book>Head First Design Patterns</book>
</books>
</category>
列表 10.6 的代码比较了 Descendants 和 DescendantsAndSelf 方法。
列表 10.6 比较 Descendants 和 DescendantsAndSelf 方法
XElement root = XElement.Load("categorizedBooks.xml");
IEnumerable<XElement> categories = root.Descendants("category");
Console.WriteLine("Descendants");
foreach(XElement categoryElement in categories)
{
Console.WriteLine(" - " + (string)categoryElement.Attribute("name"));
}
categories = root.DescendantsAndSelf("category");
Console.WriteLine("DescendantsAndSelf");
foreach (XElement categoryElement in categories)
{
Console.WriteLine(" - " + (string)categoryElement.Attribute("name"));
}
结果如我们所料:
Descendants
- .NET
- Design
DescendantsAndSelf
- Technical
- .NET
- Design
因为 Elements 和 Descendants 返回 IEnumerable<XElement> 对象,所以我们可以使用 LINQ 标准操作符进行查询。如列表 10.7 所示:
列表 10.7 使用 LINQ 查询表达式查询 XML
XElement root = XElement.Load("categorizedBooks.xml");
var books = from book in root.Descendants("book")
select (string)book;
foreach(string book in books)
{
Console.WriteLine(book);
}
10.1. 5 Ancestor s
Ancestors 方法与 Descendants 方法非常相似,不过 Ancestors 是在 XML 树中向上查询。除此之外,它们有相同的参数和重载,还有一个相同的关联方法 AncestorNodes 。本节的示例中,我们要获取一个指定的 book 所属的所有 catalog 。因为 catalog 元素是嵌套的,所以我们要得到的 catalog 路径具有如下形式:
Domain Driven Design is in the: Technical/Design category.
首先选择我们需要的 book 元素。使用如下代码:
XElement root = XElement.Load("categorizedBooks.xml");
XElement dddBook =
root.Descendants("book")
.Where(book => (string)book == "Domain Driven Design")
.First();
最后完整的代码如列表 10.8 所示:
列表 10.8 使用 Ancestors
XElement root = XElement.Load("categorizedBooks.xml");
XElement dddBook = root.Descendants("book")
.Where(book => (string)book == "Domain Driven Design").First();
IEnumerable<XElement> ancestors = dddBook.Ancestors("category").Reverse();
string categoryPath = String.Join("/",
ancestors.Select(e => (string)e.Attribute("name")).ToArray());
Console.WriteLine((string)dddBook + " is in the: " + categoryPath + "category.");
结果如下:
Domain Driven Design is in the: Technical/Design category.
10 . 1 . 6 ElementsAfterSelf, NodesAfterSelf, ElementsBeforeSelf 和 NodesBeforeSelf
ElementsAfterSelf, ElementsBeforeSelf, NodesAfterSelf 和 NodesBeforeSelf 方法提供了一种获取位于当前元素之前或者之后所有元素的简单方法。从名字可以看出, ElementsBeforeSelf 和 ElementsAfterSelf 方法返回当前元素之前或者之后的所有 XElement 对象。不过,如果你需要获取所有节点而不只是元素,那么应该使用 NodesBeforeSelf 和 NodesAfterSelf 方法。需要注意的是, ElementsAfterSelf, ElementsBeforeSelf, NodesAfterSelf 和 NodesBeforeSelf 方法的选择范围只在当前节点的同级节点上。列表 10.9 显示了 ElementsBeforeSelf 的用法。
列表 10.9 使用 ElementsBeforeSelf 查找当前节点之前的所有同级节点
XElement root = XElement.Load("categorizedBooks.xml");
XElement dddBook =
root.Descendants("book")
.Where(book => (string)book == "Domain Driven Design")
.First();
IEnumerable<XElement> beforeSelf = dddBook.ElementsBeforeSelf();
foreach (XElement element in beforeSelf)
{
Console.WriteLine((string)element);
}
结果如下:
Refactoring
可以看见,查询局限于当前节点的同级节点之内。
10.1. 7 Visual Basic XML 轴属性
(请参阅原著)
10. 2 标准查询操作符
在下一节中,我们将会使用标准查询操作符和 XML LINQ 查找一些出色的 .NET 书籍。这些最受欢迎的前 20 本 .NET 书籍是从 Amazon.com 提供的 web 服务获得的。我们将会使用这些书籍数据讲解如何使用各种标准查询操作符查询 XML 。开始之前,需要我们学会如何从 Amazon.com 获取书籍数据。
Amazon 提供了用来访问各种数据的 web 服务。为了使用这些 web 服务,你需要使用它们的 web 服务程序注册 (http://www.amazon.com/gp/aws/registration/registration-form.html) 。注册以后,你就赋予了访问 Amazon web 服务的权限。 Amazon 的 web 服务提供了 SOAP 和 REST 两个版本。 本节,我们将通过 REST 接口访问 web 服务。可以使用下面的 URL 访问 REST 版本的 TagLookup 服务。
http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService
&AWSAccessKeyId={Your Access Key Here}
&Operation=TagLookup
&ResponseGroup=Tags,Small