<xsl:for-each-group> XSLT

Элемент xsl:for-each-group Разбивает объекты последовательности на группы. Существуют четыре способа определения групп; все они подробно описаны ниже. Группировка осуществляется на основании общего значения или по шаблону, которому должен соответствовать первый или последний объект группы. Учтите, что объект исходной последовательности может принадлежать нескольким группам одновременно.

Элементы-потомки:

Синтаксис

XSLT 2.0

<xsl:for-each-group
    select = "выражение"
    group-by = "выражение"
    group-adjacent = "выражение"
    group-starting-with = "паттерн"
    group-ending-with = "паттерн"
    collation = "uri">
    <!-- Содержимое: (xsl:sort*, sequence-constructor) -->
</xsl:for-each-group>

Атрибуты

  • selectобязательный атрибут, выражение XPath, определяющее группируемые объекты.
  • group-byнеобязательный атрибут, определяет значение, общее для всех объектов группы. Например, group-by="state" создает группу для каждого уникального значения элемента <state>. Все объекты одной группы обладают одинаковым значением <state>.
  • group-adjacentнеобязательный атрибут, определяет выражение, вычисляемое для каждого объекта последовательности. Если значение выражения для текущего объекта совпадает со значением этого же выражения для предыдущего объекта, то текущий объект помещается в одну группу с предыдущим объектом. В противном случае текущий объект становится первым объектом новой группы.
  • group-starting-withнеобязательный атрибут, определяет шаблон, обозначающий начало новой группы. При обнаружении объекта, соответствующего этому шаблону, начинается новая группа, а все последующие объекты включаются в эту группу, пока не будет найден следующий совпадающий объект.
  • group-ending-withнеобязательный атрибут, определяет шаблон, обозначающий конец текущей группы. При обнаружении объекта, соответствующего этому шаблону, текущая группа закрывается. Следующий объект последовательности открывает новую группу. Режимы group-by, group-adjacent, group-starting-with и group-ending-with являются взаимоисключающими.
  • collationнеобязательный атрибут, определяет последовательность упорядочения, используемую для сравнения ключей группировки. Способ определения последовательностей упорядочения зависит от конкретного процессора XSLT; за подробностями обращайтесь к документации своего процессора. Атрибут collation может использоваться только с атрибутами group-by или group-adjacent. Попытка его использования с group-starting-with или group-ending-with приводит к ошибке.

XSLT 3.0

<xsl:for-each-group
    select = "expression"
    group-by = "expression"
    group-adjacent = "expression"
    group-starting-with = "pattern"
    group-ending-with = "pattern"
    composite = "boolean"
    collation = "uri" >
    <!-- Content: (xsl:sort*, sequence-constructor) -->
</xsl:for-each-group>

Описание и примеры

Пример 1

Мы рассмотрим четыре примера, демонстрирующих все четыре разновидности группировки. Начнем с group-by, самого распространенного типа группировки. В качестве исходного документа XML будет использоваться упрощенный список адресов клиентов:

<?xml version="1.0"?>
<!-- simplified-names.xml -->
<addressbook>
    <address>
        <first-name>Chester Hasbrouck</first-name>
        <last-name>Frisby</last-name>
        <city>Sheboygan</city>
        <state>WI</state>
    </address>
    <address>
        <first-name>Mary</first-name>
        <last-name>Backstayge</last-name>
        <city>Skunk Haven</city>
        <state>MA</state>
    </address>
    <address>
        <first-name>Natalie</first-name>
        <last-name>Attired</last-name>
        <city>Winter Harbor</city>
        <state>ME</state>
    </address>
    <address>
        <first-name>Harry</first-name>
        <last-name>Backstayge</last-name>
        <city>Skunk Haven</city>
        <state>MA</state>
    </address>
    <address>
        <first-name>Mary</first-name>
        <last-name>McGoon</last-name>
        <city>Boylston</city>
        <state>VA</state>
    </address>
    <address>
        <first-name>Amanda</first-name>
        <last-name>Reckonwith</last-name>
        <city>Lynn</city>
        <state>MA</state>
    </address>
</addressbook>

Мы воспользуемся режимом group-by для группировки адресов по штату (state). Таблица стилей выглядит так:

<?xml version="1.0"?>
<!-- for-each-group1.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text"/>
    <xsl:template match="/">
        <xsl:text>Customers grouped by state&#xA;&#xA;</xsl:text>
        <xsl:for-each-group select="/addressbook/address” group-by="state">
            <xsl:sort select="state"/>
            <xsl:text> State = </xsl:text>
            <xsl:value-of select="current-grouping-key()"/>
            <xsl:text>&#xA;</xsl:text>
            <xsl:for-each select="current-group()">
                <xsl:text>&#x9;</xsl:text>
                <xsl:value-of select="(first-name, last-name)" separator=" "/>
                <xsl:text>, </xsl:text>
                <xsl:value-of select="city"/>
                <xsl:text>&#xA;</xsl:text>
            </xsl:for-each>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

Таблица стилей выдает следующий результат:

Customers grouped by state
State = MA
Mary Backstayge, Skunk Haven
Harry Backstayge, Skunk Haven
Amanda Reckonwith, Lynn
State = ME
Natalie Attired, Winter Harbor
State = VA
Mary McGoon, Boylston
State = WI
Chester Hasbrouck Frisby, Sheboygan

Элемент <xsl:sort>, следующий за элементом <xsl:for-each-group>, сортирует сами группы. Для каждой группы выводится заголовок, а затем содержимое каждой группы обрабатывается элементом <xsl:for-each select="current-group()">. Если убрать элемент <xsl:sort>, упорядочивающий группы, результат изменится. Правильная группировка клиентов сохранится, но сами группы будут следовать в другом порядке:

Customers grouped by state
State = WI
Chester Hasbrouck Frisby, Sheboygan
State = MA
Mary Backstayge, Skunk Haven
Harry Backstayge, Skunk Haven
Amanda Reckonwith, Lynn
State = ME
Natalie Attired, Winter Harbor
State = VA
Mary McGoon, Boylston

Так как группы не были отсортированы, они следуют в том порядке, в котором таблица стилей впервые обнаруживает каждый штат.

Пример 2

Для режима group-adjacent определяется выражение, которое возвращает некоторое значение для каждого объекта в последовательности. Чтобы не усложнять пример, мы воспользуемся group-adjacent с выражением, возвращающим true() или false().

Все смежные объекты последовательности, для которых выражение возвращает одинаковые значения, помещаются в одну группу. В данном примере в документе HTML выделяются группы смежных элементов <p>; они преобразуются в элементы <ul>, в которых каждый элемент <p> становится пунктом списка. Исходный документ HTML:

<?xml version="1.0"?>
<!-- grouping-input.html -->
<html>
    <body>
        <!-- Here's some sample text from the chapter "Sorting
        and Grouping". -->
        <h1>Steps for grouping in the Muench method</h1>
        <p>Define a <code>key</code> for the property we want to use for grouping.</p>
        <p>Select all of the nodes ...</p>
        <p>For each unique grouping value, ...</p>
        <h1>Steps for grouping in XSLT 2.0</h1>
        <p>Define an XPath expression ...</p>
        <p>Select all of the nodes we want to group ...</p>
        <p>Instead of dealing with each ...</p>
    </body>
</html>

Группировка выполняется следующей таблицей стилей:

<?xml version="1.0"?>
<!-- for-each-group2.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" include-content-type="no"/>
    <xsl:template match="/">
        <html>
            <head>
                <title>Grouping with group-adjacent</title>
            </head>
            <body>
                <xsl:for-each-group select="html/body/*" group-adjacent="boolean(self::p)">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()">
                            <ul>
                                <xsl:for-each select="current-group()">
                                    <li><xsl:apply-templates select="*|text()"/></li>
                                </xsl:for-each>
                            </ul>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:apply-templates select="current-group()" />
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="*">
        <xsl:copy>
            <xsl:for-each select="@*">
                <xsl:copy/>
            </xsl:for-each>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Таблица стилей выдает следующий результат:

<html>
    <head>
        <title>Grouping with group-adjacent</title>
    </head>
    <body>
        <h1>Steps for grouping in the Muench method</h1>
        <ul>
            <li>Define a <code>key</code> for the property we want to use for grouping.</li>
            <li>Select all of the nodes ...</li>
            <li>For each unique grouping value, ...</li>
        </ul>
        <h1>Steps for grouping in XSLT 2.0</h1>
        <ul>
            <li>Define an XPath expression ...</li>
            <li>Select all of the nodes we want to group ...</li>
            <li>Instead of dealing with each ...</li>
        </ul>
    </body>
</html>

Атрибут group-adjacent определяет выражение XPath, возвращающее значение. В данном случае для любого элемента <p> выражение возвращает true(), а для всего остального возвращается false(). Впрочем, group-adjacent может содержать гораздо более сложное выражение.

Пример 3

В третьем примере используется режим группировки group-starting-with. В этом примере каждый элемент <h1> открывает новую группу; все элементы вплоть до следующего элемента <h1> включаются в эту группу. Мы создадим выходной документ в формате DocBook, в котором <h1> и все, что следует за ним, оформляется в элемент <sect1>. Таблица стилей для группировки group-starting-with:

