C# LINQ(LINQ to XML)
LINQ to XML
可扩展标记语言(XML)是存储和交换数据的重要方法。LINQ为该语言增加了一些特性,
使得XML用起来比XPath和XSLT等方法容易得多。如果你熟悉这些方法的话,会很高兴LINQ
to XML在许多方面简化了XML的创建、查询和操作。
- 可以使用单一语句自顶向下创建XML树。
- 可以在不使用包含树的XML文档的情况下在内存中创建并操作XML。
- 可以在不使用Text子节点的情况下创建和操作字符串节点。
- 一个最大的不同(改进)是,在搜索一个树时,不再需要遍历它。相反,只需要查
询树并让它返回结果。
尽管本书不会完整介绍XML,但是在介绍LINQ提供的一些XML操作特性之前,会先简单
介绍一下XML。
标记语言
标记语言(markuplanguage)是文档中的一组标签,它提供有关文档的信息并组织其内容。
也就是说,标记标签不是文档的数据一一它们包含关于数据的数据。有关数据的数据称为元数据。
标记语言是定义的一组标签,旨在传递有关文档内容的特定类型的元数据。例如,HTML是
众所周知的标记语言。标签中的元数据包含了web页面如何在浏览器中呈现以及如何使用超链
接在页面中导航的信息。
大多数标记语言包含一组预定义的标签,而几只包含少量预定义的标签,其他都由程序
员来定义,用来表示特定文档类型需要的任何元数据。只要数据的读者和编写者都知道标签的含
义,标签就可以包含设计者想要的任何有用信息。
XML基础
XWL文档中的数据包含在一个XML树中,XML树主要由嵌套元素组成。
元素是树的基本要素。每一个元素都有名字并且包含数据,一些元素还可以包含其他
嵌套元素。元素由开始和关闭标签进行划分。元素包含的任何数据都必须介于开始和关闭标签
之间。
- 开始标签从一个左尖括号开始,后面跟元素名,紧接着是可选的特性,最后是右尖括号。
- 关闭标签从一个左尖括号开始,后面是斜杠,然后是元素名和右尖括号。
- 没有内容的元素可以直接由单个标签表示,从左尖括号开始,后面是元素名和斜杠,最
后以右尖括号结束。
如下片段演示了一个叫作EmployeeName的元素,后面是空元素PhoneNumber。
其他需要了解的有关L的重要事项如下。
- XML 文档必须有一个根元素来包含所有其他元素。
- XML标签必须合理嵌套。
- 与HTML标签不同,XML标签是区分大小写的。
- XNL特性是名/值对,它包含了元素的其他元数据。特性的值部分必须包含在引号内,可
以是单引号也可以是双引号。 - XML文档中的空格是有效的。这与把空格作为单个空格输出的HTNIL不同。
下面的化文档是包含两个员工信息的XML示例。这个XML树非常简单,可以清晰显示
元素。需要了解的有关这个潘IL树的重要事项如下。 - 树包含了一个Emlopyees类型的根节点,它包含了两个Employee类型的子节点。
- 每一个Employee节点包含了包含员工姓名和电话的节点。
<Employees> <Employee> <Name>Bob Smith</Name> <PhoneNumber>408-555-1000</PhoneNumber> <CellPhone /> </Employee> <Employee> <Name>Sally Jones</Name> <PhoneNumber>415-555-2000</PhoneNumber> <PhoneNumber>415-555-2001</PhoneNumber> </Employee>
</Employees>
图20-12演示了这个简单XML树的层次结构。
XML类
LINQ To XML 可以以两种方式用于XML。第一种方式是作为简化的XML操作API,第二种
方式是使用本章前面看到的LTNQ查询工具。先介绍LINQ to XMLAPI。
LINQ to XML API由很多表示XML树组件的类组成。我们会使用的3个最重要的类包括
XElement、XAttribute和XDocument。当然,还有其他类,但这些是主要的。
从图20-12中可以看到,XML树是一组嵌套元素。图20-13演示了用于构造化树的类以
及它们如何被嵌套。
例如,图20-13显示了如下内容。
- XDOCument节点可
- 下面的每一个节类型,最多有一个:XDeclaration节点、XDocumentType节点以及
XElement节点。 - 任何数量的XProcessingInstruction节点。
- 下面的每一个节类型,最多有一个:XDeclaration节点、XDocumentType节点以及
- 如果在XDocument下有最高级别的XElement节点,那么它就是树中其他元素的根。
- 根元素可以包含任意数量的嵌套XElement、XComment或XProcessingInstruction节点,
并且可以在任何级别上嵌套。
除了XAttribute类,大多数用于创建XML树的类都派生自一个叫作XNode的类,在文献中
也叫作"XNodes”。图20-13中XNode类显示为白色背景,XAttribute类显示为灰色背景。
创建、保存、加载和显示XML文档
演示化API的简单性和用途的最好方式就是展示一段简单的示例代码。例如,如下代码
演示了使用化后执行一些重要任务是多么简单。
从创建一个简单的包含一个Employees节点的XML树开始,两个子节点包含两个职员的名
字。注意代码的如下方面。
-
树使用一条语句来创建,并同时在适当的位置创建所有的嵌套元素,这叫作函数式构造
(functional construction)。 -
每一个元素由对象创建表达式在适当的位置创建,使用了节点类型的构造函数。
创建树之后,代码使用XDocument的Save方法把它保存在一个叫作EmployeesFi1e.xml的文
件中。然后,再使用XDocument的Load静态方法把XML树从文件中重新读回,并把树赋值给一
个新的XDocument对象。最后,使用WriteLine把新的XDocument对象保存的树结构显示出来。
using System; //需要的命名空间
using System.Xml.Linq;class Program
{static void Main(){XDocument employees=new XDocument( //创建XML文档new XElement("Employees", //创建根元素new XElement("Name","Bob Smith"), //创建元素new XElement("Name","Sally Jones") //创建元素) );employees1.Save("EmlpoyeesFile.xml"); //保存到文件//将保存的文档加载到新变量中XDocument employees2=XDocument.Load("EmployeesFile.xml");Console.WriteLine(employees2);}
}
创建XML树
在之前的示例中,我们己经知道了能通过使用XDocument和XElement的构造函数在内存中创
建一个文档。在这里,对于两个构造函数:
- 第一个参数都是对象名;
- 第二个参数以及之后的参数包含了XML树的节点。构造函数的第二个参数是一个params
参数,也就是说可以有任意多的参数。
例如,如下代码产生了一个XML树并且使用Console.WriteLine方法来显示:
using System; //此命名空间是必须的
using System.Xml.Linq;class Program
{static void Main(){XDocument employeeDoc=new XDocument( //创建文档new XElement("Employees"), //创建根元素new XElement("Employee"), //第一个employee元素new XElement("Name","Bob Smith"),new XElement("PhoneNumber","408-555-1000"),)new XElement("Employee", //第二个employee元素new XElement("Name","Sally Jones"),new XElement("PhoneNumber","415-555-2000"),new XElement("PhoneNumber","415-555-2001"))));Console.WriteLine(employeeDoc); //显示文档}
}
使用XML树的值
当我们遍历XML树来获取或修改值时,XML的强大就会体现出来。表20-2列出了用于获
取数据的主要方法。
关于表20-2中的方法,需要了解的一些重要事项如下所示。
- Nodes Nodes方法返回IEnumerable<0bject>类型的对象,因为返回的节点可能是不同的
类型,比如XElement、XComment等。我们可以使用以类型作为参数的方法0fType(type)
来指定返回某个类型的节点。例如,如下代码只获取节点:
IEnumerable<XComment> comments = xd.Nodes().OfType<XComment>();
- Elements由于获取XElements是一个非常普遍的需求,就出现了Nodes.0fType(XElement)()
表达式的简短形式一一Elements方法- 使用无参数的E1ements方法返回所有子XE1ement0
- 使用单个name参数的E1ements方法只返回具有这个名字的子XE1ementso例如,如下
代码行返回所有具有名字PhoneNumber的子XE1ement节点。
IEnumerable<XElement> empPhones = emp.Elements("PhoneNumber");
- Element这个方法只获取当前节的第一个子与Elements方法相似,它可以
带一个参数也可以不带参数调用。如果没有参数,它获取第一个子XElement节点。如果
带一个姓名参数,它获取第一个具有该名字的子XElement。 - Descendants和Ancestors这些方法与Elements和Parent方法差不多,只不过它们不返
回直接的子元素或父元素,而是忽略嵌套级别,包括当前节点之下或者之上的所有节点。
如下代码演示了Element和Elements方法:
using System;
using System.Collectiosn.Generic;
using System.Xml.Linq;class Program
{static void Main(){XDocument employeeDoc=new XDocument(new XDocument("Employees",new XElement("Employee",new XElement("Name","Bob Smith"),new XElement("PhoneNumber","408-555-1000")),new XElement("Employee",new XElement("Name","Samlly Jones"),new XElement("PhoneNumber","415-555-2000"),new XElement("PhoneNumber","415-555-2001")))); //获取第一个名为"Employees”的子XElementXElement root=employeeDoc.Employee("Employees");IEnumerable<XElement>employees=root.Employees();foreach(XElement emp in employees){XElement empNameNode=emp.Employee("Name");//emp.Employee("Name")获取第一个名为"Name"的子XElementConsole.WriteLine(empNameNode.Value);IEnumerable<XElement>empPhones=emp.Elements("PhoneNumber");foreach(XElement phone in empPhones)Console.WriteLine($"{phone.Value}");}}
}
增加节点以及操作XML
我们可以使用Add方法为现有元素增加子元素。Add方法允许我们在一次方法调用中增加任
意多个元素,不管增加的节点类型是什么。
例如,如下的代码创建并显示了一个简单的XML树,然后使用Add方法为根元素增加单个
节点。之后,它再次使用Add方法来增加3个元素:两个XE1ements和一个XComment。注意输出
的结果:
using System;
using System.Xml.Linq;class Program
{static void Main(){XDocument xd=new XDocument( //创建XML树new XElement("root,new XElement("first")"));Console.WriteLine("Original tree");Consoel.WriteLine(xd);Console.WriteLine();//显示树XElement rt=xd.Element("root"); //获取第一个元素rt.Add(new XElement("second")); //添加子元素rt.Add(new XElement("third"),new XDocument("Important Comment"),new XElement("fourth"));Console.WriteLine("Modified tree");Console.WriteLine(xd); //显示Modeified tree}
}
Add方法把新的子节点放在既有子节点之后,但把节点放在子节点之前或者之间也是可以的,
使用AddFirst、AddBeforeSelf和AddAfterSelf方法即可。
表20-3列出了最重要的一些操作XML的方法。注意,某些方法针对父节点而其他一些方法
针对节点本身。
使用XML特性
特性提供了有关XElement节点的额外信息,它放在元素的开始标签中。
当我们以函数方法构造树时,在构造函数中包含XAttribute构造函数就可以
增加特性。XAttribute构造函数有两种形式,一种接受name和value,另一种接受现有XAttribute
的引用。
如下代码为root增加了两个特性。注意,提供给XAttribute构造函数的两个参数都是字符
串,第一个指定了特性名称,而第二个指定了值。
XDocument xd=new XDocument(new XElement("root",new XAttribute("Color","red"), //特性构造函数new XAttribute("size","large"), //特性构造函数new XElement("first"),new XElement("second"))
);Consoel.Writeline(xd);
这段代码产生了如下的输出。注意,特性放在了元素的开始标签中。
要从一个XElement节点获取特性可以使用Attribute方法,提供特性名称作为参数即可。下
面的代码创建了在一个节点中有两个特性(color和size)的XML树,然后从特性获取值并且
显示出来。
static void Main()
{XDocument xd=new XDocument( //创建XML数new XElement("root",new XAttribute("color","red"),new XAttribute("size","large"),new XElement("first")));Console.WriteLine(xd);Console.WriteLine(); //显示XML树XElement rt=xd.Element("root"); //获取元素XAttribute color=rt.Attribute("color"); //获取特性XAttribute size=rt.Attribute("size"); //获取特性Console.WriteLine($"color is {color.Value}"); //显示特性价Console.WriteLIne($"size is{size.Value}"); //显示特性值
}
要移除特性,可以选择一个特性然后使用Remove方法,或在它的父节点中使用set-
AttributeValue方法把特性值设置为null。下面是两种方法的演示:
static void Main()
{XDocument xd=new XDocument(new XElement("root",new XAttribute("color","red"),new XAttribute("size","large"),new XElement("first")));XElement rt=xd.Element("root"); //获取元素rt.Attribute("color").Remove(); //移除color特性rt.SetAttributeValue("size",null); //移除size特性Console.WriteLine(xd);
}
要从一个节点获取特性可以使用Attribute方法,提供特性名称作为参数即可。下
面的代码创建了在一个节点中有两个特性(color和size)的XML树,然后从特性获取值并且
显示出来。
static void Main()
{XDocument xd=new XDocument( //创建XML树new XElement("root",new XAttribute("color","red"),new XAttribute("size","large"),new XElement("first")));Console.WriteLine(xd);Console.WriteLine();//显示XML树XElement rt=rt.Attribute("color"); //获取特性XAttribute size=rt.Attribute("size"); //获取特性Console.WriteLine($"color is {color.Value}"); //显示特性值Console.WriteLine($"size is {size.Value}"); //显示特性值
}
要移除特性,可以选择一个特性然后使用Remove方法,或在它的父节点中使用set-
AttributeValue方法把特性值设置为null。下面是两种方法的演示:
static void Main()
{XDocument xd = new XDocument(new XElement("root",new XAttribute("color", "red"),new XAttribute("size", "large"),new XElement("first")));XElement rt = xd.Element("root"); //获取元素rt.Attribute("color").Remove(); //移除color特性rt.SetAttributeValue("size", null); //移除size特性Console.WriteLine(xd);
}
要向XML树增加一个特性或改变特性的值,可以使用setAttributeValue方法,如下代码
所示:
static void Main()
{XDocument xd=new XDocument(new XElement("root",new XAttribute("color","red"),new XAttribute("size","large"),new XElement("first")));XElement rt=xd.Element("root"); //获取元素rt.SetAttributeValue("size","mdeium"); //改变特性值rt.SetAttributeValue("width","narrow"); //添加特性Console.WriteLine(xd);Console.WriteLine();
}
其他类型的节点
前面示例中使用的其他3个类型的节点是XComment、XDeclaration以及XProcessinglnstruction,
如下描述。
XComment
XML注释由记号之间的文本组成0记号之间的文本会被XML解析器忽略。可以
使用XComment类向一个XML文档插人文本,如下面的代码行所示:
new XComment("This is a comment')
这段代码产生如下的文档行:
<!--This is a comment-->
XDeclaration
XML文档从包含XML使用的版本号、使用的字符编码类型以及文档是否依赖外部引用的一
行开始。这是有关的信息,因此它其实是有关元数据的元数据。这叫作XML声明,可以
使用XDeclaration类来插人。如下代码给出了一个XDeclaration语句的示例:
new XDeclaration("1.O","utf-8","yes")
这段代码产生如下的XML文档行:
<?xmlversion="1.0" encoding="utf-8" standalone="yes"?>
XProcessinglnstruction
XML处理指令用于提供关于文档的使用和解释方式的额外数据。处理指令最常用于关
联文档和样式表。
可以使用XProcessing lnstruction构造函数来包含处理指令。它接受两个字符串参数:目标和
数据串。如果处理指令接受多个数据参数,这些参数必须包含在XProcessingInstruction构造函
数的第二个字符串参数中,如下面的构造函数代码所示。注意,在这个示例中,第二个参数是一
个逐字字符串,在字符串中的双引号文本使用两个连续的双引号来表现。
new XProcessingInstruction("xml-stylesheet",@"href=""stories"",type=""text/csss""")
这段代码产生如下的XML文档行:
<?xml-stylesheet href="stories.css" type="text/css"?>
如下代码使用了所有的3个构造函数。
static void Main()
{XDocument xd=new XDocument(new XDeclaration("1.0","utf-8","yes"),new XComment("This is a comment"),new XProcessingInstruction("xml-stylesheet",@"href=""stories.css""type=""text/css"""),new XElement("root",new XElement("first"),new XElement("second")));
}
使用凵NQ to XML的LINQ查询
现在,我们可以把LINQ XML API和LINQ查询表达式组合在一起来产生简单而强大的
树搜索。
下面的代码创建了一个简单的化树,并显示在了屏幕上,然后把它保存在一个叫作
SimpleSample.xml的文件中。尽管代码没有什么新内容,但是我们会将这个XML树用于之后的
示例。
static void Main()
{XDocument Xd=new XDoCument(new XElement("MyElents",new XElement("first",new XAttribute("color","red"),new XAttribute("size","small")),new XElement("Sicond",new XAttribute("color","red"),new XAttribute("size","medium")),new XElement("third",new XAttribute("color","blue"),new XAttribute("size","large"))));Console.WrtiteLien(xd);xd.Save("SimpleSample.xml");
}
如下示例代码使用了简单的LTNQ查询来从XML树中选择节点的子集,然后以各种方式进
行显示。这段代码做了如下的事情。
- 它从XML树中选择那些名字有5个字符的元素。由于这些元素的名字是first、second
和third,只有first和third这两个名字符合搜索标准,因此这些节点被选中。 - 它显示了所选元素的名字。
- 它格式化并显示了所选节点,包括节点名以及特性值。注意,特性使用Attribute方法来
获取,特性的值使用value属性来获取。
static void Main()
{XDocument xd = XDocument.Load("SimpleSample.xml"); //加载文档XElement rt = xd.Element("MyElements"); //获取根元素var xyz = from e in rt.Elements() //选择名称包含where e.Name.ToString().Length == 5 //5个字符的元素select e;foreach(XElement x in xyz)Console.WriteLine(x.Name.ToString()); //显示所选的元素Console.WriteLine();foreach (XElement x in xyz)Console.WriteLine("Name:{0},color:{1},size:{2}",x.Name,x.Attribute("color").Value,x.Attribute("size").Value);
}
如下代码使用了一个简单的查询来获取XML树的所有顶层元素,并且为每一个元素创建了
一个匿名类型的对象。第一个WriteLine方法显示匿名类型的默认格式化,第二个WriteLine语
句显式格式化匿名类型对象的成员。
using System;
using System.Linq;
using System.Xml.Linq;static void Main()
{XDocument xd=XDocument.Load("SimpleSample.xml"); //加载文档XElement rt=xd.Element("MyElements"); //获取根元素var xyz=from e in rt.Elements()select new{e.Name,color=e.Attribute("color")};foreach(var x in xyz)Console.WriteLine(x);Console.WriteLine();forach(var x in xyz);Console.WriteLine("{0-6},color:{1,-7}",x.Name,x.color.Value);
}
从这些示例中可以看到,可以轻易地组合API和LNQ查询工具来产生强大的XML查
询能力。