来源:http://www.ibm.com/developerworks/cn/xml/x-wxxm35.html?S_TACT=105AGX52&S_CMP=techccid
XPath 2.0 是两个重要推荐标准的基础,这两个标准按照 W3C 规程已进入最后阶段:XSLT 2.0 和 XQuery。这次重大修改的目的是大大提高该语言的能力和效率。本文中,Benoît Marchal 介绍使用新的数据模型可以方便地编写更复杂的请求。
对旧标准的一次重大更新
虽然还是候选推荐标准,但 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:Descrīption>XML by Example<po:Descrīption>
<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:Descrīption>Applied XML Solutions<po:Descrīption>
<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:Descrīption>Huit Solutions Concrètes avec XML et Java</po:Descrīption>
<po:Price>40.00<po:Price>
<po:Line>
<po:Line>
<po:Quantity>1<po:Quantity>
<po:Descrīption>Internet Magazine<po:Descrīption>
<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,表达式返回then或else部分。
清单 5. 条件表达式
if(/po:PurchaseOrder/po:Seller = 'Bookstore') then 'ok' else 'ko' |
限定性表达式
如果不讨论限定性表达式关于序列的讨论就是不完整的。简而言之,限定性表达式就是作用于整个序列的测试。限定性表达式有两种:every和some。
清单 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 编写。