您的位置:首页 > 其它

使用XML LINQ查询和转换XML

2008-07-28 14:00 239 查看

使用XML LINQ查询和转换XML

本章包括
n XML LINQ查询轴方法
n 使用XML LINQ查询XML文档
n 转换XML

10.1 XML LINQ 轴方法

XML LINQ提供了一些可以使用LINQ的轴方法。本章的目的就是介绍这些轴方法,然后将它们与查询操作符联合使用。开始之前,让我们先浏览一遍示例XML文件,列表10.1展示了书籍及其目录信息的XML文件内容。
列表[/b]10.1 [/b]示例[/b]XML[/b]文件[/b][/b]
<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[/i]
- CLR via C#[/i]
- Essential .NET[/i]
为了产生这种结果,需要先熟悉一下Element, Attribute和Elements的轴方法。

10.1.1 Element

要产生以上结果,第一件要做的事就是选择. NET category元素。元素轴方法允许我们通过名称选择单个XML元素。如列表10.2所示:
列表[/b]10.2 [/b]使用元素的轴方法选择一个元素[/b][/b]
XElement root = XElement.Load("categorizedBooks.xml");
XElement dotNetCategory = root.Element("category");
Console.WriteLine(dotNetCategory);
需要阐明的一点是,Element轴方法接受一个XName参数,并返回第一个匹配此XName的子元素。以上代码将会在控制台上输出以下内容:
<category name=".NET">[/i]
<books>[/i]
<book>CLR via C#</book>[/i]
<book>Essential .NET</book>[/i]
</books>[/i]
</category>[/i]
如果Element轴方法没有找到指定名称的元素,那么将返回一个null值。下面要做的就是获取.NET category XElement对象的name属性。

10.1.2 Attribute

使用Attribute轴方法,可以获得name属性,跟Element轴方法一样,Attribute也返回第一个匹配XName参数的属性,列表10.3显示了此轴方法的用法。
列表[/b]10.3 [/b]使用[/b]Attribute[/b]获取一个[/b]XML[/b]元素上的属性[/b][/b]
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元素。
列表[/b]10.4 [/b]使用[/b]Elements[/b]方法选择所有[/b]book[/b]字节点[/b][/b]
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[/i]
- CLR via C#[/i]
- Essential .NET[/i]
此外,Elements方法还提供了一个无参数的重载,它会返回所有子节点。至此为止,我们已经实现了预定的目标。不过还有一些其它重要的轴方法也是我们应该学习的。

10.1.4 Descendants

Descendants轴方法与Elements方法类似,但是它只是在字节点中查找,Descendants将会在所有的后代节点中查找。当你不确定要查找元素的层次的时候,使用Descendants方法非常有用。Descendants有一个接受XName的重载和一个无参数的重载,概念上跟Elements方法类似。还是使用10.1中XML数据,这次我们要获取所有的book元素,而不管它在哪个目录下。因为book元素在不同的层次下,所以我们不能使用Elements轴方法,使用Descendants方法可以很好的完成这个工作,如列表10.5所示:
列表[/b]10.5 [/b]使用[/b]Descendants [/b]方法获取[/b]book[/b]元素[/b][/b]
XElement books = XElement.Load("categorizedBooks.xml");
foreach(XElement bookElement in books.Descendants("book"))
{
Console.WriteLine((string)bookElement);
}

输出如下:
CLR via C# Essential .NET Refactoring[/i]
Domain Driven Design[/i]
Patterns of Enterprise Application Architecture[/i]
Extreme Programming [/i]
Explained Pragmatic Unit Testing with C# [/i]
Head First Design Patterns[/i]
还有一个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方法。
列表[/b]10.6 [/b]比较[/b]Descendants [/b]和[/b]DescendantsAndSelf [/b]方法[/b][/b]
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所示:
列表[/b]10.7 [/b]使用[/b]LINQ[/b]查询表达式查询[/b]XML[/b]
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 Ancestors

