Объединение непустых элементов с одинаковым идентификатором в один элемент с использованием XSLT - PullRequest
0 голосов
/ 09 мая 2011

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

Образец XML-ввода:

<?xml version="1.0" encoding="UTF-8" ?>
<shiporder orderid="orderid106">
   <orderperson id="123">orderperson107</orderperson>
   <shipto>
      <name>name108</name>
      <address>address109</address>
      <city>city110</city>
      <country>country111</country>
   </shipto>

   <!--Item Group 100-->
   <item>
      <id>100</id>
      <title>
         <first>
            <a></a>
         </first>
         <last>item100_lastTitle1</last>
      </title>
      <note></note>
      <quantity></quantity>
   </item>
   <item>
      <id>100</id>
      <title>
         <first>
            <a>a_100_2</a>
         </first>
         <last>item100_lastTitle2</last>
      </title>
      <note>note100_2</note>
      <quantity></quantity>
   </item>
   <item>
      <id>100</id>
      <title>
         <first>
            <a att1="abc" att2='cde'>a_100_3</a>
         </first>
         <last id="1" attr="2">item100_lastTitle3</last>
      </title>
      <note>note100_3</note>
      <quantity>1</quantity>
   </item>

   <!--Item Group 101-->
   <item>
      <id>101</id>
      <title>
         <first>
            <a>a_101_1</a>
         </first>
         <last></last>
      </title>
      <note>note101_1</note>
      <quantity>10</quantity>
   </item>
   <item>
      <id>101</id>
      <title>
         <first>
            <a>a_101_2</a>
         </first>
         <last>item101_lastTitle2</last>
      </title>
      <note>note101_2</note>
      <quantity>5</quantity>
   </item>

   <!--Item Group 103-->
   <item>
      <id>103</id>
      <title>
         <first>
            <a>a_103_2</a>
         </first>
         <last>item103_lastTitle2</last>
      </title>
      <note>note103_1</note>
      <quantity></quantity>
   </item>
</shiporder>

ОбразецВывод XML:

<?xml version = '1.0' encoding = 'UTF-8'?>
<shiporder orderid="orderid106">
  <item>
    <id>100</id>
    <title>
      <first>
        <a>a_100_2</a>
      </first>
      <last>item100_lastTitle1</last>
    </title>
    <note>note100_2</note>
    <quantity>6</quantity>
  </item>

  <item>
    <id>101</id>
    <title>
      <first>
        <a>a_101_1</a>
      </first>
      <last>item101_lastTitle2</last>
    </title>
    <note>note101_1</note>
    <quantity>10</quantity>
  </item>

  <item>
    <id>103</id>
    <title>
      <first>
        <a>a_103_2</a>
      </first>
      <last>item103_lastTitle2</last>
    </title>
    <note>note103_1</note>
  </item>
</shiporder>

Я попытался с помощью следующего XSL-кода получить вышеуказанный вывод:

<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:template match="/shiporder">
      <shiporder>
         <xsl:if test="@orderid">
            <xsl:attribute name="orderid">
               <xsl:value-of select="@orderid"/>
            </xsl:attribute>
         </xsl:if>

         <!-- Grouping each item based on its ID element value-->
         <xsl:for-each-group select="item" group-by="id">
            <item-group>
               <!-- For Each group search for first non empty child element-->
               <xsl:for-each select="current-group()[id/text()][1]">
                  <xsl:copy-of select="id"/>
               </xsl:for-each>
               <title>
                  <first>
                     <!-- for 'a' element -->
                     <xsl:variable name="temp1">
                        <xsl:for-each select="current-group()/title/first[a/text()][1]">
                           <elm>
                              <xsl:copy-of select="a"/>
                           </elm>
                        </xsl:for-each>
                     </xsl:variable>
                     <xsl:copy-of select="$temp1/elm[1]/a"/>
                  </first>

                  <!-- for 'last' element -->
                  <xsl:variable name="temp2">
                     <xsl:for-each select="current-group()/title[last/text()][1]">
                        <elm>
                           <xsl:copy-of select="last"/>
                        </elm>
                     </xsl:for-each>
                  </xsl:variable>
                  <xsl:copy-of select="$temp2/elm[1]/last"/>
               </title>

               <!-- for 'note' element -->
               <xsl:for-each select="current-group()[note/text()][1]">
                  <xsl:copy-of select="note"/>
               </xsl:for-each>

               <!-- for 'quantity' element -->
               <xsl:for-each select="current-group()[quantity/text()][1]">
                  <xsl:copy-of select="quantity"/>
               </xsl:for-each>

            </item-group>
         </xsl:for-each-group>
      </shiporder>
   </xsl:template>
</xsl:stylesheet>

Код работает нормально, но хуже всего то, что я не смог его сделатьобобщать.Для каждого элемента в элементе мне нужно написать xsl-код, специфичный для xpath конкретного элемента.Я даже не мог написать код в терминах пользовательских шаблонов.

Я нашел уже отвеченный вопрос в заголовке " Рекурсивно комбинировать идентичные элементы в XSLT ".Но я не могу настроить его в соответствии с моими требованиями.

У меня есть сотни таких дочерних или наследственных элементов под каждым элементом, и глубина каждого элемента произвольна.Очень смешно писать xsl-код, соответствующий каждому из этих потомков или потомков.Может ли кто-нибудь из экспертов помочь мне написать общий xsl-код (независимо от количества элементов, а также его глубины) или оптимизировать существующий код?

Заранее спасибо,Касир

1 Ответ

0 голосов
/ 17 мая 2011

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

<?xml version="1.0" encoding="windows-1252" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="item" use="id" match="item" />

  <xsl:template match="item[generate-id() = generate-id(key('item',id)[1])]">
    <xsl:call-template name="combine">
      <xsl:with-param name="list" select="../*[id = current()/id]" />
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="item" />

  <xsl:template name="combine">
    <xsl:param name="list" />
    <xsl:element name="{name($list[1])}">
      <xsl:for-each select="*[not(preceding-sibling::*[name() = name(current())])]" >
        <xsl:call-template name="combine">
          <xsl:with-param name="list" select="$list/*[name() = name(current())]" />
        </xsl:call-template>
      </xsl:for-each>
      <xsl:value-of select="$list[text()][1]/text()" />
    </xsl:element>
  </xsl:template>

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

Шаблон combine берет список узлов и рекурсивно объединяет их в один, принимая первое доступное значение для каждого узла.Этот шаблон также НЕ объединяет атрибуты, но, поскольку в элементе XML, который вы указали, у вас его нет, все должно быть в порядке.<xsl:for-each в середине шаблона использует технику XSLT1 для выбора уникальных имен элементов;он выбирает только тех, у кого нет предыдущего брата с тем же именем.Вероятно, есть способ сделать это с помощью for-each-group в XSLT2, но я не могу проверить это здесь.

...