Requêtes sérieuses avec XQuery

| Kavindra Devi Palaraja | Thibaut Cuvelier | Qt Quarterly | gbdivers |

Ceux d'entre vous qui suivent le développement de Qt auront remarqué la récente introduction d'un module QtXmlPatterns. Il permet l'utilisation de XQuery dans des applications Qt, avec à la clé diverses manières de sélectionner, d'extraire et d'agréger des données dans des fichiers XML ou à structure équivalente.

Pour vous donner un avant-goût de ce que QtXmlPatterns peut vous proposer, nous implémenterons un robot Web simple et basé sur Qt qui peut être utilisé pour effectuer des requêtes XQuery basiques comme le listage d'éléments et la recherche de liens dans des documents HTML. Nous lui ajouterons une interface utilisable et fournirons ainsi une vue claire du site Web d'entrée, de la requête exécutée et de la sortie générée. Cependant, avant de commencer la construction du robot, regardons un peu plus en détail XQuery.

Cet article est une traduction autorisée de Serious Enquiries with XQuery de Kavindra Devi Palaraja.

- Titre : Requêtes sérieuses avec XQuery

- Auteurs : Kavindra Devi Palaraja, Thibaut Cuvelier, Qt Quarterly, gbdivers

- Parution : 8 octobre 2010

- Mise à jour : 23 octobre 2010

- Licence : Copyright ® 2008 Kavindra Devi Palaraja. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Un mot sur XQuery

- III. Designer le robot Web

- III-A. L'interface utilisateur

- IV. Implémenter le robot

- IV-A. Le fichier de ressources

- IV-B. La classe MainWindow

- V. Écrire les requêtes

- V-A. Charger le site

- V-B. Lister les liens RSS valides

- V-C. Lister tous les éléments d'image

- V-D. Compter les numéros des Qt Quarterly

- VI. Dernières pensées

- VII. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Qt à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Serious Enquiries with XQuery de Kavindra Devi Palaraja paru dans la Qt Quarterly Issue 25.

Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(-ies), inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Un mot sur XQuery

Au plus simple niveau, XQuery est un langage pour les requêtes dans des documents XML, avec pour but la recherche et la manipulation des informations stockées. Comme SQL est utilisé pour interagir avec des bases de données relationnelles, XQuery est le langage de choix pour la gestion du XML de sources en ligne.

XQuery est un langage fonctionnel, à typage statique, faisant partie de la famille de technologies Web concernées par l'exploitation de données XML. Il y a plus de 100 fonctions prévues dans le langage XQuery et différentes manières de les utiliser. L'utilisation en profondeur de XQuery dépasse cet article ; nous nous limiterons à un aperçu global des bases de l'utilisation du module QtXmlPatterns. Nous regarderons de plus près deux types de requêtes :

1. Les expressions de chemin (path expressions) sont utilisées pour sélectionner des parties d'un document, comprenant une séquence d'étapes séparées par des séparateurs de chemin (/) ;

