Interrogez une BD native XML avec XQuery.
Organisez vos requêtes XQuery dans des modules.
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.
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.
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>
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.
7 projects
Project | Persons |
---|---|
ACACIA | 26 |
aces | 19 |
ADEPT | 16 |
Alchemy | 37 |
ALCOVE | 35 |
Algo | 15 |
AlGorille | 16 |
<?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) };
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.
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 |
<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 };