Технология XSLT — страница 10 из 66

Несмотря на отсутствие побочных эффектов, которое является одним из основных принципов XSLT, в преобразованиях можно использовать переменные. Переменная определяется как имя, с которым связывается некоторое значение, например:

создаст переменную с именем

url
и присвоит ей строковое значение
"http://www.xsltdev.ru"
. После этого переменную можно использовать в выражениях, например:

Для того чтобы отличать переменные от путей выборки, в выражениях их именам предшествует префикс "

$
": к значению переменной с именем
url
мы обращались как к
$url
.

Каждая из переменных имеет собственную область видимости (англ. visibility scope) — область документа преобразования, в которой может быть использовано ее значение. В зависимости от этого переменные могут быть глобальными (видимыми во всем преобразовании) и локальными (видимыми только в своем родительском элементе).

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

xsl:apply-templates
или
xsl:call-template
, или самим процессором, если речь идет о глобальных параметрах.

Использование переменных и параметров в XSLT отличается от их использования в привычных процедурных языках программирования типа С++, Java или Object Pascal из-за того, что их значения не могут изменяться. После того, как переменной или параметру присвоено некоторое изначальное значение, оно будет оставаться неизменным.

Это ограничение оказывает значительное влияние на стиль программирования преобразований. В этом смысле XSLT намного ближе к функциональным языкам типа Lisp. Например, в XSLT часто используется рекурсия, которая является одним из основных принципов функционального программирования.

Выражения

Многие из задач, которые, так или иначе, выполняются во время преобразования, связаны с вычислением выражений. Для этих целей в XSLT используется язык XPath, который помимо выбора множеств узлов дерева может также выполнять некоторые основные операции над данными.

Замечание

Несмотря на то, что XPath является самостоятельным языком, его роль в XSLT настолько велика, что здесь и далее мы будем рассматривать их как единое целое.

Можно выделить четыре основные задачи, для которых в преобразованиях используются выражения:

□ выбор узлов для обработки;

□ описание условий;

□ вычисление строковых значений, которые затем будут использованы в выходящем дереве;

□ вычисление множеств узлов, которые затем будут использованы в выходящем дереве.

Первая из задач непосредственно относится к самому процессу преобразования. Выражения, содержащиеся в атрибутах

select
элементов
xsl:apply-templates
и
xsl:for-each
, вычисляют множества, к узлам которых нужно применить шаблоны.

Пример
Листинг 3.16

В этом шаблонном правиле содержатся два элемента

xsl:apply-templates
, которые применяют шаблоны к множествам, выбранным выражениями
HEAD
и
BODY
соответственно.

Логические выражения XPath могут использоваться в качестве условий в таких элементах, как

xsl:if
и
xsl:when
, обеспечивая условную обработку.

Пример

Предположим, что нам нужно выводить различные сообщения в зависимости от возрастной информации, присутствующей во входящем документе:

Листинг 3.17. Входящий документ

Johnny

19

Листинг 3.18. Преобразование

 version="1.0"

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



Welcome, 

.


Sorry, 

, access denied.


Выделенные выражения