<?xml version="1.0"?>
<!-- for-each-group3.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <chapter>
            <title>Grouping in XSLT</title>
            <xsl:apply-templates select="html/body"/>
        </chapter>
    </xsl:template>
    <xsl:template match="body">
        <xsl:for-each-group select="*” group-starting-with="h1">
            <sect1>
                <xsl:apply-templates select="current-group()"/>
            </sect1>
        </xsl:for-each-group>
    </xsl:template>
    <xsl:template match="h1">
        <title>
            <xsl:apply-templates/>
        </title>
    </xsl:template>
    <xsl:template match="p">
        <para>
            <xsl:apply-templates/>
        </para>
    </xsl:template>
    <xsl:template match="*">
        <xsl:copy>
            <xsl:for-each select="@*">
                <xsl:copy/>
            </xsl:for-each>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Наша таблица стилей генерирует документ DocBook, в котором каждый элемент <h1> со всем содержимым преобразуется в элемент <sect1>:

<?xml version="1.0" encoding="UTF-8"?>
<chapter>
    <title>Grouping in XSLT</title>
    <sect1>
        <title>Steps for grouping in the Muench method</title>
        <para>Define a <code>key</code> for the property we want to use for grouping.</para>
        <para>Select all of the nodes ...</para>
        <para>For each unique grouping value, ...</para>
    </sect1>
    <sect1>
        <title>Steps for grouping in XSLT 2.0</title>
        <para>Define an XPath expression ...</para>
        <para>Select all of the nodes we want to group ...</para>
        <para>Instead of dealing with each ...</para>
    </sect1>
</chapter>

Текст элемента HTML <h1> преобразуется в элемент DocBook <title>, а каждый элемент HTML <p> становится элементом DocBook <para>.

Пример 4

Последний пример демонстрирует группировку group-ending-with. Мы возьмем список элементов и разместим их в три столбца. Группа завершается каждым третьим элементом (position() mod 3 = 0). Исходные данные берутся из списка машин:

<?xml version="1.0"?>
<!-- carlist.xml -->
<cars>
    <make>Alfa Romeo</make>
    <make>Bentley</make>
    <make>Chevrolet</make>
    <make>Dodge</make>
    <make>Eagle</make>
    <make>Ford</make>
    <make>GMC</make>
    <make>Honda</make>
    <make>Isuzu</make>
    <model>Javelin</model>
    <model>K-Car</model>
    <make>Lincoln</make>
    <make>Mercedes</make>
    <make>Nash</make>
    <make>Opel</make>
    <make>Pontiac</make>
    <model>Quantum</model>
    <model>Rambler</model>
    <make>Studebaker</make>
</cars>

Таблица стилей:

<?xml version="1.0"?>
<!-- for-each-group4.xsl -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
    <xsl:output method="html"/>
    <xsl:template match="/">
        <html>
            <head>
                <title>Car Makes and Models</title>
            </head>
            <body style="font-family: sans-serif;">
                <h1>Car Makes and Models</h1>
                <p>Here are the car makes and models in our input document.</p>
                <table border="1" cellpadding="5">
                    <xsl:apply-templates select="cars"/>
                </table>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="cars">
        <xsl:for-each-group select="make|model" group-ending-with="*[position() mod 3 = 0]">
            <tr>
                <xsl:choose>
                    <xsl:when test="count(current-group()) = 3">
                        <xsl:for-each select="current-group()">
                            <xsl:apply-templates select="."/>
                        </xsl:for-each>
                    </xsl:when>
                    <xsl:when test="count(current-group()) = 2">
                        <xsl:apply-templates select="current-group()[1]"/>
                        <xsl:apply-templates select="current-group()[2]"/>
                        <td bgcolor="#CCCCCC">
                            &#x20;&#x20;
                        </td>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()[1]"/>
                        <td bgcolor="#CCCCCC" colspan="2">
                            &#x20;&#x20;
                        </td>
                    </xsl:otherwise>
                </xsl:choose>
            </tr>
        </xsl:for-each-group>
    </xsl:template>
    <xsl:template match="make">
        <td style="font-weight: bold;">
            <xsl:apply-templates/>
        </td>
    </xsl:template>
    <xsl:template match="model">
        <td style="font-style: italic; font-weight: bold;">
            <xsl:apply-templates/>
        </td>
    </xsl:template>
</xsl:stylesheet>

Результат:

<html>
    <head>
        <title>Car Makes and Models</title>
    </head>
    <body style="font-family: sans-serif;">
        <h1>Car Makes and Models</h1>
        <p>Here are the car makes and models in our input document.</p>
        <table border="1" cellpadding="5">
            <tr>
                <td style="font-weight: bold;">Alfa Romeo</td>
                <td style="font-weight: bold;">Bentley</td>
                <td style="font-weight: bold;">Chevrolet</td>
            </tr>
            ...
            <tr>
                <td style="font-weight: bold;">Studebaker</td>
                <td bgcolor="#CCCCCC" colspan="2"></td>
            </tr>
        </table>
    </body>
</html>

Элементы списка машин записываются в таблицу HTML группами по три. Обратите внимание: последняя группа содержит только один элемент, поэтому за ячейкой последнего элемента следует пустая серая ячейка с атрибутом colspan="2".