Поиск в XML с помощью XQuery
Тема парсинга XML настолько обширна, что грех был бы не включить в цикл статей про это рассмотрение возможностей XQuery - языка запросов к XML-базе данных на основе XPath. Сразу скажу, что библиотеку для работы с XQuery под PHP я нашел только одну - это библиотека XQuery Lite версии 1.0. Выпущена она была в году и с тех пор, к сожалению, не развивалась. |
Но это не мешает никому ее совершенствовать, т.к. она распространяется с открытым исходным кодом (правда, почему-то никто этого не делает - наверное всем достаточно текущих ее возможностей).
Почему в названии слово Lite? Потому что это сильно облегченный вариант языка XQuery, но Lite вполне достаточно для решения большинства задач по поиску и извлечению информации из XML. Официальная документация по XQuery находится здесь.
Итак, библиотека XQuery Lite 1.0 была написана неким Luis Argerich (lrargerich@yahoo.com). В ее кратком описании значится следующее:
«This is an implementation of a subset of the Xquery language with intention to add new features in next releases. It is based on flwr expressions».
Если перевести, то получится что-то вроде «Это реализация подмножества языка XQuery с намерениями дальнейшего расширения ее возможностей. Базируется на FLWR-выражениях».
Что такое FLWR-выражения будет понятно дальше.
Итак, XQuery - это SQL-подобный язык. Т.е. поисковые запросы к базе данных можно составлять словами, в отличие от XPath. Сразу приведу пример:
test.xml
<bib>
<book year="1994">
<title>TCP/IP Illustrated</title>
<author>Someone1</author>
</book>
<book year="1990">
<title>MS-DOS Operation System</title>
<author>Someone2</author>
</book>
</bib>
Допустим, нам нужно найти все книги в файле test.xml, с годом издания больше 1991:
test.php
header('Content-type: text/plain;');
// подключаем класс XQuery Lite
include_once("class_xquery_lite.php");
// формируем запрос
$query = '<bib> {
for $b in document("test.xml")/bib/book
where $b/@year > 1991
return
<book year="{$b/@year}">
{$b/title}
</book>
}
</bib> ';
$xq = new XqueryLite();
$result = $xq->evaluate_xqueryl($query);
// выводим результат
echo $result;
?>
Этот скрипт в итоге выведет:
<book year="1994"><br />
<title>TCP/IP Illustrated</title><br />
</book><br />
</bib>
Т.е., как видим, результатом будет не какой-то объект, как в случае DOM или SimpleXML, а текстовое представление, т.е. тот же XML, который в итоге можно будет дальше обработать уже другим способом.
В нашем запросе мы использовали конструкцию типа FWR - FOR, WHERE, RETURN. Конструкция FLWR - это FOR, LET, WHERE, RETURN. Что такое LET?
LET - это конструкция присваивания. Рассмотрим немного измененный запрос:
for $b in document("test.xml")/bib/book<br />
let $title := $b/title<br />
where $b/@year > 1991<br />
return<br />
<book year="{$b/@year}"><br />
{$title}<br />
</book><br />
}<br />
</bib>';
Этот запрос вернет тот же результат. Как видим, конструкция {$title} возвращает содержимое тега title вместе с самим тегом. Чтобы отбросить теги, нужно использовать функцию text():
for $b in document("test.xml")/bib/book<br />
let $title := $b/title/text()<br />
where $b/@year > 1991<br />
return<br />
<book year="{$b/@year}"><br />
{$title}<br />
</book><br />
}<br />
</bib>';
Этот запрос вернет:
<book year="1994"><br />
TCP/IP Illustrated<br />
</book><br />
</bib>
Т.е. тег title отброшен, но оставлено его текстовое содержимое.
В полной версии языка XQuery есть конструкция FLOWR - с добавлением ORDER - сортировки. Но здесь мы рассматривать ее не будем, т.к. нашей библиотекой XQuery Lite оно не поддерживается.
Если Вы внимательно прочли запросы выше, то должны были заметить, что синтаксис некоторых частей совпадает с XPath:
/bib/book
$b/@year
Конструкция
for $b in document(“test.xml”)/bib/book
Означает, что мы переменной $b присвоим все содержимое элемента book, родителем которого является bib. Используя функцию document мы говорим интерпретатору, что база данных лежит во внешнем файле test.xml. Кстати, вместо document можно использовать xmlmem:
…
// присваиваем содержимое xml переменной
$test_xml = '<?xml version="1.0"?>
<bib>
<book year="1994">
<title>TCP/IP Illustrated</title>
<author>Someone1</author>
</book>
<book year="1990">
<title>MS-DOS Operation System</title>
<author>Someone2</author>
</book>
</bib>';
// формируем запрос
$query = '<bib> {
for $b in xmlmem($test_xml)/bib/book, $t in $b/title
where $b/@year > 1991
return
<book year="{$b/@year}">
{$t}
</book>
}
</bib> ';
…
?>
В этом случае интерпретатор возьмет содержимое XML из памяти - из текстовой переменной с именем $test_xml. Заметили, что я тут изменил запрос?
Этим я хотел показать, что в конструкции FOR можно перечислять присваивание переменных через IN, чтобы эти переменные потом использовать.
После ключевого слова WHERE (кстати, конструкция WHERE необязательна) следуют условия, по которым должна быть извлечена информация. Условий может быть несколько и они могут быть соединены логическими И/ИЛИ. Так, запрос
for $b in xmlmem($test_xml)/bib/book, $t in $b/title<br />
where $b/@year > and $b/@year < 1994<br />
return<br />
<book year="{$b/@year}"><br />
{$t}<br />
</book><br />
}<br />
</bib>';
вернет
<book year="1990"><br />
<title>MS-DOS Operation System</title><br />
</book><br />
</bib>
Еще мы в WHERE можем использовать арифметические операции +,-,*,/. Можем использовать скобки для арифметических и логических выражений. Можем использовать функцию count() для вычисления количества элементов:
<bib><br />
<book year="1994"><br />
<title>TCP/IP Illustrated</title><br />
<author>Someone1</author><br />
</book><br />
<book year="1990"><br />
<title>MS-DOS Operation System</title><br />
<author>Someone2</author><br />
<author>Someone3</author><br />
</book><br />
</bib>';<br />
<br />
$query = '<bib> {<br />
for $b in xmlmem($test_xml)/bib/book, $t in $b/title<br />
where $b/@year > 1989<br />
return<br />
<book year="{$b/@year}"><br />
{$t}<br />
</book><br />
}<br />
</bib>';
Выполнение этого запроса вернет нам
<book year="1994"><br />
<title>TCP/IP Illustrated</title><br />
</book><br />
<book year="1990"><br />
<title>MS-DOS Operation System</title><br />
</book><br />
</bib>
А выполнение этого
for $b in xmlmem($test_xml)/bib/book, $t in $b/title<br />
where $b/@year > and count($b/author) = 1<br />
return<br />
<book year="{$b/@year}"><br />
{$t}<br />
</book><br />
}<br />
</bib>';
вернет
<book year="1994"><br />
<title>TCP/IP Illustrated</title><br />
</book><br />
</bib>
А еще мы можем выполнять запрос в запросе:
test.xml
<bib>
<book year="1994">
<title>TCP/IP Illustrated</title>
<author>Someone</author>
</book>
<book year="1990">
<title>MS-DOS Operation System</title>
<author>Someone</author>
</book>
<book year="2000">
<title>Windows Operation System</title>
<author>Microsoft</author>
</book>
</bib>
test.php
…
$query = '{
for $d in document("test.xml")/bib/book, $a in distinct-values($d/author)
return
{
for $b in document("test.xml")/bib/book, $b2 in $b/author
where $b2 = $a and $b/@year != $d/@year
return {$b}
}
}';
…
?>
Такой запрос вернул у меня
<title>MS-DOS Operation System</title><br />
<author>Someone</author><br />
</book><br />
<book year="1994"><br />
<title>TCP/IP Illustrated</title><br />
<author>Someone</author><br />
</book>
Основные возможности я рассмотрел. Остальные Вы можете рассмотреть самостоятельно в справке к этому языку, которую я выложил в архиве. Еще в этом архиве Вы найдете сам класс XQuery Lite 1.0 (для версий php4/5) с примером его использования. Изначально класс был написан в году для тогдашней версии PHP 4 (а может даже 3). Пришлось его немного исправить, чтобы работал и в PHP5. Для версии 4 я не проверял, поэтому там могут быть ошибки. Но они легко устранимы, если посидеть пару часов
Того, что было рассмотрено в этой статье, Вам должно хватить для самостоятельного освоения этого весьма удобного языка для извлечения инфы из XML.
Я буду рад всем комментариям в блоге, а особенно тем, которые мне скажут, что кроме этого класса есть еще какие-нибудь методы для работы с XQuery в PHP
Класс не работает под ПХП 5 (((
Какую ошибку выдает?
Call to undefined function xmldoc() in … on line 207
xmldoc — она вроде как только в 4-ке была