Все исходники, как обычно, можно скачать здесь xslt-examples.zip (директория ex3). В качестве примера берем уже знакомый список книг из выпуска.2
|
Два пути решения составления указателя в общем тоже знакомы из выпуска 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()
Организацию проверки осуществлеят оператор <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 требует памяти для каждого обращения к функции. Т.ч. используя такие приемы - проконсультируйтесь с программистами. Может быть с целью сокращения нагрузки на сервер эту задачу можно решить как-то по-другому. А если вы сами программист - тогда вам и Гугля флаг в руки