<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"  href="/xslt/final.xslt"?><html>
  <head>
    <title>XSLT- примеры. Вып.3 Алфавитные указатели средствами 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="23"/>
    <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- примеры. Вып.3 Алфавитные указатели средствами XSLT</h1>
          <ol class="tags big">
            <li>
              <a href="/xslt-recursive">xslt-recursive</a>
            </li>
            <li>
              <a href="/xslt-examples">xslt-examples</a>
            </li>
            <li>
              <a href="/xslt-Muench">xslt-Muench</a>
            </li>
          </ol>
          <div class="myContent"><p>Все исходники, как обычно, можно скачать здесь  <a href="../../../../doc/xslt-examples.zip">xslt-examples.zip</a> (директория ex3).  В качестве примера берем уже знакомый список книг из <a href="/article/22">выпуска.2 </a></p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain"><p>&lt;books&gt;              <br/>    &lt;book author="Багряк П."  cost="123" type="old"&gt;      Синие люди&lt;/book&gt;              <br/>    &lt;book author="Булгаков М."  cost="34" type="old"&gt;      Роковые яйца&lt;/book&gt;              <br/>    &lt;book author="Чехов А.П."  cost="23" type="old"&gt;      Чайка&lt;/book&gt;              <br/>    &lt;book author="Ленин В.И."  cost="344" type="old"&gt;      Апрельские тезисы&lt;/book&gt;              <br/>    &lt;book author="Бондарев Ю."  cost="23" type="old"&gt;       Берег&lt;/book&gt;              <br/>    &lt;book author="Булгаков М."  cost="34" type="old"&gt;      Собачье сердце&lt;/book&gt;              <br/>    &lt;book author="Донцова Д."  cost="237" type="new"&gt;      Кулинар&lt;/book&gt;              <br/>    &lt;book author="Булгаков М."  cost="34" type="old"&gt;      Бег&lt;/book&gt;              <br/>    &lt;book author="Чехов А.П."  cost="23" type="old"&gt;      Палата #6&lt;/book&gt;              <br/>    &lt;book author="Лесков Н."  cost="744" type="old"&gt;      Левша&lt;/book&gt;              <br/>    &lt;book author="Гаррисон Г."  cost="120" type="new"&gt;      Мир Родины&lt;/book&gt;              <br/>    &lt;book author="Ленин В.И."  cost="344" type="old"&gt;      Что делать&lt;/book&gt;              <br/>&lt;/books&gt;</p></pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Два пути решения составления указателя в общем тоже знакомы из <a href="/article/19">выпуска 1</a> Поэтому не буду особенно комментировать.</p>
<p> </p>
<h2>Пример I</h2>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;?xml version="1.0" encoding="windows-1251"?&gt; 
            &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;    
                &lt;xsl:output indent="yes"/&gt;    
            
                &lt;xsl:template match="books"&gt;    
                    &lt;ul&gt;    
                        &lt;xsl:apply-templates select="book"&gt;    
                            &lt;xsl:sort select="@author"/&gt;    
                        &lt;/xsl:apply-templates&gt;    
                    &lt;/ul&gt;    
                &lt;/xsl:template&gt;    
            
                &lt;xsl:template match="book"&gt;    
                    &lt;xsl:variable name="a" select="substring(@author,1,1)"/&gt;    
                    &lt;xsl:if test="not(preceding-sibling::book[substring(@author,1,1) = $a])"&gt;    
                        &lt;li&gt;    
                            &lt;a href="#{$a}"&gt;    
                                &lt;xsl:value-of select="$a"/&gt;    
                            &lt;/a&gt;    
                        &lt;/li&gt;    
                    &lt;/xsl:if&gt;    
                &lt;/xsl:template&gt;    
            
            &lt;/xsl:stylesheet&gt;    </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>В первом шаблонном правиле организуется перебор всех книг с сортировкой по фамилиям авторов. В шаблонном правиле для обработки единичной книги (match="book"). Производится выделение первой буквы фамилии автора при помощи строковой функции <a href="#" title="www.zvon.org/xxl/XSLTreference/Output/function_substring.html" rel="nofollow" class="external">substring</a> Затем отбрасываются все елементы (книги), у которых есть предшествующий сосед (<a href="#" title="www.zvon.org/xxl/XSLTreference/Output/axis_preceding-sibling.html" rel="nofollow" class="external">preceding-sibling</a>)   с такой же первой буквой фамилии автора. Из первой буквы формируется ссылка алфавитного указателя.</p>
<h2> Пример II. Метод Мюнча (Мюнха)</h2>

