Проблема с циклом преобразования XSL в XML

#xml #xslt

#xml #xslt

Вопрос:

Мне нужно выполнить цикл XML-документа. Никаких проблем нет.
Проблема возникает, когда мне нужен текст из предыдущей строки, которую я только что пропустил.

XML будет выглядеть примерно так

 <lines>
  <line>
    <id>1</id>
    <text>Some fancy text here 1</text>
  </line>
  <line>
    <id></id>
    <text>This I need in the next line with a ID</text>
  </line>
  <line>
    <id></id>
    <text>Also need this.</text>
  </line>
  <line>
    <id>4</id>
    <text>Here we go</text>
  </line>
</lines>
  

Выходной XML-файл должен выглядеть следующим образом

 <output>
  <line>
    <id>1</id>
    <note>Some fancy text here 1</note>
  </line>
  <line>
    <id>4</id>
    <note>Here we go</note>
    <extra>
      <note>This I need in the next line with a ID</note>
      <note>Also need this.</note>
    </extra>
  </line>
</output>
  

XSL, который у меня есть, позволяет просто сортировать строки, для которых не установлен идентификатор.

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="/">
    <output>
    <xsl:for-each select="lines/line">
      <xsl:if test="id/text() amp;> 0">
        <line>
          <id>
            <xsl:value-of select="id" />
          </id>
          <note>
            <xsl:value-of select="text" />
          </note>
        </line>
      </xsl:if>
    </xsl:for-each>
    </output>
  </xsl:template>
</xsl:stylesheet>
  

Комментарии:

1. xsl:for-each Это не цикл. Это назначение функции для каждого соответствия вашего select .

Ответ №1:

Вам нужно выбрать предыдущий-sibling; здесь есть пример.

В основном синтаксис xpath для вас был бы чем-то вроде (не тестировался):

 preceding-sibling::NodeName[1]
  

Комментарии:

1. Этот xpath собирается получить первый prceding-sibling (чего??). Кажется, что для OP нужны все предшествующие смежные братья и сестры. Не совсем ясно также, как применить это к текущему варианту использования.

Ответ №2:

Я не удержался от того, чтобы полностью просмотреть ваш код 🙂

Далее следует полное решение XSLT 1.0, более функционально ориентированное (без процедурного подхода). На первый взгляд может показаться, что это сложнее увидеть, но, имхо, это очень хороший пример для начала работы с механизмом создания шаблонов XSLT.

Также использовать xsl:for-each в вашем конкретном случае не так просто, потому что на определенном шаге цикла вы хотите получить все предыдущие смежные дочерние элементы с пустым значением id , не зная, сколько их априори.

Я также использовал шаблон идентификации, чтобы упростить работу по воссозданию вашей цели.

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

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

    <!-- match line with empty id and do not output -->
    <xsl:template match="line[not(boolean(id/text()))]"/>

    <!-- match line with id and build output -->
    <xsl:template match="line[boolean(id/text())]">
        <xsl:copy>
            <xsl:copy-of select="id"/>
            <xsl:apply-templates select="text"/>
            <extra>
                <!-- apply recursive template to the first preceding 
                sibling adajacent node with empty id -->
                <xsl:apply-templates select="(preceding-sibling::*[1])
                    [name()='line' and not(boolean(id/text()))]/text" 
                    mode="extra"/>
            </extra>
        </xsl:copy>
    </xsl:template>

    <!-- change text element to note --> 
    <xsl:template match="text">
        <note>
            <xsl:value-of select="."/>
        </note>
    </xsl:template>

    <!-- recursive template for extra note elements -->
    <xsl:template match="text" mode="extra">
        <note>
            <xsl:value-of select="."/>
        </note>
        <xsl:apply-templates select="(parent::line/preceding-sibling::*[1])
            [name()='line' and not(boolean(id/text()))]/text" 
            mode="extra"/>
    </xsl:template>

</xsl:stylesheet>
  

Примененный к вашему вводу, выдает:

 <?xml version="1.0" encoding="UTF-8"?>
<lines>
   <line>
      <id>1</id>
      <note>Some fancy text here 1</note>
      <extra/>
   </line>
   <line>
      <id>4</id>
      <note>Here we go</note>
      <extra>
         <note>Also need this.</note>
         <note>This I need in the next line with a ID</note>
      </extra>
   </line>
</lines>
  

Комментарии:

1. Это отлично работает. Возможно, я немного поторопился с первым примером, поскольку после id 4 у меня могут появиться еще несколько. Может быть несколько строк с номером после каждой другой, и всем этим нужно добавить те же две строки в дополнительное поле. Я буду с этим бороться. Еще раз спасибо