<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"  href="/xslt/final.xslt"?><html>
  <head>
    <title>XSLT- примеры. Вып.1 Группировки в XSLT</title>
    <meta name="css" content=""/>
    <meta name="js" content=""/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta name="id" content="19"/>
    <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml"/>
  </head>
  <body>
    <div class="main">
      <div class="wrap">
        <div class="L">
          <h1><a href="/">..</a> / XSLT- примеры. Вып.1 Группировки в XSLT</h1>
          <ol class="tags big">
            <li>
              <a href="/xslt-examples">xslt-examples</a>
            </li>
            <li>
              <a href="/xslt-Muench">xslt-Muench</a>
            </li>
            <li>
              <a href="/xslt-group">xslt-group</a>
            </li>
          </ol>
          <div class="myContent"><p>Типовая задача XSLT- группировка элементов по одному из параметров. Например, есть список сотрудников с указанием подразделений, в которых они работают.</p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;items&gt;
                &lt;item dep="отдел 301" &gt;             Иванов И.И.&lt;/item&gt;        
                &lt;item dep="отдел 302" &gt;             Петрова П.П.&lt;/item&gt;        
                &lt;item dep="АХО" &gt;             Тынгылчав И.И.&lt;/item&gt;        
                &lt;item dep="ВОХР" &gt;             Рабинович П.Е.&lt;/item&gt;        
                &lt;item dep="отдел 301" &gt;             Вышку Н.Н.&lt;/item&gt;        
                &lt;item dep="ВОХР" &gt;             Штырц-Кобер С.С.&lt;/item&gt;        
                &lt;item dep="АХО" &gt;             Хидияттулин Г.Г.&lt;/item&gt;        
                &lt;item dep="отдел 302" &gt;             Непийпиво С.С.&lt;/item&gt;        
                &lt;item dep="ВОХР" &gt;             Говорухо-Отрок С.П.&lt;/item&gt;        
&lt;/items&gt;              </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Необходимо сгруппировать всех сотрудников по подразделениям в следующем виде: </p>
<p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;h1&gt;отдел 301&lt;/h1&gt;
&lt;ul&gt;        
     &lt;li&gt;          Иванов И.И.&lt;/li&gt;        
     &lt;li&gt;          Вышку Н.Н.&lt;/li&gt;        
&lt;/ul&gt;        
&lt;h1&gt;          отдел 302&lt;/h1&gt;        
&lt;ul&gt;        
   &lt;li&gt;          Петрова П.П.&lt;/li&gt;        
   &lt;li&gt;          Непийпиво С.С.&lt;/li&gt;        
&lt;/ul&gt;                </pre></td>
        </tr>
    </tbody>
</table>
</p>
<p>Ниже рассмотрим три варианта решения задачи. Исходники можно скачать из <a href="#" title="/erum.ru/doc/xslt-example01.zip" rel="nofollow" class="external">архива</a>.</p>
<h2>Пример I. С вложенными циклами</h2>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"&gt;        
    &lt;xsl:output indent="yes"  /&gt;        

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


&lt;/xsl:stylesheet&gt;        

            </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Этот пример вызовет бурное негодование XSLT-пуристов, потому что циклы xsl:for-each нарушают стройную концепцию XSLT, но об этом поговорим отдельно. В остальном пример лаконичен и прост для понимания тех, кто привык к обычным языкам программирования.</p>
<p>Первый цикл (см. комментарии в листинге) формирует список отделов из общего списка сотрудников. Если бы у каждого сотрудника был параметр, указывающий на должность, можно было бы отобрать по этому параметру начальников, а по параметру @dep отобрать название подразделений. Но такого параметра нет.  Поэтому для составления списка отделов используется  фильтр, отбирающий предшествующих в списке соседей (ось preceding-sibling):</p>
<pre class="brush: plain">item[not(@dep=preceding-sibling::item/@dep)]</pre><p>Этот фильтр определяет тех сотрудников,   для которых нет предшествующих в списке  (preceding-sibling::item) коллег из такого же отдела, т.е. не выполняется условие: @dep=preceding-sibling::item/@dep. </p>
<p>Вложенный цикл xsl:for-each повторно проходит по всему списку сотрудников, и выводит  только тех сотрудников, которые принадлежат отделу отобранному в первом цикле</p>
<h2>Пример II. С шаблонными правилами и использованием ключей xsl:key</h2>
<p>Ключи ( <a href="#" title="www.citforum.ru/internet/xslt/xslt.shtml#key" rel="nofollow" class="external">xsl:key</a>) позволяют работать  с документами, содержащими перекрестные ссылки и аналогичны ключам БД.  Так же как и в БД  использование  xsl:key  ускоряет выборку узлов дерева за счет построения  XSLT-парсерами внутренних индексов.</p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;xsl:stylesheet     version="1.0"    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    &lt;xsl:output indent="yes"  /&gt;        
     &lt;!-- формирование ключа привязки сотрудника
         к подразделению
     --&gt;        
    &lt;xsl:key name="dep" match="item" use="@dep"/&gt;        

    &lt;xsl:template match="*"&gt;        
        &lt;xsl:apply-templates /&gt;        
    &lt;/xsl:template&gt;        


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

    &lt;xsl:template match="item" mode="second"&gt;        
        &lt;li&gt;            &lt;xsl:value-of select="."/&gt;            &lt;/li&gt;        
    &lt;/xsl:template&gt;        