2. Les FLWOR (prononcer « flower », acronyme de for, let, where, order by, return, les cinq clauses principales sont utilisées pour itérer le document et manipuler les données contenues.

Le robot Web exécutera quatre requêtes, chacune sera expliquée en détail dans les sections à venir.

III - Designer le robot Web

Habituellement, les robots Web analysent des sites Web à l'aide de routines comme la recherche de liens et la récolte de statistiques. Ceci est effectué en se basant sur les permissions spécifiées dans le fichier robots.txt du site en question. Mon collègue, Frans Englich, m'a suggéré un autre type de robot Web pour illustrer l'utilisation de XQuery, qui, très simplement, sélectionne une page dans un site et en analyse le contenu en quelques requêtes.

III-A - L'interface utilisateur

Qt Designer est utilisé pour designer l'interface du robot Web, qui consiste en trois sections représentées par des widgets QGroupBox :

1. L'entrée : nous utilisons un QLineEdit en lecture seule pour afficher l'adresse du site et une QWebView pour afficher la page ; entre ces deux widgets, nous plaçons un espace vertical ; les widgets et l'espace sont ensuite déposés dans un QGridLayout ;

2. La requête : un QTextEdit est utilisé pour afficher la requête en cours d'exécution ; les boutons-poussoirs sont déposés horizontalement dans un QHBoxLayout ; nous déposons ensuite l'éditeur de texte et le layout des boutons verticalement dans un QVBoxLayout ;

3. La sortie : nous utilisons un autre QTextEdit pour l'afficher ; un objet de layout n'est pas requis ici, comme la position de l'éditeur sera gérée par la boîte du groupe.

IV - Implémenter le robot

Maintenant que nous avons notre interface utilisateur faite et enregistrée dans un fichier .ui, nous pouvons implémenter le robot proprement dit.

IV-A - Le fichier de ressources

Nous avons besoin d'un fichier de ressources Qt (.qrc) dans lequel nous pouvons embarquer le fichier d'interface (.ui) et les requêtes XQuery (.xq). Les requêtes sont embarquées dans des fichiers séparés et seront chargées à l'exécution. Le contenu du fichier est montré ci-dessous.

<!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>

IV-B - La classe MainWindow

Ensuite, nous créons une classe MainWindow qui contiendra nos widgets. Cette classe hérite de QMainWindow, contenant les définitions nécessaires à la création d'une interface pour notre robot Web.

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
MainWindow();

public slots:
void evaluate(const QString &str);

Il y a un constructeur et un slot, evaluate(), qui servira à évaluer les requêtes qui seront lues depuis un fichier.

Différentes manières sont possibles pour embarquer un fichier .ui dans un programme, nous utiliserons le module QtUiTools. Ainsi, la section privée de notre classe MainWindow est constituée de tous les widgets utilisés pour l'interface, un widget central pour MainWindow, robotWidget et un objet QSignalMapper, signalMapper. Nous avons aussi une fonction privée, loadUiFile(), pour charger le fichier .ui mentionné ci-dessus.

private:
QLineEdit* ui_websiteLineEdit;
QPushButton* ui_queryButton1;
...
QWidget* robotWidget;
QSignalMapper *signalMapper;

QWidget* loadUiFile();
};

Regardons l'implémentation du constructeur.

MainWindow::MainWindow()
{
robotWidget = loadUiFile();
ui_websiteLineEdit = qFindChild<QLineEdit*>(this, "websiteLineEdit");
ui_websiteViewer = qFindChild<QWebView*>(this, "websiteViewer");
ui_queryTextEdit = qFindChild<QTextEdit*>(this, "queryTextEdit");
...

L'interface est chargée dans robotWidget. Ensuite, la fonction qFindChild de QObject est utilisée pour accéder aux widgets.

La classe QSignalMapper est utilisée pour collecter un groupe de signaux et les réémettre avec des paramètres qui correspondent à l'objet envoyeur. Cette classe est utile quand vous voulez connecter de nombreux signaux à un seul slot tout en désirant identifier quel objet a émis quel signal et le gérer de manière spécifique.

Dans notre cas, nous désirons faire correspondre quatre boutons à une seule fonction d'évaluation ; nous connectons donc le signal clicked() de ui_queryButton1 au slot map() de signalMapper. Ceci est effectué pour les quatre boutons. Vous pouvez vous représenter signalMapper comme l'objet qui s'assure que la bonne requête est exécutée quand un bouton est pressé.

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 &)));

Nous utilisons ensuite la fonction setMapping() pour fournir à chaque bouton son propre paramètre, sous forme de chaîne. Ce paramètre détermine quel fichier de requête sera chargé pour un bouton particulier. De nouveau, ceci est fait pour chacun des quatre boutons. Finalement, nous connectons le signal mapped() de signalMapper à notre fonction 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");
}

Quand le robot est lancé, nous voulons afficher la page sur laquelle nous voulons lancer nos requêtes. Nous donnons donc pour URL à ui_websiteViewer celle du site des Qt Quarterly. Finalement, nous exécutons notre première requête avec l'aide de la fonction evaluate(), affichant la sortie à l'endroit désiré.

Si vous utilisez le module QtUiTools pour charger un fichier .ui, vous devrez instancier la classe QUiLoader et utiliser sa fonction load(). La fonction ci-dessous l'illustre.

