Xpoint
   [напомнить пароль]

Группировка

Оглавление

Постановка задачи

Дано

<?xml version="1.0"?>
<list>
  <item id="aa" group="x"/>
  <item id="bb" group="y"/>
  <item id="ab" group="x"/>
  <item id="ba" group="z"/>
  <item id="cc" group="y"/>
  <item id="ac" group="y"/>
  <item id="ca" group="x"/>
  <item id="dc" group="x"/>
  <item id="ad" group="z"/>
  <!-- ... -->
</list>

Требуется получить

<?xml version="1.0"?>
<group-list>
  <group name="x">
    <item name="aa"/>
    <item name="ab"/>
    <item name="ca"/>
    <item name="dc"/>
  </group>
  <group name="y">
    <item name="bb"/>
    <item name="cc"/>
    <item name="ac"/>
  </group>
  <group name="z">
    <item name="ba"/>
    <item name="ad"/>
  </group>
</group-list>

Т.е. нужно сгруппировать узлы по некоторому признаку.

Стандартное решение: группировка Мюнха.

Метод Мюнха основан на том факте, что при передаче функции generate-id множества узлов, она возвращает id первого из них. Значит, если она вернула id текущего узла, то он — первый в этом множестве:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
  exclude-result-prefixes="xsl"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 >

<xsl:output method="xml" indent="yes"/>
<xsl:key name="group" match="item" use="@group"/>

<xsl:template match="list">
  <group-list>
    <xsl:apply-templates 
      select="item[generate-id(.) = generate-id(key('group',@group))]"
    />
  </group-list>
</xsl:template>
  
<xsl:template match="item">
  <group name="{@group}">
    <xsl:for-each select="key('group',@group)">
      <item name="{@id}"/>
    </xsl:for-each>
  </group>
</xsl:template>

</xsl:stylesheet>

Решение в XSLT 2.0

Задача столь распространена, что в XSLT 2.0 для этих целей был введен специальный элемент xsl:for-each-group. С его использованием задача становитя совсем простой:

<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
  exclude-result-prefixes="xsl"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 >

<xsl:output method="xml" indent="yes"/>

<xsl:template match="list">
  <group-list>
    <xsl:for-each-group select="item" group-by="@group">
      <group name="{@group}">
        <xsl:for-each select="current-group()">
          <item name='{@id}'/>
        </xsl:for-each>
      </group>
    </xsl:for-each-group>
  </group-list>
</xsl:template>

</xsl:stylesheet>

Вообще говоря, возможности xsl:for-each-group гораздо шире, чем позволяет видеть показанный здесь пример. Полностью они описаны в разделе «Grouping» спецификации.

Powered by POEM™ Engine Copyright © 2002-2005