&lt;/xsl:stylesheet&gt;        

            </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Для того, чтобы можно было отобрать сотрудников, привязанных к одному подразделению, в этом примере используется <a href="#" title="www.citforum.ru/internet/xslt/xslt.shtml#key" rel="nofollow" class="external">xsl:ключ</a></p>
<pre class="brush: plain">&lt;xsl:key name="dep" match="item" use="@dep"/&gt;</pre><p> который определяется в самом начале XSLT. Выборку всех сотрудников отдела с названием "ОТДЕЛ"  можно составить так:</p>
<pre class="brush: plain">&lt;xsl:apply-templates select="key('dep','ОТДЕЛ')" /&gt;     </pre><p>Но прежде чем применить эту выборку, необходимо определить список отделов. В примере это делается в первом шаблонном правиле, которое мы применяем ко всем сотрудниками  &lt;xsl:template match="item"&gt; .<br/>
Выборка осуществляется аналогично первому примеру через условный оператор:</p>
<pre class="brush: plain">&lt;xsl:if test="not(@dep=preceding-sibling::item/@dep)" &gt;</pre><p>После того как список отделов для отбора в первом шаблонном правиле для item определен, остается применить шаблонное правило для обработки всех сотрудников из этого отдела, используя ранее определенный ключ:</p>
<pre class="brush: plain">&lt;xsl:apply-templates mode="second" select="key('dep',@dep)" /&gt;</pre><p>Обратите внимание на mode="second".  Мы создаем два шаблонных правила для обработки элемента списка сотрудников item. Первое правило безусловное  &lt;xsl:template match="item"&gt;  и работает по умолчанию  Оно используется для составления списка отделов. Второе правило для обработки списков сотрудников вызывается с модой (условием)  &lt;xsl:template match="item" mode="second"&gt; Такой подход вместо использования циклов (Пример I) считается более препочтительным, но принимать его за догму было бы ошибкой.</p>
<h2>Пример III. C генерацией ключей по методу Мюнха</h2>
<p>Этот метод назван по имени автора Steve Muench (Мюнх/Мюнч).  Он лучше всего демонстрирует изощреннность (если не сказать извращенность) XSL-техник для решения элементарных задач.</p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

    &lt;xsl:key name="dep" match="item" use="@dep"/&gt;        

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

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

    &lt;xsl:template match="item" mode="next"&gt;        
        &lt;li&gt;        &lt;xsl:value-of select="."/&gt;        &lt;/li&gt;        
    &lt;/xsl:template&gt;        

&lt;/xsl:stylesheet&gt;        

            </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Отличие этого примера от предыдущего в том, что первое шаблонное правило производит выборку отделов, (точнее сотрудников по одному на отдел) без использования циклов и условных операторов, которые должен презирать каждый адепт XSLT. Разберем подробно выражение:</p>
<pre class="brush: plain">&lt;xsl:apply-templates      
      select="item[generate-id(.)=generate-id(key('dep',@dep))]"
