Предикаты против рекурсивных шаблонов против других

рассмотрим эту простую задачу:

мы хотим сопоставить этот вход с тем же выходом, за исключением первого появления элемента '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, но разумная ли это альтернатива?

  • Существуют ли другие шаблоны использования, которые я могу использовать?

По одному вопросу, пожалуйста. Как спросить

Rob 06.02.2023 15:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
65
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

XSLT имеет xsl:iterate (https://www.w3.org/TR/xslt-30/#iterate ), который позволяет вам реализовать родственную рекурсию декларативным способом, который немного похож на цикл и благодаря своей структуре а реализация позволяет избежать любой рекурсии переполнения стека; повторить пример:

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

оба ваших примера используют декларативное сопоставление с образцом. Суть рекурсивного шаблона в том, что он не использует этот метод, я бы использовал его только в сценариях, которые не подходят для такого типа логики, но я попробую.

MrD at KookerellaLtd 06.02.2023 14:41

@MrDatKookerellaLtd, как я уже отметил, моя попытка с xsl:iterate и осью self не соответствует требованию, поэтому мне все равно придется его переписать. Но попытка состояла в том, чтобы показать, как использовать родственную рекурсию с помощью xsl:iterate.

Martin Honnen 06.02.2023 14:48

ок, меня это интересует

MrD at KookerellaLtd 06.02.2023 15:22

сгиб очень интересный... сгибы мне знакомы, но их использование в контексте XSLT 3.0 немного загадочно

MrD at KookerellaLtd 06.02.2023 18:27

аккумулятор надо будет попробовать, сразу не врубаюсь

MrD at KookerellaLtd 06.02.2023 18:28

так что в основном итерация - это левая складка на наборе узлов () .... что хорошо

MrD at KookerellaLtd 07.02.2023 12:25

Шаблон, который я иногда использую для этого, представляет собой глобальную переменную в сочетании с правилом шаблона:

<xsl:variable name = "special-nodes" select = "//foo[@bar='1'][1]"/>

<xsl:template match = "$special-nodes">...</xsl:template>

Конечно, это работает только в сценарии «один документ», где глобальная переменная применяется к тому же документу, который вы обрабатываете с помощью правила шаблона.

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

MrD at KookerellaLtd 06.02.2023 18:26

Другие вопросы по теме