级别: 初级 Uche Ogbuji (uche.ogbuji@fourthought.com), 首席顾问, Fourthought, Inc.
2002 年 5 月 01 日 通过使用节点集操作的特殊特性,可以使许多常见的 XSLT 任务(包括简单循环)变得更容易。本技巧文章讨论将节点集用于简单和有效的循环控制。
和所有编程语言一样,着手了解 XSLT 的内置数据类型和结构对掌握该语言来说是最基本的。
节点集是 XPath 的数据类型中最有趣的事物(它们形成了 XSLT 的数据类型的基础)。
在本文中,我将演示两种不是显而易见的方法,使您可以用节点集来简化 XSLT 处理。
传统 XSLT 中的计数循环
XSLT 为迭代一个节点集中的所有项提供了一个原语操作:
xsl:for-each 。
如果您认真地使用过 XSLT,那么您还可能熟悉根据给定数字(而不是根据给定的节点集)进行迭代的标准方法。
作为示例,下面的 XSLT 模板采用一个数字,并打印那么多的星号:
清单 1. 打印指定数的星号的模板
<xsl:template name="print-asterisks">
<xsl:param name="count"/>
<!-- The termination condition (infinite recursion is no fun) -->
<xsl:if test="$count">
<!-- print the asterisk for this iteration -->
<xsl:text>*</xsl:text>
<!-- recursive call to print remaining asterisks -->
<xsl:call-template name="print-asterisks">
<xsl:param name="count" select="$count-1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
|
如果您不熟悉这一技术,请立刻找一本好的 XSLT 教程或书籍,以了解这种递归模板是如何工作的。
这是 XSLT 中最基本的技术之一。即使本技巧文章只提供了一个不常见到的变通方法,
您仍然可以在使用 XSLT 时做到八、九不离十,而无需具有在睡梦中还能背诵这几段代码的本领。
该模板只用了一个参数,即要计数并打印的星号数目。当最初调用该模板时,传入打印星号的总数。在该脚本中,有关错误检查,我写得很简单。例如,如果您给
count 传入了一个负数,那么结果将是无穷递归。
当计数降为零时,在正常情况下,
if 测试不做任何事情来避免无穷递归。
然后,打印一个星号并递归调用该模板(从计数中减 1)来打印剩余星号。
性能是这种方法的最大问题。这种原始形态的递归会占据许多资源。
大多数 XSLT 处理器认为它是一种
最差的递归方式示例,
可以将它优化成一个常规的迭代。这很有用,
但如果它每次都经历模板分派机制,那么甚至这样的迭代也会变慢。
或许到目前为止某些 XSLT 处理器甚至有更复杂的优化器,可以消除这一开销,但我还不指望这类先进技术。
通常,当递归中的每一步都是一个细小操作(如打印一个星号)时,开销会是一个问题。
节点集诀窍
如果您能够设计正好是您想要的长度的节点集,那么可以将
xsl:for-each 用于这种循环。
完成这一任务的一种方法是,采用比您想要的长度长的节点集,并用正确长度选择子集。
下面的这个 XPath 表达式就是完成这一任务的,其中
count 是期望的数目,
nodeset
是您知道的比
count 长的节点集:
$nodeset[position() < $count]
|
从源节点集,谓词创建另一个正好有
count 个节点的节点集。主要问题是从哪里获取
nodeset 。只要产生的节点集足够大,那么任何获取节点集的方法对于该任务来说都可以。
然后,您可以使用 XPath
//node() 从源文档获取一批节点 ― 或者更好一些,获取所有节点。
问题是您不能总是依靠源文档的长度。样式表本身可能是一种更好的来源,因为当编写转换时,您可以确保它的大小,
如果必要,甚至可以用虚拟节点填充它。表达式
document("") 将整个样式表作为一个辅助源文档。
通过使用这些诀窍,您可以将打印星号的模板重新编写成:
清单 2. 将定制的节点集用于循环
<!-- use all nodes in the current stylesheet as a source -->
<xsl:variable name="nodeset" select="document('')//node()"/>
<xsl:template name="print-asterisks">
<xsl:param name="count"/>
<xsl:if test="$count > count($nodeset)">
<!-- Basic safety measure: better to crash and burn
than to fail in a non-obvious way -->
<xsl:message terminate="yes">
Not enough nodes for iteration
</xsl:message>
</xsl:template>
<!-- Execute the loop, using the node set we want -->
<xsl:for-each select="$nodeset[position() < $count]">
<xsl:text>*</xsl:text>
</xsl:for-each>
</xsl:template>
|
样式表中所有节点的节点集都是在顶层一次性构造的,并且对于转换中任何这种循环可以重用这些节点集。
该模板首先检查是否有足够多的节点用于迭代,
如果没有,则中止所有处理。虽然您可以选择更完美的错误处理,
但请不要省略该检查,否则可能要求一定数量的迭代,没有任何警告以较少的迭代数目告终。
这类错误就很难发现。
另一个可能的缺点是:对于某些 XSLT 实现,
document("")//node() 操作在时间和空间方面的花费会很大。
可能需要重新解析样式表,然后对每个节点进行检测。但这对于样式表执行来说,这只是一次性的惩罚。
如果您多次用到这种诀窍,在速度方面,可能还将获得可观的改进。
如果您只需要较短长度的迭代,则可以使用变体
document("")/node() ,
它可以限制对顶层的节点挖掘。按照这种思路,还有一些其它诀窍可用来适合您的目的。
例如,可以通过同时从样式表
和源文档
//node()|document("")//node() 创建一个节点集来减少出现用完节点的情况。
结束语
一些挑剔之人可能认为这种技术太过平庸,但是只要您理解了用于 XSLT 的标准迭代诀窍,
那么当您真正需要它时,就可以使用这一快捷方式。这个诀窍看来对于 XPath 和 XSLT 2.0 似乎是多余的,
因为它们都有更完善的内置循环原语,但在将这些原语最终定下来并且相容的实现出现之前,可能还要有一段时间。
参考资料
关于作者
对本文的评价
|