Pull to refresh

Практический XSLT. Использование в качестве шаблонизатора

Reading time 10 min
Views 52K
В сети доступно масса документации по языку 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"?>

Начало

<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
Tags:
Hubs:
+55
Comments 101
Comments Comments 101

Articles