Главная » XSLT » Создание полиморфного XSLT-кода

0

Задача

Требуется написать XSLT-код, который выполняет одну и ту же функцию над несопоставимыми данными.

Решение

В XSLT есть два вида полиморфного поведения. Первый напоминает пере­грузку, второй – переопределение.

В некоторых современных языках, особенно в C++, можно создавать перегру­женные функции, то есть функции с одинаковым именем, но с разным числом или типами аргументов. Компилятор определяет, какой вариант функции вызвать, анализируя переданные ей в момент вызова параметры. В XSLT прямого аналога этого механизма нет, но давайте рассмотрим следующую таблицу стилей:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="html" />

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

^^^>Площади фигур</title> </head> <body>

^1>Площади фигур<^1> <table cellpadding="2" border="1"> <tbody> <tr>

<th>Фигура</th> <№>Ид фигуры</th> <th>Площадь</th> </tr>

<xsl:apply-templates/> </tbody> </table> </body> </html> </xsl:template>

<xsl:template match="shape"> <tr>

<td><xsl:value-of select="@kind"/></td> <td><xsl:value-of select="@id"/></td> <xsl:variable name="area">

<xsl:apply-templates select="." mode="area"/> </xsl:variable>

<td align="right"><xsl:value-of select="format-number($area,’#.0 00′)"/> </td> </tr>

</xsl:template>

<xsl:template match="shape[@kind=,triangle’]" mode="area">

<xsl:value-of select="@base * @height"/> </xsl:template>

<xsl:template match="shape[@kind=,square’]" mode="area">

<xsl:value-of select="@side * @side"/> </xsl:template>

<xsl:template match="shape[@kind=,rectangle’]" mode="area">

<xsl:value-of select="@width * @height"/> </xsl:template>

<xsl:template match="shape[@kind=,circle’]" mode="area">

<xsl:value-of select="3.1415 * @radius * @radius"/> </xsl:template>

</xsl:stylesheet>

Обратите внимание, что у нескольких шаблонов есть режим area. Эти шабло­ны принимают разные типы фигур и по-разному вычисляют их площадь. Если вместо слова mode (режим) подставить имя функции, а вместо образец для сопос­тавления – тип данных, то сразу станет понятно, что перед нами вариант поли­морфной перегрузки.

Второй вид полиморфизма – переопределение. В этой книги вы видели мно­гочисленные примеры переопределения. Для реализации такого поведения в XSLT используется команда xsl:import. Приведенный ниже пример – это модифика­ция таблицы стилей для DocBook из примера 10.1. Чтобы сделать ее расширяемой, мы внесли следующие изменения:

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

?               используются наборы атрибутов, в которые импортирующая таблица мо­жет добавить новые атрибуты или переопределить существующие;

?               для каждого раздела документа используется простой шаблон, который можно переопределить в импортирующей таблице стилей;

?               введена точка вклинивания – вызов именованного шаблона extra-head- meta-data; реализация по умолчанию не делает ничего.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/>

<!– Переменные, определяющие различные компоненты стилей –> <xsl:variable name="standard-font-family" select=" ‘font-family:

Times serif; font-weight’ "/>

<xsl:variable name="chapter-label-font-size" select=" ‘font-size : 48pt’ "/> <xsl:variable name="chapter-title-font-size" select=" ‘font-size : 24pt’ "/> <xsl:variable name="epigraph-font-size" select=" ‘font-size : 10pt’ "/> <xsl:variable name="sect1-font-size" select=" ‘font-size : 18pt’ "/> <xsl:variable name="sect2-font-size" select=" ‘font-size : 14pt’ "/> <xsl:variable name="normal-font-size" select=" ‘font-size : 12pt’ "/>

<xsl:variable name="normal-text-color" select=" ‘color: black’ "/> <xsl:variable name="chapter-title-color" select=" ‘color: red’ "/>

<xsl:variable name="epigraph-padding" select=" ‘padding-top:4; padding-bottom:4′ "/>

<xsl:variable name="epigraph-common-style" select="concat($standard- font-family,'; ‘, $epigraph-font-size, ‘; ‘, $epigraph-padding, ‘; ‘, $normal-text-color)"/>

<xsl:variable name="sect-common-style" select="concat($standard-font- family,'; font-weight: bold’, ‘; ‘,$normal-text-color)"/>

<!– Наборы атрибутов –>

<xsl:attribute-set name="chapter-align">

<xsl:attribute name="align">right</xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="normal-align"> </xsl:attribute-set>

<xsl:attribute-set name="chapter-label" use-attribute-sets="chapter-align"> <xsl:attribute name="style">

<xsl:value-of select="$standard-font-family"/>; <xsl:value-of select="$chapter-label-font-size"/>; <xsl:value-of select="$chapter-title-color"/> <xsl:text>; padding-bottom:10; font-weight: bold</xsl:text> </xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="chapter-title" use-attribute-sets="chapter-align"> <xsl:attribute name="style">

<xsl:value-of select="$standard-font-family"/>; <xsl:value-of select="$chapter-title-font-size"/>; <xsl:value-of select="$chapter-title-color"/>

<xsl:text>; padding-bottom:15 0; font-weight: bold</xsl:text>

</xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="epigraph-para" use-attribute-sets="chapter-align"> <xsl:attribute name="style">

<xsl:value-of select="$epigraph-common-style"/><xsl:text>; font-style: italic</xsl:text> </xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="epigraph-attribution" use-attribute-sets="chapter-align"> <xsl:attribute name="style">

<xsl:value-of select="$epigraph-common-style"/> </xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="sect1">

