• 通过 .NET Framework 中的 XPath 和 XSLT API 方便地操作 XML 数据


    本文假设您熟悉 Visual Basic .NET

    下载本文的代码: XPathandXSLT.exe (166KB)

    摘要

    XPath 是一种正在兴起的通用查询语言。通过 XPath,可以在基于 XML 的数据源中识别和处理一组相关的节点。XPath 提供了一个基础结构,它是 .NET Framework 中的 XML 支持的组成部分。XPath 导航模型甚至用在 XSLT 处理程序的内部。在本文中,作者考察了 XPath 导航器和 XSLT 处理程序的实现细节,并且包含了一些实际的示例,例如异步转换、排序节点集和 ASP.NET 服务器端转换。

    XML 的主要优点之一是,它使您可以用标记和属性标记文本的某些部分。您在文本内部标记数据的原因是您计划以后检索它。那么,如何完成这一工作呢?您可以使用 XPath。

    虽 然 XPath 不具有基于 XML 的语法,但它是为了以简洁的、相对简单的方式对 XML 文档的某些部分进行寻址而定义的语言。更为重要的是,XPath 定义了一种常见的语法,以便您可以从实现了 XML 文档对象模型 (DOM) 的类内部以及从 XSLT 中检索节点。

    在 Microsoft®.NET Framework 中,通过在 System.Xml.XPath 命名空间中定义的类对 XPath 查询语言提供了完整的支持。.NET Framework 对 XPath 的实现基于语言分析程序和评估引擎。XPath 查询的整体体系结构与数据库查询类似。就像 SQL 命令一样,需要准备 XPath 表达式并将它们提交给运行库引擎以进行评估。查询是针对 XML 数据源加以分析和执行的。接下来,您将取回一些表示查询的结果集的信息。XPath 表达式可以返回节点集(即,有序的节点集合)、布尔值、数字或字符串。

    在本文中,我将说明 XPath 如何与 .NET Framework 中的 XmlDocument 类和 XSLT 集成。我还将对 XPathNavigator 类(.NET Framework 使用它来遍历 XML 文档)进行深入的剖析。

    XPath 表达式

    XPath 是一种专门设计的查询语言,用于对 XML 文档的元素和文本进行寻址。XPath 表示法在本质上是声明性的。任何有效的表达式都会使用强调节点之间层次关系的表示法来声明节点模式。与文件系统路径类似,XPath 表达式从根(XPath 术语中的轴)前进至源文档中的特定节点集或值。然而,它与文件系统的相似性并不仅限于此。XPath 表达式总是在节点的上下文中求值。上下文节点由应用程序指定,并且代表查询的起点。它与当前目录的概念没有太大区别。

    XPath 查询的上下文包括但不限于上下文节点和上下文节点集。上下文节点集是查询所处理的节点的整个集合。通常,它是实际返回到应用程序的节点集的超集。 XPath 上下文还包含位置和命名空间信息、变量绑定和一个可供应用程序扩展的标准函数库。XPath 分析程序的任何实现都提供了一个用于对表达式进行求值的函数库。扩展函数被定义在特定于供应商的 XPath 实现中,但是也可以由专用的和基于 XPath 的编程 API(例如,XSL 转换和 XPointer)提供。XPath 表达式通常会返回节点集,但是布尔值、字符串、数字和其他类型也受到支持。

    最常用的 XPath 表达式类型是位置路径。位置路径是一个看起来与文件系统路径非常类似的表达式,它既可以是绝对路径,也可以是相对于上下文节点的相对路径。绝对位置路径以正斜杠开始。如图 1 中所示,完全限定位置路径由三部分组成:轴、节点测试以及一个或多个谓词。轴信息定义表达式的初始上下文节点集,而节点测试是标识该节点集中路径的一系列节点名称。谓词是一个逻辑表达式,它定义了用于筛选当前节点集的条件。

    fig01

    图 1 位置路径

    XPath 表达式可以包含任何数量的谓词。如果没有指定任何谓词,则在查询中返回该上下文节点的所有子节点。否则,使用简捷的 AND 运算符将用各种谓词设置的条件逻辑地串联在一起。请注意,谓词按照它们出现的顺序进行处理,以便下一个谓词对上一个谓词生成的节点集起作用。

    在 进行处理时,XPath 表达式在名为位置步骤的子表达式中被标记化,并且每个表达式都分别被计算。XPath 处理程序以迭代方式传递在上一步中生成的子表达式和上下文节点集。它返回一个可能缩小的节点集,以便用作下一个子表达式的输入参数。在该过程中,上下文节 点、位置和大小都可能变化,而变量和函数引用以及命名空间声明保持不变。每个位置步骤实际上都是一个位置路径,因此,可以根据需要以缩写形式或完全限定形 式来表示。位置步骤由正斜杠分隔。

    在 .NET Framework 中,可以通过 XmlNode 类在 XmlDocument 类中公开的方法或者通过 XPathNavigator 类使用 XPath 表达式。

    XPath 导航器

    在 .NET Framework 中,XmlDocument 类代表由 W3C 批准的标准 XML DOM(DOM 级别 2 标准)。在从 XmlDocument 中到达的每个子节点上,都实现了几个搜索方法。XmlNode 类提供了 SelectNodes 和 SelectSingleNode 方法,它们使用 XPath 表达式在文档中搜索节点。这些方法几乎与基于 COM 的 MSXML 库中具有类似名称的方法完全相同。SelectNodes 返回一系列对象,而 SelectSingleNode 只返回第一个匹配搜索条件的对象。以下是使用 SelectNodes 的方法:

    Dim doc As XmlDocument = New XmlDocument()
    doc.Load(fileName)
    Dim nodes As XmlNodeList
    nodes = doc.SelectNodes(queryString)

    SelectSingleNode 方法与 SelectNodes 的不同之处在于它返回单个 XmlNode 对象。稍后,我将详细讨论这些方法。

    对 XPath 表达式的 XmlDocument 支持具有两个目标。它使从 MSXML COM 代码到 .NET Framework 的转换变得顺畅,同时提供了一种在内存映射 XML 文档中搜索节点的内置机制。然而,XmlDocument 查询 API 是简单的高级别包装。用于处理 XPath 表达式的核心 .NET Framework API 是围绕 XPathNavigator 类生成的。导航器是一个 XPath 处理程序,它在任何公开 IXPathNavigable 接口的 XML 数据存储区之上工作。导航器通过 XPathNavigator 类中定义的接口呈现,它使用 Select 方法分析和执行表达式。与 XmlDocument 方法不同,导航器接受以纯文本形式提供的以及通过预编译对象提供的表达式。可以从 XmlDocument 类或 XPathDocument 类中以编程方式访问 XPathNavigator 对象。图 2 说明了两种访问 .NET Framework 中的 XPath 函数的方式。

    fig02

    图 2 访问 XPath 函数

    以下代码片段显示了如何根据 XML 文档创建 XPathNavigator 以及如何执行 XPath 查询:

    Dim doc As XPathDocument
    Dim nav As XPathNavigator
    Dim iterator As XPathNodeIterator
    doc = New XPathDocument(fileName)
    nav = doc.CreateNavigator()
    iterator = nav.Select(queryString)
    While iterator.MoveNext()
    ' nav points to the node subtree
    End While

    导航器返回一个 XPath 迭代器对象。迭代器只是用来在返回的节点集中移动的专用枚举器对象。稍后我将讨论迭代器。

    文档、导航器和读取器

    在 .NET Framework 出现之前,处理 XML 文件涉及到处理按照 SAX 或 XmlDocument 规范呈现的文档。

    XPath 处理程序(它是分析和执行 XPath 查询的引擎)是在 XPathNavigator 类的内部生成的。如图 2 所示,XPath 计算总是被委托给导航器,而不管高级别调用方 API 是哪个。导航器在特定的数据存储区(通常是 XPathDocument 类的实例)之上工作。只要数据存储区类实现了如下所示的 IXPathNavigable 接口,则也可以使用其他数据存储区:

    public interface IXPathNavigable {
    XPathNavigator CreateNavigator();
    }

    除了 XPathDocument 类以外,数据存储区的示例还包括 XmlDocument 和 XmlDataDocument。

    数 据存储区负责提供导航器以探索内存中的 XML 内容。XPathNavigator 实现总是特定于存储区,并且通过从 XPathNavigator 抽象类继承生成。尽管实际上您总是通过 XPathNavigator 的常见引用类型来编写导航器,但每个数据存储区类都具有它自己的导航器对象。它们是内部的未记录的类,无法以编程方式访问,并且通常以相当不同的方式实 现。图 3 列出了 .NET Framework 中定义的三个 XPath 数据存储区所使用的真实导航器类。特定于文档的导航器利用文档类的内部布局以便提供导航 API。

    XPathDocument 类为 XML 文档提供高度优化的、只读的内存中存储区。该类是专门设计以实现 XPath 数据模型的,它没有为节点提供任何标识。它只是创建一个基础的节点引用树,以便让导航器能够快速和有效地操作。XPathDocument 类的内部体系结构看起来像是节点引用链表。节点是通过一个代表 XmlNode 类的小型子集的内部类 (XPathNode) 来管理的(参见图 2)。

    与 XPathDocument 相比,XmlDocument 类提供了对基础 XML 文档的节点的读写访问。此外,还可以分别访问每个节点。

    XmlDocument 类还提供了创建导航器对象的能力,如下所示:

    Dim doc As XmlDocument = New XmlDocument()
    doc.Load(fileName)
    Dim nav As XPathNavigator = doc.CreateNavigator()

    XmlDocument 的导航器类实现了 IHasXmlNode 接口。该接口定义了一个方法,即 GetNode:

    public interface IHasXmlNode {
    XmlNode GetNode();
    }

    使用该方法,调用方可以基于 XPathNavigator 的位置访问和询问 XmlDocument 中当前选择的节点。无法为基于 XPathDocument 的导航器实现该功能,原因仅仅在于它不像 XmlDocument 类那样通过 XmlNode 类公开内部结构。这是设计使然。XPathDocument 最大限度地减小了内存足迹,并且不提供节点标识。

    因为 GetNode 方法是在 XmlDocument 上的 XPathNavigator 类上实现的,所以调用方可以通过类型转换来利用它,如以下代码片段所示:

    Dim doc As XmlDocument = New XmlDocument()
    doc.Load(fileName)
    Dim nav As XPathNavigator = doc.CreateNavigator()
    Dim i As XPathNodeIterator = nav.Select(query)
    Dim node As XmlNode = CType(i.Current, IHasXmlNode).GetNode()

    此时,调用方程序已经获得了对该节点的完全访问权限,并且可以任意读取和更新它。

    最 后,XmlDataDocument 类是 XmlDocument 的扩展,其目标是允许通过 XML 操作关系数据集。这是一个简洁的示例,它说明了 .NET Framework 导航 API 可以应用于基于 XML 的数据以及类似于 XML(数据的结构类似于 XML,但不是 XML)的数据这样一个事实。

    如果您查看 XPath 导航器的 MSDN® 文档,则将看到导航器以类似于游标的方式(向前和向后)从基于 XML 的数据存储区中读取数据,并且提供对基础数据的只读访问。此外,它保持有关当前节点的信息,并且使用多种移动方法推进内部指针。当导航器定位于给定的节点 时,它的所有属性都将反映该节点的值。当然,这类似于 XML 读取器(我在 MSDN Magazine2003 年 5 月刊的一篇文章中对它进行了讨论。那么,导航器和读取器之间有什么区别呢?

    导 航器和读取器是不同的东西。读取器类似于水龙带游标,它提供基本的只读、只进移动。导航器也是只读的,但是它们提供了一组丰富得多的移动方法(包括向前和 向后选项)。导航器还提供了多个选择方法以对搜索进行微调。读取器是更低级别的工具,可以用来读取基于 XML 或类似于 XML 的数据,以及生成内存中的数据结构。XML 读取器可以用来生成导航器所依赖的内存中数据结构。

    查询节点

    假设您需要在基 于 .NET Framework 的应用程序中实现 XPath 查询。是应当使用 XPath 导航器,还是最好坚持使用 XmlDocument 的节点接口呢?XmlNode 的 SelectNodes 方法在内部使用导航器对象来检索匹配节点列表。随后,使用导航器的 Select 方法的返回值以便初始化一个类型为 XPathNodeList(它定义在 System.Xml.XPath 命名空间中)的内部节点列表对象。正像您可能已经猜到的那样,该类从已记录的 XmlNodeList 类继承。此外,与 SelectNodes 不同的是,导航器可以完全利用已编译的表达式。

    SelectNodes 方法总是以纯文本形式接受 XPath 表达式。字符串随后被逐字地传递给导航器。只有在一种情况下,基础导航器才会收到已编译的表达式。如果您使用处理命名空间信息的 SelectNodes 方法重载,则会首先编译 XPath 表达式,然后将其传递给处理程序。重载方法的原型如下所示:

    Function SelectNodes( _
    xpathExpr As String, _
    nsm As XmlNamespaceManager) _
    As XmlNodeList

    当您在会话中频繁重用该表达式时,使用已编译表达式的优点变得非常明显,并且它们具有命名空间识别功能。XmlNamespaceManager 类允许用户指定要绑定的命名空间的前缀。

    SelectSingleNode 方法是 SelectNodes 的特例,它只返回所返回节点集中的第一个元素。遗憾的是,直到现在,SelectSingleNode 的实现也不是特别有效。如果您只需要找到第一个匹配节点,则调用 SelectSingleNode 或 SelectNodes 几乎完全相同。而且,如果您需要尽一切可能提高性能,则使用 SelectNodes 可能更好。下面的伪代码说明了 SelectSingleNode 的当前实现:

    Function SelectSingleNode(xpathExpr As String) As XmlNode 
    Dim nodes As XmlNodeList = SelectNodes(xpathExpr)
    Return nodes(0)
    End Function

    该方法在内部调用 SelectNodes,并且返回第一个匹配节点。但是,请注意,XmlNodeList 是动态生成的 — 只有在收到请求时,才会搜索下一个节点。

    一种更好的查询单个节点的方式是,向 SelectNodes 传递一个能够返回单个节点的更为精确的 XPath 表达式。其思想是避免使用如下所示的一般性通配符表达式:

    NorthwindEmployees/Employee

    您应当在 XPath 表达式上放置一个更强的筛选器,以便它返回大小正确的节点子集。要只获得第一个节点,请添加一个额外的谓词,以便在找到第一个匹配项之后停止查询:

    NorthwindEmployees/Employee[position() = 1]

    这通常是 XPath 最佳做法,并且并不特别归因于 .NET Framework 实现。可以对该方法进行提炼和概括,以便根据需要调整节点集的大小。下面的查询字符串显示了如何获得头 n 个匹配的节点:

    NorthwindEmployees/Employee[position() < n+1]

    当您需要对采用某种形式的节点标识的选定节点执行操作时,我建议您使用 XmlNode 的 SelectNodes 而不是 XPathNavigator 的实例来执行 XPath 查询。如果您需要将该节点作为 XmlNode 类的实例进行进一步的管理,则使用 SelectNodes 可以简化代码。

    用 XPathNavigator 编程

    让我们再学习一点儿有关 XPathNavigator 的编程接口的知识。通常,不管使用哪个应用程序级别的 API,对 XML 数据源执行 XPath 查询所需的步骤序列大致是相同的:

    1. 获得对支持 XPath 的文档类的引用(例如,XPathDocument 或 XmlDocument 类的实例)。

    2. 为指定的数据存储区创建一个导航器对象。

    3. 如果您计划以后重用 XPath 表达式,则还可以对其进行预编译。

    4. 调用导航器的 Select 方法以采取操作。

    导 航器对象的编程接口定义在 XPathNavigator 抽象类中。尽管您通常使用导航器对象来执行 XPath 查询,但让 XPathNavigator 类代表更为通用的组件是不值得的。导航器是一个一般性的接口,它充当类似于游标的探测器,以探测任何将其内容作为 XML 公开的数据存储区。尽管在功能上类似于 XML 读取器,但对于简单的读取操作而言,导航器却没有前者快速和有效,这是因为它是一个树导航器,并且专门用于进行检索。如果您只需要读取 XML 文档,请使用 XML 读取器;如果您需要执行查询,请使用导航器。请记住,现在导航器在完全内存映射数据源上工作。

    从功能上 说,XPathNavigator 类与只是将导航文档内容所需的所有方法组合在一起的伪类没有太大不同。较大的差异在于,XPathNavigator 是一个与文档类完全分离的独特组件。换句话说,XPathNavigator 代表某种已经映射到 XML 数据模型的数据存储区的 XML 视图。

    图 4 枚举了 XPathNavigator 类上的可用属性。像 XML 读取器和 XMLDocument 类一样,XPathNavigator 利用了名称表来更有效地存储重复字符串。该属性集看起来像是表征 XmlTextReader 类中当前节点的属性的子集。

    值得重复说明的是,XPathNavigator 的 Select 方法返回一个迭代器对象,该对象的当前元素被作为导航器(XPathNavigator 类)向后引用。要访问和处理节点信息,您只能使用该导航器的属性。图 4 中的属性是只读的,并且更为重要的是,它们没有映射到 XmlNode 类的实例。如果您需要将该节点作为 XmlNode 对象进行操作(例如,为了应用更改),以确保将 XmlDocument 用作数据存储区类,然后将迭代器的当前元素转换为 IHasXmlNode。从引用类型中,IHasXmlNode 调用 GetNode 方法,该方法返回基础节点的 XmlNode 实例。在其他所有情况下,对节点的访问权限是只读的。

    导航器对象提供了一组丰富的方法,我基于这些方法的功能将它们划分为三个主要的组:选择、移动和杂项。以下代码片段选择节点的子孙。用于获得祖先的代码几乎完全相同:

    Dim doc As XPathDocument = New XPathDocument(fileName)
    Dim nav As XPathNavigator = doc.CreateNavigator()
    nav.SelectDescendants(nodeName, nsUri, selfIncluded)

    SelectDescendants 采用该节点的本地名称并选择子孙。NsUri 变量指示子孙节点的命名空间 URI(如果存在的话)。SelfIncluded 布尔型变量是一个标志,它指示是否应该将该上下文节点包括在节点集中。

    图 5 包含了 XPathNavigator 的移动方法的列表。您可以按照命名空间限制,向任一方向跳跃 — 向前或向后(从同辈到同辈)。您可能已经注意到,有三组不同的移动方法,它们分别适用于元素、属性和命名空间节点。只有 MoveTo 和 MoveToRoot 方法可以在任何节点(不管类型如何)上调用。此外,属性和命名空间还具有用于返回其值的方法:GetAttribute 和 GetNamespace。当被选择时,导航器的 Name 属性返回命名空间前缀。Value 属性返回 URI。

    图 6 对 XPathNavigator 类上定义的其他所有方法进行了分组。其中几个方法与 XPath 表达式有关。XPath 表达式是一个代表位置路径的字符串(尽管它不仅仅是普通的命令字符串)。它具有由 XPathExpression 类封装的环绕上下文。表达式的上下文包括返回类型和命名空间信息。XPathExpression 类不是可公开创建的。要获得它的新实例,必须取得一个 XPath 字符串表达式并将其编译为 XPathExpression 对象。下面的代码片段显示了如何编译表达式并显示它的预期返回类型:

    Dim expr As XPathExpression = nav.Compile(xpathExpr)
    Console.WriteLine(expr.ReturnType.ToString())
    nav.Select(expr)

    已编译的 XPath 表达式可以由 Select、Evaluate 和 Matches 方法使用。这里的术语“编译”并不意味着 XPath 表达式成为可执行的表达式。更简单地说来,必须将编译操作视为通过收集各种信息片段产生对象的过程。表达式可以返回除节点集以外的各种类型的值。在这种情 况下,使用 Evaluate 方法计算该表达式,然后将返回的一般对象转换为特定的类型。Select 是一个更为特殊的方法,因为它假定返回类型是节点集并且将这些节点插入到迭代器中。

    对节点集进行排序

    XPathExpression 类中内置的一个有趣的扩展是能够在将节点集传回调用方之前对其进行排序。要添加排序算法,需要调用 XPathExpression 对象上的 AddSort 方法。AddSort 具有两种形式:

    Sub AddSort(expr As Object, comparer As IComparer)

    Sub AddSort(expr As Object, order As XmlSortOrder, _
    caseOrder As XmlCaseOrder, lang As String, _
    dataType As XmlDataType)

    Expr 参数表示排序关键字。它可以是表示节点名称的字符串,也可以是另外一个能够计算为节点名称的 XPathExpression 对象。在第一个重载中,comparer 参数引用实现了 IComparer 接口的类的一个实例。该接口提供了 Compare 方法,该方法实际上用于比较一对值。如果您需要指定一个自定义算法以便对节点进行排序,则请使用该重载。

    第二个重载总 是根据 dataType 参数的值执行数值或文本比较。此外,您还可以指定排序顺序(升序或降序),甚至指定大写和小写字母的排序顺序(或者,您可以通过使用值 XmlCaseOrder.None 完全忽略大写)。最后,lang 参数指定要使用哪种语言进行比较。语言名称还应当指定区域设置。例如,要指示美国英语,最好使用“us-en”而不是简单地使用“en”。

    让 我们更深入地讨论一下 IComparer 接口。为了对对象数组进行排序,.NET Framework 提供了几个预定义的比较器类,包括 Comparer 和 CaseInsensitiveComparer。比较器类相对于大小写比较对象(通常为字符串)。CaseInsensitiveComparer 完成相同的工作,但忽略大小写。要在代码中使用这两个类,请确保导入 System.Collections 命名空间。Comparer 类不具有公共构造函数,但是通过 Default 静态属性提供了单个实例。例如:

    expr.AddSort("lastname", Comparer.Default);

    如果需要,您还可以创建自己的比较器类。以下代码显示了一个相当平常的 Visual Basic® .NET 实现:

    Class MyOwnStringComparer 
    Implements IComparer
    Public Function Compare(x As Object, y As Object) _
    As Integer Implements IComparer.Compare
    Dim strX As String = CType(x, String)
    Dim strY As String = CType(y, String)
    Return String.Compare(strX, strY)
    End Sub
    End Class

    The Compare method should如果两个字符串相等,则 Compare 方法应当返回 0;如果 x 先于 y,则该方法返回一个大于 0 的值;如果 y 先于 x,则它返回一个负值。

    该类还可以定义在应用程序的体中,并且不需要单独的程序集。以下代码将一个自定义比较器与一个 XPath 表达式相关联:

    Dim comp As MyOwnStringComparer = New MyOwnStringComparer()
    expr.AddSort("lastname", comp)

    图 7 显示了该技术的一个应用。在为 XML 文档创建导航器之后,示例控制台应用程序编译要使用的表达式。假设的数据源具有以下架构:

    <MyDataSet>
    <NorthwindEmployees>
    <Employee>
    <employeeid>...</employeeid>
    <lastname>...</lastname>
    <firstname>...</firstname>
    <title>...</title>
    </Employee>
    •••
    </NorthwindEmployees>
    </MyDataSet>

    按照单个节点进行排序是容易的 — 只须将节点名传递给 AddSort 方法。按照多个字段进行排序更为复杂。其思想是指示一个由逗号分隔的节点名称列表。但是,参数字符串必须是一个能够计算为由逗号分隔的节点名称列表的 XPath 表达式。如果简单地将排序关键字指定为类似于“title, lastname”的形式,则您将获得运行库错误,因为 XPath 处理程序错误地将其当作实际的节点名称。真正需要的是 XPath 处理程序能够在运行时将其转换为所需头衔和姓氏的表达式,如下所示:

    Dim sortKey As String = "concat(concat(title, ','), lastname)"

    Concat 关键字标识 XPath 实现提供的预定义 Helper 函数之一。

    图 7 中,您还可以了解如何有效地使用迭代器。在使用 XPath迭代器时需要注意的一个要点是,代码一次遍历节点集中的所有节点。

    您可能需要深入到其中每个节点的子树中。为此,请首先克隆导航器以避免放错主要的导航器,从而损害最外层的循环:

    Dim iterator As XPathNodeIterator = nav.Select(expr)
    While iterator.MoveNext()
    Dim nav2 As XPathNavigator = iterator.Current.Clone()
    nav2.MoveToFirstChild()
    •••
    nav2.MoveToNext()
    •••
    End While

    迭代器上的 Current 属性返回节点集中的当前节点。它计算为 XPathNavigator 类的实例,并且可以使用 Clone 方法克隆。

    .NET 中的 XSL 概述

    XML 转换是用户定义的、试图用另外的(等价的)语法表达给定文档语义的算法。转换过程包含基于样式表的结构来呈现源文档。样式表是声明性的用户定义文档,它包 含用来将一个文档转换为另一个文档的规则集。XSL 是指为了表示 XML 文档的样式表而设计的元语言。XSL 文件最初被想像为 HTML 级联样式表 (CSS) 的 XML 对应物。鉴于此,XSL 被设计为可扩展的、用户可定义的工具,该工具可以用 HTML 呈现 XML 文档以便进行显示。样式表日益增长的复杂性以及 XML 架构的出现导致了 XSLT 的产生。目前,XSL 只是许多派生技术的一个总括性的术语,所有这些技术能够更好地形容和实现将 XML 文档样式化的的原始思想。XSL 所包含的各种组件是在代码中使用的实际软件实体:XSLT、XPath 和 XSL 格式化对象 (XSLFO)。

    XSLT 程序是一个一般性的转换规则集,其输出可以是任何基于文本的语言,包括 HTML、RTF 等等。正如前文提到的那样,XPath 是一种查询语言,XSLT 程序可以利用它来选择源 XML 文档的特定部分。XPath 表达式的结果随后由 XSLT 处理程序进行分析和阐述。通常,XSLT 处理程序对源文档进行顺序处理,但是如果要求访问特定的节点组,则它会将该源文档传递给 XPath。

    在 .NET Framework 中,XSLT 的核心类是 XslTransform。该类位于 System.Xml.Xsl 命名空间中,并实现了 XSLT 处理程序。可以采取两个步骤来使用该类:首先在处理程序中加载样式表,然后根据需要向任意多的源文档应用转换。XslTransform 类只支持 XSLT 1.0 规范。图 8 中的 C# 代码实现了一个命令行 XSLT 转换器。它采用三个命令行参数(XML 源、XSLT 样式表和输出文件)来设置处理程序并且将转换结果保存到输出文件中。

    XslTransform 类

    XslTransform 类提供了两个特定于其活动的方法 — Load 和 Transform。该类只有在转换操作期间才能保证以线程安全的方式进行操作。换句话说,尽管该类的实例可以由多个线程共享,但只有 Transform 方法才可以从多个线程中安全地调用。Load 方法不是线程安全的。Transform 方法读取共享状态,并且可以从多个线程中并发运行。图 9 显示了该类的内部体系结构的一部分。在加载样式表之后,XSLT 处理程序需要修改它的状态以反映所加载的文档。该操作不会在由锁定语句创建的虚拟边界中以原子方式发生。因此,并发运行的线程理论上可以访问同一个 XSLT 处理程序实例,从而破坏数据一致性。加载操作是线程敏感的,因为它改变了对象的全局状态。

    fig09

    图 9 XSL Transform 类

    在 .NET Framework 的版本 1.0 中,XslTransform 类附加了链接请求权限集。链接请求指定直接调用方运行代码必须具有的权限。调用方权限在即时编译期间进行检查:

    [PermissionSet(SecurityAction.LinkDemand, Name="FullTrust")]
    public sealed class XslTransform
    { ••• }

    XslTransform 类的权限集属性由名称表示,并且指向内置的权限集之一 — FullTrust。这对用户来说意味着什么呢?只有对所有本地资源都具有完全受信任访问权限的调用方(该检查涉及直接调用方,而不是调用方的调用方)才 可以安全地调用到 XSLT 处理程序中。例如,如果您通过网络共享调用 XSL 处理程序,则会引发安全异常。在 .NET Framework 的版本 1.1 中,该权限集已经被移除。

    在 .NET Framework XSLT 处理程序的总体行为中,可以明确地标识三个阶段:样式表文档的加载、内部状态的设置和转换。头两个阶段发生在 Load 方法的上下文中。当然,在前面的 Load 调用成功终止之前,无法调用 Transform 方法。Load 方法总是同步工作,以便当它返回时,您可以确保加载步骤已经实际完成。您将不会收到任何指示操作失败或成功的返回值。但是,每当 Load 方法发生什么错误时,都会引发一些异常。特别地,如果您指向一个丢失的样式表,则将得到 FileNotFoundException 类型的异常;如果 XSLT 脚本包含某些错误,则将得到 XsltCompileException 类型的更一般的异常。XsltCompileException 异常提供了样式表中发生错误的行位置和编号。

    可以从四个不同的源加载输入样式表:URL、XML 读取器、XPathDocument 和 XPathNavigator。不管是哪个源,Load 方法的第一个操作都是将该源表示为一个 XPathNavigator 对象。样式表必须进行编译,并且,考虑到编译器的体系结构,导航器是非常有效的对象。“编译”是这样一个过程,它简单地从原始样式表中提取一些信息,并将 其存储在方便的数据结构中以便进一步使用。这些数据结构的整个集合称为 XSLT 处理程序的状态。图 10 显示了 Load 方法的流程。

    fig10

    图 10 Load 方法流程

    样式表编译器用从源中读取的数据填充三个内部数据结构。作为已编译样式表对象引用的对象表示样式表内容的一种索引。其他两个对象是表和操作,前者包含要执行的 XPath 查询的编译版本,后者是各种模板所需的操作。

    Transform 方法至少采用两个显式参数 — 源 XML 文档和输出流,再加上几个隐式参数。当然,已编译的样式表对象是隐式输入参数之一。第二个隐式参数是 XslTransform 的 XmlResolver 属性(该属性专门用于解析外部资源)的内容。Transform 方法还可以采用第三个显式参数 — 类 XsltArgumentList 的对象。该参数包含被用作转换过程的输入的命名空间限定参数。

    XML 源文档被标准化为 XPathNavigator,并且向下传递给 XSLT 处理程序。有趣的是,Transform 方法具有两种类型的重载。其中一些重载作为 void 方法工作,并且只是写入指定的流。其他重载作为函数工作,并且明确返回一个 XML 读取器对象。正如我稍后将讨论的那样,该功能提供了一个非常有趣的机会:实现异步 XSLT 转换。在图 11 中,可以看到 Transform 方法的执行流。

    fig11

    图 11 Transform 方法

    Transform 方法还使您可以使用 XsltArgumentList 类的实例向样式表传递参数。在以这种方式向 XSLT 脚本传递参数时,您无法指定哪个模板调用将实际使用这些参数。您只是以全局方式将参数传递给 XSLT 处理程序。负责处理模板的内部模块随后将根据需要读取和导入这些参数:

    XsltArgumentList args = new XsltArgumentList();
    args.AddParam("MaxNumOfRows", "", 7);

    AddParam 方法在参数列表中创建了一个新项。该方法需要三个参数:参数名称、命名空间 URI(如果该名称由命名空间前缀限定)以及一个代表实际值的对象。不管您用来将参数值包装到参数列表中的 CLR 类型如何,该参数值都必须对应于有效的 XPath 类型:字符串、布尔型、数字、节点片段和节点集。数字对应于双精度类型,而节点片段和节点集等价于 XPathNavigators 和 XPath 节点迭代器。

    参数和扩展对象

    XsltArgumentList 不是基于集合的类。它不是派生自集合类,也没有实现任何典型的列表接口(如 IList 或 ICollection)。XsltArgumentList 类是围绕几个哈希表生成的 — 一个用于存放 XSLT 参数,一个用于收集扩展对象。扩展对象是 .NET 对象的一个活动实例,它可以作为参数传递给样式表。例如,可以用各种方式扩展 XSLT 脚本以获得嵌入式 XPath 库未提供的功能。XslTransform 类支持 <xsl:eval> 指令,该指令使您可以将 VBScript 插入到样式表中。将自定义代码嵌入到脚本中的替代方法是使用 <msxsl:script> 元素。该新指令支持托管语言并且提供对整个 .NET Framework 的访问:

    <msxsl:script 
    language = "language"
    implements-prefix = "prefix">
    •••
    </msxsl:script>

    受支持的语言包括 C#、Visual Basic 和 JScript®。Language 属性不是强制性的,并且,如果未指定该属性,则默认为 JScript。但是,Implements-prefix 属性是强制性的。它声明一个命名空间并且将用户定义的代码与它相关联。该命名空间必须在样式表中某处进行定义。此外,要使用 <msxsl:script> 指令,该样式表必须包含以下命名空间:

    xmlns:msxsl=urn:schemas-microsoft-com:xslt

    要定义简单脚本,需要在该样式表的根中声明额外的命名空间。例如:

    <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:dino="urn:dino-scripts">

    该声明是调用 <msxsl:script> 指令所需要的。该命名空间只是将某些由用户定义的脚本组合在一起。现在,需要使用前缀“dino”来限定对 <msxsl:script> 块中定义的任何函数的任何调用。在样式表的体中,调用 <msxsl:script> 块中定义的任何函数:

    <xsl:template match="lastname">
    <TD style="border:1px solid black">
    <xsl:value-of select="dino:PrepareName(., ../firstname)" />
    </TD>
    </xsl:template>

    如果您在参数的前后加上引号,则它们将被视为文字值。要确保托管函数收到节点值,请使用与用于 <xsl:value of> 指令的 select 属性的表达式相同的表达式。

    <msxsl:script> 指令并不总是最佳的解决方案。它只支持作为 XSLT 处理程序的一部分导入的一组有限的命名空间。例如,您无法使用 System.Data 命名空间。脚本适合于一次性的操作,例如,分析字符串或检索当前时间。至于更为强大的替代解决方案,请考虑扩展对象。

    扩展对象只是一个具 有一些公共方法的托管类。唯一的要求是可调用的方法接受 XPath 类型的参数或可以强制转换到该类型的参数。与嵌入式脚本(它们被自然地定义在样式表的体中)不同,扩展对象是必须以某种方式插入到样式表中的外部资源。扩 展对象参数必须按照以下代码所示进行传递:

    ExtensionObject o = new ExtensionObject();
    // *** set properties on the object if needed
    XsltArgumentList args = new XsltArgumentList();
    args.AddExtensionObject("urn:dino-objects", o);
    XslTransform xslt = new XslTransform();
    xslt.Transform(doc, args, writer);

    在 XSLT 脚本中,可以像使用嵌入式脚本那样引用扩展对象上的方法。在下面的代码片段中,DoSomething 是与带有“dino”前缀的命名空间相关联的扩展对象上的方法:

    <xsl:template match="lastname">
    <TD style="border:1px solid black">
    <xsl:value-of select="dino:DoSomething(., ../firstname)" />
    </TD>
    </xsl:template>

    扩展对象的方法被作为静态方法调用进行处理,这意味着如果您具有多个带有相同方法名称的对象,则最好使用不同的命名空间。

    使用扩展对象比使用嵌入式脚本更为可取,这至少有两个原因。首先,扩展对象提供了好得多的代码封装,更不用提实现类重用的可能性以及可以使用任何托管类型这一事实。其次,最后可以得到能够从更加无缝的代码维护中受益的更紧凑的分层样式表。

    异步 XSLT

    Transform 方法具有几个能够返回 XML 读取器的重载:

    XmlReader Transform(XPathNavigator input, XsltArgumentList args);
    XmlReader Transform(IXPathNavigable input, XsltArgumentList args);

    这些重载的签名和行为与其他重载稍有不同。首先,输入文档必须是 XPathNavigator 或 XPathDocument。更重要的是,这些方法不接受任何代表输出流的参数。实际上,转换过程的输出不是写出到流中,而是在流中创建并且通过 XML 读取器返回给用户。

    .NET Framework 中的整个 XSLT 过程通过创建一个中间数据结构(输入导航器)工作,在该数据结构中,样式表的内容被用作底层的基础。在该样式表源中发现的任何 <xsl> 标记都被替换为展开的文本或产生自嵌入式模板的任何调用序列。最终输出看起来像是已编译的程序,其中直接语句与子例程调用交替出现。这些语句被称为输出记 录,而模板则充当子例程的角色。当 Transform 方法获得可以写入的输出流时,XSLT 处理程序遍历所有记录并将文本刷新到流中。如果已经请求 XML 读取器,则处理程序会创建某个内部读取器类的实例,并将其返回给调用方。在调用方显式请求读取缓存的输出记录之前,不会执行任何转换(参见图 12)。

    fig12

    图 12 XSL 转换过程

    当 Transform 方法返回时,读取器处于其初始状态,这意味着它尚未针对读取操作进行初始化。每当您从读取器中弹出一个元素时,都将正确地展开并返回一个新的输出记录。这 样,您可以完全控制转换过程,并且可以实现许多奇特的功能。例如,您可以向用户提供反馈,基于运行库条件和用户角色丢弃节点,或者使该过程在辅助线程上异 步发生。有关异步转换的示例,请参见本文的代码下载。

    <asp:xml> 标记

    在结束本文之前,我希望考察 一个由 <asp:xml> 标记标识的特殊 ASP.NET 服务器控件。该控件是 XslTransform 类的声明性对应物。可以使用 XML 服务器控件在 Web 页中嵌入 XML 文档。在需要可供客户端使用的 XML 数据岛时,使用该控件很方便。数据岛是 HTML 页中引用或包含的 XML 数据。XML 数据可以用内联方式包含在 HTML 中,也可以存储在外部文件中。通过将该控件的能力与执行特定于浏览器的转换的样式表相结合,可以将服务器端 XML 数据转换为不受浏览器影响的 HTML。例如,以下代码嵌入指定的 XML 文件的内容,就像由该页中的样式表进行转换一样:

    <div>
    <asp:xml runat="server" id="xmldata"
    documentsource="data.xml"
    transformsource="ie5.xsl" />
    </div>

    XML 服务器控件可以通过编程方式进行配置,并且通过字符串(DocumentContent 属性)以及通过 XmlDocument 对象(Document 属性)接受源 XML。转换元素可以通过指向 XSL 文件的 URL 或者通过 XslTransform 类的实例(Transform 属性)提供。如果需要,还可以使用 TransformArgumentList 属性指示参数。该组件与 HttpBrowserCapabilities 类一起,为常见需要(数据驱动 Web 页和特定于浏览器的输出)提供了一种了不起的解决方案。

    请注意,只有格式规范的 XML 数据才能与 <asp:xml> 控件一起使用。更简单地,如果需要刷新任何文件的内容,则可以使用 Response.WriteFile 方法。

    小结

    在 本文中,我将 XPath 作为在托管应用程序中执行 XML 查询的语言加以分析,并且讨论了其实现的几个方面。在 .NET Framework 中,XPath 运行库为其他令人困惑的部分(其中第一个是 XSLT)提供了公用基础结构。我还分析了 XSLT 处理程序的关键方面,并且提供了它的几个有趣的应用,例如异步处理和 ASP.NET 控件。

    有关背景信息,请参阅:
    Applied XML Programming for Microsoft .NET by Dino Esposito (Microsoft Press, 2002)
    Essential XML Quick Reference: A Programmer's Reference to XML, XPath, XSLT, XML Schema, SOAP, and More by Aaron Skonnard and Martin Gudgin (Addison-Wesley, 2001)
    XPathNavigator over Different Stores
    MSDN Web Services Developer Center

  • 相关阅读:
    Java基础知识之设计模式--观察者模式
    Java基础知识之设计模式--工厂模式
    Java Socket应用
    Java基础知识之设计模式--单例模式
    Docker的三种网络代理配置
    Docker被禁了!只能靠它了......
    老司机带你出坑:请收下这份运维故障处理指南
    这样理解 HTTP,面试再也不用慌了~
    为什么说卷积神经网络,是深度学习算法应用最成功的领域之一?
    一文搞懂CDN加速原理
  • 原文地址:https://www.cnblogs.com/chenying99/p/1980209.html
Copyright © 2020-2023  润新知