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

XPath-выражения

Выражения для XML-документов

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

value
элемента
price
, принадлежащему элементу
product
, сделать это при помощи стандартных SAX- или DOM-интерфейсов было бы, мягко говоря, не очень удобно. И это еще простой пример. Бывают, действительно, сложные случаи, когда нужно выбрать узел определенного типа, который может находиться в нескольких местах в документе, да еще и должен обладать заданными свойствами.

Для выполнения часто встречающихся задач такого рода был создан язык XPath, название которого расшифровывается, как XML Path — язык XML- путей. Главной задачей этого языка является адресация, или, по-другому, определение местоположения частей XML-документа. На практике это означает выбор в документе множества узлов, которые соответствуют определенным условиям расположения.

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

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

К счастью, несмотря на все особенности, язык XPath настолько прост, что иногда его используют, даже не отдавая себе отчета, что это XPath. Скажем, когда мы пишем

для того, чтобы вывести номер страницы, указанный в элементе

number
, который находится в элементе
page
, мы не задумываемся о том, что
page/number
— это на самом деле XPath-выражение, точнее, путь выборки.

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

Для того чтобы четко определить все грамматические конструкции этого языка, мы опять будем применять расширенные формы Бэкуса-Наура, по возможности раскрывая и упрощая их. Чтобы не путать номера XPath-продукций с другими синтаксическими правилами, мы будем использовать в номере префикс

XP
, например:

[ХР1] LocationPath ::= RelativeLocationPath

                       | AbsoluteLocationPath

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

NCName
,
QName
и
S
, которые мы уже рассматривали ранее.
NCName
и
QName
относятся к расширенным именам, a
S
обозначает пробельное пространство.

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

В XPath отсутствует функция

eval
, которая вычисляла бы значение XPath-выражения, переданного ей в виде строки.

Примечание

Функция

eval
присутствует в некоторых XSLT-процессорах, например в Saxon в виде расширения
saxon:evaluate
.

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

Как мы видели ранее, в XSLT одно и то же правило преобразования может применяться к различным частям XML-документа и в каждом случае результат будет разным — в зависимости от того, как выглядит обрабатываемый фрагмент. Подобно этому, XPath-выражения тоже вычисляются в зависимости от контекста. Контекст показывает, какой узел в данный момент обрабатывается преобразованием, какова позиция этого узла в обрабатываемом множестве, сколько всего узлов в этом множестве, какие переменные доступны и какие значения они имеют, какие функции могут быть вызваны и, наконец, какие пространства имен объявлены. Иными словами, контекст — это полное описание положения, окружения или ситуации, в которой происходит вычисление.

Если давать строгое определение в соответствии со спецификацией XPath, то контекст составляют следующие части.

□ Контекстный узел — узел, который обрабатывается в текущий момент. Контекстный узел оказывает влияние на вычисление многих выражений — например, относительные пути выборки будут отсчитываться относительно контекстного узла. В большинстве случаев контекстный узел совпадает с текущим узлом преобразования, однако во время вычисления самих XPath-выражений, они могут различаться.

□ Целое положительное число, показывающее размер контекста — количество узлов во множестве, которое обрабатывается в данный момент. Это число может быть получено функцией

last
.

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

position
. Позиция первого узла равна 1, позиция последнего — значению функции
last
.

□ Множество связанных переменных. Это множество есть множество пар вида "имя-значение", в котором имя переменной связывается со значением, присвоенным ей. Переменные не определяются в самом XPath, для этого следует использовать элемент языка XSLT

xsl:variable
. Переменные могут содержать как значения любого из четырех базовых типов XPath (булевый тип, строка, число, множество узлов), так и значения других типов. Например, в XSLT значению переменной можно присвоить результирующий фрагмент дерева, а расширения языка так и вовсе могут присваивать переменным объекты любых типов. Другое дело, что XPath-выражения в соответствии со стандартом не должны непосредственно работать другими типами объектов, кроме своих четырех базовых. Механизмы расширения XPath и XSLT будут рассматриваться в главе 10.

В отношении переменных важно понимать, что это не более чем объекты, доступ к которым можно получить по имени.

□ Библиотека функций, состоящая из множества функций, которые могут быть выполнены процессором. В XPath определяется базовая библиотека, функции которой должны быть реализованы в процессоре, однако эта библиотека может быть расширена. Например, XSLT определяет несколько дополнительных функций, которые также должны поддерживаться всеми XSLT-процессорами. Более того, в преобразованиях можно использовать и собственные функции расширения. Таким образом, библиотека функций контекста состоит из всех функций, доступных при вычислении выражения.

□ Множество объявлений пространств имен. Это множество связывает префиксы пространств имен с уникальными идентификаторами ресурсов (URI), которые им соответствуют.

Пути выборки

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

Синтаксис путей выборки во многом похож на синтаксис путей в файловых системах — сказывается то обстоятельство, что иерархическая структура данных в XML-документах очень близка к древовидной структуре каталогов. В качестве примера сравним дерево каталогов (рис. 6.1) с таким же деревом, записанным в виде XML-документа (листинг 6.1).

Рис. 6.1. Древовидная структура каталогов

Листинг 6.1 XML-документ

В этой иерархии каталогов путь "

/
" соответствует корневому каталогу, путь "
/Java/Lib/Servlets/src
" — каталогу
src
. Путь из каталога
Java
в каталог
XMLParser
имеет вид "
Doc/XMLParser
", а путь из каталога
Lib
в каталог
images
— "
Servlets/doc/images
".

Перемещаться в системе каталогов можно не только вглубь, но также на верхние уровни при помощи пути "

..
", который осуществляет переход в родительский каталог. К примеру, для того, чтобы перейти из каталога "
/Java/Lib/Servlets/doc/images
" в каталог "
/Java/Doc/XMLParser/images
", можно воспользоваться путем "
../../../../Doc/XMLParser/images
".

Пути файловой системы, приведенные выше, в точности совпадают с путями выборки, которые мы бы использовали для обращения к соответствующим частям ХМL-документа. Путь выборки "

