Xslt 2/3 вложенная группа с сохранением порядка ввода для каждой группы

В stackoverflow есть похожие вопросы, но ответы сортируют или группируют совпадающие элементы, выводя их из последовательности по сравнению с порядком ввода.

У меня есть следующие данные

<doc>
<paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
<paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
<paragraph indent = "0" stylename = "list" SDgroup = "list">AAA. Para 1.</paragraph>
<paragraph indent = "1" stylename = "list" SDgroup = "list">BBB. Para 2</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
<paragraph indent = "2" stylename = "list" SDgroup = "list">CCC. Para 3a</paragraph>
<paragraph indent = "2" stylename = "list" SDgroup = "list">DDD. Para 3b/4</paragraph>
<paragraph indent = "1" stylename = "list" SDgroup = "list">EEE. Para 5</paragraph>
<paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
<paragraph indent = "0" stylename = "list" SDgroup = "list">FFF. Para 6</paragraph>
</doc>

Мне нужно вложить абзац[@stylename='list'} и следующий абзац[@stylename='continued'] на основе list/@indent = x и continue/@indent = x + 1.

Вывод должен выглядеть так:

<doc>
  <paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
  <paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>

  <List indent = "0" maxItems = "2">
    <paragraph stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
      <List indent = "1" maxItems = "1">
        <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
        <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
        <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
        <List indent = "2" maxItems = "2">
          <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
          <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 34</paragraph>
        </List>
        <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
        <paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
      </List>
      <paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
  </List>
</doc>

Я перепробовал много, много версий для каждой группы + группы по/смежным и обычным для каждого, но в итоге получил неупорядоченный вывод и/или правильно вложенный вывод, но дублирующие данные (т. е. элемент правильно вложен и затем в какой-то момент тот же элемент выводится снова Я полагаю, что это происходит с многократно вложенными циклами for-each.

Я на 99,9% уверен, что это выполнимо, но не могу найти решение, которое дает то, что мне нужно. Еще раз прошу помощи, которая, конечно же, будет очень, очень признательна.

К вашему сведению: у меня нет контроля над входными данными или средствами их изменения, если только они не находятся в XSLT.

Непонятно, по каким критериям начинать/формировать список, по крайней мере, мне. <paragraph indent = "1" stylename = "list" SDgroup = "list">BBB. Para 2</paragraph>, кажется, запускает list, почему <paragraph indent = "1" stylename = "list" SDgroup = "list">EEE. Para 5</paragraph> не запускает другой?

Martin Honnen 20.07.2023 18:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я пытался понять, что делает ваш пример, но я не совсем понял. На случай, если это чем-то поможет, я применил подход рекурсивной функции.

Одна вещь, которую я не вижу в вашем примере, - это серия уровней увеличения/уменьшения/увеличения отступа, поэтому мне не с чем проверять, но метод, который я отправляю, должен справиться с этим.

Как упомянул @martin-honnen в своем вопросе, существует двусмысленность в отношении того, как обращаться с типом list w.r.t. отступ.

Кроме того, я понятия не имею, какое правило может быть для атрибута maxItems.

Для чего это стоит:

<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
  version = "3.0"
  xmlns:xs = "http://www.w3.org/2001/XMLSchema"
  xmlns:l = "local:functions"
  exclude-result-prefixes = "#all">
  
  <xsl:output indent = "yes" omit-xml-declaration = "yes" />

  <xsl:mode on-no-match = "shallow-copy"/>

  <xsl:template match = "/doc" >
    <xsl:copy>
      <xsl:sequence select = "l:processParagraphs(head(*)/@indent, *)" />
    </xsl:copy>
  </xsl:template>
  
  <xsl:function name = "l:processParagraphs" as = "element()*">
    <xsl:param name = "indentLevel" as = "xs:string"           />
    <xsl:param name = "paras"       as = "element(paragraph)*" />

    <xsl:choose>
      <xsl:when test = "head($paras)/@stylename eq 'list'
                      and
                      xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)">
        <List indent = "{head($paras)/@indent}" maxItems = "???">
          <xsl:variable name = "itemsUnderList" as = "element(paragraph)*"
                        select = "l:nextInList(head($paras)/@indent, $paras)" />
          <xsl:sequence select = "l:processInner(head($paras)/@indent, $itemsUnderList)" />
          <xsl:sequence select = "l:processParagraphs($indentLevel, $paras except $itemsUnderList)" />
        </List>
      </xsl:when>
      <xsl:otherwise>
        <xsl:sequence select = "l:processInner($indentLevel, $paras)" />
      </xsl:otherwise>
    </xsl:choose>    
  </xsl:function>
  
  <xsl:function name = "l:processInner" as = "element()*">
    <xsl:param name = "indentLevel" as = "xs:string"           />
    <xsl:param name = "paras"       as = "element(paragraph)*" />
    
        <xsl:apply-templates select = "head($paras)" /> <!-- to replace list stylename -->
        <xsl:if test = "tail($paras)" >
          <xsl:sequence select = "l:processParagraphs(head($paras)/@indent, tail($paras))" />
        </xsl:if>
  </xsl:function>

  <xsl:template match = "paragraph/@stylename[. eq 'list']">
    <xsl:attribute name = "stylename" select = "'list_unordered'" />
  </xsl:template>
  
  <xsl:function name = "l:nextInList" as = "element(paragraph)*" >
    <xsl:param name = "indentLevel" as = "xs:string"           />
    <xsl:param name = "paras"       as = "element(paragraph)*" />
    
    <xsl:if test = "xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)" >
      <xsl:sequence select = "(head($paras), 
                             l:nextInList($indentLevel, tail($paras)))" />
    </xsl:if>
  </xsl:function>
  