Ancestors方法与Descendants方法非常相似,不过Ancestors是在XML树中向上查询。除此之外,它们有相同的参数和重载,还有一个相同的关联方法AncestorNodes。本节的示例中,我们要获取一个指定的book所属的所有catalog。因为catalog元素是嵌套的,所以我们要得到的catalog路径具有如下形式:
Domain Driven Design is in the: Technical/Design category.[/i]
首先选择我们需要的book元素。使用如下代码:
XElement root = XElement.Load("categorizedBooks.xml");
XElement dddBook =
root.Descendants("book")
.Where(book => (string)book == "Domain Driven Design")
.First();
最后完整的代码如列表10.8所示:
列表[/b] 10.8 [/b]使用[/b]Ancestors [/b]
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.[/i]

10.1.6 ElementsAfterSelf, NodesAfterSelf, ElementsBeforeSelf和NodesBeforeSelf

ElementsAfterSelf, ElementsBeforeSelf, NodesAfterSelf和NodesBeforeSelf方法提供了一种获取位于当前元素之前或者之后所有元素的简单方法。从名字可以看出,ElementsBeforeSelf 和 ElementsAfterSelf方法返回当前元素之前或者之后的所有XElement对象。不过,如果你需要获取所有节点而不只是元素,那么应该使用NodesBeforeSelf 和 NodesAfterSelf方法。需要注意的是,ElementsAfterSelf, ElementsBeforeSelf, NodesAfterSelf和NodesBeforeSelf方法的选择范围只在当前节点的同级节点上。列表10.9显示了ElementsBeforeSelf的用法。
列表[/b]10.9 [/b]使用[/b]ElementsBeforeSelf [/b]查找当前节点之前的所有同级节点[/b][/b]

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[/i]
可以看见,查询局限于当前节点的同级节点之内。

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
&Version=2007-07-16
&TagName={Tag}
&Count=20
如果你使用Amazon提供的key替换“{Your Access Key Here}”,并且用你喜欢的tag替换“{Tag}”,然后用浏览器打开这个链接,图10.1显示了dotnet标记返回的XML结果。可以看到XML包含一个<Tag>元素,每个<TaggedItems>元素表示在Amazon.com中搜索到的包含标记”dotnet”的每本书。

图[/b]10.1 [/b]在[/b]Amazon.com [/b]中使用[/b]TagLookup web service [/b]查找[/b]“dotnet” [/b]标记[/b]
[/b]

[/b]

10.2.1 使用Select映射

Select是最常用的标准查询操作符。在我们的示例中,序列是一个IEnumerable<XElement>对象。列表10.16显示了如何使用Select操作符查询从Amazon.com web 服务中返回的book数据。
列表[/b] 10.16 [/b]使用[/b]Select[/b]查询[/b]XML[/b]
string url =
"http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService" +
"&AWSAccessKeyId={Your Access Key Here}" +
"&Version=2007-07-16" +
"&Operation=TagLookup" +
"&ResponseGroup=Tags,Small" +
"&TagName=dotnet" +
"&Count=20";

XNamespace ns =
"http://webservices.amazon.com/AWSECommerceService/2007-07-16";

XElement tags = XElement.Load(url);
var titles = tags.Descendants(ns + "Title")
.Select(titleElement => (string)titleElement);

foreach (string title in titles)
{
Console.WriteLine(title);
}
代码的作用无需赘述,列表10.17的代码使用查询表达式完成了同样的功能。
列表[/b]10.17 [/b]使用查询表达式查询[/b]XML[/b]

XElement tags = XElement.Load(url);
var titles = from title in tags.Descendants(ns + "Title")
select (string)title;

10.2.2 使用Where

我们要查询一本关于Windows Presentation Foundation的书籍,编码之前,让我们熟悉一下表示一本书籍的标记内容:
<TaggedItems>
<Item>
<ASIN>0201734117</ASIN>
<ItemAttributes>
<Author>Don Box</Author>
<Manufacturer>Addison-Wesley Professional</Manufacturer>
<ProductGroup>Book</ProductGroup>
<Title>Essential .NET, Volume I: The Common Language Runtime</Title>
</ItemAttributes>
</Item>
</TaggedItems>

列表10.18使用Where查询表达式完成了这个任务。
列表[/b]10.18 [/b]使用[/b]where[/b]子句[/b][/b]
string url =
"http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService" +
"&AWSAccessKeyId={Your Access Key Here}" +
"&Version=2007-07-16" +
"&Operation=TagLookup" +
"&ResponseGroup=Tags,Small" +
"&TagName=dotnet" +
"&Count=20";

