В этой главе рассматривается ряд специальных возможностей XSLT, а именно шаблоны, параметры таблиц стилей и переменные. Все эти темы связаны друг с другом: параметры используются в именованных шаблонах, а переменные и параметры — это практически одно и то же, они различаются только способом создания.
Если задать шаблону имя, его можно вызывать по этому имени при помощи элемента
При вызове шаблона можно настроить его работу при помощи параметров. Например, вам может понадобиться, чтобы текст в создаваемых шаблоном текстовых узлах был на определенном языке — таком как английский, немецкий или французский — и вы можете создать новый параметр с именем language (язык). При вызове именованного шаблона, заданного для обработки этого параметра, вы можете установить язык в «en», «de» или «fr» и затем вызвать шаблон при помощи элемента
Переменные во многом похожи на параметры, с тем лишь отличием, что они по-другому создаются. Параметры, как правило, используются в именованных шаблонах, в то время как переменные применяются более широко, в выражениях XPath любого вида. Как и в языках программирования, в переменных XSLT можно хранить значения и обращаться к ним позже. Но есть одна важная особенность: за исключением особых обстоятельств, вы не можете изменять значение, хранимое в переменной. (В связи с этим некоторые авторы, пишущие об XSLT, считают неправильным называть их переменными.)
Переменные удобны для хранения значений, создание которых занимает длительное время, но в таблице стилей они часто используются. Вместо того, чтобы каждый раз заново создавать эти значения, сохраните их в переменной и ссылайтесь на ее значение. Как и в случае с параметрами, для получения значения переменной добавьте префикс «$». Например, для переменной с именем sandwich получить ее значение можно при помощи $sandwich. Как и в параметрах, в переменных можно хранить данные всех четырех типов данных XPath. Переменные также имеет смысл применять для хранения значений, которые позже в шаблоне будут изменены. Например, «.» обычно ссылается на контекстный узел шаблона, но внутри элемента
Кроме четырех типов данных XPath мы также будем использовать тип данных, поддерживаемый в XSLT 1.0, но не в XSLT 1.1 — фрагменты результирующего дерева, которые создаются элементами
Наконец, в этой главе мы также рассмотрим элемент
Для введения вполне достаточно; давайте перейдем к работе, и начнем мы с переменных.
Элемент <xsl:variable>: создание переменных
Для создания переменных в XSLT служит элемент
• name (обязательный). Имя переменной, устанавливается в QName;
• select (необязательный). Выражение XPath, задающее значение переменной. Если опустить этот атрибут, значение переменной будет определяться содержимым
Этот элемент может либо быть элементом верхнего уровня, либо применяться внутри тела шаблона. Элемент может сам содержать тело шаблона, но в таком случае нельзя использовать атрибут select.
Для создания переменной присвойте ее имя атрибуту name элемента
.
.
.
Получить значение переменной можно, добавив к ее имени префикс $:
Заметьте, что если вы присваиваете переменной литерал — как, например, присваивание значения «turkey» (индейка) переменной sandwich (бутерброд), — литерал необходимо заключить в кавычки, причем они должны отличаться от кавычек, в которые заключены значения атрибутов:
В XSLT 1.0 нет необходимости в атрибуте select — данные можно заключить внутри самого элемента
Формально, однако, при пропуске атрибута select в элементах
Стоит отметить, что имя переменной может включать префикс, как, например, star:PLANET, который должен соответствовать активному пространству имен. Сравнения осуществляются не сравнением префиксов, а проверкой фактического URI префикса — поэтому star:PLANET может быть тем же самым, что и nebula:PLANET, если пространства имен star и nebula соответствуют одному и тому же URI.
Область видимости переменной
Элемент
Областью видимости глобальной переменной является вся таблица стилей, подразумевая и импортированные или включенные таблицы стилей. Это означает, что переменная доступна в любом месте таблицы стилей, если только она не будет перекрыта локальной переменной с тем же именем. Можно даже обращаться к глобальной переменной до ее объявления. Однако нельзя создавать циклические ссылки (то есть если вы объявили a через b, нельзя объявлять b через а).
Область видимости локальной переменной ограничена следующими за ней братьями или потомками последующих братьев. В частности это значит, что если вы объявили переменную внутри таких элементов, как
Как правило, вы не можете изменять значение переменной, но вы можете перекрыть ее локальной переменной. То есть локальные переменные перекрывают глобальные в пределах области видимости локальных переменных. Пусть, например, я объявил переменную с именем movie (кинокартина):
.
.
.
Это элемент верхнего уровня, поэтому movie — глобальная переменная. Даже внутри шаблонов movie будет сохранять свое начальное значение, если не будет локальной переменной с таким же именем:
.
.
.
Однако если вы объявите локальную переменную movie, в шаблоне эта версия перекроет глобальную переменную:
.
.
.
В этом случае мы перекрыли глобальную переменную при помощи глобальной. Заметьте, однако, что нельзя снова объявить одну и ту же переменную в одном шаблоне с целью попытаться изменить ее значение:
.
.
.
За пределами шаблона локальная переменная невидима, и movie содержит глобальное значение:
.
.
.
Глобальные переменные тоже нельзя объявлять повторно:
Несмотря на все эти ограничения, вы можете менять значение переменной на каждом шаге цикла
Работа с переменными
Давайте рассмотрим примеры применения переменных. В следующем примере (листинг 9.1) я присваиваю переменной copyright сообщение об авторских правах и затем с ее помощью добавляю атрибут copyright во все элементы planets.xml.
Листинг 9.1. Применение переменной
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Вот результирующий документ, дополненный атрибутами copyright
.
.
.
Переменные зачастую удобны для хранения значении, зависимых от контекста, и мы сейчас рассмотрим еще один пример, о котором я упоминал в начале главы. В этом случае я преобразую planets.xml в новый документ, в котором для каждой планеты будет один элемент. Каждый из этих новых элементов будет содержать два элемента
Venus
Earth
Mercury
Earth
Mercury
Venus
Для примера я поочередно выбираю каждый элемент
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.
.
.
Теперь для проверки в цикле Листинг 9.2. Хранение в переменной информации, зависимой от контекста
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Теперь наша проблема решена.
Если у элемента
Строковое значение фрагмента результирующего дерева (то есть либо значение атрибута COLOR, либо значение по умолчанию, «blue») присваивается переменной COLOR. Теперь в выражениях XPath можно обращаться к значению этой переменной, $COLOR, а не к значению атрибута (@COLOR, гарантированно получая при этом значение цвета, даже если у соответствующего элемента отсутствует атрибут COLOR.
Вот еще один пример фрагмента результирующего дерева. В этом случае я сохраняю элемент буквального результата в переменной START_HTML:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
My page
.
.
.
Теперь я могу использовать этот элемент буквального результата где угодно:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
My page
Welcome to my page
И вот результат:
My page
Welcome to my page
Однако поскольку теперь фрагменты результирующего дерева не допускаются в XSLT 1.1, этот пример работать не будет. Как же тогда сохранить весь элемент буквального результата одновременно с возможностью простого вызова? Вы можете создать именованный шаблон.
Элемент <xsl:call-template>: применение именованных шаблонов
У элемента
и двух элементов
HTML, при помощи которого я создаю в документах HTML вертикальный разделитель:
Тогда я могу создать шаблон с именем «separator» (разделитель), использующий этот элемент буквального результата:
Это именованный шаблон — для его создания нужно только присвоить имя атрибуту name элемента
Заметьте, что этот шаблон не установлен для выбора чего-то конкретного. Для активизации шаблона необходимо вызвать его явно. Для этого служит элемент
• name (обязательный). Имя вызываемого шаблона, устанавливается в QName.
Следующий пример демонстрирует применение нашего шаблона «separator», для чего мне нужно было только вызвать его в соответствующих местах:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
The Planets Table
The Planets Table
Н1>
Name | Mass | Radius | Day |
Вот результат. Обратите внимание: элементы
и
были вставлены так, как требовалось:
The Planets Table
The Planets Table
Name | Mass | Radius | Day |
Mercury | .0553 (Earth = 1) | 1516 miles | 58.65 days |
Venus | .815 (Earth = 1) | 3716 miles | 116.75 days |
Earth | 1 (Earth = 1) | 2107 miles | 1 days |
Результирующий документ показан на рис. 9.1.
Рис. 9.1. Вызов именованного шаблона
Таким образом, вы можете обращаться к элементу буквального результата по имени, легко помещая его в результирующий документ. Но это слишком статично — элемент буквального результата всегда один и тот же. Впрочем, как можно заметить, вызов именованного шаблона во многом похож на вызов подпрограммы в языке программирования. Так же, как вы передаете данные в подпрограмму, вы можете передать данные в именованные шаблоны при помощи параметров.
Элементы <xsl:param> и <xsl:with-param>: создание параметров
Параметры во многом похожи на переменные — за тем исключением, что они обычно используются вместе с именованными шаблонами. Параметры позволяют вам передать в шаблон какие-то значения. Параметры создаются элементом
• name (обязательный). Имя переменной, устанавливается в QName;
• select (необязательный). Значение параметра по умолчанию. Устанавливается в выражение XPath.
Аналогично
После объявления параметра при помощи
Когда вы вызываете именованный шаблон при помощи
• name (обязательный). Имя переменной; устанавливается в QName;
• select (необязательный). Выражение XPath, задающее значение параметра. Если опустить этот атрибут, значение переменной будет определяться содержимым
Элемент может также содержать необязательное тело шаблона, создающее фрагмент результирующего дерева, но в XSLT 1.1 это уже не разрешено.
В следующем примере (листинг 9.3) я создаю именованный шаблон с именем «COLORS» (цвета), добавляющий цвет в данные планет в результирующем HTML-документе. Этот именованный шаблон использует единственный параметр, COLOR, который устанавливается в требуемый цвет. В примере я устанавливаю параметр COLOR в разные цвета для разных планет, используя
Листинг 9.3. Применение параметров таблиц стилей
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.
.
.
.
.
.
Данная таблица вызывает шаблон «COLORS» с разными значениями параметра COLOR. Я могу воспользоваться этими цветами при форматировании данных планет. Заметьте, что я объявил параметр COLOR при помощи
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.
.
.
.
.
.
И вот результат:
The Colorful Planets Table
The Colorful Planets Table
Name | Mass | Radius | Day |
Mercury | .0553 (Earth = 1) | 1516 miles | 58.65 days |
Venus | .815 (Earth = 1) | 3716 miles | 116.75 days |
Earth | 1 (Earth = 1) | 2107 miles | 1 days |
Этот результирующий документ показан на рис. 9.2 (пусть даже в черно-белом исполнении).
Рис. 9.2. Вызов именованного шаблона с параметрами
В следующем примере я использую параметры для локализации используемого в шаблоне языка. Я создаю новый шаблон localize с параметром language. Если language установлен в «en», означая английский, результирующий документ будет озаглавлен «Planets»; если language установлен в «de», немецкий, результирующий документ будет озаглавлен «Planeten», а если в «fr», французский, результирующий документ будет озаглавлен «Planetes».
Здесь я вызываю шаблон localize, установив language в «fr»:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
.
.
.
А вот как выглядит параметризованный шаблон «COLORS». Заметьте, что я объявляю параметр COLOR в шаблоне при помощи элемента
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Name | Mass | Radius | Day |
.
.
.
Вот результирующий локализованный документ:
Planètes
Name | Mass | Radius | Day |
|
|
|
|
Name | Mass | Radius | Day |
.
.
.
И вот результат — как видите, образцу удовлетворяет только Земля:
The Planets Table
The Planets Table
Name | Mass | Radius | Day |
Earth | 1 (Earth = 1) | 2107 miles | 1 days |
Но в данном случае существует больше вариантов. Например, ключу могут соответствовать несколько узлов, а это означает, что использующий ключ образец вернет набор узлов. Пусть, например, все планеты будут иметь одно и то же значение атрибута COLOR, «UNKNOWN» (неизвестен):
Если создать теперь ключ COLOR следующим образом:
The Planets Table
The Planets Table
Name | Mass | Radius | Day |
Mercury | .0553 (Earth = 1) | 1516 miles | 58.65 days |
Venus | .815 (Earth = 1) | 3716 miles | 116.75 days |
Earth | 1 (Earth = 1) | 2107 miles | 1 days |
Кроме случая, когда ключ выбирает несколько узлов, узел также может предоставить несколько значений для одного ключа. Предположим, вы установили ключ для работы с элементом
В этом случае каждый элемент
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
The Planets Table
The Planets Table
Name | Mass | Radius | Day |
.
.
.
И вот результирующий документ:
The Planets Table
The Planets Table
Name | Mass | Radius | Day |
Mercury | .0553 (Earth = 1) | 1516 miles | 58.65 days |
Venus | .815 (Earth = 1) | 3716 miles | 116.75 days |
Элемент <xsl:document>: создание нескольких результирующих документов
Весьма часто во время преобразования требуется создать несколько результирующих документов. Например, вам может понадобиться создать отчет о прохождении преобразования или разделить входной документ на несколько результирующих документов (например, разбить рассказ на главы). Или же вам может быть нужно создать набор результирующих документов, которые будут использоваться (как при создании кадрового (frameset) документа HTML) совместно с двумя документами, отображаемыми во фреймах.
Создание нескольких результирующих документов настолько распространенная задача, что практически все процессоры XSLT позволяют вам это сделать, даже в XSLT 1.0, где отсутствуют необходимые для этого средства. Процессоры XSLT добавляют для этого новые элементы расширения. Например, Xalan предоставляет элемент
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="com.lotus.xsl.extensions.Redirect"
extension-element-prefixes="xalan">
.
.
.
Теперь при помощи атрибута file элемента
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="com.lotus.xsl.extensions.Redirect"
extension-element-prefixes="xalan">
.
.
.
В процессоре Saxon применяйте элемент
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
extension-element-prefixes="saxon">
.
.
.
To же можно сделать и в XT; в этом случае используйте пространство имен
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xt="http://www.jclark.com/xt"
extension-element-prefixes="xt">
.
.
.
Все это создало весьма запутанную ситуацию, поскольку каждый разрабатывал свои собственные решения. По этой причине в XSLT 1.1 для поддержки нескольких результирующих документов был представлен новый элемент,
• href (обязательный). Показывает, где будет помещен новый документ. Устанавливается в абсолютный или относительный URI, без идентификатора фрагмента;
• method (необязательный). Определяет метод вывода, используемый для создания результирующего документа. Устанавливается в «xml», «html», «text» или QName, не являющееся NCName;
• version (необязательный). Определяет версию выходного документа. Устанавливается в NMTOKEN;
• encoding (необязательный). Устанавливает кодировку выходного документа. Устанавливается в строку;
• omit-xml-declaration (необязательный). Устанавливается в «yes» или «no», чтобы опустить объявление XML или не отпускать;
• cdata-section-elements (необязательный). Задает имена тех элементов, чье содержимое нужно вывести как разделы CDATA. Устанавливается в список QName, разделенных символами-разделителями;
• doctype-public (необязательный). Задает открытый идентификатор, который будет использован в объявлении вывода. Устанавливается в строковое значение;
• doctype-system (необязательный). Задает системный идентификатор, который будет использован в объявлении вывода. Устанавливается в строковое значение;
• indent (необязательный). Определяет, будет ли выходной документ выровнен для отображения структуры вложенности. Устанавливается в «yes» или «no»;
• media-type (необязательный). Устанавливает тип MIME вывода. Устанавливается в строковое значение;
• standalone (необязательный). Определяет, будет ли отдельное объявление включено в выходные данные, и если да, устанавливает его значение. Устанавливается в «yes» или «no».
Этот элемент содержит тело шаблона.
Следующий пример основан на упрощенной таблице стилей. В этом случае я создал в HTML-документе две рамки (frame), а также два HTML-документа для отображения в этих рамках (frame1.html и frame2.html). Первую рамку и ее документ я создал следующим образом:
Two Frames
Frame 1
This is frame 1.
.
.
.
Затем я могу создать вторую рамку и ее документ:
Two Frames
Заметьте, однако, что этот пример работоспособен только в XSLT 1.1.
На момент написания книги в одном процессоре XSLT элемент
Элемент <xsl:namespace-alias>: генерация таблиц стилей
Одна из основных задач XSLT состоит в преобразовании одних таблиц стилей в другие, хотя на первый взгляд это может быть неочевидно. Например, вам может потребоваться обработать длинные правила, которые нужно настроить непосредственно перед обработкой документов. И, как вы знаете, первоначально XSLT был представлен прежде всего для облегчения создания таблиц стилей объектов форматирования.
Но это порождает проблему: если вы обрабатываете таблицу стилей, полную таких элементов, как
Тут на помощь приходит элемент
• stylesheet-prefix (обязательный). Префикс пространства имен, используемый в таблице стилей. Устанавливается в NCName или «#default»;
• result-prefix (обязательный). Префикс, URI которого вы хотите присвоить пространству имен в результирующем документе. Устанавливается в NCName или «#default».
Следующий пример пояснит сказанное. Представьте, что вам нужно создать такую таблицу стилей:
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.1">
Заметьте, что здесь много элементов XSLT с префиксом «xsl», — значит, если вы попытаетесь создать их при помощи XSLT-преобразования, процессор XSLT попытается выполнить эти элементы. Чтобы избежать такой ситуации, я задал им новый префикс пространства имен, «xslt». Ниже показано, как это выглядит в таблице стилей, производящей предыдущую таблицу стилей (листинг 9.4). Заметьте, что нижеследующая таблица стилей просто выбирает корневой элемент исходного документа, чтобы она могла начать работать; она не использует исходный документ ни для каких других целей.
Листинг 9.4. Применение <xsl:namespace-alias>
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xslt="http://xslt">
.
.
.
Здесь я использую пространство имен "http://xslt" для префикса "xslt", но я смогу изменить его в выходном документе на правильное пространство имен XSLT, "http://www.w3.org/1999/XSL/Transform", воспользовавшись элементом
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xslt="http://xslt">
Вот результат. Заметьте, что здесь все еще используется префикс пространства имен «xslt», но это пространство имен теперь соответствует правильному пространству имен XSLT:
xmlns:xslt="http://www.w3.org/1999/XSL/Transform" version="1.1">
На этом глава завершается. В следующей главе мы рассмотрим, как работать с XSLT в коде.