<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;xsl:key name="a" match="book" use="substring(@author,1,1)"/&gt;    
            
                &lt;xsl:template match="books"&gt;    
            
       &lt;!- группировка книг по первой букве фамилии автора. --&gt;    
                    &lt;xsl:apply-templates
                        select="book[generate-id(.) = generate-id(key('a',substring(@author,1,1)))]"&gt;    
                        &lt;!  - сортировка по алфавиту --&gt;    
                        &lt;xsl:sort select="@author"/&gt;    
                    &lt;/xsl:apply-templates&gt;    
                &lt;/xsl:template&gt;    
            
                &lt;xsl:template match="book"&gt;    
                    &lt;xsl:variable name="a" select="substring(@author,1,1)"/&gt;    
                    &lt;li&gt;    
                        &lt;a href="#{$a}"&gt;    
                            &lt;xsl:value-of select="$a"/&gt;    
                        &lt;/a&gt;    
                    &lt;/li&gt;    
                &lt;/xsl:template&gt;    
            
            &lt;/xsl:stylesheet&gt;    </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Этот пример аналогичен <a href="/article/19">примеру III выпуска 2</a> В нем отсутствует условный оператор, что придает решению  <strike>(туманность)</strike> изящество. <br/>
В нем определяется ключ xsl:key для отбора и группировки книг по первой букве фамилии автора  @author.<br/>
По этому ключу составляются группы методом Мюнча.  Метод Мюнча основан на том, что функция generate-id для множества узлов генерирует на выходе  id  первого узла. Т.е. если id книги равен id группы книг, значит она в этой группе первая и необходимости в условном операторе, как в примере I нет.   <br/>
Так как порядок следования книг в XML произволен, здесь необходимо задать порядок сортировки: Чтобы сортировка шла в алфавитном порядке - напрямую указывается порядок сортировки <br/>
&lt;xsl:sort select="@author"/&gt;     . <br/>
Правило для обработки отдельной книги  (match="book") выцепляет первую букву фамилии автора и формирует из нее гиперссылку.</p>
<h2> </h2>
<h2> Пример III.  Рекурсивный вызов в XSLT</h2>
<p>Примеры I и II  вполне работают, только даже в нашем учебном примере выдают вместо алфавита молопонятную аббревиатуру. Догадаться, что это алфавит достаточно сложно. Желательно все-таки вывести реальный алфавит, в котором используемые буквы будут выводиться ссылками &lt;a&gt;     , неиспользуемые обычным текстом &lt;span&gt;     . </p>
<p>Для того чтобы алфавит был алфавитом придется прибегнуть к одному из ключевых приемов XSLT - рекурсии, без которых практически невозможно построение произвольных циклов.  Подробнее об этом <a href="#" title="xmlhack.ru/books/xslt/ch_11_04.html" rel="nofollow" class="external">можно почитать у Валикова</a>  Сам пример разобьем на две части. В одной - рассмотрим цикл для вывода алфавита, второй - приделаем к нему наш исходный набор книг из XML .</p>

<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:key name="a" match="book" use="substring(@author,1,1)"/&gt;   

    &lt;xsl:template match="books"&gt;   
        &lt;xsl:call-template name="alphabet"/&gt;   
   &lt;/xsl:template&gt;   

    &lt;xsl:template name="alphabet"&gt;   
        &lt;xsl:param name="s" select="'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЫЭЮЯ'"/&gt;   

        &lt;xsl:variable name="a"&gt;   
            &lt;xsl:value-of select="substring($s,1,1)"/&gt;   
        &lt;/xsl:variable&gt;   
        &lt;xsl:variable name="s0"&gt;   
            &lt;xsl:value-of select="substring-after($s,$a)"/&gt;   
        &lt;/xsl:variable&gt;         
        &lt;!- определение выхода из рекурсии -- &gt;   

        &lt;xsl:if test="$s != ''"&gt;   

           &lt;!-- вывод алфавита в виде последовательности ссылок -- &gt;   
           &lt;a href="#{$a}"&gt;    &lt;xsl:value-of select="$a"/&gt;    &lt;/a&gt;   
          &lt;!-- вывод алфавита в виде последовательности ссылок -- &gt;   

            &lt;!-- организация рекурсивного вызова -- &gt;   
            &lt;xsl:call-template name="alphabet"&gt;   
                &lt;xsl:with-param name="s" select="$s0"/&gt;   
            &lt;/xsl:call-template&gt;   

        &lt;/xsl:if&gt;   

    &lt;/xsl:template&gt;   

&lt;/xsl:stylesheet&gt;    </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>В самом начале задаем ключ для определения первой буквы фамилии автора книги.  Затем создаем правило для обработки всего списка (елемент books). В нем вызываем <a href="#" title="www.codenet.ru/webmast/xml/xslt/w3c.php#named-templates" rel="nofollow" class="external">именованный шаблон</a> alphabet, который и производит рекурсивную выдачу алфавита. </p>
<p>Исходно шаблон вызван без параметров, поэтому в качестве единственного параметра <strong>s</strong>  задаетcя по умолчанию строка алфавита. Строка  в цикле будет разбита по буквам.  Увы. Другого способа нет. Функция типа <span>chr() или ord() <wbr/></span> в XSLT отсутствуют.  Из входной строки формируются две переменные. В одной из них <strong>a</strong> определяется первая буква параметра <strong>s</strong>. Во второй <strong>s0</strong> - подстрока параметра <strong>s</strong> без первой буквы.  Очевидно что для организации рекурсии следует повторно вызвать  именованный шаблон alphabet с параметром равным <strong>s0</strong>. Далее рекурсивная процедура  будет "отщипывать" по одной букве с начала строки до тех пор, пока входная строка не пуста.</p>
<p>Организацию проверки осуществлеят оператор &lt;xsl:if test="$s != ''"&gt;      и в нем задано рекурсивное обращение  alphabet самой к себе, с новым параметром.  В результате выводится строка алфавита в виде набора ссылок.  </p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">&lt;a href="#А"&gt; А&lt;/a&gt; 
&lt;a href="#Б"&gt;    Б&lt;/a&gt;    
&lt;a href="#В"&gt;    В&lt;/a&gt;    
&lt;a href="#Г"&gt;    Г&lt;/a&gt;    
            
