Группировка XSLT 1.0 на одном или нескольких уровнях

Сегодняшней задачей было группирование в XSLT 1.0. Обнаружил нечто, называемое ключами и мюнхианской группировкой.

Входной XML:

<Items>
    <Item>
        <ID>1</ID>
        <Name>A</Name>
        <Country>Sweden</Country>
        <Region>Småland</Region>
    </Item>
    <Item>
        <ID>2</ID>
        <Name>B</Name>
        <Country>Sweden</Country>
        <Region>Norrland</Region>
    </Item>
    <Item>
        <ID>3</ID>
        <Name>C</Name>
        <Country>USA</Country>
        <Region>Alaska</Region>
    </Item>
    <Item>
        <ID>4</ID>
        <Name>D</Name>
        <Country>USA</Country>
        <Region>Texas</Region>
    </Item>
    <Item>
        <ID>5</ID>
        <Name>E</Name>
        <Country>Sweden</Country>
        <Region>Norrland</Region>
    </Item>
</Items>

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

<Items>
  <Country Name="Sweden">
    <Region Name="Norrland">
      <Item>
        <ID>2</ID>
        <Name>B</Name>
      </Item>
      <Item>
        <ID>5</ID>
        <Name>E</Name>
      </Item>
    </Region>
    <Region Name="Småland">
      <Item>
        <ID>1</ID>
        <Name>A</Name>
      </Item>
    </Region>
  </Country>
  <Country Name="USA">
    <Region Name="Alaska">
      <Item>
        <ID>3</ID>
        <Name>C</Name>
      </Item>
    </Region>
    <Region Name="Texas">
      <Item>
        <ID>4</ID>
        <Name>D</Name>
      </Item>
    </Region>
  </Country>
</Items>

ИЗМЕНИТЬ:

Я также хочу убедиться, что регионы оказываются в своей стране, даже если есть дубликаты. Я отредактировал ответ соответственно.

Кроме того, я хотел бы намекнуть на xsltfiddle.liberty-development.net как на простой способ занимаюсь разработкой XSLT методом проб и ошибок...


person JERKER    schedule 29.03.2019    source источник


Ответы (1)


Вдохновленный этой статьей, я нашел изящное решение к этой проблеме:

Я включил комментарии для его использования для одинарной или двойной группировки, см. комментарии в коде. Обратите внимание, как я использую первый ключ (индекс) в качестве входных данных для второго цикла for-each:

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

  <xsl:key name="country" match="Item" use="Country" />
  <xsl:key name="region" match="Item" use="concat(Region, '|', Country)" />

  <xsl:template match="/Items">
    <Items>
      <xsl:for-each select="Item[generate-id(.) = generate-id(key('country', Country))]">
        <xsl:sort select="Country" />
        <xsl:variable name="_country" select="Country" />

        <xsl:element name="Country">
          <xsl:attribute name="Name"><xsl:value-of select="$_country" /></xsl:attribute>

          <!-- single level grouping -->
          <!--<xsl:apply-templates select="key('country', Country)" />-->


          <!-- double grouping -->
          <!-- START -->
          <xsl:for-each select="key('country', Country)[generate-id(.) = generate-id(key('region', concat(Region, '|', Country)))]">
              <xsl:sort select="Region" />
              <xsl:variable name="_region" select="Region" />

              <xsl:element name="Region">
                <xsl:attribute name="Name"><xsl:value-of select="$_region" /></xsl:attribute>

                <xsl:apply-templates select="key('region', concat(Region, '|', Country))" />
              </xsl:element>
          </xsl:for-each>
          <!-- END -->

        </xsl:element>    
      </xsl:for-each>
    </Items>
  </xsl:template>

  <xsl:template match="Item">
    <xsl:element name="Item">
      <xsl:element name="ID"><xsl:value-of select="ID" /></xsl:element>
      <xsl:element name="Name"><xsl:value-of select="Name" /></xsl:element>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>
person JERKER    schedule 29.03.2019
comment
Это не выглядит правильно. Если вы хотите сгруппировать регионы внутри страны, ваш второй ключ должен быть <xsl:key name="region" match="Item" use="concat(Region, '|', Country)"/>. См. аналогичный пример здесь: в xsl 1 0"> stackoverflow.com/questions/54478865/ То есть, если вы не можете быть уверены, что имя каждого региона уникально. - person michael.hor257k; 29.03.2019
comment
@ michael.hor257k Спасибо за вклад! Я думал об этом по дороге домой... и вы подтвердили мои мысли и предложили решение! Спасибо! Я отредактирую ответ, когда проверю его. - person JERKER; 01.04.2019
comment
Я протестировал и обновил решение, чтобы убедиться, что регионы попадают в свою страну, даже если есть дубликаты. - person JERKER; 01.04.2019