</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 ($
, где $
— множество узлов этой группы.
С учетом приведенных выше возможностей группирующее преобразование переписывается удивительно элегантным образом.
<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.
Перечисление узлов
Функции name
и local-name
предоставляют возможности для работы с документом, имена элементов и атрибутов в котором заранее неизвестны. Например, если шаблон определен как:
<xsl:template match='*[starts-with(local-name(), 'чеб')]'>
...
</xsl:template>
то обрабатываться им будут все элементы, локальные части имен которых начинаются на 'чеб'
(например, 'чебуреки'
, 'Чебоксары'
, 'чебурашка'
).
Следующее преобразование демонстрирует, как при помощи функции local-name
и ключей сосчитать количество элементов и атрибутов документа с различными именами.
<foo bar='1'>
<bar foo='2'/>
<bar bar='3'/>
<foo foo='4'>
<bar bar='5'/>
<bar foo='6'/>
</foo>
</foo>
<xsl:stylesheet
version='1.0'
xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
<!-- Выводим информацию в текстовом виде -->
<xsl:output method='text'/>
<!--
| Создаем ключ, отображающий узлы атрибутов и элементов
| в их локальные части имен.
+-->
<xsl:key name='node' match='*' use='local-name()'/>
<xsl:key name='node' match='@*' use='local-name()'/>