XSLT группировки. Пример 1 (циклы, ключи, generated-id). XSLT-шаблонизаторы

XSLT группировки. Пример 1 (циклы, ключи, generated-id). XSLT-шаблонизаторы

Типовая задача XSLT- группировка элементов по одному из параметров. Например, есть список сотрудников с указанием подразделений, в которых они работают.

<items>
                <item dep="отдел 301" >             Иванов И.И.</item>        
                <item dep="отдел 302" >             Петрова П.П.</item>        
                <item dep="АХО" >             Тынгылчав И.И.</item>        
                <item dep="ВОХР" >             Рабинович П.Е.</item>        
                <item dep="отдел 301" >             Вышку Н.Н.</item>        
                <item dep="ВОХР" >             Штырц-Кобер С.С.</item>        
                <item dep="АХО" >             Хидияттулин Г.Г.</item>        
                <item dep="отдел 302" >             Непийпиво С.С.</item>        
                <item dep="ВОХР" >             Говорухо-Отрок С.П.</item>        
</items>              

Необходимо сгруппировать всех сотрудников по подразделениям в следующем виде: 

<h1>отдел 301</h1>
<ul>        
     <li>          Иванов И.И.</li>        
     <li>          Вышку Н.Н.</li>        
</ul>        
<h1>          отдел 302</h1>        
<ul>        
   <li>          Петрова П.П.</li>        
   <li>          Непийпиво С.С.</li>        
</ul>                

Ниже рассмотрим три варианта решения задачи. Исходники можно скачать из архива.

Пример I. С вложенными циклами

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

        <xsl:template match="*">        
            <-- ПЕРВЫЙ ЦИКЛ - отбор подразделений -- >        
            <xsl:for-each select="item[not(@dep=preceding-sibling::item/@dep)]" >        
                <xsl:variable name="dep" select="@dep" />        
                <h1>          <xsl:value-of select="$dep"/>          </h1>        
                    <ul>        
                    <-- ВТОРОЙ ЦИКЛ - отбор сотрудников -- >        
                        <xsl:for-each select="../item[@dep = $dep]">        
                            <li>          <xsl:value-of select="."/>          </li>        
                        </xsl:for-each>        
                    <-- ВТОРОЙ ЦИКЛ - отбор сотрудников -- >        
                    </ul>        
            </xsl:for-each>        
            <-- / ПЕРВЫЙ ЦИКЛ - отбор подразделений -- >        
        </xsl:template>        


</xsl:stylesheet>        

            

Этот пример вызовет бурное негодование XSLT-пуристов, потому что циклы xsl:for-each нарушают стройную концепцию XSLT, но об этом поговорим отдельно. В остальном пример лаконичен и прост для понимания тех, кто привык к обычным языкам программирования.

Первый цикл (см. комментарии в листинге) формирует список отделов из общего списка сотрудников. Если бы у каждого сотрудника был параметр, указывающий на должность, можно было бы отобрать по этому параметру начальников, а по параметру @dep отобрать название подразделений. Но такого параметра нет.  Поэтому для составления списка отделов используется  фильтр, отбирающий предшествующих в списке соседей (ось preceding-sibling):

item[not(@dep=preceding-sibling::item/@dep)]

Этот фильтр определяет тех сотрудников,   для которых нет предшествующих в списке  (preceding-sibling::item) коллег из такого же отдела, т.е. не выполняется условие: @dep=preceding-sibling::item/@dep. 

Вложенный цикл xsl:for-each повторно проходит по всему списку сотрудников, и выводит  только тех сотрудников, которые принадлежат отделу отобранному в первом цикле

Пример II. С шаблонными правилами и использованием ключей xsl:key

Ключи ( xsl:key) позволяют работать  с документами, содержащими перекрестные ссылки и аналогичны ключам БД.  Так же как и в БД  использование  xsl:key  ускоряет выборку узлов дерева за счет построения  XSLT-парсерами внутренних индексов.

<xsl:stylesheet     version="1.0"    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"  />        
     <!-- формирование ключа привязки сотрудника
         к подразделению
     -->        
    <xsl:key name="dep" match="item" use="@dep"/>        

    <xsl:template match="*">        
        <xsl:apply-templates />        
    </xsl:template>        


    <xsl:template match="item">        
         <!--  ЦИКЛ 1 (отбор подразделений) -->        
            <xsl:if test="not(@dep=preceding-sibling::item/@dep)">        
                <h1>            <xsl:value-of select="@dep"/>            </h1>        
                <ul>        
                    <!--  ЦИКЛ 2  (отбор сотрудников ) по ключу
                    -->        
                    <xsl:apply-templates mode="second" select="key('dep',@dep)">            </xsl:apply-templates>        
                </ul>        
            </xsl:if>        
        <!--  /ЦИКЛ 1 -->        
    </xsl:template>        

    <xsl:template match="item" mode="second">        
        <li>            <xsl:value-of select="."/>            </li>        
    </xsl:template>        

