27

我正在处理一个 XML 文件,我想在其中计算节点数,以便在编写新节点时可以将其用作 ID。

目前我有一个名为“counter”的全局变量。我可以在模板中访问它,但我还没有找到在模板中操作它的方法。

这是我的 XSLT 文件的精简版本:

<xsl:variable name="counter" select="1" as="xs:integer"/>

<xsl:template match="/"> 
   <xsl:for-each select="section">
      <xsl:call-template name="section"></xsl:call-template>
   </xsl:for-each>
</xsl:template>

<xsl:template name="section">

   <!-- Increment 'counter' here -->

   <span class="title" id="title-{$counter}"><xsl:value-of select="title"/></span>
</xsl:template>

任何建议如何从这里开始?

4

8 回答 8

44

其他人已经解释了变量是如何不可变的——XSLT 中没有赋值语句(与一般的纯函数式编程语言一样)。

对于迄今为止提出的解决方案,我有一个替代方案。它避免了参数传递(这在 XSLT 中既冗长又丑陋——即使我承认这一点)。

在 XPath 中,您可以简单地计算<section>当前元素之前的元素数量:

<xsl:template name="section">
  <span class="title" id="title-{1 + count(preceding-sibling::section)}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

(注意:空白代码格式不会出现在您的结果中,因为只有空白的文本节点会自动从样式表中删除。所以不要觉得有必要将指令放在同一行。)

这种方法的一大优势(相对于 using position())是它只依赖于当前节点,而不依赖于当前节点列表。如果您以某种方式更改了处理方式(例如,<xsl:for-each>不仅处理了部分,还处理了其他一些元素),那么 的值position()将不再必然对应<section>于文档中元素的位置。另一方面,如果count()像上面那样使用,那么它将始终对应于每个<section>元素的位置。这种方法减少了与代码其他部分的耦合,这通常是一件好事。

count() 的替代方法是使用该<xsl:number>指令。它的默认行为将为同一级别的所有同名元素编号,这恰好是您想要的:

<xsl:template name="section">
  <xsl:variable name="count">
    <xsl:number/>
  </xsl:variable>
  <span class="title" id="title-{$count}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

这是冗长的权衡(如果您仍想使用属性值模板花括号,则需要额外的变量声明),但只是稍微如此,因为它还大大简化了您的 XPath 表达式。

还有更多的改进空间。虽然我们已经移除了对当前节点列表的依赖,但我们仍然依赖于当前节点。这本身并不是一件坏事,但是通过查看模板并不能立即清楚当前节点是什么。我们只知道模板名为“ section”;要确定正在处理什么,我们必须查看代码中的其他地方。但即使这样也不一定是这样。

如果您曾经感到被引导使用<xsl:for-each>and<xsl:call-template>一起(如您的示例中所示),请退后一步,弄清楚如何使用<xsl:apply-templates>

<xsl:template match="/doc">
  <xsl:apply-templates select="section"/>
</xsl:template>

<xsl:template match="section">
  <xsl:variable name="count">
    <xsl:number/>
  </xsl:variable>
  <span class="title" id="title-{$count}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>

这种方法不仅不那么冗长(<xsl:apply-templates/>同时替换了<xsl:for-each>and <xsl:call-template/>),而且还可以立即清楚当前节点是什么。您所要做的就是查看match属性,并且您会立即知道您正在处理一个<section>元素并且<section>您正在计算元素。

有关模板规则(即<xsl:template>具有match属性的元素)如何工作的简要说明,请参阅“XSLT 工作原理”

于 2009-05-13T08:59:37.300 回答
9

XSLT 变量不能更改。您将在模板之间传递值。

如果您使用的是 XSLT 2.0,您可以使用参数并使用隧道将变量传播到正确的模板。

您的模板将如下所示:

<xsl:template match="a">
<xsl:param name="count" select="0">
  <xsl:apply-templates>
     <xsl:with-param select="$count+1"/>
  </xsl:apply-templates>
</xsl:template>

如果要创建 id,还请查看使用 generate-id()。

于 2009-05-07T06:25:25.800 回答
6

XSLT 中的变量是不可变的,因此您必须考虑到这一点来解决问题。您可以position()直接使用:

<xsl:template match="/"> 
   <xsl:for-each select="section">
      <xsl:call-template name="section"/>
   </xsl:for-each>
</xsl:template>

<xsl:template name="section">
   <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span>
</xsl:template>

或者以更面向模板的方式:

<xsl:template match="/"> 
   <xsl:apply-templates select="section"/>
</xsl:template>

<xsl:template match="section">
   <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span>
</xsl:template>
于 2009-05-07T10:42:44.540 回答
2

变量是局部作用域的,并且在 xslt 中是只读的。

于 2009-05-07T06:20:30.037 回答
2

根据您的 XSLT 处理器,您可以将脚本函数引入 XLST。例如,Microsoft XML 库支持包含 javascript。有关示例,请参见http://msdn.microsoft.com/en-us/library/aa970889(VS.85).aspx 。如果您计划在公共客户端浏览器上部署/执行 XSLT,这种策略显然不会奏效;它必须由特定的 XSLT 处理器来完成。

于 2009-05-07T06:30:09.983 回答
1

你可以使用 position() 函数来做你想做的事。它看起来像这样。

<xsl:template match="/">
  <xsl:for-each select="section">
    <xsl:call-template name="section">
      <xsl:with-param name="counter" select="{position()}"/>
    </xsl:call-template>
  </xsl:for-each>
</xsl:template>

<xsl:template name="section">
  <xsl:param name="counter"/>
  <span class="title" id="title-{$counter}">
    <xsl:value-of select="title"/>
  </span>
</xsl:template>
于 2009-05-07T06:37:29.467 回答
0

我自己还没有尝试过,但是您可以尝试将参数传递给模板。在您的第一个模板中,您在 for-each 语句中将参数设置为 count()(或者可能是 current()?),然后将该值传递给您的“部分”模板。

这里有更多关于将参数传递给模板

于 2009-05-07T06:29:40.610 回答
0

使用<xsl:variable name="RowNum" select="count(./preceding-sibling::*)" />$RowNum作为递增值。

例如:<xsl:template name="ME-homeTiles" match="Row[@Style='ME-homeTiles']" mode="itemstyle"> <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> ...<a href="{$SafeLinkUrl}" class="tile{$RowNum}"><img ....></a>

这将为具有 tile1、tile2、tile3 等值的链接创建类...

于 2013-02-21T23:52:02.013 回答