Spec-Zone.ru › Qt 5.15

Краткое руководство по 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 для обхода деревьев 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, которые могут быть полезны и познавательными.

END_OF_DOCUMENT_MARKER

Это введение содержит много ссылок на спецификации, которые, конечно же, являются основным источником информации о 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 ведёт себя не так, как ожидалось?

Быстрый ответ заключается в том, что вы, вероятно, ожидали, что ваш XQuery FLWOR будет работать так же, как цикл 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> элементов элемента. Выполнение запроса через 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.15/xquery-introduction.html

Spec-Zone.ru

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