Главная » XSLT » Эмуляция объектно-ориентированных методик повторного использования и паттернов проектирования

0

Задача

Хотелось бы перейти от «копирования и вставки» к созданию библиотек по­вторно используемого XSLT-кода.

Решение

Понятно, что XSLT – не объектно-ориентированный язык. Однако объектная ориентация – это, скорее, принципы конструирования обобщенного повторно ис­пользуемого кода, а не просто классы, объекты, наследование, инкапсуляция и т.д.

В XSLT 2.0 появилось два механизма, упрощающих разработку в объектно- ориентированном стиле. Первый – команда xsl:next-match, второй – тун­нельные параметры. Команда xsl:next-match – это обобщение команды xsl:apply-imports в XSLT 1.0. Напомним, что последняя используется для вызо­ва шаблонов с более низким уровнем приоритета. Команда xsl:next-match обоб­щает это поведение, позволяя вызывать подходящие низкоприоритетные шаблоны, определенные в той же или импортирующей таблице стилей. Этот механизм сродни вызову метода базового класса в объектно-ориентированном программировании.

<xsl:template match="author | title | subtitle | deck" priority="1"> <a name="{generate-id()}"> <span class="{name()}">

<xsl:apply-templates/> </span> </a> </xsl:template>

<xsl:template match="author" priority="2"> <div>

<span class="by">By </span> <xsl:next-match/> </div> </xsl:template>

<xsl:template match="title" priority="2">

<h1 class="title"><xsl:next-match/></h1> </xsl:template>

<xsl:template match="deck" priority="2">

<h2 class="deck"><xsl:next-match/></h2> </xsl:template>

<xsl:template match="subtitle" priority="2">

<h2 class="subtitle"><xsl:next-match/></h2> </xsl:template>

Еще одно нововведение в XSLT 2.0 – возможность передавать параметры ко­мандам xsl:next-match и xsl:apply-imports:

<xsl:next-match>

<xsl:with-param name="indent" select="2"/> </xsl:next-match>

Кроме того, в XSLT 2.0 появились туннельные параметры – разновидность динамических областей видимости, столь популярных в функциональном про­граммировании. С помощью этого механизма можно передать команде xsl:apply-templates параметры, неизвестные тем шаблонам, которые она со­чтет нужным применить. Однако эти параметры будут прозрачно передаваться от одного вызова другому, пока не достигнут шаблона, знающего о них. Отметим, что атрибут tunnel="yes" следует указывать как в точке вызова, так и в точке приема параметров:

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<!– Стандартные правила обработки документа –> <xsl:import href="doc.xslt"/>

<!– Параметр, который автор doc.xslt не предвидел –> <xsl:param name="customParam"/>

<xsl:template match="/">

<!– Вызываем шаблоны из doc.xslt, которые ничего не знают о customParam — >

<xsl:apply-templates>

<xsl:with-param name="customParam"

select="$customParam" tunnel="yes"/> </xsl:apply-templates> </xsl:template>

<xsl:template match="heading1">

<!– Сделать с элементами heading1 что-то нестандартное в зависимости от значения customParam –> <xsl:param name="customParam" tunnel="yes"/> <!– …

– >

</xsl:template>

</xsl:stylesheet>

Это чрезвычайно важное дополнение, поскольку оно позволяет отделить шаб­лоны, специфичные для конкретного приложения, от общих шаблонов, не вводя глобальных параметров или переменных.

Обсуждение

В методиках объектно-ориентированной разработки очень популярны паттерны проектирования. Это испытанные и доказавшие свою состоятельность приемы, приме­нимые к решению широкого спектра задач. Паттерны упрощают взаимодействие меж­ду разработчиками, так как позволяют назвать использованный подход к решению и контекст, в котором они применим, более или менее стандартизованным именем.

Средства, описанные в этом рецепте и в рецепте 6.3, можно использовать для реализации некоторых стандартных паттернов на языке XSLT. Ниже мы показы­ваем, как их можно адаптировать.

Паттерн Template Method (Шаблонный метод)

Определяется скелет таблицы стилей для выполнения операции, а конкрет­ные шаги делегируются шаблонам, которые реализованы в импортирующих таб­лицах. Паттерн Template Method позволяет другим разработчикам переопреде­лить некоторые шаги, не изменяя общую структуру преобразования.

Рассмотрим таблицу стилей, определяющую стандарт, которому компания следует при публикации XML-документа в Web:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="2.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" >

