XML 搜索和验证(XmlDocument、XPath to XmlDocument、LINQ to XDocument)
对数情况下,并不需要处理整个 XML 文档,只是从中抓取部分信息,使用的方法依据使用的类。
- XmlDocument
- 简单情况:使用 GetElementsByTagName()
- 复杂情况:使用 XPath 语言。
- XDocument
- 简单情况:内建的搜索方法(如 Elements())
- 复杂情况:LINQ 表达式
搜索 XmlDocument
使用 XmlDocument 执行查询最简单的方法是使用 XmlDocument. GetElementsByTagName(),返回一个 XmlNodeList 。
string xmlFile = Server.MapPath("DvdList.xml");
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
StringBuilder str = new StringBuilder();
XmlNodeList nodes = doc.GetElementsByTagName("Title");
foreach (XmlNode node in nodes)
{
str.Append("Found: <b>");
str.Append(node.ChildNodes[0].Value);
str.Append("</b><br />");
}
Response.Write(str);
更复杂的 XML 文档几乎总是有命名空间甚至多个命名空间。遇到这样的情况,可以使用 GetElementsByTagName()方法的重载版本:
XmlNodeList nodes = doc.GetElementsByTagName("Title","http://mycompany/OrderML");
如果要匹配给定命名空间下的所有标签,也可以使用星号(*)作为元素名称:
XmlNodeList nodes = doc.GetElementsByTagName("*",http://mycompany/OrderML);
使用 XPath 搜索 XmlDocument
GetElementsByTagName()方法功能非常有限。只允许你基于元素的名称搜索,你不能够基于其他条件过滤,比如某个元素的值或者特性的内容。
XPath 是一个更为强大的标准,使用和路径相似的符号允许你获取感兴趣的文档部分。
XPath 基本语法:
/ |
从根节点开始创建绝对路径。(/DvdList/DVD 选择根元素<DvdList>下所有子元素<DVD>) |
// |
搜索节点所有嵌套层,递归搜索子节点。(//DVD/Title 搜索DVD元素的所有子元素Title) |
@ |
选择节点的某个特性 |
* |
选择路径中的任意元素 (/DvdList/DVD/*) |
| |
组合多个路径。(/DvdList/DVD/Title | /DvdList/DVD/Director) |
. |
表示当前节点 |
.. |
表示父节点 |
[] |
定义一个选择条件,它可以测试包含节点或特性的值。 |
starts - with |
这个函数根据包含元素以什么样的文本开头获取元素 /…/DVD[starts-with(Title.'P')] |
position |
基于位置获取元素,从1开始计数 /…/DVD[position()=2] 等效于简写的 /…/DVD[2] |
count |
这个函数统计匹配名称的元素个数。count(DVD) 返回 <DVD> 元素的个数 |
注解:
要区分两个相关的术语:
- 子节点:父节点下一层的节点,只是第一层。
- 后代节点:父节点下所有的嵌套节点,即所有子节点的子节点的子节点的…
string xmlFile = Server.MapPath("DvdList.xml");
XmlDocument doc = new XmlDocument();
doc.Load(xmlFile);
StringBuilder str = new StringBuilder();
// 选择 Title 标签,查询条件是 父节点(DVD) 的特性相匹配的
XmlNodeList nodes = doc.SelectNodes("/DvdList/DVD/Title[../@Category='Science Fiction']");
foreach (XmlNode node in nodes)
{
str.Append("Found: <b>");
str.Append(node.ChildNodes[0].Value);
str.Append("</b><br />");
}
Response.Write(str);
使用 LINQ 搜索 XDocument
你已经知道了 XElement 类的 XElement()和 XElements()方法过滤出特定名称的元素,不过,这些方法只向下深入了一层。
最简单的解决办法是使用这些方法:
- ElementAfterSelf()、ElementBeforeSelf():找到兄弟元素
- Descendants():返回此文档或元素的子代元素集合
- Ancestors():返回此节点的上级元素的集合。
下面的示例找到文档里任意层级的所有电影标题:
string xmlFile = Server.MapPath("DvdList.xml");
XDocument doc = XDocument.Load(xmlFile);
foreach (XElement element in doc.Descendants("Title"))
{
// do something ...
}
把元素集合放到 LINQ 表达式后,就可以使用各种已经熟悉的操作。也就是说,你可以使用排序、过滤、分组和投影区取得希望的数据:
string xmlFile = Server.MapPath("DvdList.xml");
XDocument doc = XDocument.Load(xmlFile);
IEnumerable<XElement> matches = from DVD in doc.Descendants("DVD")
where (int)DVD.Attribute("ID") < 3
select DVD.Element("Title");
把数据转换为其他格式通常更有用。下面的查询创建一个匿名类型,它组合标题和价格,并按价格降序排序,然后绑定到网格显示:
string xmlFile = Server.MapPath("DvdList.xml");
XDocument doc = XDocument.Load(xmlFile);
var matches = from DVD in doc.Descendants("DVD")
orderby (decimal)DVD.Element("Price") descending
select new
{
Moive = (string)DVD.Element("Title"),
Price = (decimal)DVD.Element("Price")
};
GridView1.DataSource = matches;
GridView1.DataBind();
// 一定不要忘记进行类型转换这个步骤
// 这个步骤用于从完整的 XElement 对象取得值
LINQ to XML 还包含一组扩展方法,它们针对元素集合工作:
IEnumerable<string> matches =
from title in doc.Root.Elements("DVD").Elements("Title")
select (string)title;
这里要注意的是,第二次调用的 Elements("Title") 实质上是针对第一次的结果 IEnumerable<T> 调用的,它是一个扩展方法。由 System.Xml.Linq.Extensions 类进行了定义,专为处理 IEnumerable<XElement> ,在某些场合,比如你拥有以不同方式从 XML 文档不同部分构件的 IEnumerable<XElement> 集合时,这些扩展方法就比较有用。
Extensions 类还定义了另外几个应用到 XElement 集合的扩展方法:Ancestors()、AncestorsAndSelf()、Attributes()、Descendants()、DescendantsAndSelf()。
验证 XML 内容
我们已经知道了一系列用于读取和解析 XML 数据的策略。但如果用其中任何一种方法尝试读取一个无效的 XML 内容时,就会得到一个错误。换句话说,所有这些类都要求一个格式良好的 XML 。
基本架构:
XML 格式通常用一个展示其必需结构和数据类型的 XML 架构来规范它的内容。对于 DVD 列表文档,可以创建这样一个相似的 XML 架构:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="DvdList" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="DvdList">
<xs:complexType>
<xs:sequence maxOccurs="unbounded">
<xs:element name="DVD" type="DVDType" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="DVDType">
<xs:sequence>
<xs:element name="Title" type="xs:string" />
<xs:element name="Director" type="xs:string" />
<xs:element name="Price" type="xs:decimal" />
<xs:element name="Starring" type="StarringType" />
</xs:sequence>
<xs:attribute name="ID" type="xs:integer" />
<xs:attribute name="Category" type="xs:string" />
</xs:complexType>
<xs:complexType name="StarringType">
<xs:sequence maxOccurs="unbounded">
<xs:element name="Star" type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:schema>
验证 XmlDocument
要用一个架构验证 XML 文档,可以借助 XmlValidatingReader 类。
执行验证的第一步是引入命名空间 System.Xml.Schema,它包含的类型有 XmlSchema 和 XmlSchemaCollection 等:
using System.Xml.Schema;
下面的示例演示如何创建一个使用 DvdList.xsd 文件的带验证的阅读器,以及如何使用它验证 DvdList.xml 中的 XML 是有效的:
// 创建 XmlReaderSettings,它指定你想使用的架构
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
string xsdFile = Server.MapPath("DvdList.xsd");
settings.Schemas.Add("", xsdFile);
// 创建验证读取器并验证文档
string xmlFile = Server.MapPath("DvdList.xml");
FileStream fs = new FileStream(xmlFile, FileMode.Open);
XmlReader vr = XmlReader.Create(fs, settings);
while (vr.Read())
{
// Process document here.
// if an error is found,an exception will be thrown.
}
vr.Close();
做一个小小的改动:
<DVD ID="A" Category="Drama">
现在再次验证时,XmlSchemaValidationException (来自 System.Xml.Schema 命名空间)异常被抛出,它提示你有无效的数据类型。
不是捕获错误,而是可以响应 ValidationEventHandler 事件。如果你响应了这个事件,它会提供错误信息但不会抛出异常:
settings.ValidationEventHandler += ValidationEventHandler;
对应的事件处理程序:
void ValidationEventHandler(object sender, ValidationEventArgs e)
{
lblInfo.Text = "Erroe: " + e.Message;
}
使用 XDocument 进行验证
虽然 XDocument 没有嵌入验证功能,但 .NET 包含扩展方法。需要引入 System.Xml.Schema ;这个命名空间里也有一个 Extensions 类,它有一个可以在 XElement 和 XDocument 中使用的 Validate()方法:
{
string xmlFile = Server.MapPath("DvdList.xml");
string xsdFile = Server.MapPath("DvdList.xsd");
XDocument doc = XDocument.Load(xmlFile);
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", xsdFile);
doc.Validate(schemas,ValidationEventHandler);
}
void ValidationEventHandler(object sender, ValidationEventArgs e)
{
// do something ...
}