Предыдущие главы этой книги были посвящены, в основном, тому, что может XSLT. Эти возможности, естественно, далеко не безграничны, да и нельзя ожидать слишком многого от специализированного языка, каким является XSLT.
Вместе с тем в XSLT-преобразованиях может оказаться очень полезной функциональность традиционных языков программирования. Например, математических функций и операторов, имеющихся в XPath, явно недостаточно для выполнения сложных вычислений, которые могут потребоваться в преобразованиях. XSLT не имеет встроенных функций для обращения к базам данных, оставляют желать лучшего средства для работы с множествами, текстовыми данными, датами и временными параметрами, словом задачи, не представляющие никакой сложности в традиционных языках программирования, могут быть чрезвычайно трудоемкими в XSLT. Скажем, тригонометрические функции можно реализовать в XSLT рекурсивными вычислениями последовательностей Тейлора, но насколько проще было бы использовать функции
sin
и cos
.
Таким образом, перед разработчиками языка стояла следующая дилемма: либо дублировать функциональность традиционных языков программирования в XSLT или XPath, либо изыскивать другие средства достижения тех же результатов.
Решение этой проблемы было довольно простым: вместо того, чтобы заново реализовывать все множество функций, которые только могут понадобиться при обработке документов, спецификация XSLT позволяет процессорам предоставлять интерфейсы для расширения XSLT и XPath за счет использования других языков программирования, например, Java, JavaScript или Python.
Существуют два способа расширения XSLT: при помощи функций и элементов расширения. Не изменяя структуры преобразований, два эти способа позволяют использовать при обработке документов возможности традиционных языков, что часто бывает очень полезно, а иногда и просто необходимо.
Рассмотрим примеры.
В языке XPath нет функции, которая генерировала бы псевдослучайное значение. Следующее преобразование приводит пример решения этой задачи с помощью стандартного класса
Math
языка Java.
xmlns:xsl="http://www.w3.org/1999/XSL/Transfоrm"
xmlns:math="java:java.lang.Math"
exclude-result-prefixes="math">
Результатом выполнения этого преобразования будет документ вида:
0.0538608432986305
Значение
0.0538608432986305
было получено посредством вызова метода random
класса java.lang.Math
и представляет собой некоторое псевдослучайное значение.
В качестве примера элемента расширения можно привести элемент
saxon:entity-ref
, определенный в XSLT-процессоре Saxon. Этот элемент создает в выходящем документе сущность с указанным именем.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
extension-element-prefixes="saxon">
Everybody
needs
space
Результатом этого преобразования будет текст
Everybody needs space
Несложно понять, насколько мощным средством являются расширения. Они фактически позволяют реализовать в преобразованиях функциональность традиционных языков программирования. Иными словами, почти все то, что можно сделать в обычных языках программирования, можно сделать и в преобразованиях.
Как это часто бывает, за дополнительные возможности приходится платить. Использование механизма расширений предъявляет определенные требования и накладывает некоторые ограничения.
□ Реализация механизма расширений в текущей версии языка целиком и полностью зависит от производителей процессоров. Вследствие этого интерфейсы расширения различных XSLT-процессоров могут отличаться даже для одного языка программирования. Это в итоге ведет к несовместимости расширений и непереносимости XSLT-решений между различными процессорами.
□ Возможность использования того или иного языка для написания расширений зависит от наличия интерфейса XSLT-процессора для этого языка. Не следует ожидать, что любой процессор сможет работать с расширениями, написанными на любом языке, то есть, иначе говоря, расширения привязывают преобразования к строго определенному процессору или, в лучшем случае, группе процессоров.
□ В то время как сам XSLT не имеет сторонних эффектов, расширения этого принципа придерживаться не обязаны. Вследствие этого преобразования, в которых есть расширения с побочными эффектами могут из-за различных методов обработки входящего документа генерировать на разных процессорах разный результат.
Итак, вопрос, использовать расширения или нет — это вопрос "функциональность против переносимости", и, хотя его решение будет всегда зависеть от конкретной задачи, существуют также и довольно общие критерии оценки, которые мы приведем в следующей таблице (табл. 10.1).
Таблица 10.1. Использование расширений: критерии за и против
Использовать расширения стоит, если: | Использовать расширения не стоит, если: |
---|---|
преобразования будут выполняться на заранее известном процессоре или группе процессоров; | целевой процессор неизвестен. Преобразования должны быть переносимы, насколько это возможно; |
в XSLT нет средств для выполнения требуемой задачи, либо они очень неэффективны; | в XSLT имеются средства для выполнения требуемой задачи; |
преобразование должно обладать побочными эффектами; | преобразование может обойтись без побочных эффектов; |
целевой процессор предоставляет интерфейс для хорошо известного разработчику языка программирования | интерфейс для нужного языка программирования в целевом процессоре отсутствует |
Подводя итог, образно выражаясь, скажем, что расширения — это пушка, стрелять из которой по воробьям рекомендуется только, когда не остается ничего другого, или воробьи достаточно велики.
К сожалению, не представляется возможным описать в одной главе интерфейсы расширения даже наиболее распространенных XSLT-процессоров. Вместо этого мы постараемся изложить основные принципы создания расширения, а также приведем несколько общих примеров, которые смогут послужить основой для создания частных решений.
Основным языком реализации расширений, приводимых в этой главе, будет Java. Пожалуй, Java является единственным языком, интерфейсы расширений для которого достаточно стандартизированы, чтобы можно было говорить об общих подходах. Однако, если читатель не знаком с этим языком — ничего страшного, ведь основное внимание в этой главе уделяется использованию расширений в XSLT, а не написанию их в других языках и Java-код приводится только для того, чтобы сделать примеры рабочими.
Прежде чем описывать использование функций расширения, вспомним, как мы вызывали в преобразованиях обычные функции, например, функцию
concat
:
Атрибут
select
содержит XPath-выражение concat('para', 'bellum')
, которое с точки зрения синтаксиса XPath является вызовом функции и соответствует продукции FunctionCall
:
[XP16] FunctionCall ::= FunctionName
'(' ( Argument ( ',' Argument ) * ) ? ')'
Аргументами функции являются выражения, а имя может быть любым корректным XML-именем (за исключением
node
, comment
, processing-instruction
и text
, которые используются для проверки типа узла):
[XP17] Argument ::= Expr
[XP35] FunctionName ::= QName - NodeType
В плане синтаксиса функции расширения ничем не отличаются от стандартных функций: они отвечают тем же самым грамматическим правилам. Единственное различие состоит в том, что функции стандартных библиотек XPath и XSLT принадлежат нулевому пространству имен, в то время как пространство имен функций расширения должно обязательным образом быть ненулевым. Иными словами, вызовы функций, имеющие вид
имя(аргумент, аргумент, ...)
, будут считаться вызовами функций базовых библиотек, а вызовы вида префикс:имя(аргумент, аргумент, ...)
будут считаться вызовами функций расширения.
Выражение
round(0.6)
является вызовом функции базовой библиотеки XPath, в то время как выражение
math:round(0.6)
является вызовом функции расширения.
Практически во всех процессорах пространство имен функции расширения является звеном, которое связывает ее с конкретной реализацией.
Элемент
xsl:value-of
вычисляет выражение math:round(0.6)
, которое является вызовом функции расширения. Само имя функции состоит из локальной части round и префикса math
, которому соответствует URI java:java.lang.Math
. В большинстве XSLT-процессоров вызов такого рода будет означать обращение к статической функции round
класса java.lang.Math
.
Простейшим случаем использования расширений в XSLT-процессорах, написанных на Java, является обращение к стандартным функциям пакетов Java.
Предположим, что входящий документ описывает координаты множества точек, а преобразование создает SVG-документ, содержащий линии, которые их последовательно соединяют.
SVG — это XML-язык для описания масштабируемой векторной графики (от англ. scalable vector graphics). SVG позволяет простым XML-синтаксисом описывать векторную графику. SVG-документы могут показываться в браузерах при помощи таких компонент, как Adobe SVG Viewer или Batik от Apache XML Project.
<точки width="200" height="200">
<точка x="-50" y="-50"/>
<точка x=" 50" y="-50"/>
<точка x=" 50" y=" 50"/>
<точка x="-50" y=" 50"/>
точки>
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg">
indent="yes"
doctype-public="-//W3C//DTD SVG 1.0//EN"
doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
Simple line-based figure
x1="{@x + 100}"
y1="{@y + 100}"
x2="{following-sibling::точка[1]/@x + 100}"
y2="{following-sibling::точка[1]/@y + 100}">
select="preceding-sibling::точка[last()]/@x + 100"/>
select="preceding-sibling::точка[last()]/@y + 100"/>
Результатом этого преобразования является следующий SVG-документ.
PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
Simple line-based figure
На рис. 10.1 приведен пример визуального представления этого документа.
Рис. 10.1. Визуальное представление полученного SVG-документа
Предположим теперь, что нам нужно не просто создать по данному множеству точек набор соединяющих их линий, но еще и произвести некоторые геометрические преобразования, например поворот на заданный в градусах угол α.
Формулы преобразования координат при повороте чрезвычайно просты:
x = x'∙cos(α) − y∙sin(α),
у = x'∙sin(α) + x'∙cos(α),
где x' и y' — старые координаты точки, x и y — новые координаты точки, а α — угол поворота. Единственная загвоздка состоит в том, что функций
sin
и cos
в базовой библиотеке XPath нет.
Самым простым выходом в такой ситуации является использование расширений. Например, в случае XSLT-процессора, который может использовать Java-расширения (Saxon, Xalan, Oracle XSLT Processor и так далее) надо будет лишь только объявить пространство имен вида:
xmlns:math="java:java.lang.Math"
и использовать функции
math:sin
и math:cos
.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg"
xmlns:math="java:java.lang.Math">
indent="yes"
doctype-public="-//W3C//DTD SVG 1.0//EN"
doctype-system="http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"/>
Simple line-based figure
x1="{$x1 * math:cos($alpha-radian) -
$y1 * math:sin($alpha-radian) + 100}"
y1="{$x1 * math:sin($alpha-radian) +
$y1 * math:cos($alpha-radian) + 100}"
x2="{$x2 * math:cos($alpha-radian) -
$y2 * math:sin($alpha-radian) + 100}"
y2="{$x2 * math:sin($alpha-radian) +
$y2 * math:cos($alpha-radian) + 100}"/>
Результатом этого преобразования будет следующий документ.
PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
xmlns="http://www.w3.org/2000/svg"
xmlns:math="java:java.lang.Math"
width="200"
height="200">
Simple line-based figure
x1="81.68060041188197" y1="31.70359014757173"
x2="168.29640985242827" y2="81.68060041188197"/>
x1="168.29640985242827" y1="81.68060041188197"
x2="118.31939958811803" y2="168.29640985242827"/>
x1="118.31939958811803" y1="168.29640985242827"
x2="31.70359014757173" y2="118.31939958811803"/>
x1="31.70359014757173" y1="118.31939958811803"
x2="81.68060041188197" y2="31.70359014757173"/>
Визуальное представление этого документа демонстрирует рис. 10.2, где представлен поворот, выполненный на 30°:
Рис. 10.2. Визуальное представление полученного SVG-документа
Анализируя полученный документ, мы можем заметить объявление пространства имен с префиксом
math
, которое было в него включено:
xmlns="http://www.w3.org/2000/svg"
xmlns:math="java:java.lang.Math"
width="200"
height="200">
...
Это тот самый случай, когда объявление пространства имен используется в самом преобразовании, но является лишним в выходящем документе. Для того чтобы избавиться от него, нужно просто включить префикс
math
в атрибут exclude-result-prefixes
элемента xsl:stylesheet
.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg"
xmlns:math="java:java.lang.Math"
exclude-result-prefixes="math">
...
Поскольку мы все равно используем в этом преобразовании расширения, мы можем написать свой собственный класс, который будет выполнять вычисление новых координат точки, исключив таким образом из преобразования все математические операции.
package de.fzi.xslt;
public class rot {
public static double X(double x, double y, double degree) {
double radian = Math.PI * degree / 180;
return x * Math.cos(radian) - y * Math.sin(radian);
}
public static double Y(double x, double y, double degree) {
double radian = Math.PI * degree / 180;
return x * Math.sin(radian) + y * Math.cos(radian);
}
}
Для того чтобы использовать методы этого класса в качестве функций расширения, немного изменим объявления в элементе
xsl:stylesheet
:
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg"
xmlns:rot="java:de.fzi.xslt.rot"
exclude-result-prefixes="rot">
Создание элемента
line
теперь может быть записано в виде:
x1="{rot:X($x1, $y1, $alpha) + 100}"
y1="{rot:Y($x1, $y1, $alpha) + 100}"
x2="{rot:X($x2, $y2, $alpha) + 100}"
y2="{rot:Y($x2, $y2, $alpha) + 100}"/>
Как мы отмечали выше, интерфейсы использования функций расширения весьма различаются между разными процессорами даже в случае такого переносимого языка, как Java. Отличия могут быть и в форме вызовов функций, и в форме объявлений пространств имен. Например, в процессоре Saxon пространство имен для класса
de.fzi.xslt.rot
может быть объявлено как:
xmlns:rot="java:de.fzi.xslt.rot"
в Xalan — как:
xmlns:rot="xalan://de.fzi.xslt.rot"
в Oracle XSLT Processor — как:
xmlns:rot="http://www.oracle.com/XSL/Transform/java/de.fzi.xslt.rot"
При этом сами вызовы во всех трех случаях будут одинаковыми:
rot:X($x, $y, $angle)
для метода X или
rot:Y($x, $y, $angle)
для метода Y.
При использовании функций расширения всегда есть вероятность того, что это расширение в силу каких-либо причин поддерживаться данным процессором не будет. Чаще всего это случается, во-первых, когда процессор просто физически не в состоянии вызвать эту функцию (например, процессор, написанный на C++, вряд ли будет содержать средства для выполнения Java-кода), во-вторых, когда расширение недоступно (например, процессор не в состоянии найти указанный Java-класс или динамическую библиотеку), и в-третьих, когда пространство имен объявлено неверно (например, с URI
java:de.fzi.xslt.rot
вместо xalan://de.fzi.xslt.rot
). Результатом обращения к неподдерживаемому расширению будет, естественно, ошибка.
XSLT позволяет избежать подобного рода ошибок путем предварительной проверки наличия заданной функции расширения. Для этой цели служит стандартная функция
function-available
(от англ. function is available — функция доступна)
boolean function-available(string)
Функция
function-available
принимает на вход строку, представляющую имя функции и возвращает true
, если эта функция может быть вызвана и false
— если нет.
Строковый аргумент этой функции представляет расширенное имя функции, он должен соответствовать продукции
QName
, то есть иметь вид имя
или префикс:имя
. В первом случае function-available
проверяет, реализована ли в данном процессоре стандартная функция с таким именем, например function-available('concat')
скорее всего, возвратит true
.
В случае, если аргумент
function-available
имеет вид префикс:имя
, функция function-available
проверяет доступность указанной функции расширения. Например, для того, чтобы проверить, может ли в данном контексте быть вызвана функция rot:X
, необходимо вычислить выражение
function-available('rot:X')
В данном случае
true
будет означать, что функция rot:X
может быть вызвана, false
— что функция в силу каких-то причин недоступна.
Функция
function-available
может помочь в создании преобразований, которые используют расширения, но при этом в некоторой степени сохраняют переносимость между различными процессорами. Достаточно написать несколько вариантов вызова функции расширения для каждого из процессоров, на которых преобразование должно работать, а затем использовать вариант с доступной данному процессору функцией расширения.
Для того чтобы обеспечить работоспособность расширения, реализованного классом
de.fzi.xslt.rot
в наиболее распространенных XSLT-процессорах, написанных на Java (как-то: Saxon, Xalan и Oracle XSLT Processor), прежде всего необходимо объявить соответствующие пространства имен:
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/2000/svg"
xmlns:saxon="java:de.fzi.xslt.rot"
xmlns:xalan="xalan://de.fzi.xslt.rot"
xmlns:oracle="http://www.oracle.com/XSL/Transform/java/de.fzi.xslt.rot"
exclude-result-prefixes="saxon xalan oracle">
...
Префикс
saxon
соответствует интерфейсу расширений в XSLT-процессоре Saxon, префикс xalan
— процессору Xalan и префикс oracle
— Oracle XSLT Processor.
Теперь осталось только найти поддерживаемый вариант расширения и произвести соответствующий вызов.
x1="{saxon:X($x1, $y1, $alpha) + 100}"
y1="{saxon:Y($x1, $y1, $alpha) + 100}"
x2="{saxon:X($x2, $y2, $alpha) + 100}"
y2="{saxon:Y($x2, $y2, $alpha) + 100}"/>
x1="{xalan:X($x1, $y1, $alpha) + 100}"
y1="{xalan:Y($x1, $y1, $alpha) + 100}"
x2="{xalan:X($x2, $y2, $alpha) + 100}"
y2="{xalan:Y($x2, $y2, $alpha) + 100}"/>
x1="{oracle:X($x1, $y1, $alpha) + 100}"
y1="{oracle:Y($x1, $y1, $alpha) + 100}"
x2="{oracle:X($x2, $y2, $alpha) + 100}"
y2="{oracle:Y($x2, $y2, $alpha) + 100}"/>
Necessary extension function is not available.
Supported processors are:
Saxon, Xalan, Oracle XSLT Processor.
В случае, если хотя бы одна из функций
saxon:X
, xalan:X
, oracle:X
будет доступна при обработке, она будет использована процессором для создания атрибутов элемента line
. В противном случае, процессор прервет выполнение преобразования и выведет указанное в элементе xsl:message
сообщение.
Нельзя не согласиться с тем, что приведенный выше способ не отличается элегантностью. Реализовывать свой вариант для каждого существующего процессора может быть довольно трудоемкой задачей — но такова уж плата за возможности расширений.
Одной из самых полезных функций расширения, которая, как правило, уже штатно реализована во многих процессорах (то есть, не требует дополнительного программирования) является функция
nodeset
. Эта функция позволяет в обход прямого запрета спецификации конвертировать результирующий фрагмент дерева во множество узлов.
Предположим, что мы создаем в переменной
rtf
результирующий фрагмент дерева следующего вида:
- 1
- 2
- 3
При попытке вычислить выражение вида
$rtf/item[2]
процессор в соответствии со спецификацией должен вывести ошибку, поскольку в этом фильтрующем выражении (см. продукцию [XP20] FilterExpr
) переменная rtf
должна содержать множество узлов, а не фрагмент дерева.
Текущая спецификация языка XPath совершенно явно говорит о том, что ни один тип данных не может быть преобразован во множество узлов. Функция
nodeset
действует в обход этого запрещения: она принимает на вход результирующий фрагмент дерева и возвращает множество, состоящее из корневого узла этого фрагмента.
В разных процессорах эта функция имеет различный синтаксис: она может носить имя
nodeset
или node-set
, или nodeSet
, однако семантика ее во всех случаях одинакова:
nodeset nodeset(result-tree-fragment)
Функция принимает на вход единственный аргумент, являющийся фрагментом дерева и возвращает множество узлов, состоящее из его корня.
Предположим, что мы обрабатываем входящий документ, содержащий трехбуквенные коды языков.
- ENG
- FRE
- GER
- GRE
- ITA
- NOR
- POR
- SPA
Фрагмент шаблона, обрабатывающий этот список, может выглядеть следующим образом:
Если в преобразовании нам понадобится доопределить входящий список кодами
RUS
и UKR
, не исправляя входящий документ, можно поступить следующим образом:
□ создать в переменной фрагмент дерева, содержащий элементы
item
входящего документа плюс элементы item
, доопределяющие этот список;
□ преобразовать дерево в список узлов;
□ обрабатывать список узлов точно так же, как мы бы обрабатывали сам входящий документ.
Преобразование, реализующее эти три шага, приведено ниже.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
exclude-result-prefixes="xalan">
- RUS
- UKR
Результат этого преобразования приведен на следующем листинге.
Вне всякого сомнения, функция
nodeset
является одним из наиболее востребованных в XSLT расширений, ведь возможность не только создавать, но и манипулировать уже созданными древовидными структурами является чрезвычайно полезной.
В качестве одного из примеров применения функции
nodeset
можно привести реализацию с ее помощью многошаговых преобразований.
В качестве примера рассмотрим схему трансформации, изображенную на рис. 10.3, в которой документ А сначала нужно обработать преобразованием 1, затем полученный результат (документ В) обработать преобразованием 2. Конечным результатом цепочки преобразований в данном случае является документ С.
Рис. 10.3. Двухшаговое преобразование
При выполнении преобразования процессор применяет шаблоны ко множеству узлов входящего документа и выстраивает результирующее дерево. Таким образом, для того, чтобы повторно применить шаблоны к уже обработанному документу (читай: к полученному дереву), нужно просто иметь возможность преобразовывать дерево во множество узлов.
Представим себе два простых преобразования,
first.xsl
и second.xsl
, первое из которых заменяет во входящем документе элементы а
на элементы b
, а второе — элементы b
на элементы с
.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Для того чтобы последовательно применить два этих преобразования к некоторому входящему документу
a.xml
, мы можем, например, дважды вызвать процессор:
java org.apache.xalan.xslt.Process -IN a.xml -XSL first.xsl -OUT b.xml
java org.apache.xalan.xslt.Process -IN b.xml -XSL second.xsl -OUT c.xml
В результате этих вызовов XSLT-процессор Xalan сначала применит преобразование
first.xsl
к документу a.xml
и сохранит результат в файле b.xml
, а затем обработает полученный документ b.xml
при помощи преобразования second.xml
и сохранит результат в файле c.xml
.
В качестве альтернативы, например, для тех случаев, когда пакетная обработка невозможна, мы можем создать преобразование, последовательно применяющее шаблоны преобразований
first.xsl
и second.xsl
к входящему документу. Для этого:
□ назначим шаблонам преобразования
first.xsl
режим first
, а шаблонам преобразования second.xsl
— режим second
;
□ в основном шаблоне применим шаблоны режима
first
к узлам входящего документа, сохранив результат в переменной b
;
□ приведем результирующее дерево, содержащееся в переменной
b
ко множеству узлов;
□ обработаем полученное множество узлов шаблонами режима
second
.
Следующий листинг демонстрирует предложенный подход.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"
exclude-result-prefixes="xalan">
a:
b:
c:
Ход этого преобразования лучше всего прокомментирует полученный результат.
<а>
1
2
а>
<а>
1
2
а>
1
2
<с>
1
2
с>
Другой, несколько реже используемой, но не менее мощной возможностью расширения XSLT являются элементы расширения. В отличие от обычных элементов, при выполнении преобразования элементы расширения не просто копируются в выходящее дерево. При их обработке процессор должен выполнить определенные действия. Например, многие XSLT-процессоры, написанные на Java, позволяют связывать элементы расширения с методами Java-классов.
Предположим, что при выполнении преобразования в выходящий документ нам необходимо включить информацию о том, когда документ был сгенерирован — добавить элемент вида:
This page was generated at 10:23.
Пожалуй, самым элегантным решением этой задачи будет использование элемента расширения, который копировал бы в выходящий документ текущее время. Иначе говоря, при выполнении шаблона вида:
This page was generated at .
элемент расширения
ext:time
должен быть заменен текущим временем. Ниже мы приведем пример реализации этого элемента для процессора Xalan.
Интерфейс программирования расширений в Xalan требует, чтобы для каждого элемента расширения был определен метод вида:
тип элемент(org.apache.xalan.extensions.XSLProcessorContext context,
org.apache.xalan.templates.ElemExtensionCall elem)
где
тип
— тип возвращаемого значения, а элемент
— локальная часть имени элемента расширения. Поскольку мы создаем элемент с локальной частью имени time и строковым типом возвращаемых данных, прототип нашего метода будет выглядеть как:
public String time(XSLProcessorContext context,
ElemExtensionCall elem)
Два аргумента, которые передаются методу элемента расширения, описывают контекст преобразования (
XSLProcessorContext
) и параметры вызова элемента расширения (ElemExtensionCall
). Чуть позже мы покажем, как можно использовать эти объекты для создания более функциональных элементов расширения; пока же продолжим с элементом ext:time
.
Следующим шагом мы создадим класс расширения
ext.java
, в котором реализуем описанный выше метод time
.
package de.fzi.xslt;
import java.util.Date;
import java.text.SimpleDateFormat;
import org.apache.xalan.extensions.XSLProcessorContext;
import org.apache.xalan.templates.ElemExtensionCall;
public class ext {
public String time(XSLProcessorContext context,
ElemExtensionCall elem) {
SimpleDateFormat df = new SimpleDateFormat("HH:mm");
return df.format(new Date());
}
}
Равно как и в случае с функциями расширения, связующим звеном между элементами и Java-имплементацией их семантики служат пространства имен. В нашем случае класс
de.fzi.xslt.ext
может быть связан с префиксом пространства имен ext
следующим объявлением:
xmlns:ext="xalan://de.fzi.xslt.ext"
Однако это еще не все. Для того чтобы элементы определенного пространства имен воспринимались процессором как элементы расширения, необходимо также явно указать префиксы этих пространств в атрибуте
extension-element-prefixes
элемента xsl:stylesheet
:
...
extension-element-prefixes="ext">
...
В итоге наше преобразование будет иметь следующий вид.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="xalan://de.fzi.xslt.ext"
extension-element-prefixes="ext">
This page was generated at .
Результатом этого преобразования будет документ вида:
This page was generated at 11:56.
Функциональность предложенного выше элемента расширения может быть легко расширена. Например, мы можем создать элемент
ext:date
, который будет выводить текущую дату или время в формате, зависящем от значения его атрибута pattern
.
package de.fzi.xslt;
import java.util.Date;
import java.text.SimpleDateFormat;
import org.apache.xalan.extensions.XSLProcessorContext;
import org.apache.xalan.templates.ElemExtensionCall;
public class ext{
public String date(XSLProcessorContext context, ElemExtensionCall elem) {
SimpleDateFormat df;
// Получаем значение атрибута pattern элемента расширения
String pattern = elem.getAttribute("pattern");
// Если атрибут pattern не определен,
// используем образец форматирования, определенный по умолчанию
if (pattern == null)
df = new SimpleDateFormat();
// Если атрибут pattern определен, используем его значение
// в качестве образца форматирования
else
df = new SimpleDateFormat(pattern);
return df.format(new Date());
}
}
В преобразовании этот элемент мы можем использовать как:
This page was generated at on
.
или:
This page was generated on .
В первом случае результатом будет:
This page was generated at 12:11 on 08/10/2001.
Во втором:
This page was generated on 08.10.01 12:11.
Естественно, семантика элементов расширения не ограничивается простым копированием в выходящий документ заданных значений. Элементы расширения могут выполнять гораздо более сложные функции, ограниченные, пожалуй, лишь только воображением разработчика. При этом элементы расширения на удивление удачно вписываются в структуру самого преобразования, ведь принцип их использования не сильно отличается от принципа использования самих элементов XSLT.
boolean element-available(string)
Функция
element-available
совершенно аналогична функции function-available
: она служит для проверки доступности в преобразовании того или иного элемента. Строковый параметр element-available
задает расширенное имя элемента; функция возвращает true
, если элемент с таким именем доступен, false
— если нет.
Предположим, что преобразование, созданное нами для процессора Xalan с использованием элемента расширения
ext:date
, будет выполняться на каком-либо другом процессоре. В этом случае велика вероятность того, что вследствие несовместимости механизмов расширений это преобразование завершится ошибкой — "чужой" процессор просто не сможет выполнить элемент ext:date.
Во избежание этого, мы можем использовать функцию
element-available
для проверки доступности элемента ext:date
до его вызова.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="xalan://de.fzi.xslt.ext"
extension-element-prefixes="ext">
This page was generated at on .
Другим способом обработки исключительных ситуаций, связанных с невозможностью выполнить тот или иной элемент преобразования, является использование элемента
xsl:fallback
. Синтаксическая конструкция этого элемента следующая:
Элемент
xsl:fallback
включается в "критическую" инструкцию, то есть в элемент, который может быть неизвестен процессору. В случае, если критическая инструкция отрабатывается нормально, содержимое xsl:fallback
попросту игнорируется. Иначе, если процессор в силу некоторых причин не может выполнить критическую инструкцию, вместо нее он будет выполнять содержимое дочернего элемента xsl:fallback
.
На тот случай, если процессор не сможет выполнить наш элемент расширения
ext:date
, мы можем "подстраховать" его следующим образом:
unknown time
В этом случае шаблон
This page was generated at
unknown time
.
в случае невозможности выполнить
ext:date
выведет
This page was generated at unknown time.
Заметим, что
xsl:fallback
применим не только для обработки исключительных ситуаций, связанных с элементами расширения. Наборы доступных процессору элементов XSLT будут также меняться от версии к версии, и xsl:fallback
вполне пригодится для обеспечения обратной совместимости. Например, если в версии XSLT 2.0 будет определен элемент xsl:for-each-group
, то xsl:fallback
можно использовать при создании альтернативного варианта для процессоров, которые еще не поддерживают новую версию:
Функции и элементы расширения с лихвой восполняют ограниченность языков XSLT и XPath, предоставляя возможности обычных императивных языков там, где они необходимы. Между тем, как показывает практика, задачи, которые приходится решать при помощи расширений, как правило, совершенно стандартны — например, разобранная выше функция nodeset, так или иначе реализована почти во всех XSLT-процессорах.
Инициатива EXSLT была порождена естественным желанием разработчиков иметь в своих XSLT-преобразованиях стандартные расширения и не дублировать усилия по решению общих проблем. В рамках EXSLT создаются стандартные библиотеки расширений XSLT для различных процессоров. Кроме того, EXSLT активно поддерживается многими разработчиками XSLT-процессоров с тем, чтобы обеспечить переносимость преобразований, использующих EXSLT-расширения.
Для конечного пользователя EXSLT — это множество библиотек расширений, которые можно загрузить с сайта http://www.exslt.org. Помимо этого, EXSLT-расширения уже являются встроенными для некоторых процессоров. Например, в процессоре Saxon реализовано большинство элементов и функций расширения EXSLT.
На данном этапе разработанные в рамках EXSLT библиотеки включают в себя следующие модули.
□
Common
— общие функции и элементы расширения. Включает функции exslt:node-set
и exslt:object-type
и элемент exslt:document
.
□
Math
— математические функции.
□
Sets
— функции для работы с множествами узлов (как-то: пересечение, разность и так далее).
□
Functions
— элементы для определения пользовательских функций.
□
Dates and Times
— элементы и функции для работы с временными параметрами.
□
Strings
— модуль для работы со строками.
□
Regular Expressions
— функции для работы с регулярными выражениями.
EXSLT покрывает большинство стандартных задач расширений — поэтому, прежде, чем браться за разработку собственных модулей расширения, следует проверить — нет ли уже реализованных аналогов. Кроме того, библиотеки EXSLT могут послужить хорошим примером программирования расширений.