Краткое руководство по 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(). Мы можем перефразировать то, что делает движок запроса на каждом шаге:
- для каждого узла в начальном фокусе (узел документа)...
- для каждого дочернего узла, который является элементом
<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.
XQuery Qt XML Patterns поддерживает следующие тесты узлов. Тесты, имеющие параметр name, проверяют имя узла помимо его типа и часто называются тестами имён.
| Node Test | matches all... |
|---|---|
node() |
узлов любого типа |
text() |
текстовых узлов |
comment() |
узлов комментариев |
element() |
узлов элементов (то же, что и звёздочка: *) |
element(name) |
узлов элементов с именем name
|
attribute() |
узлов атрибутов |
attribute(name) |
узлов атрибутов с именем name
|
processing-instruction() |
узлов инструкций обработки |
processing-instruction(name) |
узлов документов (их только один) |
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. Обратите внимание, что имена атрибутов без префиксов не затрагиваются объявлением стандартного пространства имён. Они всегда считаются находящимися в нет пространства имён. Также обратите внимание, что 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>.
Два других примера из раздела с подстановочными символами wildcard можно переписать аналогичным образом. Вот 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 загружен без ошибок, элемент <oppskrift> (норвежское слово для рецепта) создаётся для каждого <recipe> элемента в кулинарной книге, а дочерние узлы элемента <recipe> копируются в элемент <oppskrift>. Но если документ кулинарной книги не существует или не содержит правильно сформированного XML, создаётся единственный элемент <oppskrift> с сообщением об ошибке.
Создание атомных значений
XQuery также имеет атомные значения. Атомное значение — это значение в пространстве значений одного из встроенных типов данных в языке XML Schema. Эти атомные типы имеют встроенные операторы для выполнения арифметических операций, сравнений и для преобразования значений в другие атомные типы. Смотрите Дерево встроенных типов данных для полного дерева встроенных, примитивных и производных атомных типов.
Примечание: Щёлкните по типу данных в дереве для получения подробной спецификации.
Для создания атомного значения в качестве содержимого элемента заключите выражение в фигурные скобки и вставьте его в конструктор элемента:
<e>{sum((1, 2, 3))}</e> Отправка этого XQuery через xmlpatterns даёт:
<e>6</e>
Для вычисления значения атрибута заключите выражение в фигурные скобки и вставьте его в значение атрибута:
declare variable $insertion := "example";
<p class="important {$insertion} obsolete"/> Отправка этого XQuery через xmlpatterns даёт:
<p class="important example obsolete"/>
declare default element namespace "http://cookbook/namespace";
let $docURI := 'cookbook.xml'
return if(doc-available($docURI))
then doc($docURI)//recipe/<oppskrift>{./node()}</oppskrift>
else <oppskrift>Failed to load {$docURI}</oppskrift> Если cookbook.xml загружен без ошибок, элемент <oppskrift> (норвежское слово для рецепта) создается для каждого <recipe> элемента в кулинарной книге, и дочерние узлы элемента <recipe> копируются в элемент <oppskrift> . Но если документ кулинарной книги не существует или не содержит правильно сформированного XML, создается единственный элемент <oppskrift> с сообщением об ошибке.
Запуск примеров кулинарной книги
Большинство примеров XQuery в этом документе относятся к файлу примера 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 и пытается найти этот расширенное имя. Но фактическое расширенное имя — {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) в конце, потому что мы не заключили выражение return в скобки.
Использование скобок для установления приоритета важнее в 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.9/xquery-introduction.html