Многие языки программирования при объявлении переменной требуют указывать, какой тип данных будет ей присваиваться. Например, в языке Java код
int i = 15;
объявит переменную целого типа
int
с именем i
и присвоит ей значение 15
. В этом случае тип данных ставится в соответствие переменной. XSLT относится к динамически типизируемым языкам, в которых тип данных ассоциируется не с переменными, а со значениями.В XSLT выделяется пять типов данных:
□ булевый тип (boolean);
□ численный тип (number);
□ строковый тип (string);
□ множество узлов (node-set);
□ результирующий фрагмент дерева (result tree fragment).
Ниже мы подробно рассмотрим особенности работы со всеми пятью типами данных.
Булевый тип (boolean)
Булевый тип данных в XSLT может принимать два значения —
true
("истина") и false
("ложь"). В XSLT нет констант для выражения тождественной "истины" или "лжи", как во многих других языках программирования, для этих целей следует использовать функции true
и false
.Значение булевого типа могут быть получены путем сравнения других типов данных при помощи операторов сравнения (таких как "
=
", ">
", "<
") или как результат вычисления более сложных логических выражений с использованием операторов "and
", "or
" и функции not
.Булевый тип может быть неявно преобразован в число (
0
для false
и 1
для true
) или в строку ("false"
и "true"
соответственно).Примеры:
1=2
→ 0 (число)
not((2>1) and (2>3))
→ "true" (строка)
Численный тип (number)
Численный тип в XSLT определяется как 64-битное значение с плавающей точкой, двойной точности, соответствующее стандарту IEEE 754-1985. Этот стандарт используется во многих других языках программирования, и потому можно сказать, что арифметика в XSLT работает "как обычно". Вместе с тем, стандарт IEEE 754 имеет свои нюансы, которые обязательно надо учитывать в практике программирования на XSLT.
Согласно строгому определению, числа в XSLT имеют форму
s
×m
×2x
, где s
— знак числа, m
— его мантисса, а x
— экспонента. Эти числа имеют следующие значения:□ знак (
s
) равен +1
для положительных чисел и -1
для отрицательных;□ мантисса (
m
) — это положительное целое число в интервале от 0
до 253-1
включительно;□ экспонента (
x
) — это целое число в интервале от -1075
до 970
включительно.Таким образом, числа в XSLT находятся в интервале приблизительно от
-10317
до 10317
.Кроме этого выделяются пять особых значений.
□ Отрицательная бесконечность. Это значение представляет отрицательные числа, меньшие, чем
-10317
; оно соответствует математическому значению -∞. Отрицательная бесконечность может быть результатом таких операций, как деление отрицательного числа на нуль или умножение двух очень больших (в абсолютном значении) чисел разного знака в случае, когда для записи их произведения не хватит 64 бит.□ Положительная бесконечность. Это значение представляет очень большие положительные числа, превосходящие
10317
; оно соответствует математическому значению ∞. Положительная бесконечность может быть результатом таких операций, как деление положительного числа на нуль или умножение двух очень больших (в абсолютном значении) чисел одного знака в случае, когда для записи их произведения не хватит 64 бит.□ Отрицательный нуль. Это значение соответствует значению предела
-1/x
при x
, стремящемся к бесконечности. Отрицательный нуль может быть результатом таких операций, как деление отрицательного числа на бесконечность или положительного числа на отрицательную бесконечность. Отрицательный нуль может также быть получен путем деления отрицательного числа на очень большое положительное число, или, наоборот, в случае, когда для записи частного не хватает 64-битной точности.□ Положительный нуль (предел
1/x
при x
, стремящемся к бесконечности). Результат таких операций, как вычитание числа из самого себя, деление положительного числа на положительную бесконечность или отрицательного — на отрицательную бесконечность. Положительный нуль может также быть частным деления двух чисел одного знака, если для записи результата не хватает 64-битной точности.□ Особое значение
NaN
, "не-число" (англ. "not-a-number"). Результат преобразования нечислового строкового значения в числовой формат.Примеры особых значений:
-1 div 0
→отрицательная бесконечность
1 div 0
→положительная бесконечность
1 div (-1 div 0)
→отрицательный нуль
-1 div (1 div 0)
→отрицательный нуль
1 div (1 div 0)
→положительный нуль
-1 div (-1 div 0)
→положительный нуль
1-1
→положительный нуль
number('one')
→ NaN, не-число
number('NaN')
→ NaN, не-число
Все числовые значения, кроме
NaN
являются упорядоченными, иначе говоря, для них определены операции сравнения.□ Отрицательная бесконечность является наименьшим численным значением. Две отрицательные бесконечности равны между собой.
□ Отрицательные конечные числа больше отрицательной бесконечности, но меньше отрицательного нуля.
□ Отрицательный и положительный нули считаются равными.
□ Положительные конечные числа больше положительного нуля, но меньше положительной бесконечности.
□ Положительная бесконечность является наибольшим числом. Две положительные бесконечности находятся в равенстве, все остальные числа всегда будут меньше.
Примеры□
1 div (1 div 0) < 1 div 0
→ true
(положительный нуль меньше положительной бесконечности);
□
1 div 0 < 2 div 0
→ false
(положительный нуль равен другому положительному нулю);
□
-2 div 0 > -1 div 0 > false -1 div 0 = -2 div 0
→ true
(отрицательные бесконечности равны между собой);
□
-1 div 0 < -1
→ true
(отрицательная бесконечность меньше любого отрицательного числа);
□
-1 < -2 div (1 div 0)
→ true
(любое отрицательное число меньше отрицательного нуля);
□
-2 div (1 div 0) = 1-1
→ true
1 div (1 div 0) > -2 div (1 div 0)
→ false
(отрицательный нуль равен положительному нулю);
□
1 > 1 div (1 div 0)
→ true
(любое положительное число превосходит положительный нуль).
Нечисловые значения,
NaN
, являются неупорядоченными — это означает, что, сравнивая их с другими числами, нельзя установить — больше они, меньше или равны. Результат сравнений операторами "<
", "<=
", "=
", ">
", ">=
" будет "ложью", если хотя бы одно из сравниваемых значений — NaN
. Единственное, что можно с точностью сказать о NaN
— это то, что они не равны никакому другому числу, включая, собственно, нечисловые значения. То есть, если хотя бы один из операндов — NaN
, результатом сравнения с использованием оператора "!=
" будет "истина". Это влечет за собой интересный способ проверки, является ли значение некоторой переменной нечисловым или нет: выражение $x!=$x
(буквально значение переменной x
не равно значению переменной x
) обратится в "истину" в том и только том случае, если значением $x
является NaN
. В шаблонных правилах эта проверка может быть записана при помощи элемента xsl:if
:
This is not a number (NaN).
Арифметические операции в XSLT никогда не вызывают ошибки. Деление на нуль, не разрешенное во многих языках программирования, не является для XSLT исключительной ситуацией. Частным такого деления будет положительная или отрицательная бесконечность. Но все же, следует осторожно использовать "опасные" выражения, например, в сравнениях. Несколько необычное поведение операторов сравнения в операциях с NaN может создать в программе курьезные, но трудно обнаруживаемые ошибки — можно легко забыть о том, что некоторые значения могут быть не равны сами себе.
Числа могут быть неявно преобразованы в булевый тип или в строку. При преобразовании числа в булевый тип, нуль (как положительный, так и отрицательный) и
NaN
преобразуются в false
, все остальные значения (включая бесконечности) — в true
.Примеры-1 div (1 div 0) > false 1 div 0
→ true
number('NaN') > false number('true')
→ false
Результатом неявного преобразования числа в строку является:
□ для конечных чисел — запись числа в десятичном формате;
□ для нулей (и положительного, и отрицательного) — "
0
";□ для бесконечностей (отрицательной и положительной) — "
-Infinity
" и "Infinity
" соответственно;□ для нечисловых значений — "
NaN
".Примеры-14 div 3
→ '-4.666666666666667'
0010.00050000
→ '10.0005'
-1 div (1 div 0)
→ '0'
1 - 1
→ '0'
1 div 0
→ 'Infinity'
-2 div 0
→ '-Infinity'
number('NaN')
→ 'NaN'
number('Infinity')
→ 'NaN'
Кроме неявного преобразования в строку, XSLT предоставляет широкие возможности для форматирования числовых значений с использованием функции
format-number
.Строковый тип (string)
Строки в XSLT практически не отличаются от строк в других языках программирования. Строка — это последовательность, состоящая из нуля или более символов определенного алфавита или набора символов (англ. character set). XSLT использует в качестве алфавита Unicode, что теоретически позволяет манипулировать любыми символами. Строки, которые не содержат символов, называются пустыми.
Строки в XSLT записываются в виде последовательностей символов, заключенных в кавычки — одинарные или двойные. Строки часто используются внутри атрибутов элементов, которые также могут быть заключены в двойные и одинарные кавычки и, потому, из соображений удобства, существует следующее негласное соглашение — значения атрибутов заключаются в двойные кавычки, а литералы (строковые значения) — в одинарные.
ПримерРезультатом выполнения элемента
будет строковый узел со значением
"text"
, в то время как элемент
создаст текстовый узел, значение которого будет равно текстовому значению элемента
text
. В первом случае выражение "text"
являлось строкой, литералом, во втором — путем выборки.Определенную сложность создает одновременное использование в литералах двойных и одинарных кавычек — некоторые процессоры будут воспринимать их как окончание значения атрибута. Такие строки проще всего будет задавать при помощи переменных, например:
'An author of "One Flew Over Cookoo's Nest"'
Следует особым образом отметить, что в XSLT, как XML-языке, символы могут быть заменены сущностями. Например, вместо символа "
"
" (двойные кавычки) можно использовать сущность "
, а вместо символа "'
" (одинарные кавычки) — '
. Это позволяет использовать внутри атрибутов такие конструкции, как'this is a string'
что эквивалентно
'this is a string'
На практике следует избегать таких приемов — они сильно запутывают текст программы. Сущности следует использовать только тогда, когда это действительно необходимо.
Строки можно сравнивать при помощи операторов "
=
" (равно) или "!=
" (не равно). При сравнении строки проверяются на посимвольное совпадение. Различные процессоры могут по-разному реализовывать процедуру сравнения, например, рассматривать разные символы с одним начертанием как одинаковые, но в одном можно быть точно уверенными — в случае, если на одних и тех же местах будут стоять символы с одинаковыми Unicode-кодами, строки будут равны.Пример'not' = 'noх74;'
→ true
Не следует также забывать, что один символ в строке — это необязательно один байт. Более того, это необязательно некое фиксированное число байт, ведь модель символов Unicode позволяет использовать для записи символа коды переменной длины.
Строка может быть приведена к булевому и численному типу.
В булевом представлении пустой строке соответствует
false
, непустой — true
. Содержимое непустой строки при этом никакой роли не играет. Булевое значение строки "false
" будет "истиной", равно, как и булевое значение строки "true
".Примеры'То be' or 'not to be'
→ true
'Full' and ''
→ false
'true' and 'false'
→ true
При приведении к численным значениям строки разбираются как числа в десятичном формате. Если строка не является представлением числа, ее численным значением будет
NaN
. В свою очередь, результатом любых вычислений, в которых участвует NaN
, будет также NaN
.Примеры'2' * '2'
→ 4
'one' + 'two'
→ NaN
'2/3' + '5/6'
→ NaN
'2' div '3' + '5' div '6'
→ 1.5
При работе с численными значениями можно использовать следующие операторы:
□
-
, унарный оператор, который выполняет отрицание своего единственного операнда — эта операция равносильна вычитанию числа из нуля;□
+
, бинарный оператор сложения, возвращает сумму своих операндов;□
-
, бинарный оператор вычитания, возвращает разность своих операндов;□
*
, бинарный оператор умножения, возвращает произведение своих операндов;□
div
, бинарный оператор деления, возвращает частное от деления первого операнда на второй;□
mod
, бинарный оператор, возвращающий остаток от деления первого операнда на второй.Обратим внимание на то, что оператор
div
в отличие от его трактовки в языке Pascal, выполняет нецелое деление. Результатом вычисления выражения 3 div 2
будет 1.5
, а не 1
.Динамическая типизация в XSLT позволяет использовать в выражениях значения разных типов — например, складывать строки и булевые значения или производить логические операции над числами. В тех случаях, когда тип данных значения отличается от типа данных, который требуется для операции, значения будут неявным образом приведены к требуемому типу, если это, конечно, возможно.
Множество узлов (node-set)
Несмотря на то, что XSLT оперирует логической моделью XML-документа как деревом с узлами, в XSLT нет типа данных, который соответствовал бы одному узлу. Вместо этого используется гораздо более мощный и гибкий тип данных, называемый множеством узлов (англ. node-set).
Множество узлов — это чистое математическое множество, состоящее из узлов дерева: оно не содержит повторений и не имеет внутреннего порядка элементов. Множества узлов выбираются особым видом XPath-выражений, которые называются путями выборки (англ. location path).
ПримерЛистинг 3.1. Документ<А>
<В/>
<С>
Предположим, что в этом документе мы хотим выбрать все узлы, являющиеся потомками элемента
C
, который находился бы в элементе A
, который находится в корне документа. Соответствующее XPath-выражение будет записано в виде /A/C//node()
.Для наглядности представим наш документ в виде дерева (рис. 3.12) и выделим в нем соответствующее множество узлов.
Рис. 3.12. Выбор множества узлов
Выбранное множество состоит из узлов элементов
D
, G
, E
, F
, H
, I (рис. 3.13):Рис. 3.13. Выбранное множество
Выбор множества не означает "клонирования", создания копий узлов, которые в него входят. Это просто выбор из всех узлов входящего документа некоторого набора, удовлетворяющего критериям, заданным путем выборки. С точки зрения программиста, множество узлов может быть представлено, как неупорядоченный список ссылок на узлы. При этом практическая реализация зависит от разработчиков конкретного процессора.
В общем случае, во множество узлов не входят дети узлов, содержащихся в нем. В нашем примере узлы элементов
G
, H
и I
вошли в выбранное множество только потому, что они соответствовали пути выборки /A/C//node()
. Если бы путь выборки имел вид /A/C/node()
(то есть, выбрать всех детей узла C
, содержащегося в узле A
, находящемся в корне документа), результат (рис. 3.14) был бы иным.Рис. 3.14. Другой путь выборки
Выбранное множество узлов имело бы вид (рис. 3.15):
Рис. 3.15. Выбранное множество
Для представления одного узла дерева в XSLT используется множество, состоящее из единственного узла. В предыдущем примере результатом выборки
/A
(выбрать узел A
, находящийся в корне документа) было бы множество, состоящее из единственного узла (рис. 3.16).
Рис. 3.16. Множество, состоящее из единственного узла
Несмотря на то, что множества узлов неупорядочены, во многих случаях обработка узлов множества производится в порядке просмотра документа. Некоторые элементы, обрабатывающие множества (такие, как
xsl:apply-templates
и xsl:for-each
) позволяют предварительно выполнять их сортировку при помощи элемента xsl:sort
.Множества узлов можно сравнивать при помощи операторов "
=
" (равно) и "!=
" (не равно). В отличие от равенства математических множеств, равенство множеств узлов A
и B
в XSLT означает то, что найдется узел a
, принадлежащий множеству A
и узел b
, принадлежащий множеству B
такие, что их строковые значения будут равны. Неравенство множеств означает наличие в них как минимум пары узлов с различными строковыми представлениями. Такие определения делают возможным при сравнении двух множеств одновременное выполнение равенства и неравенства.ПримерЛистинг 3.2. Входящий документ A
1
2
2
3
Листинг 3.3. Преобразование
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
and
Результатом этого преобразования будет строка:
true and true
Этот пример показывает, что множество дочерних элементов
int
элемента numbers
одновременно считает как равным, так и неравным множеству дочерних элементов byte
.Приведем еще несколько примеров.
Листинг 3.4. Входящий документ B
1
2
3
4
Результат:
false and true
Листинг 3.5. Входящий документ C
1
1
1
Результат:
true and false
С математической точки зрения операции сравнения множеств определены в XSLT, мягко говоря, странно. Например, единственный случай, когда для двух множеств не будет выполняться неравенство ("
!=
") — это когда все узлы обоих множеств будут иметь одинаковое строковое представление. Вместе с тем, операции сравнения множеств очень часто используются в качестве условий и потому нужно хорошо понимать различия между ними и математическими операциями сравнения.XSLT определяет единственную операцию над множествами — операцию объединения "
|
". Выражение "$A | $B
" возвратит множество узлов, присутствующих либо в $A
, либо в $B
, либо и там, и там.В XSLT нет встроенного оператора, который позволил бы установить принадлежность узла некоторому множеству. Для этой цели используется очень хитроумный прием, основанный на использовании функции
count
, которая возвращает количество узлов множества. Представим, что множество $node
содержит некоторый узел, и мы хотим проверить, входит ли он во множество $nodeset
. Сделать это можно при помощи выраженияcount($nodeset) = count($node | $nodeset)
которое будет истинным тогда и только тогда, когда
$node
полностью принадлежит $nodeset
.Этот метод позволяет реализовать в XSLT другие операции над множествами — пересечение, разность и симметрическую разность. Подробное описание этих операций приводится в главе 11.
В XSLT также нет оператора, который позволил бы проверить тождественность двух узлов. Например, если каждое из множеств
$A
и $B
содержит по одному узлу, при помощи простого оператора равенства ($A = $B
) мы не сможем проверить, один и тот же это узел или два разных узла с одинаковыми текстовыми значениями.Для того чтобы корректно выполнить такое сравнение, можно использовать функцию
generate-id
, которая для каждого из узлов дерева генерирует уникальный строковый идентификатор, присущий только этому узлу и никакому другому, причем для одних и тех же узлов идентификаторы всегда будут генерироваться одинаковыми. Таким образом, для проверки тождественности двух узлов, содержащихся во множествах $A
и $B
, будет достаточно сравнить их уникальные идентификаторы:generate-id($А) = generate-id($В)
Множества узлов могут быть преобразованы в булевые значения, числа и строки.
При преобразовании в булевый тип пустое множество узлов преобразуется в
false
, а непустое — в true
. Например, чтобы проверить, есть ли у текущего узла атрибут value
, можно написать:
Value attribute exists here.
Выражение
@value
возвратит непустое множество, состоящее из узла атрибута value
, если он есть в текущем элементе, или пустое множество, если такого атрибута нет. В первом случае логическим эквивалентом будет true
, во втором — false
, то есть текст будет выведен только в случае наличия атрибута value
.При преобразовании множества узлов в строку, результатом будет строковое значение первого в порядке просмотра узла множества.
ПримерЛистинг 3.6. Входящий документ
- A
- B
- C
- D
Листинг 3.7. Преобразование
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Результат:
A
При преобразовании множества узлов в число, множество сначала приводится к строке, а затем строка преобразуется в численное значение. Проще говоря, численным значением множества узлов будет численное значение первого узла в порядке просмотра документа.
ПримерЛистинг 3.8. Входящий документ
1
1.5
2
2.6
3
3.7
Листинг 3.9. Преобразование
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Результат:
0.5
Результирующий фрагмент дерева (result tree fragment)
Четыре типа данных, описанных выше, заимствованы языком XSLT из XPath. Вместе с тем, XSLT имеет и свой собственный тип данных, называемый result tree fragment (результирующий фрагмент дерева).
Для того чтобы понять этот тип данных, обратимся к примеру шаблона:
You may visit the following link.
Если мы применим это правило к части документа
http://www.xsltdev.ru
то получим следующий результат:
You may visit the following link.
В терминах деревьев выполнение этого шаблона показано на рис. 3.17.
Рис. 3.17. Часть дерева входящего документа и часть дерева сгенерированного документа
Поскольку XSLT оперирует документами, представленными в виде деревьев, уместнее будет сказать, что на самом деле шаблоны обрабатывают фрагменты входящего дерева и создают фрагменты исходящего. Последним и соответствует тип данных, который в XSLT называют результирующим фрагментом дерева. Попросту говоря, все, что создается шаблонами во время выполнения преобразования, является результирующими фрагментами и, в конечном итоге, дерево выходящего документа есть композиция этих фрагментов.
Структурно результирующий фрагмент дерева тоже является деревом — это просто отрезанная ветка. Шаблоны генерируют ветки, используя собственные инструкции, а также результаты выполнения шаблонов, которые они вызывают и в итоге множество веток срастается в одно большое дерево, которое и является целью преобразования.
Между тем, результирующие фрагменты деревьев могут и не попасть в само результирующее дерево, то есть совершенно не факт, что они всегда будут его частями. Например, результирующий фрагмент дерева может быть присвоен переменной как начальное значение. Это предоставляет следующие, очень интересные возможности.
□ Переменная может содержать дерево, являющееся результатом обработки документа. К сожалению, в чистом XSLT нельзя повторно обрабатывать части документов, однако, это реализуется при помощи механизма расширений.
□ Дерево может быть определено один раз в виде значения переменной и использовано несколько раз в выходящем документе.
ПримерЛистинг 3.10. Входящий документ
http://www.xsltdev.ru
Листинг 3.11. Преобразование
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
You may visit the following link.
Result as string:
Result as tree:
Листинг 3.12. Выходящий документ
Result as string:
You may visit the following link.
Result as tree:
You may visit the following
link.
Это преобразование легко понять, если обратиться к рис. 3.18.
Рис. 3.18. Генерация выходящего дерева с использованием переменных
Переменной
href
присваивается дерево, содержащее результат обработки элемента href
, находящегося в корне входящего документа. Затем переменная href
дважды используется в результирующем документе: один раз как строка, принадлежащая текстовому узлу, и один раз как результирующий фрагмент дерева.Дерево может быть преобразовано в булевое значение, число или строку. Некоторые процессоры позволяют также преобразовывать дерево во множество узлов, которое содержит единственный элемент — корневой узел этого дерева. Такие возможности бывают весьма полезными, но не являются, к сожалению, стандартными в текущей версии языка.
При преобразовании результирующего фрагмента дерева в булевое значение результатом всегда будет true, поскольку дерево никогда не бывает "пустым" — в нем всегда присутствует корневой узел.
При преобразовании дерева в строку результатом является конкатенация (строковое сложение) всех текстовых узлов дерева в порядке просмотра.
ПримерРезультирующий фрагмент дерева
You may visit the following
link.
приводится к строке
The result is: You may visit the following link.
При приведении дерева к числу, оно сначала преобразовывается в строку, а затем в число. Это означает, что деревья, в принципе, можно использовать в арифметических операциях, несмотря на то, что они для этого не предназначены.
ПримерЛистинг 3.13. Входящий документ:
1
1
2
2
3
3.5
Листинг 3.14. Преобразование:
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Integers:
Reals:
Reals minus integers:
Листинг 3.15. Результат
Integers:
123
Reals:
123.5
Reals minus integers:
0.5
Таблица преобразования типов
Для удобства использования все взаимные преобразования типов сведены в одну таблицу (табл. 3.2).
Таблица 3.2. Взаимные преобразования типов данных XSLT
Преобразовываемый тип Целевой тип boolean
(булевое значение)number
(число)string
(строка)node-set
(множество узлов)tree
(дерево)boolean
(булевое значение)0
→ false NaN
→false
другое →true
пустая →false
непустая →true
пустое →false
другое →true
всегда true
number
(число)false
→0 true
→1
разбирается, как число в десятичном формате мн-во → строка → число дерево → строка → число string
(строка)false
→"false" true
→"true"
десятичная запись числа строковое значение первого узла в порядке просмотра строковое сложение всех текстовых узлов дерева node-set
(множество узлов)нет нет нет нет tree
(дерево)нет нет нет нет
Переменные