рассмотрим эту простую задачу:
мы хотим сопоставить этот вход с тем же выходом, за исключением первого появления элемента 'foo' с "@bar = '1'", мы добавляем новый атрибут @wibble, так что это:
<root>
<foo/>
<foo/>
<foo/>
<foo bar = "1"/>
<foo bar = "1"/>
<foo/>
<foo/>
<foo/>
<foo/>
<foo/>
</root>
идет к этому:
<root>
<foo />
<foo />
<foo />
<foo wibble = "2" bar = "1" />
<foo bar = "1" />
<foo />
<foo />
<foo />
<foo />
<foo />
</root>
Я мог бы реализовать это сопоставление, используя шаблон идентификации (не знаю, как называется этот шаблон), но это будет выглядеть так:
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl = "urn:schemas-microsoft-com:xslt" exclude-result-prefixes = "msxsl"
>
<xsl:output method = "xml" indent = "yes"/>
<xsl:template match = "/">
<xsl:apply-templates select = "root" mode = "findFirst"/>
</xsl:template>
<xsl:template match = "@* | node()" mode = "findFirst">
<xsl:copy>
<xsl:apply-templates select = "@* | node()" mode = "findFirst"/>
</xsl:copy>
</xsl:template>
<xsl:template match = "foo[@bar='1'][1]" mode = "findFirst">
<xsl:copy>
<xsl:attribute name = "wibble">2</xsl:attribute>
<xsl:apply-templates select = "@* | node()" mode = "findFirst"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
т. е. мы переопределяем шаблон удостоверения некоторым оператором сопоставления, который соответствует конкретному сценарию, который мы хотим сопоставить, реализуем наше переопределяющее сопоставление, а затем продолжаем.
Я часто использую этот стиль.
Иногда, хотя оператор match является сложным (мы недавно видели это в другом вопросе о сопоставлении строк кода). Я нахожу такого рода совпадения проблематичными, в приведенном выше сценарии вариант использования прост, но иногда логика не может быть легко (или вообще) явно выражена внутри оператора сопоставления, и в этом случае у меня возникает соблазн вернуться к рекурсивным функциональным шаблонам, и в этом случае я бы написал такой рекурсивный шаблон.
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl = "urn:schemas-microsoft-com:xslt" exclude-result-prefixes = "msxsl"
>
<xsl:output method = "xml" indent = "yes"/>
<xsl:template match = "/">
<root>
<xsl:apply-templates select = "root/foo[1]" mode = "findFirst">
<xsl:with-param name = "isFound" select = "false()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match = "foo" mode = "findFirst">
<xsl:param name = "isFound"/>
<xsl:copy>
<xsl:if test = "$isFound = false() and @bar = '1'">
<xsl:attribute name = "wibble">2</xsl:attribute>
</xsl:if>
<xsl:apply-templates select = "@* | node()" mode = "identity"/>
</xsl:copy>
<xsl:choose>
<xsl:when test = "$isFound = false() and @bar = '1'">
<xsl:apply-templates select = "following-sibling::foo[1]" mode = "findFirst">
<xsl:with-param name = "isFound" select = "true()"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select = "following-sibling::foo[1]" mode = "findFirst">
<xsl:with-param name = "isFound" select = "$isFound"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match = "@* | node()" mode = "identity">
<xsl:copy>
<xsl:apply-templates select = "@* | node()" mode = "identity"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
это в основном рассматривает набор узлов как функциональный «список», принимая голову (и неявно передавая хвост). Теперь мы можем реализовать гораздо более сложную логику и использовать параметры для передачи текущего состояния (фактически свернутого) через рекурсию, но за счет дополнительной сложности.
НО....
Является ли этот стиль программирования устойчивым в XSLT? - Я всегда беспокоюсь о переполнении стека (по иронии судьбы!), из-за вероятной нехвостовой рекурсии в механизме XSLT рекурсивного шаблона.
Мои познания в XSLT 3.0 чрезвычайно ограничены (всегда приветствуются любые ссылки на хорошие учебные ресурсы), но в языке FP альтернативой прямой рекурсии было бы использование fold, где fold записывается как хвостовая рекурсивная функция, а fold IS доступен в XSLT 3.0, но разумная ли это альтернатива?
Существуют ли другие шаблоны использования, которые я могу использовать?
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes = "#all"
expand-text = "yes">
<xsl:template match = "/*">
<xsl:copy>
<xsl:apply-templates select = "@*"/>
<xsl:iterate select = "node()">
<xsl:param name = "found" select = "false()"/>
<xsl:variable name = "is-first-foo" select = "if (. instance of element(foo)) then not($found) and boolean(self::foo[@bar = 1]) else $found"/>
<xsl:choose>
<xsl:when test = "$is-first-foo">
<xsl:copy>
<xsl:attribute name = "wibble" select = "2"/>
<xsl:apply-templates select = "@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select = "."/>
</xsl:otherwise>
</xsl:choose>
<xsl:next-iteration>
<xsl:with-param name = "found" select = "$is-first-foo"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:copy>
</xsl:template>
<xsl:mode on-no-match = "shallow-copy"/>
</xsl:stylesheet>
fold-left
, безусловно, также доступен на уровне XPath 3.1, его интеграция с синтаксисом XML XSLT (3.0) немного сложнее, чем в XQuery 3.1, где в основном все является выражением. Но это, безусловно, вариант; пример онлайн:
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes = "#all"
xmlns:mf = "http://example.com/mf"
expand-text = "yes">
<xsl:function name = "mf:add-attribute" as = "element()">
<xsl:param name = "element" as = "element()"/>
<xsl:copy select = "$element">
<xsl:attribute name = "wibble" select = "2"/>
<xsl:apply-templates select = "@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:function>
<xsl:template match = "/*">
<xsl:copy>
<xsl:apply-templates select = "@*"/>
<xsl:sequence
select = "fold-left(
node(),
map { 'found-foos' : 0, 'nodes' : () },
function($a, $n) {
let $is-foo := $n instance of element(foo) and boolean($n/self::foo[@bar = 1]),
$is-first-foo := $a?found-foos = 0 and $is-foo
return
map {
'found-foos' : if ($is-foo) then $a?found-foos + 1 else $a?found-foos,
'nodes': ($a?nodes, if ($is-first-foo) then mf:add-attribute($n) else $n)
}
}
)?nodes"/>
</xsl:copy>
</xsl:template>
<xsl:mode on-no-match = "shallow-copy"/>
</xsl:stylesheet>
И для вашего примера аккумулятор может позволить вам проверить ваши условия декларативным способом, а затем использовать его значение в шаблоне соответствия, чтобы проверить, нужно ли вам добавлять свой атрибут. Онлайн пример использования аккумулятора:
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
version = "3.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes = "#all"
expand-text = "yes">
<xsl:param name = "pattern" static = "yes" as = "xs:string" select = "'foo[@bar = 1][1]'"/>
<xsl:accumulator name = "have-first-foo-bar" as = "xs:boolean" initial-value = "false()">
<xsl:accumulator-rule _match = "{$pattern}" select = "true()"/>
<xsl:accumulator-rule phase = "end" _match = "{$pattern}" select = "false()"/>
</xsl:accumulator>
<xsl:template match = "foo[accumulator-before('have-first-foo-bar')]">
<xsl:copy>
<xsl:attribute name = "wibble" select = "2"/>
<xsl:apply-templates select = "@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:mode on-no-match = "shallow-copy" use-accumulators = "#all"/>
</xsl:stylesheet>
оба ваших примера используют декларативное сопоставление с образцом. Суть рекурсивного шаблона в том, что он не использует этот метод, я бы использовал его только в сценариях, которые не подходят для такого типа логики, но я попробую.
@MrDatKookerellaLtd, как я уже отметил, моя попытка с xsl:iterate
и осью self
не соответствует требованию, поэтому мне все равно придется его переписать. Но попытка состояла в том, чтобы показать, как использовать родственную рекурсию с помощью xsl:iterate.
ок, меня это интересует
сгиб очень интересный... сгибы мне знакомы, но их использование в контексте XSLT 3.0 немного загадочно
аккумулятор надо будет попробовать, сразу не врубаюсь
так что в основном итерация - это левая складка на наборе узлов () .... что хорошо
Шаблон, который я иногда использую для этого, представляет собой глобальную переменную в сочетании с правилом шаблона:
<xsl:variable name = "special-nodes" select = "//foo[@bar='1'][1]"/>
<xsl:template match = "$special-nodes">...</xsl:template>
Конечно, это работает только в сценарии «один документ», где глобальная переменная применяется к тому же документу, который вы обрабатываете с помощью правила шаблона.
ах, хорошо, это очень мило, я попробую, это такие вещи, которые вы никогда нигде не увидите. Есть ли проблема с рекурсией, не связанной с хвостовой рекурсией, с моим немного странным рекурсивным шаблоном «список»?
По одному вопросу, пожалуйста. Как спросить