По мере распространения 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. Древовидная структура каталогов
В этой иерархии каталогов путь "
/
" соответствует корневому каталогу, путь "/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. Базовые типы узлов и направления просмотра осей навигации
Ось навигации | Базовый тип узла | Направление просмотра |
---|---|---|
|
Узел элемента | Нет |
|
Узел элемента | Прямое |
|
Узел элемента | Нет |
|
Узел элемента | Прямое |
|
Узел элемента | Прямое |
|
Узел элемента | Обратное |
|
Узел элемента | Обратное |
|
Узел элемента | Прямое |
|
Узел элемента | Прямое |
|
Узел элемента | Обратное |
|
Узел элемента | Обратное |
|
Узел атрибута | Прямое |
|
Узел пространства имен | Прямое |
Базовый тип влияет на то, как в данном шаге выборки будет выполняться тест узла, а направление просмотра на позицию, которую будет занимать тот или иной узел в данном направлении.
Легче всего понять, какие узлы и в каком порядке содержат те или иные оси навигации, представив это графически. Рис. 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
, потомками которого эти элементы а
являются.
<а>
а>
Существует также и более простое определение соответствия. Узел
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())]
— соответствует любому узлу, кроме текстового узла, узла атрибута и узла пространства имен.
Выражения XPath являются наиболее общими конструкциями этого языка. Пути выборки, разобранные ранее, — это всего лишь частный случай выражения. Выражения включают в себя арифметические и логические операции, вызов функций, операции с путями выборки и так далее.
Выражениям языка соответствует нетерминал
Expr
. И хотя синтаксическое правило, определяющее этот нетерминал, записывается очень просто, в данный момент оно нам абсолютно ничего не скажет.
Базовая конструкция, использующаяся в выражениях, называется первичным выражением (от англ. primary expression). Первичные выражения могут быть переменными, литералами, числами, вызовами функций, а также обычными выражениями Expr, сгруппированными в круглых скобках:
[XP15] PrimaryExpr ::= VariableReference
| '(' Expr ')'
| Literal
| Number
| FunctionCall
Переменные вызываются в выражениях XPath по своему имени, которому предшествует символ "
$
". Например, если мы объявили переменную nodes
:
то использовать в выражениях мы ее будем как
$nodes
.
Переменные, так же как элементы и атрибуты XML, могут иметь расширенные имена вида
QName
, состоящие из префикса пространства имен и локальной части имени. Это позволяет создавать переменные, принадлежащие различным пространствам имен.
Мы можем определить две переменные с одинаковыми локальными частями имен в разных пространствах, используя при определении имени префикс. Естественно, префикс должен быть заранее связан с URI пространства имен.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:a="uri:a"
xmlns:b="uri:b">
...
В этом преобразовании количество элементов документа, принадлежащих пространству имен
а
, будет содержаться в переменной a:elementcount
, а пространству имен b
— в переменной b:elementcount
.
Отсутствие префикса в XPath-выражениях не означает, что следует использовать префикс по умолчанию. Отсутствие префикса означает, что префикс является нулевым со всеми вытекающими последствиями. Например, если шаблон
будет корректен, в шаблоне
процессор не сможет найти объявление переменной
$elementcount
, потому что расширенное имя объявленной переменной состоит из URI пространства имен "uri:а"
и локальной части имени elementcount
, а расширенное имя переменной elementcount
состоит из нулевого URI и локальной части elementcount
. Иными словами, эти переменные принадлежат разным пространствам.
XPath поддерживает только две логические операции —
and
(логическое "и") и or
(логическое "или"). В XPath нет оператора логического отрицания, вместо него применяется функция not
, которая возвращает "истину", если аргументом была "ложь" и наоборот.
Логические операторы в XPath бинарны, то есть требуют два операнда булевого типа. Если операнды имеют другой тип, то они будут приведены к булевым значениям.
Логические вычисления в XPath абсолютно стандартны. Мы приведем их для справки в табл. 6.2.
Таблица 6.2. Вычисление логических выражений
Значение | Выражение | ||
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Как и во многих других языках, операция "и" (
and
) старше операции "или" (or
). Например, такое выражение, как A and B or C and D or E
эквивалентно выражению (A and В) or (С and D) or E
.
Приведем синтаксические правила продукций логических операций XPath. Операции "или" соответствует продукция
OrExpr
, операции "и" — продукция AndExpr
.
[XP21] OrExpr ::= AndExpr | OrExpr 'or' AndExpr
[XP22] AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
Перечень арифметических операций в XPath довольно ограничен. К ним относится сложение, вычитание, умножение, деление и унарная операция отрицания, которая меняет значение операнда на противоположное. Кроме того, числа можно сравнивать при помощи операторов сравнения.
Арифметические операции XPath сведены в табл. 6.3.
Таблица 6.3. Арифметические операции в XPath-выражениях
Операция | Синтаксис |
---|---|
Сложение |
|
Вычитание |
|
Умножение |
|
Деление |
|
Остаток деления |
|
Унарное отрицание |
|
Если операнды, значения, участвующие в операции, не являются числами, они сначала приводятся к этому типу, а уж затем производится операция. Например, можно легко перемножать литералы:
'2' * '2'
→ 4
Арифметические действия в XPath работают, что называется, "как обычно", то есть совершенно стандартным образом. Арифметика XPath основывается на стандарте IEEE 754, который был использован и в других распространенных языках программирования, например в Java. Пожалуй, следует лишь обратить внимание на операторы деления, поскольку в разных языках они означают разные действия и потому легко запутаться.
Оператор
div
делит свой первый операнд на второй. Это не целочисленное деление, как в некоторых других языках, div
осуществляет деление чисел с плавающей точкой. Оператор div
аналогичен оператору деления "/
" в таких языках, как Java, С++ и Pascal.
Примеры:
3.2 div 2.5
→ 1.28
3.2 div -2.5
→ -1.28
-3.2 div -2.5
→ 1.28
Оператор
mod
возвращает остаток деления первого своего оператора на второй. Поскольку в разных языках остаток деления вычисляется по-разному, легче всего будет пояснить его функционирование в XPath на примере:
3.2 mod 2
→ 1.2
3.2 mod -2
→ 1.2
-3.2 mod 2
→ -1.2
-3.2 mod -2
→ -1.2
Оператор mod аналогичен оператору "
%
" в таких языках, как Java и ECMAScript.
Результат остатка от деления имеет тот же знак, что и делимое. Этот факт можно использовать для того, чтобы выполнять деление без остатка, например число
A
можно нацело разделить на число B
выражением (A - (A mod B)) div B
.
Пример:
(3.2 - (3.2 mod 2)) div 2
→ 1
Во избежание ошибок следует аккуратно использовать знак вычитания в арифметических операциях. Дело в том, что синтаксис XML разрешает использовать символ "
-
" в именах элементов, которые в свою очередь также могут быть использованы в выражениях в составе путей выборки. Например, A - B
означает разность A
и B
, в то время как A-B
будет воспринято, как имя 'A-B
'. Поэтому рекомендуется выделять знак минуса пробелами.
Приведем продукции выражений с арифметическими операциями.
Унарному отрицанию соответствует продукция
UnaryExpr
. Как можно видеть, в текущей версии языка это — единственная унарная операция (то есть операция одного элемента).
[XP27] UnaryExpr ::= UnionExpr | '-' UnaryExpr
Попробуем упростить это правило, раскрыв рекурсию
UnaryExpr ::= '-' * UnionExpr
Таким образом, унарное отрицание можно повторять несколько раз:
------5
→ 5
Умножению, делению и вычислению остатка деления соответствует одна продукция
MultiplicativeExpr
:
[XP26] MultiplicativeExpr ::= UnaryExpr
| MultiplicativeExpr MultiplyOperator UnaryExpr
| MultiplicativeExpr 'div' UnaryExpr
| MultiplicativeExpr 'mod' UnaryExpr
Оператор умножения вынесен в отдельное правило:
[XP34] MultiplyOperator ::= '*'
Сложению и вычитанию соответствует правило
AdditiveExpr
:
[XP25] AdditiveExpr ::= MultiplicativeExpr
| AdditiveExpr '+' MultiplicativeExpr
| AdditiveExpr '-' MultiplicativeExpr
XPath позволяет сравнивать числа при помощи операторов, перечисленных в табл. 6.4.
Таблица 6.4. Операторы сравнения
Оператор | Значение |
---|---|
|
Равно |
|
Не равно |
|
Меньше |
|
Больше |
|
Меньше или равно (не больше) |
|
Больше или равно (не меньше) |
XPath-выражения чаще всего используются в значениях атрибутов, символ "
<
" в которых в соответствии с синтаксисом XML использовать нельзя. Поэтому операторы сравнения "меньше" ("<
") и "не больше" ("<=
") нужно записывать с использованием сущностей. Оператор "меньше" может быть записан как "<
;", а оператор "не больше" как "<=
".
Результатом обработки элемента
будет строка "
true
".
Сравнение всегда требует наличия двух операндов числового типа. Если операнды не являются числами, они будут соответствующим образом преобразованы.
В XPath вполне корректным будет выражение вида
A > B > C
. Однако результат его будет довольно неожиданным. В XPath действует правило левой ассоциативности операторов сравнения, поэтому A > B > C
будет равносильно (А > B) > C
. То есть A
будет сравнено с B
, затем результат, истина или ложь, будет преобразован в числовой тип (получится 1
или 0
) и затем сравнен со значением C
.
Пример:
3 > 2 > 1
→ (3 > 2) > 1
→ 1 > 1
→ false
3 > 2 > 0
→ (3 > 2) > 0
→ 1 > 0
→ true
Неравенствам в XPath соответствует продукция
RelationalExpr
:
[XP24] RelationalExpr ::= AdditiveExpr
| RelationalExpr '<' AdditiveExpr
| RelationalExpr '>' AdditiveExpr
| RelationalExpr '<=' AdditiveExpr
| RelationalExpr '>=' AdditiveExpr
Операции "равно" и "не равно" записываются при помощи продукции
EqualityExpr:
[XP23] EqualityExpr ::= RelationalExpr
| EqualityExpr '=' RelationalExpr
| EqualityExpr '!=' RelationalExpr
Три основные операции с множествами узлов, которые поддерживает язык XPath, — это фильтрация множества, выборка с использованием путей и объединение.
Множества узлов, которые получаются в результате вычисления выражений, можно фильтровать — то есть выбирать из них узлы, удовлетворяющие заданным свойствам подобно тому, как это делалось предикатами в шагах выборки.
В выражениях множества узлов могут также фильтроваться одним или несколькими предикатами. Узел остается в фильтруемом множестве, только если он удовлетворяет всем предикатам поочередно.
Предположим, что нам нужно оставить в фильтруемом множестве узлов, которое присвоено переменной
nodes
, только те узлы, которые имеют имя а
и атрибут href
. Искомое выражение может быть записано следующим образом:
$nodes[self::а][@href]
Можно использовать и более сложные конструкции, например, фильтровать объединение двух множеств — присвоенного переменной
nodes
и возвращаемого путем выборки body/*
:
($nodes|body/*)[self::a][@href]
Выражение, в котором производится фильтрация узлов, отвечает EBNF-правилу
FilterExpr
:
[XP20] FilterExpr ::= PrimaryExpr | FilterExpr Predicate
Если раскрыть рекурсию, которая имеется в этом правиле, его можно переписать в более простом виде:
FilterExpr ::= PrimaryExpr Predicate*
Выражение
PrimaryExpr
, которое используется в этой продукции, должно обязательным образом возвращать множество узлов. В противном случае процессор выдаст ошибку, потому что никакой другой тип не может быть преобразован во множество узлов.
Помимо того, что выражение само по себе может быть путем выборки, относительные пути можно комбинировать с другими выражениями. Например, можно выбрать все дочерние элементы узлов множества, содержащегося в переменной
$nodes/*
Для разделения шагов выборки в фильтрующих выражениях можно использовать операторы "
/
" и "//
". Например, для того, чтобы получить всех потомков узлов из множества, присвоенного переменной, можно использовать выражение вида
$nodes//node()
Здесь
node()
— это тест узла, выполняющийся для всех типов узлов, а //
, как и обычно, сокращение от /descendant-or-self:node()/
.
Выражения, которые используют пути выборки, соответствуют продукции
PathExpr
:
[XP19] PathExpr ::= LocationPath
| FilterExpr
| FilterExpr '/' RelativeLocationPath
| FilterExpr '//' RelativeLocationPath
Множества могут быть объединены при помощи оператора "
|
". В объединение будут входить узлы, которые присутствуют хотя бы в одном из множеств, причем результат не содержит повторений. Объединять можно любые выражения, результатом вычисления которых являются множества узлов.
Множество всех элементов
а
, b
и с
документа может быть задано выражением //a|//b|//c
.
Выражению объединения соответствует продукция
UnionExpr
:
[XP18] UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
Теперь, когда мы изучили все типы операций XPath, можно дать синтаксическое определение выражению и выстроить все операции в порядке старшинства.
Выражению, как самой общей конструкции XPath, соответствует продукция
Expr
, которая определяется следующим образом:
[XP14] Expr ::= OrExpr
То есть, фактически, выражение в XPath определяется через логическое выражение. Естественно, выражения не обязаны быть логическими. Просто в иерархии синтаксических правил логическое выражение "или" находится выше всего. Верхние правила определяются через более примитивные правила и так далее. В итоге иерархия выражений выстраивается следующим образом (в скобках приведены названия EBNF-правил):
□ выражения (
Expr
);
□ логические выражения "или" (
OrExpr
);
□ логические выражения "и" (
AndExpr
);
□ выражения равенства и неравенства (
EqualityExpr
);
□ выражения сравнения (
RelationalExpr
);
□ выражения сложения и вычитания (
AdditiveExpr
);
□ выражения умножения и деления (
MultiplicativeExpr
);
□ унарные выражения (
UnaryExpr
);
□ выражения объединения множеств (
UnionExpr
);
□ выражения путей выборки (
PathExpr
);
□ пути выборки (
LocationPath
), фильтрация множеств (FilterExpr
), относительные пути выборки (RelativeLocationPath
).
По этой схеме несложно выяснить старшинство операций — чем ниже выражение находится в этой иерархии, тем выше его приоритет. Для полной ясности, перечислим операции в порядке старшинства от старших, с большим приоритетом, к младшим, с меньшим приоритетом выполнения:
□ операции с путями выборки;
□ операция объединения множеств (
|
);
□ унарная операция отрицания (
-
);
□ умножение, деление и вычисление остатка от деления (
*
, div
и mod
);
□ операции сложения и вычитания (
+
и -
);
□ операции сравнения (
<
, >
, <=
, =>
);
□ операции проверки равенства и неравенства (
=
и !=
);
□ операция "и" (
and
);
□ операция "или" (
or
).
Операции одного порядка имеют левую ассоциативность, как это было показано на примере с операциями сравнения (
3 > 2 > 1
равносильно (3 > 2) >1
).
Кроме операций, которые обеспечивают примитивные базовые действия, в XPath можно использовать функции. Спецификация языка XPath определяет базовую библиотеку функций, которую должны поддерживать все XSLT-процессоры. В XSLT эта библиотека дополняется еще несколькими полезными функциями. Кроме этого, большинство процессоров реализуют механизм расширений, при помощи которого можно использовать в XSLT собственные функции.
В этой главе мы будем рассматривать только функции базовой библиотеки XPath. Функции, которые добавляются в XSLT, будут разбираться чуть позже, а функции-расширения и их создание вообще является достаточно сложной темой, вынесенной в отдельную главу.
Прежде, чем разбирать функции, рассмотрим синтаксис их вызова. Он описывается правилом
FunctionCall
:
[XP16] FunctionCall ::= FunctionName
'(' ( Argument ( ',' Argument )* )? ')'
Таким образом, вызов функции состоит из имени и перечисленных в круглых скобках аргументов, которых может в принципе и не быть. С точки зрения синтаксиса аргументом функции может быть любое выражение, однако на практике функции предъявляют к своим аргументам определенные требования. Мы будем записывать правила вызова той или иной функции прототипом вида
тип1 функция(тип2, тип3, тип4?)
где
тип1
— тип возвращаемого значения, тип2
, тип3
, тип4
— типы передаваемых параметров, символ "?
" обозначает аргумент, который может быть опущен. Также может быть использован символ *
для обозначения аргумента, который может повторяться несколько раз. Например,
string concat(string, string, string*)
определяет функцию
concat
, которая возвращает строку, а на вход принимает два или более строковых параметра.
Аргументы функции отвечают EBNF-продукции
Argument
:
[XP17] Argument ::= Expr
Имя функции определяется синтаксическим правилом
FunctionName
. Функция может иметь любое корректное с точки зрения XML имя, кроме названий типов узлов (comment
, processing-instruction
, text
и node
):
[XP35] FunctionName ::= QName - NodeType
В базовой библиотеке XPath выделяют четыре типа функций: функции для работы с булевыми значениями, с числами, со строками и с множествами узлов.
boolean boolean(object)
Функция
boolean
явным образом преобразует объект, который ей передается в булевый тип в соответствии с правилами, изложенными в главе "Типы данных XPath". Напомним вкратце эти правила.
□ Число преобразуется в "ложь", если оно является положительным или отрицательным нулем или не-числом (
NaN
). В противном случае число будет преобразовано в "истину".
□ Строка преобразуется в "ложь", если она не содержит символов, то есть, ее длина равна нулю. Непустая строка преобразуется в "истину".
□ Множество узлов преобразуется в "ложь", если оно пусто. Непустое множество узлов преобразуется в "истину".
□ Объекты других типов преобразуются в булевые значения по собственным правилам. Например, результирующий фрагмент дерева всегда преобразуется в "истину".
Примеры:
boolean(2-2)
→ false
boolean(number('two'))
→ false
boolean(-1)
→ true
boolean(1 div 0)
→ true
boolean(-1 div (1 div 0))
→ false
boolean(-1 div (-1 div 0))
→ false
boolean(-1 div (-1 div 0) +1)
→ true
boolean('')
→ false
boolean('true')
→ true
boolean('false')
→ true
boolean(/)
→ true
Это выражение всегда будет обращаться в
true
, поскольку у документа всегда есть корневой узел.
boolean(/self::node())
→ true
Это выражение также обратится в
true
, поскольку корневой узел соответствует тесту node()
.
boolean(/self::text())
→ false
Это выражение обратится в
false
, поскольку корневой узел не является текстовым узлом.
boolean not(boolean)
Функция
not
выполняет логическое отрицание. Если аргументом была "истина", not
возвращает "ложь", если аргумент был "ложью", not
вернет "истину". Если функции был передан аргумент не булевого типа (например, число), то он сначала будет сконвертирован в тип boolean
.
Примеры:
not(false)
→ true
not(true)
→ false
not('false')
→ false
not('true')
→ false
not(0)
→ true
not(/)
→ false
boolean true()
boolean false()
Две функции
true
и false
возвращают тождественную "истину" и тождественную "ложь" соответственно. В XPath нет констант и, тем более, логических констант, определяющих "истину" и "ложь", как в других языках. Функции true
и false
восполняют эту нехватку.
Примеры:
true() or $var
→ true
Это выражение всегда будет истинным вне зависимости от значения переменной
var
, поскольку дизъюнкция (логическая операция "или") с тождественной "истиной" всегда будет "истиной".
false() and $var
→ false
Это выражение всегда будет ложным вне зависимости от значения переменной
var
, поскольку конъюнкция (логическая операция "и") с тождественной "ложью" всегда будет "ложью".
boolean lang(string)
Функция
lang
может использоваться для того, чтобы определить языковой контекст контекстного узла. В элементах XML можно использовать атрибут lang
пространства имен xml
для определения языка содержимого узла, например;
Yet no living human being have been ever blessed with seeing...
Пространство имен, соответствующее префиксу
xml
, не требуется объявлять. Это служебное пространство имен, которое неявно задано во всех XML-документах.
Функция
lang
возвратит "истину", если идентификатор языка, который передан ей в виде строкового параметра, соответствует языковому контексту контекстного узла. Это определяется следующим образом.
□ Если ни один из предков контекстного узла не имеет атрибута
xml:lang
, функция возвращает "ложь".
□ Иначе строковый параметр проверяется на соответствие значению атрибута
xml:lang
ближайшего предка. Если эти значения равны в любом регистре символов, или атрибут начинается как значение параметра функции и имеет суффикс, начинающийся знаком "-
", функция возвращает "истину".
□ В противном случае функция возвращает "ложь".
Примеры:
Функция
lang('en')
возвратит "истину" в контексте любого из следующих элементов:
Функция
lang('de')
возвратит "истину" в контексте элемента b
и "ложь" — в контексте элементов а
и с
:
<а>
number number(object?)
Функция
number
явным образом конвертирует свой аргумент в числовой тип. Если аргумент не указан, функции передается множество узлов, состоящее из единственного контекстного узла. Коротко напомним правила преобразования в числовой тип.
□ Значения булевого типа преобразуются в
0
или 1
следующим образом: "ложь" преобразуется в 0
, "истина" в 1
.
□ Строковое значение преобразуется в число, которое оно представляет.
□ Множество узлов сначала преобразуется в строку, а затем, как строка в число. Фактически численным значением множества узлов является численное значение его первого узла.
□ Объекты других типов преобразуются в число в соответствии с собственными правилами. Например, результирующий фрагмент дерева так же как и множество узлов сначала преобразуется к строке, а затем в численный формат.
number($to_be or not($to_be))
→ 1
Значение этого выражения будет
1
, поскольку $to_be or not($to_be)
будет истинным вне зависимости от значения переменной to_be
.
number(false())
→ 0
number('00015.0001000')
→ 15.0001
number('.0001000')
→ 0.0001
number('1.')
→ 1
number('-.1')
→ -0.1
number('-5')
→ -5
number sum(node-set)
Функция
sum
суммирует значения узлов из переданного ей множества. Строковые значения узлов сначала преобразуются в числа, а затем все полученные числа складываются.
- 1
- 3
- 5
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
1
4
9
16
25
В этом преобразовании мы заменяем каждый элемент
item
на сумму значений предшествующих ему элементов плюс собственное значение. Предшествующие элементы выбираются путем выборки preceding-sibling::item
, текущий элемент — сокращенным путем ".
", затем эти два множества объединяются при помощи оператора |
, и, наконец, мы вычисляем сумму значений узлов, входящих в них функцией sum
.
Строковые значения суммируемых узлов преобразовываются в числовой формат так же, как они преобразовывались бы функцией
number
. Например, если входящий документ будет иметь вид
- 1
- 3
- five
- 7
- 9
то на выходе мы получим
1
4
NaN
NaN
NaN
потому что, начиная с третьего элемента, в суммировании будет участвовать значение
number('five')
, то есть не-число (NaN
).
number floor(number)
number ceiling(number)
Функции
floor
и ceiling
(англ. пол и потолок соответственно) осуществляют округление численного аргумента до ближайшего не большего и ближайшего не меньшего целого соответственно.
floor(2.3)
→ 2
ceiling(2.3)
→ 3
floor(-2.3)
→ -3
ceiling(-2.3)
→ -2
floor(-1 div 0)
→ -Infinity
ceiling(-1 div 0)
→ -Infinity
floor('zero')
→ NaN
ceiling(-1 div (-1 div 0))
→ 0
number round(number)
Функция
round
округляет число до ближайшего целого значения. У этой функции есть несколько нюансов, которые мы сейчас разберем.
□ Если дробная часть числа равна 0.5, то функция вернет ближайшее большее целое.
□ Если аргумент является не-числом (NaN), то результат также будет NaN.
□ Если аргумент является положительной или отрицательной бесконечностью, то результатом будет тоже положительная или отрицательная бесконечность, то есть аргумент не изменится.
□ Если аргумент является положительным или отрицательным нулем, результатом будет также положительный или отрицательный нуль, то есть аргумент не изменится.
□ Если аргумент меньше нуля, но больше или равен — 0.5, результатом будет отрицательный нуль.
round(2.5)
→ 3
round(2.49)
→ 2
round(-1.7)
→ -2
1 div round(0.5)
→ Infinity
1 div round(-0.5)
→ -Infinity
round(1 div 0)
→ Infinity
round('one')
→ NaN
string string(object?)
Подобно функциям
boolean
и number
, функция string
преобразует свой аргумент к строковому типу явным образом. Если аргумент опущен, функции передается множество узлов, состоящее из единственного контекстного узла.
Напомним вкратце правила приведения других типов к строке.
□ Булевые значения преобразуются в строку следующим образом:
• "истина" (
true
) преобразуется в строку "true
";
• "ложь" (
false
) преобразуется в строку "false
".
□ Числа преобразуются к строковому виду следующим образом:
• не-число (
NaN
) преобразуется в строку "NaN
";
• положительный нуль преобразуется в строку "
0
";
• отрицательный нуль преобразуется в строку "
0
";
• положительная бесконечность преобразуется в строку "
Infinity
";
• отрицательная бесконечность преобразуется в строку "
-Infinity
";
• положительные целые преобразуются в свое десятичное представление без ведущих нулей и без точки ("
.
"), отделяющей дробную часть от целой;
• отрицательные целые преобразуются так же, как и положительные, но с начальным знаком "минус" ("
-
");
• остальные числа преобразуются в десятичное представление с использованием точки ("
.
"), отделяющей целую часть от дробной части; целая часть не содержит ведущих нулей (кроме случая с числами в интервале (-1;1)), дробная часть содержит столько цифр, сколько требуется для точного представления числа.
□ Множество узлов преобразуется в строковое значение своего первого в порядке просмотра документа узла. Если множество пусто, функция возвращает пустую строку.
□ Объекты других типов преобразуются в строку в соответствии с собственными правилами. Например, результирующий фрагмент дерева преобразуется в конкатенацию всех своих строковых узлов.
string(boolean(0))
→ false
string(number('zero'))
→ NaN
string(number('.50000'))
→0.5
string(number(00500.))
→ 500
Для строкового форматирования чисел рекомендуется использовать функцию XSLT
format-number
совместно с элементом xsl:decimal-format
.
string concat(string, string, string*)
Функция
concat
принимает на вход две или более строки и возвращает конкатенацию (строковое сложение) своих аргументов.
Пример:
concat('not','with','standing',' problem')
→ 'notwithstanding problem'
boolean starts-with(string, string)
Функция
starts-with
принимает на вход два строковых аргумента и возвращает true
, если первая строка начинается второй и false
в противном случае.
starts-with('http://www.xsltdev.ru', 'http')
→ true
starts-with('Title', 'ti')
→ false
boolean contains(string, string)
Функция
contains
принимает на вход два строковых аргумента и возвращает true
, если первая строка содержит вторую и false
в противном случае.
contains('address@host.com', '(@')
→ true
string substring-before(string, string)
Функция
substring-before
принимает на вход два строковых аргумента. Эта функция находит в первой строке вторую и возвращает подстроку, которая ей предшествует. Если вторая строка не содержится в первой, функция вернет пустую строку.
substring-before('12-May-1998', '-')
→ '12'
substring-before('12 May 1998', ' ')
→ '12'
substring-before('12 May 1998', ' ')
→ '12'
substring-before('12 May 1998', '-')
→ ''
string substring-after(string, string)
Эта функция аналогична функции
substring-before
, только она возвращает строку, которая следует за вторым аргументом. Если вторая строка не содержится в первой, эта функция также вернет пустую строку.
substring-after('12-May-1998', '-')
→ 'May-1998'
substring-after('12 May 1998', ' ')
→ 'May 1998'
substring-after('12 May 1998', ' ')
→ 'May 1998'
substring-after('12 May 1998', '-')
→ ''
string substring(string, number, number?)
Функция
substring
возвращает подстроку переданного ей строкового аргумента, которая начинается с позиции, указанной вторым аргументом и длиной, указанной третьим аргументом. Если третий аргумент опущен, подстрока продолжается до конца строки. Если численные аргументы являются нецелыми, они округляются при помощи функции round
.
В XPath позицией первого символа является
1
, а не 0
, как в некоторых других языках программирования.
При вычислении подстроки учитываются следующие условия.
□ Если первый численный аргумент меньше
1
(это относится и к отрицательной бесконечности), то подстрока начинается с начала строки.
□ Если первый численный аргумент больше длины строки (это относится и к положительной бесконечности), то подстрока будет пустой.
□ Если второй численный аргумент меньше
1
(это относится и к отрицательной бесконечности), то подстрока будет пустой.
□ Если второй численный аргумент, сложенный с первым, больше длины строки плюс один, подстрока будет продолжаться до конца строки.
substring('123456', 2, 3)
→ '234'
substring('123456', 2, 5)
→ '23456'
substring('123456', 2, 6)
→ '23456'
substring('123456', 2)
→ '23456'
substring('123456', -4)
→ '123456'
substring('123456', 5, 5)
→ '5'
substring('123456', 5)
→ '56'
substring ('123456', 6)
→ '6'
substring('123456', 1 div 0, )
→ ''
substring('123456', 2, -1)
→ ''
number string-length(string?)
Функция
string-length
возвращает число символов строкового аргумента. Если аргумент опущен, string-length
возвращает длину строкового представления контекстного узла.
Напомним, что длина строки не имеет ничего общего с количеством байт, которое требуется для ее представления. Разные формы кодирования используют разное количество байт для записи символов, внутренние представления строк также могут быть различными, но длина строки в любом случае — это число символов, которые ее составляют.
string-length('Barnes and Noble')
→16
string-length('Barness#x20;& Noble')
→ 14
string normalize-space(string?)
Функция
normalize-space
производит со своим строковым аргументом так называемую нормализацию пробельного пространства. Это означает, что в строке удаляются ведущие и заключающие пробельные символы, а все последовательности пробелов заменяются одним пробельным символом. Иными словами, функция удаляет "лишние" пробелы в строке.
Если аргумент функции опущен, она выполняется со строковым значением контекстного узла.
normalize-space(' А - В - С ')
→ 'А-В-С'
normalize-space('А х9; В х9; С') > 'A B C'
string translate(string, string, string)
Функция
translate
производит замену символов первого своего строкового аргумента, которые присутствуют во втором аргументе на соответствующие символы третьего аргумента.
translate('abcdefgh', 'aceg', 'ACEG')
→ 'AbCdEfGh'
Если некоторый символ повторяется во втором аргументе несколько раз, учитывается только первое его появление.
translate('abcdefgh', 'acaeaga', 'ACBECGD')
→ 'AbCdEfGh'
Если второй аргумент длиннее третьего, символы, для которых нет соответствующей замены, удаляются из строки.
translate('a b-c=d+e|f/g\h', 'aceg-=+|/\', 'ACEG')
→ 'AbCdEfGh'
Если третий аргумент длиннее второго, остаток строки игнорируется.
translate('abcdefgh', 'aceg', 'ACEGBDFH')
→ ' AbCdEfGh'
Функцию
translate
можно использовать, например, для изменения регистра символов. Конечно, это будет работать только для тех языков, для которых такая функция будет записана, но и этого в большинстве случаев будет достаточно. В будущем предполагается включить в новые версии языка более мощные функции для работы с регистрами символов.
Для того чтобы изменять регистр слов русского языка, мы можем определить две переменные,
lowercase
и uppercase
, которые будут содержать строчные и прописные символы основного русского алфавита (мы включили в него букву ё — строчную ("ё
") и прописную ("Ё
"), хотя в соответствии с Unicode она относится к расширениям). Мы также создадим два именованных шаблона, которые будут менять регистр символов строкового параметра str
. Для удобства использования мы вынесем определения переменных и шаблонов во внешний модуль ru.xsl
.
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
name="uppercase"
select="concat('АБВГ',
'ДЕЁЖЗ',
'ИЙКЛ',
'МНОП',
'РСТУ',
'ФХЦЧ',
'ШЩЪЫ',
'ЬЭЮЯ')"/>
name="lowercase"
select="concat('абвг',
'деёжЗ',
'ийкл',
'мноп',
'рсту',
'фхцч',
'шщъы',
'ьэюя')"/>
Использовать этот модуль можно, включив или импортировав его в основное преобразование элементами
xsl:include
или xsl:import
. После этого в основном преобразовании будут доступны переменные lowercase
и uppercase
, которые можно будет использовать в функции translate
и шаблоны с именами lower
и upper
.
Использовать функцию
translate
с переменными lowercase
и uppercase
можно следующим образом:
translate('Дом', $uppercase, $lowercase)
→ 'дом'
translate('Дом', $lowercase, $uppercase)
→ 'ДОМ'
Именованные шаблоны можно вызывать элементом
xsl:call-template
, передавая параметр при помощи xsl:with-param
. Например, следующий фрагмент шаблона
...
>
...
создаст в выходящем дереве текстовый узел со значением "
дом
".
number last()
number position()
Функция
last
возвращает текущий размер контекста — число, которое показывает, сколько узлов находится в обрабатываемом в данный момент множестве.
Функция
position
возвращает позицию контекста — число, показывающее порядковый номер контекстного узла в обрабатываемом множестве.
В этом примере мы будем заменять все элементы элементами вида
...
где атрибут
name
будет содержать имя, a position
— через дробь позицию элемента в контексте и размер контекста.
<а>
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
Отметим, что если бы мы не удаляли лишние пробельные символы во входящем документе при помощи элемента
xsl:strip-space
, в контексте преобразования учитывались бы также и текстовые узлы, которые им соответствуют. Выходящий документ без этого элемента имел бы следующий вид:
number count(node-set)
Функция
count
возвращает число узлов, которое входит во множество, переданное ей в качестве аргумента.
Для того чтобы подсчитать количество всех элементов второго уровня, можно воспользоваться выражением
count(/*/*)
. Например, для входящего документа из примера к функциям last
и position
(листинг 6.7) это выражение примет значение 3
.
Приведем несколько других примеров, используя тот же документ.
Покажем, что дерево документа на листинге 6.7 имеет ровно один корневой узел:
count(/)
→ 1
и ровно один элемент, находящийся в корне:
count(/*)
→ 1
Подсчитаем количество текстовых узлов, принадлежащих элементу
a
(это те самые пробельные текстовые узлы, которые были удалены элементом xsl:strip-space
):
count(/a/text())
→ 4
Подсчитаем общее количество элементов в документе:
count(//*)
→ 6
string local-name(node-set?)
string namespace-uri(node-set?)
string name(node-set?)
Функция
local-name
возвращает локальную часть имени первого в порядке просмотра документа узла множества, переданного ей в качестве аргумента. Эта функция выполняется следующим образом.
□ Если аргумент опущен, то значением функции по умолчанию является множество, содержащее единственный контекстный узел. Иными словами, функция возвратит локальную часть расширенного имени контекстного узла (если она существует).
□ Если аргументом является пустое множество, функция возвращает пустую строку.
□ Если первый в порядке просмотра документа узел переданного множества не имеет расширенного имени, функция возвращает пустую строку.
□ В противном случае функция возвращает локальную часть расширенного имени первого в порядке просмотра документа узла переданного множества.
Функция
namespace-uri
работает совершенно аналогично функции local-name
за тем исключением, что возвращает не локальную часть расширенного имени, a URI пространства имен этого узла. Эта функция выполняется следующим образом.
□ Если аргумент опущен, его значением по умолчанию является множество, содержащее единственный контекстный узел.
□ Если аргументом является пустое множество, функция возвращает пустую строку.
□ Если первый в порядке просмотра документа узел переданного множества не имеет расширенного имени, функция возвращает пустую строку.
□ Если первый в порядке просмотра документа узел переданного множества не принадлежит никакому пространству имен, функция возвращает пустую строку.
□ В противном случае функция возвращает URI пространства имен первого в порядке просмотра документа узла переданного множества.
Функция
name
возвратит имя вида QName
, которое будет соответствовать расширенному имени первого в порядке просмотра документа узла переданного ей множества.
Это имя должно соответствовать расширенному имени узла, то есть должны совпадать локальные части и пространства имен. Вместе с тем, это вовсе не означает, что префиксы также будут совпадать. Например, если в элементе определены несколько префиксов для одного пространства, функция
name
может использовать любой из них.
Для следующего элемента
xmlns:a="http://www.a.com"
xmlns:b="http://www.a.com"
xmlns:c="http://www.a.com"/>
функция
name
может вернуть a:body
, b:body
или c:body
.
Большинство процессоров все же возвращает префикс, с которым узел был объявлен.
Так же как
local-name
и namespace-uri
, функция name имеет следующие особенности использования.
□ Если аргумент опущен, то его значением по умолчанию является множество, содержащее единственный контекстный узел.
□ Если аргументом является пустое множество, то функция возвращает пустую строку.
□ Если первый в порядке просмотра документа узел переданного множества не имеет расширенного имени, то функция возвращает пустую строку.
□ В противном случае функция возвращает имя вида
QName
, соответствующее расширенному имени первого в порядке просмотра документа узла переданного множества.
Мы можем видоизменить преобразование, приведенное в примере к функциям
last
и position
(листинг 6.7), чтобы генерируемые элементы содержали информацию об имени, пространстве имен и локальной части имени элементов.
xmlns:a="http://www.a.com"
xmlns:b="http://www.b.com">
version="1.0"
xmlns:xsl="http: //www.w3.org/1999/XSL/Transform"
xmlns:a="http://www.a.com"
xmlns:b="http://www.b.com">
name="{name()}"
namespace-uri="{namespace-uri()}"
local-name="{local-name()}">
xmlns:a="http://www.a.com"
xmlns:b="http://www.b.com"
name="a:a"
namespace-uri="http://www.a.com"
local-name="a">
namespace-uri="http://www.b.com"
local-name="b">
namespace-uri=""
local-name="c"/>
node-set id(object)
Мы уже встречались с функцией
id
, когда говорили об уникальных атрибутах элементов и еще раз — когда разбирали паттерны. Функция id
позволяет обращаться к элементам по значениями их уникальных атрибутов.
Функция
id
выполняется по-разному в зависимости от того, какой тип данных ей передается.
□ Если аргументом функции является строка, она рассматривается как набор идентификаторов, разделенных пробелами. В результирующее множество узлов войдут узлы тех элементов текущего документа, значения уникальных атрибутов которых входят в набор идентификаторов, определяемый строковым аргументом.
□ Если аргументом функции является множество узлов, результатом ее выполнения будет объединение результатов функции
id
, примененной к строковому значению каждого из узлов множества.
□ Если аргументом функции является объект другого типа, аргумент вначале преобразуется в строку и функция
id
выполняется как со строковым параметром.
Предположим, что мы используем XML для того, чтобы описать граф — множество вершин, соединенных дугами (рис. 6.11).
Рис. 6.11. Изображение графа, который мы будем описывать в XML
Каждой вершине будет соответствовать элемент
vertex
. Для того чтобы описать все связи, мы дадим каждой вершине уникальное имя, которое будет записано в ее ID
-атрибуте name
. Имена вершин, с которыми она соединена, будут перечислены в атрибуте connects
типа IDREFS
.
Документ, описывающий этот граф, может выглядеть следующим образом.
Декларация типа документа вынесена во внешний файл
gemini.dtd
.
name ID #REQUIRED
connects IDREFS #REQUIRED>
При обработке этого документа функция
id
будет очень полезна для выбора элементов соединенных вершин. Действительно, функция id
, которой будет передано значение атрибута connects
(в котором через пробелы перечислены вершины, смежные данной), возвратит множество, состоящее из элементов с перечисленными уникальными идентификаторами. Так, например, функция id('tau upsilon')
возвратит множество, состоящее из двух элементов с атрибутами name, равными tau
и upsilon
соответственно.
Более того, функция
id
может быть вычислена и от множества узлов. В этом случае ее значением будет объединение множеств, полученных в результате выполнения функции от строкового значения каждого узла переданного множества. Например, id(id('tau upsilon')/@connects)
возвратит множество, состоящее из вершин с именами alpha
, beta
, delta
, epsilon
, theta
, iota
и kappa
— множество вершин, смежных с вершинами tau и upsilon.
Приведем пример преобразования, которое в каждый элемент
vertex
добавляет комментарий, в котором перечислены имена вершин, достижимых из текущей, не более чем за два шага.
Для того чтобы найти множество вершин, достижимых за один шаг (иначе говоря, смежных), мы воспользуемся выражением вида
id(@connects)
, для выборки множества вершин, достижимых из текущей за два шага — выражением id(id(@connects)/@connects)
. Таким образом, множество вершин, достижимых не более чем за два шага, будет вычисляться как
id(@connects)|id(id(@connects)/@connects)
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
В этом разделе мы приведем базовые синтаксические правила языка XPath. Со многими из них мы уже встречались в правилах более высокого уровня, некоторые определены в спецификации для того, чтобы облегчить реализацию лексического разбора XPath-выражений в различных процессорах.
Литералы — это строковые значения, заключенные в одинарные или двойные кавычки. В литералах нельзя использовать символ кавычек, в которые они заключены. Кроме этого, поскольку XPath-выражения чаще всего используются в атрибутах элементов, в них нельзя использовать символы "
<
" и "&
" — они должны заменяться на сущности. Литералам соответствует продукция Literal
, определяемая в виде:
[XP29] Literal ::= '"' [^"]* '"' | "'" [^']* "'"
XPath использует десятичную систему счисления. Наборы цифр, соответствующие правилу
Digits
, могут состоять из цифр от 0
до 9
:
[XP31] Digits ::= [0-9]+
Число в XPath состоит из последовательности цифр, которые могут быть разделены точкой, причем точка может стоять как в начале числа (
.5
), так и в конце (5.
). Числу соответствует EBNF-правило Number
:
[XP30] Number ::= Digits ('.' Digits?)? | '.' Digits
Оператору умножения соответствует символ "
*
" и синтаксическое правило MultiplyOperator
:
[XP34] MultiplyOperator ::= '*'
Именам переменных, которые используются в XPath, предшествует символ "
$
". Сами же имена должны удовлетворять продукции QName
, которую мы рассматривали в разделе "Расширенные имена".
[XP36] VariableReference ::= '$' QName
Продукция
NodeType
, использованная в тесте узла (см. раздел "Тесты узлов" данной главы, продукция [XP7]
), определяет типы узлов, которые можно проверить при тесте — comment
(комментарий), text
(текстовый узел), processing-instruction
(узел инструкции по обработке) и node
(узел любого типа). NodeType
записывается следующим образом:
[XP38] NodeType ::= 'comment'
| 'text'
| 'processing-instruction'
| 'node'
Другая конструкция,
NameTest
, которая также используется в тесте узла, проверяет узлы базового типа оси на соответствие определенному имени. EBNF-правило NameTest
имеет следующий синтаксис:
[ХР37] NameTest ::= '*' | NCName ':' '*' | QName
Имя функции в XPath может быть любым корректным XML-именем за исключением тех имен, которые используются для обозначения типов узлов. Правило
FunctionName
имеет вид:
[XP35] FunctionName ::= QName - NodeType
В целях удобочитаемости, в выражениях можно использовать пробельное пространство. Ему соответствует EBNF-правило
ExprWhiteSpace
:
[XP39] ExprWhitespace ::= S
Хотя синтаксис языка XPath укладывается в тридцать с небольшим синтаксических правил, реализация интерпретатора XPath-выражений может быть довольно непростой задачей. Для того чтобы хоть как-то упростить ее, в XPath определяются так называемые токены выражения (англ. expression token). Токены — это единицы, из которых состоит выражение. Будучи сами очень простыми, они выстраиваются в более сложные конструкции, образуя, в итоге, выражения.
Примером токенов являются операторы, которым соответствуют продукции
Operator
и OperatorName
:
[XP33] OperatorName ::= 'and' | 'or' | 'mod* | 'div'
[XP32] Operator ::= OperatorName
| MultiplyOperator
| '/' | '//' | '|' | '+' | '-'
| '=' | '!=' | '<' | '>' | '<=' | '>='
Продукция самого токена выражения имеет вид:
[ХР28] ExprToken ::= '(' | ')' | '[' | ']'
| ' . ' | ' .. ' | '@' | ' | ':: '
| NameTest
| NodeType
| Operator
| FunctionName
| AxisName
| Literal
| Number
| VariableReference
При разборе XPath-выражения оно сначала разбивается на отдельные токены, а затем из них организуются более сложные структуры. При разбивке выражения на отдельные токены, следует всегда выбирать токен с самым длинным строковым представлением.
Помимо этого, для того чтобы грамматика XPath-выражений была однозначной, в спецификации языка приводятся следующие правила разбора.
□ Если текущему токену предшествует другой токен, причем этот предшествующий токен не является символом
@
, ::
, (
, [
или нетерминалом Operator
, то текущий токен, являющийся символом *
, должен восприниматься как знак умножения, а токен, являющийся NCName
, — как нетерминал OperatorName
.
□ Если за текущим токеном вида
NCName
следует открывающая круглая скобка (символ "(
"), токен должен восприниматься или как имя функции (FunctionName
), или как тип узла (NodeType
).
□ Если за текущим токеном вида
NCName
следуют символы "::
", токен должен восприниматься как имя оси навигации (AxisName
).
□ Если ничего из вышеперечисленного не выполняется, токен не должен восприниматься, как
MultiplyOperator
, OperatorName
, NodeType
, FunctionName
или AxisName
.
Мы привели эти правила в точности так, как они описаны в спецификации языка XPath. Их довольно непросто понять в такой формулировке, поэтому мы попытаемся объяснить их другими словами.
□ Символ
*
является знаком умножения (MultiplyOperator
) тогда и только тогда, когда ему предшествует токен, но этот токен не является токеном @
, ::
, (
, [
или Operator
.
□ Токен
NCName
представляет имя оператора (OperatorName
) тогда и только тогда, когда ему предшествует токен, но этот токен не является токеном ::
, (
, [
или Operator
.
□ Токен
NCName
является именем функции (FunctionName
) или типом узла (NodeType
) тогда и только тогда, когда за ним следует символ "(
".
□ Токен
NCName
является именем оси навигации (AxisName
) тогда и только тогда, когда за ним следуют символы "::
".