Описывая основы построения XML-документов, мы отмечали, что иерархическая организация информации в XML лучше всего описывается древовидными структурами. Дерево — это четкая, мощная и простая модель данных и именно она была на концептуальном уровне применена в языках XSLT и XPath. Как пишет Кнут [Кнут 2000], "деревья — это наиболее важные нелинейные структуры, которые встречаются при работе с компьютерными алгоритмами". Добавим, что это без сомнения самая важная структура из тех, которыми оперируют языки XSLT и XPath.
В этих языках документ моделируется как дерево, состоящее из узлов. Узлы дерева соответствуют структурным единицам XML — элементам, атрибутам, тексту и так далее, а дуги, ветки — отношениям между этими единицами. Простейшим примером является принадлежность одних элементов другим. Документ вида
<а>
<с/>
а>
может быть представлен деревом (рис. 3.1).
Рис. 3.1. Представление документа в виде дерева
Аналогия совершенно очевидна — элемент
а
содержит элементы b
и с
, в то время как в дереве вершина а
является родительским узлом для вершин b
и с
.
Естественно, XML-документы состоят далеко не из одних только элементов и потому для того, чтобы дерево достоверно отражало структуру и содержание документа, в нем выделяются узлы следующих семи типов:
□ корневой узел;
□ узлы элементов;
□ узлы атрибутов;
□ текстовые узлы;
□ узлы пространств имен;
□ узлы инструкций по обработке;
□ узлы комментариев.
Каждый узел дерева имеет соответствующее ему строковое значение, которое вычисляется в зависимости от типа. Строковым значением корневого узла и узла элемента является конкатенация (строковое сложение) строковых значений всех их потомков, для остальных типов узлов строковое значение является частью самого узла. Порядок вычисления строковых значений узлов будет более детально определен в описании каждого из типов.
Мы подробно разберем каждый из типов узлов чуть позже, а пока отметим, что дерево в XSLT и XPath является чисто концептуальной моделью. Процессоры не обязаны внутренне представлять документы именно в этой структуре, концептуальная модель означает лишь то, что все операции преобразования над документами будут определяться в терминах деревьев. Для программиста же это означает, что вовсе необязательно вникать в тонкости реализации преобразований в том или ином процессоре. Все равно они должны работать, так, как будто они работают непосредственно с деревьями.
Отметим также и то, что на практике некоторые из процессоров вообще не воссоздают внутреннюю модель документа, экономя, таким образом, ресурсы. Документы могут быть слишком объемными, и это препятствует загрузке их в память в виде древовидной структуры.
Некоторые из процессоров, напротив, используют DOM-модель документа для внутреннего представления обрабатываемой информации. Несмотря на большие требования к ресурсам памяти, такое решение также может иметь свои плюсы, например, универсальность и легкая интеграция в существующие XML-решения на базе DOM.
Но, так или иначе, с воссозданием древовидной структуры документа в памяти или нет, процессоры все равно оперируют одной и той же концептуальной моделью, которой мы сейчас и займемся.
Прежде, чем мы приступим к рассмотрению типов узлов и отношений между ними, необходимо определиться с самой структурой дерева. Древовидная структура задает для своих элементов отношение ветвления, очень похожее на строение обычного дерева — есть корневой узел (ствол), от которого происходят другие узлы (ветки).
Формально [Кнут 2000] дерево определяется, как конечное множество T, состоящее из одного или нескольких элементов (узлов), обладающих следующими свойствами:
□ во множестве T выделяется единственный узел, называемый корневым узлом или корнем;
□ все остальные узлы разделены на m≥0 непересекающихся множеств T1, …, Tm, каждое из которых в свою очередь также является деревом.
Деревья T1, …, Tm называются поддеревьями корня дерева T.
Это определение является рекурсивным, то есть в нем дерево определяется само через себя. Конечно, существуют нерекурсивные определения, но, пожалуй, следует согласиться с тем, что рекурсивное определение лучше всего отражает суть древовидной структуры: ветки дерева также являются деревьями.
В XSLT и XPath деревья являются упорядоченными, то есть для множеств T1, …, Tm задается порядок следования, который называется порядком просмотра документа. В XSLT деревья упорядочиваются в порядке появления текстового представления их узлов в документах.
Существует множество способов графического изображения деревьев. Мы будем рисовать их так, что корень дерева будет находиться наверху, а поддеревья будут упорядочены слева направо. Такой способ является довольно стандартным для XML, хотя и здесь существует множество вариантов. Примером изображения дерева может быть следующий рисунок (рис. 3.2):
Рис. 3.2. Изображение дерева
Мы часто будем говорить о дочерних узлах, родительских узлах, братских узлах, узлах-предках и узлах-потомках. Дадим определения этим понятиям.
□ Дочерним узлом текущего узла называется любой из корней его поддеревьев. Например, в дереве на рис. 3.2 дочерними узлами узла
а
являются узлы b и с
, а дочерними узлами узла b
— узлы d
и e
. Узел с
не имеет дочерних узлов — такие узлы иначе называются листьями.
□ Каждый узел называется родительским узлом корней своих поддеревьев. На рис. 3.2 узел
а
является родителем узлов b
и с
, а узел b
— родителем узлов d
и e
.
□ Корни поддеревьев называются братскими узлами или узлами-братьями. На рис. 3.2 братьями являются узлы
b
и с
, а также узлы d
и e
.
□ Предками текущего узла являются его родитель, а также родители его родителей и так далее. На рис. 3.2 предками узла
d
являются узлы b
и а
.
□ Потомками текущего узла являются его дочерние узлы, а также дочерние узлы его дочерних узлов и так далее. На рис. 3.2 потомками узла
а
являются узлы b
, c
, d
и e
.
Корневой узел XML-документа — это узел, который является корнем дерева документа. Не следует путать его с корневым элементом документа, поскольку помимо корневого элемента дочерними узлами корня также являются инструкции по обработке и комментарии, которые находятся вне корневого элемента.
Мы будем помечать корневой узел документа символом "
/
" и изображать следующим образом (рис. 3.3):
Рис. 3.3. Изображение корневого узла
На рис. 3.4 показано изображение документа,
<В/>
корневой узел которого помимо корневого элемента содержит комментарии и инструкции по обработке.
Рис. 3.4. XML-документ и его изображение
Корневой элемент не имеет имени. Функция
name(/)
будет всегда возвращать пустую строку.
Строковым значением корневого узла является конкатенация строковых значений всех его текстовых потомков в порядке просмотра документа.
Каждому элементу XML-документа соответствует узел элемента. Дочерними узлами узла элемента могут быть узлы его дочерних элементов, а также узлы комментариев, инструкций по обработке и текстовые узлы, которые представляют его непосредственное содержимое. Следует обратить внимание на то, что узлы атрибутов не считаются дочерними узлами своего элемента, они лишь только ассоциируются с ними.
При изображении деревьев мы будем помечать узлы элементов их именами. Например, элемент
A
будет изображен следующим образом (рис. 3.5):
Рис. 3.5. Изображение элемента
А
Каждый элемент имеет расширенное имя, которое состоит из локальной части и идентификатора пространства имен. Пространство имен, которому принадлежит элемент, может быть нулевым, если элемент не имеет префикса, и в его предках не было объявлено пространство имен по умолчанию.
Подобно корневому узлу, строковым значением узла элемента будет конкатенация значений его текстовых потомков в порядке просмотра документа. В силу того, что атрибуты не являются потомками элементов, их текстовые значения учитываться не будут.
Атрибутам того или иного элемента соответствуют узлы атрибутов. Считается, что узел элемента является родителем узла своего атрибута, но вместе с тем узел атрибута не является дочерним узлом узла его элемента. Такая ситуация несколько отличает дерево документа в XSLT от классического дерева, как оно было определено ранее — отношение между узлом элемента и узлом атрибута является отношением ассоциации. Говорят, что узел атрибута ассоциируется с узлом элемента.
Между тем, отношения такого рода не сильно отличаются от отношений между родительскими и дочерними узлами — один атрибут ассоциируется ровно с одним элементом. Поэтому мы будем изображать узлы атрибутов точно так же, как если бы они были дочерними узлами элементов. Для того чтобы отличать узлы атрибутов от остальных узлов, мы будем помечать их именем, которому будет предшествовать символ "
@
". При необходимости, значение атрибута будет приводиться в нижней части изображения узла.
Рис. 3.6 показывает возможные варианты изображения элемента, определенного как
.
Рис. 3.6. Изображение элемента и принадлежащего ему атрибута
В случаях, когда значение атрибута не является важным, оно может отсутствовать в изображении узла.
Каждый атрибут имеет расширенное имя, состоящее из локальной части и идентификатора пространства имен. Пространство имен атрибута будет нулевым, если оно не было определено по умолчанию и у имени атрибута нет префикса.
Строковым значением узла атрибута является значение атрибута, который ему соответствует.
Символьные данные, содержащиеся в документе, организуются в виде текстовых узлов. Последовательности символов, встречающиеся в документах, в целях экономии никогда не разбиваются на два или более текстовых узла, а текстовые узлы никогда не бывают пустыми. Содержание секций CDATA обрабатываются так, как если бы их содержимое было просто включено в документ с заменой символов "
<
" и "&
", на сущности <
и &
.
Текст, содержащийся в значениях атрибутов, а также в комментариях и инструкциях по обработке не оформляется в виде текстовых узлов — эти данные принадлежат соответствующим узлам атрибутов, комментариев и инструкций.
Мы будем помечать текстовые узлы символьными последовательностями, которые они содержат, заключенными в кавычки. В случаях, когда сам текст не важен, мы будем помечать эти узлы как "
...
".
Элемент, заданный как
Welcome
, может быть изображен следующим образом (рис. 3.7):
Рис. 3.7. Варианты изображения текстового узла элемента
Текстовые узлы не имеют имен. Строковым значением текстового узла является последовательность символов, которую он содержит.
Каждому пространству имен, которое определено для данного элемента, соответствует узел пространства имен, ассоциируемый с узлом этого элемента. Множество узлов пространств имен, которое ассоциируется с данным элементом, включает в себя следующие узлы.
□ Узел, который соответствует пространству имен
xml
. Это пространство неявно определено в любом XML-документе.
□ Узел, который соответствует пространству имен, заданному по умолчанию, если такое есть.
□ По одному узлу на каждый префикс пространств имен, доступный в данном элементе.
Напомним, что пространства имен, доступные в данном элементе, и пространство имен по умолчанию могут быть определены в его предках.
Подобно узлам атрибутов, узлы пространств имен ассоциируются с узлом элемента. Узел элемента является их родительским узлом, но при этом они сами не являются дочерними узлами узла элемента.
Расширенные имена узлов пространств имен состоят из локальной части имени, которая равна префиксу, использованному для объявления этого пространства и нулевого идентификатора пространства имен. Локальная часть пространства, определенного по умолчанию, будет пустой.
Строковым значением узла пространства имен является уникальный идентификатор ресурса (URI), с которым оно связано.
Мы будем помечать узлы пространств имен метками вида
xmlns:префикс
для обычного пространства и xmlns
для пространства имен по умолчанию. Мы не будем показывать в деревьях узлы пространства имен xml
, поскольку они ассоциируются со всеми узлами элементов. При необходимости в нижней части изображения узла мы будем приводить URI пространства, которое ему соответствует.
Приведем изображение дерева (рис. 3.8) документа
<а xmlns="urn:a">
Рис. 3.8. Изображение дерева документа с узлами пространств имен
Каждой инструкции по обработке соответствует свой узел. В дерево не включаются узлы инструкций, которые были приведены в декларации типа документа (DTD). Кроме этого, поскольку декларация XML не является инструкцией по обработке, ей не будет соответствовать никакой узел в дереве документа.
Локальной частью расширенного имени инструкции по обработке является имя целевого приложения инструкции. Пространство имен инструкции по обработке всегда нулевое.
Строковым значением инструкции по обработке является ее содержание — последовательность символов, которая начинается после имени приложения и следующего за ним пробела и заканчивается перед символами "
?>
", которые закрывают инструкцию. Напомним, что символьные данные инструкции по обработке не выделяются в отдельный текстовый узел.
Узел инструкции по обработке помечается именем целевого приложения, заключенным в символы "
" и "?>
". В нижней части изображения узла пространства имен может быть указано содержимое инструкции.
Узел инструкции по обработке
может быть изображен следующим образом (рис. 3.9):
Рис. 3.9. Изображение узла инструкции по обработке
Узел комментария соответствует каждому из комментариев, которые присутствуют в документе кроме тех, которые находятся в декларации типа документа (DTD). Узлы комментариев не имеют имен; их строковым значением является текст комментария — последовательность символов после "
". В изображении дерева узлы комментариев будут помечаться символами "
". В нижней части при необходимости будет указываться текст комментария.
Узел комментария
может быть изображен следующим образом (рис. 3.10):
Рис. 3.10. Изображение узла комментария
Для удобства использования мы можем свести в одну таблицу (табл. 3.1) такие характеристики узлов, как строковое значение, локальная часть имени, пространство имен и так далее.
Таблица 3.1. Характеристики различных типов узлов
Тип узла | Характеристики | ||||
---|---|---|---|---|---|
Строковое значение | Расширенное имя | Дочерние узлы | Родительские узлы | ||
Локальная часть имени | Пространство имен | ||||
Корневой узел | Конкатенация текстовых потомков | Нет | Узлы элементов, комментариев, инструкций по обработке | Нет | |
Узел элемента | Конкатенация текстовых потомков | Имя элемента | Пространство имен элемента | Узлы элементов, комментариев, инструкций по обработке, текстовые узлы | Корневой узел или узел элемента |
Узел атрибута | Значение атрибута | Имя атрибута | Пространство имен атрибута | Нет | Узел элемента |
Текстовый узел | Символьные данные | Нет | Нет | Узел элемента | |
Узел пространства имен | URI пространства имен | Префикс пространства имен | Нулевое | Нет | Узел элемента |
Узел инструкции по обработке | Содержимое инструкции | Имя целевого приложения | Нулевое | Нет | Корневой узел или узел элемента |
Узел комментария | Текст комментария | Нет | Нет | Корневой узел или узел элемента |
Модель XML-документа, описанная выше, является вполне достаточной для того, чтобы манипулировать структурой документа и данными, которые он содержит. Между тем, эта модель имеет определенные ограничения, а именно:
□ Не учитывается информация, содержащаяся в блоке DTD. Как следствие, в XSLT невозможно манипулировать определениями сущностей, элементов, атрибутов и так далее.
□ Не учитываются некоторые синтаксические особенности входящего XML-документа. Например: использовались ли в определенном атрибуте одинарные или двойные кавычки; была ли определенная строка задана сущностью или просто текстом, был ли текст заключен в секции CDATA или нет.
□ Если атрибут элемента был определен в DTD со значением по умолчанию, то в преобразовании нельзя точно сказать, присутствовал ли он физически во входящем документе или нет.
□ Не учитывается, был ли пустой элемент определен как
или
.
Одним словом, предложенная выше модель не учитывает информацию, которая не важна с точки зрения структуры документа. На практике помимо структуры бывает также важен и детальный синтаксис документа (например, необходимо вместо
выводить
). К сожалению, применение XSLT для таких задач ограничено вследствие ограничений самой модели документа.
Узлы дерева XML-документа находятся в определенном порядке, который называется порядком просмотра документа (англ. document order). Этот порядок важен для вычисления XPath-вырэжений, которые оперируют множествами узлов. Несмотря на то, что эти множества не имеют внутреннего порядка, при вычислении выражений узлы в них будут перебираться в прямом или обратном порядке просмотра документа в зависимости от того, какие оси навигации применяются в выражении.
Порядок просмотра документа — это порядок, который соответствует появлению в документе первого символа текстовой записи узла. Например, для элементов это будет порядок появления в документе открывающих тегов.
Более четко порядок просмотра документа определяется следующими правилами:
□ корневой узел является первым узлом в порядке просмотра документа;
□ узлы элементов предшествуют своим дочерним узлам, узлам пространств имен и узлам атрибутов;
□ узлы пространств имен предшествуют узлам атрибутов;
□ узлы атрибутов предшествуют другим дочерним узлам своего элемента;
□ остальные узлы упорядочиваются в последовательности их появления в документе.
Обратным порядком просмотра документа называется порядок, который в точности противоположен обычному порядку просмотра документа. Обычный порядок просмотра документа также называют прямым порядком или порядком документа.
В качестве примера приведем схему дерева и выясним порядок просмотра
следующего документа:
<а level="0" xmlns:b="urn:b" xmlns="urn:a">
alpha
delta
Дерево этого документа показано на рис. 3.11. Порядок просмотра данного документа будет следующим:
□ корневой узел;
□ узел комментария
;
□ узел инструкции по обработке
;
□ узел элемента
a
;
□ узел пространства имен
"urn:а"
;
□ узел пространства имен
"urn:b"
;
□ атрибут
level
;
□ текстовый узел "
alpha
";
□ узел элемента
b:bravo
;
□ узел пространства имен
"urn:а"
;
□ узел пространства имен
"urn:b
";
□ комментарий с текстом "
To do ...
";
□ элемент
charlie
;
□ узел пространства имен
"urn:а"
;
□ узел пространства имен
"urn:b"
;
□ текстовый узел "
delta
";
□ узел инструкции по обработке
.
Рис. 3.11. Схема дерева XML-документа
Соответственно, обратный порядок просмотра документа будет начинаться с инструкции по обработке
и заканчиваться корневым элементом.
Многие языки программирования при объявлении переменной требуют указывать, какой тип данных будет ей присваиваться. Например, в языке Java код
int i = 15;
объявит переменную целого типа
int
с именем i
и присвоит ей значение 15
. В этом случае тип данных ставится в соответствие переменной. XSLT относится к динамически типизируемым языкам, в которых тип данных ассоциируется не с переменными, а со значениями.
В XSLT выделяется пять типов данных:
□ булевый тип (boolean);
□ численный тип (number);
□ строковый тип (string);
□ множество узлов (node-set);
□ результирующий фрагмент дерева (result tree fragment).
Ниже мы подробно рассмотрим особенности работы со всеми пятью типами данных.
Булевый тип данных в 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" (строка)
Численный тип в 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
.
Строки в 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 позволяет использовать в выражениях значения разных типов — например, складывать строки и булевые значения или производить логические операции над числами. В тех случаях, когда тип данных значения отличается от типа данных, который требуется для операции, значения будут неявным образом приведены к требуемому типу, если это, конечно, возможно.
Несмотря на то, что XSLT оперирует логической моделью XML-документа как деревом с узлами, в XSLT нет типа данных, который соответствовал бы одному узлу. Вместо этого используется гораздо более мощный и гибкий тип данных, называемый множеством узлов (англ. node-set).
Множество узлов — это чистое математическое множество, состоящее из узлов дерева: оно не содержит повторений и не имеет внутреннего порядка элементов. Множества узлов выбираются особым видом XPath-выражений, которые называются путями выборки (англ. location path).
<А>
<В/>
<С>
Предположим, что в этом документе мы хотим выбрать все узлы, являющиеся потомками элемента
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
такие, что их строковые значения будут равны. Неравенство множеств означает наличие в них как минимум пары узлов с различными строковыми представлениями. Такие определения делают возможным при сравнении двух множеств одновременное выполнение равенства и неравенства.
1
2
2
3
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
and
Результатом этого преобразования будет строка:
true and true
Этот пример показывает, что множество дочерних элементов
int
элемента numbers
одновременно считает как равным, так и неравным множеству дочерних элементов byte
.
Приведем еще несколько примеров.
1
2
3
4
Результат:
false and true
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
.
При преобразовании множества узлов в строку, результатом будет строковое значение первого в порядке просмотра узла множества.
- A
- B
- C
- D
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Результат:
A
При преобразовании множества узлов в число, множество сначала приводится к строке, а затем строка преобразуется в численное значение. Проще говоря, численным значением множества узлов будет численное значение первого узла в порядке просмотра документа.
1
1.5
2
2.6
3
3.7
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Результат:
0.5
Четыре типа данных, описанных выше, заимствованы языком 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 нельзя повторно обрабатывать части документов, однако, это реализуется при помощи механизма расширений.
□ Дерево может быть определено один раз в виде значения переменной и использовано несколько раз в выходящем документе.
http://www.xsltdev.ru
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
You may visit the following link.
Result as string:
Result as tree:
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.
При приведении дерева к числу, оно сначала преобразовывается в строку, а затем в число. Это означает, что деревья, в принципе, можно использовать в арифметических операциях, несмотря на то, что они для этого не предназначены.
1
1
2
2
3
3.5
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Integers:
Reals:
Reals minus integers:
Integers:
123
Reals:
123.5
Reals minus integers:
0.5
Для удобства использования все взаимные преобразования типов сведены в одну таблицу (табл. 3.2).
Таблица 3.2. Взаимные преобразования типов данных XSLT
Преобразовываемый тип | |||||
---|---|---|---|---|---|
Целевой тип | (булевое значение) |
(число) |
(строка) |
(множество узлов) |
(дерево) |
(булевое значение) |
→ → другое →
|
пустая → непустая →
|
пустое → другое →
|
всегда
|
|
(число) |
→ →
|
разбирается, как число в десятичном формате | мн-во → строка → число | дерево → строка → число | |
(строка) |
→ →
|
десятичная запись числа | строковое значение первого узла в порядке просмотра | строковое сложение всех текстовых узлов дерева | |
(множество узлов) |
нет | нет | нет | нет | |
(дерево) |
нет | нет | нет | нет |
Несмотря на отсутствие побочных эффектов, которое является одним из основных принципов 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
, вычисляют множества, к узлам которых нужно применить шаблоны.
В этом шаблонном правиле содержатся два элемента
xsl:apply-templates
, которые применяют шаблоны к множествам, выбранным выражениями HEAD
и BODY
соответственно.
Логические выражения XPath могут использоваться в качестве условий в таких элементах, как
xsl:if
и xsl:when
, обеспечивая условную обработку.
Предположим, что нам нужно выводить различные сообщения в зависимости от возрастной информации, присутствующей во входящем документе:
Johnny
19
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
.
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
использовалось для генерации символьных данных. Между тем, выражения вполне пригодны и для того, чтобы создавать в выходящем документе целые фрагменты:
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
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]
" являются предикатами — логическими выражениями, которые фильтруют множество.
Фильтрующие выражения также позволяют вычислять пути выборки относительно узлов фильтруемых множеств.
a
b
c
1
2
3
Следующее преобразование демонстрирует использование относительных путей выборки в фильтрующих выражениях:
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Тransform">
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).
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="строковое значение">
...
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
name="{name()}"
context-position="{position()}"
context-size="size()"
string-value="{.}">
- A
- B
- C
D
E
F
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"/>
Во вводной главе мы говорили, что преобразования в XSLT являются наборами шаблонных правил, каждое из которых обрабатывает определенный фрагмент входящего документа с тем, чтобы сгенерировать фрагмент выходящего документа.
При выполнении преобразования каждая из его инструкций, каждый из элементов обрабатывается в некотором контексте. Контекст преобразования состоит из двух частей: из текущего множества узлов и из текущего узла, которые показывают, что именно обрабатывается в данный момент. XSLT-процессор поочередно обрабатывает каждый из узлов текущего множества (при этом делая этот узел текущим узлом) и объединяет результаты в одно дерево.
Контекст преобразования тесно связан с контекстом вычисления выражений:
□ текущий узел контекста преобразования соответствует контекстному узлу вычисления выражений;
□ позиция текущего узла в текущем обрабатываемом множестве соответствует позиции контекста вычисления выражений;
□ размер текущего множества узлов соответствует размеру контекста вычисления выражений.
Контекст преобразования может изменяться только двумя элементами —
xsl:apply-templates
и xsl:for-each
. Каждый из этих элементов вычисляет множество узлов, которое становится текущим и затем обрабатывается. После этого контекст преобразования восстанавливается до того состояния, каким он был перед обработкой.
Изменения контекста могут быть продемонстрированы на следующем примере.
June
July
August
Этому документу соответствует следующее дерево (рис. 3.19):
Рис. 3.19. Дерево входящего документа
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Summer
Забегая вперед скажем, что в изначальном контексте преобразования текущее множество состоит из единственного узла — корневого узла документа. Он становится текущим и обрабатывается соответствующим шаблоном.
В нашем случае шаблонное правило, обрабатывающее корневой узел, выглядит как:
Summer
Тело этого шаблона выполняется в том самом изначальном контексте, о котором мы только что упомянули: текущее множество состоит из корневого узла, он же является и текущим узлом. Мы можем показать контекст, выделяя текущее множество, пунктиром, а текущий узел — полужирной линией (рис. 3.20).
Рис. 3.20. Первоначальный контекст преобразования
Атрибут
select
элемента xsl:apply-templates
задает выражение, вычисляющее множество узлов, которые должны быть обработаны. Выражение summer
, которое содержит этот атрибут, является относительным путем выборки, который возвращает все дочерние элементы summer
текущего узла. Поскольку текущим узлом в данном контексте является корневой узел дерева, значением выражения summer
будет множество узлов, состоящее из субэлемента summer, корневого узла.
При выполнении элемента
xsl:apply-templates
процессор сделает это вычисленное множество узлов текущим множеством и начнет поочередно обрабатывать его узлы, делая их при этом текущими. Иначе говоря, выполнение элемента
сведется к выполнению шаблона, обрабатывающего элемент
summer
. Этот шаблон выглядит следующим образом:
Выполняться он будет в следующем контексте (рис. 3.21):
Рис. 3.21. Контекст шаблона элемента
summer
Атрибут
select
элемента xsl:apply-templates,
который присутствует в этом шаблоне, вычисляет новое текущее множество: путь выборки month
возвращает все дочерние элементы month
текущего узла. Текущим узлом является элемент summer
, то есть новое текущее множество будет состоять из трех его дочерних элементов month
. Таким образом, процессор будет поочередно выполнять шаблоны в каждом из трех следующих контекстов, показанных на рис. 3.22.
Рис. 3.22. Изменение контекста при выполнении шаблона элемента
month
Шаблон, вычисляемый в каждом из этих контекстов, имеет следующий вид:
Элемент
xsl:value-of
этого шаблона создает в элементе td
текстовый узел, значение которого равно строковому значению выражения ".
", то есть строковому значению текущего узла, и в каждом случае это будет строковое значение соответствующего элемента month
.
Контекст преобразования позволяет более четко определить такие понятия, как "обработка узла", "применение шаблона к узлу" и так далее. Все эти выражения означают одно: выполнение соответствующего шаблона с данным узлом в качестве текущего.
Несмотря на полную свободу в порядке выполнения шаблонов, правила изменения контекста и компоновки результирующего дерева, спецификация XSLT оговаривает очень четко — это делает XSLT весьма гибким языком, программы на котором при этом выполняются совершенно детерминированным образом.
Типовой процесс выполнения преобразования согласно спецификации включает следующие стадии:
□ дерево выходящего документа создается путем обработки множества, состоящего из единственного узла — текущего узла дерева;
□ результатом применения шаблонов к обрабатываемому множеству узлов является объединение фрагментов деревьев, которые являются результатами обработки каждого из узлов множества;
□ каждый из узлов обрабатываемого множества преобразуется следующим образом:
• из всех шаблонов, определенных в данном преобразовании, выбираются шаблоны, соответствующие данному узлу (соответствие определяется паттерном, указанным в атрибуте
match
элемента xsl:template
);
• из этих шаблонов выбирается наиболее подходящий;
• выбранный шаблон выполняется в контексте обрабатываемого множества как текущего множества узлов и обрабатываемого узла как текущего узла;
□ если шаблон содержит инструкции
xsl:apply-templates
или xsl:foreach
, которые дополнительно выбирают узлы для обработки, процесс рекурсивно продолжается до тех пор, пока обрабатываемое множество будет содержать хотя бы один узел.
В общих чертах этот процесс был продемонстрирован на примере, приведенном в описании контекста преобразования. Сейчас мы завершим картину, показав, как в каждом из шаблонов будут создаваться результирующие фрагменты деревьев и как они затем будут "сращиваться" в дерево выходящего документа.
На сей раз, мы начнем с самых "глубоких" шаблонов — шаблонов, обрабатывающих элементы
month
.
Каждый из них создает результирующий фрагмент дерева следующего вида (рис. 3.23).
Рис. 3.23. Результат обработки элемента month
Шаблоны к элементам
month
применяются элементом xsl:apply-templates
при обработке элемента summer
соответствующим шаблоном:
Результатом выполнения
xsl:apply-templates
будет объединение результирующих фрагментов деревьев, которые получатся при обработке элементов month
. Таким образом, результирующий фрагмент этого шаблона будет "собран" в следующем виде (рис. 3.24):
Рис. 3.24. Результат обработки элемента
summer
Пунктиром выделены результирующие фрагменты деревьев, сгенерированные при обработке элементов
month
; эти фрагменты объединяются и используются при создании фрагмента дерева, являющегося результатом обработки элемента summer
.
Этот результат, в свою очередь, используется в главном шаблоне — шаблоне, который обрабатывает корневой элемент:
Summer
Сгенерированный при обработке элемента
summer
результирующий фрагмент дерева включается в корневом шаблоне в элемент body
(рис.3.25).
Рис. 3.25. Результат обработки корневого узла
Пунктиром выделен результирующий фрагмент дерева, который был получен при обработке элемента
summer
.
Результирующий фрагмент дерева, полученный в результате обработки корневого узла, является деревом выходящего документа. В чистом XSLT это и есть результат выполнения преобразования. Для того чтобы получить физическую интерпретацию — в данном случае HTML-документ, дерево сериализуется, обращаясь в следующий выходящий документ.
Summer
June
July
August
Надо сказать, что спецификация языка XSLT не оговаривает, в каком именно порядке процессоры должны выполнять шаблонные правила — главное, чтобы результат преобразования совпадал с результатом, полученным при обработке приведенным выше способом. На практике это означает, что разработчикам совершенно необязательно знать, как именно конкретный процессор применяет правила — достаточно понимать принципы шаблонной обработки. В этом одно из главных достоинств XSLT как языка, не имеющего побочных эффектов.