I have a reasonably complex call to xsl:apply-templates:
<xsl:apply-templates select="columnval[@id
and not(@id='_Name_')
and not(@id='Group')
and not(@id='_Count_')]"/>
The expression is reused in other places like this:
<xsl:apply-templates select="someothernode[@id
and not(@id='_Name_')
and not(@id='Group')
and not(@id='_Count_')]"/>
I want to generalize it somehow, so I can define it once and reuse it elsewhere. However, this doesn't seem to work:
<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]"/>
Is there a better / different way of doing this? All I want is to reuse the xpath expression in multiple different calls to xsl:apply-templates (some of which select from different children).
This is going开发者_Go百科 to be used in a client application, so I can't use any extensions or switch to XSLT 2 unfortunately. :(
Thanks.
You can't construct XPath dynamically in XSLT (at least, not XSLT 1.0). But you can easily accomplish what you're trying to do using template modes:
<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>
Refactoring @Robert Rossney and @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>
I would take a look at using an extension to xslt. I don't think you can do it in "standard" xslt.
This extension can do what you want: http://www.exslt.org/dyn/functions/evaluate/index.html
With the extension exsl:nodeset, you can create a named template that accepts a nodeset $x and returns the filtered nodeset according to your static predicate.
You can also define a function, in XSLT 2.0.
How about:
<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>
You could make $filter
a param, and pass it in from the outside for example.
What you cannot do (as you've noticed) is use variables to store XPath expressions.
Both XSLT 1.0 and XSLT 2.0 do not support dynamic evaluation.
One way to do this is using <xsl:function>
in XSLT 2.0 or <xsl:call-template>
in XSLT 1.0.
<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>
then you could use this function:
<xsl:apply-templates select="columnval[my:test(.)]"/>
Certainly, you could specify the test in specific match patterns as suggested by Robert Rossney, and this might be the best way.
In case you need to dynamically define which filtering function to use, one powerful tool is the FXSL library, which implements Higher-Order-Functions (HOF) in XSLT. HOF are functions that accept other functions as parameters and can return a function as their result.
Using this approach, you dynamically determine and pass to the my:test()
as parameter a function that does the test.
精彩评论