В сети доступно масса документации по языку XSL. Данный раздел не претендует на роль документации по языку, а лишь кратко, по шагам объясняет, как создать свой XSLT-шаблон.
Описанная ниже схема успешно мною используется уже более 3 лет. По началу я к XSLT относился с большой опаской (особенно, когда разбирал чужие исходники), однако однажды поняв, что к чему, уже не представляю, как без него можно работать.
У меня входной XML документ выдает CMS-система, в которой каждая страница с материалом собирается в XML-дерево.
К XHTML-макету никаких ограничений нет. Есть лишь определенные рекомендации по верстке, которые позволят значительно сэкономить время на формирование шаблона.
В качестве парсера (сборщика) конечного документа можно использовать браузер. Нужно лишь указать в XML-документы путь к файлу шаблону:
Хотя, как показала практика, этот механизм довольно глючный (мне пришлось пользовать IE). Лучше воспользоваться средствами XML-парсинга языка, на котором написана CMS-система. Я использую Parser (на нем, вообщем-то, у меня вся система и работает).
Я использую следующую схему:
Обозначенный выше пример схемы не претендует на свою оптимальность. В силу тех или иных причин, мне он удобен. Но, обо всем по порядку.
Описанная ниже схема успешно мною используется уже более 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"?>
Начало
<lang_table>
/>
</lang_table>
<item id="0" parent_id="0" is_published="1" section="1">
Начало
/
<item id="1" parent_id="0" is_published="1" section="1">
Новости
news
Обозначенный выше пример схемы не претендует на свою оптимальность. В силу тех или иных причин, мне он удобен. Но, обо всем по порядку.
<?xml version="1.0" encoding="windows-1251"?>
— заголовок XML-файла. Должен идти строго с начала файла. В нем прописана версия используемого XML-языка и кодировка документа. Я как правило работаю в windows-1251 (пока так удобнее), но, по идее UTF-8 лучше. - корневой элемент документа (можно придумать свое имя). Атрибуты:
- Lang - язык документа. Нужен для создания мультиязычных шаблонов.
- Id - идентификатор текущего раздела.
<lang_table>
- таблица языков, используемых на сайте.
- блок элементов навигации:
- блок основной навигации (основная структура сайта):
<item id="0" parent_id="0" is_published="1" section="1">
- элемент структуры сайта. Атрибуты:
- Id - идентификатор раздела.
- Parent_id - идентификатор родительского раздела.
- Is_published - опубликован ли раздел.
- Dir - uri-адрес раздела. По нему формируются полные адреса.
- Section - тип раздела. Используется если необходимо разбить меню на основное и сервисное.
- блок содержимого.
В моей CMS используется модульная структура: все наполнение сайта представляет собой модули двух видов:
- Html - текстовый модуль. Статические модули, которые заполняет редактор сайта.
- Com - модуль-компонента. Динамические модули, которые формируют различные программные модули CMS: новости, статистика, поисковые блоки и т.д.
В XSL-шаблонах есть разметка блоков, в которые можно размещать модули. Для определения блоков я использую простую нумерацию.
CMS при сборке страницы просто выводит в все модули, которые задействованы на странице в виде:
Атрибуты:
- Id - идентификатор модуля.
Container - блок-назначение (в каком блоке шаблона выводиться).
Sorting - порядок вывода в блоке.
Type - тип:
- Com - модуль-компонентаю
Html - текстовый модуль.
Method - обработчик данных.
Title - название модуля.
DTD я практически не использую (лишь в самом общем виде):
<!DOCTYPE site_page [
<!ENTITY nbsp " ">
<!ENTITY sect "§" >
<!ENTITY copy "©">
<!ENTITY laquo "«">
<!ENTITY reg "®">
<!ENTITY deg "°">
<!ENTITY plusmn "±">
<!ENTITY para "¶">
<!ENTITY raquo "»">
<!ENTITY times "×">
<!ENTITY bull "•">
<!ENTITY hellip "…">
<!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-комментариев:
...
Администрирование сайта
...
- Начало
- Новости
- Разделы
...
Всякие новости
...
Текст
...
Основы описания 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 - шаблоны модулей.
Нам для начала потребуется создать каталог xsl/my_template и в нем, файл layout.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="/node()">
</xsl:template>
</xsl:stylesheet>
Где:
<xsl:template match="/node()"> </xsl:template>
- шаблон для элемента /node() (корневого). Вместо /node() можно указать //document, т.к. он у нас являеться корневым узлом.
Копируем весь XHTML-код внутрь блока <xsl:template match="/node()"></xsl:template>
Этот шаблон будет автоматически применяться ко всему XML-документу. В нашем случае, XSL-преобразование заменит весь XML-код на XHTML-код вашего шаблона.
Далее, необходимо в директории XSL создать файл template.xsl (где, template - название вашего шаблона), в котором размещаем следующий код:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href=" my_template /layout.xsl"/>
</xsl:stylesheet>
Где:
<xsl:import href="my_template/layout.xsl"/>
Директива импорта внешнего XSL-файла (обрабатываеться XSL-процессором) из указанного файла. Путь к файлу указываем относительный.
Создание шаблона для основного навигационного меню
Наш предыдущий шаблон не обладает никакой динамикой, т.к. просто заменяет весь выходной XML-документ на код нашего шаблона.
Следующий шаг - создание шаблона для меню.
Меню навигации сайта строиться на основе его структуры, представленной в XML-документе в следующем виде:
<item id="0" parent_id="0" is_published="1" section="1">
Начало
/
<item id="1" parent_id="0" is_published="1" section="1" hit="yes">
Новости
news
Текущий раздел определяется по двум параметрам:
- Атрибуту id у корневого элемента document - он всегда равен id текущего раздела.
- Атрибуту hit у элемента item - если таковой имеется, то это значит, мы находимся на "главной странице раздела".
Соответственно, для того, чтобы вывести меню сайта необходимо создать шаблон для элементов:
- sections - корневой элемент меню.
- 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">
- Начало
- Разделы
- Новости
</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">
<xsl:call-template name="href_attribute"/>
<xsl:value-of select="title"/>
</xsl:template>
Где:
<xsl:call-template name="href_attribute"/>
- вызов шаблона по имени. При этом шаблон не имеет привязки к элементу, т.е. вызывается произвольно.
<xsl:value-of select="title"/>
- вставка-вывод значения элемента title текущего элемента. Если в параметре перед именем элемента поставить символ @ - выводиться будет значения атрибута текущего элемента.
6. Немного изменяем шаблон sections:
<xsl:template match="sections" mode="global_menu">
</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">
<xsl:choose>
<xsl:when test="descendant-or-self::*/@id = /node()/@id">
<xsl:value-of select="title"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="href_attribute"/>
<xsl:value-of select="title"/>
</xsl:otherwise>
</xsl:choose>
</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, т.е. построить полный адрес текущего узла-раздела.
Далее, нам необходимо каким-то образом определить, как у нас будут обрабатываться модули содержимого. Но, об этом в следующий раз.
UPD:
Материалы к статье. Собрал из того, что было:
parser.proc.ru/iso/xslt-1.zip
В шаблоне все пути прописаны от корня (делал на основе шаблона работающего на реальном сайте) поэтому либо перепишите их на относительные либо запускайте из под Apache.
В архиве входной XML-документ лежит в /xsl/document.xml