XNamespace ns =
"http://webservices.amazon.com/AWSECommerceService/2007-07-16";
XElement tags = XElement.Load(url);
var wpfBooks =
from book in tags.Descendants(ns + "Item")
let bookAttributes = book.Element(ns + "ItemAttributes")
let title = ((string)bookAttributes.Element(ns + "Title"))
where title.Contains("Windows Presentation Foundation")
select title;

foreach (string title in wpfBooks)
{
Console.WriteLine(title);
}

结果如下:
Windows Presentation Foundation Unleashed (WPF) (Unleashed) [/i]
Programming Windows Presentation Foundation (Programming)[/i]

10.2.3 排序和分组

列表10.19的代码完成了按照书籍名称排序的功能。
列表[/b]10.19 [/b]使用[/b]orderby[/b]表达式排序结果[/b][/b]

XNamespace ns =
"http://webservices.amazon.com/AWSECommerceService/2007-07-16";

string url =
"http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService" +
"&AWSAccessKeyId={Your Access Key Here}" +
"&Version=2007-07-16" +
"&Operation=TagLookup" +
"&ResponseGroup=Tags,Small" +
"&TagName=dotnet" +
"&Count=20";

XElement tags = XElement.Load(url);
var groups =
from book in tags.Descendants(ns + "Item")
let bookAttributes = book.Element(ns + "ItemAttributes")
let title = (string)bookAttributes.Element(ns + "Title")
orderby title
select title;

如果需要对书籍按照publisher分组,可以使用GroupBy查询操作符。如列表10.20所示。
列表[/b] 10.20 [/b]使用[/b]group[/b]表达式分组[/b][/b]

XElement tags = XElement.Load(url);
var groups =
from book in tags.Descendants(ns + "Item")
let bookAttributes = book.Element(ns + "ItemAttributes")
let title = (string)bookAttributes.Element(ns + "Title")
let publisher = (string)bookAttributes.Element(ns + "Manufacturer")
orderby publisher, title
group title by publisher;
分组后的结果实现了IGrouping<K, T>接口。K类型是用来分组的类型,T类型是被分组的类型。如下所示:
group title by publisher;
T K
通过如下代码可以将分组的结果输出到控制台上。
foreach (var group in groups)
{
Console.WriteLine(group.Count() + " book(s) published by " + group.Key);
foreach (var title in group)
{
Console.WriteLine(" - " + title);
}
}
输出结果如下:

4 book(s) published by Addison-Wesley Professional
- Essential .NET, Volume I: The Common Language Runtime
- Framework Design Guidelines: Conventions, Idioms, and Patterns for
Reusable .NET Libraries (Microsoft .NET Development Series)
- The .NET Developer's Guide to Directory Services Programming (Microsoft
.NET Development Series)
- The .NET Developer's Guide to Windows Security (Microsoft .NET Development Series)
5 book(s) published by Apress

