Серьезные запросы с XQuery

Kavindra Devi Palaraja

Те из вас, кто следит за ходом разработки Qt заметили недавнее появление модуля QtXmlPatterns. Этот модуль позволяет использовать XQuery в Qt приложениях для обеспечения различных способов выбора, извлечения и объединения данных в XML или подобным им по структуре файлах.

Чтобы дать вам понять возможности QtXmlPatterns, мы разработаем основанный на Qt простой Web робот, который можно использовать для выполнения ряда базовых запросов XQuery, таких как формирование списка элементов и проверка ссылок в HTML документах. Мы сформируем удобный пользовательский интерфейс для четкого представления ввода с Web сайта, выполнения запроса и генерируемого вывода. Однако прежде чем мы начнем строить наш робот, давайте немного более подробно рассмотрим XQuery.

О XQuery

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

XQuery это функциональный, статически типизированный язык, который является частью семейства Web технологий, связанных с обработкой XML данных. В XQuery существует более 100 встроенных функций с различными способами их использования. В этой статье мы не будем углубляться в применение XQuery, а изложим основные принципы использования модуля QtXmlPatterns.  Мы рассмотрим два вида запросов:

Web робот будет выполнять четыре запроса. Все они будут рассмотрены подробно в последующих разделах.

Проектирование Web-робота

Обычно Web роботы анализируют Web сайты, обеспечивая выполнение рутинных задач, таких как проверка ссылок и сбор статистики. Это делается на основе разрешений, указанных в файле robots.txt Web сайта. Мой коллега, Frans Englich, для иллюстрации использования XQuery предложил другой тип Web робота, который просто выбирает одну страницу с Web сайта и анализирует содержимое, выполняя несколько запросов.  

Пользовательский интерфейс

Для разработки пользовательского интерфейса Web робота используется Qt Designer. Интерфейс включает три секции, представленные виджетами QGroupBox:

The user interface in Qt Designer

The Webrobot GUI

Реализация Web робота

Теперь, когда мы разработали наш пользовательский интерфейс и сохранили его в файле .ui Qt Designer, мы может перейти к реализации Web робота.

Файл ресурсов

Нам требуется файл ресурсов Qt (.qrc), в который мы должны включить (.ui) файл и запросы XQuery (.xq). Запросы находятся в отдельных файлах и будут загружены во время выполнения. Ниже приводится содержимое файла ресурсов.

    <!DOCTYPE RCC><RCC version="1.0">

   <qresource>

        <file>forms/robot.ui</file>

       <file>queries/query1.xq</file>

        <file>queries/query2.xq</file>

       <file>queries/query3.xq</file>

       <file>queries/query4.xq</file>

    </qresource>

    </RCC>

Класс MainWindow

Далее мы создаем класс MainWindow для поддержки наших виджетов. Этот класс является подклассом QMainWindow, и содержит определения, необходимые для создания представления нашего Web робота.

    class MainWindow : public QMainWindow

    {

        Q_OBJECT

    public:

        MainWindow();

    public slots:

        void evaluate(const QString &str);

Здесь задан конструктор и слот evaluate(), предназначенный для количественной оценки запросов, которые будут читать непосредственно из файла.

Существуют различные способы включения файла .ui в программу, но мы будем использовать модуль UiTools. Таким образом, в private секции нашего класса MainWindow будут  все виджеты, используемые в пользовательском интерфейсе, центральный виджет  MainWindow, robotWidget, и signalMapper, объект класса QSignalMapper,. Также мы имеем private функцию loadUiFile() для загрузки упомянутого выше файла .ui.

    private:

        QLineEdit* ui_websiteLineEdit;

        QPushButton* ui_queryButton1;

        ...

        QWidget* robotWidget;

        QSignalMapper *signalMapper;

        QWidget* loadUiFile();

    };

