Spec-Zone.ru › Qt 5.11

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

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

Введение

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

<recipe/>

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

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

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

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

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

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

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

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

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

doc('cookbook.xml')//recipe

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

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

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

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

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

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

Шаги осей

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

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

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

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

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

Указатели осей

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

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

Тесты узлов

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

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

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

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

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

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

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

Тесты имён

Тесты имён являются тестами узлов, имеющими параметр name. Тест имени должен соответствовать имени узла имя помимо типа узла вид. Мы уже видели тесты имён, использованные:

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

В этом выражении пути как recipe, так и title являются тестами имён, записанными в сокращенной форме. XQuery разрешает эти имена (QNames) до их расширенной формы, используя все известные объявления пространств имён. Разрешение имени до его расширенной формы означает замену его префикса пространства имён, если он присутствует (в примере их нет), на URI пространства имён. Расширенное имя состоит тогда из URI пространства имён и локального имени.

Но имена в примере выше не имеют префиксов пространств имён, потому что мы не включали объявление пространства имён в наш cookbook.xml файл. Однако, мы часто будем использовать XQuery для запроса XML-документов, которые используют пространства имён. Забыть объявить правильное(ые) пространство(а) имён в XQuery — распространённая причина ошибок XQuery. Давайте добавим по умолчанию пространство имён к cookbook.xml сейчас. Измените элемент документа в cookbook.xml с:

<cookbook>

на...

<cookbook xmlns="http://cookbook/namespace">

Это называется объявлением пространства имён по умолчанию, потому что оно не включает префикс пространства имён. Включив это объявление пространства имён по умолчанию в элемент документа, мы подразумеваем, что все имена элементов без префиксов в документе, включая сам элемент документа (cookbook), автоматически находятся в пространстве имён по умолчанию http://cookbook/namespace. Обратите внимание, что имена атрибутов без префиксов не затрагиваются объявлением пространства имён по умолчанию. Они всегда считаются находящимися в без пространства имён. Также обратите внимание, что URL, который мы выбираем в качестве URI пространства имён, не обязательно должен ссылаться на фактическое местоположение, и в данном случае не ссылается. Но нажмите на http://www.w3.org/XML/1998/namespace, например, который является URI пространства имён для элементов и атрибутов с префиксом xml:.

Теперь, когда мы попытаемся запустить предыдущий пример XQuery, вывод не будет сгенерирован! Выражение пути больше не соответствует чему-либо в файле кулинарной книги, потому что наш XQuery ещё не знает об объявлении пространства имён, которое мы добавили в документ кулинарной книги. Есть два способа объявить пространство имён в XQuery. Мы можем присвоить ему префикс пространства имён (например, c для кулинарной книги) и добавить префикс пространства имён к каждому тесту имени:

declare namespace c = "http://cookbook/namespace";
doc('cookbook.xml')//c:recipe/c:title

Или мы можем объявить пространство имён как пространство имён элементов по умолчанию, и тогда мы всё равно можем запустить оригинальный пример XQuery:

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

Оба метода будут работать и дадут тот же вывод, все <title> элементы:

<title xmlns="http://cookbook/namespace">Quick and Easy Mushroom Soup</title>
<title xmlns="http://cookbook/namespace">Cheese on Toast</title>
<title xmlns="http://cookbook/namespace">Hard-Boiled Eggs</title>

Но обратите внимание, как вывод немного отличается от вывода, который мы видели до добавления объявления пространства имён по умолчанию в файл кулинарной книги. Qt XML Patterns автоматически включает правильный атрибут пространства имён в каждый <title> элемент в выводе. При загрузке документа и расширении 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:///...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 загружается без ошибок, элемент <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 на списках рассылки:

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

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

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

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

ЧАВО

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

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

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

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

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

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

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

doc("myPlainHTML.html")/body

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

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

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

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

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

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

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

Почему не работает моя фраза return?

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

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

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

Использование скобок для установления приоритета более важно в XQuery, чем в других языках, поскольку XQuery — это язык, основанный на выражениях. В этом случае, без скобок, окружающих $i + $d, фраза return возвращает только $i. У +$d будет результатом выражения FLWOR в качестве левого операнда. И, поскольку область действия переменной $d заканчивается в конце фразы return, будет сообщено об ошибке переменной, выходящей за пределы области видимости. Исправьте эти проблемы с помощью скобок.

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

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

Вероятно, вы неправильно расположили фигурные скобки. Если вы хотите вычислить выражение внутри конструктора элемента, заключите выражение в фигурные скобки. Без фигурных скобок выражение будет интерпретироваться как текст. Вот выражение sum(), используемое в элементе <e>. Таблица показывает случаи, когда фигурные скобки отсутствуют, неправильно расположены и расположены правильно:

Конструктор элемента с выражением... вычисляется до...
<e>sum((1, 2, 3))</e> <e>sum((1, 2, 3))</e>
<e>sum({(1, 2, 3)})</e> <e>sum(1 2 3)</e>
<e>{sum((1, 2, 3))}</e> <e>6</e>

Мой предикат правильный, так почему он не выбирает нужные данные?

Либо вы поместили предикат в неправильное место в выражении пути, либо забыли добавить скобки. Рассмотрим этот входной файл doc.txt:

<doc>
  <p>
    <span>1</span>
    <span>2</span>
  </p>
  <p>
    <span>3</span>
    <span>4</span>
  </p>
  <p>
    <span>5</span>
    <span>6</span>
  </p>
  <p>
    <span>7</span>
    <span>8</span>
  </p>
  <p>
    <span>9</span>
    <span>a</span>
  </p>
  <p>
    <span>b</span>
    <span>c</span>
  </p>
  <p>
    <span>d</span>
    <span>e</span>
  </p>
  <p>
    <span>f</span>
    <span>0</span>
  </p>
</doc>

Предположим, вам нужен первый <span> элемент каждого элемента <p>. Примените фильтр позиции ([1]) к шагу пути /span:

let $doc := doc('doc.txt')
return $doc/doc/p/span[1]

Применение фильтра [1] к шагу /span возвращает первый <span> элемент каждого элемента <p>:

<span>1</span>
<span>3</span>
<span>5</span>
<span>7</span>
<span>9</span>
<span>b</span>
<span>d</span>
<span>f</span>

Примечание: Вы можете записать тот же запрос следующим образом:

for $a in doc('doc.txt')/doc/p/span[1]
return $a

Или вы можете сократить его до этого:

doc('doc.txt')/doc/p/span[1]

С другой стороны, предположим, что вам нужен только один <span> элемент, первый в документе (то есть, вам нужен только первый <span> элемент в первом элементе <p>). Тогда вам нужно выполнить дополнительную фильтрацию. Есть два способа сделать это. Вы можете применить фильтр [1] в том же месте, что и выше, но заключите выражение пути в скобки:

let $doc := doc('doc.txt')
return ($doc/doc/p/span)[1]

Или вы можете применить второй фильтр позиции ([1]) к шагу пути /p:

let $doc := doc('doc.txt')
return $doc/doc/p[1]/span[1]

В любом случае запрос вернёт только первый <span> элемент в документе:

<span>1</span>

Почему моя FLWOR работает не так, как ожидалось?

Короткий ответ заключается в том, что вы, вероятно, ожидали, что ваша XQuery FLWOR будет работать так же, как цикл for в C++. Но это не так. Рассмотрим простой пример:

for $a in (8, -4, 2)
let $b := ($a * -1, $a)
order by $a
return $b

Этот запрос вычисляет 4 -4 -2 2 -8 8. Фраза for действительно создаёт цикл типа for, который вычисляет остальную часть FLWOR несколько раз, один раз для каждого значения, возвращаемого выражением in. Это похоже на цикл C++ for.

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

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

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

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

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

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

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

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

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

Запрос находит все элементы <p> в файле. Для каждого элемента <p> он создаёт элемент <p> в выводе, содержащий конкатенированные содержимое всех дочерних элементов <p> элемента. При выполнении запроса через xmlpatterns может быть получен следующий вывод, который не отсортирован в ожидаемом порядке.

<p>78</p>
<p>9a</p>
<p>12</p>
<p>bc</p>
<p>de</p>
<p>34</p>
<p>56</p>
<p>f0</p>

Можно использовать цикл for, чтобы убедиться, что порядок набора результатов соответствует порядку входной последовательности:

for $a in doc('doc.txt')//p
 return <p>{$a/span/node()}</p>

Этот вариант даёт тот же набор результатов, но в ожидаемом порядке:

<p>12</p>
<p>34</p>
<p>56</p>
<p>78</p>
<p>9a</p>
<p>bc</p>
<p>de</p>
<p>f0</p>

Почему я не могу использовать true и false в моём XQuery?

Вы можете, но не просто используя имена true и false напрямую, потому что они являются тестами имён, хотя и выглядят как булевы константы. Простой способ создания булевых значений — использовать встроенные функции true() и false() везде, где вы хотите использовать true и false. Другой способ — вызвать конструктор булевых значений:

xs:boolean("true")

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

Spec-Zone.ru

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