Spec-Zone.ru › Qt 5.13

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

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

Введение

Если Java и C++ — это языки, основанные на выражениях, то язык XQuery основан на выражениях. Простейшее выражение XQuery — это конструктор XML-элемента:

<recipe/>

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

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

 <html xmlns="http://www.w3.org/1999/xhtml/"
       xml:id="{doc("other.html")/html/@xml:id}"/>

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

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

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

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

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

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

doc('cookbook.xml')//recipe

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

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

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

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

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

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

Шаги осей

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

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

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

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

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

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

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

Спецификатор оси ссылается на ось, содержащую...
self:: сам контекстный узел
attribute:: все узлы атрибутов контекстного узла
child:: все дочерние узлы контекстного узла (не атрибуты)
descendant:: все потомки контекстного узла (дети, внуки и т. д.)
descendant-or-self:: все узлы в descendant + self
parent:: родительский узел контекстного узла или пусто, если родителя нет
ancestor:: все предки контекстного узла (родитель, дед и т. д.)
ancestor-or-self:: все узлы в ancestor + self
following:: все узлы в дереве, содержащем контекстный узел, не включая descendant, и следующие за контекстным узлом в документе
preceding:: все узлы в дереве, содержащем контекстный узел, не включая ancestor, и предшествующие контекстному узлу в документе
following-sibling:: все дочерние узлы parent контекстного узла, следующие за контекстным узлом в документе
preceding-sibling:: все дочерние узлы parent контекстного узла, предшествующие контекстному узлу в документе

Тесты узлов

Тест узла — это условное выражение, которое должно быть истинным для узла, если узел должен быть выбран шагом оси. Условное выражение может проверять только тип узла или тип узла и имя узла. Спецификация XQuery для тестов узлов также определяет третье условие — тип схемы узла, но тесты типа схемы не поддерживаются в Qt XML Patterns.

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

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

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

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

Краткая синтаксическая запись Сокращение от... сопоставляет все...
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 пространства имён URL не обязательно должен ссылаться на фактическое расположение и в данном случае не ссылается. Но щелкните по 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> элемент в выводе. При загрузке документа и расширении QName Qt XML Patterns создаёт экземпляр QXmlName, который сохраняет префикс пространства имён вместе с URI пространства имён и локальным именем. Смотрите QXmlName для получения дополнительной информации.

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

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

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

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

Упс! Если вы сохраните этот XQuery в file.xq и запустите его через xmlpatterns, он не сработает. Вместо этого вы получите сообщение об ошибке, что-то вроде этого: Ошибка SENR0001 в файле:///...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 at x-query.com.

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

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

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

ЧАВО

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

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

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

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

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

declare namespace x = "http://www.w3.org/1999/xhtml/";
doc("index.html")/x:html/x:body/x:p[@class="example"]

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

doc("myPlainHTML.html")/body

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

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

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

Просто помните, что необходимо объявить оба пространства имён в вашем XQuery и правильно их использовать. Рассмотрим следующий запрос, который предназначен для генерации XHTML-выходных данных из XML-входных данных:

declare default element namespace "http://www.w3.org/1999/xhtml";
<html>
    <body>
        {
            for $i in doc("testResult.xml")/tests/test[@status = "failure"]
            order by $i/@name
            return <p>{$i/@name}</p>
        }
    </body>
</html>

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

declare namespace x = "http://www.w3.org/1998/xhtml";
<x:html>
    <x:body>
        {
            for $i in doc("testResult.xml")/tests/test[@status = "failure"]
            order by $i/@name
            return <x:p>{$i/@name}</x:p>
        }
    </x:body>
</x:html>

Почему не работает моя возвращающая часть?

Помните, что 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, фраза 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> элемента <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.13/xquery-introduction.html

Spec-Zone.ru

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