Давайте посмотрим на реализацию конструктора:

    MainWindow::MainWindow()

    {

        robotWidget = loadUiFile();

        ui_websiteLineEdit = qFindChild<QLineEdit*>(this,

            "websiteLineEdit");

        ui_websiteViewer = qFindChild<QWebView*>(this,

            "websiteViewer");

        ui_queryTextEdit = qFindChild<QTextEdit*>(this,

            "queryTextEdit");

        ...

Интерфейс пользователя загружается в robotWidget. Далее, функция qFindChild() QObject, используется для доступа к его виджетам.

Класс QSignalMapper используется для получения группы сигналов и повторного возбуждения их с параметрами, соответствующими объекту, который отправляет сигнал. Данный класс полезен, если вы хотите подключить несколько сигналов на один слот и определить, какой из объектов возбудил сигнал и соответствующим образом его обработать.

В нашем случае, мы хотим сопоставить четыре кнопки с одной функцией обработки. Следовательно, мы подключаем сигнал clicked() ui_queryButton1 к слоту map() signalMapper. Проделаем это для всех четырех кнопок. Мы можем представить SignalMapper как посредника, который гарантирует, что при нажатии кнопки будет выполняться правильный запрос.

      signalMapper = new QSignalMapper(this);

        connect(ui_queryButton1, SIGNAL(clicked()),

            signalMapper, SLOT (map()));

        ...

        signalMapper->setMapping(ui_queryButton1,

            QString(":queries/") + "query1.xq");

        ...

        connect(signalMapper, SIGNAL(mapped(const QString &)),

            this, SLOT(evaluate(const QString &)));

Затем мы вызываем функцию setMapping(), чтобы обеспечить каждой кнопке свой собственный текстовый параметр. Этот параметр определяет, какой файл запроса будет загружен для соответствующей кнопки. То же самое выполним для всех четырех кнопок. Наконец мы подключаем к сигналу mapped()signalMapper нашу функцию evaluate().

      connect(ui_websiteViewer,

            SIGNAL(urlChanged(const QUrl &)),

            this, SLOT(updateLocation(const QUrl &)));

        ui_websiteViewer->setUrl(

            QUrl("http://doc.trolltech.com/qq/"));

        setCentralWidget(robotWidget);

        setWindowTitle(tr("XQuery Web Robot"));

        evaluate(":queries/query1.xq");

    }

Во время работы Web робота, мы предполагаем отображать страницу, для которой хотим выполнять наши запросы. Мы поставили в ui_websiteViewer адрес URL сайта Qt Quarterly. В завершении мы выполняем наш первый запрос с помощью функции evaluate(), отображая выходные данные во вьюере.

Если мы используем модуль UiTools Qt для загрузки файла .ui, то необходимо создать экземпляр класса QUiLoader и вызвать его функцию load(). Функция, приведенная ниже, иллюстрирует данное действие.

    QWidget* MainWindow::loadUiFile()

    {

        QUiLoader loader;

        QFile file(":/forms/robot.ui");

        file.open(QFile::ReadOnly);

        QWidget *formWidget = loader.load(&file, this);

        file.close();

        return formWidget;

    }

Следующая функция в нашем классе MainWindow является evaluate(), которая принимает параметр QString. Именно в этой функции мы обрабатываем наши запросы.

    void MainWindow::evaluate(const QString &fileName)

    {

        QFile queryFile(fileName);

        queryFile.open(QIODevice::ReadOnly);

        QString queryString =

            QTextStream(&queryFile).readAll();

        ui_queryTextEdit->setPlainText(queryString);

Мы начинаем с чтения файла fileName, содержащего запрос. Затем мы отображаем его в нашем вьюере запросов.

Для оценки запроса к содержимому документа, мы создаем объект QXmlQuery, связывая переменную inputDocument с URL документа, и вызываем setQuery(),  чтобы установить наш запрос для текста, который мы читаем из файла запроса.

      QXmlQuery query;

        query.bindVariable("inputDocument",

                           QVariant(ui_websiteViewer->url()));

        query.setQuery(queryString, ui_websiteViewer->url());

Прежде чем выполнить запрос, проверяем – является ли он допустимым. Если это неверный запрос, мы выдаем QMessageBox с соответствующим сообщением.

      if (!query.isValid()) {

            QMessageBox::information(this,

                tr("Invalid Query"), tr("The query you are "

                "trying to execute is invalid."));

            return;

        }

Следующий шаг заключается в обработке запроса. Начнем с объявления переменных, необходимых для обеспечения вывода.

      QByteArray outArray;

      QBuffer buffer(&outArray);

      buffer.open(QIODevice::ReadWrite);

QXmlFormatter является классом, отвечающим за форматирование XML вывода, делая его более читабельным. Мы создаем QXmlFormatter с параметрами QXmlQuery и QIODevice.

      QXmlFormatter formatter(query, &buffer);

        if (!query.evaluateTo(&formatter)) {

            QMessageBox::information(this,

                tr("Cannot Execute Query"), tr("An error "

                "occured while executing the query."));

            return;

        }

Мы попытаемся выполнить наш запрос, используя функцию evaluateTo(). В случае успешного выполнения функции мы отображаем выходные данные. В противном случае мы выдаем окно с сообщением об ошибке для оповещения пользователя.

Наконец мы закрываем наш буфер и редактируем содержимое выходного outArray в текстовом редакторе нашего выходного вьюера. Поскольку выходные данные предоставляются в виде текста в кодировке UTF8, мы должны декодировать его перед передачей в текстовый редактор.

      buffer.close();

        ui_outputTextEdit->setPlainText(

            QString::fromUtf8(outArray.constData()));

    }