<xsl:attribute name="align">left</xsl:attribute> <xsl:attribute name="style">

<xsl:value-of select="$sect-common-style"/>; <xsl:value-of select="$sect1-font-size"/> </xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="sect2">

<xsl:attribute name="align">left</xsl:attribute> <xsl:attribute name="style">

<xsl:value-of select="$sect-common-style"/>; <xsl:value-of select="$sect2-font-size"/> </xsl:attribute> </xsl:attribute-set>

<xsl:attribute-set name="normal">

<xsl:attribute name="align">left</xsl:attribute> <xsl:attribute name="style">

<xsl:value-of select="$standard-font-family"/>; <xsl:value-of select="$normal-font-size"/>; <xsl:value-of select="$normal-text-color"/> </xsl:attribute> </xsl:attribute-set>

<!– Шаблоны –> <xsl:template match="/"> <html>

<head>

<xsl:apply-templates mode="head"/> <xsl:call-template name="extra-head-meta-data"/> </head> <body style=

"margin-left:100;margin-right:100;margin-top:5 0;margin-bottom:5 0"> <xsl:apply-templates/>

<xsl:apply-templates select="chapter/chapterinfo/*" mode="copyright"/> </body> </html>

</xsl:template>

<!– Заголовок –>

<xsl:template match="chapter/title" mode="head">

<title><xsl:value-of select="."/></title> </xsl:template>

<xsl:template match="author" mode="head">

<meta name="author" content="{concat(firstname,’ surname)}"/> </xsl:template>

<xsl:template match="copyright" mode="head">

<meta name="copyright" content="{concat(holder,’ ‘,year)}"/> </xsl:template>

<xsl:template match="text( )" mode="head"/>

<!– Переопределить в импортирующей таблице стилей, если необходимо –> <xsl:template name="extra-head-meta-data"/>

<!— Тело –>

<xsl:template match="chapter">

<div xsl:use-attribute-sets="chapter-label"> <xsl:value-of select="@label"/> </div>

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

<xsl:template match="chapter/title">

<div xsl:use-attribute-sets="chapter-title"> <xsl:value-of select="."/>

</div> </xsl:template>

<xsl:template match="epigraph/para">

<div xsl:use-attribute-sets="epigraph-para"

<xsl:value-of select="."/> </div> </xsl:template>

<xsl:template match="epigraph/attribution">

<div xsl:use-attribute-sets="epigraph-attribution">

<xsl:value-of select="."/> </div> </xsl:template>

<xsl:template match="sect1">

<h1 xsl:use-attribute-sets="sect1">

<xsl:value-of select="title"/> </h1>

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

<xsl:template match="sect2">

<h2 xsl:use-attribute-sets="sect2">

<xsl:value-of select="title"/>

</h2>

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

<xsl:template match="para">

<p xsl:use-attribute-sets="normal">

<xsl:value-of select="."/> </p>

</xsl:template>

<xsl:template match="text( )"/>

<xsl:template match="copyright" mode="copyright">

<div style="font-size : 10pt; font-family: Times serif; padding-top : 100"> <xsl:text>Copyright </xsl:text> <xsl:value-of select="holder"/> <xsl:text> </xsl:text> <xsl:value-of select="year"/> <xsl:text>. All rights reserved.</xsl:text>

</div> </xsl:template>

<xsl:template match="*" mode="copyright"/> </xsl:stylesheet>

Обсуждение

Назвать XSLT объектно-ориентированным языком значило бы приукрасить истину. Поведение xsl:import лишь отдаленно напоминает наследование и работает эта команда на уровне таблицы в целом. Ни о какой инкапсуляции или абстрагировании данных и речи не идет. Тем не менее, программисты с объектно-ориентированным складом ума могут применить накопленный опыт для создания таблиц стилей с большей степенью модульности и повторной ис­пользуемости.

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

<xsl:template match="shape"> <tr>

<td><xsl:value-of select="@kind"/></td> <td><xsl:value-of select="@id"/></td> <xsl:variable name="area">

<xsl:call-template name="area"/> </xsl:variable>

<td align="right"><xsl:value-of select="format-number($area,’#.000′)"/> </td> </tr> </xsl:template>

<xsl:template name="area"> <xsl:choose>

<xsl:when test="@kind=’triangle’ ">

<xsl:value-of select="@base * @height"/> </xsl:when>

<xsl:when test="@kind=’square’ " >

<xsl:value-of select="@side * @side"/> </xsl:when>

<xsl:when test="@kind=’rectangle’ ">

<xsl:value-of select="@width * @height"/> </xsl:when>

<xsl:when test="@kind=’circle’ ">

<xsl:value-of select="3.1415 * @radius * @radius"/> </xsl:when>

</xsl:choose>

</xsl:template>

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

Переопределение – ключ к созданию модульного повторно используемого XSLT-кода. Однако повторная используемость обходится не даром, требуется за­ранее все спланировать. Конкретно, нужно обдумать три вещи:

1.              Что могут захотеть изменить пользователи вашей таблицы стилей?

2.              Что, на ваш взгляд, они не должны изменять?

3.              Что они, скорее всего, изменять не будут?

Если не ответить на первый вопрос, то таблица стилей окажется недостаточно гибкой и использовать ее повторно можно будет только путем копирования, вставки и изменения. Не ответив на второй вопрос, вы рискуете расставить капка­ны для пользователей. А не задумавшись над третьим вопросом, вы создадите из­лишне сложную таблицу стилей, которая будет работать медленно и окажется не­удобной для сопровождения.

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

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

По теме:

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