2010-10-07 9 views
9

У меня есть достаточно сложный вызов XSL: Наносить-шаблоны:Как использовать переменную XSL в 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]"/> 

Есть ли лучший способ для этого? Все, что я хочу, - это повторно использовать выражение xpath в нескольких разных вызовах xsl: apply-templates (некоторые из которых выбираются из разных дочерних элементов).

Это будет использоваться в клиентском приложении, поэтому я не могу использовать какие-либо расширения или переключиться на XSLT 2, к сожалению. :(

Спасибо.

+0

Хороший вопрос. См. Мой ответ для описания двух возможных решений (XSLT 1.0 и XSLT 2.0) и намека на более мощное решение с использованием функций более высокого порядка. –

ответ

5

Вы не можете динамически строить XPath в XSLT (по крайней мере, не 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> 
+0

+1 это эффективный подход. Единственным недостатком является то, что фильтр жестко запрограммирован и, следовательно, не является переменным. Если переменный фильтр не нужен, я бы пошел с этим решением. – Tomalak

+0

Режимы шаблонов не имели для меня никакого смысла на всех, пока я не столкнулся с проблемами, такими как OP. –

1

Я хотел бы взглянуть на использование расширения для XSLT. Я не думаю, что вы можете сделать это в «стандартной» XSLT.

Это расширение может делать то, что вы хотите : http://www.exslt.org/dyn/functions/evaluate/index.html

+0

Обновленный вопрос - мы не можем использовать расширения, так как мы полагаемся на MSXML для преобразования :( – Colen

+0

'msxsl: node-set' будет работать. –

1

с расширением exsl: набор узлов, вы можете создать именованный шаблон, который принимает множество узлов $ х и возвращает отфильтрованный набор узлов, в соответствии с вашим статическим предиката

вы также можете определить функцию в XSLT 2.0.

+0

Обновленный вопрос - к сожалению, мы застряли с XSLT 1.0.: ( – Colen

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.

+0

Глупый вопрос: почему вы можете делать это с параметрами, но не с переменными? – Colen

+0

@Colen: переменная (или параметр, если на то пошло) '$ filter' хранит строку, а не выражение. Я выбрал' | 'как delimiter. ;-) – Tomalak

1

И XSLT 1.0 и XSLT 2.0 не поддерживают динамическую оценку.

Один из способов сделать это с помощью <xsl:function> в XSLT 2.0 или <xsl:call-template> в 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> 

, то вы могли бы использовать эту функцию:

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

Конечно, вы могли бы указать тест в конкретных моделях матча, как предложили Роберт Rossney, и это может быть лучшим способом.

В случае, если вам нужно динамически определить, какие функции фильтрации использовать, один мощный инструмент библиотека FXSL, которая реализует более высокий порядок-функцию (Хоф) в XSLT. HOF - это функции, которые принимают другие функции в качестве параметров и могут возвращать функцию в качестве результата.

Используя этот подход, вы динамически определяете и передаете my:test() в качестве параметра функцию, выполняющую тест.

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

Смежные вопросы