2010-10-07 13 views
9

tengo una llamada razonablemente complejo de xsl: apply-templates:Cómo usar la variable XSL en xsl: apply-templates?

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

La expresión se vuelve a utilizar en otros lugares de esta manera:

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

quiero generalizar de alguna manera, por lo que puede definir una vez y reutilizarlo en otro lugar. Sin embargo, esto no parece funcionar:

<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]"/> 

¿Hay una manera mejor/diferente de hacer esto? Todo lo que quiero es reutilizar la expresión xpath en múltiples llamadas diferentes a xsl: apply-templates (algunas de las cuales seleccionan de diferentes hijos).

Esto se va a utilizar en una aplicación de cliente, por lo que no puedo utilizar ninguna extensión o cambiar a XSLT 2 por desgracia. :(

Gracias.

+0

Buena pregunta. Consulte mi respuesta para obtener una descripción de dos soluciones posibles (XSLT 1.0 y XSLT 2.0) y una sugerencia de una solución más poderosa que utiliza funciones de orden superior. –

Respuesta

5

No puede construir XPath dinámicamente en XSLT (al menos, no XSLT 1.0). Pero se puede lograr fácilmente lo que estamos tratando de hacer uso de los modos de plantilla:

<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> 
+0

+1 este es un enfoque eficiente. El único inconveniente es que el filtro está codificado y, por lo tanto, no es variable. Si no se necesita un filtro variable, yo iría con esta solución. – Tomalak

+0

Los modos de plantilla no tenían sentido para mí en absoluto hasta que comencé a tener problemas como los de OP. –

1

Me gustaría echar un vistazo a el uso de una extensión de XSLT. No creo que pueda hacerlo en XSLT "estándar".

Esta extensión puede hacer lo que quiera : http://www.exslt.org/dyn/functions/evaluate/index.html

+0

Pregunta actualizada: no podemos usar extensiones, ya que estamos confiando en que MSXML haga la transformación :( – Colen

+0

'msxsl: node-set' funcionará, entonces. –

1

con la exsl extensión: nodeset, puede crear una plantilla llamada que acepta un conjunto de nodos $ x y devuelve el filtrado nodeset de acuerdo a su predicado estática

también puede definir una función, en XSLT 2.0.

+0

Pregunta actualizada: lamentablemente estamos atascados con XSLT 1.0. ( – Colen

1

¿Qué tal:

<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> 

Se podría hacer $filter un parámetro, y pasarlo desde el exterior, por ejemplo.

Lo que no puedes hacer (como habrás notado) son las variables de uso para almacenar expresiones XPath.

+0

Pregunta estúpida: ¿por qué puedes hacer eso con parámetros, pero no variables? – Colen

+0

@Colen: la variable (o param, para el caso) '$ filter' almacena una cadena, no una expresión. Escogí' | 'como el delimiter. ;-) – Tomalak

1

Tanto XSLT 1.0 y XSLT 2.0 no son compatibles con la evaluación dinámica.

Una forma de hacer esto es usar <xsl:function> en XSLT 2.0 o <xsl:call-template> en 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> 

entonces se podría utilizar esta función:

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

Ciertamente, se podría especificar la prueba en combinar patrones específicos según lo sugerido por Robert Rossney, y esto puede ser la mejor manera.

En caso de que necesite dinámicamente definir qué función de filtrado de usar, una herramienta poderosa es la biblioteca FXSL, que implementa de orden superior-Funciones (HOF) en XSLT. HOF son funciones que aceptan otras funciones como parámetros y pueden devolver una función como resultado.

Usando este enfoque, determina dinámicamente y pasa al my:test() como parámetro una función que realiza la prueba.

2

Refactoring @ Robert Rossney y @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

Dado que los atributos son por definición miembros de elementos, puede reemplazar el 'nodo()' genérico por '*'. :-) – Tomalak