使用 XML

XPath 2.0 入门

理解新的数据模型

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 使用 XML

敬请期待该系列的后续内容。

此内容是该系列的一部分:使用 XML

敬请期待该系列的后续内容。

对旧标准的一次重大更新

虽然还是候选推荐标准,但 XPath 2.0 即将得到正式批准。这是 1999 年以来对 XPath 推荐标准的第一次修订,市场对此抱有很大期望,事实上一些工具已经开始实现最新的草案。这些修改是根本性的,我预料到时候人们也许会把 XPath 1.0 看作是 XPath 2.0 的草案。

XPath 2.0 推荐标准是 XSLT 2.0 和 XQuery 1.0 的基础。这两种语言都以 XPath 作为核心查询引擎,并增加了一些语句来格式化查询结果(请参阅 参考资料)。

XPath 1.0 和 XPath 2.0 之间的区别包括:

  • 基于序列而非节点集的新的数据模型
  • 绑定变量的能力,以前的变量绑定在宿主语言(XSLT)中
  • 完全支持 XML Schema 数据类型
  • 很多新功能,包括正则表达式、日期/时间和字符串操作
  • 注释,虽然不是一个重要的特性,但是在调试查询时很方便:测试时可以注释掉路径的一部分

本文主要讨论新的数据模型,具体来说即序列的使用,因为对表达能力来说这是最根本的变化。

XPath 2.0 中的序列

XPath 2.0 将一切都作为序列来处理。序列 是不同类型的项组成的有序集合。项可以是 XML 文档中的节点或者原子值。原子值可以是 XML Schema 推荐标准中定义的任何类型,包括复杂类型。在 XPath 中声明一个序列,只需要把项用逗号分开,整个序列用括号括起来:

(2, 'declencheur', 5.10)

实际上,基本上所有有效的 XPath 1.0 请求在 XPath 2.0 中仍然是有效的。换句话说,XPath 2.0 保留了熟悉的 XPath 1.0 语法:路径仍然由正斜杠(/)分开的定位步组成,如:

/po:PurchaseOrder/po:ProductList/po:Name

但是,在 XPath 2.0 中定位步表示的是序列(重复一次,这些项可以是 XML 节点)中的项而不是树中的节点(XPath 1.0 数据模型)。

XPath 2.0 中的每个概念都因序列而改变了。比如,XPath 1.0 中接受节点集的函数改为处理序列。

既然 XML 文档是层次化的,XPath 1.0 模型(树结构)不是更合理吗?但它有自己的局限性,因为 XPath 不能生成树,所以不能把一次请求的结果传递给另一个请求作进一步处理。不能编写像 SQL 那样复杂的请求。

使用序列

如前所述,仍然使用 XPath 1.0 语法,但是 XPath 2.0 也引入了一些专门用于序列的新语句。首先来看一看 for 表达式,顾名思义,它用于循环遍历序列中的项。

典型的 for 表达式如清单 1 所示:

清单 1. XPath 2.0 例子
for $line in /po:PurchaseOrder/po:OrderLines/po:Line
   return $line/po:Price * $line/po:Quantity

该 XPath 将用于清单 2 所示的订单。它计算订单中每一订单行的总金额并返回下面的序列:

(29.99, 89.98, 80, 3.1)

清单 2. 订单(示例 XML 文档)
<?xml version="1.0" encoding="ISO-8859-1"?>
<po:PurchaseOrder xmlns:po="http://www.marchal.com/2006/po">
   <po:Buyer>Pineapplesoft<po:Buyer>
   <po:Seller>Bookstore<po:Seller>
    <po:OrderLines>
      <po:Line>
         <po:Code type="ISBN">0-7897-2504-5<po:Code>
         <po:Quantity>1<po:Quantity>
         <po:Description>XML by Example<po:Description>
         <po:Price>29.99<po:Price>
      </po:Line>
      <po:Line>
         <po:Code type="ISBN">0-672-32054-1</po:Code>
         <po:Quantity>2<po:Quantity>
         <po:Description>Applied XML Solutions<po:Description>
         <po:Price>44.99</po:Price>
      </po:Line>
      <po:Line>
         <po:Code type="ISBN">2-10-005763-4<po:Code>
         <po:Quantity>2<po:Quantity>
         <po:Description>Huit Solutions Concrètes avec XML et Java</po:Description>
         <po:Price>40.00<po:Price>
      <po:Line>
      <po:Line>
         <po:Quantity>1<po:Quantity>
         <po:Description>Internet Magazine<po:Description>
         <po:Price>3.10<po:Price>
      <po:Line>
   </po:OrderLines>
<po:PurchaseOrder><

清单 1 中的关键字是 for,它遍历行序列并将每个项(每一行)绑定到变量 $product

该路径使用类似 XPath 1.0 的定位步(po:PurchaseOrder/po:OrderLines/po:Line)选择该序列。

然后是表达式的返回部分。Return 动态创建一个序列。基本上就是对循环中的每个项在输出序列中增加零个、一个或多个项。

