Spec-Zone.ru › Qt 5.14

Краткое руководство по XQuery

XQuery — язык для запросов к данным XML или данным, которые можно смоделировать как XML. XQuery определен W3C.

Введение

Если 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 файле.

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

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

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

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

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

doc('cookbook.xml')//recipe

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

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

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

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

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

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

Шаги оси

Наиболее распространённый тип шага пути — шаг оси, который сообщает движку запросов, как перемещаться от узла контекста и какие проверки выполнять при обнаружении узлов по пути. Шаг оси состоит из двух частей: спецификатора оси и теста узла. По сути, вычисление шага оси происходит следующим образом: для каждого узла в наборе фокуса движок запросов перемещается от узла по указанной оси и применяет тест узла к каждому встреченному узлу. Узлы, удовлетворяющие тесту узла, собираются в наборе результатов, который становится набором фокуса для следующего шага.

В примере XQuery выше второй и третий шаги являются шагами оси. Оба применяют тест узла element(name) к узлам, обнаруженным при обходе по некоторой оси. Но в этом примере два шага оси записаны в сокращённой форме, где спецификатор оси и тест узла не написаны явно, а подразумеваются. XQuery обычно записываются в этой сокращённой форме, но также могут быть записаны в развернутой форме. Если мы перепишем 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 будет точно таким же, как выход сокращённой формы.

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

Спецификаторы осей

Спецификатор оси определяет направление, в котором движок запросов должен двигаться от узла контекста. Qt XML Patterns поддерживает следующие оси.

Спецификатор оси относится к оси, содержащей...
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 для тестов узлов также определяет третье условие — тип схемы узла, но тесты типа схемы не поддерживаются в Qt XML Patterns.

Qt XML Patterns поддерживает следующие тесты узлов. Тесты с параметром name проверяют имя узла помимо его типа и часто называются тестами имён.

Node Test matches all...
node() узлы любого типа
text() текстовые узлы
comment() узлы комментариев
element() узлы элементов (то же, что и звёздочка: *)
element(name) узлы элементов с именем name
attribute() узлы атрибутов
attribute(name) узлы атрибутов с именем name
processing-instruction() узлы инструкций обработки
processing-instruction(name) узлы инструкций обработки с именем name
document-node() узлы документов (их только один)
document-node(element(name)) узел документа с элементом документа name

Сокращённая форма

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

Сокращённый синтаксис Сокращение от... matches all...
name child::element(name) дочерние узлы, являющиеся элементами name
* child::element() дочерние узлы, являющиеся элементами (node() соответствует всем дочерним узлам)
.. parent::node() родительские узлы (их только один)
@* attribute::attribute() узлы атрибутов
@name attribute::attribute(name) атрибуты name
// descendant-or-self::node() потомки (когда используется вместо '/')

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

Тесты имён

Тесты имён — это тесты узлов, имеющие параметр 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, результат не будет выведен! Выражение пути больше не соответствует чему-либо в файле кулинарной книги, так как наш XQuery ещё не знает о декларации пространства имён, которую мы добавили в документ кулинарной книги. Существует два способа объявления пространства имён в XQuery. Мы можем дать ему префикс пространства имён (например, c для кулинарной книги) и добавить префикс пространства имён к каждому тесту имени:

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>

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

Следует помнить из этого обсуждения пространства имён, будь то вы запускаете XQuery в программе Qt с помощью Qt XML Patterns или запускаете их из командной строки с помощью xmlpatterns, что если вы не получаете ожидаемый результат, возможно, дело в том, что данные, которые вы запрашиваете, используют пространства имён, но вы не объявили эти пространства имён в своём XQuery.

Подстановочные знаки в тестах имён

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

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