- Foundations of F#
- Pro .NET 2.0 Windows Forms and Custom Controls in C#
- Pro C# 2005 and the .NET 2.0 Platform, Third Edition
- Pro C# with .NET 3.0, Special Edition (Pro)
- Pro WF: Windows Workflow in .NET 3.0 (Expert's Voice in .Net)
1 book(s) published by Cambridge University Press
- Data Structures and Algorithms Using C#
3 book(s) published by Microsoft Press
- Applications = Code + Markup: A Guide to the Microsoft Windows
Presentation Foundation (Pro - Developer)
- CLR via C#, Second Edition (Pro Developer)
- Inside Windows Communication Foundation (Pro Developer)
4 book(s) published by O'Reilly Media, Inc.
- C# Cookbook, 2nd Edition (Cookbooks (O'Reilly))
- Programming .NET Components, 2nd Edition
- Programming WCF Services (Programming)
- Programming Windows Presentation Foundation (Programming)
1 book(s) published by Sams
- Windows Presentation Foundation Unleashed (WPF) (Unleashed)
2 book(s) published by Wrox
- Professional .NET Framework 2.0 (Programmer to Programmer)
- Professional C# 2005 (Wrox Professional Guides)
可以看到XML LINQ完全支持标准的查询操作符,不过在XML LINQ出现之前,对XML的查询都是通过XPath完成的。

10.3 在XML LINQ中使用XPath查询

XPath是用来在XML文档中查找信息的语言。跟轴方法和LINQ不同的是,XPath提供了一种基于文本的查询语言来定义查询。
虽然我们可以使用XML LINQ进行信息的查询,但是有时仍然需要使用XPath查询。为了在LINQ XML中使用XPath查询,需要添加System.Xml.XPath命名空间。如下:
using Sytem.Xml.XPath;
添加了System.Xml.XPath命名空间,XNode类就添加了一系列扩展方法。第一个扩展方法是CreateNavigator方法,这允许我们从一个现有的XNode对象创建一个XPathNavigator对象。除了可以创建一个XPathNavigator对象,XPathEvaluate扩展方法允许对XNode上的XPath表达式进行计算。最后,XPathSelectElement方法返回第一个匹配表达式的元素, XPathSelectElements方法返回所有匹配表达式的元素,为了查询如下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.21.显示了如何使用XPath查询XML数据:
列表[/b]10.21 [/b]使用[/b]XPath[/b]查询[/b]XElement [/b]对象[/b][/b]
XElement root = XElement.Load("categorizedBooks.xml");
var books = from book in root.XPathSelectElements("//book")
select book;

foreach(XElement book in books)
{
Console.WriteLine((string)book);
}

输出结果如下:
CLR via C# Essential .NET Refactoring[/i]
Domain Driven Design[/i]
Patterns of Enterprise Application Architecture[/i]
Extreme Programming Explained [/i]
Pragmatic Unit Testing with C# [/i]
Head First Design Patterns[/i]
有了这些扩展方法,我们可以有两种选择处理XML。使我们的应用程序可以进行一个平稳的移植。

10.4 使用XML LINQ和XSLT进行转换

为了使用XML LINQ和XSLT转换XML,需要添加System.Xml.Xsl命名空间的引用。列表10.26显示了实现转换的代码。
列表[/b]10.26 [/b]使用[/b]Transforming an XElement using XSLT[/b]
string xsl = @"<?xml version='1.0' encoding='UTF-8' ?>
<xsl:stylesheet version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<xsl:template match='books'>
<html>
<title>Book Catalog</title>
<ul>
<xsl:apply-templates select='book'/>
</ul>
</html>
</xsl:template>
<xsl:template match='book'>
<li>
<xsl:value-of select='title'/> by
<xsl:apply-templates select='author'/>
</li>
</xsl:template>
<xsl:template match='author'>
<xsl:if test='position() > 1'>, </xsl:if>
<xsl:value-of select='.'/>
</xsl:template>
</xsl:stylesheet>";

XElement books = XElement.Load("books.xml");
XDocument output = new XDocument();
using (XmlWriter writer = output.CreateWriter())
{
XslCompiledTransform xslTransformer = new XslCompiledTransform();
xslTransformer.Load(XmlReader.Create(new StringReader(xsl)));
xslTransformer.Transform(books.CreateReader(), writer);
}
Console.WriteLine(output);
为了重用转换代码,可以将转换逻辑封装到一个扩展方法中,如列表10.27所示:
列表[/b]10.27 [/b]使用[/b]XSL[/b]转换[/b]XNode[/b]的扩展方法[/b][/b]
public static class XmlExtensions {
public static XDocument XslTransform(this XNode node, string xsl)
{
XDocument output = new XDocument();
using (XmlWriter writer = output.CreateWriter())
{
XslCompiledTransform xslTransformer = new XslCompiledTransform();
xslTransformer.Load(XmlReader.Create(new StringReader(xsl)));
xslTransformer.Transform(node.CreateReader(), writer);
}
return output;
}
}
使用这个扩展方法,我们可以应用如下代码进行转换:
XElement.Load("books.xml").XslTransform(xsl));

10.5 摘要

在本章中,我们为您展示了如何使用XML LINQ查询和转换。并学习了许多XML LINQ轴方法。这些轴方法允许我们选择元素和属性。接下来又讲述了如何在XML LINQ中使用查询操作符,不过还是有一些我们没有遇到的通用场景,我们将会在下一章讲述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: