SLT 2.0 与 XPath 2.0 携手并肩。之所以将它们分开描述,是因为 XPath 2.0 也可用于XSLT之外的环境,比如XQuery 1.0。但是对于XSLT用户来说,它们是互相关联的。你不能在XSLT 1.0 中使用 XPath 2.0,或者在 XSLT 2.0 中使用 XPath 1.0。(至少到目前为止,W3C还没有这种组合的提议。)
注
你也许会问:XSLT 1.1 发生了什么?XSLT 1.1 已经取消了。官方的说法是在2001年8月,实际上在几个月前,XSLT工作组中止了XSLT 1.1,集中力量开发XSLT和XPath 2.0,将XSLT 1.1 的前期要求转到XSLT 2.0。
欢迎到来
许多的XSLT用户在很大程度上参与了新版XSLT的制订过程。就像许多语言的第一版一样,不经过实践的检验,这个语言的哪些扩展被证明是最重要的往往不很清楚。自从1999年11月16日XSLT 1.0 成了推荐标准以后,显然在某些方面缺少的功能应当在下一个版本中包括进来,本文我们将从下面四个方面看看XSLT 2.0:
- 从结果树片断(result tree fragments)到节点集(node-sets)的转化
- 有多个输出的文档
- 对分组的内在支持
- 用户定义函数(以XSLT实现)
RTF的末日
在XSLT 1.0 中结果树片断(Result Tree Fragment,RTF)类型很像节点集,实际上却是个二等公民。当你使用xsl:variable
构建一个临时树的时候,得到的就是RTF。问题是你不能使用XPath表达式访问这个树的内容,除非你使用一个供应商提供的扩展函数,通常是node-set()之类,来把这个RTF转化成一类节点集(含有一个根节点)。这种RTF数据类型的存在本来是为了减少实现时的限制,但由于几乎所有的XSLT处理器都提供诸如node-set()
之类的扩展函数,这种考虑显得有些不切实际。不管怎样,突破这种限制的呼声总会越来越明显,因为把复杂的变换分解成一系列简单的变换非常重要。
不知道你有没有猜出来,XSLT 2.0 为RTF打开了这个门。现在当你使用xsl:variable
创建一个临时树,这个变量的值就是个真正的节点集。事实上,用XPath 2.0 的术语来讲,它是个真正的节点序列(true node sequence),包含一个 document node (这是一个XPath 2.0 的名称,也就是XPath 1.0 的"root node")。在这个序列上你可以使用XPath表达式深入到这棵树,对它应用模板(template)等等,就像使用其它源文件一样。有了XLST 2.0 就不再需要node-set()
之类的扩展函数。
允许多个输出文档
许多XSLT 1.0 处理器所提供的另一个扩展就是多个输出文档,这种扩展被证明非常有用,对具有多个页面的网站的静态生成尤其如此。问题在于这个扩展功能不是标准的。每个处理器完成这种扩展的元素都不一样,例如 saxon:output, xt:document等等。
XSLT 2.0 使用 xsl:result-document
元素提供了多个输出文档的一个标准方法。下面的样式表例子构建了多个输出文档,一个“主要结果文件”和几个“次级结果文件”。主要结果文件以XHTML存储,次级结果文件以纯文本方式存储。
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output method="xhtml"/> <xsl:output method="text" name="textFormat"/> <xsl:template match="/"> <html> <head> <title>Links to text documents</title> </head> <body> <p>Here is a list of links to text files:</p> <ul> <xsl:apply-templates select="//textBlob"/> </ul> </body> </html> </xsl:template> <xsl:template match="textBlob"> <xsl:variable name="uri" select="concat(‘text‘, position(), ‘.txt‘)"/> <li> <a href="{$uri}"> <xsl:value-of select="$uri"/> </a> </li> <xsl:result-document href="{$uri}" format="textFormat"> <xsl:value-of select="."/> </xsl:result-document> </xsl:template> </xsl:stylesheet>
xsl:document
的 href
属性用来为相应的输出文件指定一个URI。对于许多处理器来说,这意味着以该文件名存储文件。而format
属性用来引用一个已命名的指定输出。在这里,它指向以textFormat命名的xsl:output元素。
上例中的XHTML输出方式也是在XSLT 2.0中新引入的,这里勿须赘言。
简化的分组
XSLT 1.0 没有对分组的内在支持。特定的分组问题无疑可使用各种技巧解决,例如Muenchian方法,但是这种解决方法相当复杂难懂。对XSLT 2.0 的要求之一就是必须要简化分组,就像我们在下例中所看到的,这种方法能满足要求。
出现在 Requirements document 和XSLT 2.0 工作草案的这个例子是关于把下面简单XML文档中的城市列表,
<cities> <city name="milan" country="italy" pop="5"/> <city name="paris" country="france" pop="7"/> <city name="munich" country="germany" pop="4"/> <city name="lyon" country="france" pop="2"/> <city name="venice" country="italy" pop="1"/> </cities>
转换为如下所示的以国家进行分组的HTML表格:
<table> <tr> <th>Country</th> <th>City List</th> <th>Population</th> </tr> <tr> <td>italy</td> <td>milan, venice</td> <td>6</td> </tr> <tr> <td>france</td> <td>paris, lyon</td> <td>9</td> </tr> <tr> <td>germany</td> <td>munich</td> <td>4</td> </tr> </table>
这种转换的难点在于生成最后三行(如粗体所示)。下面是XSLT1.0的一种解决方案:
<xsl:for-each select="cities/city[not(@country = preceding::*/@country)]"> <tr> <td><xsl:value-of select="@country"/></td> <td> <xsl:for-each select="../city[@country = current()/@country]"> <xsl:value-of select="@name"/> <xsl:if test="position() != last()">, </xsl:if> </xsl:for-each> </td> <td><xsl:value-of select="sum(../city[@country = current()/@country]/@pop)"/></td> </tr> </xsl:for-each>
在上例中,对每个特定的国家,我们首先要用下面的XPath表达式找出它的第一个城市:
cities/city[not(@country = preceding::*/@country)]
然后,对每个分组,为了获得每个国家的城市名字列表以及该国的总人口,我们需要再回头引用该组的所有其他成员,这两种情况我们不得不作一些额外的工作,因为只能用下面的表达式才能引用到当前的分组:
../city[@country = current()/@country]
显然这不是个理想的局面,因为这种额外的代码往往是错误之源。 使用 xsl:for-each-group
, XSLT 2.0 为你的大多数分组问题提供了答案。下面就是XSLT2.0对这个问题的解决办法(粗体表示新特性):
<xsl:for-each-group select="cities/city" group-by="@country"> <tr> <td><xsl:value-of select="@country"/></td> <td> <xsl:value-of select="current-group()/@name" separator=", "/> </td> <td><xsl:value-of select="sum(current-group()/@pop)"/></td> </tr> </xsl:for-each-group>
在上例中,xsl:for-each-group
作为XPath的取值上下文的一部分,对"current group"进行初始化,当前的分组是一个简单序列。一旦我们使用group-by
属性设置好分组,以后就可以使用current-group()
函数引用当前的分组。这就完全消除了XSLT1.0方案中的额外开销。
注意xsl:value-of
中的separator
属性。这个属性的存在是为了告诉处理器不只是要输出这个序列中的第一个元素的string值(XSLT1.0是如此作的),而是要按顺序输出该序列中所有元素的值。separator
属性的取值为一个可选的字符串,用作输出中每个字符串的分隔符。为了与XSLT1.0兼容,如果没有指定separator
属性,只输出该序列中的一个成员的值。
最后,根据xsl:for-each-group
的三个属性的取值,可以解决不同的分组问题:group-by
(如上面所示),goup-adjacent
(用于根据文当中的节点顺序的相邻关系进行分组,例如将inline的<para>元素转化为块级(block)的<para>元素),以及group-start-with
(根据序列中元素的模式(patterns)分组)。这些方法的例子可以在最新的XSLT2.0工作草案的"13.3 Examples of Grouping"中找到。
用户定义函数
XSLT2.0引入了一个新特性,就是允许定义他们自己的函数,并可以在Xpath表达式中使用。这是个非常强大的功能,无疑是非常有用的。样式函数(Stylesheet functions)是用xsl:function
元素定义的。这个元素必须指定一个name
属性。它包含0个或多个xsl:param
元素,然后是0个或多个xsl:variable
元素,后面是唯一的xsl:result
元素。这种严格的内容模型看起来是个限制,但是你会发现XSLT2.0的真正强大之处就在于可定义xsl:result
元素的select
属性。你可能会想到,XPath2.0具备有条件表达式(if...then
)和迭代(iterative)(列举?)表达式(for...return
)。
就像下面例子(直接取于最新的工作草案)所示,许多工作是在xsl:result
的select
属性中完成的。这个样式表调用了用户自定义的一个递归函数str:reverse()
来输出字符串"MAN BITES DOG"。
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://user.com/namespace" version="2.0" exclude-result-prefixes="str"> <xsl:function name="str:reverse"> <xsl:param name="sentence"/> <xsl:result select="if (contains($sentence, ‘ ‘)) then concat(str:reverse(substring-after($sentence, ‘ ‘)), ‘ ‘, substring-before($sentence, ‘ ‘)) else $sentence"/> </xsl:function> <xsl:template match="/"> <output> <xsl:value-of select="str:reverse(‘DOG BITES MAN‘)"/> </output> </xsl:template> </xsl:transform>
其他有用的东西
XSLT2.0还有其他有用的新特性,我们在这里难以祥述。包括一种机制,可定义XPath表达式的缺省名称空间(namespace),可在模式匹配判定(match pattern predicates)中使用变量,为排序说明命名(named sort specifications),以非解析文本(unparsed text)的方式读入外部文件等等。
此外,XSLT2.0说明书的一大部分仍在制订中,特别是有关构建和复制W3C XML Schema类型的内容。关于这点,最新的工作草案中说,“这项工作正在进行,有关构建元素和属性的类型信息关联,将可能会出现在XSLT2草案的未来版本中。(This is work in progress. Facilities for associating type information with constructed elements and attributes are likely to appear in future drafts of XSLT 2)”。