/
" содержит корневой узел, путь выборки "
/java/Lib/Servlets/src
" — элемент
src
, принадлежащий элементу
Servlets
, который принадлежит элементу
Lib
, который принадлежит элементу
Java
, находящемуся в корне элемента. Путь выборки "
Doc/XMLParser
" выбирает элементы
XMLParser
, находящиеся в элементах
Doc
, принадлежащих контекстному узлу.

В XPath существует два вида путей выборки — относительные и абсолютные пути. Абсолютный путь (например, "

/Java/Doc/ClassGenerator
") начинается ведущей косой чертой ("
/
") и отсчитывается от корневого узла документа, в то время как относительный путь (например, "
Doc/XMLParser
") отсчитывается от контекстного узла.

И абсолютный, и относительный пути выборки состоят из нескольких шагов выборки, разделенных косой чертой ("

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

Пример

В файловой системе выполнить путь вида

Lib/Servlets/classes
означает:

□ из текущего каталога перейти в подкаталог

Lib
;

□ затем перейти в подкаталог

Servlets
;

□ и наконец — в подкаталог

classes
.

Для того чтобы выполнить такой же путь выборки в XML-документе, нужно

сделать следующее:

□ выполнить первый шаг, "

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

□ затем выполнить шаг "

Servlets
" — для каждого из узлов, выбранных предыдущим шагом, выбрать дочерние элементы "
Servlets
" и объединить их в одно множество;

□ наконец, выполнить шаг "

classes
" — для каждого из узлов, выбранных на предыдущем этапе, выбрать дочерние элементы
classes
и объединить их в одно множество.

Опишем более подробно алгоритм вычисления пути выборки:

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

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

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

Пример

Рассмотрим процесс выполнения пути выборки

/A/B/D/G/I
в следующем документе:

На рис. 6.2 показано логическое дерево, соответствующее этому документу.

Рис. 6.2. Логическое дерево, представляющее XML-документ

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

1. Данный путь (рис. 6.3) является абсолютным путем выборки, значит, он должен выполняться, начиная от корневого узла.

Рис. 6.3. Начальный узел пути выборки

2. Первым шагом пути (рис. 6.4) является шаг

A
, который выбирает все дочерние элементы
A
контекстного узла.

Рис. 6.4. Первый шаг

3. Вторым шагом пути (рис. 6.5) является шаг

B
, который выбирает все дочерние элементы в узлов множества, выбранного на предыдущем шаге. Так как тогда был выбран единственный узел
A
, текущий шаг выберет два дочерних элемента в этого узла.

Рис. 6.5. Второй шаг

4. На очередном шаге (рис. 6.6) мы выбираем дочерние элементы

D
. Как можно заметить, один из элементов в, выбранных на прошлом этапе, не содержит таких элементов, значит, в этом случае, шаг выборки возвратит пустое множество. Второй элемент
B
имеет три дочерних элемента
B
. В итоге мы получим множество, состоящее из трех элементов
D
.

Рис. 6.6. Третий шаг

5. Следующий шаг,

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

Рис. 6.7. Четвертый шаг

6. Последний шаг,

I
(рис. 6.8) выбирает для каждого из четырех элементов
G
дочерние элементы
I
. Первый элемент
G
не имеет дочерних элементов, второй имеет один дочерний элемент
I
, третий не содержит элементов и четвертый содержит два элемента
I
. В итоге результатом выполнения этого шага будет множество, состоящее из 3 элементов
I
.

Рис. 6.8. Пятый шаг

Пути выборки соответствует продукция

LocationPath
, которая записывается следующим образом:

[XP1] LocationPath ::= RelativeLocationPath

                       | AbsoluteLocationPath

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

RelativeLocationPath
, либо абсолютным путем с продукцией
AbsoluteLocationPath
:

[XP2] AbsoluteLocationPath ::= '/' RelativeLocationPath?

                               | AbbreviatedAbsoluteLocationPath

[XP3] RelativeLocationPath ::= Step

                               | RelativeLocationPath '/' Step

                               | AbbreviatedRelativeLocationPath

Упростим

LocationPath
, раскрыв дочерние продукции:

LocationPath ::= '/'

                 | RelativeLocationPath

                 | '/' RelativeLocationPath

                 | '//' RelativeLocationPath

Таким образом, путь выборки имеет четыре основных варианта, которые мы сейчас и разберем:

□ путь

'/'
— используется для обращения к корневому узлу дерева;

□ путь вида

RelativeLocationPath
— есть относительный путь выборки;

□ путь вида

'/' RelativeLocationPath
— это абсолютный путь выборки, то есть относительный путь, которому предшествует
'/'
;

□ путь вида

'//' RelativeLocationPath
— это абсолютный путь выборки, в котором использован сокращенный синтаксис. Путь такого вида эквивалентен пути вида
'/descendant-or-self:node()/' RelativeLocationPath
. Первой его частью является путь
'/descendant-or-self:node()'
, который выбирает все узлы документа (кроме узлов атрибутов и пространств имен).

Главной деталью

LocationPath
является относительный путь выборки, продукция которого также может быть переписана в раскрытом и упрощенном виде:

RelativeLocationPath ::= Step

                         | RelativeLocationPath '/' Step

                         | RelativeLocationPath '//' Step

В соответствии с этой продукцией, относительный путь выборки состоит из одного или нескольких шагов выборки, разделенных

'/'
или
'//'
. Как уже отмечалось ранее, конструкция
'//'
есть сокращенный вариант от
'/descendant-or-self::node()/'
. Таким образом, главным элементом пути выборки является шаг выборки.

Примеры:

/
— выберет корневой узел документа;

— выберет элемент
а
, находящийся в корне документа;

//а
— выберет множество всех элементов
а
текущего документа.

Шаги выборки

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

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

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

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

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

Пример

Шаг выборки

attribute::href[. = 'http://www.xsltdev.ru']
состоит из оси навигации
attribute
, которая выбирает атрибуты данного узла, теста узла
href
, который выбирает узлы с именем
href
и нулевым пространством имен, и предиката
[. = 'http://www.xsitdev.ru']
, который оставляет в выбираемом множестве те узлы, текстовое значение которых равно
"http://www.xsltdev.ru"
. Таким образом, на этом шаге будут выбраны все атрибуты
href
текущего узла, имеющие значение
"http://www.xsltdev.ru"
.

Шаг выборки соответствует EBNF-продукции

Step
, а первая его часть, ось навигации — продукции
AxisSpecifier
:

[XP4] Step          ::= AxisSpecifier NodeTest Predicate*

                        | AbbreviatedStep

[XP5] AxisSpecifier ::= AxisName '::'

                        | AbbreviatedAxisSpecifier

Продукцию

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

Step ::= '.'

         | '..'

         | NodeTest Predicate*

         | '@' NodeTest Predicate*

         | AxisName '::' NodeTest Predicate*

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

□ шаг выборки

'.'
эквивалентен шагу
self::node()
, который выбирает контекстный узел;

□ шаг выборки

'..'
эквивалентен шагу
parent::node()
, который выбирает родительский узел контекстного узла;

□ шаг выборки вида

NodeTest Predicate*
эквивалентен шагу выборки вида
'child::' NodeTest Predicate*
, который выбирает узлы из множества дочерних узлов контекстного узла;

□ шаг выборки вида

'@' NodeTest Predicate*
эквивалентен шагу выборки вида
'attribute::' NodeTest Predicate*
, который выбирает узлы из множества атрибутов контекстного узла.

Последний случай,

AxisName ' ::' NodeTest Predicate*
представляет полный синтаксис шага выборки: сначала идет наименование оси и тест узла, разделенные двумя двоеточиями (
"::"
), затем несколько предикатов.

Оси навигации

Важной особенностью путей выборки является то, что шаги в них могут совершаться не в двух направлениях (вглубь и на верхний уровень), как в случае с файловыми системами, а во многих других. При выполнении шага выборки из некоторого контекстного узла направление движения по логическому дереву документа задается первой частью этого шага, осью навигации. В XPath имеется 13 осей навигации, а именно:

self
— эта ось навигации содержит только сам контекстный узел;

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

parent
— содержит родительский узел контекстного узла, если он есть;

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

descendant-or-self
— содержит контекстный узел, а также всех его потомков; не содержит узлов атрибутов и пространств имен;

ancestor
— содержит узлы, которые являются предками контекстного узла;

ancestor-or-self
— содержит контекстный узел, а также всех его предков;

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

following-sibling
— содержит братские узлы контекстного узла, которые следуют за ним в порядке просмотра документа; если контекстный узел является атрибутом или узлом пространства имен, то
following-sibling
не будет содержать никаких узлов;

preceding
— содержит узлы, предшествующие контекстному узлу в порядке просмотра документа; не содержит его предков; не содержит узлов атрибутов и пространств имен;

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

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

namespace
— содержит узлы пространств имен контекстного узла, если он является элементом; в противном случае не содержит ничего.

Шаг выборки вида

ось::node()
будет содержать все узлы, принадлежащие этой оси. Например,
attribute::node()
(или, сокращенно
@node()
) будет содержать все атрибуты текущего узла.

Для того чтобы понять, как оси навигации расположены в дереве документа, обратимся к рис. 6.9.

Рис. 6.9. Расположение в документе осей навигации

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

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

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

self
и
parent
не могут содержать более одного узла, порядок просмотра для них не играет никакого значения.

Базовые типы узлов и направление их просмотра можно свести в одну таблицу (табл. 6.1).


Таблица 6.1. Базовые типы узлов и направления просмотра осей навигации

Ось навигацииБазовый тип узлаНаправление просмотра
self
Узел элементаНет
child
Узел элементаПрямое
parent
Узел элементаНет
descendant
Узел элементаПрямое
descendant-or-self
Узел элементаПрямое
ancestor
Узел элементаОбратное
ancestor-or-self
Узел элементаОбратное
following
Узел элементаПрямое
following-sibling
Узел элементаПрямое
preceding
Узел элементаОбратное
preceding-sibling
Узел элементаОбратное
attribute
Узел атрибутаПрямое
namespace
Узел пространства именПрямое

Базовый тип влияет на то, как в данном шаге выборки будет выполняться тест узла, а направление просмотра на позицию, которую будет занимать тот или иной узел в данном направлении.

Легче всего понять, какие узлы и в каком порядке содержат те или иные оси навигации, представив это графически. Рис. 6.10 иллюстрирует выбор узлов осями навигации. Здесь показано дерево документа, контекстный узел, выделенный жирной линией, и множество узлов, содержащееся в данной оси, ограниченное пунктиром. Узлы выбранного множества пронумерованы в порядке просмотра оси.

Рис. 6.10. Расположение и порядок просмотра осей навигации в документе

Приведем продукцию

AxisName
, которая описывает синтаксис осей навигации.

[XP6] AxisName ::= 'ancestor'

                   | 'ancestor-or-self'

                   | 'attribute'

                   | 'child'

                   | 'descendant'

                   | 'descendant-or-self'

                   | 'following'

                   | 'following-sibling'

                   | 'namespace'

                   | 'parent'

                   | 'preceding'

                   | 'preceding-sibling'

                   | 'self'

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

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

Тесты узлов

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

Продукция

NodeTest
, соответствующая тесту узла, определяется следующим образом:

[XP7] NodeTest ::= NameTest

                   | NodeType '(' ')'

                   | 'processing-instruction' '(' Literal ')'

Раскрыв продукции

NameTest
и
NodeType
, EBNF-синтаксис теста узла можно переписать в упрощенном виде:

NodeTest ::= '*'

             | NCName:*

             | QName

             | 'comment()'

             | 'text()'

             | 'processing-instruction'

             | 'processing-instruction' '(' Literal ')'

             | 'node()'

Рассмотрим подробно каждый случай.

□ Тест узла

'*'
выполняется для любого узла, тип которого является базовым типом оси навигации данного шага выборки. Иными словами, шаг выборки
attribute::*
или
@*
выберет все атрибуты контекстного узла, а
namespace::*
— все узлы пространств имен. Для всех остальных осей тест
*
будет выбирать узлы элементов, принадлежащих данной оси.

□ Тест узла вида

'NCName:*'
выполняется для узлов определенного пространства имен. Этот тест имеет вид
префикс:*
, где
префикс
соответствует проверяемому пространству (он должен быть определен в контексте вычисляемого шага выборки). Этот тест выполняется для всех узлов пространства имен, которое соответствует префиксу вне зависимости от локальной части имени.

□ Тест вида

QName
выполняется для узлов базового типа, которые имеют расширенные имена, равные
QName
. Если в
QName
не указан префикс, то тест будет выполняться для узлов с соответствующим именем и нулевым пространством имен. В случае, если префикс указан, узел будет удовлетворять тесту, если его пространство имен будет совпадать с пространством имен, которое соответствует префиксу, а локальная часть имени будет равна локальной части
QName
.

□ Тест

'comment()'
выполняется для любого узла комментария.

□ Тест

'text()'
выполняется для любого текстового узла.

□ Тест узла

'processing-instruction()'
выполняется для любого узла инструкции по обработке.

□ Тест

'processing-instruction (' Literal ')'
, или, в упрощенном виде
processing-instruction(строка)
выполняется для инструкций по обработке, имеющих имя, равное строковому параметру этого теста узла.

□ Тест узла

'node()'
выполняется для любого узла. Шаг выборки вида
ось::node()
выберет все узлы, принадлежащие данной оси.

Примеры:

child::node()
— выберет все дочерние узлы контекстного узла;

child::*
— выберет дочерние элементы контекстного узла;

attribute::*
— выберет атрибуты контекстного узла;

xsl:*
— выберет все дочерние элементы контекстного узла, принадлежащие пространству имен с префиксом
xsl
;

xsl:template
  — выберет все дочерние элементы
template
контекстного узла, принадлежащие пространству имен с префиксом
xsl
;

comment()
— выберет все дочерние узлы комментариев;

self::comment()
— выберет контекстный узел, если он является комментарием, или пустое множество в противном случае;

descendant::processing-instruction()
— выберет все узлы инструкций по обработке, которые являются потомками контекстного узла;

following::processing-instruction('арр')
— выберет все узлы инструкций по обработке с целевым приложением "
app
", которые следуют за контекстным узлом в порядке просмотра документа.

Тест узла показывает, какого типа узлы мы ищем. Комментарии? Текстовые узлы? Узлы с определенными именами или принадлежащие определенному пространству имен? Или подойдут любые узлы?

Итак, ось навигации позволяет указывать направления шага по дереву документа, тест узла — тип или имя выбираемого узла. Третья часть шага выборки (один или несколько предикатов) позволяет дополнять эти критерии логическими условиями, которые должны выполняться для выбираемых на данном шаге узлов.

Предикаты

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

Продукция предиката,

Predicate
, определяется следующим образом:

[XP8] Predicate     ::= '[' PredicateExpr ']'

[XP9] PredicateExpr ::= Expr

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

Predicate ::= '[' Expr ']'

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

Фильтрация множества узлов выполняется следующим образом.

□ Фильтруемое множество сортируется в направлении просмотра оси навигации данного шага. Для осей

ancestor
,
ancestor-or-self
,
preceding
,
preceding-sibling
фильтруемое множество сортируется в обратном порядке просмотра документа, для остальных осей — в прямом порядке просмотра.

□ Выражение предиката вычисляется для каждого узла отсортированного множества в следующем контексте.

 • Фильтруемый узел (тот, для которого в данный момент вычисляется предикат) становится контекстным узлом.

 • Количество узлов фильтруемого множества становится размером контекста.

 • Позиция фильтруемого узла в отсортированном множестве становится позицией контекста.

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

 • Если результатом вычисления является число, равное позиции контекста, булевым значением предиката будет

true
, в противном случае —
false
. Например, предикат
[2]
равносилен предикату
[position()=2]
— он обратится в истину только для второго узла фильтруемого множества.

 • Все остальные типы данных приводятся к булевому типу в соответствии со стандартными правилами (см. также раздел "Типы данных" настоящей главы).

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

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

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

Примеры:

a[1]
— выберет первый в порядке просмотра документа дочерний элемент
а
контекстного узла;

a[position() mod 2 = 0]
— выберет все четные дочерние элементы
а
;

*[. = 'а']
— выберет все дочерние элементы, текстовое значение которых равно "
а
";

*[name() = 'a']
— выберет все дочерние элементы, имя которых равно "
а
";

*[starts-with(name(), 'a')]
— выберет все дочерние элементы, имя которых начинается с "
а
";

*[. = 'а'][1]
— выберет первый дочерний элемент, текстовое значение которого равно "
а
";

*[. = 'a'][position() mod 2 = 0]
— выберет все дочерние элементы, текстовое значение которых равно "
а
", затем из них выберет четные элементы.

Сокращенный синтаксис

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

[XP10] AbbreviatedAbsoluteLocationPath

 :: = '//' RelativeLocationPath

[XP11] AbbreviatedRelativeLocationPath

 ::= RelativeLocationPath '//' Step

[XP12] AbbreviatedStep

 ::= '.'

     | '..'

[XP13] AbbreviatedAxisSpecifier

 ::= '@'?

Первое сокращение,

'//'
— это краткая версия для
"/descendant-or-self::node()/"
. Шаг выборки
descendant-or-self::node()
возвращает всех потомков контекстного узла (не включая узлов атрибутов и пространств имен). Сокращенный путь вида
'//' RelativeLocationPath
раскрывается в путь вида

'/descendant-or-self::node()/' RelativeLocation

а путь вида

RelativeLocationPath '//' Step
— в путь

RelativeLocationPath '/descendant-or-self::node()/' Step

Сокращенный шаг вида

'.'
возвращает контекстный узел, его полная версия —
self::node()
.

Сокращенный шаг '

..
' возвращает родительский узел контекстного узла. Это сокращение равносильно шагу выборки
parent::node()
.

Заметим, что сокращения

"."
и
".."
являются сокращенными шагами выборки. Это, в частности, означает, что к ним нельзя присовокуплять предикаты и так далее. Выражение
".[ancestor::body]"
будет с точки зрения синтаксиса XPath некорректным. Вместо этого можно использовать выражение
"self::node()[ancestor::body]"
, которое будет синтаксически правильным.

Наиболее часто используемой осью навигации является ось

child
, содержащая все дочерние узлы контекстного узла. Шаги выборки, которые обращаются к дочерним узлам, имеют вид
'child::' NodeTest Predicate*
. Самым полезным сокращением является то, что в шагах такого вида дескриптор оси
'child::'
может быть опущен, и тогда упрощенные шаги будут иметь вид
NodeTest Predicate*
.

Дескриптор оси навигации

'attribute::'
также может быть сокращен до
'@'
. Шаг выборки
'attribute::' NodeTest Predicate*
может быть переписан с использованием, сокращенного синтаксиса в виде
'@'. NodeTest Predicate*
.

Примеры:

.//*
— выберет все элементы-потомки контекстного узла;

..//*
— выберет все дочерние элементы родителя контекстного узла;

@*
— выберет все атрибуты контекстного узла;

.//@*
— выберет все атрибуты всех потомков контекстного узла;

//*
— выберет все элементы документа, содержащего контекстный узел;

//@*
— выберет все атрибуты всех элементов документа, содержащего контекстный узел;

html/body
— выберет элементы
body
, принадлежащие дочерним элементам
html
контекстного узла.

Примеры путей выборки

Простые шаги выборки:

child::*
— выберет все дочерние элементы контекстного узла;

child::comment()
— выберет все узлы комментариев контекстного узла;

child::node()
— выберет все дочерние узлы контекстного узла вне зависимости от их типа;

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

child::xsql:*
— выберет все дочерние элементы, которые находятся в пространстве имен, определяемом префиксом
xsql
;

child::xsql:query
— выберет все дочерние элементы
query
, которые находятся в пространстве имен, определяемом префиксом
xsql
;

attribute::*
— выберет все атрибуты контекстного узла;

attribute::href
— выберет атрибут
href
контекстного узла, если он существует;

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

parent::node()
— выберет родительский узел контекстного узла вне зависимости от его типа. Единственный случай, когда этот шаг выберет пустое множество — это когда контекстный узел является корневым узлом документа;

parent::xsl:template
— выберет родительский узел, если тот является элементом с именем template и имеет пространство имен с префиксом
xsl
, иначе выберет пустое множество;

self::*
— выберет контекстный узел, если он является элементом и пустое множество узлов, если контекстный узел имеет другой тип;

self:*
— выберет все дочерние элементы контекстного узла, принадлежащие пространству имен с префиксом
self
;

self::text()
— выберет контекстный узел, если он является текстовым узлом;

self::node()
— выберет контекстный узел вне зависимости от его типа;

self::query
— выберет контекстный узел, если он является элементом с именем
query
, и пустое множество, если контекстный узел имеет другое имя или не является элементом;

preceding::para
— выберет все элементы
para
, которые предшествуют контекстному узлу в порядке просмотра документа;

preceding::comment()
— выберет все узлы комментариев, которые предшествуют контекстному узлу в порядке просмотра документа;

preceding-sibling::*
— выберет все братские (принадлежащие тому же родителю) элементы контекстного узла, которые предшествуют ему в порядке просмотра документа;

following::processing-instruction('fop')
— выберет все узлы инструкций по обработке, которые имеют имя (целевое приложение)
"fop"
и следуют за контекстным узлом в порядке просмотра документа;

following-sibling::text()
— выберет все текстовые узлы, которые являются братьями контекстного узла и следуют за ним в порядке просмотра документа;

descendant::*
— выберет все элементы-потомки контекстного узла;

descendant::node()
— выберет все узлы-потомки контекстного узла;

descendant::b
— выберет все элементы
b
, являющиеся потомками контекстного узла;

descendant-or-self::*
— выберет все элементы-потомки контекстного узла, а также сам контекстный узел, если он также является элементом;

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

ancestor::node()
— выберет все узлы, являющиеся предками контекстного узла; выбранное множество будет включать корневой узел (за исключением того случая, когда контекстный узел сам является корневым);

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

ancestor-or-self::node()
— выберет контекстный узел, а также все узлы, являющиеся его предками. Выбранное этим шагом множество будет всегда включать корневой узел;

ancestor-or-self::body
— выберет все элементы
body
, которые являются предками контекстного узла, а также сам контекстный узел, если он является элементом
body
;

namespace::*
— выберет все узлы пространств имен, ассоциированные с контекстным узлом; это множество будет, как минимум, содержать узел пространства имен
xml
;

namespace::xyz
— выберет узел пространства имен, определяемого префиксом
xyz
; поскольку один префикс может соответствовать только одному пространству, возвращаемое множество будет содержать не более одного узла.

Шаги выборки с предикатами:

child::*[1]
— выберет первый дочерний элемент контекстного узла; этот шаг выборки равносилен
child::*[position()=1]
;

child::p[1]
— выберет первый дочерний элемент p контекстного узла; этот шаг выборки равносилен
child::p[position()=1]
;

child::*[last()]
— выберет последний дочерний узел контекстного узла; этот шаг выборки равносилен
child::*[position()=last()]
;

child::*[last()-1]
— выберет предпоследний дочерний узел контекстного узла; этот шаг выборки равносилен шагу
child::*[position()=last()]
. Если контекстный узел имеет только один дочерний элемент, выбираемое множество будет пустым;

child::p[position() mod 2 = 0]
— выберет все четные элементы
p
;

child::p[position() mod 2 = 1]
— выберет все нечетные элементы
p
;

child::а[2][attribute::name='b']
— Выберет второй дочерний элемент
а
контекстного узла, если он имеет атрибут
name
со значением
"b"
;

child::a[attribute::name='b'][2]
— выберет второй дочерний элемент а контекстного узла из тех, которые имеют атрибут
name
со значением
"b"
; этот шаг выборки отличается от шага выборки в предыдущем примере — порядок следования предикатов имеет значение;

child::a[position()=$i]
— выберет дочерний элемент
а
, позиция которого равна значению переменной
i
;

parent::*['root']
— выберет родительский узел контекстного узла, если он является элементом; если он является корнем документа, этот шаг выборки не выберет ничего; предикат
['root']
не имеет никакого действия, поскольку строка
'root'
как непустая строка тождественно преобразуется в истину;

preceding-sibling::*[attribute::*]
— выберет все братские узлы контекстного узла, которые предшествуют ему, являются элементами и содержат, по крайней мере, один атрибут;

preceding-sibling:p[1]
— выберет ближайший (первый в обратном порядке просмотра) элемент p, который предшествует контекстному узлу;

following::а[attribute::href][not(descendant::img)]
— выберет все узлы, которые следуют за контекстным в порядке просмотра документа, являются элементами с именем
а
, имеют атрибут
href
, но не имеют элементов-потомков с именем
img
;

ancestor::node()[2]
— выберет второго предка (то есть "деда") контекстного узла;

descendant-or-self::a[attribute::href]
— выберет контекстный узел, а также все узлы-потомки контекстного узла, если они являются элементами с именем
а
и имеют атрибут
href
;

namespace::*[contains(self::node(), 'XML')]
— выберет узлы пространств имен, которые ассоциируются с контекстным узлом, и строковое значение (URI) которых содержит подстроку
'XML'
.

Использование сокращенного синтаксиса в шагах выборки:

table
— выберет все дочерние элементы
table
контекстного узла; этот шаг выборки равносилен
child::table
;

@*
— выберет все атрибуты контекстного узла; полная версия этого шага выборки выглядит как
attribute::*
;

*[i] 
— выберет первый дочерний элемент контекстного узла; этот шаг выборки равносилен шагу
child::*[position()=1]
;

*[last()]
— выберет последний дочерний узел контекстного узла; этот шаг выборки равносилен шагу
child::*[position()=last()]
;

descendant-or-self::a[@href]
— выберет контекстный узел, а также все узлы-потомки контекстного узла, если они являются элементами с именем а и имеют атрибут
href
; этот шаг выборки эквивалентен шагу
descendant-or-self::a[attribute::href]
; еще короче его можно записать как
.//a[@href]
;

following::a[@href][not(@target)]
— выберет все узлы, которые следуют в порядке просмотра документа за контекстным узлом, являются элементами с именем
а
, имеют атрибут
href
, но не имеют атрибута
target
; этот шаг эквивалентен шагу
following::a[attribute::href][not(attribute::target)]
;

..
— выберет родительский узел контекстного узла; этот шаг выборки эквивалентен шагу
parent::node()
;

namespace::*[contains(., 'XML')]
— выберет узлы пространств имен, которые ассоциируются с контекстным узлом, и строковое значение (URI) которых содержит подстроку
'XML'
. Этот шаг выборки эквивалентен шагу
namespace::*[contains(self::node(), 'XML')]
;

preceding-sibling::*[@*]
— выберет все братские узлы контекстного узла, которые предшествуют ему, являются элементами и содержат, по крайней мере, один атрибут;

*[not(self::para)]
— выберет все дочерние элементы контекстного узла, кроме элементов
para
;

*[para or chapter]
— выберет все дочерние элементы контекстного узла, которые имеют хотя бы один дочерний элемент
para
или
chapter
;

*[para and chapter]
— выберет все дочерние элементы контекстного узла, которые имеют хотя бы один дочерний элемент para и хотя бы один дочерний элемент
chapter
;

*[para][chapter]
— выберет все дочерние элементы контекстного узла,
которые
имеют хотя бы один дочерний элемент
para
и хотя бы один дочерний элемент
chapter
; этот шаг выборки равносилен
*[para and chapter]
, однако в общем случае это неверно;

*[* or @*]
— выберет все дочерние элементы, содержащие атрибуты или элементы;

*[not(* or @*)]
— выберет все дочерние элементы, не содержащие ни атрибутов, ни элементов;

*[@name or @href]
— выберет все дочерние элементы контекстного узла, имеющие хотя бы один из атрибутов
name
или
href
;

@*[count(.|../@href) != count(../@href)]
— выберет все атрибуты контекстного узла, кроме атрибутов
href
;

[local-name() != 'href']
— выберет все атрибуты контекстного узла, кроме атрибутов
href
; это выражение практически аналогично предыдущему (за тем исключением, что в этом способе не учитываются пространства имен).

Примеры путей выборки:

/
— выберет корневой узел документа;

/*
— выберет элемент, находящийся в корне документа (элемент документа);

ancestor::body/a
— выберет все элементы
а
, принадлежащие всем предкам-элементам
body
контекстного узла;

/ancestor::body/a
— выберет все элементы
а
, принадлежащие всем предкам-элементам
body
корневого узла (это будет пустое множество);

//ancestor::body/a
— выберет все элементы
а
, принадлежащие всем предкам-элементам
body
корневого узла и потомков корневого узла; иными словами, путь
//ancestor::body
выбирает элементы
body
, являющиеся предками каких-либо узлов документа, шаг
a
— дочерние узлы этих элементов; это выражение равносильно выражению
//body[node()]/a
;

preceding::а/@b
— выберет атрибуты
b
элементов
а
, предшествующих контекстному узлу;

/doc/chapter
— выберет элементы
chapter
, принадлежащие элементам
doc
, которые находятся в корне документа;

//doc/chapter
— выберет элементы
chapter
, которые находятся в любом элементе
doc
документа;

doc/chapter
— выберет элементы
chapter
, которые находятся в дочерних элементах
doc
контекстного узла;

self::node()[ancestor::body[1]]
— выберет множество, состоящее из контекстного узла, если во множестве его предков
body
есть первый элемент (иначе — пустое множество); это выражение равносильно выражению
self::node()[ancestor::body]
, поскольку если
ancestor::body
— непустое множество, то у него будет первый элемент;

*[contains(name(), 'ody')]/*[contains(name(),'able')]
— выберет множество элементов, в имени которых присутствует строка
"able"
при условии, что они принадлежат дочерним элементам контекстного узла, в имени которых присутствует строка
"ody"
;

*[last()]/preceding-sibling::*[2]
— выберет второй с конца дочерний элемент контекстного узла. Это выражение равносильно
*[last()-2]
;

*/@*
— выберет все атрибуты всех дочерних элементов контекстного узла;

//* [local-name(.) = 'body']
— выберет все элементы
body
текущего документа вне зависимости от их пространств имен.

Паттерны

В языке XSLT определяется подмножество выражений языка XPath, которые называются паттернами (от англ. pattern — образец). Паттерны представляют собой упрощенные пути выборки, которые используются для определения, соответствует ли узел заданному образцу.

Чаще всего паттерны применяются в элементе

xsl:template
в атрибуте
match
. Шаблоны такого типа будут выполняться только для тех узлов, которые удовлетворяют заданному образцу. Например, следующий шаблон будет выполняться только для элементов
body
, принадлежащих элементу
html
:

 ...

Кроме этого, паттерны применяются при нумерации и при определениях ключей.

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

child
,
attribute
и
descendant-or-self
, причем ось навигации
descendant-or-self 
может быть указана только в сокращенном виде оператором "
//
". То, что в паттернах используются только оси атрибутов и узлов-потомков, позволяет XSLT-процессорам значительно оптимизировать процесс сопоставления узла заданному образцу — ведь теперь даже в самом худшем сценарии не нужно метаться по всему документу, выбирая узлы, содержащиеся в тех или иных осях навигации. Правда, оператор "
//
" остается не менее опасным — при его проверке может понадобиться перебрать всех предков текущего узла, что может быть весьма и весьма затруднительно (хотя и проще, чем перебор всех потомков).

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

X
, паттерну
body/a
, совершенно необязательно вычислять путь выборки
body/a
и затем проверять, входит ли узел
X
в полученное множество. Достаточно проверить, является ли именем узла "
a
", а именем его родителя (если он, конечно, есть) — "
body
".

Образцы для сравнения могут состоять из одного или нескольких паттернов, которые перечисляются через знак "

|
". Для того чтобы соответствовать такому перечислению в целом, узел должен соответствовать хотя бы одному из паттернов, входящих в него. Здесь тоже есть определенная аналогия с множествами, оператор "
|
" означает как бы объединение: узел входит в объединение множеств, если он входит хотя бы в одно из объединяемых множеств. Но, конечно же, и здесь упрощенный синтаксис играет свою роль для оптимизации — оперировать множествами, выбираемыми каждым из паттернов, было бы очень неэкономно.

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

NodeTest
,
Predicate
и другие).

При нумерации EBNF-продукций паттернов мы будем нумеровать их с префиксом

PT
(
[PT1]
,
[PT2]
и т.д.), чтобы не путать с продукциями других языков, рассматриваемых в этой книге.

Самая общая продукция паттерна называется

Pattern
и показывает, что образец соответствия может быть как одиночным паттерном, так и перечислением нескольких паттернов с разделяющими символами "
|
". Продукция
LocationPathPattern
соответствует одиночному паттерну, показывая своим названием (англ. location path pattern — образец пути выборки) конструкционную близость к самим путям выборки.

[PT1] Pattern ::= LocationPathPattern

                  | Pattern '|' LocationPathPattern

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

[PT2] LocationPathPattern

 ::= '/' RelativePathPattern?

     | IdKeyPattern (('/' | '//') RelativePathPattern)?

     | '//'? RelativePathPattern

Упростив эту продукцию, мы получим следующее правило:

LocationPathPattern ::= '/'

                        | RelativePathPattern

                        | '/' RelativePathPattern

                        | '//' RelativePathPattern

                        | IdKeyPattern

                        | IdKeyPattern '/' RelativePathPattern

                        | IdKeyPattern '//' RelativePathPattern

Если учесть, что нетерминал

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

□ Паттерну

'/'
соответствует только корневой узел.

□ Паттерн

RelativePathPattern
задает образец относительного пути. Например, паттерну
a/b
соответствуют элементы
b
, находящиеся в элементах
a
.

□ Паттерну

'/' RelativePathPattern
соответствуют узлы, которые соответствуют образцу относительного пути при отсчете от корневого узла. Например, паттерну
/a/b
соответствуют элементы
b
, находящиеся в элементах
a
, находящихся в корне документа.

□ Паттерну

'//' RelativePathPattern
соответствуют узлы, которые соответствуют относительному пути при отсчете от любого узла документа. Например, паттерну
//a/b
соответствуют любые элементы
b
, имеющие родителем элемент с именем
а
. Фактически, этот паттерн не отличается от паттерна
a/b
(единственное различие в том, что они могут иметь разные приоритеты).

Последние три случая в правиле

LocationPathPattern
относятся к таким механизмам XSLT, как адресация по уникальным идентификаторам и ключам.

В первой главе книги, когда мы описывали синтаксис и семантику языка разметки документов XML, мы коротко остановились на уникальных атрибутах — атрибутах, которые определяются типом

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

XSLT позволяет использовать уникальные атрибуты элементов при помощи функции

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

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

key
.

Поскольку два этих механизма схожи по семантике, они определяются в XSLT в едином паттерне:

[PT3] IdKeyPattern ::= 'id' '(' Literal ')'

                       | 'key' '(' Literal ',' Literal ')'

Этому паттерну соответствуют только узлы, принадлежащие результату одной из двух функций —

id
или
key
.

Оставим детали использования ключей и

ID
-атрибутов на потом и вернемся к разбору вариантов синтаксиса паттернов.

□ Паттерну

IdKeyPattern '/' RelativePathPattern
соответствуют узлы, которые соответствуют образцу пути
RelativePathPattern
отсчитанного относительного узла, соответствующего
IdKeyPattern
. Например, узел соответствует паттерну
id('index5')/a/b
, если он является элементом с именем
b
, его родителем является элемент
а
, а его родитель в свою очередь имеет уникальный атрибут со значением
"index5"
.

□ Паттерн

IdKeyPattern '//' RelativePathPattern
аналогичен предыдущему: ему соответствуют узлы, которые соответствуют паттерну
RelativePathPattern
, отсчитанному от любого потомка или самого узла, входящего в
IdKeyPattern
. Например, паттерну
id('index5')//a/b
будет соответствовать любой дочерний элемент
b
элемента
a
, являющегося потомком элемента, уникальный атрибут которого имеет значение
index5
, или если он сам имеет такой атрибут.

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

id
и
key
, а пока обратимся к главной детали всех вышеперечисленных продукций — к образцу относительного пути,
RelativePathPattern
. Его продукция записывается в следующем виде:

[PT4] RelativePathPattern

 ::= StepPattern

     | RelativePathPattern '/' StepPattern

     | RelativePathPattern '//' StepPattern

Если сравнить это правило с упрощенной продукцией

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

Эти шаги соответствуют продукции

StepPattern
, которая отличается от продукции
Step
только тем, что разрешает использовать только оси
child
и
attribute
.

[PT5] StepPattern ::= ChildOrAttributeAxisSpecifier NodeTest

                      Predicate*

Продукция

ChildOrAxisSpecifier
описывает дескрипторы осей
child
и
attribute
в полном или сокращенном виде:

[P6] ChildOrAttributeAxisSpecifier

 ::= AbbreviatedAxisSpecifier

     | ('child' | 'attribute') '::'

Для простоты мы можем раскрыть эту продукцию, получив ее в следующем виде:

ChildOrAttributeAxisSpecifier

 ::= '@' ?

     | 'child::'

     | 'attribute::'

Тогда продукцию

StepPattern
тоже можно переписать:

StepPattern ::= NodeTest Predicate*

                | '@' NodeTest Predicate*

                | 'child::' NodeTest Predicate*

                | 'attribute::' NodeTest Predicate*

Теперь стало совершенно очевидно, что шаг паттерна это не что иное, как подмножество шагов выборки, в которых ограничено множество осей навигации.

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

descendant-or-self
в виде оператора), но зато можно в качестве узла отсчета использовать узел, выбранный по своему уникальному атрибуту или по значению ключа.

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

□ атрибуты

count
и
from
элемента
xsl:number
;

□ атрибут

match
элемента
xsl:key
;

□ атрибут

match
элемента
xsl:template
.

Последние два случая паттернов отличаются от первого тем, что в них нельзя использовать переменные. Определение вида

 ...

будет некорректным.

Семантика паттернов

Остановимся подробнее на вопросе — что же означает "соответствие узла некоторому паттерну".

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

Узел

X
соответствует паттерну
P
тогда и только тогда, когда существует такой узел
Y
, принадлежащий оси
ancestor-or-self
узла
X
, что множество, получаемое в результате вычисления выражения
P
в контексте узла
Y
будет содержать узел
X
.

Пример

Рассмотрим это определение на примере паттерна

body//а
. Строго говоря, узел будет соответствовать этому паттерну, если во множестве его предков (плюс сам узел) найдется такой узел, что множество
body//а
, вычисленное в его контексте, будет содержать проверяемый узел. На практике первые два элемента
а
приведенного ниже документа соответствуют этому паттерну, потому что существует элемент
html
, содержащий элемент
body
, потомками которого эти элементы
а
являются.

Листинг 6.2

<а>

Существует также и более простое определение соответствия. Узел

X
соответствует паттерну
P
тогда и только тогда, когда
X
принадлежит множеству
//P
. В приведенном выше примере паттерну
body//а
соответствуют все узлы множества
//body//а
.

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

