技巧: 用节点集计数

来源:互联网 发布:linux修改文件 编辑:程序博客网 时间:2024/06/11 18:36

<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.bloggoogle_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 似乎是多余的,因为它们都有更完善的内置循环原语,但在将这些原语最终定下来并且相容的实现出现之前,可能还要有一段时间。

参考资料

  • 请阅读 Jeni Tennison 的 XSLT 页面,它们是有关易于使用的 XSLT 技术方面的极佳参考资料。

  • 查看 XSLT 常见问题解答,可以搞清楚 XSLT 的许多不明确的方面,包括节点集的方法。

  • 想要详细了解 XSLT 和 XPath 之间的所有细微差别吗?我建议您阅读 Mike Kay 的 XSLT Programmer's Reference

  • 在 W3C 站点上获得有关 XSLT 的最新消息。

  • 可以查看 IBM WebSphere Studio Site Developer,它是构建、测试和部署 Java Server Page、servlet 和与 XML 相关的应用程序和网站的一种易于使用的集成开发环境。

  • 请查找您如何才能成为一名 IBM Certified Developer in XML and related technologies。

  • 希望我们每个星期都发给您类似于此的有用 XML 技巧吗?请在 developerworks 的 XML Tips 时事通讯上注册。

  • 请在 developerworks XML zone 上查找更多的有关 XML 参考资料。

<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.bloggoogle_ad_channel = "8548491739";</script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
原创粉丝点击