QWidget* MainWindow::loadUiFile()
{
QUiLoader loader;
QFile file(":/forms/robot.ui");
file.open(QFile::ReadOnly);
QWidget *formWidget = loader.load(&file, this);
file.close();
return formWidget;
}

La prochaine fonction dans notre classe MainWindow est la fonction evaluate(), qui prend un paramètre QString. C'est dans cette fonction que nous effectuons nos requêtes.

void MainWindow::evaluate(const QString &fileName)
{
QFile queryFile(fileName);
queryFile.open(QIODevice::ReadOnly);

QString queryString = QTextStream(&queryFile).readAll();

ui_queryTextEdit->setPlainText(queryString);

Nous commençons par lire le fichier de nom fileName, qui contient une requête. Ensuite, nous l'affichons dans la partie réservée aux requêtes.

Pour évaluer une requête sur le contenu du document, nous créons un objet QXmlQuery, lions la variable inputDocument de l'URL du document et appelons setQuery() pour que notre requête soit effectuée sur le texte lu depuis le fichier de requête.

QXmlQuery query;

query.bindVariable("inputDocument", QVariant(ui_websiteViewer->url()));
query.setQuery(queryString, ui_websiteViewer->url());

Dès que la requête est liée à une variable, nous vérifions que la requête à essayer est valide. Si elle ne l'est pas, nous affichons un QMessageBox avec un message approprié.

if (!query.isValid()) {
QMessageBox::information(this, tr("Invalid Query"), tr("The query you are trying to execute is invalid."));
return;
}

La prochaine étape est d'évaluer la requête. Nous commençons par déclarer les variables nécessaires pour recevoir la sortie.

QByteArray outArray;
QBuffer buffer(&outArray);
buffer.open(QIODevice::ReadWrite);

QXmlFormatter est la classe responsable du formatage de la sortie XML, la rendant plus lisible. Nous construisons donc un QXmlFormatter avec la QXmlQuery et le QIODevice en paramètres.

QXmlFormatter formatter(query, &buffer);

if (!query.evaluateTo(&formatter)) {
QMessageBox::information(this, tr("Cannot Execute Query"), tr("An error occured while executing the query."));
return;
}

Nous tentons d'évaluer notre requête en utilisant la fonction evaluateTo() : si elle réussit, nous affichons la sortie ; sinon, nous affichons un message pour en informer l'utilisateur.

Finalement, nous fermons notre buffer et envoyons le contenu de outArray à notre éditeur de texte servant à l'affichage de la sortie. Puisque la sortie est fournie comme un texte encodé en UTF8, nous devons le décoder avant de le passer au widget.

buffer.close();
ui_outputTextEdit->setPlainText(QString::fromUtf8(outArray.constData()));
}

Nous ajoutons un nouveau slot pour que le QLineEdit contenant l'adresse du site soit en phase avec le navigateur.

void MainWindow::updateLocation(const QUrl &url)
{
ui_websiteLineEdit->setText(url.toString());
}

La partie visible de notre robot est maintenant complète. Maintenant, regardons l'implémentation des requêtes elles-mêmes.

V - Écrire les requêtes

Avant de regarder comment écrire des requêtes, il est important de noter que les requêtes XQuery ne fonctionnent que sur des documents XML bien formés. Nous ne serons donc pas capables de l'utiliser sur des sites qui utilisent du HTML, pas de problème pour le XHTML.

Les requêtes que nous utiliserons sont simples dans le sens qu'elles ne font que filtrer l'information trouvée dans des documents en ligne, fournissant une sortie facile à comprendre quand elle est affichée en tant que texte brut.

V-A - Charger le site

La première requête à écrire est utilisée pour obtenir le nœud du document dans le site des Qt Quarterly. Nous utilisons la fonction doc() sur le contenu de la variable inputDocument :

doc($inputDocument)

Rappelez-vous que inputDocument est liée à la valeur que nous avons enregistrée dans la fonction evaluate(), le navigateur Web va afficher le site des Qt Quarterly pendant que la sortie affichera la source XHTML correspondant à cette page.

V-B - Lister les liens RSS valides

