Как развернуть и поместить элементы между двумя другими элементами

В настоящее время я переношу запрос MS Access в XML, чтобы затем использовать XSLT 3.0 для ввода XML в FrameMaker для целей публикации. В процессе мне приходится анализировать различные поля Access RTF с помощью анализатора HTML Дэвида Карлайла (https://github.com/davidcarlisle/web-xslt/tree/main?tab=readme-ov-file), чтобы преобразовать информацию я поместил в разные поля в узлы XML.

Я пытаюсь реализовать маркировку 2-го или 3-го уровня, которая изначально не поддерживается в RTF (по крайней мере, как реализовано в Access). Для этого я создаю в поле текст, который затем анализируется в элемент XML для обозначения маркера второго уровня. Одна проблема, с которой я сталкиваюсь, заключается в том, что поле RTF Access применяет узлы <div> и <font> вокруг любых текстовых элементов, поэтому, когда я пытаюсь это сделать (обратите внимание, что «узлы» <list2> — это просто текст, находящийся внутри узла, к которому затем применяется HTMLParse к этому):

- item 1
<list2>
- subitem 1
</list2>

Получается что-то близкое к этому:

- item 1
<div><font><list2></font></div>
- subitem 1
<div><font></list2></font></div>

Это, конечно, не работает, поскольку это некорректный XML. Есть ли способ преобразовать это с помощью XSLT в исходный блок, где подэлемент содержится в узле списка?? Один из вариантов — начать с

- item 1
<div><font><list2></list2></font></div>
- subitem 1
<div><font><list2></list2></font></div>

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

Обновлено: я пытался упростить пример в своем примере, но упустил важные детали. Я пытался поделиться ссылкой XSLT Fiddle, но это не сработало. Ниже приведен XML-файл, за которым следует сокращенная версия XSLT. Обратите внимание: я вполне уверен, что файл XSLT настроен не идеально, поскольку я начал его создавать, не имея никаких знаний, и со временем добавлял кое-что. Если у вас есть предложения по устранению дублирования или передовому опыту, который мне следует использовать, я также буду признателен за них.

XML-вход:

    <?xml version = "1.0" encoding = "UTF-8"?>
<dataroot xmlns:od = "urn:schemas-microsoft-com:officedata" generated = "2024-03-05T13:21:03">
<TEQuery>
<Description>
&lt;ul&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;The proposed facility is intended to operate for at least 10 years. &lt;/font&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;list2&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;ul&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;between July 1, 2011, and October 5, 2017, receive compensation (wages and benefits) at least 50 percent higher than the per capita personal income (PCPI) for the county at the time of the application, or at least equal to county PCPI while providing health insurance benefits, or,&lt;/font&gt;&lt;/li&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;since October 6, 2017 (HB 2066, 2017):&lt;/font&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;/list2&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;list3&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;(i) receive compensation meeting the above minimums or that is at least 30 percent more than county PCPI for locations outside any metropolitan statistical area, and&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;(ii) (in all cases) receive an average annual wage at least equal to the then current average wage for the county. &lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;/list3&amp;gt;&lt;/font&gt;&lt;/div&gt;
</Description>
</TEQuery>
</dataroot>

XSLT

        <xsl:stylesheet version = "3.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
  xmlns:dc = "data:,dpc"
  xmlns:xs = "http://www.w3.org/2001/XMLSchema"
  xmlns:mf = "http://example.com/mf"
  exclude-result-prefixes = "#all">
  
<xsl:output method = "xml" omit-xml-declaration = "no" encoding = "UTF-8" indent = "yes" />
  
  
<xsl:import href = "https://raw.githubusercontent.com/davidcarlisle/web-xslt/main/htmlparse/htmlparse.xsl"/>

<xsl:strip-space elements = "* except TitleTab"/>

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

  <xsl:template match=
  "*[not(node())]
  |
   *[not(node()[2])
   and
     node()/self::text()
   and
     not(normalize-space())
     ]
  "/>
  
<xsl:template match = "*"> <!--this is here to remove namespace prefixes that were propogating weirdly all over-->
    <!-- remove element prefix -->
    <xsl:element name = "{local-name()}">
      <!-- process attributes -->
      <xsl:for-each select = "@*">
        <!-- remove attribute prefix -->
        <xsl:attribute name = "{local-name()}">
          <xsl:value-of select = "."/>
        </xsl:attribute>
      </xsl:for-each>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>
  
<!-- may want to add tab into each list item. If so, I can do so here-->
   <xsl:template match = "li">
    <li>
        <paragraph>
        <xsl:apply-templates/>
        </paragraph>
    </li>
  </xsl:template>
    
  <!-- removes some weird formatting that comes out of the rich text format fields. NEEDS FIXED TO NOT REMOVE NESTED LISTS!!! -->
  
  <xsl:template match = "font"> 
    <xsl:apply-templates/>
  </xsl:template>
  
  
  <xsl:template match = "div"> 
    <paragraph>
      <xsl:apply-templates 
                  select = "node()[boolean(normalize-space(translate(., '&#160;', ' ')))]
                         |@*"/>
    </paragraph>
  </xsl:template>

  <!-- converts weird rtf formatting to a list format that FrameMaker can work with NEED to allow nested lists, still need to figure out how-->
  <xsl:template match = "ul">
    <xsl:choose>
    <xsl:when test = "child::*[1][self::ul]">
        <xsl:apply-templates/>
    </xsl:when>
    <xsl:otherwise>
    <unorderedList>
      <xsl:apply-templates/>
    </unorderedList>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
    <!--does same for ordered lists-->
  <xsl:template match = "ol">
    <xsl:choose>
    <xsl:when test = "child::*[1][self::ol]">
        <xsl:apply-templates/>
    </xsl:when>
    <xsl:otherwise>
        <orderedList>
          <xsl:apply-templates/>
        </orderedList>
    </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
  <xsl:function name = "mf:wrap-lists" as = "node()*"> <!-- function to help wrap the list items to create second and third order lists-->
    <xsl:param name = "nodes" as = "node()*"/>
    <xsl:param name = "list-level" as = "xs:integer"/>
    <xsl:for-each-group select = "$nodes" group-starting-with = "div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
      <xsl:choose>
        <xsl:when test = "self::div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
          <xsl:for-each-group select = "tail(current-group())" group-ending-with = "div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]">
            <xsl:choose>
              <xsl:when test = "current-group()[last()][self::div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]]">
                <xsl:element name = "list{$list-level}">
                  <xsl:apply-templates select = "current-group()[position() ne last()]"/>
                </xsl:element>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select = "current-group()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select = "current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>
    <!-- This template does (optional recursively) what you need without the need of matching specific elements. This parses the RTF field that is similar to HTML, allowing us to manually create nodes withing other nodes. For example lists or graphics.-->

   <xsl:template match = "Description">
    <xsl:copy>
      <xsl:sequence select = "fold-left(2 to 3, dc:htmlparse(., '', false())!node(), function($n, $c) { mf:wrap-lists($n, $c) })"/>
    </xsl:copy>
  </xsl:template>
  
  
</xsl:stylesheet>

''' Дает результат

<Description xmlns:od = "urn:schemas-microsoft-com:officedata">
         <unorderedList>
            <li>
               <paragraph>
                  <paragraph>The proposed facility is intended to operate for at least 10 years. </paragraph>
               </paragraph>
            </li>
         </unorderedList>
         <list2>
            <unorderedList>
               <li>
                  <paragraph>
                     <paragraph>between July 1, 2011, and October 5, 2017, receive compensation (wages and benefits) at least 50 percent higher than the per capita personal income (PCPI) for the county at the time of the application, or at least equal to county PCPI while providing health insurance benefits, or,</paragraph>
                  </paragraph>
               </li>
               <li>
                  <paragraph>
                     <paragraph>since October 6, 2017 (HB 2066, 2017):</paragraph>
                  </paragraph>
               </li>
            </unorderedList>
         </list2>
         <paragraph>&lt;list3&gt;</paragraph>
         <paragraph>(i) receive compensation meeting the above minimums or that is at least 30 percent more than county PCPI for locations outside any metropolitan statistical area, and</paragraph>
         <paragraph>(ii) (in all cases) receive an average annual wage at least equal to the then current average wage for the county. </paragraph>
         <paragraph>&lt;/list3&gt;</paragraph>
      </Description>

Трудно понять, в какой момент у вас есть текст с разметкой и начальными и/или конечными тегами, а в какой — узлы. Я не совсем понимаю, почему, если ваш <list2> рассматривается как текст, в конечном итоге вы получаете это как разметку внутри другой разметки или элементы-обертки div и font вокруг текста, который не экранирован (т. е. я мог бы представить, что у меня есть <div><font>&lt;list2&gt;</font></div> с элементом font, содержащим (в сериализованной форме) экранированную разметку начального тега.

Martin Honnen 11.03.2024 19:37

Я также не понимаю, в какой момент вы используете htmlparse Дэвида Карлайла, он наверняка будет использовать такие вещи, как <div><font><list2></font></div>, даже если это неправильно сформированный X(HT)ML.

Martin Honnen 11.03.2024 20:17

Спасибо, Мартин, вы правы в том, что мой первоначальный вопрос был плохо сформулирован, я надеялся упростить ситуацию. Я опубликовал полный файл XML и XSLT, с которым можно работать, чтобы понять, что я имею в виду.

DSB 11.03.2024 21:30

Пожалуйста, сократите код до минимума, необходимого для демонстрации проблемы (см.: минимальный воспроизводимый пример) ​​и добавьте ожидаемый результат..

michael.hor257k 11.03.2024 22:03

Спасибо, Михаил, надеюсь, что это обновление поможет. Я попробовал решение @MartinHonnen, и оно почти привело меня к цели, но в моем XSLT-файле есть что-то, что мешает правильному преобразованию элемента list3, и я не могу понять, что именно.

DSB 12.03.2024 19:58
Стоит ли изучать 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
5
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Интересно, следует ли вам использовать функцию htmlparse один раз для преобразования содержимого элементов Description в узлы элементов, некоторые из которых представляют собой div с дочерними элементами font, имеющими экранированные начальный и конечный теги <listX>.

Если ввод такой же простой и регулярный, я бы попробовал использовать вложенный </listX>, чтобы преобразовать начальные/конечные теги экранированного списка в for-each-group group-starting-with/group-ending-with элементы-оболочки.

Например, чтобы взять часть входных данных выборки, вы можете обработать, например.

<Description>
&lt;ul&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;The proposed facility is intended to operate for at least 10 years. &lt;/font&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;list2&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;ul&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;between July 1, 2011, and October 5, 2017, receive compensation (wages and benefits) at least 50 percent higher than the per capita personal income (PCPI) for the county at the time of the application, or at least equal to county PCPI while providing health insurance benefits, or,&lt;/font&gt;&lt;/li&gt;
 &lt;li&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;since October 6, 2017 (HB 2066, 2017):&lt;/font&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;/list2&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;list3&amp;gt;&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;(i) receive compensation meeting the above minimums or that is at least 30 percent more than county PCPI for locations outside any metropolitan statistical area, and&lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;(ii) (in all cases) receive an average annual wage at least equal to the then current average wage for the county. &lt;/font&gt;&lt;/div&gt;

&lt;div&gt;&lt;font face=&quot;Times New Roman&quot; color=black&gt;&amp;lt;/list3&amp;gt;&lt;/font&gt;&lt;/div&gt;
</Description>

с кодом типа

<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
  version = "3.0"
  xmlns:xs = "http://www.w3.org/2001/XMLSchema"
  xmlns:dc = "data:,dpc"
  exclude-result-prefixes = "#all"
  xmlns:mf = "http://example.com/mf"
  expand-text = "yes">
  
  <xsl:function name = "mf:wrap-lists" as = "node()*">
    <xsl:param name = "nodes" as = "node()*"/>
    <xsl:param name = "list-level" as = "xs:integer"/>
    <xsl:for-each-group select = "$nodes" group-starting-with = "div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
      <xsl:choose>
        <xsl:when test = "self::div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
          <xsl:for-each-group select = "tail(current-group())" group-ending-with = "div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]">
            <xsl:choose>
              <xsl:when test = "current-group()[last()][self::div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]]">
                <xsl:element name = "list{$list-level}">
                  <xsl:apply-templates select = "current-group()[position() ne last()]"/>
                </xsl:element>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select = "current-group()"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select = "current-group()"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>
  
  <xsl:import href = "https://raw.githubusercontent.com/davidcarlisle/web-xslt/main/htmlparse/htmlparse.xsl"/>

  <xsl:mode on-no-match = "shallow-copy"/>
  
  <xsl:template match = "Description">
    <xsl:copy>
      <xsl:sequence select = "fold-left(2 to 3, dc:htmlparse(., '', false())!node(), function($n, $c) { mf:wrap-lists($n, $c) })"/>
    </xsl:copy>
  </xsl:template>
  
</xsl:stylesheet>

и получил бы, например.

<Description>
<ul>
 <li><font face = "Times New Roman" color = "black">The proposed facility is intended to operate for at least 10 years. </font></li>
</ul>

<list2>

<ul>
 <li><font face = "Times New Roman" color = "black">between July 1, 2011, and October 5, 2017, receive compensation (wages and benefits) at least 50 percent higher than the per capita personal income (PCPI) for the county at the time of the application, or at least equal to county PCPI while providing health insurance benefits, or,</font></li>
 <li><font face = "Times New Roman" color = "black">since October 6, 2017 (HB 2066, 2017):</font></li>
</ul>

</list2>

<list3>

<div><font face = "Times New Roman" color = "black">(i) receive compensation meeting the above minimums or that is at least 30 percent more than county PCPI for locations outside any metropolitan statistical area, and</font></div>

<div><font face = "Times New Roman" color = "black">(ii) (in all cases) receive an average annual wage at least equal to the then current average wage for the county. </font></div>

</list3>
</Description>

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

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

  <xsl:mode name = "wrap" on-no-match = "shallow-copy"/>
  
  <xsl:function name = "mf:wrap-lists" as = "node()*"> <!-- function to help wrap the list items to create second and third order lists-->
    <xsl:param name = "nodes" as = "node()*"/>
    <xsl:param name = "list-level" as = "xs:integer"/>
    <xsl:for-each-group select = "$nodes" group-starting-with = "div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
      <xsl:choose>
        <xsl:when test = "self::div[*[1][self::font[. = '&lt;' || 'list' || $list-level || '&gt;']]]">
          <xsl:for-each-group select = "tail(current-group())" group-ending-with = "div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]">
            <xsl:choose>
              <xsl:when test = "current-group()[last()][self::div[*[1][self::font[. = '&lt;/' || 'list' || $list-level || '&gt;']]]]">
                <xsl:element name = "list{$list-level}">
                  <xsl:apply-templates select = "current-group()[position() ne last()]" mode = "wrap"/>
                </xsl:element>
              </xsl:when>
              <xsl:otherwise>
                <xsl:apply-templates select = "current-group()" mode = "wrap"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each-group>
        </xsl:when>
        <xsl:otherwise>
          <xsl:apply-templates select = "current-group()" mode = "wrap"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each-group>
  </xsl:function>

   <xsl:template match = "Description">
    <xsl:copy>
      <xsl:apply-templates select = "fold-left(2 to 3, dc:htmlparse(., '', false())!node(), function($n, $c) { mf:wrap-lists($n, $c) })"/>
    </xsl:copy>
  </xsl:template>

Таким образом, результат для вашего более короткого входного образца в редактировании, но с другим кодом из вашего образца XSLT, будет

<dataroot generated = "2024-03-05T13:21:03">
   <TEQuery>
      <Description xmlns:od = "urn:schemas-microsoft-com:officedata">
         <unorderedList>
            <li>
               <paragraph>The proposed facility is intended to operate for at least 10 years. </paragraph>
            </li>
         </unorderedList>
         <list2>
            <unorderedList>
               <li>
                  <paragraph>between July 1, 2011, and October 5, 2017, receive compensation (wages and benefits) at least 50 percent higher than the per capita personal income (PCPI) for the county at the time of the application, or at least equal to county PCPI while providing health insurance benefits, or,</paragraph>
               </li>
               <li>
                  <paragraph>since October 6, 2017 (HB 2066, 2017):</paragraph>
               </li>
            </unorderedList>
         </list2>
         <list3>
            <paragraph>(i) receive compensation meeting the above minimums or that is at least 30 percent more than county PCPI for locations outside any metropolitan statistical area, and</paragraph>
            <paragraph>(ii) (in all cases) receive an average annual wage at least equal to the then current average wage for the county. </paragraph>
         </list3>
      </Description>
   </TEQuery>
</dataroot>

Мартин, это было очень полезно, хотя, когда я включаю это в свой полный XSLT, узел list3 не создается. Я обновил свой пост, добавив более короткий XSLT-файл, который, надеюсь, приблизит нас к минимально воспроизводимому примеру, хотя, к сожалению, я думаю, что некоторые из этих элементов необходимо включить, потому что один из них, скорее всего, является проблемой, и я не могу точно сказать, какой именно.

DSB 12.03.2024 18:37

@DSB, посмотрите, поможет ли редактирование, оно переводит предложенную мной обработку в специальный режим и таким образом отделяет предложенную мной обработку от той, которую вы делаете, в основном для контента Description, сначала выполняется моя обработка, и ее результат подается в существующие шаблоны.

Martin Honnen 12.03.2024 22:44

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