#r "nuget: dotNetRDF, 3.2.1"- dotNetRDF, 3.2.1
Navigation : << 2-RDFBasics | Index | 4-SPARQL >>
Ce notebook manipule des graphes RDF au moyen de la bibliothèque dotNetRDF (open-source,
dotnetrdf.org), qui implémente le modèle de données abstrait de RDF 1.1 (Cyganiak, Wood, Lanthaler (eds.), RDF 1.1 Concepts and Abstract Syntax, W3C Recommendation, 25 février 2014).
VDS.RDF.Parsing.VDS.RDF.Writing, en comparant verbose vs compresse.| # | Section | Thème | Sortie attendue |
|---|---|---|---|
| 1 | Lecture | Parsers, StringParser, Handlers | 3 méthodes de lecture + CountHandler |
| 2 | Écriture | Writers, configuration | NTriples / CompressingTurtle / RDF/XML |
| 3 | Fusion | Merge, deduplication | Graphes combines sans doublons |
| 4 | Sélection | GetTriplesWithXxx + LINQ | Sélection par sujet/prédicat/objet |
| 5 | Listes RDF | AssertList/RetractList/Add/Remove | Liste [1,2,3] + modifications |
| 6 | Exercices | 3 exemples guides + 3 exercices | Chargement, fusion, listes |
Coût total : < 10 secondes (1 installation NuGet + 20 chargements + 5 écritures + 3 fusions + 6 sélections + 4 manipulations de listes).
Concepts clés : RDF 1.1 (triplets sujet-prédicat-objet), formats sérialisés (Turtle, NTriples, RDF/XML, JSON-LD), graphes orientes, sémantique d’ensemble, listes chaînées avec rdf:first/rdf:rest, LINQ comme langage de requête.
Références : Cyganiak, Wood, Lanthaler (eds.) 2014 (RDF 1.1 Concepts), Beckett et al. 2014 (RDF 1.1 Turtle), dotNetRDF library documentation (dotnetrdf.org).
.net-csharp (.NET Interactive 9.0, cf. ML/ML.Net/ML-1-Introduction-Python.ipynb).dotNetRDF, 3.2.1 (bibliothèque .NET de reference pour RDF/Semantic Web). Le kernel télécharge cette dépendance à la volée via #r "nuget: dotNetRDF, 3.2.1".Lecture de l’installation dotNetRDF (cellule code[0]) :
La commande magic #r "nuget: dotNetRDF, 3.2.1" télécharge le package NuGet dotNetRDF version 3.2.1 depuis nuget.org, le compile, et l’attache au contexte de session du kernel .net-csharp. Tous les using VDS.RDF.*; suivants peuvent referencer ses types.
Pourquoi dotNetRDF plutot que alternatives :
| Bibliothèque | Langage | Avantage | Inconvénient |
|---|---|---|---|
| dotNetRDF | C#/.NET | Mature, complète, LINQ-friendly | Dernière release 3.2.1 (2023) |
| Apache Jena | Java | Ecosystem large, Fuseki + ARQ | Pas .NET |
| RDFLib | Python | Standard Python, large | Pas .NET |
| Redland | C | Bas niveau, très performant | API complexe |
Pour une application .NET, dotNetRDF est le choix naturel. C’est l’équivalent de RDFLib en .NET ou de Jena en Java.
Le pattern #r "nuget: ..." :
C’est une commande magic du kernel .net-csharp (cf. Microsoft.DotNet.Interactive). Le download prend ~10-30 secondes la première fois (mise en cache dans ~/.nuget/packages/). Les executions ulterieures reutilisent le cache.
Sortie attendue : chargement du package (pas de sortie directe visible, mais la cellule est marquée exec_count=1 après compilation).
Coût : ~15 secondes la première fois (download + compilation), < 1 ms en cache.
Importation des espaces de noms dotNetRDF et configuration de l’environnement de travail.
Les parsers sont dans VDS.RDF.Parsing. Chacun implémente IRdfReader et supporte Load(IGraph, ...) avec un chemin de fichier, un StreamReader ou un TextReader. Formats supportés : NTriples, Turtle, Notation 3, RDF/XML, JSON-LD, RDF/JSON, RDFa, TriG, NQuads.
Chaque format correspond à une Recommandation W3C distincte de la famille RDF 1.1 : Turtle (Terse RDF Triple Language), NTriples, RDF/XML, JSON-LD (JavaScript Object Notation for Linking Data), etc.
Pourquoi cette variété de formats :
Le modèle de données abstrait de RDF 1.1 (triplets sujet-prédicat-objet) est unique, mais chaque format de sérialisation a ses propres conventions de syntaxe. Turtle est le plus compact et lisible par les humains ; NTriples est le plus simple à parser (1 triplet par ligne) ; RDF/XML est le plus ancien et le plus répandu historiquement ; JSON-LD est le plus naturel pour les applications web.
Trois méthodes de lecture complémentaires :
TurtleParser + fichier : format explicite, lecture en streaming. Idéal pour les fichiers connus et les pipelines ETL.StringParser.Parse() : détection automatique du format depuis une chaîne. Idéal pour les formats inconnus ou les entrées utilisateurs.CountHandler + Parser : comptage sans chargement en mémoire. Idéal pour les fichiers très volumineux où la mémoire est contrainte.Sortie attendue (cellule code[2]) : un graphe g chargé depuis Turtle, avec ses triplets accessibles via g.Triples.
Coût : ~1 seconde (téléchargement NuGet dotNetRDF 3.2.1 la première fois + parsing d’un petit fichier Turtle).
// 1.1 Chargement depuis un fichier Turtle
IGraph g = new Graph();
IGraph h = new Graph();
TurtleParser ttlparser = new TurtleParser();
ttlparser.Load(g, "data/Example.ttl"); // par chemin
ttlparser.Load(h, new StreamReader("data/Example.ttl")); // par stream
Console.WriteLine($"Fichier : {g.Triples.Count} triplets");
Console.WriteLine($"Stream : {h.Triples.Count} triplets");
Console.WriteLine($"Identique: {g.Triples.Count == h.Triples.Count}");Fichier : 4 triplets
Stream : 4 triplets
Identique: True
Lecture du chargement TurtleParser (cellule code[2]) :
Trois étapes du chargement :
new Graph() : cree un graphe RDF vide en mémoire. Le graphe est un container pour les triplets, avec une table de hachage interne pour la recherche rapide.new TurtleParser() : cree un parser pour le format Turtle (.ttl). Le parser implémente IRdfReader avec une méthode Load(IGraph, ...) qui prend un chemin de fichier, un StreamReader, ou un TextReader.parser.Load(g, "data/example.ttl") : charge le fichier dans le graphe. Après cette ligne, g.Triples contient tous les triplets parses.Pourquoi un graphe vide à l’initialisation :
C’est une convention de l’API : le graphe est cree vide, puis les triplets sont ajoutes par le parser. Cela permet de charger plusieurs fichiers dans le même graphe (via Merge()), ou de construire un graphe programme par programme.
Sortie attendue : les lignes commitees ci-dessus – « Fichier : 4 triplets / Stream : 4 triplets / Identique: True » (le même example.ttl lu par les deux voies).
Coût : ~0.1 seconde (parsing d’un fichier de 5-50 triplets).
Utilisation de StringParser pour la détection automatique de format et de NTriplesParser pour un parsing explicite.
// 1.2 StringParser (detection auto) vs Parser specifique
Graph g1 = new Graph();
StringParser.Parse(g1, "<http://example.org/a> <http://example.org/b> <http://example.org/c>.");
Console.WriteLine($"StringParser (auto) : {g1.Triples.Count} triplet");
Graph g2 = new Graph();
new NTriplesParser().Load(g2, new StringReader("<http://example.org/a> <http://example.org/b> <http://example.org/c>."));
Console.WriteLine($"NTriplesParser : {g2.Triples.Count} triplet");StringParser (auto) : 1 triplet
NTriplesParser : 1 triplet
Comptage des triplets avec CountHandler sans charger le graphe en mémoire.
animals.ttl : 51 triplets (comptes sans charger en memoire)
| Méthode | Avantage | Inconvénient |
|---|---|---|
TurtleParser + fichier |
Format explicite, fiable | Fichier requis |
StringParser.Parse() |
Détection auto du format | Peut échouer sur fragments ambigus |
NTriplesParser + StringReader |
Format explicite, en mémoire | Necessite de connaitre le format |
CountHandler + Parser |
Comptage sans chargement mémoire | Pas d’accès aux triplets |
Pourquoi la détection automatique peut échouer :
StringParser.Parse() se base sur des heuristiques (présence de <...> pour XML, {...} pour JSON-LD, @prefix pour Turtle, etc.). Sur des fragments ambigus (par exemple, une chaîne qui pourrait être Turtle ou NTriples), la détection peut se tromper. C’est pourquoi les parsers spécifiques (TurtleParser, NTriplesParser) sont preferes quand le format est connu.
Pourquoi CountHandler est utile :
Pour les graphes de plusieurs millions de triplets, charger le graphe entier en mémoire peut saturer la RAM. CountHandler est un handler qui incrémente un compteur à chaque triplet rencontre, sans stocker le graphe. C’est l’équivalent d’un wc -l en streaming.
Trois exemples d’usage :
Coût : ~0.1 seconde (parsing d’un petit graphe en mémoire).
Les writers sont dans VDS.RDF.Writing et implementent IRdfWriter. Formats : NTriples, Turtle, RDF/XML, CompressingTurtle, RDF/JSON, CSV, TSV.
// 2.1 Comparaison de formats de sortie
IGraph g = new Graph();
new TurtleParser().Load(g, "data/Example.ttl");
Console.WriteLine("=== NTriples ===");
Console.WriteLine(VDS.RDF.Writing.StringWriter.Write(g, new NTriplesWriter()));
Console.WriteLine("=== Turtle Compresse ===");
Console.WriteLine(VDS.RDF.Writing.StringWriter.Write(g, new CompressingTurtleWriter()));=== NTriples ===
<http://www.w3.org/TR/rdf-syntax-grammar> <http://purl.org/dc/elements/1.1/title> "RDF/XML Syntax Specification (Revised)"^^<http://www.w3.org/2001/XMLSchema#string> .
_:autos3 <http://example.org/stuff/1.0/fullname> "Dave Beckett"^^<http://www.w3.org/2001/XMLSchema#string> .
_:autos3 <http://example.org/stuff/1.0/homePage> <http://purl.org/net/dajobe/> .
<http://www.w3.org/TR/rdf-syntax-grammar> <http://example.org/stuff/1.0/editor> _:autos3 .
=== Turtle Compresse ===
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix dc: <http://purl.org/dc/elements/1.1/>.
@prefix ex: <http://example.org/stuff/1.0/>.
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/>];
dc:title "RDF/XML Syntax Specification (Revised)".
NTriples : format verbeux – chaque triplet occupe une ligne complète avec les URIs en entier. Pas de préfixe, pas de compression. Idéal pour le debugging, le piping UNIX, les tests automatises.
Turtle compresse : format compact – les préfixes sont définis une fois (@prefix), les noeuds blancs sont regroupés avec ; (même sujet) et les propriétés sont imbriquees avec [ (blanks). Idéal pour les fichiers de données volumineux, les graphes publiés sur le web.
RDF/XML : format ancien – syntaxe XML stricte, namespaces obligatoires. Idéal pour l’interopérabilité avec les systèmes legac Java/Jena (Apache Jena, Sesame).
Tableau comparatif :
| Format | Compression | Lisibilité | Interopérabilité |
|---|---|---|---|
| NTriples | 1.0x (baseline) | Moyenne | Universelle |
| Turtle compresse | ~3-5x | Élevée | Standard W3C |
| RDF/XML | ~2x | Faible | Legacy Java |
| JSON-LD | ~2.5x | Moyenne | Web apps |
Pourquoi CompressingTurtleWriter est le défaut moderne :
C’est le meilleur compromis compression/lisibilité, et il est standardisé W3C (Recommandation 2014). Il génère automatiquement les préfixes pour les URIs partagees, ce qui réduit significativement la taille du fichier.
Sortie attendue (cellule code[5]) : les deux sérialisations commitees du même graphe de 4 triplets (NTriples : une ligne par triplet ; Turtle compresse) ; le RDF/XML suit dans la cellule suivante.
Coût : ~0.1 seconde (3 sérialisations d’un graphe de 5-10 triplets).
Sérialisation du graphe en RDF/XML via deux méthodes equivalentes : helper statique et StringWriter explicite.
// 2.2 RDF/XML : deux methodes pour obtenir une chaine
var rdfxmlwriter = new RdfXmlWriter();
// Methode 1 : Helper (une ligne)
String data1 = VDS.RDF.Writing.StringWriter.Write(g, rdfxmlwriter);
// Methode 2 : System.IO.StringWriter (controle total)
var sw = new System.IO.StringWriter();
rdfxmlwriter.Save(g, sw);
String data2 = sw.ToString();
Console.WriteLine($"Helper : {data1.Length} car.");
Console.WriteLine($"StringWriter: {data2.Length} car.");
Console.WriteLine($"Identiques : {data1 == data2}");
Console.WriteLine($"\n=== RDF/XML (extrait) ===\n{data1.Substring(0, Math.Min(data1.Length, 400))}...");Helper : 1017 car.
StringWriter: 1017 car.
Identiques : True
=== RDF/XML (extrait) ===
<?xml version="1.0" encoding="utf-16"?>
<!DOCTYPE rdf:RDF [
<!ENTITY rdf 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<!ENTITY rdfs 'http://www.w3.org/2000/01/rdf-schema#'>
<!ENTITY xsd 'http://www.w3.org/2001/XMLSchema#'>
<!ENTITY dc 'http://purl.org/dc/elements/1.1/'>
<!ENTITY ex 'http://example.org/stuff/1.0/'>
]>
<rdf:RDF xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:x...
Configuration avancee des writers (compression, pretty-print) pour optimiser la sortie Turtle.
// 2.3 Configuration avancee des writers
public static string SaveConfigured(IGraph g, IRdfWriter writer)
{
if (writer is IPrettyPrintingWriter ppw) ppw.PrettyPrintMode = true;
if (writer is IHighSpeedWriter hsw) hsw.HighSpeedModePermitted = false;
if (writer is ICompressingWriter cw) cw.CompressionLevel = WriterCompressionLevel.High;
return VDS.RDF.Writing.StringWriter.Write(g, writer);
}
Console.WriteLine("=== Turtle haute compression ===");
Console.WriteLine(SaveConfigured(g, new CompressingTurtleWriter()));=== Turtle haute compression ===
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix dc: <http://purl.org/dc/elements/1.1/>.
@prefix ex: <http://example.org/stuff/1.0/>.
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/>];
dc:title "RDF/XML Syntax Specification (Revised)".
| Writer | Format | Lisibilité | Taille |
|---|---|---|---|
NTriplesWriter |
NTriples | Faible | Verbose |
CompressingTurtleWriter |
Turtle | Élevée | Compact |
RdfXmlWriter |
RDF/XML | Moyenne | Moyenne |
| Interface | Capacite |
|---|---|
IPrettyPrintingWriter |
Formatage lisible (indentation) |
ICompressingWriter |
Compression des préfixes |
Pourquoi configurer les writers :
Les writers ont des options par défaut raisonnables, mais pour des cas d’usage spécifiques (production, publication, debugging), il peut être utile de :
Trois exemples d’usage :
CompressingTurtleWriter pour minimiser la taille des fichiers sur disque et la bande passante lors du transfert.NTriplesWriter pour une sortie 1-triplet-par-ligne facile à grepper.RdfXmlWriter ou JsonLdWriter pour l’interopérabilité avec les systèmes tiers.Coût : ~0.1 seconde (1 sérialisation par writer teste).
La méthode Merge() combine deux graphes en respectant la sémantique RDF : pas de doublons, renommage des noeuds blancs.
// 3.1 Fusion de deux graphes
Graph g1 = new Graph();
Graph g2 = new Graph();
var ttlP = new TurtleParser();
ttlP.Load(g1, "data/Example.ttl");
ttlP.Load(g2, "data/animals.ttl");
int somme = g1.Triples.Count + g2.Triples.Count;
Console.WriteLine($"Graphe 1 (Example.ttl) : {g1.Triples.Count} triplets");
Console.WriteLine($"Graphe 2 (animals.ttl) : {g2.Triples.Count} triplets");
g1.Merge(g2);
Console.WriteLine($"Apres fusion : {g1.Triples.Count} triplets (somme theorique: {somme})");
Console.WriteLine($"Doublons supprimes : {somme - g1.Triples.Count}");Graphe 1 (Example.ttl) : 4 triplets
Graphe 2 (animals.ttl) : 51 triplets
Apres fusion : 55 triplets (somme theorique: 55)
Doublons supprimes : 0
g1.Merge(g2) modifie g1, pas g2Pourquoi la sémantique d’ensemble est importante :
Un graphe RDF est un ensemble de triplets (pas une multiset). Quand on fusionne deux graphes, les triplets identiques ne sont pas dupliques – c’est la propriété d’ensemble. Cela reflete le modèle abstrait : un triplet <s, p, o> est soit present, soit absent.
Pourquoi les noeuds blancs sont renommés :
Les noeuds blancs (blank nodes) sont des identifiants locaux sans IRI. Deux graphes peuvent avoir des noeuds blancs avec le même label interne (_:b1) qui représentent en fait des entités differentes. Pour éviter les collisions, dotNetRDF renomme les noeuds blancs du graphe fusionne avec des nouveaux identifiants uniques.
Pourquoi l’opération est asymétrique :
g1.Merge(g2) ajoute les triplets de g2 à g1. g2 n’est pas modifié. C’est la convention standard pour les opérations de fusion : l’un des deux graphes est “absorbé” par l’autre. Si on veut une copie de g2 non modifiée, on peut utiliser g1.Merge(new Graph(g2)) ou appliquer la méthode d’extension GraphExtensions.Merge(g1, g2).
Trois cas d’usage :
Coût : ~0.1 seconde (fusion de 2 graphes de 5-10 triplets).
IGraph propose des méthodes GetTriplesWithXxx() retournant IEnumerable<Triple>, composables avec LINQ.
// 4.1 Selection par predicat et par sujet
Graph g = new Graph();
new TurtleParser().Load(g, "data/animals.ttl");
IUriNode rdfType = g.CreateUriNode("rdf:type");
Console.WriteLine("=== Triplets rdf:type ===");
foreach (Triple t in g.GetTriplesWithPredicate(rdfType))
Console.WriteLine($" {t}");
IUriNode rex = g.GetUriNode(new Uri("http://example.org/animals#rex"));
if (rex != null)
{
Console.WriteLine("\n=== Proprietes de Rex ===");
foreach (Triple t in g.GetTriplesWithSubject(rex))
Console.WriteLine($" {t}");
}=== Triplets rdf:type ===
http://example.org/animals#Animal , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Mammal , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Bird , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Dog , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Cat , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Parrot , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#name , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#age , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#sound , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#canFly , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#rex , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
http://example.org/animals#minou , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Cat
http://example.org/animals#coco , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Parrot
http://example.org/animals#buddy , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
=== Proprietes de Rex ===
http://example.org/animals#rex , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
http://example.org/animals#rex , http://example.org/animals#name , Rex^^http://www.w3.org/2001/XMLSchema#string
http://example.org/animals#rex , http://example.org/animals#age , 5^^http://www.w3.org/2001/XMLSchema#integer
http://example.org/animals#rex , http://example.org/animals#sound , Woof^^http://www.w3.org/2001/XMLSchema#string
Sélection par prédicat rdf:type : 14 triplets trouves – 6 déclarations de classes (Animal, Mammal, Bird, Dog, Cat, Parrot), 4 déclarations de propriétés (name, age, sound, canFly) et 4 instances (rex/Dog, minou/Cat, coco/Parrot, buddy/Dog).
Sélection par sujet ex:rex : 4 propriétés – type (Dog), name (Rex), age (5), sound (Woof). Rex est un Dog de 5 ans qui aboie.
Sélection combinee : en combinant GetTriplesWithPredicate + LINQ Where(), on peut filtrer par prédicat puis par valeur d’objet. Par exemple, “tous les animaux qui peuvent voler” = triplets.Where(t => t.Predicate.Equals(ns.canFly) && t.Object.Equals(true)).
Trois exemples de sélection avancee :
GetTriplesWithPredicate(rdfType).Where(t => t.Object.Equals(ns.Dog)) – liste tous les chiens.GetTriplesWithSubject(ns.rex) – propriétés directes d’une entite.GetTriplesWithPredicate(ns.age).Select(t => t.Object) – ages de tous les animaux.Pourquoi LINQ est adapte :
LINQ (Language Integrated Query) est un langage de requête en mémoire pour .NET. Il permet de composer des filtres, projections, et aggregations de manière déclarative, sans boucles impératives. C’est l’équivalent de Python comprehensions ou SQL pour les collections .NET.
Coût : ~0.1 seconde (sélection de 10-50 triplets).
Combinaison de sélections par prédicat et par objet avec des expressions LINQ pour filtrer les animaux.
// 4.2 Selection combinee et LINQ
IUriNode nameProp = g.CreateUriNode(new Uri("http://example.org/animals#name"));
Console.WriteLine("=== Noms des animaux ===");
foreach (Triple t in g.GetTriplesWithPredicate(nameProp))
Console.WriteLine($" {t.Subject} -> {t.Object}");
// LINQ : animaux de plus de 5 ans
IUriNode ageProp = g.CreateUriNode(new Uri("http://example.org/animals#age"));
var older = g.GetTriplesWithPredicate(ageProp)
.Where(t => t.Object is ILiteralNode lit && int.TryParse(lit.Value, out int age) && age > 5)
.Select(t => new { Animal = t.Subject, Age = t.Object });
Console.WriteLine("\n=== Animaux > 5 ans (LINQ) ===");
foreach (var a in older)
Console.WriteLine($" {a.Animal} : {a.Age}");=== Noms des animaux ===
http://example.org/animals#rex -> Rex^^http://www.w3.org/2001/XMLSchema#string
http://example.org/animals#minou -> Minou^^http://www.w3.org/2001/XMLSchema#string
http://example.org/animals#coco -> Coco^^http://www.w3.org/2001/XMLSchema#string
http://example.org/animals#buddy -> Buddy^^http://www.w3.org/2001/XMLSchema#string
=== Animaux > 5 ans (LINQ) ===
http://example.org/animals#coco : 12^^http://www.w3.org/2001/XMLSchema#integer
http://example.org/animals#buddy : 7^^http://www.w3.org/2001/XMLSchema#integer
| Méthode | Pattern | Équivalent SPARQL |
|---|---|---|
GetTriplesWithSubject(s) |
s ? ? |
SELECT ?p ?o WHERE { s ?p ?o } |
GetTriplesWithPredicate(p) |
? p ? |
SELECT ?s ?o WHERE { ?s p ?o } |
GetTriplesWithObject(o) |
? ? o |
SELECT ?s ?p WHERE { ?s ?p o } |
GetTriplesWithSubjectPredicate(s,p) |
s p ? |
SELECT ?o WHERE { s p ?o } |
Pourquoi ces méthodes existent :
Elles correspondent aux patterns de triplets du modèle abstrait RDF 1.1. Pour chaque combinaison de variables fixées (sujet, prédicat, objet), il y a une méthode qui retourne les triplets correspondants.
Avantage par rapport à SPARQL :
Where(), Select(), GroupBy().IEnumerable<Triple> directement, pas une table de résultats à parser.Limitation par rapport à SPARQL :
Coût : ~0.1 seconde (sélection + filtrage LINQ).
Les listes RDF sont des structures chaînées encodees avec rdf:first et rdf:rest. dotNetRDF fournit des méthodes haut niveau dans VDS.RDF.Extensions.
// 5.1 Creer et lire une liste RDF
IGraph g = new Graph();
string rdfData = @"
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix ex: <http://example.org/ns#> .
ex:subj ex:pred _:b1 .
_:b1 rdf:first 1 . _:b1 rdf:rest _:b2 .
_:b2 rdf:first 2 . _:b2 rdf:rest _:b3 .
_:b3 rdf:first 3 . _:b3 rdf:rest rdf:nil .";
TurtleParser parser = new TurtleParser();
parser.Load(g, new StringReader(rdfData));
IUriNode predicate = g.CreateUriNode("ex:pred");
INode root = g.GetTriplesWithPredicate(predicate).First().Object;
Console.WriteLine($"GetListItems : [{string.Join(", ", g.GetListItems(root))}]");
Console.WriteLine($"GetListNodes : [{string.Join(", ", g.GetListNodes(root))}]");
Console.WriteLine($"GetListAsTriples : {g.GetListAsTriples(root).Count()} triplets");GetListItems : [1^^http://www.w3.org/2001/XMLSchema#integer, 2^^http://www.w3.org/2001/XMLSchema#integer, 3^^http://www.w3.org/2001/XMLSchema#integer]
GetListNodes : [_:b1, _:b2, _:b3]
GetListAsTriples : 6 triplets
root -> [1 | rest] -> [2 | rest] -> [3 | rest] -> nil
| Méthode | Retourne | Exemple |
|---|---|---|
GetListItems(root) |
Valeurs (rdf:first) |
1, 2, 3 |
GetListNodes(root) |
Noeuds intermédiaires | _:b1, _:b2, _:b3 |
GetListAsTriples(root) |
Tous les triplets | rdf:first + rdf:rest |
Pourquoi les listes RDF sont chaînées :
En RDF 1.1, il n’y a pas de structure de liste native. Les listes sont encodees avec des triplets rdf:first (la valeur) et rdf:rest (le noeud suivant), terminées par un noeud spécial rdf:nil. C’est un choix de design minimaliste : tout en RDF est un triplet, donc les listes sont aussi encodees comme des triplets.
Avantage :
Inconvénient :
rdf:first, un pour rdf:rest). Pour N=100, c’est 200 triplets.Trois alternatives :
rdf:Seq : collections ordonnées natives (RDF 1.1 + schema.org), plus efficaces que les listes chaînées.[...] directement convertible en listes RDF.Coût : ~0.1 seconde (creation + lecture d’une liste de 3 éléments).
// 5.2 AssertList et RetractList
INode newRoot = g.AssertList(new List<INode>() { (true).ToLiteral(g), (false).ToLiteral(g) });
Console.WriteLine($"AssertList : [{string.Join(", ", g.GetListItems(newRoot))}]");
int avant = g.Triples.Count;
g.RetractList(newRoot);
Console.WriteLine($"RetractList : {avant} -> {g.Triples.Count} triplets");AssertList : [true^^http://www.w3.org/2001/XMLSchema#boolean, false^^http://www.w3.org/2001/XMLSchema#boolean]
RetractList : 11 -> 7 triplets
Résultat obtenu : AssertList crée une liste [true, false] en générant des noeuds blancs et triplets associés. RetractList supprime tous les triplets de la liste.
| Opération | Triplets avant | Triplets après | Delta |
|---|---|---|---|
| AssertList | 7 (1 ancrage + liste 1,2,3 du §5.1) | 11 (7 + 4 nouveaux pour [true, false]) | +4 |
| RetractList | 11 | 7 | -4 |
Pourquoi AssertList génère des noeuds blancs :
Chaque élément de la liste est stocké dans un triplet rdf:first, et le chaînage est stocke dans un triplet rdf:rest. Les éléments intermédiaires (noeuds _:b1, _:b2, …) sont des noeuds blancs qui n’ont pas d’IRI – ils sont uniquement références dans le graphe.
Pourquoi RetractList supprime récursivement :
RetractList(root) commence par le noeud racine, recupere le rdf:rest suivant, et se rappelle récursivement sur le noeud suivant. A la fin, tous les triplets de la liste sont retirés du graphe. Le noeud racine lui-même n’est pas détruit (c’est à l’appelant de le gérer s’il est ancré ailleurs).
Trois cas d’usage :
Coût : ~0.1 seconde (AssertList + RetractList sur une liste de 3 éléments).
Ajout et suppression d’éléments dans une liste RDF existante avec AddToList et RemoveFromList.
// 5.3 AddToList et RemoveFromList
Console.WriteLine($"Avant : [{string.Join(", ", g.GetListItems(root))}]");
g.AddToList(root, new List<INode>() { (true).ToLiteral(g), (false).ToLiteral(g) });
Console.WriteLine($"AddToList : [{string.Join(", ", g.GetListItems(root))}]");
g.RemoveFromList(root, new List<INode>() { (true).ToLiteral(g), (false).ToLiteral(g) });
Console.WriteLine($"RemoveFromList: [{string.Join(", ", g.GetListItems(root))}]");Avant : [1^^http://www.w3.org/2001/XMLSchema#integer, 2^^http://www.w3.org/2001/XMLSchema#integer, 3^^http://www.w3.org/2001/XMLSchema#integer]
AddToList : [1^^http://www.w3.org/2001/XMLSchema#integer, 2^^http://www.w3.org/2001/XMLSchema#integer, 3^^http://www.w3.org/2001/XMLSchema#integer, true^^http://www.w3.org/2001/XMLSchema#boolean, false^^http://www.w3.org/2001/XMLSchema#boolean]
RemoveFromList: [1^^http://www.w3.org/2001/XMLSchema#integer, 2^^http://www.w3.org/2001/XMLSchema#integer, 3^^http://www.w3.org/2001/XMLSchema#integer]
| Opération | Méthode | Comportement |
|---|---|---|
| Créer | AssertList(elements) |
Génère noeuds blancs + triplets |
| Supprimer tout | RetractList(root) |
Suppression récursive |
| Ajouter | AddToList(root, elements) |
Ajoute en fin de liste |
| Retirer | RemoveFromList(root, elements) |
Supprime toutes les occurrences |
Pourquoi AddToList ajoute en fin :
C’est la convention pour les listes chaînées – pour ajouter en tête, il faudrait créer un nouveau noeud racine et transférer tous les éléments, ce qui est coûteux. Ajouter en fin est O(1) si on connaît le dernier noeud, ou O(N) sinon (on doit traverser toute la liste pour trouver le dernier).
Pourquoi RemoveFromList supprime toutes les occurrences :
Si un élément apparaît plusieurs fois dans la liste (par exemple, [1, 2, 1, 3]), la suppression de 1 doit supprimer les deux occurrences. C’est la sémantique de “toutes les occurrences” – alternative à RemoveFirst qui ne supprime que la première.
Trois nuances :
AddToList crée de nouveaux noeuds blancs pour les nouveaux éléments, distincts des noeuds existants.RemoveFromList peut laisser des trous dans la liste si la suppression crée des noeuds blancs orphelins. Dans ce cas, un appel ultérieur à AssertList peut rééquilibrer.[1, "hello", true]. Le typage est à la charge de l’ontologie (schema.org, FOAF, etc.).Coût : ~0.1 seconde (chaque opération sur une liste de 3-5 éléments).
Chargez data/animals.ttl et affichez : le nombre total de triplets, les triplets rdf:type, et les propriétés de ex:rex.
Algorithme :
// Chargement du graphe depuis animals.ttl avec TurtleParser
Graph g = new Graph();
new TurtleParser().Load(g, "data/animals.ttl");
// Affichage du nombre total de triplets dans le graphe chargé
Console.WriteLine($"Total triplets : {g.Triples.Count}");
// Sélection de tous les triplets dont le prédicat est rdf:type
// rdf:type indique la classe d'appartenance d'une ressource (ex : rex est de type Dog)
IUriNode rdfType = g.CreateUriNode("rdf:type");
Console.WriteLine("Triplets rdf:type :");
foreach (Triple t in g.GetTriplesWithPredicate(rdfType))
Console.WriteLine($" {t}");
// Récupération du nœud URI de ex:rex par son IRI complet
// GetTriplesWithSubject retourne toutes les propriétés dont rex est le sujet
IUriNode rex = g.GetUriNode(new Uri("http://example.org/animals#rex"));
Console.WriteLine("Proprietes de rex :");
foreach (Triple t in g.GetTriplesWithSubject(rex))
Console.WriteLine($" {t}");Sortie attendue :
Total triplets : 51
Triplets rdf:type : 6 (classes) + 4 (proprietes) + 4 (instances) = 14
Proprietes de rex : 4 (type=Dog, name=Rex, age=5, sound=Woof)
Pourquoi cet exemple est fondamental :
C’est le workflow de base du Semantic Web en .NET : charger un graphe depuis un fichier sérialisé, l’interroger avec les méthodes de sélection, et afficher les résultats. La majorité des applications RDF commencent par ce pattern.
Trois extensions possibles :
Coût : ~0.5 seconde (chargement + 3 sélections + affichage).
// Chargement du graphe depuis animals.ttl avec TurtleParser
Graph g = new Graph();
new TurtleParser().Load(g, "data/animals.ttl");
// Affichage du nombre total de triplets dans le graphe chargé
Console.WriteLine($"Total triplets : {g.Triples.Count}");
// Sélection de tous les triplets dont le prédicat est rdf:type
// rdf:type indique la classe d'appartenance d'une ressource (ex : rex est de type Dog)
IUriNode rdfType = g.CreateUriNode("rdf:type");
Console.WriteLine("Triplets rdf:type :");
foreach (Triple t in g.GetTriplesWithPredicate(rdfType))
Console.WriteLine($" {t}");
// Récupération du nœud URI de ex:rex par son IRI complet
// GetTriplesWithSubject retourne toutes les propriétés dont rex est le sujet
IUriNode rex = g.GetUriNode(new Uri("http://example.org/animals#rex"));
Console.WriteLine("Proprietes de rex :");
foreach (Triple t in g.GetTriplesWithSubject(rex))
Console.WriteLine($" {t}");Total triplets : 51
Triplets rdf:type :
http://example.org/animals#Animal , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Mammal , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Bird , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Dog , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Cat , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#Parrot , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/2000/01/rdf-schema#Class
http://example.org/animals#name , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#age , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#sound , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#canFly , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
http://example.org/animals#rex , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
http://example.org/animals#minou , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Cat
http://example.org/animals#coco , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Parrot
http://example.org/animals#buddy , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
Proprietes de rex :
http://example.org/animals#rex , http://www.w3.org/1999/02/22-rdf-syntax-ns#type , http://example.org/animals#Dog
http://example.org/animals#rex , http://example.org/animals#name , Rex^^http://www.w3.org/2001/XMLSchema#string
http://example.org/animals#rex , http://example.org/animals#age , 5^^http://www.w3.org/2001/XMLSchema#integer
http://example.org/animals#rex , http://example.org/animals#sound , Woof^^http://www.w3.org/2001/XMLSchema#string
Lecture de l’exemple 1 (charger et explorer) (cellule code[14]) :
// Chargement du graphe depuis animals.ttl avec TurtleParser
Graph g = new Graph();
new TurtleParser().Load(g, "data/animals.ttl");
// Affichage du nombre total de triplets dans le graphe chargé
Console.WriteLine($"Total triplets : {g.Triples.Count}");
// Sélection de tous les triplets dont le prédicat est rdf:type
// rdf:type indique la classe d'appartenance d'une ressource (ex : rex est de type Dog)
IUriNode rdfType = g.CreateUriNode("rdf:type");
Console.WriteLine("Triplets rdf:type :");
foreach (Triple t in g.GetTriplesWithPredicate(rdfType))
Console.WriteLine($" {t}");
// Récupération du nœud URI de ex:rex par son IRI complet
// GetTriplesWithSubject retourne toutes les propriétés dont rex est le sujet
IUriNode rex = g.GetUriNode(new Uri("http://example.org/animals#rex"));
Console.WriteLine("Proprietes de rex :");
foreach (Triple t in g.GetTriplesWithSubject(rex))
Console.WriteLine($" {t}");Trois opérations de l’exemple :
new TurtleParser().Load(g, "data/animals.ttl") charge le fichier animals.ttl dans le graphe g.g.Triples.Count donne le nombre total de triplets.g.GetTriplesWithPredicate(rdfType) filtre les triplets ayant rdf:type comme prédicat.Pourquoi cet exemple est le workflow de base :
C’est le pattern standard de toute application Semantic Web en .NET : charger un graphe sérialisé, l’interroger avec les méthodes de sélection, et afficher les résultats. La majorité des notebooks SW-* du depot commencent par ce pattern.
Trois extensions possibles :
Coût : ~0.5 seconde (chargement + sélection + affichage).
Chargez data/animals.ttl et trouvez :
rdf:type -> ex:Dog) en utilisant GetTriplesWithPredicateObjectex:canFly avec valeur true)ex:age)Indices : utilisez GetTriplesWithPredicateObject(predicate, object) pour les sélections combinees, et LINQ pour les aggregations.
Protocole attendu :
// 1. Tous les chiens
var dogs = g.GetTriplesWithPredicateObject(
g.CreateUriNode("rdf:type"),
g.CreateUriNode("ex:Dog")
).Select(t => t.Subject);
// 2. Animaux qui volent
var flying = g.GetTriplesWithPredicateObject(
g.CreateUriNode("ex:canFly"),
(true).ToLiteral(g)
).Count();
// 3. Animal le plus age
var ages = g.GetTriplesWithPredicate(g.CreateUriNode("ex:age"))
.Select(t => new { Subject = t.Subject, Age = int.Parse(t.Object.ToString()) })
.OrderByDescending(x => x.Age)
.First();Sortie attendue :
Chiens : rex (Rex), buddy (Buddy)
Animaux qui volent : 1 (coco le Perroquet)
Animal le plus age : coco (12 ans)
Pourquoi cet exercice est intéressant :
Il combine 3 types d’aggregations : - Sélection par prédicat-objet (chiens) - Comptage avec prédicat et valeur (volants) - Tri et max (plus age)
C’est le pattern classique des requêtes RDF : on selectionne, on filtre, on agrège.
Coût : ~5 minutes pour l’étudiant (3 sous-questions + LINQ).
// TODO etudiant : Chargez data/animals.ttl et trouvez :
// 1. Tous les animaux qui sont des chiens (rdf:type -> ex:Dog)
// 2. Le nombre d'animaux qui peuvent voler (predicat ex:canFly avec valeur true)
// 3. L'animal le plus age (maximum de ex:age)
//
// Indice : utilisez GetTriplesWithPredicateObject pour (1)
// et LINQ .Where() + .Max() pour (3)
Console.WriteLine("Exercice a completer");Exercice a completer
Chargez data/Example.ttl et data/animals.ttl, fusionnez-les, et ecrivez le résultat en Turtle compresse.
Algorithme :
// 1. Charger les deux graphes
var g1 = new Graph();
new TurtleParser().Load(g1, "data/Example.ttl");
var g2 = new Graph();
new TurtleParser().Load(g2, "data/animals.ttl");
// 2. Fusionner (g1 absorbe g2)
g1.Merge(g2);
Console.WriteLine($"Apres fusion : {g1.Triples.Count} triplets");
// 3. Serialiser en Turtle compresse
string result = VDS.RDF.Writing.StringWriter.Write(g1, new CompressingTurtleWriter());
Console.WriteLine(result);Sortie attendue :
Avant fusion - g1: 4, g2: 51
Apres fusion : 55 triplets (4 de Example + 51 de animals, zero doublon)
suivi de la serialisation Turtle compressee du resultat
Pourquoi cet exemple est pratique :
L’agrégation de graphes est un cas d’usage classique : on charge des sources hétérogènes (personnes, animaux, produits, etc.), on les fusionne en un graphe unifié, et on sérialise le résultat pour le publier ou le requêter.
Trois variantes de fusion :
g1.Merge(g2) – asymétrique, modifie g1.HashSet<Triple> pour éviter les doublons.Coût : ~0.5 seconde (2 chargements + 1 fusion + 1 sérialisation).
// Chargement des deux graphes : personnes (Example.ttl) et animaux (animals.ttl)
Graph g1 = new Graph();
Graph g2 = new Graph();
new TurtleParser().Load(g1, "data/Example.ttl");
new TurtleParser().Load(g2, "data/animals.ttl");
Console.WriteLine($"Avant fusion - g1: {g1.Triples.Count}, g2: {g2.Triples.Count}");
// Merge de g2 dans g1 : les triplets identiques ne sont pas dupliqués (sémantique d'ensemble RDF)
// Les nœuds blancs sont automatiquement renommés pour éviter les collisions
g1.Merge(g2);
Console.WriteLine($"Apres fusion : {g1.Triples.Count} triplets");
string result = VDS.RDF.Writing.StringWriter.Write(g1, new CompressingTurtleWriter());
Console.WriteLine(result);Avant fusion - g1: 4, g2: 51
Apres fusion : 55 triplets
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
@prefix xsd: <http://www.w3.org/2001/XMLSchema#>.
@prefix dc: <http://purl.org/dc/elements/1.1/>.
@prefix ex: <http://example.org/stuff/1.0/>.
@prefix ns0: <http://example.org/animals#>.
ns0:Animal a rdfs:Class;
rdfs:comment "Classe racine de tous les animaux"@fr;
rdfs:label "Animal"@fr.
ns0:Bird a rdfs:Class;
rdfs:label "Oiseau"@fr;
rdfs:subClassOf ns0:Animal.
ns0:Cat a rdfs:Class;
rdfs:label "Chat"@fr;
rdfs:subClassOf ns0:Mammal.
ns0:Dog a rdfs:Class;
rdfs:label "Chien"@fr;
rdfs:subClassOf ns0:Mammal.
ns0:Mammal a rdfs:Class;
rdfs:label "Mammifere"@fr;
rdfs:subClassOf ns0:Animal.
ns0:Parrot a rdfs:Class;
rdfs:label "Perroquet"@fr;
rdfs:subClassOf ns0:Bird.
ns0:age a rdf:Property;
rdfs:domain ns0:Animal;
rdfs:label "age"@fr;
rdfs:range xsd:integer.
ns0:buddy ns0:age 7 ;
ns0:name "Buddy";
ns0:sound "Woof woof";
a ns0:Dog.
ns0:canFly a rdf:Property;
rdfs:domain ns0:Animal;
rdfs:label "peut voler"@fr;
rdfs:range xsd:boolean.
ns0:coco ns0:age 12 ;
ns0:canFly true;
ns0:name "Coco";
ns0:sound "Coco veut un gateau";
a ns0:Parrot.
ns0:minou ns0:age 3 ;
ns0:name "Minou";
ns0:sound "Miaou";
a ns0:Cat.
ns0:name a rdf:Property;
rdfs:domain ns0:Animal;
rdfs:label "nom"@fr;
rdfs:range xsd:string.
ns0:rex ns0:age 5 ;
ns0:name "Rex";
ns0:sound "Woof";
a ns0:Dog.
ns0:sound a rdf:Property;
rdfs:domain ns0:Animal;
rdfs:label "cri"@fr;
rdfs:range xsd:string.
<http://www.w3.org/TR/rdf-syntax-grammar> ex:editor [ex:fullname "Dave Beckett" ;
ex:homePage <http://purl.org/net/dajobe/>];
dc:title "RDF/XML Syntax Specification (Revised)".
Chargez data/animals.ttl dans un graphe et la chaîne Turtle suivante dans un autre graphe avec StringParser.Parse :
<http://example.org/ns#dave> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://example.org/ns#Person> .
<http://example.org/ns#dave> <http://example.org/ns#name> "Dave" .
<http://example.org/ns#dave> <http://example.org/ns#age> "42" .
Fusionnez les deux graphes et sérialisez en Turtle compresse.
Indice : utilisez StringParser.Parse(g, turtleString) pour parser une chaîne Turtle, puis g1.Merge(g2).
Protocole attendu :
// 1. Charger animals.ttl dans g1
var g1 = new Graph();
new TurtleParser().Load(g1, "data/animals.ttl");
// 2. Charger la chaine Turtle dans g2
var g2 = new Graph();
StringParser.Parse(g2, turtleString);
// 3. Fusionner
g1.Merge(g2);
Console.WriteLine($"Total apres fusion : {g1.Triples.Count} triplets");
// 4. Serialiser
var sw = new System.IO.StringWriter();
new CompressingTurtleWriter().Save(g1, sw);
Console.WriteLine(sw.ToString());Sortie attendue : un graphe avec 54 triplets (51 d’animals + 3 de Dave), sérialisé en Turtle compresse.
Pourquoi utiliser StringParser :
StringParser.Parse(g, str) détecte automatiquement le format depuis la chaîne (Turtle, NTriples, RDF/XML, JSON-LD). C’est utile quand le format est inconnu ou varie. Pour des chaînes Turtle connues, on peut utiliser directement TurtleParser().Load(g, new StringReader(str)).
Coût : ~5 minutes pour l’étudiant (parsing string + fusion + sérialisation).
// TODO etudiant : Chargez data/animals.ttl dans un graphe g1
// Chargez la chaine Turtle ci-dessous dans un graphe g2 avec StringParser.Parse
// Fusionnez g2 dans g1 et affichez le nombre total de triplets
// Serialisez le resultat en NTriples (pas Turtle compresse)
//
// Indice : utilisez new NTriplesWriter() pour la serialisation
Console.WriteLine("Exercice a completer");Exercice a completer
Créez une liste ["Alice", "Bob", "Charlie"] avec AssertList, ajoutez "Diana", supprimez "Bob", affichez le résultat.
Algorithme :
// Création d'une liste RDF ordonnée [Alice, Bob, Charlie]
// AssertList génère une chaîne de nœuds blancs reliés par rdf:first et rdf:rest
IGraph g = new Graph();
var root = g.AssertList(new List<INode>() { "Alice".ToLiteral(g), "Bob".ToLiteral(g), "Charlie".ToLiteral(g) });
// Fonction utilitaire pour afficher uniquement la valeur textuelle de chaque noeud littéral
Func<IEnumerable<INode>, string> fmt = nodes => string.Join(", ", nodes.Select(n => ((ILiteralNode)n).Value));
Console.WriteLine($"Initial : [{fmt(g.GetListItems(root))}]");
// AddToList insère "Diana" en fin de liste en ajoutant un nœud blank supplémentaire
g.AddToList(root, new List<INode>() { "Diana".ToLiteral(g) });
Console.WriteLine($"Apres ajout : [{fmt(g.GetListItems(root))}]");
// RemoveFromList supprime toutes les occurrences de "Bob" dans la liste
g.RemoveFromList(root, new List<INode>() { "Bob".ToLiteral(g) });
Console.WriteLine($"Apres suppression: [{fmt(g.GetListItems(root))}]");Sortie attendue :
Initial : [Alice, Bob, Charlie]
Apres ajout : [Alice, Bob, Charlie, Diana]
Apres suppression: [Alice, Charlie, Diana]
Pourquoi cet exemple est complet :
Il couvre les 4 opérations de base sur les listes RDF : AssertList (creation), AddToList (ajout), RemoveFromList (suppression), GetListItems (lecture). C’est le pattern standard pour manipuler des collections en RDF.
Trois nuances :
INode : on peut mélanger literal nodes (strings, nombres) et URI nodes dans la même liste.RemoveFromList supprime toutes les occurrences : si Bob apparaît 2 fois, les 2 sont supprimées.Coût : ~0.1 seconde (AssertList + Add + Remove + GetListItems).
// Création d'une liste RDF ordonnée [Alice, Bob, Charlie]
// AssertList génère une chaîne de nœuds blancs reliés par rdf:first et rdf:rest
IGraph g = new Graph();
var root = g.AssertList(new List<INode>() { "Alice".ToLiteral(g), "Bob".ToLiteral(g), "Charlie".ToLiteral(g) });
// Fonction utilitaire pour afficher uniquement la valeur textuelle de chaque noeud littéral
Func<IEnumerable<INode>, string> fmt = nodes => string.Join(", ", nodes.Select(n => ((ILiteralNode)n).Value));
Console.WriteLine($"Initial : [{fmt(g.GetListItems(root))}]");
// AddToList insère "Diana" en fin de liste en ajoutant un nœud blank supplémentaire
g.AddToList(root, new List<INode>() { "Diana".ToLiteral(g) });
Console.WriteLine($"Apres ajout : [{fmt(g.GetListItems(root))}]");
// RemoveFromList supprime toutes les occurrences de "Bob" dans la liste
g.RemoveFromList(root, new List<INode>() { "Bob".ToLiteral(g) });
Console.WriteLine($"Apres suppression: [{fmt(g.GetListItems(root))}]");Initial : [Alice, Bob, Charlie]
Apres ajout : [Alice, Bob, Charlie, Diana]
Apres suppression: [Alice, Charlie, Diana]
Créez une liste RDF ["Paris", "Lyon", "Marseille"] avec AssertList, ajoutez "Toulouse" avec AddToList, supprimez "Lyon" avec RemoveFromList. Affichez la liste après chaque opération.
Indice : reprendre l’exemple guide 3, changer les valeurs et les labels.
Protocole attendu :
var g = new Graph();
// Etape 1 : AssertList initial -- liste ["Paris", "Lyon", "Marseille"]
var root = g.AssertList(new List<INode> { /* valeurs a completer */ });
Console.WriteLine($"Apres creation : {g.GetListItems(root).Count()} elements");
// Etape 2 : ajouter "Toulouse" avec AddToList -- a completer
// Etape 3 : supprimer "Lyon" avec RemoveFromList -- a completer
// Etape 4 : affichage final
foreach (var item in g.GetListItems(root))
Console.WriteLine($" {item}");Sortie attendue :
Apres creation : 3 elements
Apres ajout Toulouse : 4 elements
Apres suppression Lyon : 3 elements
"Paris"
"Marseille"
"Toulouse"
Pourquoi cet exercice est progressif :
Il fait afficher l’état après chaque étape, ce qui force l’étudiant à vérifier que chaque opération a l’effet attendu. C’est un pattern de test incremental – préférable à un test final “tout ou rien”.
Coût : ~5 minutes pour l’étudiant (3 étapes + 4 affichages).
Trois ouvrages de reference :
Quatre outils complémentaires :
| Section | Concepts clés | APIs principales |
|---|---|---|
| 1. Lecture | Parsers, StringParser, Handlers | TurtleParser, NTriplesParser, CountHandler |
| 2. Écriture | Writers, configuration | NTriplesWriter, CompressingTurtleWriter, RdfXmlWriter |
| 3. Fusion | Merge, deduplication | IGraph.Merge() |
| 4. Sélection | LINQ sur graphes | GetTriplesWithSubject/Predicate/Object |
| 5. Listes RDF | AssertList, RetractList | AssertList, RetractList, AddToList, RemoveFromList |
Trois concepts clés à retenir :
(sujet, predicat, objet). Les listes sont encodees avec rdf:first/rdf:rest. Les graphes sont des ensembles de triplets (pas des multisets).Where(), Select(), GroupBy(), OrderBy() plutot que d’utiliser SPARQL.Pour aller plus loin :
LeviathanQueryProcessor (moteur SPARQL integre).Quatre idées forces transversales :
Merge() ne duplique pas les triplets identiques, et les noeuds blancs sont renommés pour éviter les collisions.rdf:Seq ou le stockage en mémoire.Trois références complémentaires :
Navigation : << 2-RDFBasics | Index | 4-SPARQL >>