Les fondamentaux de RDF : Triplets, Noeuds et Serialisation
Ce notebook explore en detail le modèle de données RDF (Resource Description Framework), la brique fondamentale du Web sémantique. Nous etudierons l’anatomie des triplets, les différents types de noeuds, la gestion des espaces de noms et les principaux formats de serialisation.
Objectifs d’apprentissage
A la fin de ce notebook, vous saurez : 1. Decrire l’anatomie d’un triplet RDF (sujet, predicat, objet) et sa signification 2. Distinguer et créer les trois types de noeuds : URI, Blank et Literal 3. Configurer des espaces de noms et des prefixes avec NamespaceMapper 4. Serialiser un graphe RDF dans les 4 formats principaux (NTriples, Turtle, RDF/XML, JSON-LD)
Concepts cles
Concept
Description
Triplet RDF
Unite fondamentale : assertion (sujet, predicat, objet)
IUriNode
Noeud identifie par un URI, represente une ressource
IBlankNode
Noeud anonyme sans identifiant global
ILiteralNode
Noeud contenant une valeur (texte, nombre, date)
Espace de noms
Prefixe qui abrege les URIs longues
Serialisation
Encodage d’un graphe RDF dans un format textuel
Prerequis
Notebook SW-1-Setup complete
Duree estimee : 45 minutes
0. Installation de dotNetRDF
Comme dans le notebook précédent, nous commencons par charger la bibliotheque dotNetRDF et ses espaces de noms principaux.
// Installation du package dotNetRDF (version épinglée pour reproductibilité)#r "nuget: dotNetRDF, 3.2.1"using VDS.RDF;using VDS.RDF.Parsing;using VDS.RDF.Writing;using System;using System.IO;using StringWriter = System.IO.StringWriter;Console.WriteLine("dotNetRDF 3.2.1 charge avec succes.");Console.WriteLine("Espaces de noms disponibles : VDS.RDF, VDS.RDF.Parsing, VDS.RDF.Writing");
Installed Packages
dotNetRDF, 3.2.1
dotNetRDF 3.2.1 charge avec succes.
Espaces de noms disponibles : VDS.RDF, VDS.RDF.Parsing, VDS.RDF.Writing
Interpretation
La cellule ci-dessus installe et importe les trois espaces de noms principaux de dotNetRDF :
Espace de noms
Contenu
Usage dans ce notebook
VDS.RDF
Classes de base (Graph, Triple, INode)
Creation de graphes et de noeuds
VDS.RDF.Parsing
Parsers (TurtleParser, RdfXmlParser)
Lecture de données RDF
VDS.RDF.Writing
Writers (NTriplesWriter, CompressingTurtleWriter)
Serialisation de graphes
Note technique : La version 3.2.1 est epinglee pour garantir la reproductibilite. L’alias using StringWriter = System.IO.StringWriter evite un conflit de noms avec VDS.RDF.Writing.StringWriter.
1. Les triplets RDF
Anatomie d’un triplet
En RDF, toute information est exprimee sous forme d’un triplet compose de trois éléments :
RDF (Resource Description Framework) est le standard W3C de description de données du Web Sémantique, défini a l’origine par Lassila & Swick (1999) et raffine dans RDF 1.1 (Cyganiak, Wood & Lanthaler, W3C Recommendation 2014). Le modèle de triplet expose ci-dessous en est la brique fondamentale.
(Sujet) ---[Predicat]---> (Objet)
Élément
Rôle
Types autorises
Sujet
La ressource decrite
URI Node, Blank Node
Predicat
La relation / propriete
URI Node uniquement
Objet
La valeur ou la ressource liee
URI Node, Blank Node, Literal Node
Chaque triplet est une assertion qui exprime une relation entre deux entites :
Phrase naturelle
Sujet
Predicat
Objet
“dotNetRDF dit Hello World”
http://www.dotnetrdf.org
http://example.org/says
"Hello World"
“Alice connait Bob”
ex:Alice
foaf:knows
ex:Bob
“Alice a 30 ans”
ex:Alice
foaf:age
"30"^^xsd:integer
Un graphe RDF est un ensemble de triplets. Visuellement, il forme un graphe oriente :
// Création d'un graphe vide et de nos premiers tripletsIGraph g =newGraph();// Création des noeuds URI (sujet et prédicat)IUriNode dotNetRDF = g.CreateUriNode(UriFactory.Create("http://www.dotnetrdf.org"));IUriNode says = g.CreateUriNode(UriFactory.Create("http://example.org/says"));// Création des noeuds literals (objets)ILiteralNode helloWorld = g.CreateLiteralNode("Hello World");ILiteralNode bonjourMonde = g.CreateLiteralNode("Bonjour tout le Monde","fr");// Assertion des triplets dans le grapheg.Assert(newTriple(dotNetRDF, says, helloWorld));g.Assert(newTriple(dotNetRDF, says, bonjourMonde));Console.WriteLine($"Nombre de triplets dans le graphe : {g.Triples.Count}");Console.WriteLine();// Affichage détaillé de chaque tripletforeach(Triple t in g.Triples){ Console.WriteLine($" Sujet: {t.Subject}"); Console.WriteLine($" Predicat: {t.Predicate}"); Console.WriteLine($" Objet: {t.Object}"); Console.WriteLine();}
Nombre de triplets dans le graphe : 2
Sujet: http://www.dotnetrdf.org/
Predicat: http://example.org/says
Objet: Hello World^^http://www.w3.org/2001/XMLSchema#string
Sujet: http://www.dotnetrdf.org/
Predicat: http://example.org/says
Objet: Bonjour tout le Monde@fr
Interpretation : Premier triplet RDF
Nous avons créé un graphe contenant deux triplets qui partagent le même sujet et le même predicat, mais différent par l’objet :
Triplet
Sujet
Predicat
Objet
1
<http://www.dotnetrdf.org>
<http://example.org/says>
"Hello World"
2
<http://www.dotnetrdf.org>
<http://example.org/says>
"Bonjour tout le Monde"@fr
Points cles : 1. g.Assert() ajoute un triplet au graphe. C’est l’equivalent de “declarer un fait” dans la base de connaissances. 2. UriFactory.Create() interne les URIs pour optimiser la memoire et accelerer les comparaisons d’egalite. 3. Le second literal possede une etiquette de langue ("fr"), ce qui en fait un noeud distinct du premier.
Note : La sortie affichée par les proprietes Subject, Predicate, Object est une representation de debogage, pas une syntaxe RDF standard. Pour une serialisation conforme, il faut utiliser un Writer (cf. section 4).
2. Types de noeuds
RDF définit trois types de noeuds fondamentaux, representes dans dotNetRDF par des interfaces spécifiques.
Type
Interface dotNetRDF
Notation RDF
Rôles autorises
URI Node
IUriNode
<http://...>
Sujet, Predicat, Objet
Blank Node
IBlankNode
_:id
Sujet, Objet
Literal Node
ILiteralNode
"valeur"
Objet uniquement
2.1 URI Nodes (IUriNode)
Les noeuds URI identifient de maniere unique et globale une ressource sur le Web. Ils constituent le coeur de RDF : chaque ressource, chaque propriete est identifiee par un URI.
Deux méthodes de creation existent dans dotNetRDF.
IGraph g =newGraph();// Méthode 1 : URI absolue avec UriFactoryIUriNode person = g.CreateUriNode(UriFactory.Create("http://example.org/person/Alice"));Console.WriteLine($"URI absolue : {person.Uri}");// Méthode 2 : URI avec préfixe (QName) - nécessite un namespace déclarég.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));IUriNode knows = g.CreateUriNode("ex:knows");Console.WriteLine($"URI via prefixe : {knows.Uri}");// Les deux méthodes produisent des IUriNode equivalentsIUriNode alice2 = g.CreateUriNode("ex:person/Alice");Console.WriteLine($"\nComparaison : {person.Uri} == {alice2.Uri} ? {person.Uri == alice2.Uri}");Console.WriteLine($"Type du noeud : {person.NodeType}");
URI absolue : http://example.org/person/Alice
URI via prefixe : http://example.org/knows
Comparaison : http://example.org/person/Alice == http://example.org/person/Alice ? True
Type du noeud : Uri
Interpretation : Creation d’URI Nodes
Méthode
Code
Quand l’utiliser
URI absolue
g.CreateUriNode(UriFactory.Create("..."))
URIs ponctuelles, URIs externes
Prefixe QName
g.CreateUriNode("prefix:local")
Usage repete d’un même namespace
Points cles : 1. Les URIs sont des identifiants globaux : deux noeuds avec la même URI sont identiques, même dans des graphes différents. 2. UriFactory.Create() optimise la memoire en internant les URIs (une seule instance par URI distincte). 3. La méthode QName necessite que le prefixe soit prealablement declare dans g.NamespaceMap.
2.2 Blank Nodes (IBlankNode)
Les noeuds anonymes (blank nodes) representent des ressources dont on ne connait pas (ou n’a pas besoin de) l’identifiant global. Ils sont identifies localement par un ID interne au graphe.
Cas d’usage typiques : - Structures intermediaires (adresses composees, coordonnees) - Ressources dont l’identite exacte n’importe pas - Existentielles : “il existe quelque chose qui…”
IGraph g =newGraph();g.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));// Blank node avec ID généré automatiquementIBlankNode bnode1 = g.CreateBlankNode();Console.WriteLine($"Blank node auto : _:{bnode1.InternalID}");// Blank node avec ID expliciteIBlankNode address = g.CreateBlankNode("addr1");Console.WriteLine($"Blank node explicite : _:{address.InternalID}");// Utilisation : Alice a une adresse (structure anonyme)IUriNode alice = g.CreateUriNode("ex:Alice");IUriNode hasAddress = g.CreateUriNode("ex:hasAddress");IUriNode street = g.CreateUriNode("ex:street");IUriNode city = g.CreateUriNode("ex:city");g.Assert(newTriple(alice, hasAddress, address));g.Assert(newTriple(address, street, g.CreateLiteralNode("42 rue de la Paix")));g.Assert(newTriple(address, city, g.CreateLiteralNode("Paris")));Console.WriteLine($"\nNombre de triplets : {g.Triples.Count}");foreach(Triple t in g.Triples){ Console.WriteLine($" {t}");}
Blank node auto : _:autos1
Blank node explicite : _:addr1
Nombre de triplets : 3
http://example.org/Alice , http://example.org/hasAddress , _:addr1
_:addr1 , http://example.org/street , 42 rue de la Paix^^http://www.w3.org/2001/XMLSchema#string
_:addr1 , http://example.org/city , Paris^^http://www.w3.org/2001/XMLSchema#string
Interpretation : Blank Nodes
Le blank node _:addr1 sert de pivot pour regrouper les proprietes de l’adresse :
ex:Alice ---ex:hasAddress---> _:addr1
|--- ex:street ---> "42 rue de la Paix"
|--- ex:city ---> "Paris"
Propriete
Blank Node vs URI Node
Portee
Locale au graphe (pas d’identifiant global)
Reutilisabilite
Ne peut pas etre reference depuis un autre graphe
Notation
_:id en NTriples/Turtle
Creation auto
g.CreateBlankNode() genere un ID unique
Bonne pratique : Preferez les URI Nodes pour les ressources importantes. Reservez les Blank Nodes aux structures intermediaires ou temporaires.
2.3 Literal Nodes (ILiteralNode)
Les noeuds literaux representent des valeurs concretes : texte, nombres, dates, booléens. Ils ne peuvent apparaitre qu’en position objet dans un triplet.
Trois variantes existent :
Variante
Syntaxe RDF
Exemple
Plain literal
"texte"
"Hello World"
Literal avec langue
"texte"@lang
"Bonjour"@fr
Literal type
"valeur"^^xsd:type
"42"^^xsd:integer
IGraph g =newGraph();// --- Variante 1 : Plain literal (chaine simple) ---ILiteralNode plain = g.CreateLiteralNode("Hello World");Console.WriteLine($"Plain literal : \"{plain.Value}\"");// --- Variante 2 : Literal avec étiquette de langue ---ILiteralNode langFr = g.CreateLiteralNode("Bonjour le monde","fr");ILiteralNode langEn = g.CreateLiteralNode("Hello World","en");Console.WriteLine($"Literal @fr : \"{langFr.Value}\"@{langFr.Language}");Console.WriteLine($"Literal @en : \"{langEn.Value}\"@{langEn.Language}");// --- Variante 3 : Literal type (avec type XSD) ---// Méthode manuelle (verbose)ILiteralNode ageManuel = g.CreateLiteralNode("30", UriFactory.Create(XmlSpecsHelper.XmlSchemaDataTypeInteger));Console.WriteLine($"\nTyped (manuel) : \"{ageManuel.Value}\"^^<{ageManuel.DataType}>");// Méthode avec extension .ToLiteral() (recommandée)INode ageExt =(30).ToLiteral(g);INode piExt =(3.14159).ToLiteral(g);INode dateExt = DateTime.Now.ToLiteral(g);INode boolExt =(true).ToLiteral(g);Console.WriteLine($"\nTyped via .ToLiteral() :");Console.WriteLine($" int -> {ageExt}");Console.WriteLine($" double -> {piExt}");Console.WriteLine($" DateTime -> {dateExt}");Console.WriteLine($" bool -> {boolExt}");
Points cles : 1. Les méthodes d’extension .ToLiteral() evitent la construction manuelle des URIs XSD et rendent le code beaucoup plus lisible. 2. Un literal ne peut pas avoir a la fois un type et une etiquette de langue (c’est l’un ou l’autre). 3. Sans type ni langue, un literal est considere comme un xsd:string par defaut.
Note technique : Les types XSD (XML Schema Datatypes) definissent un ensemble standard de types de données utilises dans tout le Web sémantique. Pour les types courants (int, DateTime, etc.), les extensions .ToLiteral() produisent automatiquement l’encodage RDF correct.
Recapitulatif : les trois types ensemble
Construisons un graphe utilisant les trois types de noeuds en combinaison.
// Graphe combinant les trois types de noeudsIGraph g =newGraph();g.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));g.NamespaceMap.AddNamespace("foaf", UriFactory.Create("http://xmlns.com/foaf/0.1/"));// URI Nodes (ressources nommées)IUriNode alice = g.CreateUriNode("ex:Alice");IUriNode bob = g.CreateUriNode("ex:Bob");IUriNode knows = g.CreateUriNode("foaf:knows");IUriNode name = g.CreateUriNode("foaf:name");IUriNode age = g.CreateUriNode("foaf:age");IUriNode rdfType = g.CreateUriNode("rdf:type");IUriNode foafPerson = g.CreateUriNode("foaf:Person");// Blank Node (compétence anonyme d'Alice)IBlankNode skill = g.CreateBlankNode("skill1");IUriNode hasSkill = g.CreateUriNode("ex:hasSkill");IUriNode skillName = g.CreateUriNode("ex:skillName");IUriNode skillLevel = g.CreateUriNode("ex:skillLevel");// Assertions avec les 3 types de noeudsg.Assert(newTriple(alice, rdfType, foafPerson));// URI -> URIg.Assert(newTriple(alice, name, g.CreateLiteralNode("Alice Dupont","fr")));// URI -> Literal@frg.Assert(newTriple(alice, age,(30).ToLiteral(g)));// URI -> Literal^^xsd:intg.Assert(newTriple(alice, knows, bob));// URI -> URIg.Assert(newTriple(alice, hasSkill, skill));// URI -> Blankg.Assert(newTriple(skill, skillName, g.CreateLiteralNode("C#")));// Blank -> Literalg.Assert(newTriple(skill, skillLevel, g.CreateLiteralNode("Expert")));// Blank -> Literalg.Assert(newTriple(bob, rdfType, foafPerson));// URI -> URIg.Assert(newTriple(bob, name, g.CreateLiteralNode("Bob Martin","fr")));// URI -> Literal@frConsole.WriteLine($"Graphe avec {g.Triples.Count} triplets :");Console.WriteLine();foreach(Triple t in g.Triples){string sType = t.Subjectis IBlankNode ?"Blank":"URI";string oType = t.Objectis IBlankNode ?"Blank":(t.Objectis ILiteralNode ?"Literal":"URI"); Console.WriteLine($" [{sType}] {t.Subject} --> {t.Predicate} --> [{oType}] {t.Object}");}
Ce graphe illustre toutes les combinaisons de types rencontrees en pratique :
Triplet
Sujet
Predicat
Objet
Pattern
Alice est une Person
URI
URI
URI
Classification
Alice a un nom
URI
URI
Literal@fr
Propriete textuelle multilingue
Alice a 30 ans
URI
URI
Literalxsd:int
Propriete numérique
Alice connait Bob
URI
URI
URI
Relation entre ressources
Alice a une competence
URI
URI
Blank
Lien vers structure anonyme
La competence s’appelle C#
Blank
URI
Literal
Detail de la structure
Point cle : Le predicat est toujours un URI Node. C’est la seule position contrainte a un seul type.
3. Espaces de noms et prefixes
Le problème des URIs longues
Sans prefixes, chaque URI doit etre ecrite en entier, ce qui rend le code verbeux et difficile a lire :
// Sans prefixes (fastidieux)g.CreateUriNode(UriFactory.Create("http://xmlns.com/foaf/0.1/name"));g.CreateUriNode(UriFactory.Create("http://xmlns.com/foaf/0.1/knows"));g.CreateUriNode(UriFactory.Create("http://xmlns.com/foaf/0.1/mbox"));// Avec prefixes (lisible)g.CreateUriNode("foaf:name");g.CreateUriNode("foaf:knows");g.CreateUriNode("foaf:mbox");
Les espaces de noms (namespaces) associent un prefixe court a un URI de base. Le NamespaceMapper de dotNetRDF gere cette correspondance.
IGraph g =newGraph();// Prefixes prédéfinis par dotNetRDFConsole.WriteLine("=== Prefixes predefinis ===");foreach(var prefix in g.NamespaceMap.Prefixes){ Console.WriteLine($" {prefix}: -> {g.NamespaceMap.GetNamespaceUri(prefix)}");}// Ajout de préfixes standardg.NamespaceMap.AddNamespace("foaf", UriFactory.Create("http://xmlns.com/foaf/0.1/"));g.NamespaceMap.AddNamespace("dc", UriFactory.Create("http://purl.org/dc/elements/1.1/"));g.NamespaceMap.AddNamespace("schema", UriFactory.Create("http://schema.org/"));g.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));Console.WriteLine("\n=== Apres ajout des prefixes personnalises ===");foreach(var prefix in g.NamespaceMap.Prefixes){ Console.WriteLine($" {prefix}: -> {g.NamespaceMap.GetNamespaceUri(prefix)}");}// Utilisation des préfixes pour créer des noeudsIUriNode foafName = g.CreateUriNode("foaf:name");IUriNode dcTitle = g.CreateUriNode("dc:title");Console.WriteLine($"\nResolution : foaf:name -> {foafName.Uri}");Console.WriteLine($"Resolution : dc:title -> {dcTitle.Uri}");
Prefixes standard recommandes (a ajouter selon le domaine) :
Prefixe
URI
Domaine
foaf:
http://xmlns.com/foaf/0.1/
Personnes et relations sociales
dc:
http://purl.org/dc/éléments/1.1/
Metadonnees Dublin Core
schema:
http://schema.org/
SEO / données structurees (Google)
skos:
http://www.w3.org/2004/02/skos/core#
Thesaurus et taxonomies
owl:
http://www.w3.org/2002/07/owl#
Ontologies Web
Bonne pratique : Declarez vos prefixes en debut de notebook pour eviter les URIs longues dans le code. Le registre officiel des prefixes est disponible sur prefix.cc.
Resolution et reduction d’URIs
Le NamespaceMapper fonctionne dans les deux sens : resolution (prefixe vers URI complete) et reduction (URI complete vers prefixe).
IGraph g =newGraph();g.NamespaceMap.AddNamespace("foaf", UriFactory.Create("http://xmlns.com/foaf/0.1/"));// Résolution : préfixe -> URI completeUri fullUri = g.NamespaceMap.GetNamespaceUri("foaf");Console.WriteLine($"Resolution : foaf: -> {fullUri}");// Réduction : URI complete -> préfixe:localUri testUri = UriFactory.Create("http://xmlns.com/foaf/0.1/name");string reduced;if(g.NamespaceMap.ReduceToQName(testUri.ToString(),out reduced)){ Console.WriteLine($"Reduction : {testUri} -> {reduced}");}// Vérification d'existence de préfixesConsole.WriteLine($"\nPrefixe 'foaf' existe ? {g.NamespaceMap.HasNamespace("foaf")}");Console.WriteLine($"Prefixe 'owl' existe ? {g.NamespaceMap.HasNamespace("owl")}");
Resolution : foaf: -> http://xmlns.com/foaf/0.1/
Reduction : http://xmlns.com/foaf/0.1/name -> foaf:name
Prefixe 'foaf' existe ? True
Prefixe 'owl' existe ? False
Interpretation : Opérations sur les namespaces
Opération
Méthode
Direction
Ajout
AddNamespace(prefix, uri)
Declaration d’un prefixe
Resolution
GetNamespaceUri(prefix)
Prefixe -> URI complete
Reduction
ReduceToQName(uri, out qname)
URI complete -> Prefixe:local
Verification
HasNamespace(prefix)
Le prefixe existe-t-il ?
Note technique : Les prefixes sont essentiels pour la lisibilite en Turtle et en SPARQL. Les Writers de dotNetRDF les utilisent automatiquement lors de la serialisation.
4. Formats de serialisation
Un graphe RDF est une structure abstraite (un ensemble de triplets). Pour le stocker ou le transmettre, il faut le serialiser dans un format textuel. Quatre formats principaux existent, chacun avec ses forces et faiblesses.
Ces formats sont normalises par le W3C : NTriples et Turtle (Beckett & Berners-Lee, W3C Rec 2014), RDF/XML (W3C Rec 1999, revise en RDF 1.1 en 2014) et JSON-LD (Speicher, Kellogg et al., W3C Rec 2014/2020). Les vocabulaires couramment utilises (foaf:, dc:, skos:) renvoient respectivement a FOAF (Brickley & Miller), Dublin Core (DCMI Metadata Terms) et SKOS (Miles & Bechhofer, W3C Rec 2009).
Nous allons créer un graphe de reference, puis le serialiser dans chaque format pour comparer.
// Graphe de référence pour les démonstrations de sérialisationIGraph demo =newGraph();demo.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));demo.NamespaceMap.AddNamespace("foaf", UriFactory.Create("http://xmlns.com/foaf/0.1/"));IUriNode alice = demo.CreateUriNode("ex:Alice");IUriNode bob = demo.CreateUriNode("ex:Bob");IUriNode rdfType = demo.CreateUriNode("rdf:type");IUriNode foafPerson = demo.CreateUriNode("foaf:Person");IUriNode foafName = demo.CreateUriNode("foaf:name");IUriNode foafAge = demo.CreateUriNode("foaf:age");IUriNode foafKnows = demo.CreateUriNode("foaf:knows");demo.Assert(newTriple(alice, rdfType, foafPerson));demo.Assert(newTriple(alice, foafName, demo.CreateLiteralNode("Alice Dupont","fr")));demo.Assert(newTriple(alice, foafAge,(30).ToLiteral(demo)));demo.Assert(newTriple(alice, foafKnows, bob));demo.Assert(newTriple(bob, rdfType, foafPerson));demo.Assert(newTriple(bob, foafName, demo.CreateLiteralNode("Bob Martin","fr")));Console.WriteLine($"Graphe de reference cree : {demo.Triples.Count} triplets.");Console.WriteLine("Ce graphe sera serialise dans 4 formats differents.");
Graphe de reference cree : 6 triplets.
Ce graphe sera serialise dans 4 formats differents.
4.1 NTriples : le format le plus simple
NTriples est un format ligne par ligne, sans abreviations ni prefixes. Chaque ligne contient un triplet complet, termine par un point.
// Sérialisation en NTriplesvar ntWriter =newNTriplesWriter();var sw =newStringWriter();ntWriter.Save(demo, sw);string ntOutput = sw.ToString();Console.WriteLine("=== Format NTriples ===");Console.WriteLine(ntOutput);Console.WriteLine($"Taille : {ntOutput.Length} caracteres");
Conventions syntaxiques : - URIs toujours entre chevrons : <http://example.org/Alice> - Literals entre guillemets : "Alice Dupont" - Etiquettes de langue après le literal : "Bonjour"@fr - Types indiques par ^^ : "30"^^<http://www.w3.org/2001/XMLSchema#integer> - Chaque triplet termine par un point .
Avantage
Inconvenient
Très simple a parser
Très verbeux (URIs repetees integralement)
Pas d’ambiguite
Fichiers volumineux
Ideal pour le streaming ligne par ligne
Peu lisible pour un humain
4.2 Turtle : le format lisible
Turtle (Terse RDF Triple Language) est le format le plus lisible pour les humains. Il utilise des prefixes, des abreviations et des regroupements par sujet.
// Sérialisation en Turtle (CompressingTurtleWriter pour les abréviations maximales)var turtleWriter =newCompressingTurtleWriter();var sw =newStringWriter();turtleWriter.Save(demo, sw);string turtleOutput = sw.ToString();Console.WriteLine("=== Format Turtle ===");Console.WriteLine(turtleOutput);Console.WriteLine($"Taille : {turtleOutput.Length} caracteres");
Plusieurs serialisations possibles pour le même graphe
Note : Le writer genere automatiquement des prefixes nsX pour les URIs non declarees dans le NamespaceMap. RDF/XML est de moins en moins utilise au profit de Turtle et JSON-LD.
4.4 JSON-LD : le format moderne
JSON-LD (JSON for Linked Data) encode des données RDF dans du JSON standard. C’est le format privilegie pour les APIs Web et le SEO (Schema.org utilise par Google).
// Sérialisation en JSON-LD// Note : JsonLdWriter implémente IStoreWriter (pas IRdfWriter)// Il faut donc envelopper le graphe dans un TripleStoreusing VDS.RDF;var store =newTripleStore();store.Add(demo);var jsonLdWriter =newJsonLdWriter();var sw =newStringWriter();jsonLdWriter.Save(store, sw);string jsonLdOutput = sw.ToString();Console.WriteLine("=== Format JSON-LD ===");Console.WriteLine(jsonLdOutput);Console.WriteLine($"Taille : {jsonLdOutput.Length} caracteres");
JSON-LD utilise des mots-cles speciaux prefixes par @ :
Mot-cle
Signification
Equivalent RDF
@context
Declaration des prefixes
@prefix en Turtle
@id
Identifiant de la ressource
URI du sujet
@type
Type de la ressource
rdf:type
@value
Valeur d’un literal
Contenu du literal
@language
Langue d’un literal
Etiquette de langue
Avantage
Inconvenient
Compatible ecosysteme JSON / JavaScript
Moins compact que Turtle
Utilise par Schema.org et Google
Contextualisation peut etre complexe
Facile a integrer dans les APIs REST
Plusieurs formes (expanded, compacted, flattened)
Comparaison des 4 formats
Critere
NTriples
Turtle
RDF/XML
JSON-LD
Lisibilite
Faible
Excellente
Faible
Bonne
Compacite
Faible
Elevee
Moyenne
Moyenne
Complexite de parsing
Très faible
Moyenne
Elevee
Moyenne
Prefixes
Non
Oui (@prefix)
Oui (xmlns)
Oui (@context)
Streaming
Oui (ligne par ligne)
Difficile
Difficile
Non
Usage principal
Dump de données
Documentation, SPARQL
Interoperabilite XML
APIs Web, SEO
Writer dotNetRDF
NTriplesWriter
CompressingTurtleWriter
RdfXmlWriter
JsonLdWriter
Recommandation : Utilisez Turtle pour la lisibilite et le travail humain, NTriples pour les traitements batch et le streaming, JSON-LD pour les APIs Web et le SEO.
5. Exemple guide 1 : Modeliser un reseau social
Objectif : Créer un graphe RDF modelisant un petit reseau social avec : - 3 personnes (avec nom, age et email) - Des relations foaf:knows entre elles - Au moins un Blank Node (par exemple, une adresse) - Serialiser le résultat en Turtle
Vocabulaires a utiliser : foaf: (Friend of a Friend), ex: (votre domaine)
Le code ci-dessous est une solution possible. Modifiez-le pour decrire votre propre reseau.
// Exemple guide 1 : Modéliser un réseau social en RDFIGraph social =newGraph();// Déclaration des préfixessocial.NamespaceMap.AddNamespace("foaf", UriFactory.Create("http://xmlns.com/foaf/0.1/"));social.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));// Noeuds prédicats réutilisablesIUriNode rdfType = social.CreateUriNode("rdf:type");IUriNode foafPerson = social.CreateUriNode("foaf:Person");IUriNode foafName = social.CreateUriNode("foaf:name");IUriNode foafAge = social.CreateUriNode("foaf:age");IUriNode foafMbox = social.CreateUriNode("foaf:mbox");IUriNode foafKnows = social.CreateUriNode("foaf:knows");IUriNode exAddress = social.CreateUriNode("ex:hasAddress");IUriNode exStreet = social.CreateUriNode("ex:street");IUriNode exCity = social.CreateUriNode("ex:city");// --- Carol ---IUriNode carol = social.CreateUriNode("ex:Carol");social.Assert(newTriple(carol, rdfType, foafPerson));social.Assert(newTriple(carol, foafName, social.CreateLiteralNode("Carol Lefevre","fr")));social.Assert(newTriple(carol, foafAge,(28).ToLiteral(social)));social.Assert(newTriple(carol, foafMbox, social.CreateUriNode(UriFactory.Create("mailto:carol@example.org"))));// --- David ---IUriNode david = social.CreateUriNode("ex:David");social.Assert(newTriple(david, rdfType, foafPerson));social.Assert(newTriple(david, foafName, social.CreateLiteralNode("David Moreau","fr")));social.Assert(newTriple(david, foafAge,(35).ToLiteral(social)));social.Assert(newTriple(david, foafMbox, social.CreateUriNode(UriFactory.Create("mailto:david@example.org"))));// --- Emma ---IUriNode emma = social.CreateUriNode("ex:Emma");social.Assert(newTriple(emma, rdfType, foafPerson));social.Assert(newTriple(emma, foafName, social.CreateLiteralNode("Emma Durand","fr")));social.Assert(newTriple(emma, foafAge,(42).ToLiteral(social)));// Relations foaf:knowssocial.Assert(newTriple(carol, foafKnows, david));social.Assert(newTriple(david, foafKnows, emma));social.Assert(newTriple(emma, foafKnows, carol));// Blank Node : adresse de CarolIBlankNode carolAddr = social.CreateBlankNode("carolAddr");social.Assert(newTriple(carol, exAddress, carolAddr));social.Assert(newTriple(carolAddr, exStreet, social.CreateLiteralNode("15 avenue Victor Hugo")));social.Assert(newTriple(carolAddr, exCity, social.CreateLiteralNode("Lyon")));// Sérialisation en Turtlevar turtleWriter =newCompressingTurtleWriter();var sw =newStringWriter();turtleWriter.Save(social, sw);Console.WriteLine($"Reseau social : {social.Triples.Count} triplets");Console.WriteLine();Console.WriteLine(sw.ToString());
Observations sur la sortie Turtle : - Les prefixes @prefix foaf: et @prefix ex: sont declares en tete - Les proprietes d’un même sujet sont regroupees avec ; - L’adresse de Carol apparait comme _:carolAddr (Blank Node) - Le raccourci a remplace rdf:type dans la sortie
6. Exemple guide 2 : Comparer les tailles de serialisation
Objectif : Serialiser le graphe du reseau social dans les différents formats et comparer les tailles obtenues. Cela illustre concretement les différences de compacite entre formats.
// Exemple guide 2 : Comparer les tailles de sérialisation// Fonction utilitaire pour sérialiser et mesurerstringSerializeGraph(IGraph graph, IRdfWriter writer){var sw =newStringWriter(); writer.Save(graph, sw);return sw.ToString();}// Formats IRdfWriter (NTriples, Turtle, RDF/XML)var formats =new List<(string Name, IRdfWriter Writer)>{("NTriples",newNTriplesWriter()),("Turtle",newCompressingTurtleWriter()),("RDF/XML",newRdfXmlWriter())};Console.WriteLine($"Graphe de test : {social.Triples.Count} triplets\n");Console.WriteLine($"{"Format",-12} {"Taille(car)",-15} {"Lignes",-10} {"Ratio vs NT",-15}");Console.WriteLine(newstring('-',52));int ntSize =0;foreach(var(formatName, writer)in formats){string output =SerializeGraph(social, writer);int size = output.Length;int lines = output.Split('\n').Length;if(formatName =="NTriples") ntSize = size;string ratio = ntSize >0? $"{(double)size / ntSize:P0}":"---"; Console.WriteLine($"{formatName,-12} {size,-15} {lines,-10} {ratio,-15}");}// JSON-LD : IStoreWriter (nécessite un TripleStore)var jsonStore =newTripleStore();jsonStore.Add(social);var jsonLdWriter =newJsonLdWriter();var jsonSw =newStringWriter();jsonLdWriter.Save(jsonStore, jsonSw);string jsonOutput = jsonSw.ToString();string jsonRatio = ntSize >0? $"{(double)jsonOutput.Length / ntSize:P0}":"---";Console.WriteLine($"{"JSON-LD",-12} {jsonOutput.Length,-15} {jsonOutput.Split('\n').Length,-10} {jsonRatio,-15}");
Graphe de test : 17 triplets
Format Taille (car) Lignes Ratio vs NT
----------------------------------------------------
NTriples 1644 18 100 %
Turtle 803 23 49 %
RDF/XML 1567 32 95 %
JSON-LD 2069 99 126 %
Interpretation de l’Exemple guide 2
Résultats attendus (les valeurs exactes varient selon le graphe) :
Format
Taille relative
Raison
NTriples
100% (reference)
Aucune compression, URIs completes repetees
Turtle
~40-60%
Prefixes, regroupement par sujet, raccourci a
RDF/XML
~95%
Espaces de noms XML, structure verbose (proche de NTriples sur ce graphe)
JSON-LD
~126%
Contexte JSON, structure d’objets (plus verbose que NTriples ici, format le plus lourd)
Conclusion : Turtle est presque toujours le format le plus compact grace aux prefixes et aux abreviations syntaxiques. Le choix du format depend de l’usage : - Documentation et lecture humaine : Turtle - Echange machine / streaming : NTriples - APIs Web et SEO : JSON-LD - Integration XML : RDF/XML
7. Exercice : a vous de jouer
Vous maitrisez maintenant les triplets, les trois types de noeuds, les namespaces et la serialisation. Mettez tout en pratique sur un cas concret.
Enonce : Construisez un graphe RDF decrivant un livre :
Un noeud URI pour le livre (ex : ex:livre/RDF101).
Un literal type (xsd:integer) pour l’annee de publication.
Un literal avec tag de langue (@fr) pour le titre.
Un blank node pour l’auteur, relie au livre via ex:auteur, avec un nom (literal simple).
Declarez les prefixes ex (http://example.org/) et dc (http://purl.org/dc/éléments/1.1/).
Serialisez le graphe en Turtle et affichez-le, puis affichez le nombre de triplets (g.Triples.Count).
Indice : reutilisez UriFactory.Create, g.CreateLiteralNode(valeur, new Uri(...)) pour le type, g.CreateLiteralNode(valeur, "fr") pour la langue, g.CreateBlankNode(), g.NamespaceMap.AddNamespace(...) et CompressingTurtleWriter.
Résultat attendu : un graphe de ~5-6 triplets serialise en Turtle lisible.
Exercice : Construire un graphe avec les 3 types de noeuds
Les sections 2.1-2.3 ont montre les URI Nodes, Blank Nodes et Literal Nodes. Construisez un graphe qui combine les trois types.
g.Assert(new Triple(s, p, o)) pour ajouter un triplet
// Exercice : Construire un graphe combinant URI, Blank et Literal nodes// TODO étudiant : construisez un graphe avec au moins 1 URI, 1 Blank et 1 Literal//// Étape 1 : créer IGraph g = new Graph() et définir un namespace// Étape 2 : créer un sujet URI, un prédicat URI, un objet Literal// Étape 3 : ajouter un Blank Node comme sujet d'un autre triplet// Étape 4 : afficher tous les triplets avec g.TriplesConsole.WriteLine("Exercice a completer");
Exercice a completer
Exercice : Serialiser un graphe dans les 4 formats
Les sections 4.1-4.4 ont montre les formats NTriples, Turtle, RDF/XML et JSON-LD. Construisez un graphe RDF et comparez la taille de chaque format de serialisation.
writer.Save(g, sw) puis sw.ToString().Length pour la taille
// Exercice : Sérialiser un graphe dans les 4 formats et comparer les tailles// TODO étudiant : construisez un graphe avec 5 triplets, puis sérialisez dans les 4 formats//// Étape 1 : créer un Graph et ajouter 5 triplets (sujet, prédicat, objet)// Étape 2 : sérialiser avec CompressingTurtleWriter, NTriplesWriter, RdfXmlWriter, JsonLdWriter// Étape 3 : comparer la taille (en caractères) de chaque format// Étape 4 (bonus) : quel format est le plus compact ? Le plus lisible ?Console.WriteLine("Exercice a completer");
Exercice a completer
Exercice : Créer un graphe RDF complet avec namespaces
En utilisant les concepts vus dans ce notebook (URI nodes, literals, blank nodes, namespaces), construisez un graphe RDF complet et serialisez-le en Turtle.
Indices : utilisez NamespaceMap, UriFactory, et CompressingTurtleWriter.
// Exercice : votre code ici// Etapes suggerees :// IGraph g = new Graph();// g.NamespaceMap.AddNamespace("ex", UriFactory.Create("http://example.org/"));// g.NamespaceMap.AddNamespace("dc", UriFactory.Create("http://purl.org/dc/elements/1.1/"));// ... créer les noeuds (URI, literals type/langue, blank node) ...// ... ajouter les triplets avec g.Assert(...) ...// ... sérialiser en Turtle avec CompressingTurtleWriter ...// Console.WriteLine($"Nombre de triplets : {g.Triples.Count}");Console.WriteLine("Exercice a completer");
Exercice a completer
Resume
Élément
Ce que nous avons appris
Triplet RDF
Unite fondamentale (sujet, predicat, objet), asserte avec g.Assert()
URI Nodes
IUriNode : identifiant global, creation par URI absolue ou QName
Blank Nodes
IBlankNode : noeud anonyme local, pour structures intermediaires
Literal Nodes
ILiteralNode : valeurs (plain, avec langue @fr, avec type ^^xsd:)
Espaces de noms
NamespaceMapper : prefixes pour abreger les URIs (foaf:, dc:, etc.)
NTriples
Format simple, une ligne par triplet, pas de prefixes
Turtle
Format lisible avec prefixes et abreviations (;, ,, a)
RDF/XML
Format historique W3C base sur XML
JSON-LD
Format moderne pour APIs Web et SEO
Ce qui vient ensuite
Dans le notebook suivant (SW-3-GraphOperations), nous apprendrons a : - Sélectionner des triplets avec GetTriplesWithXxx() - Fusionner et comparer des graphes - Lire des fichiers RDF depuis le disque et le Web - Convertir entre formats de serialisation