Объединение соседних узлов одного типа (XSLT 1.0)

Можно ли объединить каждую последовательность узлов одного и того же указанного типа? (в данном случае 'aaa') (не только первое вхождение последовательности)

Вот мой ввод XML:

<block>
    <aaa>text1</aaa>
    <aaa>text2</aaa>
    <aaa><xxx>text3</xxx></aaa>
    <bbb>text4</bbb>
    <aaa>text5</aaa>
    <bbb><yyy>text6</yyy></bbb>
    <bbb>text7</bbb>
    <aaa>text8</aaa>
    <aaa><zzz>text9</zzz></aaa>
    <aaa>texta</aaa>
</block>

И я хочу следующий вывод:

<block>
    <aaa>text1text2<xxx>text3</xxx></aaa>
    <bbb>text4</bbb>
    <aaa>text5</aaa>
    <bbb><yyy>text6</yyy></bbb>
    <bbb>text7</bbb>
    <aaa>text8<zzz>text9</zzz>texta</aaa>
</block>

Любая помощь приветствуется


person Peter    schedule 26.11.2009    source источник


Ответы (3)


Вот еще один способ сделать это.

Сначала сопоставьте все дочерние узлы блочного элемента.

<xsl:template match="block/child::*">

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

<xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">

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

<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>

Все это вместе дает

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

   <!-- Match children of the block element -->
   <xsl:template match="block/child::*">
      <xsl:variable name="name" select="local-name()"/>

      <!-- Is this the first element in a sequence? -->
      <xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">
         <xsl:copy>
            <xsl:apply-templates />

            <!-- Match the next sibling if it has the same name -->
            <xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
         </xsl:copy>
      </xsl:if>
   </xsl:template>

   <!-- Recursive template used to match the next sibling if it has the same name -->
   <xsl:template match="block/child::*" mode="next">
      <xsl:variable name="name" select="local-name()"/>
         <xsl:apply-templates />
      <xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
   </xsl:template>

   <!-- Template used to copy a generic node -->
   <xsl:template match="@* | node()">
         <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
         </xsl:copy>
   </xsl:template>
</xsl:stylesheet>
person Tim C    schedule 27.11.2009
comment
Для решения моей проблемы мне нужно было только объединить узлы «aaa», поэтому мне пришлось немного изменить ваше решение, изменив «match=int:block/child::*» на «match=int:block/child::aaa» в остальном работает безотказно. - person Peter; 27.11.2009

Предполагая, что у вас есть только один block, Мюнхенский метод является наиболее оптимизированным способом это:

<!-- group nodes by name -->
<xsl:key name="block-children-by-name" match="block/*" use="name()"/>

<!-- for nodes that aren't first in their group, no output -->
<xsl:template match="block/*" />

<!-- for nodes that are first in their group, combine group children and output -->
<xsl:template match="block/*[generate-id() =
                             generate-id(key('block-children-by-name', name())[1])]">
   <xsl:copy>
     <xsl:copy-of select="key('block-children-by-name', name())/*"/>
   </xsl:copy>
</xsl:template>

Обратите внимание, что это объединяет только дочерние узлы, а не, например. любые атрибуты, которые могут встречаться в самих aaa и bbb.

person Pavel Minaev    schedule 26.11.2009

Вот еще один подход без использования рекурсивных шаблонов.

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

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

  <xsl:template match="aaa">
    <xsl:if test="not(preceding-sibling::*[1]/self::aaa)">
      <xsl:variable name="following" 
                    select="following-sibling::aaa[
                              not(preceding-sibling::*[
                                not(self::aaa) and
                                not(following-sibling::aaa = current())
                              ])
                            ]"/>
      <xsl:copy>
        <xsl:apply-templates select="$following/@*"/>
        <xsl:apply-templates select="@*"/>
        <xsl:apply-templates select="node()"/>
        <xsl:apply-templates select="$following/node()"/>
      </xsl:copy>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Довольно запутанное выражение XPath для выбора следующих одноуровневых узлов aaa, которые объединяются с текущим:

following-sibling::aaa[                       # following 'aaa' siblings
  not(preceding-sibling::*[                   #   if they are not preceded by
    not(self::aaa) and                        #     a non-'aaa' node
    not(following-sibling::aaa = current())   #     after the current node
  ])
]
person Jukka Matilainen    schedule 27.11.2009