技巧: 用节点集计数


<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() &lt; $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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值