Практический XSLT. Использование в качестве шаблонизатора
В сети доступно масса документации по языку XSL. Данный раздел не претендует на роль документации по языку, а лишь кратко, по шагам объясняет, как создать свой XSLT-шаблон.
Описанная ниже схема успешно мною используется уже более 3 лет. По началу я к XSLT относился с большой опаской (особенно, когда разбирал чужие исходники), однако однажды поняв, что к чему, уже не представляю, как без него можно работать.
Рабочий стол
Определим, что нам нужно для работы:
- Входной XML-документ
- XHTML-макет шаблона
- Парсер XML для склейки XML с XSL
У меня входной XML документ выдает CMS-система, в которой каждая страница с материалом собирается в XML-дерево.
К XHTML-макету никаких ограничений нет. Есть лишь определенные рекомендации по верстке, которые позволят значительно сэкономить время на формирование шаблона.
В качестве парсера (сборщика) конечного документа можно использовать браузер. Нужно лишь указать в XML-документы путь к файлу шаблону:
<?xml-stylesheet type="text/xsl" href="template.xsl" ?>
Хотя, как показала практика, этот механизм довольно глючный (мне пришлось пользовать IE). Лучше воспользоваться средствами XML-парсинга языка, на котором написана CMS-система. Я использую Parser (на нем, вообщем-то, у меня вся система и работает).
Входной XML-документ
Для начала разберемся со входным XML-документом. Для того, чтобы использовать XSL нужно иметь полное представление о его структуре.
Я использую следующую схему:
<?xml version="1.0" encoding="windows-1251"?>
<document lang="ru" id="0">
<header>
<title>Начало</title>
</header>
<lang_table>
<item hit="yes" lang="ru" title="Russian"/>
</lang_table>
<navigation>
<sections>
<item id="0" parent_id="0" is_published="1" section="1">
<title>Начало</title>
<dir>/</dir>
</item>
<item id="1" parent_id="0" is_published="1" section="1">
<title>Новости</title>
<dir>news</dir>
</item>
</sections>
</navigation>
<content>
<item id="1" container="1" sorting="2" type="com" method="list_news" title="Новости"></item>
</content>
</document>
Обозначенный выше пример схемы не претендует на свою оптимальность. В силу тех или иных причин, мне он удобен. Но, обо всем по порядку.
<?xml version="1.0" encoding="windows-1251"?>
- заголовок XML-файла. Должен идти строго с начала файла. В нем прописана версия используемого XML-языка и кодировка документа. Я как правило работаю в windows-1251 (пока так удобнее), но, по идее UTF-8 лучше.
<document lang="ru" id="0">
- корневой элемент документа (можно придумать свое имя). Атрибуты:
- Lang - язык документа. Нужен для создания мультиязычных шаблонов.
- Id - идентификатор текущего раздела.
<lang_table>
- таблица языков, используемых на сайте.
<navigation>
- блок элементов навигации:
<sections>
- блок основной навигации (основная структура сайта):
<item id="0" parent_id="0" is_published="1" section="1">
- элемент структуры сайта. Атрибуты:
- Id - идентификатор раздела.
- Parent_id - идентификатор родительского раздела.
- Is_published - опубликован ли раздел.
- Dir - uri-адрес раздела. По нему формируются полные адреса.
- Section - тип раздела. Используется если необходимо разбить меню на основное и сервисное.
<content>
- блок содержимого.
В моей CMS используется модульная структура: все наполнение сайта представляет собой модули двух видов:
- Html - текстовый модуль. Статические модули, которые заполняет редактор сайта.
- Com - модуль-компонента. Динамические модули, которые формируют различные программные модули CMS: новости, статистика, поисковые блоки и т.д.
В XSL-шаблонах есть разметка блоков, в которые можно размещать модули. Для определения блоков я использую простую нумерацию.
CMS при сборке страницы просто выводит в все модули, которые задействованы на странице в виде: <item id="1" container="1" sorting="2" type="com" method="list_news" title="Новости">
Атрибуты:
- Id - идентификатор модуля.
- Container - блок-назначение (в каком блоке шаблона выводиться).
- Sorting - порядок вывода в блоке.
-
Type - тип:
- Com - модуль-компонентаю
- Html - текстовый модуль.
- Method - обработчик данных.
- Title - название модуля.
DTD я практически не использую (лишь в самом общем виде):
<!DOCTYPE site_page [
<!-- Character entity references for ISO 8859-1 characters -->
<!ENTITY nbsp " ">
<!ENTITY sect "§" >
<!ENTITY copy "©">
<!ENTITY laquo "«">
<!ENTITY reg "®">
<!ENTITY deg "°">
<!ENTITY plusmn "±">
<!ENTITY para "¶">
<!ENTITY raquo "»">
<!ENTITY times "×">
<!-- Character entity references for symbols, mathematical symbols, and Greek letters -->
<!ENTITY bull "•">
<!ENTITY hellip "…">
<!-- Character entity references for markup-significant and internationalization characters -->
<!ENTITY ndash "–">
<!ENTITY mdash "—">
<!ENTITY lsquo "‘">
<!ENTITY rsquo "’">
<!ENTITY sbquo "‚">
<!ENTITY ldquo "“">
<!ENTITY rdquo "”">
<!ENTITY bdquo "„">
<!ENTITY lsaquo "‹">
<!ENTITY rsaquo "›" >
<!ENTITY euro "€">
]>
Его можно вставить прямо в XML-документ. Сразу после <?xml version="1.0" encoding="windows-1251"?>
.
Подготовка XHML-шаблона
XSL-шаблон создается на базе XHTML-шаблона (некой типовой страницы сайта). Код XHTML-страницы, при этом, должен быть валидным.
Рассмотрим по шагам процесс создания шаблона.
Проверив валидность XHML-страницы своего шаблона, для облегчения собственной работы, обозначьте в нем положение всех динамических блоков:
- Меню (и других элементов навигации).
- Информационных блоков страницы - то место в шаблоне, в котором будут выводиться модули сайта.
- Заголовка/названия страницы.
Сделать это лучше всего с помощью обычных HTML-комментариев:
...
<h1 class="top">
<!-- Название раздела -->
Администрирование сайта
<!-- /Название раздела -->
</h1>
...
<!-- меню -->
<ul id="main-menu">
<li><a href="#">Начало</a></li>
<li class="curent-item"><a href="#">Новости</a></li>
<li><a href="#">Разделы</a></li>
</ul>
<!-- /меню -->
...
<!-- блок левых модулей -->
Всякие новости
<!-- /блок левых модулей -->
...
<!-- Блок основного содержимого -->
Текст
<!-- /Блок основного содержимого -->
...
Основы описания XSL-шаблонов
Все файлы XSL-шаблонов имеют следующий вид:
<xsl:stylesheet version = `1.0` encoding="UTF-8"?>
<xsl:template match="element">
данные шаблона
</xsl:template>
</xsl:stylesheet>
Где: <xsl:stylesheet version = `1.0` encoding="UTF-8"?>
- определяет тип XML-документа и кодировку. Я использую UTF-8 (не спрашивайте, почему).
<xsl:stylesheet> </xsl:stylesheet>
- начало и конец XSL-документа.
<xsl:template match="element"> </xsl:template>
- начало и конец шаблона для элемента element.
Шаблоны можно условно разделить на три вида:
-
<xsl:template match="element"></xsl:template>
- шаблон, описывающий правила преобразования элемента element. Применяется автоматически ко всем элементам element. -
<xsl:template match="element" mode="mode1"></xsl:template>
- шаблон, описывающий правила преобразования элемента element в режиме mode1. Таким образом можно описать различные правила обработки элементов element. -
<xsl:template name="template-name"></xsl:template>
- шаблон с именем template-name. Не имеет привязки к какому-либо элементу XML-документа.
Если элементы одного вида могут встречаться в различных частях структуры XML-документа (например, в XML-документе, формируемом системой элемент item используется повсеместно и имеет разное значение), то в шаблоне можно указать "структурный адрес" такого элемента:
<xsl:template match="navigation/sections/item"></xsl:template>
При этом, порядок применения шаблонов иерархичный, т.е., сначала шаблон применяется к корневому элементу, а затем, к дочерним, т.е. если мы вызвали обработчик для navigation, то для вызова обработчика для navigation/sections/item нам достаточно указать адрес sections/item.
Структура папок шаблонов
Для того, чтобы хранить на одном сайте несколько модулей необходимо как-то продумать структуру их хранения в папкам. При этом, удобнее разбить шаблоны на модули по нескольким xsl-файлам. Такой подход позволит в дальнейшем повторно их использовать при создании новых шаблонов.
В простейшем варианте можно создать каталог xsl и там все складировать.
Далее, чтобы внутри этого каталог шаблоны не путались (для каждого шаблона у нас получиться несколько файлов) создадим вложенные каталоги:
- template_folder - каталог с файлами шаблона. Называть ее можно по имени шаблона, например my_template.
- dtd - файлы описания основных сущностей. Могут быть полезными.
- lang - шаблоны сообщений для различных языков (если на сайте используется их используется несколько).
- mod - шаблоны модулей.
Создание шаблона для основного навигационного меню
Наш предыдущий шаблон не обладает никакой динамикой, т.к. просто заменяет весь выходной XML-документ на код нашего шаблона.
Следующий шаг - создание шаблона для меню.
Меню навигации сайта строиться на основе его структуры, представленной в XML-документе в следующем виде:
<navigation>
<sections>
<item id="0" parent_id="0" is_published="1" section="1">
<title>Начало</title>
<dir>/</dir>
</item>
<item id="1" parent_id="0" is_published="1" section="1" hit="yes">
<title>Новости</title>
<dir>news</dir>
</item>
</sections>
</navigation>
Текущий раздел определяется по двум параметрам:
- Атрибуту id у корневого элемента document - он всегда равен id текущего раздела.
- Атрибуту hit у элемента item - если таковой имеется, то это значит, мы находимся на "главной странице раздела".
Соответственно, для того, чтобы вывести меню сайта необходимо создать шаблон для элементов:
- sections - корневой элемент меню.
- item - элемент меню.
При этом, необходимо учесть, что элементы item могут содержать другие элементы item, в том случае, если у раздела есть подразделы:
<item>
<item></item>
</item>
1. Создаем в директории xsl/my_template файл navigation.xsl следующего вида:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet SYSTEM "../dtd/entities.dtd">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Глобальное меню навигации -->
<xsl:template match="sections" mode="global_menu">
</xsl:template>
<!-- /Глобальное меню навигации -->
</xsl:stylesheet>
2. Вставляем в шаблон код нашего меню из файла layout.xsl:
<!-- Глобальное меню навигации -->
<xsl:template match="sections" mode="global_menu">
<ul id="main-menu">
<li class="curent-item"><a href="/">Начало</a></li>
<li><a href="/sections/">Разделы</a></li>
<li><a href="/news/">Новости</a></li>
</ul>
</xsl:template>
<!-- /Глобальное меню навигации -->
3. …а на его место в файле layout.xsl вставляем вызов нашего шаблона меню:
<!-- меню -->
<xsl:apply-templates select="navigation/sections" mode="global_menu"/>
<!-- /меню -->
Где:
select="navigation/sections" - относительный (относительно текущего) путь-адрес элемента. При этом, будут обработаны все элементы navigation/sections.
mode="global_menu" - используем шаблон с режимом global_menu. Это нам нужно на тот случай, если нужно будет выводить еще и сервисное меню, отдельно, или "хлебные крошки", или что-еще другое на основе одной и той же ветки навигации.
4. Плюс, добавим в файл layout.xsl директиву импорта файла шаблона navigation.xsl:
<xsl:import href="navigation.xsl"/>
5. Далее, создаем в файле navigation.xsl еще один шаблон, для обработки пунктов меню:
<!-- Обработка ссылок меню -->
<xsl:template match="item" mode="global_menu">
<li>
<a>
<xsl:call-template name="href_attribute"/>
<xsl:value-of select="title"/>
</a>
</li>
</xsl:template>
<!-- /Обработка ссылок меню -->
Где:
<xsl:call-template name="href_attribute"/>
- вызов шаблона по имени. При этом шаблон не имеет привязки к элементу, т.е. вызывается произвольно.
<xsl:value-of select="title"/>
- вставка-вывод значения элемента title текущего элемента. Если в параметре перед именем элемента поставить символ @ - выводиться будет значения атрибута текущего элемента.
6. Немного изменяем шаблон sections:
<!-- Глобальное меню навигации -->
<xsl:template match="sections" mode="global_menu">
<ul id="main-menu">
<xsl:apply-templates select="item" mode="global_menu"/>
</ul>
</xsl:template>
<!-- /Глобальное меню навигации -->
Где:
<xsl:apply-templates select="item" mode="global_menu"/>
- обработка всех элементов item элемента sections. При этом, элементы item самих элементов item (sections/item/item) обрабатываться не будут, т.е. выводиться только один уровень меню разделов.
Мы вынесли обработку элементов item (пунктов меню) в отдельный шаблон. При этом, в нем мы добавили еще и вызов другого шаблона: <xsl:call-template name="href_attribute"/>
Этот шаблон будет формировать нормальные uri-ссылки для элементов нашего меню. О нем немного позже.
7. Теперь нам необходимо доделать меню,
чтобы оно учитывало, какой раздел является текущим. Для этого нам придется добавить условную обработку в наш шаблон элемента item:
<!-- Обработка ссылок меню -->
<xsl:template match="item" mode="global_menu">
<li>
<xsl:choose>
<!-- если Текущий раздел -->
<xsl:when test="descendant-or-self::*/@id = /node()/@id">
<xsl:value-of select="title"/>
</xsl:when>
<!-- если раздел не текщий -->
<xsl:otherwise>
<a>
<xsl:call-template name="href_attribute"/>
<xsl:value-of select="title"/>
</a>
</xsl:otherwise>
</xsl:choose>
</li>
</xsl:template>
<!-- /Обработка ссылок меню -->
Здесь мы сталкиваемся с новой конструкцией:
<xsl:choose>
<xsl:when></xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
…которая, собственно, и задает условную обработку XML-элементов. В качестве параметра мы задаем условие: <xsl:when test="descendant-or-self::*/@id = /node()/@id">
В нашем случае это условие равенства атрибутов ID у корневого элемента (document) и текущего элемента (item), которое и определяет, является ли элемент текущим.
Внутри блока <xsl:when></xsl:when>
располагается то, что выводиться в случае выполнения условия. В блоке <xsl:otherwise></xsl:otherwise>
- если условие не выполняется.
8. Теперь, разберем шаблон href_attribute:
<!-- Обработка адреса ссылок меню -->
<xsl:template name="href_attribute">
<xsl:attribute name="href">
<xsl:text>/</xsl:text>
<xsl:for-each select="ancestor-or-self::item">
<xsl:value-of select="dir"/>
<xsl:text>/</xsl:text>
</xsl:for-each>
</xsl:attribute>
</xsl:template>
<!-- /Обработка адреса ссылок меню -->
Здесь мы сталкиваемся с инструкцией xsl:attribute. Она позволяет создавать атрибуты для элементов внутри которого она вызывается. В нашем случае мы вызываем ее из элемента a, соответственно, она создаст для него атрибут href, т.е. адрес.
Инструкция <xsl:for-each select="ancestor-or-self::item">
задает цикл обработки для всех элементов, удовлетворяющих условию. В нашем случае мы выбираем ancestor-or-self::item - ось элементов от корневого элемента до текущего по цепочке. В нашем случае это позволяет выбрать для всей цепочки узлы dir, т.е. построить полный адрес текущего узла-раздела.
Далее, нам необходимо каким-то образом определить, как у нас будут обрабатываться модули содержимого. Но, об этом в следующий раз.
Рекомендуем почитать