Упс! Если вы сохраните этот XQuery в file.xq и запустите его через xmlpatterns, он не сработает. Вместо этого вы получите сообщение об ошибке, подобное такому: Ошибка SENR0001 в file:///...file.xq, на строке 1, столбце 1: Атрибут xml:id не может быть сериализован, так как он появляется на верхнем уровне. XQuery фактически выполнилось правильно. Он выбрал несколько атрибутов xml:id и поместил их в набор результатов. Но затем xmlpatterns отправил набор результатов сериализатору, который попытался вывести его как правильно сформированный XML. Поскольку набор результатов содержит только атрибуты, а атрибуты сами по себе не являются правильно сформированным XML, сериализатор сообщает об ошибке сериализации.

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

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

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

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

declare default element namespace "http://cookbook/namespace";
doc('cookbook.xml')/cookbook/@*

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

Предикаты могут быть использованы для дополнительного фильтрации узлов, выбранных выражением пути. Предикат — это выражение в квадратных скобках ('[' и ']'), которое возвращает либо булево значение, либо число. Предикат может появиться в конце любого шага пути в выражении пути. Предикат применяется к каждому узлу в наборе фокуса. Если узел проходит фильтр, узел включается в набор результатов. Запрос ниже выбирает элемент рецепта, имеющий элемент <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/>, гарантируя, что его длина строки будет 0. Это всё ещё будет работать, если элемент метода написан как <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 , если строка пуста, и true , если строка не пуста.
  • Выражение, которое оценивается как булево значение (т. е. тип xs:boolean ), — это то значение.
  • Если выражение оценивается как что-либо другое, это ошибка (например, тип xs:date).

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

declare default element namespace "http://cookbook/namespace";
doc('cookbook.xml')/cookbook/recipe[method[empty(step)]]

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

Но даже эта версия не безупречна. Предположим, что метод содержит шаги, но все шаги сами по себе пусты. Это всё ещё случай рецепта без инструкций, который не будет обнаружен. Существует лучший способ:

declare default element namespace "http://cookbook/namespace";
doc('cookbook.xml')/cookbook/recipe[not(normalize-space(method))]

Эта версия использует функции not и normalize-space(). normalize-space(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]

Создание элементов

В разделе о использовании подстановочных знаков в тестах имён мы увидели три простых примера XQuery, каждый из которых выбирал другой список 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, который выбирает все атрибуты из элемента документа:

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"/>

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

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

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 загрузится без ошибок, для каждого элемента <recipe> в кулинарной книге будет создан элемент <oppskrift> (норвежское слово для рецепта), и дочерние узлы элемента <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"/>
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 загрузится без ошибок, для каждого элемента <recipe> в кулинарной книге будет создан элемент <oppskrift> (норвежское слово для рецепта), и дочерние узлы элемента <recipe> будут скопированы в элемент <oppskrift> . Но если документ кулинарной книги не существует или не содержит хорошо сформированного XML, будет создан единственный элемент <oppskrift> , содержащий сообщение об ошибке.

Выполнение примеров кулинарной книги

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

xmlpatterns file.xq

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

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

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

  • qt-interest
  • talk на x-query.com.

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

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

  • XQuery 1.0: Язык запросов XML - основной источник синтаксиса и семантики.
  • Функции и операторы XQuery 1.0 и XPath 2.0 - встроенные функции и операторы.

ЧАВО

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

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

Самая распространённая причина этой ошибки — отказ от объявления одного или нескольких пространств имён в вашем XQuery. Рассмотрим следующий запрос для выбора всех примеров в документе 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 — язык на основе выражений, а не на основе операторов. Поскольку XQuery состоит из множества выражений, очень важно понимать приоритет выражений XQuery. Рассмотрим следующий запрос:

for $i in(reverse(1 to 10)),
    $d in xs:integer(doc("numbers.xml")/numbers/number)
return $i + $d

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

Использование скобок для определения приоритета выражений в 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. В этом отношении это похоже на цикл for в C++.

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

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

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

let $i := (2, 3, 1)
order by $i[1]
return $i

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

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

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

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

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

Запрос находит все <p> элементы в файле. Для каждого <p> элемента он создаёт <p> элемент в выводе, содержащий объединённое содержимое всех дочерних <p> элементов <span> элемента. Выполнение запроса через 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. Другой способ — вызвать конструктор булевого значения:

xs:boolean("true")

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.14/xquery-introduction.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API