Как вызвать именованные шаблоны на основе переменной?
Я не знаю, возможно ли это, но мне интересно, как это сделать...
Скажем, у нас есть следующий XSL:
<xsl:template name="foo">
Bla bla bla
</xsl:template>
...
<xsl:template name="bar">
Bla bla bla
</xsl:template>
...
<xsl:template match="/">
<xsl:if test="$templateName='foo'">
<xsl:call-template name="foo"/>
</xsl:if>
<xsl:if test="$templateName='bar'">
<xsl:call-template name="bar"/>
</xsl:if>
</xsl:template>
Можно ли изменить XSL, чтобы читать что-то вроде...
<xsl:template match="/">
<xsl:call-template name="$templateName"/>
</xsl:template>
Ответы
Ответ 1
Нет, это невозможно невозможно. Вызывающее соглашение:
<xsl:call-template name="QName" />
Где QName определяется как:
QName ::= PrefixedName | UnprefixedName
PrefixedName ::= Prefix ':' LocalPart
UnprefixedName ::= LocalPart
Prefix ::= NCName
LocalPart ::= NCName
В основном это сводится к "символам, а не выражениям". Как подчеркивают другие ответы, на самом деле есть способы сделать что-то эквивалентное, но простой подход/наивный подход не сработает.
Ответ 2
Это невозможно точно, как вы описываете, но если вы хотите иметь возможность выбирать шаблон во время выполнения на основе некоторого значения, которое вы задали в другом месте, есть трюк. Идея состоит в том, чтобы ваш именованный шаблон также соответствовал символу node с соответствующим именем в отдельном режиме (так, чтобы он не испортил ваше нормальное преобразование), а затем сравните его. Например:
<xsl:stylesheet ... xmlns:t="urn:templates">
<!-- Any compliant XSLT processor must allow and ignore any elements
not from XSLT namespace that are immediate children of root element -->
<t:templates>
<t:foo/>
<t:bar/>
</t:templates>
<!-- document('') is the executing XSLT stylesheet -->
<xsl:variable name="templates" select="document('')//t:templates" />
<xsl:template name="foo" match="t:foo" mode="call-template">
Bla bla bla
</xsl:template>
<xsl:template name="bar" match="t:foo" mode="call-template">
Bla bla bla
</xsl:template>
<xsl:template match="/">
<xsl:variable name="template-name" select="..." />
<xsl:apply-templates select="$templates/t:*[local-name() = $template-name]"
mode="call-template"/>
</xsl:template>
Обратите внимание, что вы можете использовать <xsl:with-param>
в <xsl:apply-templates>
, поэтому вы можете сделать все с помощью этого, что вы можете сделать с простым <xsl:call-template>
.
Кроме того, приведенный выше код несколько длиннее, чем вам может понадобиться, поскольку он пытается избежать использования каких-либо расширений XSLT. Если ваш процессор поддерживает exslt:node-set()
, вы можете просто сгенерировать узлы напрямую с помощью <xsl:element>
и использовать node-set()
для преобразования полученного фрагмента дерева в обычный node для соответствия, без необходимости в document('')
hack.
Для получения дополнительной информации см. FXSL - это библиотека функционального программирования для XSLT, основанная на этой концепции.
Ответ 3
Для любой будущей ссылки:
Вот рабочий пример, основанный на ответе Павла Минаева. Никакой оригинальной мысли с моей стороны.;-) Я переключил его на использование msxml: node -set, как он описал (более или менее), чтобы он работал в .NET.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" version="1.0">
<xsl:variable name="templates">
<templates>
<foo />
<bar />
</templates>
</xsl:variable>
<xsl:template name="foo" match="foo" mode="call-template">
<FooElement />
</xsl:template>
<xsl:template name="bar" match="bar" mode="call-template">
<BarElement />
</xsl:template>
<xsl:template match="/">
<Root>
<xsl:variable name="template-name">bar</xsl:variable> <!-- Change this to foo to get the other template. -->
<xsl:apply-templates select="msxsl:node-set($templates)/*/*[local-name() = $template-name]" mode="call-template" />
</Root>
</xsl:template>
</xsl:stylesheet>
Ответ 4
Обновить. Ссылки ниже были обновлены, чтобы указать на web.archive.org - к сожалению, IDEALLIANCE сделало все материалы конференции Exteme Markup Languages недоступными...
В свое время я найду более постоянное место для этих двух статей.
Это реализовано в FXSL.
Есть хорошие объяснения основных принципов FXSL.
См. следующие две статьи:
" Функциональное программирование в XSLT с использованием библиотеки FXSL" (для XSLT 1.0), (PDF) по адресу:
http://web.archive.org/web/20070710091236/http://www.idealliance.org/papers/extreme/proceedings/xslfo-pdf/2003/Novatchev01/EML2003Novatchev01.pdf
(HTML):
http://conferences.idealliance.org/extreme/html/2003/Novatchev01/EML2003Novatchev01.html
" Функциональное программирование более высокого порядка с использованием XSLT 2.0 и FXSL" (PDF) по адресу:
http://web.archive.org/web/20070222111927/http://www.idealliance.org/papers/extreme/proceedings/xslfo-pdf/2006/Novatchev01/EML2006Novatchev01.pdf
(HTML):
http://conferences.idealliance.org/extreme/html/2006/Novatchev01/EML2006Novatchev01.html
Используя FXSL, я смог легко и элегантно решить многие проблемы, которые кажутся "невозможными для XSLT". Можно найти множество примеров здесь.
Ответ 5
Я думаю, что у меня была более или менее та же проблема, что и вы. У меня был "внешний" шаблон и я хотел вызвать другой "внутренний" шаблон в зависимости от некоторой переменной, установленной во время выполнения. Я нашел ваш вопрос по Googling для создания динамического <xsl:call-template>
. Я решил это, используя <xsl:apply-templates>
, а именно:
Входной XML (сгенерированный во время выполнения) содержит что-то по строкам:
<template name="template_name_1"/>
XSL во внешнем шаблоне имеет:
<xsl:apply-templates select="template"/>
(select="template"
в этом применении шаблоны относятся к тегу <template>
во входном XML)
И, наконец, "внутренний" шаблон, который я хотел включить в результате значения атрибута name
в моем XML, выглядит так:
<xsl:template match="template[@name='template_name_1']">
<!-- XSL / XHTML goes here -->
</xsl:template>
(Опять же, match="template[@name='xyz']"
относится к предыдущему select="template"
и, в свою очередь, к тегу <template>
и его атрибуту name
во входном XML)
Таким образом, я могу динамически "вызывать" шаблон просто управляемым из моего входного XML.
Это может быть не та самая проблема, которую вы пытаетесь решить, но я думаю, что она довольно близка и намного проще, чем решения FSXL, упомянутые в других разделах этой страницы.
Ответ 6
Как насчет этого?:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="xsl:template[@name='foo']" name="foo">
Bla bla bla foo
</xsl:template>
<xsl:template match="xsl:template[@name='bar']" name="bar">
Bla bla bla bar
</xsl:template>
<xsl:template match="/">
<xsl:variable name="templateName" select="'bar'"/>
<xsl:apply-templates select="document('')/*/xsl:template[@name=$templateName]"/>
<xsl:apply-templates select="document('')/*/xsl:template[@name='foo']"/>
</xsl:template>
</xsl:stylesheet>
Вы можете упростить "вызов" шаблона, используя переменную, аналогичную описанной в более раннем вкладе:
<xsl:variable name="templates" select="document('')/*/xsl:template"/>
<xsl:apply-templates select="$templates[@name=$templateName]"/>
<xsl:apply-templates select="$templates[@name='foo']"/>
Обратите внимание, что необязательный <xsl:with-param>
может использоваться как обычно.