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.
Image non disponible
Image non disponible

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.

 
Sélectionnez
    <!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.

 
Sélectionnez
    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.

 
Sélectionnez
    private:
        QLineEdit* ui_websiteLineEdit;
        QPushButton* ui_queryButton1;
        ...
        QWidget* robotWidget;
        QSignalMapper *signalMapper;
    
        QWidget* loadUiFile();
    };

Regardons l'implémentation du constructeur.

 
Sélectionnez
    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é.

 
Sélectionnez
      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().

 
Sélectionnez
      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.

 
Sélectionnez
    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.

 
Sélectionnez
    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.

 
Sélectionnez
        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é.

 
Sélectionnez
        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.

 
Sélectionnez
        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.

 
Sélectionnez
        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.

 
Sélectionnez
        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.

 
Sélectionnez
    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 :

 
Sélectionnez
    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.

 
Sélectionnez
    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 :

Image non disponible
Image personnelle

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.

 
Sélectionnez
    doc($inputDocument)//*:img

Voici le résultat :

Image non disponible

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".

 
Sélectionnez
    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.

Image non disponible

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 !