Мы уже рассматривали задачу группировки, когда разбирали устройство и функционирование ключей — это была та самая задача, в которой из документа вида:
Пример 11.1. Входящий документ
<items> <item source="a" name="A"/> <item source="b" name="B"/> <item source="a" name="C"/> <item source="c" name="D"/> <item source="b" name="E"/> <item source="b" name="F"/> <item source="c" name="G"/> <item source="a" name="H"/> </items>
нужно было получить документ вида:
Пример 11.2. Требуемый результат
<sources>
<source name="a">
<item source="a" name="A"/>
<item source="a" name="C"/>
<item source="a" name="H"/>
</source>
<source name="b">
<item source="b" name="B"/>
<item source="b" name="E"/>
<item source="b" name="F"/>
</source>
<source name="c">
<item source="c" name="D"/>
<item source="c" name="G"/>
</source>
</sources>
Легко понять, почему такая задача называется задачей группировки: требуется сгруппировать элементы item по значениям одного из своих атрибутов.
Напомним вкратце решение, которое было тогда предложено. При обработке первого объекта каждой группы мы создавали элемент source, в который включали все элементы item, принадлежащие этой группе. Для определения первого элемента мы использовали выражение
preceding-sibling::item[@source=current()/@source]
которое возвращало непустое множество только тогда, когда элемент не был первым в группе.
В этом разделе мы приведём гораздо более эффективное и остроумное решение задачи группировки, впервые предложенное Стивом Мюнхом (Steve Muench), техническим гуру из Oracle Corporation. Оно основывается на двух посылках:
Мы можем выбрать множество узлов по их свойствам при помощи ключей.
Мы можем установить, является ли узел первым узлом множества в порядке просмотра документа при помощи функции generate-id.
С первым пунктом всё, пожалуй, ясно — выбор множества узлов по определённому критерию — это самое прямое предназначение ключей. Второй же пункт оставляет лёгкое недоумение: функция generate-id вроде бы предназначена только для генерации уникальных значений.
Для того чтобы развеять все сомнения, напомним, как ведёт себя эта функция, если аргументом является множество узлов. В этом случае generate-id возвращает уникальный идентификатор первого в порядке просмотра документа узла переданного ей множества. Значит для того, чтобы проверить, является ли некий узел первым узлом группы, достаточно сравнить его уникальный идентификатор со значением выражения generate-id($group), где $group — множество узлов этой группы.
С учётом приведённых выше возможностей группирующее преобразование переписывается удивительно элегантным образом.
Пример 11.3. Группирующее преобразование
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="src" match="item" use="@source"/>
<xsl:template match="items">
<sources>
<xsl:apply-templates
select="item[generate-id(.)=generate-id(key('src',@source))]"/>
</sources>
</xsl:template>
<xsl:template match="item">
<source name="{@source}">
<xsl:copy-of select="key('src',@source)"/>
</source>
</xsl:template>
</xsl:stylesheet>
Результат выполнения этого преобразования уже был приведён в листинге 11.2.