Arhn - архитектура программирования

Рекурсивный шаблон, использующий набор узлов и использующий ось предыдущего брата

У меня есть такой XML:

<TEI>
  <text>
    <div type="scene" n="1">
      <sp xml:id="sp1">
        <speaker>Julius</speaker>
        <l>Lorem ipsum dolor sit amet</l>
        <ptr cRef="..." />
        <stage>Aside</stage>
        <ptr cRef="..." />
        <l>consectetur adipisicing elit</l>
        <stage>To Antony</stage>
        <l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
      </sp>
      <sp xml:id="sp2">
        ...

И мне нужно поднять все элементы <stage> на один уровень, чтобы они стали братьями и сестрами <sp>, разбивая <sp> так, чтобы элементы <stage> сохраняли свои предшествующие и последующие отношения с другими элементами внутри <sp>, например

<TEI>
  <text>
    <div type="scene" n="1"> 
     <sp by="#Julius">
       <l>Lorem ipsum dolor sit amet</l>
       <ptr cRef="..." />
     </sp>
     <stage>Aside</stage>
     <sp by="#Julius">
       <ptr cRef="..." />
       <l>consectetur adipisicing elit</l>
     </sp>
     <stage>To Antony</stage>
     <sp by="#Julius">
       <l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
     </sp>

Я работал над XSLT, чтобы сделать это. Он включает рекурсивный шаблон, который предназначен для использования всех дочерних элементов <sp> до (но не включая) первого дочернего элемента <stage> и выдачи их в результирующем дереве как дочерних элементов нового <sp>. Затем испустите первый элемент <stage>. А затем повторите все элементы, следующие за этим первым элементом <stage>. В конце концов, когда в списке дочерних элементов не останется <stage>, все оставшиеся элементы выводятся в результирующем дереве внутри нового <sp>. Вот код, включая отладку <xsl:message>s:

<xsl:template name="sp-with-stage">
  <!-- call with speaker -->
  <xsl:param name="speaker" />
  <!-- call with an <sp> element -->
  <xsl:param name="sp" />
  <!-- $content parameter is optional, by default it's the children of the given $sp; this is the parameter whose value is different with each recursive call -->
  <xsl:param name="content" select="$sp/*" />
  <!-- find the first <stage> element amongst the $content node set -->
  <xsl:variable name="stage" select="$content/following-sibling::stage[1]" />

  <xsl:message>ID = <xsl:value-of select="$sp/@xml:id" /></xsl:message>
  <xsl:message>speaker = "<xsl:value-of select="$speaker" />"</xsl:message>
  <xsl:message>content length = <xsl:value-of select="count($content)" /></xsl:message>
  <xsl:if test="$stage">
  <xsl:message>nodes before $stage = <xsl:value-of select="count($stage/preceding-sibling::*)" /></xsl:message>
  <xsl:message>nodes after $stage = <xsl:value-of select="count($stage/following-sibling::*)" /></xsl:message>
  </xsl:if>

  <xsl:if test="$stage">
    <sp by="#{$speaker}">
      <!-- process all the nodes in the $content node set before the current <stage> -->
      <xsl:message>Processing <xsl:value-of select="count($stage/preceding-sibling::*)" /> nodes before "<xsl:value-of select="$stage/text()" />"</xsl:message>
      <xsl:apply-templates select="$stage/preceding-sibling::*" />
    </sp>
    <xsl:apply-templates select="$stage" />
  </xsl:if>
  <xsl:choose>
    <xsl:when test="$stage/following-sibling::stage">
      <!-- if there's another <stage> element in the $content node set then call this template recursively -->
      <xsl:message>Call recursively with <xsl:value-of select="count($stage/following-sibling::*)" /> following nodes</xsl:message>
      <xsl:call-template name="sp-with-stage">
        <xsl:with-param name="speaker"><xsl:value-of select="$speaker" /></xsl:with-param>
        <xsl:with-param name="sp" select="$sp" />
        <!-- the $content node set for this call is all the nodes after the current <stage> -->
        <xsl:with-param name="content" select="$stage/following-sibling::*" />
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="$stage/following-sibling::*">
      <!-- if there's no <stage> element in the $content node set, but there are still some elements, emit them in an <sp> element -->
      <sp by="#{$speaker}">
        <xsl:message>Processing <xsl:value-of select="count($stage/following-sibling::*)" /> trailing nodes</xsl:message>
        <xsl:apply-templates select="$stage/following-sibling::*" />
      </sp>
    </xsl:when>
  </xsl:choose>
</xsl:template>

Затем этот шаблон вызывается следующим образом:

<xsl:template match="sp[stage]">
  <xsl:call-template name="sp-with-stage">
    <xsl:param name="speaker"><xsl:value-of select="speaker" /></xsl:param>
    <xsl:param name="sp" select="." />
  </xsl:call-template>
</xsl:template>

Проблема заключается в том, что я использую $stage/preceding-sibling::*, под которым я подразумеваю обработку только узлов из текущего набора узлов $content, которые предшествуют текущему узлу $stage. На самом деле происходит следующее: при каждом рекурсивном вызове все узлы, предшествующие текущему $stage узлу из его исходного <sp> контекста, выбираются этим $stage/preceding-sibling::*. И это несмотря на то, что рекурсивные вызовы каждый раз получают правильный новый набор узлов $content и что узел $stage берется из этого правильного набора узлов $content.