Nous avons mentionné plus tôt les FLWOR, qui sont utilisées dans des requêtes plus compliquées pour joindre et trier des données, construire des éléments, etc. Pour notre deuxième requête, nous utilisons ce type d'expression pour extraire les liens RSS que nous validons avec la fonction doc-available(). La sortie de la requête n'affichera que les liens valides.

for $alternate in doc($inputDocument)//*:link[@rel="alternate" and @type="application/rss+xml"]
return
if (doc-available(resolve-uri($alternate/@href)))
then $alternate
else ()

Bien que nous n'ayons ni let ni where, notre requête est valide, car un FLWOR ne requiert qu'une clause for ou let.

Le double slash (//) en combinaison avec la chaîne du lien sélectionne des éléments de lien n'importe où dans le document ; l'astérisque (*) indique que les éléments sont de n'importe quel espace de nom. Nous ne sélectionnons que des éléments avec des attributs réels et types souhaitables.

Nous utilisons la fonction resolve-uri() pour résoudre l'URI relative en fonction de l'URI de base, format l'URI absolue. Le résultat de la requête pour des liens RSS ressemble à ceci :

Les fonctions dont nous avons besoin

- doc(), qui prend une URI et récupère son nœud document (l'entièreté du document XML) ;

- doc-available(), qui prend une URI et y appelle la fonction doc() : elle retourne true si la fonction doc() retourne un nœud de document, false sinon ;

- resolve-uri(), qui résout une URI relative en URI absolue ;

- count(), qui compte le nombre d'arguments ;

- starts-with(), qui retourne true ou false, indiquant si une chaîne commence avec les caractères d'une autre.

V-C - Lister tous les éléments d'image

Notre troisième requête tente de lister tous les éléments d'image, n'importe où sur le site des Qt Quarterly, dans n'importe quel espace de noms.

doc($inputDocument)//*:img

Voici le résultat :

V-D - Compter les numéros des Qt Quarterly

Supposons que nous aimerions compter le nombre de numéros des Qt Quarterly sortis jusqu'à présent. Nous savions que chaque image est embarquée dans un tableau. Nous sélectionnons donc tous les éléments table, tous les éléments td et tous les éléments img à l'intérieur. Ensuite, nous appelons la fonction starts-with(), parce que nous n'avons besoin que des éléments img dont l'attribut alt commence par "issue".

count(doc($inputDocument)//*:table//*:td//*:img[
starts-with(@alt, "Issue")])

La sortie de cette requête est de 24 au moment de la rédaction.

VI - Dernières pensées

Le robot Web n'est qu'un petit aperçu de ce qui est possible avec le module QtXmlPatterns. Vous pouvez modifier les requêtes pour effectuer des opérations plus avancées, comme la vérification de liens cassés, la recherche de données encodées dans le document en utilisant des microformats et ainsi de suite.

Aussi, si vous souhaitez analyser plus d'un site, vous devrez hériter de QAbstractXmlNodeModel, qui peut représenter n'importe quel type de données, d'une forme qui peut être exploitée par QXmlQuery. Souvenez-vous que tous les sites ne sont pas faits de XML bien formé - nous occuper de ce problème est un challenge que nous avons décidé de ne pas tenter lors de l'écriture de cet article.

Un autre aspect de XQuery que nous n'avons pas exploré ici est l'utilisation de requêtes comme modèles, où les expressions elles-mêmes sont écrites comme des parties de documents XML. Ceci rend possible d'écrire des applications qui peuvent prendre du contenu, le traiter et même le fournir à un navigateur Web. La manipulation de contenu le long de ces lignes pourrait même former la base d'un générateur de reports ou une interface de base de données.

Qt fournit un set d'exemples et de démonstrations pour le module QtXmlPatterns qui montre comment utiliser XQuery dans vos applications. Nous vous recommandons de commencer avec l'exemple Recipes avant d'aller voir des fonctionnalités plus avancées.

VII - Divers

J'adresse ici de chaleureux remerciements à beaucoup de monde, en particulier à ram-0000 et jacques_jean pour leur relecture et leurs conseils avisés.

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisés à traduire cet article !