В одном созданных нами слотов сохраним строку редактирования, содержащую адрес Web сайта, синхронно помещая его в браузер.

    void MainWindow::updateLocation(const QUrl &url)

    {

        ui_websiteLineEdit->setText(url.toString());

    }

Видимая часть нашего Web робота теперь завершена. Далее мы рассмотрим реализацию запросов.

Написание запросов

До того, как мы рассмотрим написание запросов, важно отметить, что запросы XQuery работают только с правильными (well-formed) XML документами. Поэтому мы сможем их использовать только с сайтами, которые используют XHTML, а не HTML.

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

Загрузка Web сайта

Первый запрос, который мы напишем, используется для получения узла документа Web сайта Qt Quarterly. Мы вызываем функцию doc(), содержащую переменную inputDocument:

    doc($inputDocument)

Напомним, что inputDocument ограничивается значением, которое мы установили в нашей функции evaluate(), поэтому в Web браузере будет отображаться Web сайт Qt Quarterly, в то время как выходном вьюере будет отображаться соответствующий XHTML исходного текста страницы.

Список допустимых ссылок RSS

Ранее мы упомянули о FLWORs, которые используются в более сложных запросах для объединения данных, конструирования элементов, сортировки данных и так далее. Для нашего второго запроса мы используем этот тип выражения, чтобы извлечь RSS ссылки, которые мы проверим с помощью функции doc-available(). Результат запроса будет отображать только допустимые ссылки.

    for $alternate in doc($inputDocument)//*:link[@rel=

        "alternate" and @type="application/rss+xml"]

    return

        if (doc-available(resolve-uri($alternate/@href)))

        then $alternate

        else ()

Хотя  мы не используем let, наш запрос по-прежнему корректный, потому что FLWOR достаточно только одного оператора for или let.

Двойной слеш (//) в сочетании со строкой link выбирает  элементы ссылки в любом месте в документе, а «звездочка» (*) означает, что элементы могут быть из любого пространства имен. Также мы только выбираем элементы с подходящими атрибутами rel и type.

Мы вызвали функцию resolve-uri() для разрешения относительного URI вместо базового URI, формируя абсолютный URI. Результат запроса для RSS ссылок выглядит подобно данному:

The output from a query

Необходимые нам встроенные функции XQuery

Из множества встроенных функций XQuery мы выбираем пять общих функций для использования с нашим Web роботом:

· doc() принимает URI и извлекает узел его документа (весь XML документ).

· doc-Available() принимает URI и вызывает для него функцию doc(). Возвращает true если результат функции doc() есть узел документа, в противном случае возвращает false.

· Resolve-URI() позволяет относительному URI использоваться вместо абсолютного URI.

· Count() подсчитывает количество аргументов.

· starts-with() возвращает true или false, что указывает – существует ли хоть одна строка, начинающаяся с символов другой строкой.

Перечисление всех элементов изображений

Наш третий запрос пытается перечислить все элементы изображения в любом месте Web сайта Qt Quarterly в рамках любого пространства имен.

    doc($inputDocument)//*:img

Ниже показан результат.

The output from a query

Подсчет выпусков Qt Quarterly

Предположим, что мы хотели бы подсчитать количество выпусков Qt Quarterly. Мы знаем, что каждое изображение вставлено внутрь таблицы. Следовательно, мы выбираем все элементы table, все элементы td и все элементы img в них. Затем мы вызываем функцию starts-with(), поскольку нам необходимы только элементы изображений, у которых атрибут alt начинаются с "Issue".

    count(doc($inputDocument)//*:table//*:td//*:img[

        starts-with(@alt, "Issue")])

На момент написания статьи выходными данными для этого запроса будет "24".

Заключение

Web робот является лишь небольшим введением в возможности модуля QtXmlPatterns. Вы можете изменять запросы для выполнения более сложных операций, таких как проверка на «брошенные» ссылки, поиск данных, закодированных в документе с помощью microformats и так далее.

Также, если вы хотите проанализировать более чем один Web сайт, вы должны создать подкласс QAbstractXmlNodeModel , который сможет представлять любой вид исходных данных, в формате подходящем для QXmlQuery. Помните, что не все Web сайты сделаны с правильным XML, Решение этой проблемы реального мира является вызовом, не решенным, как мы считаем, на момент написания данной статьи.

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

Qt поставляется с набором примеров и демонстраций для модуля QtXmlPatterns, которые показывают, как использовать XQuery в ваших приложениях. Мы рекомендуем начать с примером Recipes прежде чем переходить к более расширенным возможностям.

Сайт создан в системе uCoz