<script type="text/javascript"> google_ad_client = "pub-5033576919944123"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_type = "text_image"; //2007-10-24: csdn.blog google_ad_channel = "8548491739"; </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
使用 XSLT 节点集的特殊特性使事情变得更加容易
通过使用节点集操作的特殊特性,可以使许多常见的 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 似乎是多余的,因为它们都有更完善的内置循环原语,但在将这些原语最终定下来并且相容的实现出现之前,可能还要有一段时间。
参考资料
<script type="text/javascript"> google_ad_client = "pub-5033576919944123"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_type = "text_image"; //2007-10-24: csdn.blog google_ad_channel = "8548491739"; </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>