如何在xsl:apply-templates中使用XSL变量?

9

我有一个相对复杂的xsl:apply-templates调用:

<xsl:apply-templates select="columnval[@id 
                                       and not(@id='_Name_') 
                                       and not(@id='Group') 
                                       and not(@id='_Count_')]"/>

表达式在其他地方被重复使用,例如:
<xsl:apply-templates select="someothernode[@id 
                                           and not(@id='_Name_') 
                                           and not(@id='Group') 
                                           and not(@id='_Count_')]"/>

我希望能够将其归纳为一般性内容,这样我就可以在其他地方定义并重复使用。然而,这似乎行不通:

<xsl:variable name="x">@id and not(@id='_Name_') and not(@id='Group') and not(@id='_Count_')</xsl:variable>
<xsl:apply-templates select="columnval[$x]"/>
<xsl:apply-templates select="someothernode[$x]"/>

有没有更好/不同的方法来做这件事?我只想在多个不同的xsl:apply-templates调用中重复使用xpath表达式(其中一些从不同的子元素选择)。
这将在客户端应用程序中使用,所以很遗憾我不能使用任何扩展或切换到XSLT 2。 :(
谢谢。

好问题。请看我的答案,其中描述了两种可能的解决方案(XSLT 1.0和XSLT 2.0),并提供了一个更强大的解决方案的提示,使用高阶函数。 - Dimitre Novatchev
6个回答

5

您无法在XSLT中动态构建XPath(至少在XSLT 1.0中是这样)。但是,您可以使用模板模式轻松完成您尝试做的事情:

<xsl:apply-templates select="columnval" mode="filter"/>
<xsl:apply-template select="someothernode" mode="filter"/>

...

<!-- this guarantees that elements that don't match the filter don't get output -->
<xsl:template match="*" mode="filter"/>

<xsl:template match="*[@id and not(@id='_Name_') and not(@id='Group') and not(@id='_Count_')]" mode="filter">
   <xsl:apply-templates select="." mode="filtered"/>
</xsl:template>

<xsl:template match="columnval" mode="filtered">
   <!-- this will only be applied to the columnval elements that pass the filter -->
</xsl:template>

<xsl:template match="someothernode" mode="filtered">
   <!-- this will only be applied to the someothernode elements that pass the filter -->
</xsl:template>

+1 这是一个高效的方法。唯一的缺点是筛选器是硬编码的,因此不能变量化。如果不需要变量过滤器,我会选择这个解决方案。 - Tomalak
直到我遇到像 OP 这样的问题,模板模式才开始让我感到困惑不解。 - Robert Rossney

2
重构 @Robert Rossney 和 @Tomalak
<xsl:apply-templates select="columnval" mode="filter"/> 
<xsl:apply-templates select="someothernode" mode="filter"/> 

<xsl:template match="*" mode="filter">
   <xsl:param name="pFilter" select="'_Name_|Group|_Count_'"/> 
   <xsl:apply-templates select="self::*
                                [not(contains( 
                                        concat('|',$pFilter,'|'),  
                                        concat('|',@id,'|')))
                                 and @id]"/> 
</xsl:template> 

1
由于属性本质上是元素的成员,因此您可以用“*”替换通用的“node()”。 :-) - Tomalak

1

这样怎么样:

<xsl:variable name="filter" select="_Name_|Group|_Count_" />

<xsl:apply-templates select="columnval" mode="filtered" />
<xsl:apply-templates select="someothernode" mode="filtered" />

<xsl:template match="someothernode|columnval" mode="filtered">
  <xsl:if test="not(contains(
    concat('|', $filter,'|'), 
    concat('|', @id,'|'), 
  ))">
    <!-- whatever -->
  </xsl:if>
</xsl:template>

你可以将$filter作为参数,并从外部传入。

但正如你所注意到的那样,你不能使用变量来存储XPath表达式。


愚蠢的问题 - 为什么你可以使用参数,而不能使用变量来做到这一点? - Colen
@Colen:变量(或参数)$filter存储的是一个字符串,而不是表达式。我选择了 | 作为分隔符。;-) - Tomalak

1

XSLT 1.0和XSLT 2.0都不支持动态评估。

一种方法是在XSLT 2.0中使用<xsl:function>或在XSLT 1.0中使用<xsl:call-template>

 <xsl:function name="my:test" as="xs:boolean">
  <xsl:param name="pNode" as="element()"/>

  <xsl:variable name="vid" select="$pNode/@id"/>

  <xsl:sequence select=
  "$vid and not($vid=('_Name_','Group','_Count_')"/>
 </xsl:function>

那么你可以使用这个函数

<xsl:apply-templates select="columnval[my:test(.)]"/>

当然,你可以像Robert Rossney建议的那样指定特定匹配模式进行测试,这可能是最好的方法。

如果你需要动态地定义要使用哪个过滤函数,一个强大的工具是FXSL库,它在XSLT中实现了高阶函数(HOF)。HOF是接受其他函数作为参数并且可以返回函数作为结果的函数。

使用这种方法,你可以动态地确定并将一个执行测试的函数作为参数传递给my:test()


1

更新的问题 - 我们不能使用扩展,因为我们依赖于MSXML来进行转换 :( - Colen

1

使用扩展 exsl:nodeset,您可以创建一个命名模板,该模板接受节点集 $x 并根据您的静态谓词返回过滤后的节点集。

在 XSLT 2.0 中,您还可以定义一个函数。


更新的问题 - 不幸的是,我们被困在XSLT 1.0中。 :( - Colen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接