</xsl:stylesheet>

производит (в https://martin-honnen.github.io/xslt3fiddle/)

<doc>
   <paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
   <paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
   <List indent = "0" maxItems = "???">
      <paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
      <List indent = "1" maxItems = "???">
         <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
         <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
         <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
         <List indent = "2" maxItems = "???">
            <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
            <List indent = "2" maxItems = "???">
               <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 3b/4</paragraph>
            </List>
            <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
            <paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
         </List>
         <List indent = "0" maxItems = "???">
            <paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
         </List>
      </List>
   </List>
</doc>

Спасибо, альтруизм. Извините за мое расплывчатое объяснение. maxItems должен подсчитывать количество <paragraph stylename = "list"> в этом <List> отступах RE: исходный файл представляет собой плоский XML, т. е. ВСЕ элементы являются одноуровневыми в <doc>. Наконец, вывод не совсем правильный. Вложенный <List> создается только тогда, когда значение отступа в пределах <paragraph stylename = "list"> > предыдущего отступа Para, содержащего: <paragraph indent = "2" stylename = "list" SDgroup = "list">DDD. Параграф 3b/4</paragraph> должен находиться в том же <List>, что и: <paragraph indent = "2" stylename = "list" SDgroup = "list">CCC. Параграф 3а</параграф>

markh 21.07.2023 17:11

это работает ОЧЕНЬ, ОЧЕНЬ хорошо. Спасибо. Спасибо. Вы не представляете, сколько сна я потерял, пытаясь достичь этой функциональности. Я до сих пор не совсем понимаю, как это работает, но я использую свой отладчик, чтобы пройти и посмотреть на внутренности, но результат - это то, что я искал. Еще раз спасибо.

markh 24.07.2023 16:32
Ответ принят как подходящий

После вашего комментария стало немного легче понять ваш вывод; однако я предлагаю, чтобы предоставленный вами вывод не соответствовал вашему описанию, потому что maxItems для второго элемента List (сразу после AAA должно быть 2, а не 1, поскольку есть два элемента list_unordered, которые являются непосредственными дочерними элементами этого List элемента.

Я переработал свой пример реализации:

<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
                version = "3.0"
                xmlns:xs = "http://www.w3.org/2001/XMLSchema"
                xmlns:l = "local:functions"
                exclude-result-prefixes = "#all">

    <xsl:output indent = "yes" omit-xml-declaration = "yes" />

    <xsl:mode on-no-match = "shallow-copy"/>

    <xsl:template match = "/doc" >
        <xsl:copy>
            <xsl:sequence select = "l:processParagraphs('-1', *)" />
        </xsl:copy>
    </xsl:template>

    <xsl:function name = "l:processParagraphs" as = "element()*">
        <xsl:param name = "indentLevel" as = "xs:string"           />
        <xsl:param name = "paras"       as = "element(paragraph)*" />

        <xsl:choose>
            <xsl:when test = "head($paras)/@stylename eq 'list'
                            and
                            xs:integer(head($paras)/@indent) gt xs:integer($indentLevel)">
                <xsl:variable name = "itemsUnderList" as = "element(paragraph)*"
                              select = "l:nextInList(head($paras)/@indent, $paras)" />
                <xsl:variable name = "processedInner" as = "element()*"
                              select = "l:processInner(head($paras)/@indent, $itemsUnderList)" />
                <List indent = "{head($paras)/@indent}" maxItems = "{count($processedInner[@stylename eq 'list_unordered'])}">
                    <xsl:sequence select = "$processedInner" />
                </List>
                <xsl:sequence select = "l:processParagraphs($indentLevel, $paras except $itemsUnderList)" />
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select = "l:processInner($indentLevel, $paras)" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:function>

    <xsl:function name = "l:processInner" as = "element()*">
        <xsl:param name = "indentLevel" as = "xs:string"           />
        <xsl:param name = "paras"       as = "element(paragraph)*" />

        <xsl:apply-templates select = "head($paras)" /> <!-- to replace list stylename -->
        <xsl:if test = "tail($paras)" >
            <xsl:sequence select = "l:processParagraphs($indentLevel, tail($paras))" />
        </xsl:if>
    </xsl:function>

    <xsl:template match = "paragraph/@stylename[. eq 'list']">
        <xsl:attribute name = "stylename" select = "'list_unordered'" />
    </xsl:template>

    <xsl:function name = "l:nextInList" as = "element(paragraph)*" >
        <xsl:param name = "indentLevel" as = "xs:string"           />
        <xsl:param name = "paras"       as = "element(paragraph)*" />

        <xsl:if test = "xs:integer(head($paras)/@indent) ge xs:integer($indentLevel)" >
            <xsl:sequence select = "(head($paras),
                               l:nextInList($indentLevel, tail($paras)))" />
        </xsl:if>
    </xsl:function>

</xsl:stylesheet>

который производит:

<doc>
   <paragraph indent = "0" stylename = "heading_l1_toc" SDgroup = "heading">heading-l1</paragraph>
   <paragraph indent = "0" stylename = "heading_l2_toc" SDgroup = "heading">heading-l2</paragraph>
   <List indent = "0" maxItems = "2">
      <paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">AAA. Para 1.</paragraph>
      <List indent = "1" maxItems = "2">
         <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">BBB. Para 2</paragraph>
         <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para1</paragraph>
         <paragraph indent = "2" stylename = "continued" SDgroup = "list">BBB. Continued para2</paragraph>
         <List indent = "2" maxItems = "2">
            <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">CCC. Para 3a</paragraph>
            <paragraph indent = "2" stylename = "list_unordered" SDgroup = "list">DDD. Para 3b/4</paragraph>
         </List>
         <paragraph indent = "1" stylename = "list_unordered" SDgroup = "list">EEE. Para 5</paragraph>
         <paragraph indent = "2" stylename = "continued" SDgroup = "list">EEE. Continued para</paragraph>
      </List>
      <paragraph indent = "0" stylename = "list_unordered" SDgroup = "list">FFF. Para 6</paragraph>
   </List>
</doc>

это работает ОЧЕНЬ, ОЧЕНЬ хорошо. Спасибо. Спасибо. Вы не представляете, сколько сна я потерял, пытаясь достичь этой функциональности. Я до сих пор не совсем понимаю, как это работает, но я использую свой отладчик, чтобы пройти и посмотреть на внутренности, но результат - это то, что я искал. Еще раз спасибо.

markh 24.07.2023 16:33

Основной алгоритм заключается в том, что при спуске элементов мы записываем каждый из них с помощью l:processParagraphs(), которая, если нам не нужен <List>, просто вызывает l:processInner(). если мы находим список, и этот список имеет уровень отступа выше, чем предыдущие уровни списка, то мы добавляем элемент <List>, обновляем indentLevel и затем рекурсивно обрабатываем в l:processParagraphs() те элементы, которые будут частью этого конкретного < List> (определяется l:nextInList()). (Функция l:nextInList() может быть упрощена с помощью выражения оси :following-sibling.)

al.truisme 25.07.2023 08:45

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