Краткое руководство по 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(). Мы можем перефразировать действия, выполняемые движком запроса на каждом шаге:
- для каждого узла в начальном фокусе (узел документа)...
- для каждого дочернего узла, являющегося элементом
<recipe>... - собрать дочерние узлы, являющиеся элементами
<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 обычно записываются в сокращенной форме. Вот таблица некоторых часто используемых сокращенных форм:
| Сокращенный синтаксис | Сокращение от... | соответствует всем... |
|---|---|---|
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 для 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>
Но обратите внимание, как выходные данные немного отличаются от выходных данных, которые мы видели до того, как добавили объявление пространства имён по умолчанию в файл рецептов. 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 на почтовых списках:
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> Почему моя команда возврата не работает?
Помните, что 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) в конце, потому что мы не заключили выражение возврата в скобки.
Использование скобок для установления приоритета в XQuery важнее, чем в других языках, потому что XQuery основан на выражениях. В этом случае, без скобок, окружающих $i + $d, команда возврата возвращает только $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> элемента <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.12/xquery-introduction.html