On a vu précédemment comment utiliser l'API Sesame depuis un…
Exemples de requêtes SPARQL sur le modèle SKOS
SKOS Play utilise du SPARQL pour lire les fichiers de thesaurus qu’on lui envoie, en voici quelque-unes pour illustrer son fonctionnement et peut-être vous servir de point de départ dans l’écriture de vos propres requêtes ou dans la découverte des possibilités de ce langage d’interrogation. SKOS, le modèle de systèmes d’organisation de connaissances et de terminologies du web sémantique, a l’avantage d’être assez simple, cependant les requêtes pour l’exploiter correctement sont plus compliquées que ce qu’on pourrait croire au premier abord. Toutes les requêtes ci-dessous exploitent les opérateurs SPARQL 1.1.
Concept Schemes et Concepts de premier niveau
L’interface de SKOS Play commence par lister les concepts schemes triés par langue :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT ?conceptScheme
WHERE {
?conceptScheme a skos:ConceptScheme .
OPTIONAL {
?conceptScheme skos:prefLabel ?prefLabel .
FILTER(lang(?prefLabel) = ‘fr’)
}
}
ORDER BY ?prefLabel[/sql]
On utilise un OPTIONAL pour le cas où le libellé du concept scheme ne serait pas connu, et on tri par langue. Notez par ailleurs que la requête s’attend à un skos:prefLabel. SKOS ne dit rien sur le nommage des concept schemes, on peut trouver aussi bien du skos:prefLabel que du rdfs:label que du dcterms:title, aucune de ces 3 propriétés ne définissant un domaine.
Ensuite, on essaie de trouver les langues utilisées. Pour cela on regarde les langues utilisées sur les prefLabel, altLabels et hiddenLabels :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT DISTINCT (lang(?label) AS ?lang)
WHERE {
?concept a skos:Concept .
?concept skos:prefLabel|skos:altLabel|skos:hiddenLabel ?label .
}[/sql]
On utilise l’opérateur ‘|’ (alternative) des property path SPARQL 1.1 pour sélectionner les prefLabel ou les altLabels ou hiddenLabels, et on sélectionne la liste dédoublonnée de leurs langues. A noter que les valeurs sans langues (ce qui ne devrait pas arriver en SKOS en principe), ne sont pas traitées par la query.
Une fois un concept scheme sélectionné, on a souvent besoin de trouver les concepts de premier niveau de ce scheme. 2 solutions : première solution, interroger la propriété hasTopConcept ou isTopConceptOf du scheme :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT DISTINCT ?scheme ?top
WHERE {
?scheme skos:hasTopConcept|^skos:isTopConceptOf ?top .
}[/sql]
Ici on combine l’opérateur d’alternative ‘|’ avec celui d’inverse ‘^’ pour sélectionner soit les skos:hasTopConcept du scheme vers le concept soit les isTopConceptOf du concept vers le scheme; comme SKOS Play ne connait pas a priori la « tête » des thesaurus à traiter, il faut mieux être prudent et essayer de lire tout ce qu’on peut. La variable ?scheme est « bindée » avec le mécanisme de binding de variables Sesame à la valeur du scheme sélectionnée.
Deuxième solution, si le vocabulaire contrôlé n’indique aucun concept de premier niveau explicitement, et si donc la première requête ne ramène rien, c’est de récupérer les concepts sans parent dans la hiérarchie broader/narrower :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT DISTINCT ?concept
WHERE {
?concept a skos:Concept .
?concept skos:inScheme ?scheme .
FILTER NOT EXISTS { ?concept skos:broader|^skos:narrower ?parent }
}[/sql]
On utilise la construction SPARQL 1.1 « FILTER NOT EXISTS » pour vérifier la non-existence d’un lien broader du concept vers un parent, ou bien d’un lien narrower d’un parent vers le concept (là encore, on est prudent et on teste toutes les possibilités). La variable scheme est bindée à la bonne valeur avec le mécanisme de binding de variables Sesame.
Quelques requêtes sur les concepts
Une fois trouvé un concept scheme et les concepts de premier niveau, c’est parti pour le traitement des concepts ! on va essentiellement avoir besoin de faire deux choses : premièrement, lire les libellés d’un concept :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT ?label
WHERE {
?concept a skos:Concept .
?concept skos:prefLabel ?label .
FILTER(lang(?label) = ‘fr’) .
}[/sql]
On sélectionne les prefLabels dans la langue voulue. Dans l’application, on gère un cache de ces libellés pour éviter de les requêter plusieurs fois. On pourrait aussi utiliser l’opérateur GROUP_CONCAT pour concaténer plusieurs valeurs entre elles (ce qui ne devrait jamais arriver en SKOS pour le prefLabel, tous les concepts devrait avoir au maximum un prefLabel par langue).
Deuxième opération essentielle, naviguer dans la hiérarchie des concepts. Pour cela on exploite les liens skos:broader et skos:narrower :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT ?narrower
WHERE {
?concept skos:narrower|^skos:broader ?narrower .
?narrower skos:inScheme <http://uri.du.concept.scheme> .
}[/sql]
On retrouve la même combinaison de l’opérateur d’alternative ‘|’ et de celui d’inverse ‘^’ pour interroger d’un seul coup les broaders et les narrowers. On filtre sur l’URI du concept scheme pour s’assurer qu’on ne sélectionne que les enfants qui restent dans le même ensemble de concepts.
Un peu plus compliqué !
L’affichage alphabétique du thesaurus dans SKOS Play demande de sélectionner tous les prefLabels et les altLabels, en gardant la correspondance entre un altLabel et le prefLabel pour pouvoir faire un lien, et en prenant en compte les cas où le concept n’a pas de libellé dans la langue demandé. Voici cette belle requête :
[sql]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT ?label ?prefLabel ?concept
WHERE {
?concept a skos:Concept .
{
{
# les prefLabels des concepts
?concept skos:prefLabel ?label
FILTER(lang(?label) = ‘fr’)
} UNION {
# les altLabels et leur pref
?concept skos:altLabel ?label .
?concept skos:prefLabel ?prefLabel .
FILTER(lang(?label) = ‘fr’ && lang(?prefLabel) = ‘fr’)
} UNION {
# pas de prefLabel dans la langue ! on prend l’URI
?concept a skos:Concept .
FILTER NOT EXISTS {
?concept skos:prefLabel ?nopref
FILTER(lang(?nopref) = ‘fr’)
BIND(str(?concept) as ?label)
}
}
}
}[/sql]
3 clauses se combinent ici avec UNION pour sélectionner plusieurs cas : lignes 7 à 9, pour sélectionner le prefLabel, sans histoire de « correspondance » avec un prefLabel; ligne 11 à 14, pour sélectionner tous les altLabels et leur correspondance avec le prefLabel; ligne 15 à 21, pour traiter le cas où le prefLabel n’existe pas dans la langue demandée. On utilise un BIND pour ramener l’URI du concept comme un label dans ce cas. Pas mal, non ?
Enfin, je reproduis ici une requête dont SKOS Play ne se sert pas (encore ?) pour faire de l’autocompletion sur des libellés SKOS, publiée de l’autre côté de la terre, quelque part en Australie, par Ben Chadwick qui travaille sur le thesaurus SCOT :
[sql gutter= »false »]PREFIX skos: <http://www.w3.org/2004/02/skos/core#>SELECT DISTINCT ?uri ?label
WHERE {
?uri a skos:Concept.
?uri skos:prefLabel ?label.
FILTER (lang(?label) = “en”).
?uri skos:prefLabel|skos:altLabel|skos:hiddenLabel ?lab.
FILTER regex (str($lab), “ach”, ‘i’)
FILTER (lang (?lab) = “en”).
BIND (STRLEN(STRBEFORE (str(?lab), “ach”)) AS ?place).
BIND (STRLEN(STR(?lab)) AS ?len)
}
ORDER BY ?place ?len ?lab
LIMIT 10[/sql]
Pratique pour implémenter un composant d’autocompletion sur les libellés d’un thesaurus.
C’est intéressant de voir les requêtes qui se cachent derrière SKOS play!, je connaissais pas les opérateurs | et |^ au niveau des prédicats. Idée : SKOS play! ne pourrait-il pas être décliné en un « RDF Play! » générique afin de visualiser tout type de données RDF ?
La réalité de l’application enrobe ces requêtes d’une couche de complexité supplémentaire : les requêtes sont générées dynamiquement en fonction du choix de langue par exemple, la récupération des libellés passe par un cache, il faut retrier les résultats pour ne pas que les caractères accentués se retrouvent en fin de liste, etc.
Visualiser des données RDF de façon générique ne rendrait pas grand chose, juste afficher un tableau de triplets; ou alors il faut donner la possibilité à l’utilisateur de choisir comment il veut afficher quel prédicat (celui-là en hiérarchie, celui-là en liste, celui-là c’est le titre, etc.).
Pour les hiérarchies j’aimerais bien utiliser des colonnes de Miller : http://www.laurencenoel.fr/wp/?p=4