.. / XSLT- примеры. Вып.3 Алфавитные указатели средствами XSLT

  1. xslt-recursive
  2. xslt-examples
  3. xslt-Muench

Все исходники, как обычно, можно скачать здесь  xslt-examples.zip (директория ex3).  В качестве примера берем уже знакомый список книг из выпуска.2

<books>
    <book author="Багряк П."  cost="123" type="old"> Синие люди</book>
    <book author="Булгаков М."  cost="34" type="old"> Роковые яйца</book>
    <book author="Чехов А.П."  cost="23" type="old"> Чайка</book>
    <book author="Ленин В.И."  cost="344" type="old"> Апрельские тезисы</book>
    <book author="Бондарев Ю."  cost="23" type="old"> Берег</book>
    <book author="Булгаков М."  cost="34" type="old"> Собачье сердце</book>
    <book author="Донцова Д."  cost="237" type="new"> Кулинар</book>
    <book author="Булгаков М."  cost="34" type="old"> Бег</book>
    <book author="Чехов А.П."  cost="23" type="old"> Палата #6</book>
    <book author="Лесков Н."  cost="744" type="old"> Левша</book>
    <book author="Гаррисон Г."  cost="120" type="new"> Мир Родины</book>
    <book author="Ленин В.И."  cost="344" type="old"> Что делать</book>
</books>

Два пути решения составления указателя в общем тоже знакомы из выпуска 1 Поэтому не буду особенно комментировать.

 

Пример I

<?xml version="1.0" encoding="windows-1251"?> 
            <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    
                <xsl:output indent="yes"/>    
            
                <xsl:template match="books">    
                    <ul>    
                        <xsl:apply-templates select="book">    
                            <xsl:sort select="@author"/>    
                        </xsl:apply-templates>    
                    </ul>    
                </xsl:template>    
            
                <xsl:template match="book">    
                    <xsl:variable name="a" select="substring(@author,1,1)"/>    
                    <xsl:if test="not(preceding-sibling::book[substring(@author,1,1) = $a])">    
                        <li>    
                            <a href="#{$a}">    
                                <xsl:value-of select="$a"/>    
                            </a>    
                        </li>    
                    </xsl:if>    
                </xsl:template>    
            
            </xsl:stylesheet>    

В первом шаблонном правиле организуется перебор всех книг с сортировкой по фамилиям авторов. В шаблонном правиле для обработки единичной книги (match="book"). Производится выделение первой буквы фамилии автора при помощи строковой функции substring Затем отбрасываются все елементы (книги), у которых есть предшествующий сосед (preceding-sibling)  с такой же первой буквой фамилии автора. Из первой буквы формируется ссылка алфавитного указателя.

 Пример II. Метод Мюнча (Мюнха)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
                <xsl:output indent="yes"/>    
            
    
                <xsl:key name="a" match="book" use="substring(@author,1,1)"/>    
            
                <xsl:template match="books">    
            
       <!- группировка книг по первой букве фамилии автора. -->    
                    <xsl:apply-templates
                        select="book[generate-id(.) = generate-id(key('a',substring(@author,1,1)))]">    
                        <!  - сортировка по алфавиту -->    
                        <xsl:sort select="@author"/>    
                    </xsl:apply-templates>    
                </xsl:template>    
            
                <xsl:template match="book">    
                    <xsl:variable name="a" select="substring(@author,1,1)"/>    
                    <li>    
                        <a href="#{$a}">    
                            <xsl:value-of select="$a"/>    
                        </a>    
                    </li>    
                </xsl:template>    
            
            </xsl:stylesheet>    

Этот пример аналогичен примеру III выпуска 2 В нем отсутствует условный оператор, что придает решению  (туманность) изящество.
В нем определяется ключ xsl:key для отбора и группировки книг по первой букве фамилии автора  @author.
По этому ключу составляются группы методом Мюнча.  Метод Мюнча основан на том, что функция generate-id для множества узлов генерирует на выходе  id  первого узла. Т.е. если id книги равен id группы книг, значит она в этой группе первая и необходимости в условном операторе, как в примере I нет.  
Так как порядок следования книг в XML произволен, здесь необходимо задать порядок сортировки: Чтобы сортировка шла в алфавитном порядке - напрямую указывается порядок сортировки
<xsl:sort select="@author"/> .
Правило для обработки отдельной книги  (match="book") выцепляет первую букву фамилии автора и формирует из нее гиперссылку.

 

 Пример III.  Рекурсивный вызов в XSLT