etc</pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Таким образом половина работы сделана (исходник в файле 3a.xsl). Осталось только прицепить к этому алфавиту исходный XML c набором книг. Тут кроется не вполне очевидная деталь механизма работы   <a href="#" title="www.codenet.ru/webmast/xml/xslt/w3c.php#named-templates" rel="nofollow" class="external">именованных шаблонов</a>. Во всяком случае в отдельных вводных статьях и книгах на этой детали внимание не заостряется. В отличие от  <code>xsl:apply-templates</code>, <code>xsl:call-template</code> работает с текущим узлом и не меняет текущий набор узлов. Т.е. если мы вызвали именованный шаблон в правиле обработки списка книг  (xsl:template match="books") текущим для него будет узел books. Узел этот при рекурсии не меняется и на любом шаге рекурсии мы можем к нему обратиться.  Что мы и сделаем, заменив в XSLT-шаблоне выше строки отвечающие за обработку буквы алфавита:</p>

<table class="code">
    <tbody>
        <tr>
            <td><!--php-->
            <pre class="brush: plain">           &lt;!-- вывод алфавита в виде последовательности ссылок (или спанов) -- &gt;
            &lt;xsl:choose&gt;   
                &lt;xsl:when test=" key('a',$a)"&gt;   
                    &lt;a href="#{$a}"&gt;   
                        &lt;xsl:value-of select="$a"/&gt;   
                    &lt;/a&gt;   
                &lt;/xsl:when&gt;   
                &lt;xsl:otherwise&gt;   
                    &lt;span&gt;   
                        &lt;xsl:value-of select="$a"/&gt;   
                    &lt;/span&gt;   
                &lt;/xsl:otherwise&gt;   
            &lt;/xsl:choose&gt;   
          &lt;!-- вывод алфавита в виде последовательности ссылок (или спанов)  -- &gt;    </pre><!--/php--></td>
        </tr>
    </tbody>
</table>
<p>Теперь все готово. Окончательный вариант в файле 3b.xsl  <br/>
Несмотря на то, что пример выдает красивый результат, в реальной жизни он нехорош. XSLT-преобразования ресурсоемки, а рекурсия  сама по себе, даже вне XSLT требует памяти для каждого обращения к функции. Т.ч. используя такие приемы - проконсультируйтесь с программистами. Может быть с целью сокращения нагрузки на сервер эту задачу можно решить как-то по-другому. А если вы сами программист - тогда вам и  <strike><a href="#" title="www.google.ru/search?hl=ru&amp;newwindow=1&amp;q=Cache+php&amp;btnG=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA&amp;lr=&amp;aq=f&amp;oq=" rel="nofollow" class="external">Гугля </a></strike>флаг в руки</p></div>
          <ol class="tags big">
            <li class="date">2008-11-05</li>
            <li>
              <a href="/xslt-recursive">xslt-recursive</a>
            </li>
            <li>
              <a href="/xslt-examples">xslt-examples</a>
            </li>
            <li>
              <a href="/xslt-Muench">xslt-Muench</a>
            </li>
          </ol>
          <ol class="see">
            <li>
              <a href="#"><span>www.artlebedev.ru/tools/technogrette/xslt/alpha-index/</span> - <b>Еще один вариант построения алфавитного указателя средствами XSLT</b></a>
            </li>
            <li>
              <a href="#"><span>xmlhack.ru/books/xslt/ch_11_04.html</span> - <b>Рекурсия в книге Валикова "Технология XSLT"</b></a>
            </li>
            <li>
              <a href="#"><span>www.dpawson.co.uk/xsl/sect2/sect21.html</span> - <b>XSLT Questions and Answers - FAQ</b></a>
            </li>
          </ol>
          <ul class="comment">
            <li id="a147" title="a0">
              <a name="&#x410;&#x440;&#x442;&#x443;&#x440;" title="" rel="08.11.08"/>
              <div>непонятно. как вызывается дерево исходного xml в call template тоесть как туда передали дерево веть параметров не задано?</div>
            </li>
            <li id="a148" title="a147">
              <a name="&#x418;&#x441;&#x430;&#x430;&#x43A; &#x422;&#x44B;&#x43D;&#x433;&#x44B;&#x43B;&#x447;&#x430;&#x432;" title="erum.ru" rel="09.11.08"/>
              <div>Я написал об этом. В Call-Template по умолчанию передается контекстый узел. Т.е. теле именованного шаблона [template name] контекстый узел тот, который был контекстным в момент его вызова.</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>

