Turtle ne se limite pas aux simples triplets.
Raccourcis, chemins, listes, nœuds anonymes —
le langage qui donne des ailes à RDF.
Dans le cours #2, on a découvert Turtle : sujet prédicat objet.
Dans le cours #3, on a utilisé RDF pour représenter des ontologies OWL.
Mais Turtle, c'est bien plus qu'une syntaxe à triplet unique.
Ce quatrième cours explore la face cachée de Turtle : les raccourcis qui transforment un listing verbeux en un graphe compact, les nœuds anonymes qui évitent de nommer l'innommable, les listes qui encodent des séquences, et les chemins de propriétés qui parcourent le graphe comme on suivrait une piste dans une forêt profonde.
Ce cours suppose que vous maîtrisez les bases de Turtle (cours #2, chapitre 6). On va maintenant ouvrir le capot et voir tout ce que le standard peut faire pour vous.
Le problème existentiel de tout rédacteur Turtle : on a un seul sujet
et plein de triplets à écrire. Socrate a 15 propriétés.
Vous allez recopier ex:Socrate 15 fois ? Non. Vous allez utiliser
le point-virgule et la virgule.
; : même sujet, prédicats différents
Quand plusieurs triplets partagent le même sujet,
on peut chaîner les prédicats avec ; :
Turtle # Version longue (11 lignes, on pleure) ex:Socrate rdf:type ex:Humain . ex:Socrate ex:estLeMaitreDe ex:Platon . ex:Socrate ex:estLeMaitreDe ex:Aristote . ex:Socrate ex:age "70"^^xsd:integer . ex:Socrate ex:nom "Socrate"@grc . ex:Socrate ex:nom "Σωκράτης"@grc . # Version courte (point-virgule, plus de larmes) ex:Socrate rdf:type ex:Humain ; ex:estLeMaitreDe ex:Platon , ex:Aristote ; ex:age "70"^^xsd:integer ; ex:nom "Socrate"@grc , "Σωκράτης"@grc .
, : même sujet ET même prédicatQuand en plus le prédicat est identique, la virgule permet de lister les objets sans répéter le prédicat. C'est magique.
Turtle # Avant : répétition du prédicat ex:Socrate ex:estLeMaitreDe ex:Platon . ex:Socrate ex:estLeMaitreDe ex:Aristote . ex:Socrate ex:estLeMaitreDe ex:Xenophon . # Après : point-virgule + virgule ex:Socrate ex:estLeMaitreDe ex:Platon , ex:Aristote , ex:Xenophon . # Mélange des deux : ex:Socrate rdf:type ex:Humain ; ex:estLeMaitreDe ex:Platon , ex:Aristote , ex:Xenophon ; ex:age "70"^^xsd:integer .
sujet prédicat1 objet1 , objet2 ; prédicat2 objet3 .a : sucre pour rdf:type
Le prédicat rdf:type (qui signifie « est un ») est tellement
courant qu'il a sa propre notation abrégée : a.
Turtle # Version classique ex:Socrate rdf:type ex:Humain . # Version sucrée (les deux sont strictement identiques) ex:Socrate a ex:Humain . # Avec les raccourcis : ex:Socrate a ex:Humain ; ex:nom "Socrate"@grc . # On peut enchaîner les types : ex:Socrate a ex:Humain , ex:Philosophe , ex:Athénien .
a vient de la syntaxe
N3 (Notation 3), une extension de Turtle inventée par
Tim Berners-Lee lui-même. L'idée : puisque « est un » est le prédicat
le plus utilisé dans tous les graphes RDF, autant lui
donner la lettre la plus courte de l'alphabet. Logique.
En N3, a signifie littéralement « is a ».
L'anglais au service de la brièveté.
Réécrivez ces triplets en deux lignes avec ;, ,, a :
:Platon rdf:type :Humain . :Platon ex:age 80 . :Platon ex:nom "Platon" . :Platon ex:nom "Πλάτων" .
Le a ne peut être utilisé que comme prédicat.
Vous ne pouvez pas écrire ex:Humain a ex:Socrate
pour dire « Humain a pour instance Socrate ».
Ça ne marche pas. La vie est injuste.
Parfois, un nœud existe, mais on ne veut pas (ou on ne peut pas) lui donner d'URI. C'est une ressource sans nom. En RDF, on appelle ça un nœud blanc (blank node).
[] avec contenu
Un nœud anonyme se note avec des crochets [].
On peut y mettre des propriétés à l'intérieur :
Turtle # Un Humain sans nom (littéralement) ex:Socrate ex:aPourEleve [ a ex:Humain ; ex:nom "Inconnu"@fr ] . # Ceci crée deux triplets avec un blank node intermédiaire : ex:Socrate ex:aPourEleve _:b1 . _:b1 rdf:type ex:Humain . _:b1 ex:nom "Inconnu"@fr .
Les crochets ouvrent un scope : tout ce qui est à l'intérieur décrit un nœud anonyme qui est l'objet du triplet parent.
[] vide : nœud anonyme sans propriétéParfois on veut juste dire « il existe une relation » sans décrire la cible :
Turtle # Socrate a un maître (qu'on ne connaît pas / qu'on ne décrit pas) ex:Socrate ex:aPourMaitre [] . # Strictement équivalent à : ex:Socrate ex:aPourMaitre _:b1 .
On peut aussi décrire un nœud anonyme en le réutilisant avec
[] comme sujet. Turtle permet d'utiliser des crochets
des deux côtés :
Turtle # Un événement anonyme (sans URI) [ a ex:Evenement ; ex:type "naissance"@fr ; ex:lieu ex:Athenes ; ex:date "-470"^^xsd:gYear ] ex:aPourActeur ex:Socrate . # Noter le sujet entre crochets, suivi du prédicat-objet
_:b1,
_:b2, etc. Mais ces identifiants sont locaux
à chaque fichier : un _:b1 dans un fichier n'est pas le même
que _:b1 dans un autre. La solitude du blank node.
| Situation | Blank node ? | Pourquoi |
|---|---|---|
| Une personne a une adresse complète qu'on ne cite jamais ailleurs | ✅ Oui | L'adresse n'a pas d'identité propre |
| Un livre a un auteur qu'on retrouve dans d'autres livres | ❌ Non | Il a besoin d'une URI pour être référencé |
| Un triplet dont l'objet est juste « il existe » | ✅ Oui | Pas besoin de décrire plus |
| Un événement historique unique qu'on décrit en détail | ⚠️ Débat | Si vous voulez le lier depuis d'autres données, donnez-lui une URI |
Les [] peuvent s'imbriquer :
:Socrate :maitreDe [ :nom "Platon" ; :age 80 ] .
Un blank node dans un blank node.
C'est des tortues jusqu'en bas.
Combinaison avec les listes :
:Socrate :eleves ( [ :nom "Platon" ] [ :nom "Aristote" ] ) .
RDF est un graphe, pas un arbre. En théorie, il n'y a pas d'ordre entre les triplets. Mais parfois, l'ordre est important : les ingrédients d'une pizza, les enfants dans l'ordre de naissance, les étapes d'une recette. Pour ça, RDF a inventé les listes.
rdf:first / rdf:restUne liste RDF est une liste chaînée à la Lisp. Chaque élément pointe vers le suivant :
Turtle # Une liste de deux ingrédients (en interne RDF) ex:Margherita ex:ingredients _:l1 . _:l1 rdf:first ex:Mozzarella . _:l1 rdf:rest _:l2 . _:l2 rdf:first ex:Tomate . _:l2 rdf:rest rdf:nil .
(...)Fort heureusement, personne n'écrit les listes RDF à la main. Turtle propose une syntaxe parenthésée :
Turtle # La même liste, en 10 fois moins de lignes ex:Margherita ex:ingredients ( ex:Mozzarella ex:Tomate ) . # Liste vide ex:Platon ex:enfants () . # Liste avec des littéraux ex:Socrate ex:oeuvres ( "Apologie" "Criton" "Phédon" "République" ) . # Liste avec des nœuds anonymes ex:Socrate ex:eleves ( [ ex:nom "Platon" ] [ ex:nom "Aristote" ] ) .
Il y a une différence fondamentale entre une liste et une série de triplets avec le même prédicat :
Turtle # ENSEMBLE (pas d'ordre, pas de notion de totalité) ex:Socrate ex:eleve ex:Platon , ex:Aristote . # LISTE (ordre préservé, notion de « ces éléments et pas d'autres ») ex:Socrate ex:eleves ( ex:Platon ex:Aristote ) . # Sémantique différente : # La 1ʳᵉ version dit : Socrate a des élèves (parmi d'autres peut-être) # La 2ᵉ version dit : la liste des élèves de Socrate EST [Platon, Aristote]
rdf:first/rdf:rest
est directement inspirée des cons et cdr
du langage Lisp (1958). Le W3C a littéralement recyclé une structure de données
de 40 ans pour le web sémantique. Quand on a une bonne idée,
on la garde. Et rdf:nil, c'est la version RDF de nil
en Lisp. Le web sémantique doit beaucoup à John McCarthy
(inventeur de Lisp), même s'il ne l'a jamais su.
Une liste de n éléments génère 2n+1 triplets RDF en interne (n × first + n × rest + rdf:nil). Le confort syntaxique a un coût.
On peut avoir des listes dans des listes :
ex:menu ( (ex:entrée ex:plat) (ex:dessert ex:café) ) .
Les parenthèses s'emboîtent.
En SPARQL, on peut déconstruire les listes
avec ?l rdf:first ?e ou utiliser des
extensions comme SPARQL 1.2 pour
déstructurer directement.
Les littéraux sont les valeurs concrètes : chaînes, nombres, dates, booléens. En Turtle, ils peuvent être enrichis d'étiquettes de langue et de types de données.
@Un même nom dans différentes langues, c'est le quotidien du web multilingue :
Turtle ex:Socrate ex:nom "Socrate"@fr , "Socrates"@en , "Σωκράτης"@grc , "سقراط"@ar . # Les étiquettes suivent le standard BCP47 # @fr = français, @en = anglais, @grc = grec ancien, @ar = arabe # On peut préciser la région : @en-US, @fr-CA, @zh-Hans
^^Pour préciser qu'une valeur est un entier, une date, un booléen :
Turtle # Littéraux typés ex:Socrate ex:age "70"^^xsd:integer . ex:Platon ex:anneeNaiss "-428"^^xsd:gYear . ex:Aristote ex:estVivant "false"^^xsd:boolean . ex:Socrate ex:taille "1.70"^^xsd:decimal . # Les types XSD les plus courants : # xsd:string, xsd:integer, xsd:decimal, xsd:boolean # xsd:date, xsd:dateTime, xsd:gYear, xsd:gYearMonth # xsd:float, xsd:double, xsd:hexBinary, xsd:base64Binary
Turtle offre des raccourcis syntaxiques pour les types courants :
Turtle # Entiers (xsd:integer implicite) ex:Socrate ex:age 70 . # Décimaux (xsd:decimal implicite) ex:Socrate ex:taille 1.70 . # Booléens (xsd:boolean implicite) ex:Socrate ex:mortel true . # ou false # Chaîne simple (xsd:string implicite) ex:Socrate ex:nom "Socrate" . # Identiques en interne : ex:Socrate ex:age "70"^^xsd:integer . # et ex:Socrate ex:age 70 . # produisent exactement le même triplet
Pour les textes longs, Turtle propose des triples guillemets :
Turtle # Chaîne longue (peut contenir des retours à la ligne) ex:Socrate ex:citation """ Je ne sais qu'une chose : c'est que je ne sais rien. — Socrate """@fr . # Échappement dans les chaînes ex:Socrate ex:citation "Il disait : \"Connais-toi toi-même\""@fr .
1 (sans guillemets)
est un entier. "1" (avec guillemets) est une chaîne.
"1"^^xsd:integer est un entier au format chaîne.
"1"^^xsd:string est une chaîne explicitement typée.
Ces trois valeurs sont différentes en RDF.
Oui, le web sémantique fait la différence entre le nombre 1
et le caractère « 1 ». C'est taquin, mais précis.
BCP47 permet des langues comme @grc (grec ancien),
@la (latin), @cu (vieux slave),
@non (vieux norrois). Parfait pour décrire
les sagas islandaises en RDF.
xsd:decimal est exact (base 10).
xsd:float est approché (base 2).
1.70 en Turtle est un xsd:decimal
exact. Pas d'arrondi. Votre banquier approuve.
Les chemins de propriétés (property paths) sont l'un des ajouts les plus puissants de SPARQL 1.1 (et Turtle les supporte pour la définition de graphes). Ils permettent de parcourir le graphe sans connaître sa structure exacte.
| Opérateur | Syntaxe | Sens | Exemple SPARQL |
|---|---|---|---|
| Séquence | / |
p1 puis p2 (composition) | ex:maitreDe / ex:maitreDe |
| Union | | |
p1 ou p2 (alternative) | ex:maitreDe | ex:eleve |
| Un ou plus | + |
p1, p1/p1, p1/p1/p1… | ex:ancetreDe+ |
| Zéro ou plus | * |
p1, p1/p1… ou juste soi-même | ex:influence* |
| Optionnel | ? |
p1 ou rien (zéro ou un) | ex:surnom? |
| Inverse | ^ |
Parcourir dans le sens inverse | ^ex:maitreDe |
| Négation | ! |
Tout sauf p1 | !ex:maitreDe |
SPARQL # 1. Qui est le maître du maître de X ? (grand-maître) # Séquence : ex:maitreDe / ex:maitreDe SELECT ?grandMaitre ?eleve WHERE { ?grandMaitre ex:maitreDe / ex:maitreDe ?eleve . } # → (Socrate → Platon → Aristote) ⇒ Socrate est grand-maître d'Aristote # 2. Tous les ancêtres académiques (chaîne de maîtres) # Un ou plus : ex:maitreDe+ SELECT ?ancetre ?descendant WHERE { ?ancetre ex:maitreDe+ ?descendant . } # → remonte toute la chaîne : maître, maître du maître, etc. # 3. Toute la famille académique (maîtres + élèves) # Union : ex:maitreDe | ^ex:maitreDe SELECT ?x ?y WHERE { ?x ( ex:maitreDe | ^ex:maitreDe ) ?y . } # → toutes les paires maître-élève, dans les deux sens # 4. Qui est influencé par Socrate (directement ou indirectement) ? # Zéro ou plus : ex:influence* SELECT ?influence WHERE { ex:Socrate ex:influence* ?influence . } # → Socrate lui-même, ses élèves directs, ses élèves d'élèves, ... # 5. Les Humains qui ont un surnom (optionnel) SELECT ?h ?surnom WHERE { ?h a ex:Humain . ?h ex:surnom? ?surnom . } # → tous les Humains, avec surnom si présent (None sinon) # 6. L'inverse : qui est élève de qui ? SELECT ?eleve ?maitre WHERE { ?eleve ^ex:maitreDe ?maitre . } # Strictement équivalent à : ?maitre ex:maitreDe ?eleve
^ex:employeDe / ex:dirige / ex:sousTraitant+
peut révéler des chaînes de responsabilité dans une organisation
que vous n'auriez jamais trouvées à la main.
* et +
peuvent être très lents sur des grands graphes.
Un ex:connait+ sur un graphe social de 10⁶ nœuds,
c'est potentiellement un chemin de 10⁶ de long.
À utiliser avec précaution. Comme une tronçonneuse.
Les chemins de propriétés ne sont pas réservés à SPARQL. En Turtle, on peut les utiliser dans les définitions de SHACL (Shapes Constraint Language) ou de OWL 2 pour décrire des propriétés complexes :
Turtle # SHACL : une shape qui utilise un property path ex:AncetreShape a sh:NodeShape ; sh:targetClass ex:Humain ; sh:property [ sh:path ( ex:maitreDe [ sh:zeroOrMorePath ex:maitreDe ] ) ; sh:minCount 1 ] . # OWL 2 : propriété transitive (chemin de longueur ≥ 1) ex:ancetre a owl:TransitiveProperty . # OWL 2 : propriété composée (chemin séquence) ex:grandMaitre a owl:ObjectProperty ; owl:propertyChainAxiom ( ex:maitreDe ex:maitreDe ) .
Les property paths de SPARQL sont l'équivalent des prédicats
récursifs en Prolog. ex:ancetre+ correspond
à la règle ancetre(X,Y) :- parent(X,Y). ancetre(X,Y) :- parent(X,Z), ancetre(Z,Y).
Même puissance, syntaxe différente.
* inclut le nœud lui-même. Utile pour les requêtes
qui veulent « tout le monde, y compris soi-même ».
Exemple : ex:connait* → tout le réseau social
accessible, y compris la personne initiale.
!ex:maitreDe signifie « toute propriété sauf maitreDe ».
Attention : ça matche n'importe quelle autre propriété.
C'est rarement ce qu'on veut. À utiliser avec des
FILTER ou dans des contextes SHACL.
Au chapitre 3, on a vu les listes RDF (rdf:first/rdf:rest).
Mais RDF propose d'autres structures pour grouper des ressources :
les conteneurs et les collections.
Avant les listes, RDF avait des conteneurs (un peu l'ancêtre un peu poussiéreux). Ils existent toujours dans le standard :
| Type | Signification | Ordre | Doublons |
|---|---|---|---|
rdf:Bag | Ensemble non ordonné | ❌ Non | ✅ Oui |
rdf:Seq | Séquence ordonnée | ✅ Oui | ✅ Oui |
rdf:Alt | Alternatives (choix) | ✅ Oui (priorité) | ❌ Non |
Turtle # rdf:Seq — une séquence (avec indices rdf:_1, rdf:_2, …) ex:Socrate ex:oeuvres [ a rdf:Seq ; rdf:_1 "Apologie" ; rdf:_2 "Criton" ; rdf:_3 "Phédon" ] . # rdf:Bag — un sac (pas d'ordre, avec rdf:_1, rdf:_2, …) ex:Socrate ex:eleves [ a rdf:Bag ; rdf:_1 ex:Platon ; rdf:_2 ex:Aristote ; rdf:_3 ex:Xenophon ] . # rdf:Alt — des alternatives (la première est la valeur par défaut) ex:Platon ex:ecole [ a rdf:Alt ; rdf:_1 ex:Academie ; rdf:_2 ex:Lycee ] .
rdf:Bag,
rdf:Seq, rdf:Alt étaient la première tentative
du W3C pour représenter des groupes en RDF (1999). Mais ils ont
plusieurs défauts : (1) pas de fermeture — on peut toujours ajouter
rdf:_4 sans changer le type ; (2) pas de restriction sur
les indices — rdf:_42 est valide même s'il n'y a que 3 éléments ;
(3) ils sont lourds à parser. Les listes
(rdf:first/rdf:rest) sont arrivées plus tard
et sont devenues la méthode recommandée. Les conteneurs sont
comme les « pantalons à taille haute » de RDF : ils existent encore,
mais plus personne ne les utilise vraiment.
Au quotidien, vous utiliserez presque exclusivement la syntaxe
parenthésée vue au chapitre 3. C'est plus propre, plus court,
et sémantiquement plus clair. Les conteneurs
rdf:Bag/rdf:Seq sont surtout utiles quand
vous importez des données d'anciens systèmes RDF.
Turtle # Recommandé : liste RDF (propre, fermée, ordonnée) ex:Socrate ex:oeuvres ( "Apologie" "Criton" "Phédon" "République" ) . # Acceptable : rdf:Seq (si vous avez besoin de compatibilité ascendante) ex:Socrate ex:oeuvres [ a rdf:Seq ; rdf:_1 "Apologie" ; rdf:_2 "Criton" ] . # À éviter : rdf:Bag (sauf si vraiment pas d'ordre) ex:Socrate ex:oeuvres [ a rdf:Bag ; rdf:_1 "Apologie" ; rdf:_2 "Criton" ] .
Listes : ordre garanti, fermeture, syntaxe courte, mais 2n+1 triplets. rdf:Seq : ordre garanti, pas de fermeture, n+2 triplets. rdf:Bag : pas d'ordre, pas de fermeture, n+2 triplets.
Si vous créez des données aujourd'hui, utilisez les listes
((...)). Si vous lisez des données historiques,
préparez-vous à gérer les trois formats.
L'interopérabilité, c'est la guerre.
Turtle (et RDF) est très permissif : n'importe quel sujet peut avoir n'importe quelle propriété avec n'importe quelle valeur. C'est la liberté. Mais la liberté sans règles, c'est le chaos.
SHACL (Shapes Constraint Language) est le langage qui permet de valider un graphe RDF. C'est comme une grammaire pour vos données : « un Humain DOIT avoir un nom, PEUT avoir un âge, et son âge DOIT être un entier. »
Turtle # Définition d'une forme (shape) pour les Humains ex:HumainShape a sh:NodeShape ; sh:targetClass ex:Humain ; # s'applique à tous les Humain sh:property [ sh:path ex:nom ; sh:minCount 1 ; # au moins 1 nom sh:maxCount 3 ; # au plus 3 noms sh:datatype xsd:string ; # doit être une chaîne sh:minLength 2 ; sh:maxLength 100 ] ; sh:property [ sh:path ex:age ; sh:datatype xsd:integer ; sh:minInclusive 0 ; sh:maxInclusive 150 ] ; sh:property [ sh:path ex:estLeMaitreDe ; sh:class ex:Humain # l'objet doit être un Humain ] .
Avec cette shape, tout graphe RDF contenant des
ex:Humain peut être validé : chaque Humain doit avoir
entre 1 et 3 noms (chaînes de 2 à 100 caractères), un âge entier
entre 0 et 150, et ses élèves doivent être des Humains.
SHACL supporte les chemins de propriétés dans les contraintes. Vous pouvez exprimer des règles complexes :
Turtle # Règle : un Humain ne peut pas être son propre ancêtre ex:AntiHeritage a sh:NodeShape ; sh:targetClass ex:Humain ; sh:property [ sh:path ( ex:maitreDe+ ) ; # chemin « un ou plus » sh:maxCount 0 ; # interdit sh:message "Pas d'auto-heritage academique"@fr ] . # Règle : un maître doit avoir au moins un élève ex:MaitreShape a sh:NodeShape ; sh:targetClass ex:Humain ; sh:property [ sh:path ( ^ex:estLeMaitreDe ) ; # inverse sh:minCount 1 ; sh:message "Un maitre doit avoir au moins un eleve"@fr ] .
Validation par regex :
sh:pattern "^[A-Z]" — le nom doit commencer
par une majuscule. Utile pour les noms propres.
Un nœud doit être conforme à une autre shape :
sh:node ex:AdresseShape.
Permet de valider des structures complexes.
Contrainte sur le nombre de valeurs ayant une certaine forme : « exactement 2 élèves doivent être des philosophes ».
Turtle est souvent présenté comme « la syntaxe simple de RDF ».
J'espère que ce cours vous a montré qu'il est bien plus que ça.
Avec ses raccourcis (;, ,, a),
ses nœuds anonymes ([]),
ses listes ((...)),
ses littéraux typés (^^, @),
et ses chemins (/, |, +, *),
Turtle est un langage riche et expressif.
; et ,.( ... ).Les property paths sont encore plus puissants en SPARQL.
Ajoutez-y FILTER, OPTIONAL,
CONSTRUCT, SERVICE — vous avez
un langage de requête complet.
SHACL permet des règles d'inférence (SHACL-Rule), des rapports d'erreur détaillés, et des formes paramétrées. C'est le niveau supérieur.
Le même RDF, mais en JSON. Pour les développeurs web qui trouvent que Turtle c'est bien mais que tout est en JSON de toute façon. Même graphe, syntaxe différente.
L'extension de Turtle par Tim Berners-Lee avec des règles logiques. C'est Turtle + Prolog dans le même fichier. Le Graal du web sémantique.
Des triplets qui parlent d'autres triplets (réification simplifiée). « Socrate a dit que Platon est un Homme, avec une confiance de 0.9. »
Publier et éditer des données RDF via HTTP. Créez votre propre API REST sémantique. Turtle comme format de sérialisation.
| Concept | Norme / Référence |
|---|---|
| Turtle (spec) | W3C Recommendation — Turtle |
| SPARQL 1.1 Property Paths | SPARQL 1.1 Query Language §9 |
| RDF 1.1 Concepts | W3C — RDF 1.1 |
| SHACL | W3C — Shapes Constraint Language |
| RDF Lists | RDF Schema §5.3 |
| BCP47 (langues) | IETF — Tags for Identifying Languages |