</xsl:stylesheet>        

            

Для того, чтобы можно было отобрать сотрудников, привязанных к одному подразделению, в этом примере используется xsl:ключ

<xsl:key name="dep" match="item" use="@dep"/>

 который определяется в самом начале XSLT. Выборку всех сотрудников отдела с названием "ОТДЕЛ"  можно составить так:

<xsl:apply-templates select="key('dep','ОТДЕЛ')" />     

Но прежде чем применить эту выборку, необходимо определить список отделов. В примере это делается в первом шаблонном правиле, которое мы применяем ко всем сотрудниками  <xsl:template match="item"> .
Выборка осуществляется аналогично первому примеру через условный оператор:

<xsl:if test="not(@dep=preceding-sibling::item/@dep)" >

После того как список отделов для отбора в первом шаблонном правиле для item определен, остается применить шаблонное правило для обработки всех сотрудников из этого отдела, используя ранее определенный ключ:

<xsl:apply-templates mode="second" select="key('dep',@dep)" />

Обратите внимание на mode="second".  Мы создаем два шаблонных правила для обработки элемента списка сотрудников item. Первое правило безусловное  <xsl:template match="item">  и работает по умолчанию  Оно используется для составления списка отделов. Второе правило для обработки списков сотрудников вызывается с модой (условием)  <xsl:template match="item" mode="second"> Такой подход вместо использования циклов (Пример I) считается более препочтительным, но принимать его за догму было бы ошибкой.

Пример III. C генерацией ключей по методу Мюнха

Этот метод назван по имени автора Steve Muench (Мюнх/Мюнч).  Он лучше всего демонстрирует изощреннность (если не сказать извращенность) XSL-техник для решения элементарных задач.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:key name="dep" match="item" use="@dep"/>        

    <xsl:template match="*">        
        <xsl:apply-templates      select="item[generate-id(.)=generate-id(key('dep',@dep))]"/>        
    </xsl:template>        

    <xsl:template match="item">        
        <h1>        <xsl:value-of select="@dep"/>        </h1>        
        <ul>        
            <xsl:apply-templates mode="next" select="key('dep',@dep)">        </xsl:apply-templates>        
        </ul>        
    </xsl:template>        

    <xsl:template match="item" mode="next">        
        <li>        <xsl:value-of select="."/>        </li>        
    </xsl:template>        

</xsl:stylesheet>        

            

Отличие этого примера от предыдущего в том, что первое шаблонное правило производит выборку отделов, (точнее сотрудников по одному на отдел) без использования циклов и условных операторов, которые должен презирать каждый адепт XSLT. Разберем подробно выражение:

<xsl:apply-templates      
      select="item[generate-id(.)=generate-id(key('dep',@dep))]"
/>                                                                  (*) 

выражение generate-id(.) - формирует уникальный ID для текущего узла (сотрудника).
выражение key('dep','АХО') формирует множество узлов (сотрудников) из отдела с названием "АХО" (административно-хозяйственный отдел)
выражение generate-id(key('dep','ОТДЕЛ')) определяет уникальный ID множества. Уникальный ID множества соответствует ID первого элемента множества. Т.е. ID первого сотрудника из выбранного множества всех сотрудников, работающих в АХО
Таким образом полностью выражение (*) означает отбор тех сотрудников, кто стоит первым из своего отдела в общем списке, т.е. чей generate-id равен generate-id отдела.
Остальное уже было разобранов в примере II.

Любопытно, что чуществует еще один метод Мюнча (Reed and Muench), используемый в биологических исследованиях.  Он применяется для определения летальной дозы препарата в биологических исследованиях. 

PS

Все что написано в этом блоге относится исключительно к стандарту XSLT1.0, который не предусматривает вменяемых средств для группировки данных. В новом стандарте XSLT2 разработчики ввели дополнительные инструменты для сортировки и группировки данных, но пока стандарт XSLT2, принятый два года назад не успел получить широкой поддержки со стороны производителей софта.

23 September 2008 ключевые слова:
Ссылки на статьи по теме: "
Обсуждение в блоге-форуме тем: ""