|
Александр Самиляк
7 июля 2011 |
|
| Задача. | Разобрать по косточкам теорию и практику использования временных деревьев в 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> ...
Вот так в два простых приема из неудобного входящего
Шаг третий — равномерно распределяем группы по 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-трансформаторах.