Главная » XSLT » Для группировки пользуйтесь командой for-each-group, а не методом Мюнха

0

Задача

В XSLT 1.0 нет явной поддержки для группировки, поэтому приходится изоб­ретать косвенные и не слишком понятные методы.

Решение

Всякий раз, когда нужна группировка, пользуйтесь преимуществами весьма мощной команды xsl:for-each-group. У нее есть обязательный атрибут select, значением которого является выражение, отбирающее множество узлов, кото­рые нужно сгруппировать. Чтобы определить критерии разбиения этого множества на группы, задается один из четырех атрибутов группировки. Все они будут рассмотрены ниже. По ходу обработки вы можете пользоваться функцией current-group() для доступа ко всем узлам в текущей группе. Функция current-grouping-key() возвращает значение ключа, определяющего текущую группу, в случае, когда группировка осуществляется по значениям или по соседним узлам. Для группи­ровки по начальному или конечному узлу эта функция не возвращает никакого значения.

Группы можно сортировать, если вставить одну или несколько команд xsl:sort, определяющих критерии сортировки. Собственно, то же самое делает­ся и при использовании команды for-each.

Группировка по значениям (group-by="expression")

Классическая задача группировки часто возникает при генерировании отче­тов. Рассмотрим данные о продажах. Менеджер по продажам часто хочет сгруп­пировать данные по регионам, по типам продукции или по продавцам в зависимо­сти от стоящей перед ним задачи. Чтобы указать, по каким значениям группируются отобранные узлы, воспользуйтесь атрибутом group-by. Напри­мер, group-by="@dept" объединяет в одну группу узлы с одним и тем же значе­нием атрибута dept.

<xsl:template match="Employees"> <EmployeesByDept>

<xsl:for-each-group select="employee" group-by="@dept"> <dept name="{current-grouping-key()}">

<xsl:copy-of select="current-group"/> </dept> </xsl:for-each-group> </EmployeesByDept>

</xsl:template>

Группировка по соседним узлам (group-adjacent="expression")

В некоторых случаях, в частности, при обработке документов требуется рас­сматривать узлы, обладающие каким-то общим значением, при условии, что они также являются соседними. Как и group-by, атрибут group-adjacent опреде­ляет значение, по которому осуществляется группировка, однако два узла с таким значением будут входить в одну группу лишь, если они соседние. Значение атри­бута group-adjacent должно быть последовательностью, содержащей в точно­сти один элемент; пустая или многоэлементная последовательность приводит к ошибке.

Рассмотрим документ, состоящий из элементов para, перемежающихся с эле­ментами heading. Хотелось бы извлекать только элементы para, не теряя одна­ко информацию о том, что последовательность таких элементов принадлежит од­ному и тому же разделу:

<xsl:template match="doc"> <xsl:copy>

<xsl:for-each-group select="*" group-adjacent="name()"> <xsl:if test="self::para"> <topic>

<xsl:copy-of select="current-group()"/>

</topic> </xsl:if> </xsl:for-each-group> </xsl:copy>

</xsl:template>

Группировка по начальному узлу (group-starting-with="pattern")

Часто, особенно при обработке документов, группа взаимосвязанных узлов помечается специальным узлом, например title или subtitle, или каким-нибудь другим, обозначающим заголовок. Группировка по начальному узлу упрощает обработку таких слабо структурированных документов. Атрибут group- starting-with определяет образец, соответствующий узлам, которые отмеча­ют начало каждой группы. Здесь есть прямая аналогия с атрибутом match в ко­манде xsl:template. Если образец соответствует какому-то узлу в отобранном множестве, то в группу помещаются все следующие за ним узлы вплоть до следу­ющего соответствия. Первый узел в наборе начинает группу вне зависимости от того, соответствует он образцу или нет. Это гарантирует, что в множестве имеется по меньшей мере одна группа, даже если ни один узел не соответствует образцу.

Классический пример – наделение структурой неструктурированного доку­мента. XHTML может служить примером слабо структурированного языка раз­метки, особенно в части заголовочных элементов (hi, h2 и т.д.). Следующее пре­образование внесет в него некую структуру путем помещения внутрь элемента div каждой группы, начинающейся с элемента hi:

<xsl:template match="body"> <xsl:copy>

<xsl:for-each-group select="*" group-starting-with="h1">

<div>

<xsl:apply-templates select="current-group()"/> </div> </xsl:for-each-group> </xsl:copy>

</xsl:template>

Группировка по конечному узлу (group-ending-with="pattern")

Этот вид группировки аналогичен group-starting-with, но образец задается атрибутом group-ending-with и определяет последний узел в текущей группе. Первый узел в отобранном множестве начинает новую группу, поэтому по меньшей мере одна группа всегда существует, даже если нет соответствующих образцу узлов.

Из всех способов группировки у этого, вероятно, меньше всего применений. И объясняется это тем, что в документах, ориентированных на человека, для формирования групп обычно используются начальные, а не конечные элементы. В книге XSLT Programmer’s Reference Майкл Кэй приводит пример серии докумен­тов, разбитых на части для передачи по каналам связи. Границы документов опоз­наются по отсутствию атрибута continued=’yes’. Чуть более реалистичный пример – структурирование плоского файла путем объединения в один кусок эле­ментов на основе некоторого критерия, определяющего конец куска. Например, следующий код группирует абзацы в разделы по пять абзацев в каждом:

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="doc"> <xsl:copy>

<xsl:for-each-group select="para"

group-ending-with="para[position() mod 5 eq 0]">

<section>

<xsl:for-each select="current-group()"> <xsl:copy>

<xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:for-each> </section> </xsl:for-each-group> </xsl:copy> </xsl:template>

<xsl:template match="@* | node()"> <xsl:copy>

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

Обсуждение

Метод Мюнха, названный в честь Стива Мюнха (Steve Muench) из корпора­ции Oracle, когда-то был новаторским способом группировки данных в XSLT 1.0. В нем используется встроенная в XSLT возможность индексировать документы по ключу. Смысл трюка в том, чтобы с помощью индекса эффективно получить множество уникальных ключей группировки, а затем воспользоваться этим мно­жеством для обработки всех узлов в группе:

<xsl:key name="products-by-category" select="product" use="@category"/>

<xsl:template match="/">

<xsl:for-each select="//product[count(. | key(‘products-by-category’, @category)[1]) = 1]">

<xsl:variable name="current-grouping-key"

select="@category"/> <xsl:variable name="current-group"

select="key(‘current-grouping-key’, $current-grouping-key)"/> <xsl:for-each select="$current-group/*"> <!– обработка элементов в группе –> <!— при необходимости можно добавить и xsl:sort —> </xsl:for-each/> </xsl:for-each/>

<xsl:template match="/">

Хотя метод Мюнха будет работать и в версии 2.0, ему следует предпочесть ко­манду for-each-group, так как она, скорее всего, не менее, а, возможно, даже более эффективна. Немаловажно также, что в этом случае код становится намно­го понятнее, особенно для неопытных пользователей.

К тому же, одна и та же конструкция позволяет выполнить четыре разных за­дачи группировки, тогда как метод Мюнха годится только для группировки по значениям. Пожалуй, единственная причина продолжать применять метод Мюн­ха в XSLT 2.0 – обратная совместимость с первой версией.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии