Краткое введение в XQuery

Оригигал на: http://doc.qt.nokia.com/4.6/xquery-introduction.html

XQuery это язык запросов XML или не XML данных, которые могут быть смоделированы в соответствии с XML XQuery, определенным W3C.

Введение. 1

Использование выражений Path для сравнения и выбора элементов. 2

Шаги осей. 3

Осевые спецификаторы.. 4

Проверки узлов. 4

Краткая форма. 5

Проверки имен. 5

Использование предикатов в выражениях путей. 8

Позиционные предикаты.. 9

Логические предикаты.. 9

Построение элементов. 10

Конструкторы элементов являются выражениями. 11

Построение атомарного значения. 11

Выполнение примеров. 12

Дополнительные материалы.. 12

FAQ.. 13

Почему мое выражение пути ничего не находит?. 13

Что делать, если мое входное  пространство имен отличается от моего выходного пространства имен?  13

Почему не работает мое предложение return?. 14

Почему мое выражение не получает вычисления?. 14

Мой предикат правильный, так почему он не выбирает правильно?. 15

Почему мой FLWOR не ведет себя как ожидалось?. 16

Почему мои элементы создаются в неправильном порядке?. 17

Почему нельзя использовать true и false в моем XQuery?. 18

Введение

В отличие от языков Java и C++, базирующихся на предложениях,  язык XQuery основывается на выражениях. Простейшим выражением XQuery является конструктор XML элемента:

 <recipe/>

Данный элемент <recipe/> является XQuery выражением, которое формирует полный XQuery. На самом деле этот XQuery реально не является каким-либо запросом. Он просто создает на выходе пустой элемент <recipe/>. Но конструирование новых элементов в XQuery часто бывает необходимо.

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

  <html xmlns="http://www.w3.org/1999/xhtml/"

        xml:id="{doc("other.html")/html/@xml:id}"/>

Это выражение создает на выходе новый элемент <html> и устанавливает его атрибут id в соответствии  с найденным атрибутом id из элемента <html> в файле other.html.

Использование выражений Path для сравнения и выбора элементов

В C++ и Java для поиска нужных элементов мы пишем вложенные циклы  for и рекурсивные функции обхода XML деревьев. В XQuery мы записываем эти итеративные и рекурсивные алгоритмы посредством выражений пути.

Выражение пути выглядит подобно обычному выражению файлового пути, используемому для указания расположения файла в иерархической файловой системе. Это последовательность одного или нескольких шагов, разделенных слешем “ / "или двойным слешем  "/ / ". Хотя выражения пути используется для обхода XML деревьев, а не файловых систем, в QtXmlPatterms мы может моделировать файловую систему, чтобы она выглядела подобно XML дереву. Следовательно, применяя QtXmlPatterns, мы можем использовать XQuery для  обхода файловой системы. См. пример файловой системы.  

Думаю, что использование выражения пути, как алгоритма обхода XML дерева с целью поиска и получения элементов, интересно. Этот алгоритм определяется посредством вычисления каждого шага при перемещении в заданной последовательности пути слева направо. Вычисляемый шаг с набором входных элементов (узлы и атомарные значения), иногда называют фокус. Шаг вычисляется для каждого элемента в фокусе. Эти вычисления создают новый набор элементов, называемый результатом, который затем становится фокусом, и  передается на следующий шаг. Вычисление последнего шага определяет окончательный результат, который и является результатом XQuery. Элементы в результирующем наборе представлены без дублирования в том же порядке, что и в документе.

В QtXmlPatterns стандартным способом установки начального фокуса в запросе является вызов (QXmlQuery::setFocus

). Другой распространенный способ – разрешить XQuery самостоятельно установить начальный фокус, используя первый шаг выражения пути при вызове функции doc().  Функция doc()загружает XML-документ и возвращает узел документа. Обратите внимание, что узел документа не тоже, что и элемент документа. Узел документа это узел, построенный в памяти при загрузке документа. Он представляет собой цельный XML документ, а не элемент документа. Элемент  документа это одиночный ,XML элемент верхнего уровня в файле. Функция doc() возвращает узел документа, который становится одноэлементный узлом в начальном наборе, получившим фокус. Узел документа будет иметь один дочерний узел, и этот дочерний узел будет представлять элемент документа. Рассмотрим следующий запрос XQuery.

 doc('cookbook.xml')//recipe

doc() функция загружает файл cookbook.xml и возвращает узел документа. Узел документа становится фокусом для следующего шага //recipe. Здесь двойной слеш означает выбор всех элементов <recipe>, находящихся под узлом документа, независимо от того, где они располагаются в дереве документа. Запрос выбирает все элементы <recipe> в поваренной книге. Смотрите пример  Running The Cookbook  для пояснения того, как запустить этот запрос (и большинство аналогичных, которые последуют далее) из командной строки.

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

 doc('cookbook.xml')//recipe/title

Этот XQuery является простым выражением пути, состоящим из трех шагов. На первом шаге создается начальный фокус посредством вызова функции doc(). Мы перечислим, что делает обработчик запросов на каждом шаге:

  1. устанавливает узел в начальный фокус (узел документа)...
  2. отбирает все узлы потомки, которые являются элементом <recipe>...
  3. отбирает элементы <title>, являющиеся дочерними узлами элемента <recipe>,.

Снова двойной слеш определяет выбор всех элементов, <recipe> в документе. Одиночный слеш перед элементом <title>  определяет выбор только тех элементов <title>, которые являются дочерними элементами элемента <recipe> (т.е. не внуков и т.д.). XQuery вычисляется как окончательный результирующий набор, содержащий из поваренной книги элемент <title> каждого элемента <recipe>.

Шаги осей

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

Выше, в примере XQuery, и второй и третий шаги являются осевыми шагами. На том и другом шаге  выполняется проверка узла element(name)  для узлов, появляющихся в процессе обхода соответствующих осей. В этом примере оба осевых шага написаны в краткой форме, где спецификатор оси и условия проверки узла явно не записаны, но подразумеваются. В XQueries обычно используют  краткую форму. Но запросы также можно писать и в полной форме. Если переписать XQuery в полной форме, то он выглядит так:

 doc('cookbook.xml')/descendant-or-self::element(recipe)/child::element(title)

Оба осевых шага были расширены. Первый шаг (//recipe) был переписан, как /descendant-or-self::element(recipe), где descendant-or-self:: является спецификатором оси, а element(recipe) условием проверки узла. Второй шаг (title) был переписан, как /child::element(title), где child:: является спецификатором оси и element(title) условием проверки узла. Выходные данные расширенного XQuery будут совершенно аналогичны тому, что получится при использовании краткой формы.

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

Осевые спецификаторы

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

Описатель оси

ссылается на оси, содержащую...

self::

сам узел контекста

attribute::

все атрибут узлы узла контекста

child::

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

descendant::

все потомки узла контекста (дети, внуки, и т.д.)

descendant-or-self::

все узлы descendant + self

parent::

родительский узел узла контекста, или пустой, если нет родителя

ancestor::

все предки узла контекста (родители, родители родителей и т.д.)

ancestor-or-self::

все узлы ancestor + self

following::

все узлы в дереве, включая контекстный узел, не включая descendant, и которые следуют за контекстным узлом в документе

preceding::

все узлы в дереве, включая контекстный узел, не включая ancestor, и которые предшествуют контекстному узлу в документе

following-sibling::

все дети parent контекстного узла, которые следуют за контекстным узлом в документе

preceding-sibling::

все дети parent контекстного узла, которые предшествуют контекстному узлу в документе

Проверки узлов

Проверка узла представляет собой условное выражение, которое должно быть верно для узла, если узел выбран на осевом шаге. Условное выражение может проверить только вид узла или тип и имя узла. Спецификации XQuery также определяет для проверки узла еще одно третье условие, а именно, тип схемы узла. Однако проверка типа схемы не поддерживается QtXmlPatterns.

QtXmlPatterns поддерживает следующие проверки узлов. Проверки, которые имеют параметр name проверяют имя узла в дополнение к проверке вида и часто называются проверками имен.

Проверка узла

соответствует всем...

node()

узлы любого вида

text()

текстовые узлы

comment()

узлы комментариев

element()

узлы элементов (подобно звездочке: *)

element(name)

узлы элементов с именем name

attribute()

узлы атрибутов

attribute(name)

узлы атрибутов с именем name

processing-instruction()

инструкции обработки

processing-instruction(name)

инструкции обработки с именем name

document-node()

узел документа (существует только один)

document-node(element(name))

узел документа с элементом документа name

Краткая форма

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

Сокращенный

синтаксис

Сокращено для...

соответствует всем...

name

child::element(name)

дочерние узлы, являющиеся элементами с  name

*

child::element()

дочерние узлы, которые являются элементами (node() соответствует всем дочерним узлам)

..

parent::node()

родительские узлы (существует только один)

@*

attribute::attribute()

узлы атрибуты

@ имя

attribute::attribute(name)

атрибуты с именем name

//

descendant-or-self::node()

узлы потомки (при использовании вместо '/')

Спецификация языка XQuery имеет более подробный раздел, посвященный краткой форме, которую она называет сокращенный синтаксис (abbreviated syntax). Там же находятся дополнительные примеры выражений путей, написанные в краткой форме. Есть и раздел представляющий примеры выражений путей в полной форме.

Проверки имен

Проверки имен это проверки  узлов (Node Tests), которые имеют параметр name. Проверка имени должна проверить на соответствие имя узла в дополнение к проверке вида узла. Мы уже делали проверку имени, задавая

 doc('cookbook.xml')//recipe/title

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

Имена в приведенном выше примере не имеют префиксов пространства имен, так как мы не включаем декларацию пространства имен в наш файл cookbook.xml. Тем не менее, мы будем часто использовать XQuery для запроса XML документов, которые используют пространства имен. Забывчивость объявления правильных пространств имен  в XQuery является распространенной причиной сбоев XQuery. Давайте сейчас добавим пространство имен по умолчанию для cookbook.xml. Измените элемент документа в cookbook.xml с:

 <cookbook>

на...

 <cookbook xmlns="http://cookbook/namespace">

Это называется объявление пространства имен по умолчанию, так как оно не содержит префикс пространства имен. Включая это объявление пространства имен по умолчанию в элемент документа, мы полагаем следующее. Все имена элементов без префикса в документе, включая и собственно элемент документа (cookbook), автоматически будут в пространство имен по умолчанию                                            http://cookbook/namespace. Обратите внимание, что к именам атрибута эффект пространства имен по умолчанию не применим. Они всегда считаются вне пространства имен. Также обратите внимание, что выбранное нами URL  в виде URI нашего пространства имен не должен ссылаться на реальное местонахождение и в данном случае ни к чему не относится. Тем не менее, щелкните, например, на http://www.w3.org/XML/1998/namespace, который является URI пространства имен для элементов и атрибутов с префиксом xml:.

Теперь, когда мы попытаемся работать с предыдущим примером XQuery, ничего не выдается! Выражение пути не совпадает с чем-либо в файле cookbook, поскольку наш XQuery еще не знает об объявлении пространства имен, добавленного в cookbook документ. Существует два способа для объявления пространств имен в XQuery. Мы можем связать с ним префикс пространства имен (например, c для cookbook). В этом случае префикс каждого имени сверяется с префиксом пространства имен:

 declare namespace c = "http://cookbook/namespace";

 doc('cookbook.xml')//c:recipe/c:title

Используя второй способ, мы объявляем, что пространство имен будет пространством имен по умолчанию, и после этого мы снова запускаем оригинальный XQuery:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')//recipe/title

Оба метода будут работать, и выдавить на выходе один и тот же результат, а именно, все элементы <title>:

 <title xmlns="http://cookbook/namespace">Quick and Easy Mushroom Soup</title>

 <title xmlns="http://cookbook/namespace">Cheese on Toast</title>

 <title xmlns="http://cookbook/namespace">Hard-Boiled Eggs</title>

Но обратите внимание, данный вывод немного отличается от вывода, который мы видели ранее, до того как добавили объявление пространства имен по умолчанию в файл cookbook. QtXmlPatterns автоматически включает в выходные данные корректный атрибут пространства имен в каждый элемент <title>. Когда QtXmlPatterns загружает документ и расширяет QName, он создает экземпляр QXmlName, который запоминает префикс пространства имен с URI пространства имен и локальное имя. Для получения дополнительной информации см. QXmlName.

Одно дело говорить о пространстве имен в ходе обсуждения, и совсем другое выполнять XQueries в Qt программе, используя QtXmlPatterns, или запускать их из командной строки, используя xmlpatterns. Возможно вы не получите ожидаемый результат, поскольку запрашиваемые данные используют пространство имен, но это пространство имен не объявлено в XQuery.

Подстановочные символы в проверяемых именах

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

 doc('cookbook.xml')//@xml:*

Oops! Если вы сохраните этот XQuery в file.xq и запустите его через xmlpatterns, он не сработает. Вы получите сообщение об ошибке, что-то вроде этого: Error SENR0001 in  file:///...file.xq, at line 1, colunm 1: Attribute xml:id can't be serialized because it appears at the top level ( Атрибут xml:id не может быть сериализован поскольку он появляется на верхнем уровне). XQuery, на самом деле отработал правильно. Он выбран связку атрибутов xml:id и положил их в  результирующий набор. Но затем xmlpatterns направил результирующий набор сериализатору, который пытался его вывести как правильный (well-formed) XML. Поскольку результирующий набор содержит только атрибуты, а одни атрибуты   не являются правильным XML, сериализатор выдает сообщение об ошибке сериализации.

Не бойтесь. XQuery может сделать больше, чем просто находить и выбирать элементы и атрибуты. Можно создавать новые элементы и атрибуты на лету, а это именно то, что мы должны сделать здесь, если мы хотим, чтобы xmlpatterns позволил нам увидеть выбранные атрибуты. К примеру выше и примерам ниже еще вернемся в разделе Создание элементов. Вы можете перепрыгнуть вперед, чтобы сейчас увидеть измененные примеры, а затем вернуться, или вы можете остаться здесь.

Чтобы найти все атрибуты name в поваренной книге и выбрать их все, независимо от их пространства имен надо заменить префикс пространства имен со звездочкой на name (имя атрибута) как локальное имя:

 doc('cookbook.xml')//@*:name

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

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/@*

Использование предикатов в выражениях путей

Предикаты могут использоваться для дальнейшей фильтрации узлов, выбранных посредством выражения пути. Предикат – это выражение в квадратных скобках ('[' и ']'), которое возвращает или логическое значение или число. Предикат может появиться в конце любого шага пути в выражении пути. Предикат применяется для каждого узла в наборе фокуса. Если узел проходит фильтр, он включается в результирующий набор. Запрос, представленный ниже, выбирает элемент recipe, который имеет элемент <title> со значением «Hard-Boiled Eggs».

 declare default element namespace "http://cookbook/namespace";

 doc("cookbook.xml")/cookbook/recipe[title = "Hard-Boiled Eggs"]

Выражение точка ('.') может использоваться в предикатах и выражениях путей для ссылки на текущий контекстный узел. В следующем запросе используется точка для ссылки на текущий элемент <method>. Запрос выбирает пустые элементы <method> из поваренной книги.

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')//method[string-length(.) = 0]

Обратите внимание, что передача точки в функцию string-length() является необязательной. При вызове string-length() без параметра контекстный узел подразумевается:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')//method[string-length() = 0]

На самом деле выбор пустого элемента <method> может сам по себе не оказаться очень полезным. Он не скажет вам, какой рецепт имеет пустой метод:  

 <method xmlns="http://cookbook/namespace"/>

Вместо этого вы, вероятно, захотите увидеть элементы <recipe>, которые имеют пустые элементы <method>:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')//recipe[string-length(method) = 0]

При проверке узла предикат использует функцию string-length() для проверки длины каждого элемента <method> в каждом найденном элементе <recipe>. Если <method> не содержит текст, предикат вычисляет значение true и элемент <recipe> выбрается. Если метод содержит текст, предикат вычисляет значение false и элемент <recipe> удаляется. Выходом является набор рецептов, не имеющих инструкции по подготовке:

 <recipe xmlns="http://cookbook/namespace" xml:id="HardBoiledEggs">

    <title>Hard-Boiled Eggs</title>

    <ingredient name="Eggs" quantity="3" unit="eggs"/>

    <time quantity="3" unit="minutes"/>

    <method/>

 </recipe>

Проницательный читатель заметит, что такое использование string-length() для нахождения пустого элемента является ненадежным. Он работает в этом случае, потому что элемент method записан как <method/>, гарантируя, что его длина строки будет равно 0. Он по-прежнему будет работать, если элемент method записывается как <method></method>, но будет работать не правильно, если есть  пробелы между начальным и конечным тегом <method>. Более надежный способ поиска рецептов с пустыми тегами  <method> представлен в разделе Логические предикаты.

Существует больше количество функций и операторов, определенных для XQuery и XPath. Они все документированы в спецификации.

Позиционные предикаты

Предикаты часто используются для фильтрации элементов, основываясь на их позиции в последовательности. При обработке выражения пути элементы загружаются из XML документов в нормальной последовательности, т.е. в той последовательности как они представлены в документе (документном порядке). Данный запрос возвращает второй элемент <recipe> из файла cookbook.xml:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[2]

Другая часто используемая позиционная функция – это last(), которая возвращает числовую позицию последнего элемента в наборе фокуса. Можно сказать          по-другому, last() возвращает размер набора фокуса. Следующий запрос возвращает последний рецепт в поваренной книге:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[last()]

Этот запрос возвращает предпоследний <recipe>:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[last() - 1]

Логические предикаты

Другой вид предиката вычисляет значение true или false. Логический (boolean) предикат берет значение переданного ему выражения и определяет его действительное логическое значение в соответствии со следующими правилами:

Мы уже рассматривали использование некоторых логических предикатов. Ранее мы видели не столь надежный способ поиска рецептов, которые не имели инструкции. [string-length(method) = 0] является логическим предикатом, который будет ошибочен в примере, если пустой элемент был записан с начальным  и конечным тегом и между ними были пробелы. Вот более надежный способ, использующий другой логический предикат.

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[method[empty(step)]]

Он использует функцию empty(), и которая проверяет, содержит ли элемент method какие-либо step. Если method не содержит step, то empty(step) вернет true, и поэтому в предикате будет вычислено true.

Но даже и этот вариант не достаточно верный. Предположим, что method содержит step, но все step, сами по себе являются пустыми. По-прежнему у рецепта нет инструкции. Есть способ лучше:  

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[not(normalize-space(method))]

Данная версия использует функции not и normalize-space().                      Normalize-Space(method)) возвращает содержимое элемента method в виде строки, но все пробелы нормализуются, т.е. значение строки в каждом  элементе <step> будет нормализовано, а затем значения всех нормализованных шагов будут объединены. Если строка пуста, то not() возвращает true и предикат имеет true.

Мы также можем применить функцию position(), используя сравнение для проверки позиции с логикой условия. Функция position() возвращает индекс позиции текущего контекста элемента в последовательности элементов:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[position() = 2]

Обратите внимание, что первое место в последовательности позиции 1, а не 0. Мы можем таким же образом выбрать все рецепты, начиная со второго:

 declare default element namespace "http://cookbook/namespace";

 doc('cookbook.xml')/cookbook/recipe[position() > 1]

Построение элементов

В разделе об использовании подстановочных символов в проверяемых именах мы видели три простых примера XQueries, каждый из которых выбирал различные списки атрибутов XML из поваренной книги. Однако мы не могли использовать xmlpatterns для выполнения этих запросов, потому что xmlpatterns отправляет результаты XQuery к сериализатору, который ожидает их в виде правильного XML. Так как список атрибутов XML сам по себе не является правильным XML, сериализатор выдает сообщение об ошибке для каждого XQuery.

Поскольку атрибут должен появиться в элементе, для каждого атрибута в результирующем наборе, мы должны создать XML элемент. Мы можем сделать это, используя при построении элемента предложение for  со связанной переменной и предложение return:

 for $i in doc("cookbook.xml")//@xml:*

 return <p>{$i}</p>

Предложение for создает последовательность узлов атрибутов из результата выражения пути. Каждый узел атрибута в последовательности привязывается к переменной $i. Затем предложение return создает элемент <p> вокруг узла атрибута. Вот результат:

 <p xml:id="MushroomSoup"/>

 <p xml:id="CheeseOnToast"/>

 <p xml:id="HardBoiledEggs"/>

Выходные данные содержат один элемент <p> для каждого атрибута xml:id в поваренной книге. Отметим, что XQuery помещает каждый атрибут в правильное место элемента <p>, несмотря на то, что в предложении return переменная $i позиционируется таким образом, будто она намеревается стать содержимым элемента <p>.  

Два других примера из раздела о подстановочных символах можно так же переписать. Вот XQuery, который выбирает все атрибуты name, независимо от пространства имен:

 for $i in doc("cookbook.xml")//@*:name

 return <p>{$i}</p>

А вот его результат:

 <p name="Fresh mushrooms"/>

 <p name="Garlic"/>

 <p name="Olive oil"/>

 <p name="Milk"/>

 <p name="Water"/>

 <p name="Cream"/>

 <p name="Vegetable soup cube"/>

 <p name="Ground black pepper"/>

 <p name="Dried parsley"/>

 <p name="Bread"/>

 <p name="Cheese"/>

 <p name="Eggs"/>

Здесь XQuery, который выбирает все атрибуты элемента document:

 declare default element namespace "http://cookbook/namespace";

 for $i in doc("cookbook.xml")/cookbook/@*

 return <p>{$i}</p>

А вот его результат:

 <p xmlns="http://cookbook/namespace" count="3"/>

Конструкторы элементов являются выражениями

Поскольку конструкторы узлов есть выражения, они могут использоваться в XQueries везде, где выражения допустимы.

 declare default element namespace "http://cookbook/namespace";

 let $docURI := 'cookbook.xml'

 return if(doc-available($docURI))

        then doc($docURI)//recipe/<oppskrift>{./node()}</oppskrift>

        else <oppskrift>Failed to load {$docURI}</oppskrift>

Если cookbook.xml загружается без ошибок, элемент <oppskrift> (норвежское слово рецепт) создается для каждого элемента <recipe> в поваренной книге, и дочерние узлы <recipe> копируются в элемент <oppskrift>. Но если поваренная книга документ не существует или не содержит правильный XML, строится единственный элемент <oppskrift>, содержащий сообщение об ошибке.

Построение атомарного значения

XQuery также имеет атомарные значения. Атомарное значение есть значение в пространстве значений одного из встроенных типов данных  языка XML Schema. Данные атомарные типы имеют встроенные операторы для выполнения арифметических операций, операций сравнения, а также преобразования значений из одного атомарного типа в другой. Просмотрите иерархию встроенных типов данных для всего дерева встроенных, примитивных и производных атомарных типов. Примечание: Щелкните на типе данных в дереве для получения его подробного описания.

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

 <e>{sum((1, 2, 3))}</e>

Пропустив этот XQuery через xmlpatterns получим:

 <e>6</e>

Для вычисления значения атрибута, заключите выражение в фигурные скобки и вставьте его в значении атрибута:

 declare variable $insertion := "example";

 <p class="important {$insertion} obsolete"/>

Пропустив этот XQuery через xmlpatterns получим:

<p class="important example obsolete"/>

Выполнение примеров  

Большинство примеров XQuery в этом документе ссылаются на файл cookbook.xml из Recipes Example. Скопируйте cookbook.xml в ваш текущий каталог, сохраните один из примеров XQuery в файле .xq (например, file.xq) и запустите XQuery, используя Qt утилиту командной строки:  

 xmlpatterns file.xq

Дополнительные материалы

Язык XQuery значительно шире, чем мы представили в этом кратком введение. Мы будем добавлять в него в более поздних версиях. Также поупражняйтесь с утилитой xmlpatterns, и внесите изменения в представленные здесь примеры XQuery. Это будет весьма полезным. Приобретение учебника по XQuery  будет хорошей инвестицией.

Также вы можете задать вопросы по XQuery на почтовые списки:

FunctX имеет коллекцию функций XQuery, которые могут быть как полезными, так и помогут в обучении.

Это введение содержит множество ссылок на спецификации, которые, конечно, являются основным источником информации об XQuery. Они могут быть достаточно сложными, поэтому предпочтительней приобрести учебник:

FAQ

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

Почему мое выражение пути ничего не находит?

Наиболее распространенной причиной этой ошибки является неправильное объявление одного или нескольких пространств имен в XQuery. Рассмотрим следующий запрос для выбора всех example в XHTML документе:  

 doc("index.html")/html/body/p[@class="example"]

Он не соответствует ничему, поскольку index.html есть файл XHTML, а все файлы, XHTML по умолчанию объявлены в пространстве имен                   "http://www.w3.org/1999/xhtml"  в корневом элементе (<html>). Но запрос не объявил это пространство имен, следовательно, выражение пути расширяет html до html ({}) и пытается сопоставить это расширенное имя. Но фактически расширенное имя {http://www.w3.org/1999/xhtml}html. Одним из возможных способов это исправить является объявление правильного пространства имен по умолчанию в XQuery:  

 declare namespace x = "http://www.w3.org/1999/xhtml/";

 doc("index.html")/x:html/x:body/x:p[@class="example"]

Другой распространенной причиной этой ошибки является путаница узла документа с узлом верхнего элемента. Они отличаются. Этот запрос ничего не найдет:

 doc("myPlainHTML.html")/body

Функция doc() возвращает узел документа, а не узел верхнего элемента (<html>). Не забудьте вставить верхний элемент узла в выражении пути:

 doc("myPlainHTML.html")/html/body

Что делать, если мое входное  пространство имен отличается от моего выходного пространства имен?

Запомните, нужно объявить оба пространства имен в XQuery и использовать их должным образом. Рассмотрим следующий запрос. Он предназначен для получения на выходе сгенерированного XHTML из входного XML:

 declare default element namespace "http://www.w3.org/1999/xhtml";

 <html>

     <body>

         {

             for $i in doc("testResult.xml")/tests/test[@status = "failure"]

             order by $i/@name

             return <p>{$i/@name}</p>

         }

    </body>

 </html>

Мы хотим, чтобы на выходе были созданы узлы <html>, <body> и <p>, входящие в стандартное пространство имен XHTML, Поэтому мы объявляем пространство имен по умолчанию http://www.w3.org/1999/xhtml. Это верно для выхода, но это же самое пространство имен по умолчанию также будет применяться и для имен узлов в выражении пути,  которое мы попытаемся сопоставить со входными данными (/tests/test[@status = "failure"]). Это неправильно, поскольку пространство имен, используемое в testResult.xml возможно является пустым пространством имен. Следовательно, мы должны объявить выходное пространство имен с префиксом пространства имен и затем использовать префикс с именами узлов в выражении пути. Этот запрос, вероятно, будет работать лучше:

 declare namespace x = "http://www.w3.org/1998/xhtml";

 <x:html>

     <x:body>

         {

             for $i in doc("testResult.xml")/tests/test[@status = "failure"]

             order by $i/@name

             return <x:p>{$i/@name}</x:p>

         }

    </x:body>

 </x:html>

Почему не работает мое предложение return?  

Напомним, что XQuery является языком на основе выражений (expression-based), не на основе утверждений. Поскольку XQuery включает много выражений, понимание приоритета выражений в XQuery является очень важным. Рассмотрим следующий запрос:

 for $i in(reverse(1 to 10)),

     $d in xs:integer(doc("numbers.xml")/numbers/number)

 return $i + $d

Он выглядит нормально, но это не так. Запрос предполагает наличие выражения FLWOR, включающего предложение for и предложение return, но это не совсем так. Запрос конечно (с предложениями for и return) имеет выражение FLWOR, но он также имеет арифметическое выражение  с «висячим» (+ $d), в конце, поскольку мы не заключили выражение возврата в скобки.

Использование скобок для установления приоритета является в XQuery более важным, чем в других языках, потому что XQuery язык на основе выражений. В  данном случае без заключения в скобки $i + $d, предложение return вернет только $i. $d будет представлять результат выражения FLWOR, т.к. является его левым операндом. Поскольку область переменной $d заканчивается в конце предложения return, переменная оказывается вне этой области, о чем будет сообщать ошибка. Устраним эти проблемы с помощью круглых скобок.

 for $i in(reverse(1 to 10)),

     $d in xs:integer(doc("numbers.xml")/numbers/number)

 return ($i + $d)

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

Вы, вероятно, неправильно расставили фигурные скобки. Когда вы хотите, чтобы выражение вычислялось внутри конструктора элемента, заключите выражение в фигурные скобки. Без фигурных скобок выражение будет интерпретировано как текст. Здесь выражение sum(),  используется в элементе <e>. В таблице приведены случаи, когда фигурные скобки поставлены ошибочно и правильно:

конструктор элемента с выражением...

вычисляет...

<e>sum ((1, 2, 3))</e>

<e>sum ((1, 2, 3))</e>

<e>sum ({(1, 2, 3)})</e>

<e>sum (1 2 3)</e>

<e>{ sum ((1, 2, 3))}</e>

<e>6</e>

Мой предикат правильный, так почему он не выбирает правильно?

Либо ваш предикат помещен в неправильном месте в выражении пути, или вы забыли добавить некоторые круглые скобки. Рассмотрим этот входной файл doc.txt:

 <doc>

  <p>

    <span>1</span>

     <span>2</span>

   </p>

   <p>

     <span>3</span>

     <span>4</span>

   </p>

   <p>

     <span>5</span>

     <span>6</span>

   </p>

   <p>

     <span>7</span>

     <span>8</span>

   </p>

   <p>

     <span>9</span>

     <span>a</span>

   </p>

   <p>

     <span>b</span>

     <span>c</span>

   </p>

   <p>

     <span>d</span>

     <span>e</span>

   </p>

   <p>

     <span>f</span>

     <span>0</span>

   </p>

 </doc>

Предположим, вы хотите выбрать первый элемент <span> каждого элемента <p>. Применим фильтр позиции ([1])  к шагу пути /span:

 let $doc := doc('doc.txt')

 return $doc/doc/p/span[1]

Применение фильтра [1] к шагу /span  возвращает первый элемент <span> каждого элемента <p>:

 <span>1</span>

 <span>3</span>

 <span>5</span>

 <span>7</span>

 <span>9</span>

 <span>b</span>

 <span>d</span>

 <span>f</span>

Примечание:: Вы можете написать, то же запрос следующим образом:

 for $a in doc('doc.txt')/doc/p/span[1]

 return $a

Или вы можете сократить его вплоть до этого:

 doc('doc.txt')/doc/p/span[1]

С другой стороны, предположим, что вы действительно хотите только один, первый в документе, элемент <span> (т.е. вы только хотите первый элемент <span> в первом элементе <p>). Для этого вы должны сделать дополнительную фильтрацию. Это можно сделать двумя способами. Вы можете применить фильтр [1] в том же месте, что и выше, но заключить выражение пути в круглые скобки:

 let $doc := doc('doc.txt')

 return ($doc/doc/p/span)[1]

Или вы можете применить фильтр во второй позиции (снова [1]) для шага пути /p:

 let $doc := doc('doc.txt')

 return $doc/doc/p[1]/span[1]

В любом случае запрос будет возвращать только первый элемент <span> в документе:

 <span>1</span>

Почему мой FLWOR не ведет себя как ожидалось?

Быстрый ответ – вы, вероятно, полагаете, что ваш FLWOR XQuery будет вести себя так же, как цикл for C++. Но это не так. Рассмотрим простой пример:

 for $a in (8, -4, 2)

 let $b := ($a * -1, $a)

 order by $a

 return $b

Данный запрос выдает 4 -4 -2 2-8 8. Предложение for устанавливает стиль итерации цикла for, который вычисляет остальной FLWOR несколько раз, один раз для каждого значения, возвращаемого выражением in. Это похоже на на цикл C++.  

Но рассмотрим предложение return. В C++ если вы попадаете на оператор return, вы прерываете цикл for и возвращаетесь из функции с одним значением. В XQuery не так. Предложение return является последним предложением FLWOR, и это значит: добавление возвращаемого значения в список результатов, а затем возврат к следующей итерации FLWOR. Когда в предложении for выражение in больше не возвращает значение, возвращается весь результирующий список.

Далее рассмотрим предложение order by. Оно не выполняет сортировки при каждой итерации FLWOR. FLWOR просто вычисляет выражение на каждой итерации (в данном случае $a) для получения упорядоченного значения, чтобы сопоставить результирующий элемент из каждой итерации. Эти упорядоченные значения хранятся в параллельном списке. Результирующий список сортируется в конце, с использованием параллельных списков упорядоченных значений.

Последнее отличие, которое здесь отметим, заключается в том, что предложение let не устанавливает итерацию через последовательность значений, как это делает предложение for. Предложение let не сортирует вложенный цикл. Это вообще не цикл. Это просто привязка переменной. На каждой итерации он связывает всю последовательность значений справа с переменной слева. В приведенном выше примере он связывает (4 -4) с $b на первой итерации, (2-2) на второй, (8-8) на третьей. Следовательно, следующий запрос не выполняет итерацию и ничего не упорядочивает:

 let $i := (2, 3, 1)

 order by $i[1]

 return $i

Он связывает всю последовательность (1, 2, 3) с $i только один раз; предложение order by имеет только одно предназначение - упорядочивать и, следовательно, ничего не делает. Запрос вычисляет 2 3 1, эта последовательность назначается  $i.

Примечание: Мы не включили предложение where в пример. Предложение where предназначено для фильтрации результатов.

Почему мои элементы создаются в неправильном порядке?

Краткий ответ – ваши элементы не созданы в неправильном порядке, потому что когда они появляться в качестве операндов выражения пути у них уже нет правильного порядка. Рассмотрим следующий запрос, который вновь использует входной файл doc.txt:

 doc('doc.txt')//p/<p>{span/node()}</p>

Запрос находит все элементы <p> в файле. Для каждого входного элемента <p> он строит выходной элемент <p>, включающий соединение содержимого элементов <span>, дочерних соответствующему входному элементу <p>. Выполнив запрос через xmlpatterns, можем получить следующие выходные данные, которые не отсортированы в ожидаемом порядке.

 <p>78</p>

 <p>9a</p>

 <p>12</p>

 <p>bc</p>

 <p>de</p>

 <p>34</p>

 <p>56</p>

 <p>f0</p>

Используем цикл for для гарантированного получения упорядоченного  результирующего набора из входной последовательности:

 for $a in doc('doc.txt')//p

  return <p>{$a/span/node()}</p>

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

 <p>12</p>

 <p>34</p>

 <p>56</p>

 <p>78</p>

 <p>9a</p>

 <p>bc</p>

 <p>de</p>

 <p>f0</p>

Почему нельзя использовать true и false в моем XQuery?

Можно, но не непосредственно используя имена true и false, потому что они являются проверками имен, хотя выглядят подобно логическим константам. Простой способ создания логического значения является использование встроенных функций true() и false() там, где вы хотите использовать true и false. Другой способ заключается в вызове конструктора boolean:

 xs:boolean("true")

Сайт создан в системе uCoz