XQuery Travaux Pratiques

Inria

Interrogez une BD native XML avec XQuery.

Organisez vos requêtes XQuery dans des modules.

BD XML

Prérequis

  • XML
  • XPath

Cours

  • XQuery

Inria publie annuellement son RApport d'activité sur le WEB (RAWEB) à partir de données sources transformées en XML puis dynamiquement en HTML. Vous allez manipuler quelques uns de ces documents avec XQuery.

Import des documents XML dans la BD

Téléchargez les puis dézippez les dans un répertoire (que par la suite on référencera par "[RAWEB]").

Ouvrez l'un des fichiers XML dans un navigateur pour en étudier la structure.

Au lieu de traiter les fichiers individuellement, on les stocke dans une base de données XML (eXist), ce qui permet d'interroger la base avec une grande souplesse grâce à XQuery. On lancera les requêtes XQuery depuis l'interface cliente d'eXist.

Téléchargez eXist et installez-le à l'aide du wizard.

Lancez le dashboard eXist, puis allez dans les collections, et téléversez les fichiers XML dans une nouvelle collection (nommée "raweb").
Vérifiez que les fichiers sont accessibles par :
http://localhost:8080/exist/rest/raweb/acacia.xml

Lancez eXide, l'IDE intégré d'eXist.

Saisissez la première requête :