/&gt;                                                                  (*) </pre><p>выражение generate-id(.) - формирует уникальный ID для текущего узла (сотрудника).<br/>
выражение key('dep','АХО') формирует множество узлов (сотрудников) из отдела с названием "АХО" (административно-хозяйственный отдел)<br/>
выражение generate-id(key('dep','ОТДЕЛ')) определяет уникальный ID множества. Уникальный ID множества соответствует ID <strong>первого элемента множества</strong>. Т.е. ID первого сотрудника из выбранного множества всех сотрудников, работающих в АХО<br/>
Таким образом полностью выражение (*) означает отбор тех сотрудников, кто стоит первым из своего отдела в общем списке, т.е. чей generate-id равен generate-id отдела. <br/>
Остальное уже было разобранов в примере II.</p>
<p>Любопытно, что чуществует еще один метод Мюнча (Reed and Muench), используемый в биологических исследованиях.  Он применяется для определения летальной дозы препарата в биологических исследованиях. </p>
<h2>PS</h2>
<p>Все что написано в этом блоге относится исключительно к стандарту XSLT1.0, который не предусматривает вменяемых средств для группировки данных. В новом стандарте XSLT2 разработчики ввели дополнительные инструменты для сортировки и группировки данных, но пока <a href="#" title="www.w3.org/TR/xslt20/" rel="nofollow" class="external">стандарт XSLT2</a>, принятый два года назад не успел получить широкой поддержки со стороны производителей софта.</p></div>
          <ol class="tags big">
            <li class="date">2008-09-23</li>
            <li>
              <a href="/xslt-examples">xslt-examples</a>
            </li>
            <li>
              <a href="/xslt-Muench">xslt-Muench</a>
            </li>
            <li>
              <a href="/xslt-group">xslt-group</a>
            </li>
          </ol>
          <ol class="see">
            <li>
              <a href="#"><span>xmlhack.ru/books/xslt/ch_11.html#ch_11_01</span> - <b>А. Валиков Технология XSLT. Глава 11. Готовые решения. Группировки.</b></a>
            </li>
            <li>
              <a href="#"><span>www.artlebedev.ru/tools/technogrette/xslt/alpha-index/</span> - <b>Создание алфавитных указателей средствами XSLT</b></a>
            </li>
            <li>
              <a href="#"><span>www.raleigh.ru/a/pub/2001/unique-ids.html</span> - <b>Создание уникальных идентификаторов средствами XSLT</b></a>
            </li>
            <li>
              <a href="#"><span>www.w3schools.com/xsl/func_generateid.asp</span> - <b>XSLT generate-id() Function</b></a>
            </li>
          </ol>
          <ul class="comment">
            <li id="a140" title="a0">
              <a name="AngryCAT" title="angrycat.info" rel="26.10.08"/>
              <div>ССылка на архив не работает.<br/>Жду новых статей по XML+XSLT</div>
            </li>
            <li id="a141" title="a140">
              <a name="&#x418;&#x441;&#x430;&#x430;&#x43A; &#x422;&#x44B;&#x43D;&#x433;&#x44B;&#x43B;&#x447;&#x430;&#x432;" title="erum.ru" rel="30.10.08"/>
              <div>справил ошибку. Спасибо.<br/>С новыми статьями пока задержка. Материалы есть, и написаны давно. Но причесать-пргладить и выложить никак не удается по времени.</div>
            </li>
            <li id="a142" title="a141">
              <a name="Le capitaine Nemo" title="" rel="30.10.08"/>
              <div>хателось бы больше про то как вместо шаблонизаторов XSLT использовать в php.</div>
            </li>
            <li id="a144" title="a142">
              <a name="Le capitaine Nemo" title="" rel="02.11.08"/>
              <div>XSLT (в отличие от других шаблонизаторов) встроен в PHP, да и в другие современные языки программирования.<br/>Чтобы связать XML и XSLT нужно всего 3-4 строки.  Подробнее см. заметку: Codeigniter + XSLT (шаблонизаторы маздай!)</div>
            </li>
            <li id="a180" title="a142">
              <a name="&#x418;&#x441;&#x430;&#x430;&#x43A; &#x422;&#x44B;&#x43D;&#x433;&#x44B;&#x43B;&#x447;&#x430;&#x432;" title="erum.ru" rel="26.11.08"/>
              <div>XSLT (в отличие от других шаблонизаторов) встроен в PHP, да и в другие современные языки программирования.<br/>Чтобы связать XML и XSLT нужно всего 3-4 строки. Подробнее см. заметку: Codeigniter + XSLT (шаблонизаторы маздай!)</div>
            </li>
            <li id="a516" title="a0">
              <a name="&#x414;&#x430;&#x440;&#x44C;&#x44F;" title="" rel="19.05.10"/>
              <div>Очень нужна помощь.<br/>по первому примеру, со вложенными циклами, все работает, все понятно, НО<br/>как быть, если есть структура вроде:<br/> Булки и масло<br/> Волынский<br/> Нижневолынск<br/> Зольдер стрит, 13<br/> 9:00-20:00<br/>Т.е. список магазинов, и нужно сгруппировать не по одному признаку, а по двум:<br/>Store name:<br/>region<br/>city<br/>        <br/>     вся информация по магазину        <br/>        <br/>я понимаю, что нужен третий вложенный цикл, но никак не могу его написать, не получается. Заранее спасибо.</div>
            </li>
            <li id="a517" title="a516">
              <a name="dasha" title="" rel="19.05.10"/>
              <div>порезало теги в пред. комменте.<br/>в xml структура:<br/>store<br/> storename<br/> region<br/> city<br/> adress<br/> worktime<br/>и сгруппировать нужно:<br/>h1 - region<br/>h2 - city<br/>и потом все найденные магазины в городе определенного района.</div>
            </li>
            <li id="a570" title="a0">
              <a name="&#x421;&#x442;&#x430;&#x441;" title="" rel="02.11.10"/>
              <div>Спасибо за примеры! Очень помогли!)</div>
            </li>
            <li id="a571" title="a0">
              <a name="&#x421;&#x442;&#x430;&#x441;" title="" rel="02.11.10"/>
              <div>Есть вопрос по первому и второму примеру.<br/>В чем отличие первого примера от второго в смысле производительности? Какой более быстрый?</div>
            </li>
            <li id="a574" title="a0">
              <a name="&#x410;&#x43D;&#x434;&#x440;&#x435;&#x439;" title="" rel="25.11.10"/>
              <div>А как выбрать ТОЛЬКО сотрудников 'отдела 301'</div>
            </li>
          </ul>
        </div>
      </div>
      <div class="R">
        <a href="/" title="&#x41D;&#x430; &#x433;&#x43B;&#x430;&#x432;&#x43D;&#x443;&#x44E;"/>
      </div>
    </div>
    <div id="li"/>
  </body>
</html>