返回序列很重要,因为序列可以通过 XPath 进一步处理。比如,通过将返回的序列传递给 sum() 函数来计算订单总值非常简单。sum() 是一个 XPath 1.0 函数,扩展后可以处理序列,如清单 3 所示:

清单 3. 处理 XPath 的返回结果
fn:sum(for $line in /po:PurchaseOrder/po:OrderLines/po:Line
   return $line/po:Price * $line/po:Quantity)

原来的方式

清单 4 的算法和 清单 3 类似,不过采用 XPath 1.0 和 XSLT 1.0 实现:

清单 4. 用 XPath 1.0 计算订单总值
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:po="http://www.marchal.com/2006/po"
                xmlns:exslt="http://exslt.org/common"
                version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
   <xsl:variable name="lines">
      <xsl:for-each select="/po:PurchaseOrder/po:OrderLines/po:Line">
         <line-total><xsl:value-of select="po:Price * po:Quantity"/><line-total>
      <xsl:for-each>
   </xsl:variable>
   <xsl:value-of select="sum(exslt:node-set($lines)/line-total)"/>
<xsl:template>
</xsl:stylesheet>

清单 4 首先计算每一行的总金额然后将结果传递给 sum() 函数。但是在 XPath 1.0 中,变量必须在宿主语言(这里是 XSLT)中声明,因此在变量 lines 中创建了临时结果集。然后将变量的内容提供给第二个 XPath 表达式计算订单总值。

比较 清单 3清单 4,可以看到 XPath 2.0 的表达能力明显提高。清单 4 使用了两个 XPath 语句而不是一个,并且依赖于宿主语言(XSLT)与中间结果通信。清单 4 可读性比较差,因为将请求分在两个 XPath 语句,也限制了查询优化的可能性。

条件表达式

XPath 2.0 还引入了条件表达式(if),如清单 5 所示。语法的含义很明确:根据括号中表达式的计算结果为 true 还是 false,表达式返回 thenelse 部分。

清单 5. 条件表达式
if(/po:PurchaseOrder/po:Seller = 'Bookstore') then 'ok' else 'ko'

限定性表达式

如果不讨论限定性表达式关于序列的讨论就是不完整的。简而言之,限定性表达式 就是作用于整个序列的测试。限定性表达式有两种:everysome

清单 6 是一个 every 表达式。包括两部分;首先将序列绑定到变量(和循环一样),然后指定序列中的项必须满足的条件。限定性表达式和循环之间的差别在于条件表达式 返回 Boolean 值,而循环 返回序列。

具体来说,如果序列中的每一项都符合条件则 every 表达式返回 true,如果序列中至少有一项满足条件表达式则 some 表达式返回 true。

清单 6. 限定性表达式
every $line in /po:PurchaseOrder/po:OrderLines/po:Line satisfies $line/po:Code

如果对 清单 2 中的文档运行 清单 6 将返回 false,因为第四行没有 po:Code 元素。如果将关键字 every 替换为 some,表达式就会返回 true,因为至少有一行包含 po:Code 元素。

无限组合

XPath 2.0 的强大在于能够将表达式组合起来创建复杂的请求。清单 7 用不同的公式计算订单总值:只计算包含产品代码的那些行,其他行被忽略掉(大概因为这些产品不能发货)。代码很简单,因为增加一个 if 表达式就足够了,如果不满足条件,该表达式就返回 empty 序列。

清单 7. 组合表达式
fn:sum(for $line in /po:PurchaseOrder/po:OrderLines/po:Line
   return if($line/po:Code) then $line/po:Price * $line/po:Quantity else ())

总之,感谢 XPath 2.0 基于序列的新数据模型,编写复杂请求大大简化了。过去需要很多 XSLT 代码的请求现在可以单纯用 XPath 编写。


相关主题

  • 您可以参阅本文在 developerWorks 全球网站上的 英文原文
  • XSLT 是什么类型的语言?(developerWorks,2001 年 2 月,2005 年 4 月修订):看看作者 Michael Kay 关于 XSLT 的讨论 —— 这种语言因何而来,好在哪里以及为何使用。
  • Influences on the design of XQuery(developerWorks,2003 年 9 月):阅读 XQuery 先驱 Don Chamberlin 关于 XQuery 语言来历的讨论,XML 数据查询语言的需求以及背后的基本原则。
  • An early look at XQuery(developerWorks,2002 年 2月):Kevin Williams 说明如何在 XQuery 的核心使用 FLWR(“flower”)子句。
  • Comparing XSLT 2.0 and XQuery(developerWorks,2006 年 4 月):作者 Benoît Marchal 比较了 XPath 2.0 的两种宿主语言。
  • An introduction to XQuery(developerWorks,2001 年6 月,2006 年 1 月修订):Howard Katz 讨论了 XQuery,包括历史背景、文档的形成过程、规范的现状。
  • Saxon:在 Saxon 中结合 XSLT 和 XQuery 处理。
  • developerWorks 中国网站 XML 专区:通过这些文章和教程扩展您的 XML 技巧。
  • IBM XML 认证:了解如何成为一名 IBM 认证的 XML 及相关技术的开发人员。
static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=172391
ArticleTitle=使用 XML: XPath 2.0 入门
publish-date=11032006