XSLT - Группировка Мюнха - углубляемся

Порой возникает необходимость осуществить группировку элементов по какому - либо признаку при преобразовании XML документа при помощи технологии XSLT. Дан следующий XML документ:
<?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>
То есть необходимо произвести группировку элементов "item" по значению атрибута "group". Метод Мюнха поможет решить такого рода задачу.
<?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 <xsl:key>

Простое определение

Ключ может содержать элемент или множество элементов. Получить элемент или множество элементов из ключа можно при помощи функции key() по переданному значению.

Пример

Получить элемент по значению из множества: key('group','x') - вернет 4 "item" значение атрибутов "group" которых является "x"

Определение из учебника

Элемент <xsl:key> объявляет именованный ключ, т. е. пару «имя-значение», связанную с указанным элементом в XML-документе. Этот ключ используется с фукнцией key() в выражениях XPath для осуществления эффективного доступа к связанным элементам в сложном XML-документе.

Оригинал

The <xsl:key> element is a top-level element which declares a named key that can be used in the style sheet with the key() function.

Ссылки на документацию

https://www.w3schools.com/xml/ref_xsl_el_key.asp В примере выше создается ключ с именем group, который содержит множество item (match="item") и этот элемент "item" или его множество можно получить по значению атрибута group (use="@group"). В примере выше в ключе "group" содержится множество "item", доступ к которым можно получить по значению атрибута "group" через функцию "key()".

generate-id

Простое определение

Возвращает строку, которая уникально идентифицирует первый узел документа. Если в функцию передать множество, то будет возращен уникальный id первого элемента из этого множества

Определение из учебника

Функция generate-id возвращает уникальный строковый идентификатор первого в порядке просмотра документа узла, передаваемого ей в виде аргумента. Если аргумент опущен, функция возвращает уникальный идентификатор контекстного узла. Если аргументом является пустое множество, функция должна возвращать пустую строку. Функция generate-id возвращает для двух узлов один и тот же идентификатор тогда и только тогда, когда эти два узла совпадают. Это означает, что во время выполнения одного преобразования функция generate-id будет возвращать один идентификатор для одного и того же узла, а для разных узлов generate-id обязательно возвратит разные идентификаторы.

Оригинал

The generate-id() function returns a string value that uniquely identifies a specified node. If the node-set specified is empty, an empty string is returned. If you omit the node-set parameter, it defaults to the current node.

Ссылки на документацию

https://xsltdev.ru/xpath/generate-id/ https://www.w3schools.com/xml/func_generateid.asp

Квадратные скобки

Простое определение

Существует возможность ограничить количество элементов, отвечающих шаблону, введя фильтр - выражение, заключенное в квадратные скобки и следующее непосредственно за оператором пути.

Пример

Следующий образец указывает, что обрабатывать надо только импортные товары (т.е. только те элементы PRODUCT, у которых атрибут import равен "yes":
<xsl:apply-templates select="PRODUCTS/PRODUCT[@import='yes']" />
Этой строчкой
<xsl:apply-templates select="item[generate-id(.) = generate-id(key('group',@group))]" />
мы говорим следующее: Примени все шаблоны к тем элементам item, которые соответствуют критерии (фильтру), который указан в квадратных скобках В этой части
key('group',@group)
мы говорим следующее: Дайте мне множество элементов "item" из ключа "group", значение атрибута "group" которых равняется значению атрибута "group" у "item" находящегося в "group-list" Идем далее. Этой строчкой
generate-id(key('group',@group)
Мы говорим следующее Вернув множество item с одинаковым значением атрибута "group", дай мне id первого элемента из множества Этой строчкой
item[generate-id(.) = generate-id(key('group',@group))]
Мы говорим следующее Примени все шаблоны для элементов "Item", generate-id которых равняется уникальному generate-id элемента. То есть грубо говоря на каждый "item" из набора производится попытка наложить фильтр, чтобы отобрать нужные элементы. Сначала фильтр накладывается на первый элемент:
<item id="aa" group="x"/>
его generate-id равняется generate-id первого элемента из множества, которое находится в ключе, со значением атрибута "group"? Да, равняется. Применяем шаблон. Далее
<item id="ab" group="x"/>
его generate-id равняется generate-id первого элемента из множества, которое находится в ключе, со значением атрибута "group"? Нет, не равняется. Шаблон не пременяется. В итоге получается, что шаблоны применятся только для элементов "item" с уникальным атрибутом "group". Этой частью
<xsl:template match="item">
    <group name="{@group}">
        <xsl:for-each select="key('group',@group)">
            <item name="{@id}"/>
        </xsl:for-each>
    </group>
</xsl:template>
Мы говорим следующее: Создай элемент "group" атрибут "name" которого является значением атрибута "group" у "item", к которому был применен шаблон. Далее перебери множество "item" из ключа "group", атрибуты которых равняются значению атрибута "group" у "item", к которому был применен шаблон. В переборе создай элемент "item" атрибут "name" которого равняется значению атрибута "id" у "item" в переборе.

Зачем метод Мюнха

Введение

В XSLT 2.0+ есть элемент for-each-group. Он решает поставленную в начале статьи задачу с группировкой элементов не прибегая к методу Мюнха

Определение из учебника

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

Так зачем?

В силу разных причин, многие не могут использовать новые версии XSLT процессора. Например стандартный XSLT процессор в PHP ("XSLTProcessor" - http://php.net/manual/en/class.xsltprocessor.php) работает только с XSLT 1.0, в котором отсутствует элемент "for-each-group" и нечего более не остается, как прибегать к данному методу, чтобы решить задачу с группировкой элементов

Дополнительные материалы

Группировка Мюнха
XSLT <xsl:key>
generate-id XPath
XSLT generate-id()

Информация

Автор конспекта


Дата создания: 01.01.2019
Категория: Веб-разработка