age >= 21
и
age < 21
(сущности
>
и
<
обозначают символы "
<
", и "
>
") определяют условия: содержимое первого элемента
xsl:if
будет выполняться, только если значение элемента
age
было не меньше
21
; содержимое второго — только если значение
age
было строго меньше
21
. Этот же самый шаблон может быть переписан с использованием элементов
xsl:choose
,
xsl:when
и
xsl:otherwise
.

Листинг 3.19

 version="1.0"

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


Welcome, 

.

Sorry, 

, access denied.


Результатом этого преобразования будет текст

Sorry, Johnny, access denied.

В этой строке имя

johnny
было заимствовано из входящего документа. Оно было создано элементом
xsl:value-of
:

Этот элемент вычислил значение выражения

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

В данном случае выражение

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

Листинг 3.20. Преобразование

 version="1.0"

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



Листинг 3.21. Выходящий документ

John

19

Элемент

xsl:copy-of
, который использовался в этом преобразовании, делает примерно то же самое, что и
xsl:value-of
— вычисляет значение выражения и включает его в дерево выходящего документа. Главным отличием
xsl:copy-of
является то, что при его выполнении вычисленное выражение не преобразуется в строку, что позволяет копировать в выходящее дерево множества узлов и результирующие фрагменты. В приведенном выше примере элементы
name
и
age
выходящего документа являются копиями элементов
name
и
age
входящего документа.

В преобразованиях выражения могут использоваться только в атрибутах элементов и никогда — в тексте самого преобразования. Элемент

 age

будет скопирован в выходящий документ, содержащий текст "

age
". Ни о каком вычислении выражения
age
речь, конечно же, не идет. Для того чтобы в результирующий документ был скопирован результат вычисления выражения, оно должно быть заключено в атрибут одного из вычисляющих элементов, например,
xsl:copy-of
:

В этом случае в элемент reason будет включен результат вычисления выражения

age
.

Виды выражений

Выражения языка XPath можно условно разделить на несколько основных типов:

□ пути выборки;

□ выражения фильтрации множеств;

□ выражения объединения множеств;

□ сравнения;

□ логические операции;

□ вызовы функций.

Рассмотрим подробно назначение и принципы работы каждого из типов выражений.

Пути выборки

Путь выборки является самым главным видом выражений, которые применяются в XSLT. Путь выборки в соответствии с некоторыми критериями выбирает множество узлов входящего документа.

Путь выборки может быть абсолютным (отсчитываться от корневого узла дерева) или относительным (отсчитываться от контекстного узла). Он может состоять из нескольких шагов выборки, каждый из которых относительно предыдущего шага (или начального узла) выбирает некоторое множество узлов. Результатом вычисления пути выборки является множество узлов, выбранное его последним шагом.

Пример

Предположим, что нам нужно получить узел элемента

title
, находящийся в элементе
head
, который находится в элементе
html
, находящемся в корне документа. Соответствующий путь выборки будет выглядеть как:

/html/head/title

Означает он примерно следующее:

□ "

/
" — ведущая косая черта обозначает абсолютный путь выборки, то есть путь, который отсчитывается от корневого узла;

□ "

html
" — шаг выборки элементов
html
;

□ "

/
" — разделитель шагов выборки;

□ "

head
" — шаг выборки элементов
head
;

□ "

/
" — разделитель шагов выборки;

□ "

title
" — шаг выборки элементов
title
.

Поскольку каждый из шагов отсчитывается от результатов предыдущего, шаг "

html
" будет выбирать элементы
html
, являющиеся дочерними элементами корневого узла и так далее. Пошаговое вычисление этого пути можно описать следующим образом:

□ "

/
" — путь, который выбирает корневой узел;

□ "

/html
" — путь, который выбирает дочерние элементы
html
корневого узла;

□ "

/html/head
" — путь, который выбирает дочерние элементы
head
элементов
html
, находящихся в корне документа;

□ "

/html/head/title
" — путь, выбирающий дочерние элементы
title
субэлементов
head
элементов
html
, которые находятся в корне документа.

Можно заметить очевидную аналогию с файловыми системами: пути выборки очень похожи на пути в структуре каталогов. Между тем, пути выборки гораздо мощнее:

□ для каждого из шагов выборки можно указать направление, в котором он будет выбирать узлы в документе — например, дочерние узлы, узлы- потомки или, наоборот, узлы-предки или братские узлы;

□ выбор узлов может проводиться не только по имени, но также и по типу или принадлежности определенному пространству имен;

□ выбранное на каждом шаге множество может фильтроваться одним или более предикатом (в отфильтрованном множестве останутся только те из выбранных узлов, которые поочередно удовлетворяют каждому из логических условий-предикатов).

Пути выборки являются средством получения информации, содержащейся в обрабатываемых документах. Неважно, что они возвращают множества узлов, неявное преобразование типов позволяет спокойно записывать выражения вида:

data/a + data/b

Несмотря на то, что

data/a 
и
data/b
являются множествами узлов, в арифметическом выражении они будут неявно преобразованы к численному типу. То же самое касается строкового и булевого типа.

Фильтрующие выражения

Фильтрующие выражения выполняют две основные задачи:

□ выбор из вычисленного множества узлов некоторого подмножества в соответствии с заданными логическими критериями-предикатами;

□ вычисление путей выборки относительно узлов фильтрованного множества.

Примеры

Предположим, что переменной

nodeset
присвоено некоторое множество узлов. Задачи типа "выбрать каждый второй узел этого множества" или "выбрать первый узел этого множества" или вообще, любой выбор узлов этого множества в соответствии с некоторыми заданными логическими критериями являются задачами фильтрации. Выражение
$nodeset[1]
выберет первый в порядке просмотра документа узел множества
$nodeset
; выражение
$nodeset[position() mod 2 = 0]
выберет четные узлы множества
$nodeset
. Здесь "
[1]
" и "
[position() mod 2 = 0]
" являются предикатами — логическими выражениями, которые фильтруют множество.

Фильтрующие выражения также позволяют вычислять пути выборки относительно узлов фильтруемых множеств.

Пример
Листинг 3.22. Входящий документ

a

b

c

1

2

3

Следующее преобразование демонстрирует использование относительных путей выборки в фильтрующих выражениях:

Листинг 3.23. Преобразование

 version="1.0"

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


Листинг 3.24. Входящий документ

a

b

c

1

2

3

Элемент

values
выходящего документа содержит множество, являющееся результатом вычисления выражения
(string | number)/value
. Это будет множество элементов
value
, принадлежащих элементам
string
или
number
.

Объединение множеств

Единственная операция над множествами, которая определена в XSLT, — это операция объединения. Если

$nodeset1
и
$nodeset2
— два множества узлов, то результатом вычисления

$nodeset1 | $nodeset2

будет множество узлов, которые принадлежат хотя бы одному из этих множеств.

Следует сказать, что, поскольку никакой тип данных не может быть преобразован во множество узлов, операнды объединения сами всегда должны быть множествами. То есть, выражение вида:

'а' | body/a

не добавит текстовый узел "

а
" к множеству элементов
а
, принадлежащих элементу
body
— оно просто будет некорректным.

Арифметические операции

Четыре основные бинарные операции — "

+
", "
-
", "
div
", "
mod
" и пятая, унарная операция отрицания "
-
" обеспечивают в XSLT основные арифметические действия. Поскольку любой из типов данных может быть преобразован в численный тип, в качестве операндов арифметических операций можно использовать что угодно — например, вычитать из строки булевое выражение:

'0.5' - true() 
 -0.5

Следует осторожно обращаться со знаком "

-
". Имена элементов и атрибутов могут включать этот знак и поэтому выражение
first-last
будет воспринято не как разность значений элементов
first
и
last
, а как путь выборки элементов с именами "
first-last
". Для того чтобы избежать таких казусов, операторы всегда следует выделять пробелами:

first - last

Операции сравнения

В XSLT имеются следующие шесть операторов сравнения:

□ "

=
" — равно;

□ "

!=
" — не равно;

□ "

<
" меньше;

□ "

>
" больше;

□ "

<=
" меньше или равно (не больше);

□ "

>=
" больше или равно (не меньше).

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

Операции сравнения определяются в спецификации в три этапа:

□ сначала сравнение, в котором участвуют множества узлов, определяется в терминах сравнения более простых типов данных;

□ затем для простых типов данных определяются равенство ("

=
") и неравенство ("
!=
");

□ наконец, для простых типов данных определяются сравнения "

<
", "
<=
", "
>
", "
>=
".

Сравнение, хотя бы один из операндов которого является множеством узлов, определяется следующим образом:

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

□ если один из операндов является множеством узлов, а второй имеет численный тип, сравнение будет истинным тогда и только тогда, когда во множестве узлов найдется такой узел, что сравнение текстового значения этого узла, преобразованного к числу, и самого численного операнда будет истинным;

□ если один из операндов является множеством узлов, а второй имеет строковый тип, сравнение будет истинным тогда и только тогда, когда во множестве узлов найдется такой узел, что сравнение его текстового значения и самого строкового операнда будет истинным;

□ если оба операнда являются множествами узлов, их сравнение будет истинным тогда и только тогда, когда найдется узел в первом множестве и узел во втором множестве, такие, что их сравнение будет истинным.

Примеры выражений, которые мы будем приводить, будут использовать следующий входящий документ (листинг 3.25).

Листинг 3.25. Входящий документ

0.5

50%

1/2

0.5

1.0

1.5

Примеры сравнений множества узлов с булевым значением:

/values/string = true() 
 true

В этом равенстве множество узлов сравнивается с булевым значением "истины". Множество узлов, выбираемое путем

/values/string
, приводится к булевому типу. Результатом приведения будет "истина", поскольку множество элементов string, принадлежащих элементу
values
, непусто. Таким образом, сравнение является проверкой на равенство двух "истин" — и результат, естественно, тоже будет "истиной".

/values/string != boolean(/values/boolean) 
 false

В этом случае мы проверяем множество узлов

/values/string
на неравенство булевому значению множества
/values/boolean
. Второй операнд является "истиной" (поскольку множество элементов
boolean
, принадлежащих элементу
values
, не пусто), а значит, все сравнение обратится в "ложь".

/values/string = boolean(/values/booleans) 
 false

В данном случае множество

/values/string
сравнивается с булевым значением множества
/values/booleans
, которое будет "ложью", поскольку это множество будет пустым. Таким образом, результат сравнения также будет "ложью".

/values/strings = boolean(/values/booleans) 
 true

Множества

/values/strings
и
/values/booleans
будут пустыми, поэтому, сравнивая первое с булевым значением второго, мы получим "истину", так как "ложь" равна "лжи".

Примеры сравнения множества узлов с числом:

/values/number < 1 
 true

Множество узлов

/values/number
может считаться меньше, чем число
1
, поскольку первый элемент этого множества имеет строковое значение "
0.5
", при приведении которого к числу мы получаем
0.5
, что меньше
1
.

/values/number > 1 
 true

То же самое множество узлов может считаться также и большим

1
, поскольку последний элемент этого множества имеет строковое значение "
1.5
", при приведении которого к числу мы получаем
1.5
, что больше
1
.

/values/number = 1 
 true

Второй элемент множества

/values/number
равен
1
, то есть и это сравнение будет истинным.

Примеры сравнения множества узлов со строковым значением:

/values/number = '1' 
 false

Множество

/values/number
не будет равно строке "
1
", поскольку ни один из узлов этого множества не имеет строкового значения "
1
".

/values/number = '1.0' 
 true

Множество

/values/number
будет считаться равным строке "
1.0
", поскольку второй узел этого множества имеет текстовое значение "
1.0
".

/values/number != '1.0' 
 true

Множество

/values/number
может также считаться не равным строке "
1.0
", поскольку первый узел этого множества имеет текстовое значение "
0.5
", не равное "
1.0
".

Примеры сравнения двух множеств узлов:

/values/number = /values/string 
 true

Для двух этих множеств будет выполняться равенство, поскольку оба они имеют по узлу с равными строковыми значениями — первый узел

/values/number
и первый узел
/values/string
равны "
0.5
".

values/number != /values/string 
 true

Для этих же множеств будет выполняться неравенство, поскольку в них найдется неравная пара узлов (например, узел с текстовым значением "

1.0
" в
/values/number
и узел с текстовым значением "
50%
" в
/values/string
).

Определим теперь равенство и неравенство значений простых типов. При проверке на равенство или неравенство оба операнда приводятся к общему типу и сравниваются. Приведение к общему типу производится следующим образом:

□ если хотя бы один из операндов имеет булевый тип, второй также приводится к булевому типу;

□ иначе, если хотя бы один из операндов — число, второй также приводится к численному типу;

□ иначе, если хотя бы один из операндов — строка, второй также приводится к строковому типу.

После того, как оба операнда приведены к некоторому общему типу, они проверяются на равенство или неравенства как два значения этого общего типа:

□ два булевых значения равны тогда и только тогда, когда они оба являются "истиной" или оба являются "ложью";

□ равенство численных значений понимается в обычном смысле (строгое определение равенства чисел дано в стандарте IEEE 754, но вряд ли оно представляет для нас большой интерес);

□ две строки равны тогда и только тогда, когда они представлены одинаковыми последовательностями Unicode-символов.

Два значения простых типов (то есть — булевого, численного или строкового типа) неравны тогда и только тогда, когда для них не выполняется равенство.

Примеры сравнения значений простых типов:

true() = 1 
 true

При приведении числа

1
к булевому типу получается "истина", что и подтверждается этим равенством.

true() = 100 
 true

Результатом приведения числа

100
к булевому типу также является "истина".

false() = 'false' 
 false

При приведении непустой строки "false" к булевому типу, получается "истина". Отсюда — неверность равенства.

.5 =0.5 
 true

.5
и
0.5
представляют одно и то же число, хоть и они записаны в разной форме.

.5 = '0.5' 
 true

Это равенство также будет верным, поскольку результатом преобразования строки "

0.5
" в число будет также
0.5
.

1 != 'two' 
 true

Результатом преобразования строки "

two
" в численный тип будет значение
NaN
, которое не равно
1
.

При сравнении с использованием операторов "

<
", "
<=
", "
>
" и "
>=
", оба операнда всегда приводятся к численному типу и сравниваются как числа.

Примеры сравнений с использованием операторов "

<
", "
<=
", "
>
" и "
>=
":

false () > true() 
 false

В численном виде

true()
соответствует
1
, a
false()
0
, то есть это сравнение равносильно сравнению
0 > 1
, результатом которого является "ложь".

'0' <= false() 
 true

Это сравнение равносильно сравнению

0 <= 0
, результатом его будет "истина".

'1' >= '0' 
 true

Это сравнение равносильно сравнению

1 >= 0
, результатом его будет "истина".

Следует обратить внимание, на то, что символы "

<
" и "
>
" заменены сущностями
<
и
>
соответственно. В случае символа "
<
" такая замена необходима, чтобы не нарушать выражениями синтаксис XML-документа. Заменять символ "
>
" обязательной нужды нет, это делается исключительно из соображений единообразности.

Логические операции

В XSLT имеются две логические операции —

or
и
and
. Эти операции бинарны, то есть каждая из них определена для двух операндов. Если операнды не являются булевыми значениями, они неявным образом приводятся к булевому типу.

Семантика

or
и
and
очевидна — они соответствуют операциям логического сложения и умножения.

Результатом операции

or
будет "истина", если хотя бы один из операндов является "истиной". При этом если первый операнд имеет значение
true
, второй операнд не вычисляется — результат и так будет "истиной".

Результатом операции

and
будет "истина", если оба операнда истинны. При этом если первый из операндов — "ложь", то второй операнд не вычисляется — результат и так будет "ложью".

Функции

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

Функции можно условно разделить на стандартные функции, которые определены в XPath и XSLT и должны поддерживаться (хотя на самом деле поддерживаются далеко не всегда) всеми XSLT-процессорами, и функции расширения, которые могут создаваться разработчиками в дополнение к стандартным функциям.

Контекст вычисления выражений

Выражения всегда вычисляются в некотором контексте — окружении, которое зависит от того, какая часть документа обрабатывается XSLT-процессором в данный момент, и какие объявления присутствовали в самом преобразовании.

Контекст преобразования состоит из узла, называемого контекстным узлом, двух целых чисел — размера контекста и позиции в контексте, объявлений переменных, объявлений пространств имен и библиотеки функций.

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

Пример

Для того чтобы показать, как изменяется контекст во время преобразования, мы напишем шаблон, который заменяет все элементы входящего документа элементами вида:

 name="имя элемента"

 context-position="позиция в контексте"

 context-size="размер контекста"

 string-value="строковое значение">

 ...

Листинг 3.26. Преобразование

 version="1.0"

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



   name="{name()}"

   context-position="{position()}"

   context-size="size()"

   string-value="{.}">


Листинг 3.27. Входящий документ

A

B

C

D

E

F

Листинг 3.28. Выходящий документ

 context-position="1" context-size="1" string-value="ABCDEF">

  context-position="1" context-size="2" string-value="ABC">

  context-position="1" context-size="3" string-value="A"/>

  context-position="2" context-size="3" string-value="B"/>

  context-position="3" context-size="3" string-value="C"/>

  context-position="2" context-size="2" string-value="DEF">

  context-position="1" context-size="3" string-value="D"/>

  context-position="2" context-size="3" string-value="E"/>

  context-position="3" context-size="3" string-value="F"/>

Модель