Как создать новый список и пометить в нем узлы с помощью xslt

#list #xslt

#Список #xslt

Вопрос:

Я преобразую XML в другой, используя преобразование идентификаторов, и во время этого, основываясь на условии, я хочу пометить несколько узлов в новом списке. Предположим, у меня есть XML типа:
<nod>
<a> A</a>
<b> B</b>
<c><p> p1</p></c>
<c><p> p2 </p></c>
<c><p> p3</p></c>
<c><p> p4 </p></c>
</nod>

Из этого XML я хочу обновить имя ‘nod’ на ‘newnod’ и создать упорядоченный список для элемента ‘c’ ; так что выходные данные будут выглядеть следующим образом:
<newnod>
<a> A</a>
<b>B</b>
<orderedlist>
<listitem><p> p1</p></listitem>
<listitem><p> p2</p></listitem>
<listitem><p>p3</p ></listitem>
<listitem><p>p4</p></listitem>
</упорядоченный список>
</newnod>

Кто-нибудь, пожалуйста, может сказать мне, как это сделать.

Спасибо!!!

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

1. Вы хотите преобразовать все <c> элементы, или вы хотите преобразовать все элементы, количество которых > 1?

2. Хороший вопрос, 1. Смотрите мое решение, которое полностью выполнено в «push-стиле».

Ответ №1:

Вот истинное решение в стиле push:

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

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

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

 <xsl:template match="c[1]">
  <orderedlist>
   <xsl:apply-templates mode="wrap"
    select=".|following-sibling::c"/>
  </orderedlist>
 </xsl:template>

 <xsl:template match="c" mode="wrap">
  <listitem>
   <xsl:apply-templates/>
  </listitem>
 </xsl:template>

 <xsl:template match="c"/>
</xsl:stylesheet>
  

когда это преобразование применяется к предоставленному XML-документу:

 <nod>
 <a>A</a>
 <b>B</b>
 <c><p>p1</p></c>
 <c><p>p2</p></c>
 <c><p>p3</p></c>
 <c><p>p4</p></c>
</nod>
  

получен желаемый, правильный результат:

 <newnod>
  <a>A</a>
  <b>B</b>
  <orderedlist>
    <listitem>
      <p>p1</p>
    </listitem>
    <listitem>
      <p>p2</p>
    </listitem>
    <listitem>
      <p>p3</p>
    </listitem>
    <listitem>
      <p>p4</p>
    </listitem>
  </orderedlist>
</newnod>
  

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

1. Привет, Дмитрий, хорошее решение, конечно. Проблема только в том, что OP подразумевает сортировку ряда c * (<упорядоченный список>), требование, которое не может быть согласовано с рекурсией родственного типа. С наилучшими пожеланиями, Майкл

2. @Michael-Ludwig: Спасибо за ваш комментарий. Я думаю, что имя элемента (orderedlist) не обязывает решателя текущей проблемы выполнять какую-либо сортировку — это не запрашивается OP, и элементы в предоставленном XML-документе уже отсортированы — они просто не помечены как таковые — и эта маркировка / перенос является сутью вопроса.

3. Похоже, вы правы. Я давно не сталкивался с такого рода проблемами XSLT, и ваше решение является отличным напоминанием для меня о том, как выглядит чистый код XSLT. Спасибо.

Ответ №2:

Это дает желаемый результат для вашего примера:

 <xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output indent="yes"/>

  <xsl:template match="/nod">
    <newnod>
      <xsl:copy-of select="*[not( self::c )]"/>
      <orderedlist>
        <xsl:apply-templates select="c">
          <xsl:sort/>
        </xsl:apply-templates>
      </orderedlist>
    </newnod>
  </xsl:template>

  <xsl:template match="nod/c">
    <listitem>
      <xsl:copy-of select="node()"/>
    </listitem>
  </xsl:template>

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

</xsl:stylesheet>
  

Обратите внимание, что для вашего фактического ввода может потребоваться некоторая настройка. Например, детали сортировки. Или последовательность a , b и c . И объединить все c вместе или нет? Но это работает для вашего примера.

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

1. Привет, Майкл, как ты сказал, это работает для этого примера. Но в случае, если после всех элементов <c> есть еще один элемент <d>, то элемент <d> в выходных данных помечается перед списком. Можете ли вы, пожалуйста, посоветовать, что мне делать в этом случае.

2. В этом случае вам нужно указать, используете ли вы XSLT 1.0 или 2.0? В версии 1.0 вы бы прибегли к одноранговой рекурсии, тогда как в версии 2.0 вы бы использовали средство группировки.

3. @Piyush: Проверьте ответ @Dimitre и мой для более общих шаблонов.

Ответ №3:

Вы могли бы использовать что-то вроде:

 <xsl:template match="nod">
    <newnod>
        <xsl:copy-of select="a"/>
        <xsl:copy-of select="b"/>
        <orderedlist>
            <xsl:for-each select="c">
                <listitem><p><xsl:value-of select="."/></p></listitem>
            </xsl:for-each>
        </orderedlist>
    </newnod>
</xsl:template>
  

Ответ №4:

Другой подход заключается в перемещении по следующей оси следующим образом:

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="nod">
        <newnod>
            <xsl:apply-templates select="node()[1]|@*"/>
        </newnod>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="c">
        <orderedlist>
            <xsl:call-template name="wrap"/>
        </orderedlist>
        <xsl:apply-templates select="following-sibling::node()[1]"
                             mode="search"/>
    </xsl:template>
    <xsl:template match="c" name="wrap" mode="wrap">
        <listitem>
            <xsl:apply-templates select="node()[1]|@*"/>
        </listitem>
        <xsl:apply-templates select="following-sibling::node()[1]"
                             mode="wrap"/>
    </xsl:template>
    <xsl:template match="node()" mode="wrap"/>
    <xsl:template match="c" mode="search">
        <xsl:apply-templates select="following-sibling::node()[1]"
                             mode="search"/>
    </xsl:template>
    <xsl:template match="node()" mode="search">
        <xsl:apply-templates select="."/>
    </xsl:template>
</xsl:stylesheet>
  

Или короче (более «нажимной стиль»):

 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()[1]|@*"/>
        </xsl:copy>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="nod">
        <newnod>
            <xsl:apply-templates select="node()[1]|@*"/>
        </newnod>
        <xsl:apply-templates select="following-sibling::node()[1]"/>
    </xsl:template>
    <xsl:template match="c">
        <orderedlist>
            <xsl:call-template name="wrap"/>
        </orderedlist>
        <xsl:apply-templates select="following-sibling::node()
                                        [not(self::c)][1]"/>
    </xsl:template>
    <xsl:template match="c" name="wrap" mode="wrap">
        <listitem>
            <xsl:apply-templates select="node()[1]|@*"/>
        </listitem>
        <xsl:apply-templates select="following-sibling::node()[1]
                                        /self::c"
                             mode="wrap"/>
    </xsl:template>
</xsl:stylesheet>
  

Оба вывода:

 <newnod>
    <a>A</a>
    <b>B</b>
    <orderedlist>
        <listitem>
            <p>p1</p>
        </listitem>
        <listitem>
            <p>p2</p>
        </listitem>
        <listitem>
            <p>p3</p>
        </listitem>
        <listitem>
            <p>p4</p>
        </listitem>
    </orderedlist>
</newnod>
  

Примечание: Даже с XSLT 2.0 некоторые сложные последовательности обработки выражаются лучше с помощью этого шаблона.