count( //identification )
et exécutez-là; vous devriez obtenir "7": c'est le nombre de projets qui ont été chargés dans la base. Accessoirement, vous pouvez enregistrer les requêtes dans un fichier dans la BD, et inversement charger une requête depuis un fichier.

Info Quelques BD XML


BaseX
BaseX

eXist
eXist

Lux
Lux

Zorba
Zorba

Requêtes XQuery simples

Modifiez la requête pour que le résultat soit contenu dans un élément, comme ceci :

<projets>7</projets>
<projets>
    { count( //identification ) }
</projets>

Modifiez la requête pour que le résultat soit contenu dans un attribut, comme ceci :

<projets nbre="7"/>
<projets nbre="{ count( //identification ) }"/>

Modifiez la requête pour obtenir le même résultat, mais en créant l'attribut à l'aide du constructeur d'attributs attribute.

<projets>
    { attribute nbre
        { count( //identification ) }
    }
</projets>

Créez les requêtes suivantes :

Liste des <shortname> des projets, l'ensemble étant entouré du littéral <projets> (utiliser let... return)

let $project := /raweb/identification/shortname
return <projets>{$project}</projets>

Liste des <shortname> des projets, chacun étant entouré du littéral <projet> (utiliser for... return)

for $project in /raweb/identification/shortname
return <projet>{$project}</projet>

Liste des <shortname> des projets, chacun étant entouré du littéral <projet> et l'ensemble étant entouré du littéral <projets>

<projets>
{
    for $project in /raweb/identification/shortname
    return <projet>{$project}</projet>
}
</projets>

Faites disparaître le <span class="..."> pour ne garder que le nom du projet ; faites ensuite disparaître <shortname> pour ne garder que le nom du projet.

<projets>
{
    for $project in /raweb/identification/shortname
    return <projet>{ string( $project ) }</projet>
}
</projets>

Production HTML avec XQuery

L'objectif est de reproduire ce résultat XHTML, bien qu'une meilleure méthode serait de construire une extraction XML intermédiaire qu'on transformerait avec XSLT.

Résultat escompté

RAWEB teams summary

7 projects

Project Persons
ACACIA 26
aces 19
ADEPT 16
Alchemy 37
ALCOVE 35
Algo 15
AlGorille 16
Code HTML
<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>RAWEB teams summary</title>
    </head>
    <body>
        <h1>RAWEB teams summary</h1>
        <p>7 projects</p>
        <table border="2">
            <tr>
                <th>Project</th>
                <th>Persons</th>
            </tr>
            <tr>
                <td valign="top">ACACIA</td>
                <td>26</td>
            </tr>
            <tr>
                <td valign="top">aces</td>
                <td>19</td>
            </tr>
            <tr>
                <td valign="top">ADEPT</td>
                <td>16</td>
            </tr>
            <tr>
                <td valign="top">Alchemy</td>
                <td>37</td>
            </tr>
            <tr>
                <td valign="top">ALCOVE</td>
                <td>35</td>
            </tr>
            <tr>
                <td valign="top">Algo</td>
                <td>15</td>
            </tr>
            <tr>
                <td valign="top">AlGorille</td>
                <td>16</td>
            </tr>
        </table>
    </body>
</html>

Produisez le résultat XHTML attendu d'abord sans l'espace de nommage de XHTML.

xquery version "1.0";
<html>
    <head><title>RAWEB teams summary</title></head>
    <body><h1>RAWEB teams summary</h1>
        <p>{ count(/raweb/identification/shortname) } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
</html>

Ajoutez l'attribut littéral xmlns="http://www.w3.org/1999/xhtml" à l'élément racine <html> dans votre requête. Que constatez-vous ? Pourquoi ?

On ne voit plus les projets !

Le préfixe par défaut est celui associé à l'espace de nom de XHTML ; or, les expressions XPath2 utilisées dans la requête XQuery n'ont pas de préfixe ; ce sont donc des éléments de l'espace de nom de XHTML qui sont donc interrogés ; les éléments des rapports de projet ne sont pas concernés ce qui donne des résultats intermédiaires vides.

xquery version "1.0";
<html xmlns="http://www.w3.org/1999/xhtml">
    <head><title>RAWEB teams summary</title></head>
    <body><h1>RAWEB teams summary</h1>
        <p>{ count(/raweb/identification/shortname) } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
</html>

Quelle différence avec XPath1 remarquez-vous ?

L'espace de nommage par défaut s'applique aux éléments sans préfixe dans les expressions XPath2, alors que les noms des éléments sans préfixes d'une expression XPath1 ne sont dans aucun espace de nommage.

Utilisez le constructeur d'élément pour construire son nom avec l'espace de nommage, et faites en sorte que la requête fonctionne. Attention à la syntaxe: element {QName(nsURI,localName)} {contentExpr} : le contenu est une expression, ce peut être une séquence d'éléments séparés par une virgule.

xquery version "1.0";
element {QName("http://www.w3.org/1999/xhtml", "html")} {
    <head><title>RAWEB teams summary</title></head>,
    <body><h1>RAWEB teams summary</h1>
        <p>{ count(/raweb/identification/shortname) } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
}

Isolez la partie comptage dans une fonction séparée (declare function ...).

xquery version "1.0";
declare function local:count-projects() as xs:integer {
    count(/raweb/identification/shortname)
};
element {QName("http://www.w3.org/1999/xhtml", "html")} {
    <head><title>RAWEB teams summary</title></head>,
    <body><h1>RAWEB teams summary</h1>
        <p>{ local:count-projects() } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
}

Isolez la fonction dans un module externe dans le namespace http://raweb.inria.fr/ et appelez le dans votre fichier de requête principal. Déposez votre module et votre requête dans le répertoire webapp/ du répertoire d'installation d'eXist de sorte qu'il puisse être appelé par la requête principale.
http://localhost:8080/exist/raweb.xqy

xquery version "1.0";
import module
    namespace raweb = "http://raweb.inria.fr/" at "raweb-module.xq";
element {QName("http://www.w3.org/1999/xhtml", "html")} {
    <head><title>RAWEB teams summary</title></head>,
    <body><h1>RAWEB teams summary</h1>
        <p>{ raweb:count-projects() } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
}
module namespace raweb = "http://raweb.inria.fr/";
declare function raweb:count-projects() as xs:integer {
    count(/raweb/identification/shortname)
};

Mapping des requêtes XQuery sur des URLs

L'objectif est de stocker les requêtes et les modules XQuery dans eXist, pour pouvoir les adresser directement par HTTP, ce qui va provoquer leur exécution.

Chargez dans la base (dans la même collection que les documents) le module et la requête précédente. Vérifiez dans les propriétés de ces ressources que le type MIME est application/xquery. Ces fichiers peuvent être édités directement pour les modifier.

Faites une requête qui importe votre module et lance le comptage avec un paramètre HTTP (le nom du projet). Pour cela, importez la librairie http://exist-db.org/xquery/request et utilisez la fonction get-parameter() pour récupérer le paramètre HTTP. Pour exécuter votre requête, adressez la directement depuis un navigateur en passant le paramètre : http://localhost:8080/raweb/rest/raweb/projects-html6.xqy?proj=aces

xquery version "1.0";
import module
    namespace raweb = "http://raweb.inria.fr/" at "raweb-module.xq";
import module
    namespace request = "http://exist-db.org/xquery/request";
element {QName("http://www.w3.org/1999/xhtml", "html")} {
    <head><title>RAWEB teams summary</title></head>,
    <body><h1>RAWEB teams summary</h1>
        <p>{ raweb:count-projects() } projects</p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                where string($project/shortname)=request:get-parameter("proj", "ACACIA")
                return <tr><td valign="top">{ string($project/shortname) }</td><td>{ $persons }</td></tr>
            }
        </table>
    </body>
}

Produisez le projet et la liste de ses membres en HTML (voir le résultat escompté ci-dessous). La liste des membres devra être donnée par une fonction implémentée dans le module externe.

Résultat escompté

RAWEB teams summary

7 projects

Count persons for project aces

Project Persons Firstname Lastname
aces 19 Michel Banâtre
Evelyne Livache
Ciaran Bryce
Paul Couderc
Jean-Paul Routeau
Frédéric Weis
Fabien Allard
Mathieu Becus
Ronan Menard
Antoine Luu
Damien Martin-Gutteriez
Mickaël LeBaillif
Xavier Le Bourdon
Pierre Duquesne
Azza Jediddi
Claude Vittoria
Cedric Mosch
Arnaud Guiton
Mazen Tlais
Code HTML
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>RAWEB teams summary</title>
    </head>
    <body>
        <h1>RAWEB teams summary</h1>
        <p>7 projects</p>
        <p> Count persons for project aces</p>
        <table border="2">
            <tr>
                <th>Project</th>
                <th>Persons</th>
                <th>Firstname</th>
                <th>Lastname</th>
            </tr>
            <tr>
                <td valign="top" rowspan="19">aces</td>
                <td valign="top" rowspan="19">19</td>
                <td>Michel</td>
                <td>Banâtre</td>
            </tr>
            <tr>
                <td>Evelyne</td>
                <td>Livache</td>
            </tr>
            <tr>
                <td>Ciaran</td>
                <td>Bryce</td>
            </tr>
            <tr>
                <td>Paul</td>
                <td>Couderc</td>
            </tr>
            <tr>
                <td>Jean-Paul</td>
                <td>Routeau</td>
            </tr>
            <tr>
                <td>Frédéric</td>
                <td>Weis</td>
            </tr>
            <tr>
                <td>Fabien</td>
                <td>Allard</td>
            </tr>
            <tr>
                <td>Mathieu</td>
                <td>Becus</td>
            </tr>
            <tr>
                <td>Ronan</td>
                <td>Menard</td>
            </tr>
            <tr>
                <td>Antoine</td>
                <td>Luu</td>
            </tr>
            <tr>
                <td>Damien</td>
                <td>Martin-Gutteriez</td>
            </tr>
            <tr>
                <td>Mickaël</td>
                <td>LeBaillif</td>
            </tr>
            <tr>
                <td>Xavier</td>
                <td>Le Bourdon</td>
            </tr>
            <tr>
                <td>Pierre</td>
                <td>Duquesne</td>
            </tr>
            <tr>
                <td>Azza</td>
                <td>Jediddi</td>
            </tr>
            <tr>
                <td>Claude</td>
                <td>Vittoria</td>
            </tr>
            <tr>
                <td>Cedric</td>
                <td>Mosch</td>
            </tr>
            <tr>
                <td>Arnaud</td>
                <td>Guiton</td>
            </tr>
            <tr>
                <td>Mazen</td>
                <td>Tlais</td>
            </tr>
        </table>
    </body>
</html>
xquery version "1.0";
import module
    namespace raweb = "http://raweb.inria.fr/" at "raweb-module2.xq";
import module
    namespace request = "http://exist-db.org/xquery/request";
let $proj := request:get-parameter("proj", "ACACIA")
return element {QName("http://www.w3.org/1999/xhtml", "html")} {
    <head><title>RAWEB teams summary</title></head>,
    <body><h1>RAWEB teams summary</h1>
        <p>{ raweb:count-projects() } projects</p>
        <p> Count persons for project {$proj} </p>
        <table border="2">
            <tr><th>Project</th><th>Persons</th><th>Firstname</th><th>Lastname</th></tr>
            {
                for $project in /raweb/identification
                let $persons := count($project//person)
                where string($project/shortname)=$proj
                return
                    for $p at $i in raweb:list-members(string($proj))
                    return <tr>
                        { if ($i=1) then
                            (<td valign="top" rowspan="{ $persons }">{ string($project/shortname) }</td>,
                            <td valign="top" rowspan="{ $persons }">{ $persons }</td>)
                            else ()
                        }
                        <td>{ string($p/firstname) }</td><td>{ string($p/lastname) }</td>
                    </tr>
            }
        </table>
    </body>
}
module namespace raweb = "http://raweb.inria.fr/";
declare function raweb:count-projects() as xs:integer {
    count(/raweb/identification/shortname)
};

declare function raweb:list-members($proj as xs:string) as element()* {

 for $project1 in /raweb/identification
                let $m := $project1/team
                where string($project1/shortname)=$proj
                return $m/participants/person

   };