Применение временных деревьев № 5 — многоступенчатая трансформация

Содержание

Задача. Разобрать по косточкам теорию и практику использования временных деревьев в XSLT.

Случается, что на XSL падает задача сложной обработки входящего XML, скажем группировки и сортировки. Пример такой задачи рассматривал Дима Филатов в своей статье про алфавитные указатели. Напомню, что там в качестве метода группировки фамилий по первой букве была применена группировка Мюнха, довольно непростой для понимания и чтения метод. Решим ту же самую задачу с помощью многоступенчатой трансформации.

Итак, на входе у нас есть XML вида:

<list>
    <item>Орлова</item>
    <item>Владимирова</item>
    <item>Якушева</item>
    <item>Владин</item>
    <item>Александров</item>
    ...
</list>

Требуется сгруппировать все фамилии по первой букве, отсортировать группы по алфавиту и равномерно распределить группы по n колонкам.

Шаг первый — сортируем фамилии и сохраняем плоским списком:

<xsl:variable name="sorted_plain">
    <xsl:for-each select="/list/item">
        <xsl:sort select="." />
        <xsl:copy-of select="." />
    </xsl:for-each>
</xsl:variable>

Теперь в переменной $sorted_plain хранится временное дерево вида:

<item>Александров</item>
<item>Алферова</item>
<item>Бутыркина</item>
<item>Владимирова</item>
<item>Владин</item>
...

Шаг второй — группируем фамилии по первой букве:

<xsl:variable name="sorted_and_grouped">
    <xsl:apply-templates select="exsl:node-set($sorted_plain)/item[1]" mode="items_grouping" />
</xsl:variable>

<xsl:template match="item" mode="items_grouping">
    <xsl:variable name="first_letter" select="substring(., 1, 1)" />
    <xsl:variable
        name="items_with_this_letter"
        select="../item[substring(., 1, 1) = $first_letter]" />
    <group letter="{$first_letter}">
        <xsl:copy-of select="$items_with_this_letter" />
    </group>

    <!--
    Входящий список в $sorted_plain отсортирован,
    поэтому в следующую группу отправляем элемент,
    идущий за последним элементом в текущей группе
    -->
    <xsl:apply-templates
        select="$items_with_this_letter[last()]/following-sibling::item[1]"
        mode="items_grouping" />
</xsl:template>

Сейчас в переменной $sorted_and_grouped находится новое временное дерево, содержащее отсортированные группы:

<group letter="А">
    <item>Александров</item>
    <item>Алферова</item>
</group>
<group letter="Б">
    <item>Бутыркина</item>
</group>
...

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

Шаг третий — равномерно распределяем группы по n колонкам-дивам. Это можно сделать, например, так:

<xsl:variable name="COLS" select="4" />
<xsl:variable name="groups" select="exsl:node-set($sorted_and_grouped)/group" />
<xsl:variable
    name="count_of_groups_in_one_col"
    select="ceiling(count($groups) div $COLS)" />

<!--
Импровизированный цикл по счетчику,
основанный на предположении о том, что число колонок меньше,
чем количество элементов, распределяемых по этим колонкам
-->
<xsl:for-each select="$groups[position() <= $COLS]">
    <xsl:variable name="i" select="position()" />

    <div class="col col_{$i}">
        <xsl:for-each select="$groups[
        position() >     $count_of_groups_in_one_col * ($i - 1) and
        position() <= $count_of_groups_in_one_col * $i
        ]">
            <div class="group">
                <h3>
                    <xsl:value-of select="@letter" />
                </h3>
                <ul>
                    <xsl:for-each select="item">
                        <li>
                            <xsl:value-of select="." />
                        </li>
                    </xsl:for-each>
                </ul>
            </div>
        </xsl:for-each>
    </div>
</xsl:for-each>

На выходе получаем такой HTML:

<div class="col col_1">
    <div class="group">
        <h3>А</h3>
        <ul><li>Александров</li><li>Алферова</li></ul>
    </div>
    <div class="group">
        <h3>Б</h3>
        <ul><li>Бутыркина</li></ul>
    </div>
    ...
</div>
<div class="col col_2">
    <div class="group">
        <h3>Г</h3>
        <ul><li>Гомиашвили</li><li>Гончар</li><li>Гусев</li></ul>
    </div>
    ...
</div>
<div class="col col_3">
    <div class="group">
        <h3>Л</h3>
        <ul><li>Ломов</li></ul>
    </div>
    ...
</div>
<div class="col col_4">
    <div class="group">
        <h3>Ф</h3>
        <ul><li>Феоктистов</li><li>Фролов</li></ul>
    </div>
    ...
</div>

Добавить щепотку CSS — и наш алфавитный указатель готов.

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

Это было последнее применение временных деревьев, о котором я хотел рассказать. В следующей части мы поговорим об XSL-трансформаторах.

Временные деревья в XSLT

  1. Часть первая. Что такое временное дерево
  2. Часть вторая. Применение временных деревьев № 1 — хранение статических данных в коде XSL-шаблона
  3. Часть третья. Применение временных деревьев № 2 — передача сложных параметров
  4. Часть четвертая. Применение временных деревьев № 3 — шаблонизация однотипных строк
  5. Часть пятая. Применение временных деревьев № 4 — подстройка под готовый шаблон
  6. Часть шестая. Применение временных деревьев № 5 — многоступенчатая трансформация
  7. Часть седьмая. О трансформаторах, версиях XSLT и переносимости кода, используещего временные деревья
  8. Часть восьмая. О производительности при работе с временными деревьями
  9. Часть девятая. Тонкости использования временных деревьев


7 июля 2011 г.
Александр Самиляк
http://www.artlebedev.ru/tools/technogrette/xslt/temptrees-6/