<xsl:output method="xhtml" indent="yes"/>

<xsl:param name="titlePrefix" select=" ” " as="xs:string"/>

<xsl:template match="/"> <html> <head>

<title><xsl:value-of

select="concat($titlePrefix, /*/title)"/></title>

</head> <body>

<xsl:call-template name="standard-processing-sequence"/> </body> </html>

</xsl:template>

<xsl:template name="standard-processing-sequence"> <xsl:apply-templates mode="front-matter">

<xsl:with-param name="mode" select=" ‘front-matter’ " tunnel="yes" as="xs:string"/> </xsl:apply-templates>

<xsl:apply-templates mode="toc">

<xsl:with-param name="mode" select=" ‘too’ "

tunnel="yes" as="xs:string"/> </xsl:apply-templates>

<xsl:apply-templates mode="body">

<xsl:with-param name="mode" select=" ‘body’ " tunnel="yes" as="xs:string"/> </xsl:apply-templates>

<xsl:apply-templates mode="appendicies">

<xsl:with-param name="mode" select=" ‘appendicies’ " tunnel="yes" as="xs:string"/> </xsl:apply-templates> </xsl:template>

<xsl:template match="/*" mode="#all">

<xsl:param name="mode" tunnel="yes" as="xs:string"/> <div class="{$mode}">

<xsl:apply-templates mode="#current"/> </div> </xsl:template>

<!– Здесь могут быть шаблоны, применяемые по умолчанию в различных режимах. Однако в импортирующих таблицах стилей их можно переопределить. –>

</xsl:stylesheet>

Здесь мы воспользовались режимами для определения каждого этапа обра­ботки. А название текущего режима еще и передается в туннельном параметре.

У такого подхода есть два преимущества. Во-первых, он полезен для отладки шаблонов, которые сопоставляются в нескольких режимах. Во-вторых, если пове­дение шаблонов с несколькими режимами отличается незначительно, то отличия можно реализовать с помощью функций от режима, ничего не зная о специфике конкретного режима. Например, если шаблон присоединяет CSS-стили к выход­ным элементам, то в названия стилей можно в качестве префикса добавить назва­ние режима или придумать еще какое-то общее отображение режима на стиль (на­пример, в виде справочной таблицы).

Паттерн Chain of Responsibility (Цепочка обязанностей)

Назначение этого паттерна – разорвать связь между инициатором преобразо­вания с шаблонами, которые это преобразование реализуют, предоставив не­скольким шаблонам шанс обработать запрос. Для выбора подходящего шаблона используется механизм сопоставления, а не условная логика.

Ключом к переносимости этого паттерна служат приоритеты, поскольку не­которые процессоры XSLT отказываются обрабатывать шаблоны с неоднознач­ными правилами предшествования. Следующий пример взят из проекта дина­мического сайта, над которым я работал, применяя систему Cocoon. В этом проекте использовались шаблонные HTML-файлы, а способ преобразования динамического содержимого из базы данных XML в HTML-разметку диктовал­ся атрибутами class. Детали шаблонов я опущу, поскольку они не так важны, как общая структура. В данном примере в любом узле xhtm:td сопоставляется только один шаблон, но в общем случае можно воспользоваться командой xsl:next-match для объединения результатов применения нескольких под­ходящих шаблонов.

<xsl:template match="xhtm:td[matches(@class, ,Akeep-(\w+)’)]" mode="template" priority="2.1">

</xsl:template>

<xsl:template match="xhtm:td[matches(@class, ,A(flow|list)-(\w+)’)]" mode="template" priority="2.2">

</xsl:template>

<xsl:template match="xhtm:td[matches(@class, ,Arepeat-(\w+)’)]" mode="template" priority="2.3">

</xsl:template>

<xsl:template match="xhtm:td[matches(@class, ,Adownload-(\w+)’)]" mode="template" priority="2.4">

</xsl:template>

<xsl:template match="xhtm:td[matches(@class, ,Akeep-(\w+)’)]" mode="template" priority="2.1">

Паттерн Decorator (Декоратор)

Добавить поведение в низкоприоритетные шаблоны путем сопоставления уз­лам шаблонов с более высоким приоритетом импорта. Вызвать базовое поведение с помощью команды xsl:next-match.

Этот классический способ применения следующего соответствия мы уже об­суждали в разделе «Решение», поэтому здесь обойдемся без примера.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии