转载http://www.cnblogs.com/luluping/archive/2009/05/19/1459842.html
操作XML
XML LINQ简介
本章包括
n XML LINQ设计原则
n XML LINQ类层次
n 加载、解析和操作XML
XML LINQ允许我们使用我们已经熟悉的LINQ查询语法查询XML数据,而不是学习查询XML的新的API。此外,XML LINQ也提供了一些轻量级XML API用来操作XML,这些API利用了提供了类似DOM的操作,但是在设计上更加直观。本章的目的就是学习XML LINQ提供的API。为了成为XML LINQ的专家,我们首先需要掌握一些关键的设计原则,在本章中,我们将会介绍这些设计原则和LINQ XML API的核心概念。
9.1 什么是XML API?
XML API提供了操作XML数据的编程接口。通过使用这些API,我们可以创建使用XML的程序,如创建一个使用列表9.1所示XML的应用程序,列表9.1包含了一个站点地址列表。
列表 9.1 包含站点链接的XML文件
<links>
<link>
<url>http://linqinaction.net</url>
<name>LINQ in Action</name>
</link>
<link>
<url>http://hookedonlinq.com</url>
<name>Hooked on LINQ</name>
</link>
<link>
<url>http://msdn.microsoft.com/data/linq/</url>
<name>The LINQ Project</name>
</link>
</links>
我们可以打开一个XML文件读取其内容,还可以修改指定元素的内容,这些工作就是XML API工作的基本内容。在.NET中,我们有大量的API用来完成这些操作。如:XmlTextReader,XmlReader,XmlNode, XPathNavigator等。每种API都有自己的优点和缺点。但是都能完成开发者处理XML的目标。既然已经有那么多可用的XML API可用,又为什么需要XML LINQ呢?
9.2为什么我们需要XML LINQ?
使用那些现有的API,开发者需要做很多的思考。我需要知道XSLT, XPath, XQuery, XML DOM等技术内容,而这些技术跟C#是一个完全不同的概念模型。如果每天都在使用这些API处理XML,这可能不是一个问题。但是对于大多数开发者而言,针对这些XML技术的工作量是巨大的。
XML LINQ的目标就是提供一种简单处理XML的方法。它提供了查询和转换XML的能力,并将XQuery和XPath集成到.NET编程语言中。除了提供给开发者更易于使用的XML API,XML LINQ同样利用了编程语言的优点,如利用扩展方法,匿名类型和lambda表达式等。下面就让我们看一下XML LINQ的设计原则。
9.3 XML LINQ 设计原则
为了让XML开发人员的工作更加高效和愉快,微软使用了一种全新的方法设计了XML LINQ。无论从概念上还是性能上,XML LINQ都是一个轻量级的XML API。下面会使用XML LINQ创建一个XML 文档,用以阐述XML LINQ如何让我们的工作变得高效。我们的目标是创建一个包含book信息的XML文档,如列表9.2:
列表9.2 包含Book信息的XML文档
<books>
<book>
<title>LINQ in Action</title>
<author>Fabrice Marguerie </author>
<author>Steve Eichert</author>
<author>Jim Wooley</author>
<publisher>Manning</publisher>
</book>
</books>
下面是使用传统的XML API创建文档的代码,如列表9.3:
列表 9.3 使用DOM创建XML文档
XmlDocument doc = new XmlDocument();
XmlElement books = doc.CreateElement("books");
XmlElement author1 = doc.CreateElement("author");
author1.InnerText = "Fabrice Marguerie";
XmlElement author2 = doc.CreateElement("author");
author2.InnerText = "Steve Eichert";
XmlElement author3 = doc.CreateElement("author");
author3.InnerText = "Jim Wooley";
XmlElement title = doc.CreateElement("title");
title.InnerText = "LINQ in Action";
XmlElement book = doc.CreateElement("book");
book.AppendChild(author1);
book.AppendChild(author2);
book.AppendChild(author3);
book.AppendChild(title);
books.AppendChild(book);
doc.AppendChild(books);
这种传统的编程方式很难让我们看到创建的目标XML是什么样子。此外还需要创建很多临时变量用来保持那些我们创建的元素。结果是这种代码很难阅读,调试和维护。下面让我们看看XML LINQ如何创建XML文档,如列表9.4:
列表9.4 使用XML LINQ创建XML文档
new XElement("books",
new XElement("book",
new XElement("author", "Fabrice Marguerie"),
new XElement("author", "Steve Eichert"),
new XElement("author", "Jim Wooley"),
new XElement("title", "LINQ in Action"),
new XElement("publisher", "Manning")
)
);
通过使用这些便利的构造函数,我们可以使用XML LINQ快速的编写创建我们文档的代码。不用担心创建那些父节点元素了,而且代码结构与结果XML文档的结构非常相似。这就是设计XML LINQ的关键概念。
9.3.1 关键概念:功能构建
XML LINQ为创建XML元素提供了一种更强大的方式:功能构建模式。功能构建模式允许使用一个语句创建一个XML树。这使得创建过程与结果很相似。这是传统的API所不具备的。功能构建模式的目的是让我们的头脑中想着XML而不是创建XML的技术。图9.1显示XML LINQ代码何其创建的XML文本,它们非常相似。
图 9.1 XML LINQ代码和其创建的XML文本结果
下面要讲述的是XML LINQ的另一个关键概念,自由上下文(context-free)XML创建。
9.3.2 关键概念:自由上下文XML创建
当使用DOM创建XML的时候,每个对象都必须在其文档对象的上下文中创建。使用XML LINQ,元素和属性可以在文档对象之外创建。这允许程序员用更自然的方法创建元素,而不是使用那些工厂方法。这么做的结果使得代码更容易阅读和理解,此外,创建元素和属性变得更加容易,因为不再需要一个文档节点。虽然文档节点失去了以往的高贵地位,但是在创建那些包含XML声明,文档类型定义和XML处理指令的时候,XML LINQ同样提供了XDocument类。下面要讲述的是另一个关键概念。
9.3.3 关键概念:简单名称
XML中最令人困惑的一个方面就是XML名称,XML命名空间和命名空间前缀。当使用DOM创建元素的时候,开发人员有很多重载的工厂方法允许指定元素名称的细节。DOM计算元素名称的方式会让人感到困惑。使用XML LINQ的时候,XML名称大大的简化了。不用再担心本地名称,限定名称,命名空间和命名空间前缀,我们可以只关注一个完整的扩展名称。XName类表示一个完整的扩展名称,它包含了元素的名称空间和本地名称。当一个XName包含命名空间的时候,会按如下形式组织:{http://schemas.xyxcorp.com/}localname。
除了简化命名,XML LINQ同样使得针对指定了命名空间的XML查询变得简单。如下查询RSS反馈的查询代码。列表9.5显示了XML文档结构:
列表9.5 使用XML命名空间的RSS反馈
<?xml-stylesheet href=http://iqueryable.com/friendly-rss.xsl type="text/xsl" media="screen"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Steve Eichert</title>
<link>http://iqueryable.com/</link>
<generator>ActiveType CMS v0.1</generator>
<dc:language>en-US</dc:language>
<description />
<item>
<dc:creator>Steve Eichert</dc:creator>
<title>Parsing WordML using LINQ to XML</title>
<link>http://iqueryable.com/LINQ/ParsingWordMLusingLINQ to XML</link>
<pubDate>Wed, 02 Aug 2006 15:52:44 GMT</pubDate>
<guid>http://iqueryable.com/LINQ/ParsingWordMLusingLINQ to XML</guid>
<comments>
http://iqueryable.com/LINQ/ParsingWordMLusingLINQ to XML#comments
</comments>
<wfw:commentRss>
http://iqueryable.com/LINQ/ParsingWordMLusingLINQ to
XML/commentRss.aspx
</wfw:commentRss>
<slash:comments>1</slash:comments>
<description>Foo…</description>
</item>
</channel>
</rss>
该RSS文档使用了一些XML命名空间:http://purl.org/dc/ele-ments/1.1/, http://purl.org/rss/1.09/modules/slash/和http://well-formedweb.org/commentapi/。列表9.6显示了查询使用了上述命名空间的元素的DOM代码。
列表9.6 通过DOM查询包含命名空间的元素
XmlDocument doc = new XmlDocument();
doc.Load("http://iqueryable.com/rss.aspx");
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");
ns.AddNamespace("slash", "http://purl.org/rss/1.0/modules/slash/");
ns.AddNamespace("wfw", "http://wellformedweb.org/CommentAPI/");
XmlNodeList commentNodes = doc.SelectNodes("//slash:comments", ns);
foreach(XmlNode node in commentNodes)
{
Console.WriteLine(node.InnerText);
}
我们创建一个XMLNamespaceManager对象,用来查询那些使用特定命名空间的元素,如果需要查询那些没有命名空间的元素,可以使用如下代码:
XmlNodeList titleNodes = doc.SelectNodes("/rss/channel/item/title");
foreach(XmlNode node in titleNodes)
{
Console.WriteLine(node.InnerText);
}
XML LINQ提供了一个更自然的处理命名空间的方法。忘了XMLNamespaceManager吧,但需要记住如下一个简单规则:
处理元素和属性的时候,总是使用完整扩展名
列表9.7显示了XML LINQ查询XML的代码
列表9.7 使用XML LINQ查询包含命名空间的元素
XElement rss = XElement.Load("http://iqueryable.com/rss.aspx");
XNamespace dc = "http://purl.org/dc/elements/1.1/";
XNamespace slash = "http://purl.org/rss/1.0/modules/slash/";
XNamespace wfw = "http://wellformedweb.org/CommentAPI/";
IEnumerable<XElement> comments = rss.Descendants(slash + "comments");
foreach(XElement comment in comments)
{
Console.WriteLine((int)comment);
}
IEnumerable<XElement> titles = rss.Descendants("title");
foreach(XElement title in titles)
{
Console.WriteLine((string)title);
}
如上所示,XML LINQ处理命名空间的方式是很直接的。在我们的第一个查询中,我们将本地名称追加到一个XNamespace (slash)上。在第二个查询中,我只是使用本地名称进行查询。通过将本地名称和命名空间合并一个概念中,XML LINQ使得处理XML名称,命名空间,命名空间前缀时变得简单。所有有关名称的概念都被封装到一个XName中。
下面将会讲出XML LINQ的类层次,通过这些类,我们可以轻松的构建一个XML应用程序。
9.4 XML LINQ 类层次
在使用XML LINQ处理XML之前,首先需要理解XML LINQ提供的那些主要的类。幸运的是,XML LINQ有一个相对较小的层次结构,如图9.2所示:
图 9.2 XML LINQ类层次结构
在图9.2的上方是XObject类,XObject类是XML LINQ中大多数类的基类。它提供了一个AddAnnotation方法来添加用户定义信息,如行号,到XML LINQ对象中,也提供了一个RemoveAnnotation方法来删除注释。为了获取注释,XObject对象提供了Annotation, Annotation<T>, Annotations和Annotations<T>这几个辅助方法。
在XObject类的下方是抽象的XNode类,XNode类是XML LINQ中表示元素节点的类的基类。它提供了许多更新和获取内容的方法,如AddAfterSelf, AddBeforeSelf, Remove, Ancestors, ElementsAfterSelf, ElementsBeforeSelf, NodesAfterSelf, and NodesBeforeSelf。
在XNode类的下方是XContainer类。XContainer是可以包含其它XNode对象的类的基类。XContainer添加了额外的更新方法,如Add, AddFirst, RemoveNodes和ReplaceNodes。还添加了一些辅助方法,如Nodes, Descendants, Element和Elements。XContainer是XML LINQ中最重要的两个类的基类:XElement 和XDocument。XElement类表示一个XML元素节点,它可以包含其它元素节点。它添加了其它的辅助方法,如Attributes, AncestorsAndSelf和 DescendantAndSelf, 及一些更新方法RemoveAll, RemoveAttributes, SetElementValue和SetAttributeValue。作为XML LINQ中最重要的类,XElement同样提供了一个静态的Load方法,它允许从外部源加载XML。还提供了一个Parse方法,允许从一个XML字符串创建一个XElement对象。最后XElement提供了一个Save方法,用来将内存中的XML内容存储到磁盘中。同样,WriteTo方法允许将XML写入到一个XmlWriter中。
XAttribute类表示XML LINQ中的元素属性。跟其它的类不同,XAttribute类并不从XNode继承。XAttribute对象是与XElement对象相关的名值对。XAttribute类提供了一个Parent属性和Remove方法。
虽然XML文档在XML LINQ中功能已经被削弱了,但是我们仍然需要它,所以XML LINQ包含了XDocument类,XDocument类表示一个完整的XML文档。跟XElement类一样,它也包含一个静态的Load方法,用来加载XML文档。也包含一个Parse方法,使得可以从一个XML字符串构造一个XDocument对象。还有Save和WriteTo方法用来保存XML文档。XDocument和XElement类的主要区别是XDocument类包含一个跟XElement对象,而且它还可以包含一下任何一个节点:
n 一个 XML声明
n 一个XML文档类型
n XML处理指令
前面提到过,XML LINQ的一个关键概念就是简化了XML名称。实现此功能的两个类是XName 和 XNamespace,XName表示一个XElement 或者XAttribute的完整名称。这个完整名称以{命名空间}本地名称 的形式展示。XNamespace表示XName的命名空间部分,可以通过XName的Namespace属性来获取命名空间属性值。XName和XNamespace类都实现了一个重载的隐式转换操作符,允许一个字符串隐式转换到一个XName或XNamespace对象。这使得我们在构造XML的时候变得简单。在XML LINQ类层次中,还有一些补充类,也许你很少见到它们,如表9.1所示:
表 9.1 XML LINQ的补充类
Class | Description |
XDeclaration | 代表一个XML声明 |
XComment | 代表XML注释 |
XDocumentType | 代表XML DTD. |
XProcessingInstruction | 代表XML处理指令 |
XStreamingElement | 使用此类可以创建支持延迟流输出的 XML 树 |
XText和 XCData | XML LINQ的文本节点类。文本节点用来创建CData节。CData节用来处理混合内容 |
现在,我们已经熟悉了XML LINQ的类层次结构,下面就让我们亲自动手使用XML LINQ处理XML文档。
9.5 使用LINQ处理XML
在本节中,我们将会集中关注那些在构建应用程序是最关键的类和方法。对XML的处理一般会在以下几个方面:一是加载XML文档,包括从磁盘中或者Web站点中。一旦读取了XML文本,就可以使用XElement或XDocument的Parse方法对文本进行解析。接下来,我们还将学习如何创建自己的XML文档,以及如何修改现有文档。
9.5.1 加载XML
XML LINQ允许从各种输入源加载XML。包括:文件,URL,XmlReader。使用XElement的静态的Load方法可以完成XML的加载操作。如下:
XElement x = XElement.Load(@"c:"books.xml");
该方法同样支持从一个URL加载XML文档。如下:
XElement x = XElement.Load("http://msdn.microsoft.com/rss.xml");
默认情况下,当使用XDocument或XElement加载XML的时候,文档中的所有空白都将被移除。如果你想保留源文档中的那些空白,你需要使用Load方法的重载方法,该方法接受一个LoadOptions参数,LoadOptions用来指示加载XML时的选项。可用的值有None, PreserveWhitespace, SetBaseUri和SetLineInfo。下面的代码使用了LoadOptions.PreserveWhitespace选项。
string xmlUrl = "http://msdn.microsoft.com/rss.xml";
XElement x = XElement.Load(xmlUrl, LoadOptions.PreserverWhitespace);
当从一个文件或者URL加载一个XML文档的时候,XML LINQ使用XmlReader类,XmlReader类首先会读取参数指定的XML,一旦获取了XML内容,XmlReader将其解析成一个内存中XML LINQ对象树。如果XElement使用XmlReader来加载XML,需要将XmlReader定位在一个元素节点上。
在列表9.8中,我们使用XmlReader.Create加载了books.xml文件。让后找到一个NodeType为XmlNodeType.Element的节点上。之后,我们使用XNode.ReadFrom方法来创建一个XElement对象。
列表9.8 从XmlReader创建一个XElement对象
using(XmlReader reader = XmlReader.Create("books.xml"))
{
while(reader.Read())
{
if(reader.NodeType == XmlNodeType.Element)
break;
}
XElement booksXml = (XElement) XNode.ReadFrom(reader);
}
以上代码指示加载一个XML元素的内容,如果你需要处理XML声明,XML处理指令,XML文档类型定义或者XML注释,那么你需要将XML加载到XDocument对象中。加载的方式与XElement的加载方式相同,如下:
XDocument msdnDoc = XDocument.Load("http://msdn.microsoft.com/rss.xml");
上面讲述了如何从文件或者URL加载一个XML文档,接下来要讲述如何加载一个字符串中包含的XML内容。
9.5.2 解析XML
使用XElement. Parse方法可以从字符串创建一个XElement对象。该方法与Load方法有着相似的接口。所以没有什么新东西可以讲述,列表9.10显示了如何使用Parse方法加载一个包含在字符串中的XML。
列表9.10 将一个XML字符串解析为一个XElement
XElement x = XElement.Parse(
@"<books>
<book>
<author>Don Box</author>
<title>Essential .NET</title>
</book>
<book>
<author>Martin Fowler</author>
<title>Patterns of Enterprise Application Architecture</title>
</book>
</books>");
同样,Parse方法也允许你指定处理空白的选项,如下:
XElement x = XElement.Parse("<books/>", LoadOptions.PreserveWhitespace);
使用XmlReader解析XML的时候,如果遇到无效的XML内容,那么XmlReader将会抛出一个异常,XML LINQ中的Load和Parse方法并没有捕获这个异常,所以我们需要捕获这异常并进行适当的处理。如下代码所示:
try
{
XElement xml = XElement.Parse("<bad xml>");
}
catch (System.Xml.XmlException e)
{
// log the exception
}
可以看到,使用XML LINQ加载XML的方式并没有多大改变。在内部,XML LINQ还是利用了现有的XmlReader进行文档的加载和解析。
9.5.3 创建XML
前面讲到,XML LINQ使用了一种功能构建模式来创建XML,它允许我们使用一句代码创建一个XML文档,下面是我们将要创建的XML内容:
<books>
<book>
<author>Don Box</author>
<title>Essential .NET</title>
</book>
</book>
下面的代码使用XElement的一个构造函数创建了该XML文档片段,如列表9.11:
列表9.11 使用功能构建模式创建一个XElement
XElement books = new XElement("books",
new XElement("book",
new XElement("author", "Don Box"),
new XElement("title", "Essential .NET")
)
);
可以看到,代码保持了结果XML的外形,列表9.12是使用传统方式创建XML的代码。
列表9.12 使用传统方式创建XElement对象
XElement book = new XElement("book");
book.Add(new XElement("author", "Don Box"));
book.Add(new XElement("title", "Essential .NET"));
XElement books = new XElement("books");
books.Add(book);
通过比较可以得出,利用功能构建模式编写的代码具有更好的可读性。为了支持功能构建模式,XElement类提供了以下三个构造函数:
public XElement(XName name)
public XElement(XName name, object content)
public XElement(XName name, params object[] content)
content参数可以是任何类型的对象,合法的子节点内容包含:
n 一个字符串,它将被看作文本内容添加到XElement对象中。推荐使用这种方式为一个元素添加值。XML LINQ实现将会在内部创建一个XText节点
n 一个XText对象,可以包含一个string或者CData值。它将作为XElement对象的子节点添加,这种情况主要用于CData值。
n 一个XElement,作为父XElement的子节点添加
n 一个XAttribute,作为XElement对象的属性添加
n 一个XProcessingInstruction或者XComment,作为子节点添加
n 一个IEnumerable,将会对包含每个对象应用以上规则
n 其它任何对象,这时会调用该对象的ToString方法,返回结果作为子节点添加到XElement对象
n 空,被忽略
创建XElement最简单的方法是使用只采用一个XName参数的构造函数:
XElement book = new XElement("book");
为了提高XML LINQ的可用性,XName类实现了从string的隐式转换。这表示不用显式指定XName转换操作符,就可以将一个字符串,如”book”,转换到XName对象。所以我们可以直接将”book”传递给XElement的构造函数,由XML LINQ在内部实现自动转换。
创建包含文本内容的元素同样非常简单,只需要把文本内容作为XElement构造函数的第二个参数传递即可:
XElement name = new XElement("name", "Steve Eichert");
将会产生如下结果:
<name>Steve Eichert</name>
为了创建包含子节点的元素,我们可以使用XElement的第三个构造函数,该构造函数由一个声明为params的参数。Params关键字允许传递可变参数,为了创建如下XML:
<books>
<book>LINQ in Action</book>
<book>Ajax in Action</book>
</books>
可以使用如下代码:
XElement books = new XElement("books",
new XElement("book", "LINQ in Action"),
new XElement("book", "Ajax in Action")
);
列表9.13创建了一个完整的XML文档树:
列表9.13 使用XML LINQ创建一个XML树
XElement books =
new XElement("books",
new XElement("book",
new XElement("title", "LINQ in Action"),
new XElement("authors",
new XElement("author", "Fabrice Marguerie"),
new XElement("author", "Steve Eichert"),
new XElement("author", "Jim Wooley")
),
new XElement("publicationDate", "January 2008")
),
new XElement("book",
new XElement("title", "Ajax in Action"),
new XElement("authors",
new XElement("author", "Dave Crane"),
new XElement("author", "Eric Pascarello"),
new XElement("author", "Darren James")
),
new XElement("publicationDate", "October 2005")
)
);
当然,处理XML的时候会不可避免的遇到命名空间的问题。为了创建包含命名空间的元素,你可以传递完整的XML名称,也可以创建一个XNamespace对象并追加本地名称,然后传递给XElement的构造函数。列表9.14显示如何创建一个带有命名空间的XElement:
列表9.14 创建带有命名空间的XElement对象
XElement book = new XElement("{http://linqinaction.net}book");
XNamespace ns = "http://linqinaction.net";
XElement book = new XElement(ns + "book");
如果你只创建一个使用命名空间的元素,那么应该使用完整名称来指定命名空间。如果你要创建多个使用相同命名空间的元素,那么声明一次XNamespace然后多次使用,将会使你的代码看起来很整洁,如列表9.15所示:
列表9.15 使用XNamespace
XNamespace ns = "http://linqinaction.net";
XElement book =
new XElement(ns + "book",
new XElement(ns + "title", "LINQ in Action"),
new XElement(ns + "author", "Fabrice Marguerie"),
new XElement(ns + "author", "Steve Eichert"),
new XElement(ns + "author", "Jim Wooley"),
new XElement(ns + "publisher", "Manning")
);
这将会产生如下的XML:
<book xmlns="http://linqinaction.net">
<title>LINQ in Action</title>
<author>Fabrice Marguerie</author>
<author>Steve Eichert</author>
<author>Jim Wooley</author>
<publisher>Manning</publisher>
</book>
如果你希望在XML中包含一个命名空间前缀,你应该修改代码,让命名空间与一个前缀相关联。为了做到这点,需要为当前命名空间添加一个XAttribute,如列表9.16所示:
列表9.16 将命名空间与前缀相关联
XNamespace ns = "http://linqinaction.net";
XElement book = new XElement(ns + "book",
new XAttribute(XNamespace.Xmlns + "l", ns)
);
产生的XML如下:
<l:book xmlns:l="http://linqinaction.net" />
目前我们只是集中在创建XML元素上,当在真实的场景中,我们需要处理属性,处理指令,XML DTD,注释或者其它内容。使用功能构建模式处理这些内容是很容易的事情。例如,为了给book元素添加一个属性,我们可以创建一个XAttribute并作为一个参数传递给XElement的构造函数。如列表9.17所示:
列表9.17 创建带有属性的XML
XElement book = new XElement("book",
new XAttribute("publicationDate", "October 2005"),
new XElement("title", "Ajax in Action")
);
9.5.4 使用Visual Basic XML literals创建XML
(请参阅原著)
9.5.5 创建XML文档
在使用XDocument的时候,会发现它跟XElement的用法非常类似。它们之间主要的区别就是它们允许包含的内容不同。XElement可以包含XElement对象,XAttribute对象,XText对象,IEnumerable和strings。XDocument允许包含如下子节点:
n 一个DTD XDocumentType
n 一个XDeclaration对象
n 任意多个XProcessingInstruction对象
n 一个XElement对象,此节点是XML文档的根元素
n 任意多个XComment对象。注释节点可能是根元素的兄弟节点,但是不能是文档中的第一个节点,因为一个合法的XML文档不能以注释开始
在大多数使用场景中,XML文档都以功能构建模式创建,如列表9.22所示:
列表9.22 使用XDocument创建XML文档
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XProcessingInstruction("XML-stylesheet", "friendly-rss.xsl"),
new XElement("rss",
new XElement("channel", "my channel")
)
);
下面让我讨论一下构建XDocument中使用一些类。
XDeclaration
XDeclaration类表示一个XML声明。一个XML声明用来声明文档的版本和编码,还有文档是否是独立的。所以,XDeclaration有一个如下的构造函数:
public XDeclaration(string version, string encoding, string standalone)
XDeclaration可以使用现有的XDeclaration或XmlReader来创建。当使用现有的XmlReader构造XDeclaration的时候,XmlReader中的XML声明信息被XDeclaration使用。为了使用XmlReader中的XML声明信息,XmlReader中的当前节点必须定位到XML声明上,否则将会抛出一个InvalidOperationException异常。
XProcessingInstruction
XProcessingInstruction类表示一个XML处理指令。处理指令向处理XML的应用程序传达了一个信息。根XDeclaration类一样,XProcessingInstruction类也可以使用XmlReader示例来构建,或者通过如下构造函数:
public XProcessingInstruction(string target, string data)
最常见的XML处理指令是用来为XML文件指定XSLT样式表。如列表9.23所示:
列表9.23 创建为XML文档指定样式表的处理指令
XDocument d = new XDocument(
new XProcessingInstruction("XML-stylesheet",
"href='http://iqueryable.com/friendly-rss.xsl' type='text/xsl' media='screen'"),
new XElement("rss", new XAttribute("version", "2.0"),
new XElement("channel",
new XElement("item", "my item")
)
)
);
XDocumentType
XDocumentType类表示XML文档类型定义。当构建XML的时候,我们可以使用DTD来定义文档的规则,例如现有的元素类型,以及元素之间的关系。跟以上讲述的类相同,XDocumentType也可以从XmlReader构建。不过也有一个不需要XmlReader的构造函数如下:
public XDocumentType(string name, string publicId, string systemId, string internalSubset)
下面一个使用XDocumentType类的示例,创建了一个带有DTD的的HTML文档,结果HTML文档如下:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<body>This is the body!</body>
</html>
为了创建这段HTML,请使用如列表9.24所示代码:
列表9.24 使用XDocumentType创建一个带有文档类型定义的HTML文档
XDocument html = new XDocument(
new XDocumentType("HTML", "-//W3C//DTD HTML 4.01//EN",
"http://www.w3.org/TR/html4/strict.dtd", null),
new XElement("html",
new XElement("body", "This is the body!")
)
);
现在我们看到如何为XML文档添加XML文档类型定义了。最后一个要介绍的类就是表示XML注释的类。
XComment
跟在C#代码中加入注释一样,XML文档中也可以加入XML注释,用来解释XML文档中的内容。XComment对象可以通过一个字符串或者XmlReader对象构建。但是我们打算就讲到这里,因为XML就是用来提高可读性的,那么为什么还需要注释呢?到了该介绍更新和修改XML文档的时候了。
9.5.6 为XML添加内容
XML LINQ提供了操作XML的完整的方法集合。对现有的XElement对象添加一个子元素或者属性,可以使用Add方法。它提供了两个重载。如下:
public void Add(object content)
public void Add(params object[] content)
这两个重载的方法都可以使用我们讨论过的功能构建模式。添加单个子节点的可以使用如下代码:
XElement book = new XElement("book");
book.Add(new XElement("author", "Dr. Seuss"));
当然,Add方法可以接受任何类型的参数。可以传递一个XAttribute对象用来为元素添加一个属性,如下代码所示:
XElement book = new XElement("book");
book.Add(new XAttribute("publicationDate", "October 2005"));
还可以接受一个可变个数的对象列表,更改元素的内容,如列表9.25所示:
列表9.25 使用Add方法为XElement对象添加内容
XElement books = new XElement("books");
books.Add(new XElement("book",
new XAttribute("publicationDate", "May 2006"),
new XElement("author", "Chris Sells"),
new XElement("title", "Windows Forms Programming")
)
);
注意到,Add方法还可以处理实现IEnumerable接口的对象。它将会对该对象实行递归处理。这允许我们可以使用LINQ来构建XML。下面的代码显示了对Add方法传递实现IEnumerable对象的用法:
XElement existingBooks = XElement.Load("existingBooks.xml");
XElement books = new XElement("books");
books.Add(existingBooks.Elements("book"));
默认情况下,调用XElement的Add方法,都会将参数内容添加为当前元素的最后一个子节点。如果添加了一个XElement,那么此元素是最后一个子元素。如果这不是你所需要的功能,那么你可以使用AddFirst方法。如果要选择插入位置,可以使用AddAfterSelf 或者 AddBeforeSelf方法。如下所示:
XElement newBook = new XElement("book", "LINQ in Action");
XElement firstBook = books.Element("book");
firstBook.AddAfterSelf(newBook);
9.5.7 删除XML内容
XElement提供了许多删除子节点的方法。最简单的方法就是定位到要删除的节点,然后调用Remove方法。列表9.26显示了如何删除单个节点,以及删除所有book节点:
列表9.26 删除一个或多个元素
books.Element("book").Remove(); // remove the first book
books.Elements("book").Remove(); // remove all books
虽然不是那么直观,但是XElement .SetElementValue方法同样可以实现删除功能。只需要传递一个null参数:
books.SetElementValue("book", null);
如果你需要保留元素,而删除其内容,你可以使用Value属性。为了在一下XML中删除author元素的内容(“Steve Eichert”):
<books>
<book>
<author>Steve Eichert</author>
</book>
</books>
你可以导航到author元素,并将其Value属性设置为空字符串:
books.Element("book").Element("author").Value = String.Empty;
结果XML如下:
<books>
<book>
<author></author>
</book>
</books>
9.5.8 更新XML内容
使用XML LINQ更新XML内容最直接的方式是使用XElement. SetElementValue方法。它允许使用简单内容替换子节点,下面是要被更新的XML:
<books>
<book>
<title>LINQ in Action</title>
<author>Steve Eichert</author>
</book>
</books>
为了更新author元素,我们导航到第一个book元素。之后调用SetElementValue进行更新如下:
XElement books = new XElement("books.xml");
books.Element("book").SetElementValue("author", "Bill Gates");
代码执行完毕,author元素的内容已经被更新为Bill Gates
<books>
<book>
<title>LINQ in Action</title>
<author>Bill Gates</author>
</book>
</books>
需要注意的是,SetElementValue只支持简单内容的更新。如果传递更为高级的内容,SetElementValue方法将会把这些内容转换一个字符串(通过调用XContainer. GetStringValue获得)。例如,如果传递一个XElement,如下所示:
books.Element("book").SetElementValue("author", new XElement("foo"));
执行这些代码,XContainer将会抛出一个异常,因为它不接受任何从XObject继承的对象作为内容。为了处理更复杂的内容,应该使用XContainer. ReplaceNodes方法。ReplaceNodes方法提供了许多不同的重载,它支持可变参数内容。以下代码使用了ReplaceNodes方法:
books.Element("book").Element("author").ReplaceNodes(new XElement("foo"));
结果XML如下:
<books>
<book>
<title>LINQ in Action</title>
<author>
<foo/>
</author>
</book>
</books>
调用ReplaceNodes方法使得所有当前节点的子节点将被删除,然后把参数内容添加为当前节点的子节点。内容参数可以是任何有效的XElement的子元素,同样支持IEnumerable。如果传递了一个IEnumerable,那么每个元素都将被添加为子元素。ReplaceNodes同样提供了一个拥有可变参数的重载,如下:
列表9.27 使用新内容替换一个元素的内容
books.Element("book").ReplaceNodes(
new XElement("title", "Ajax in Action"),
new XElement("author", "Dave Crane")
);
SetElementValue和ReplaceNodes两个方法都是用来替换元素的内容。如果你需要替换整个节点,你可以使用XNode. ReplaceWith方法。如果我们需要将<title/>元素替换为<book_title/>,需要使用如下方式:
列表 9.28 使用ReplaceWith 替换整个节点
var titles = books.Descendants("title").ToList();
foreach(XElement title in titles)
{
title.ReplaceWith(new XElement("book_title", (string) title));
}
示例中,我们选择了所有的<title/>元素,对其进行迭代。对每个元素调用ReplaceWith方法,传递一个XName为book_title,值为当前元素值的XElement对象,结果是所有的<title/>元素都被替换为<book_title/> 元素(XElement实现了一个显示转换操作符string(),将一个XElement对象转换为string将会返回该XElement的Value属性)。
9.5.9 使用属性
在XML LINQ中,XAttribute类表示一个属性。它与元素和节点并不在一个继承层次里。在XML LINQ中,属性是一个简单的名值对。所以XAttribute有如下构造函数:
public XAttribute(XName name, object value)
在创建XML的过程中,可以使用功能构建模式使用XAttribute。为了创建包含出版日期属性的book元素,可以使用如下代码:
new XElement("book", new XAttribute("pubDate", "July 31, 2006"));
还可以通过调用Add方法添加XAttribute对象:
book.Add(new XAttribute("pubDate", "July 31, 2006"));
两种方法返回同样的结果:
<book pubDate="July 31, 2006"/>
除了使用Add方法,我们还可以通过使用SetAttributeValue方法添加属性,SetAttributeValue方法跟SetElementValue方法类似。SetAttributeValue方法可以用来在现有的XElement对象上添加或者更新属性,如果属性已经存在,那么此属性将会被更新。否则将会作为新属性添加。可以使用SetAttributeValue方法更新pubDate属性:
book.SetAttributeValue("pubDate", "October 1, 2006");
同样,还可以使用SetAttributeValue方法删除一个属性,只需要传递一个null值参数。不过XAttribute已经提供了一个Remove方法用来删除属性:
book.Attribute("pubDate").Remove();
你可以在XAttribute或者IEnumerable<XAttribute>上调用Remove方法。结果将会是移除所有与元素相关的属性。
9.5.10 保存 XML
XML LINQ保存XML的方式相当直接。XElement和XDocument类都提供了一个Save方法来保存XML到一个文件,XmlTextWriter或者XmlWriter。如列表9.29所示:
列表9.29 使用XElement.Save 方法
XElement books =
new XElement("books",
new XElement("book",
new XElement("title", "LINQ in Action"),
new XElement("author", "Steve Eichert"),
new XElement("author", "Jim Wooley"),
new XElement("author", "Fabrice Marguerie")
)
);
books.Save(@"c:"books.XML");
此外,你可以通过传递SaveOptions.DisableFormatting参数给Save方法来禁用格式化功能。现在我们已经全面的介绍了XML LINQ API,但并没有覆盖每个细节,学习一种新技术的最好的方法就是在你的应用程序中使用它。
9.6 摘要
XML LINQ建立在LINQ框架之上,允许我们可以使用标准的查询操作符进行查询。XML LINQ提供了很多帮助方法让我们处理XML时变得简单。XML LINQ的核心类是XElement,这些类使用起来相当直观。下一章要讲述的就是XML LINQ提供的XML查询和转换功能。