本文将带你领略一下XPath2.0的一些新特性。假定你已经对XPath1.0有了初步了解,并且经常在XSLT环境下使用它。在此并不准备对它作详尽的描述,只是点出一些最值得关注的特性。
和 中都这样说:“XPath是用于对XML文件的部分进行定址(addressing)的语言”。这是对XPath 1.0特性的恰当描述。(当然,这里没有提到你可以使用算术表达式和字符串、数字和布尔表达式,但这些特性被尽可能地减到最少。)另一方面,人们对XPath2.0的特性所寄予的期望更多。XPath2.0是一个非常强大的语言,可操作的数据类型范围更广。对XPath2.0的较好的描述是: 它是一个处理序列的表达式语言,并内在地支持XML文件的查询。查询?这不是XQuery的任务吗?
一年多以来,W3C的XSL 和XML Query 工作组就一直在一起工作,目标是在XSL2.0和XQuery1.0之间有尽可能多的共享内容,并且在技术和原则上可行,最后把这两者之间的共同部分命名为"XPath 2.0"。这就意味着XPath2.0出台的背后不但有,而且还包括许多。
XPath2.0是XQuery的一个严格的句法子集。事实上,工作草案和语言语法都是由同一个来源自动产生的(当然是用XML和XSLT)。虽然在严格意义上,一个工作草案不是另一个的子集(因为某些段落是专为XPath2.0而设的),但几乎是一样的。不管怎样,中有80%的文本是这两个草案中所共有的。 XQuery1.0的XPath2.0子集要大一些。从乐观的角度看,一旦你在学习XPath2.0的过程中通过了所有障碍,就会欣喜地发现,你几乎已经掌握了XQuery。
事实上,XQuery所独有的特性主要是由顶层的查询封装机制组成。例如函数的定义、名称空间的声明、schema的引入以及元素的构建。而在XPath2.0的另一个主要使用环境XSLT2.0中,就不需要这些机制,因为XSLT2.0一般会提供自己的版本,所以就不把它包含在共有的子集中。
回顾一下,XPath1.0所支持的表达式只有四种类型:
关于XPath2.0表达式的返回值的种类的详细资料,请参阅 文件。出于实用性,我们可以简单地说,表达式的返回值可以是简单类型、节点、节点或简单类型序列。就像我们即将看到的,实际上每个表达式都返回一个序列。
XPath2.0中对节点的定义基本上与XPath1.0中相同,不同之处在于,现在可以将某些种类(元素和属性)与XML Schema类型联系起来并作为这些类型进行处理。与XPath1.0一样,有7种节点类型:文件节点(document nodes), 元素(elements),属性(attributes),名称空间节点(namespace nodes),处理器指令(processing instructions),注释(comments)和文本节点(text nodes)。在术语上有个小差别,在这里根节点("root nodes")现在有了个更加贴切的名称 -- 文件节点("document nodes")。
作为一种处理序列的语言,让我们来讨论XPath2.0是如何对待序列的,以及它的表现。下面是一些准则,你应当牢记在心。这些关于序列的格言是XPath2.0工作的基础。理解它们是深入理解并欣赏XPath2.0工作方式的前提。
如果你想要你的朋友对你留下深刻印象,就指着一个XPath2.0表达式(或XQuery表达式),然后不经意地说出这个表达式显然返回一个序列。为使这个把戏逼真,你不必揭穿这个谜底:事实上所有表达式的返回值都是序列。
只要你明白这样一个事实,即一切都是序列,你就会理解一个简单类型值(或节点)和一个简单类型值(或节点)序列之间没有任何区别。基于这样的原因,XPath2.0工作草案和一般谈论时经常说返回一个“数字”或“字符串”的表达式,实际上意味着“一个数值序列”或“一个字符串序列”。因为这两者之间没有区别,两种用法都可接受。只需记住一切都是序列是没错的。
没有序列之序列。如果你试图在序列中嵌套序列,当然在句法上可以接受,不过你得到的是一个“膨胀的”序列,子序列和包含它的序列仍然是依次排列的。
例如,下面这个表达式
(2, 4, (1,2,3), 6)
经过取值变换后和这个表达式是同一个序列:
(2, 4, 1, 2, 3, 6)
以及这个表达式,诸如此类:
( (((2)), (4,1,2,3,(6)) )
…
与XPath1.0的节点集不同,序列是有序的。考虑下面这个表达式:
(/foo/bar, /foo)
就像你所知道的,逗号(,
)是一个构造(连接)序列的操作符。我将 /foo
放在 /foo/bar
之后,构造的序列中 bar
元素直接在 foo
元素之前,而不管它们在源文件中出现的先后次序。后边,我们将会看到XPath2.0的序列可以取代XPath1.0的节点集,而不失功能性和兼容性。
与XPath1.0的节点集(或一般的集合)不同,序列内可以有重复。例如,我们稍微修改上面的表达式:
(/foo/bar, /foo, /foo/bar)
这个序列的组成是 bar
元素, 之后是 foo
元素,接着还是同样的 bar
元素。在XPath1.0中,我们无法构造这样的集合,因为根据定义,节点集内不能包含同样的元素一次以上。
在XPath1.0中,如果你想处理多个节点,就必须和节点集打交道。在XPath2.0中,节点集的概念更趋一般化,并且作了扩展。正如我们所看到的,序列可以包含简单类型值以及节点。我们还看到,序列和节点集的不同点在于它是有序的,并且可以有重复。人们自然会问:如何在不破坏XPath的同时处理节点集?
XPath1.0的节点集确实是无序的。然而,XSLT作为XPath的主要使用环境,节点集内的节点总是以某种次序进行处理的。处理节点集的缺省次序就是文件内的顺序(因为所有节点的顺序在文件内总是给定的)。在XPath2.0中,用以处理节点集合(即序列)的缺省顺序未必和文件中的顺序相同,但是和序列内的顺序是一致的。为了与XPath1.0兼容,路径表达式(以及其他1.0的表达式如联合(union)表达式)被定义成总是返回文件内的顺序。特别地,只要在直接表达式中使用"/",结果总是以文件内的顺序。此外,总是自动从结果中除去重复的元素。XPath2.0就是这样在一个只有序列的世界中模拟节点集的。
如果你还没跟得上,也不必担心。到目前为止,你可能还没意识到XPath1.0的节点集是无序的。这对那些说明书的作者来说好处最大,它们确信一切都是一致的和完备定义(well-defined)的。姑且相信序列实际上是有序的,而路径表达式仍然和从前一样。
除了引入许多新的数据类型和函数, XPath2.0还引入了几个基于关键字的运算符,我们来看看其中的几个。
在XPath2.0处理序列的新运算符中,最强大的大概就是 for
表达式。它可以对序列进行列举,为引用序列的每个成员返回一个新值。这一点与 xsl:for-each
很相似,不同之处在于它实际上返回的是一个序列,你然后可以按照序列对这个返回值进行操作。
我们来看下面的例子,它返回一个简单类型的序列,每项都是各订单中各个项目的总费用:
for $x in /order/item return $x/price * $x/quantity
然后我们就可以用 sum() 函数得到订单的全部费用:
sum(for $x in /order/item return $x/price * $x/quantity)
与XSLT/XPath1.0相比,像这样的情况在XPath2.0中使用序列非常容易解决。如果不使用序列,这个问题非常难办,通常要构造一个临时的“结果树片断”,然后使用 node-set()
扩展函数。
XPath2.0最强大(也是要求最多的)的结构之一就是条件表达式。这里是XPath2.0工作草案中的一个例子:
if ($widget1/unit-cost < $widget2/unit-cost) then $widget1 else $widget2XPath1.0的等于运算符(=
)是这个语言很有力的一个方面,这是因为它可以对节点集进行比较。我们来看这个例子:
/students/student/name = "Fred"
在XPath1.0中,如果任何一个学生的名字等于"Fred",这个表达式就返回true。这可以称为存在定量(existential quantification)
,因为它测试满足某种条件的成员是否存在。 XPath2.0保留了这项功能,还提供了更明确的测试方法:
some $x in /students/student/name satisfies $x = "Fred"
这个公式更加强大,因为你可以用任何想要的比较式来代替 $x = "Fred"
,而不仅仅是比较是否相等。而且,XPath1.0没有提供一种手段来判断是否每个学上的名字都是"Fred"。XPath2.0引入了这项功能来进行 全局定量(universal quantification), 所用的句法和上面的相似。
在XPath1.0中,唯一的集合运算符是联合(|
)。这使得很难确定某个节点是否在给定的节点集内。例如,为了确定节点 $x
是否包含在 /foo/bar
节点集内,我们大约必须这么写:
/foo/bar[generate-id(.)=generate-id($x)]
或者
count(/foo/bar)=count(/foo/bar | $x)
XPath2.0所引入的 intersect
运算符缓解了这种痛苦。上面的那种令人头晕的写法现在可以简单地这么写:
$x intersect /foo/bar
XPath2.0还引入了 except
运算符,马上可以用来选择某个节点集中除了某些节点之外的所有元素。在XPath1.0中如果要选择除了某个特定名称空间的名字之外的所有属性,必须这样写:
如果你曾看过XPath2.0说明,会发现我没有提到许多新关键字,包括 cast
, treat
, assert
, 和 instance of
。它们也是该语言的重要部分。但其重要性部分地取决于你在什么环境下使用XPath2.0。如果你打算在XSLT2.0环境下使用XPath,就没有必要每天使用它们。在某种情况下你确实想使用它们(例如,将一个字符串转换成日期),但不是非用不可。然而在XQuery1.0环境下
,你会很快熟悉它们的。
原因是XQuery1.0是设计为一种静态类型的语言(statically typed language)。在执行查询之前,要考虑查询表达式的返回数据类型,并据此对查询进行分析和优化。这只有在用户明确指定了表达式的返回类型之后才有可能。这样做的另一个好处是可以尽早地捕捉到错误,以便增强查询的正确性。
当然,在可用性和类型安全性之间要有个取舍。为同时满足两个团体(有时人为地把使用者分为面向文档的(document-oriented)和面向数据的(data-oriented)两大类)的需要,XPath2.0提供了一种手段,让使用环境可以决定在这种取舍之间站在何处。XPath2.0可以由其使用环境设置参数。这听起来好像是解决可移植性的药方。重要的是要清楚这些进展背后的指导原则。这个原则就是,任何XPath2.0表达式如果它不首先返回错误的话,则它的返回值应和其他环境下的返回值相同。这样,如果一个表达式在一种环境下会产生错误,而在另一种环境下不会,则它不会产生两个不同的表达式结果。换句话说,你得到的要么是正确答案,要么是错误信息。正确答案永远不可能多于一个。
对XSLT的用户而言,大多数情况下它们不必过分担忧。一个XPath2.0的表达式在XQuery的环境下可能会抛出一个“异常”,而在XSLT环境下同样的表达式只是悄悄地调用一个后备转换程序。
现在可以明确的是,XPath2.0是XPath1.0的一个非常重要的升级。它的发展动力既有XPath1.0用户团体的要求,也有XQuery1.0对它的要求。也许你对整个方案不能认同,却很难否认它代表了一个显著的协同。再加上一些幸运的话,它同样会成为几种用户团体的一个强大的标准工具。