Чтобы уточнить, в случае приведенного выше примера XML, когда <stage>To Antony</stage> является узлом $stage, а узел $content содержит только:

<l>consectetur adipisicing elit</l>
<stage>To Antony</stage>
<l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>

выражение $stage/preceding-sibling::* по-прежнему дает все дочерние элементы исходного <sp> до <stage>To Antony</stage>.

Я думаю, должно быть что-то в preceding-sibling, чего я не совсем понимаю. Какие-либо предложения? Или даже какие-либо предложения совершенно разных способов достижения трансформации?

21.03.2014

  • Вы уверены, что хотите это сделать? Ваша структура уже далеко не идеальна; показанный вами результат будет очень определять, кто является текущим говорящим. В любом случае, было бы полезно увидеть больше исходного кода XML. 21.03.2014
  • Вам действительно нужно сказать, хотите ли вы решение XSLT 1.0 или 2.0. Скорее всего, они будут совсем другими. 21.03.2014
  • @ michael.hor257k Я понимаю, что требуемый вывод кажется не очень хорошим, но я работаю с чужой схемой, которая неизменяема. 21.03.2014
  • @MichaelKay Извините, да, это XSLT 1.0, так как мое приложение использует libxslt 21.03.2014
  • @ michael.hor257k Я обновил пример XML, как исходный, так и требуемый вывод. Обратите внимание, что исходные <sp> могут содержать больше, чем просто <l> элементов, включая любое количество элементов. Также обратите внимание, что каждый результат <sp> должен включать все элементы между <stage>. 21.03.2014
  • @ michael.hor257k Также обратите внимание, что я забыл указать в тексте вопроса, что каждый результат <sp> будет иметь атрибут @by, содержащий имя говорящего; это в настоящее время работает в моем преобразовании. 21.03.2014

Ответы:


1

Это проблема группировки — вы хотите сгруппировать все элементы внутри каждого sp (кроме speaker и stage) по их ближайшему предшествующему stage (если он есть). Стандартный подход к этому в XSLT 1.0 называется группировка по Мюнху. Вы определяете ключ, задающий критерии группировки, а затем используете прием generate-id для обработки первого узла в каждой группе в качестве прокси для группы в целом.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes" />

  <!-- group first by the parent sp and then by the nearest preceding stage.
       generate-id(emptynodeset) is the empty string by definition, so this
       is still well defined for the elements before the first stage in an sp -->
  <xsl:key name="groupKey" match="sp/*[not(self::speaker | self::stage)]" use="
     concat(generate-id(..), '|', generate-id(preceding-sibling::stage[1]))" />

  <!-- identity template - copy everything as-is unless overridden -->
  <xsl:template match="@*|node()">
    <xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
  </xsl:template>

  <xsl:template match="sp">
    <!-- for each group -->
    <xsl:for-each select="*[generate-id() = generate-id(key('groupKey',
          concat(generate-id(..), '|', generate-id(preceding-sibling::stage[1]))
        )[1])]">
      <!-- the "stage" if there is one - if we are before the first stage in this
           sp then the preceding-sibling:: will select nothing -->
      <xsl:apply-templates select="preceding-sibling::stage[1]" />
      <sp by="#{../speaker}">
        <!-- the following elements up to the next stage -->
        <xsl:apply-templates select="key('groupKey',
          concat(generate-id(..), '|', generate-id(preceding-sibling::stage[1]))
        )" />
      </sp>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

Это работает для вашего примера ввода, но может потребовать некоторых изменений, если есть какие-либо случаи, когда у вас есть два последовательных элемента stage, между которыми ничего нет.

21.03.2014

2

Я подозреваю, что вы делаете это намного сложнее, чем нужно. Взгляните на следующую таблицу стилей:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="sp">
    <xsl:copy>
        <xsl:copy-of select="speaker"/>
        <xsl:copy-of select="l[1]"/>
    </xsl:copy>
    <xsl:apply-templates select="stage | l[position() > 1]"/>
</xsl:template>

<xsl:template match="l">
    <sp>
        <xsl:copy-of select="."/>
    </sp>
</xsl:template>

</xsl:stylesheet>

Применительно к следующему примеру ввода:

<root>
    <sp id="sp1">
      <speaker>Julius</speaker>
      <l>Lorem ipsum dolor sit amet</l>
      <stage>Aside</stage>
      <l>consectetur adipisicing elit</l>
      <stage>To Antony</stage>
      <l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
    </sp>
    <sp id="sp2">
      <speaker>Antony</speaker>
      <l>Nullam at dui.</l>
      <stage>Front</stage>
      <l>Nunc lobortis. </l>
    </sp>
</root>

результат:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <sp>
      <speaker>Julius</speaker>
      <l>Lorem ipsum dolor sit amet</l>
   </sp>
   <stage>Aside</stage>
   <sp>
      <l>consectetur adipisicing elit</l>
   </sp>
   <stage>To Antony</stage>
   <sp>
      <l>sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</l>
   </sp>
   <sp>
      <speaker>Antony</speaker>
      <l>Nullam at dui.</l>
   </sp>
   <stage>Front</stage>
   <sp>
      <l>Nunc lobortis. </l>
   </sp>
</root>
21.03.2014
  • Это решение, по-видимому, предполагает, что каждый <l> преобразуется ровно в один <sp>. К сожалению, это не то, что мне нужно. Я попытался сделать пример более понятным; это больше похоже на произвольные элементы, разделенные на <stage>s. 21.03.2014
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..