Примеры паттернов

body
— соответствует элементам
body
с нулевым пространством имен;

xhtml:body
— соответствует элементам
body
, принадлежащим пространству имен с префиксом
xhtml
;

body/a
— соответствует дочерним элементам
а
элемента
body
;

*
— соответствует любому элементу, который принадлежит нулевому пространству имен;

а[1]
— соответствует каждому первому элементу
а
своего родительского узла; элемент будет соответствовать этому паттерну, если ему не предшествует никакой братский элемент
a
— то есть из всех дочерних элементов
а
некоторого узла этому паттерну будет соответствовать только первый в порядке просмотра документа элемент;

a[position() mod 2 = 0]
— соответствует каждому четному элементу
a
своего родительского узла; иначе говоря, из всех элементов
а
некоторого узла этому паттерну будут соответствовать только четные;

/
— соответствует корневому узлу;

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

//html
— соответствует любому элементу
html
документа, принадлежащему нулевому пространству имен; этот паттерн равносилен паттерну
html
;

*[starts-with(local-name(), 'A') or starts-with(local-name(), 'a')]
— соответствует любому элементу, имя которого начинается на букву
"а"
в любом регистре символов;

*[string-length(local-name())=2]
— соответствует любому элементу, локальная часть имени которого состоит из двух символов;

*[starts-with(namespace-uri(),'http') or starts-with(namespace-uri(), 'HTTP')]
— соответствует любому элементу, URI пространства имен которого начинается на
"http"
или
"HTTP"
;

br[not(*)]
— соответствует элементу
br
, который не имеет дочерних элементов;

id('i')
— соответствует элементу, уникальный атрибут которого (атрибут, имеющий тип
ID
) равен
"i"
;

id('i')/@id
— соответствует атрибуту
id
элемента, уникальный атрибут которого равен
"i"
; заметим, что уникальный атрибут элемента вовсе не обязательно должен иметь имя
id
;

key('name', 'html')/@href
— соответствует атрибуту
href
узла, значение ключа с именем
"name"
которого равно
"html"
;

*|@*
— соответствует любому элементу или атрибуту;

a|b|с
— соответствует элементам
а
,
b
и
с
;

node()
— соответствует любому узлу, кроме узла атрибута и пространства имен (поскольку они не являются дочерними узлами своих родителей);

node() | attribute::* | namespace::*
— соответствует любому узлу, включая узлы атрибутов и пространств имен;

node()[not(self::text())]
— соответствует любому узлу, кроме текстового узла, узла атрибута и узла пространства имен.

Выражения