Примеры I и II  вполне работают, только даже в нашем учебном примере выдают вместо алфавита молопонятную аббревиатуру. Догадаться, что это алфавит достаточно сложно. Желательно все-таки вывести реальный алфавит, в котором используемые буквы будут выводиться ссылками <a> , неиспользуемые обычным текстом <span> . 

Для того чтобы алфавит был алфавитом придется прибегнуть к одному из ключевых приемов XSLT - рекурсии, без которых практически невозможно построение произвольных циклов.  Подробнее об этом можно почитать у Валикова  Сам пример разобьем на две части. В одной - рассмотрим цикл для вывода алфавита, второй - приделаем к нему наш исходный набор книг из XML .

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">   
    <xsl:key name="a" match="book" use="substring(@author,1,1)"/>   

    <xsl:template match="books">   
        <xsl:call-template name="alphabet"/>   
   </xsl:template>   

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

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

        <xsl:if test="$s != ''">   

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

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

        </xsl:if>   

    </xsl:template>   

</xsl:stylesheet>    

В самом начале задаем ключ для определения первой буквы фамилии автора книги.  Затем создаем правило для обработки всего списка (елемент books). В нем вызываем именованный шаблон alphabet, который и производит рекурсивную выдачу алфавита. 

Исходно шаблон вызван без параметров, поэтому в качестве единственного параметра s  задаетcя по умолчанию строка алфавита. Строка  в цикле будет разбита по буквам.  Увы. Другого способа нет. Функция типа chr() или ord() в XSLT отсутствуют.  Из входной строки формируются две переменные. В одной из них a определяется первая буква параметра s. Во второй s0 - подстрока параметра s без первой буквы.  Очевидно что для организации рекурсии следует повторно вызвать именованный шаблон alphabet с параметром равным s0. Далее рекурсивная процедура  будет "отщипывать" по одной букве с начала строки до тех пор, пока входная строка не пуста.

Организацию проверки осуществлеят оператор <xsl:if test="$s != ''"> и в нем задано рекурсивное обращение  alphabet самой к себе, с новым параметром.  В результате выводится строка алфавита в виде набора ссылок.  

<a href="#А"> А</a> 
<a href="#Б">    Б</a>    
<a href="#В">    В</a>    
<a href="#Г">    Г</a>    
            
etc

Таким образом половина работы сделана (исходник в файле 3a.xsl). Осталось только прицепить к этому алфавиту исходный XML c набором книг. Тут кроется не вполне очевидная деталь механизма работы  именованных шаблонов. Во всяком случае в отдельных вводных статьях и книгах на этой детали внимание не заостряется. В отличие от  xsl:apply-templates, xsl:call-template работает с текущим узлом и не меняет текущий набор узлов. Т.е. если мы вызвали именованный шаблон в правиле обработки списка книг  (xsl:template match="books") текущим для него будет узел books. Узел этот при рекурсии не меняется и на любом шаге рекурсии мы можем к нему обратиться.  Что мы и сделаем, заменив в XSLT-шаблоне выше строки отвечающие за обработку буквы алфавита:

           <!-- вывод алфавита в виде последовательности ссылок (или спанов) -- >
            <xsl:choose>   
                <xsl:when test=" key('a',$a)">   
                    <a href="#{$a}">   
                        <xsl:value-of select="$a"/>   
                    </a>   
                </xsl:when>   
                <xsl:otherwise>   
                    <span>   
                        <xsl:value-of select="$a"/>   
                    </span>   
                </xsl:otherwise>   
            </xsl:choose>   
          <!-- вывод алфавита в виде последовательности ссылок (или спанов)  -- >    

Теперь все готово. Окончательный вариант в файле 3b.xsl 
Несмотря на то, что пример выдает красивый результат, в реальной жизни он нехорош. XSLT-преобразования ресурсоемки, а рекурсия  сама по себе, даже вне XSLT требует памяти для каждого обращения к функции. Т.ч. используя такие приемы - проконсультируйтесь с программистами. Может быть с целью сокращения нагрузки на сервер эту задачу можно решить как-то по-другому. А если вы сами программист - тогда вам и  Гугля флаг в руки

  1. 2008-11-05
  2. xslt-recursive
  3. xslt-examples
  4. xslt-Muench
  1. xmlhack.ru/books/xslt/ch_11_04.html - Рекурсия в книге Валикова "Технология XSLT"
  2. www.artlebedev.ru/tools/technogrette/xslt/alpha-index/ - Еще один вариант построения алфавитного указателя средствами XSLT
  3. www.dpawson.co.uk/xsl/sect2/sect21.html - XSLT Questions and Answers - FAQ
Go Index Test