IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

技巧: 用节点集计数

使用 XSLT 节点集的特殊特性使事情变得更加容易

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

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



参考资料



关于作者

Uche Ogbuji 的照片

Uche Ogbuji 是 Fourthought Inc. 的顾问和共同创始人, 该公司是专为企业知识管理提供 XML 解决方案的软件供应商和咨询公司。Fourthought 开发了 4Suite, 它是 XML、RDF 和知识管理应用程序的开放源码平台。Ogbuji 先生是一位出生于尼日利亚的计算机工程师和作家, 他现在在美国科罗拉多州博耳德(Boulder)生活和工作。可以通过 uche.ogbuji@fourthought.com与 Ogbuji 先生联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款