========================= JSON-LD par la pratique ========================= :auteur: Pierre-Antoine Champin (http://champin.net/) :licence: `CC-BY-SA`_ .. role:: en .. role:: playground .. contents:: Table des matières :local: :backlinks: none Introduction ============ JSON-LD (`JSON for Linked Data`:en:) est une syntaxe concrète pour `RDF`_, basée sur le très populaire format `JSON`_. Mais c'est en même temps plus que cela. JSON-LD permet d'exprimer un graphe RDF en JSON sous une multitude de formes. Et inversement, il permet d'interpréter une large classe de documents JSON comme des graphes RDF. .. THIS IS THE OLD VERSION OF THE FIGURE graphviz: : :name: fig-algos :caption: Formes documentaires et algorithmes JSON-LD digraph { rankdir=LR R [label="RDF"] X [label="JSON-LD\nExpanded form"] C [label="JSON(-LD)\nCompact form"] R -> X [label="fromRdf"] X -> R [label="toRdf"] X -> C [label="compact"] C -> X [label="expand"] #C -> R [label="toRdf"; style=dashed, constraint=false] } .. figure:: _static/jsonld-algorithms.svg :name: fig-algos Formes documentaires et algorithmes JSON-LD JSON-LD est décrit par plusieurs recommandations du W3C: * `JSON-LD 1.1, a JSON-based serialization for Linked Data`_ décrit la syntaxe de JSON-LD, et définit deux *formes* pour cette syntaxe: la forme étendue, et la forme compacte (plus de détails ci-dessous). * `JSON-LD 1.1 processing algorithms and API`_ décrit les algorithmes permettant de passer d'une forme à l'autre, ainsi que de convertir JSON-LD vers et depuis d'autres formats RDF (représentés sur la `fig-algos`). .. note:: Ces recommandations décrivent en réalité d'autres formes et d'autres algorithmes, mais qui jouent un rôle moins importants pour les utilisateurs de JSON-LD. Nous ne les aborderons donc pas ici. De plus, il existe une troisième recommandation, `JSON-LD 1.1 framing`_, que nous n'aborderons pas non plus. L'objectif de ce document est d'initier le lecteur aux bases de JSON-LD. La forme étendue ================ Considérons le graphe RDF représenté ci-dessous en Turtle dans le `code-ttl-ex1`, et illustré par la `fig-ex1`. .. code-block:: text :name: code-ttl-ex1 :caption: Alice et Bob (Turtle) prefix s: prefix xsd: <#alice> a s:Person; s:name "Alice"; s:birthDate "1987-06-05"^^xsd:date; s:knows [ a s:Person; s:name "Bob"@en, "ぼぶ"@ja; s:knows <#alice> ]. .. graphviz:: :name: fig-ex1 :caption: Alice et Bob digraph { { rank=source Person [label="s:Person"] } { rank=same alice [label="#alice"] bob [label=""] } { rank=same node [shape=box] salice [label="Alice"] sboben [label="Bob"] sbobja [label="ぼぶ"] date [label="1987-06-05"] } alice -> Person [label="a"] alice -> salice [label="s:name"] alice -> date [label="s:birthDate"] alice -> bob [label="s:knows"] bob -> Person [label="a"] bob -> sboben [label="s:name"] bob -> sbobja [label="s:name"] bob -> alice [label="s:knows"] } Une représentation JSON-LD en forme étendue de ce graphe est donnée dans le `code-expanded-ex1a`:numref: ci-dessous. Pour cet exemple et dans tous les suivants, le lien «PLAYGROUND» permet de l'ouvrir dans le `JSON-LD playground`_, qui vous permet de tester interactivement les différents algorithmes de JSON-LD. .. code-block:: json :name: code-expanded-ex1a :caption: Alice et Bob (forme étendue) :playground:`table` [ { "@id": "http://example.com/#alice", "@type": [ "http://schema.org/Person" ], "http://schema.org/name": [ { "@value": "Alice" } ], "http://schema.org/birthDate": [ { "@value": "1987-06-05", "@type": "http://www.w3.org/2001/XMLSchema#date" } ], "http://schema.org/knows": [ { "@id": "_:b" } ] }, { "@id": "_:b", "@type": [ "http://schema.org/Person" ], "http://schema.org/name": [ { "@value": "Bob", "@language": "en" }, { "@value": "ぼぶ", "@language": "ja" } ], "http://schema.org/knows": [ { "@id": "http://example.com/#alice" } ] } ] Cet exemple illustre les différents principes de la forme étendue : * Le document est un tableau JSON. * Chaque élément de ce tableau représente un nœud du graph. C'est un objet JSON muni d'un attribut ``@id`` qui contient un IRI (1er élément dans l'exemple) ou un identifiant local commençant par ``_:`` dans le cas d'un nœud vide (`blank node`:en:, 2ème élément dans l'exemple). * Les autres attributs représentent les arcs sortants du nœud; le nom de l'attribut est l'IRI du prédicat, et la valeur est un tableau d'objets JSON, représentant le(s) nœud(s) destination(s) (exemple de valeurs multiples : l'attribut ``…/name`` du 2ème élément). * Lorsque le nœud destination d'un arc est un IRI ou un nœud vide, il est représenté par un objet JSON muni d'un attribut ``@id``, contenant l'IRI ou l'identifiant local correspondant (cf. les arcs ``…/knows`` dans l'exemple). * Lorsque le nœud destination d'un arc est un littéral, il contient un attribut ``@value`` contenant la valeur lexicale du littéral, et l'un ou l'autre des attributs suivants : - ``@language``, qui contient le code de la langue au format `BCP 47`_, dans le cas d'un chaîne linguistique (`language string`:en:, cf. les deux noms de Bob), - ``@type``, qui contient l'IRI du type de donnée, dans les autres cas (cf. la date de naissance d'Alice); - dans le cas particulier des littéraux de type ``xsd:string``, ``@type`` peut être omis (cf. le nom d'Alice). * Cas particulier: la propriété ``rdf:type`` est représentée par l'attribut ``@type``, dont la ou les valeurs sont représentées par un simple tableau d'IRIs. .. warning:: Comme on le voit: l'attribut ``@type`` joue deux rôle différents en JSON-LD *en fonction du contexte dans lequel il apparaît*. Utilisé conjointement à l'attribut ``@value``, il représente le type de donnée d'un littéral. Dans tous les autres cas, il représente un arc ``rdf:type``. .. admonition:: Exercice * Ouvrez le `code-expanded-ex1a` dans le `playground`:en:. Pour chacune des questions ci-dessous, vérifiez dans l'onglet Table ou N-Quads que les triplets ont bien été ajoutés ou modifiés. * Modifiez le JSON pour ajouter une date de naissance à Bob. * Modifiez le JSON pour préciser que le nom d'Alice est en anglais (``en``). * Ajoutez à Alice le type ``http://xmlns.com/foaf/0.1/Agent``. * Supprimez le nom japonnais de Bob. Il est également possible d'imbriquer hiérarchiquement les descriptions des nœuds : .. admonition:: Exercice * En repartant du `code-expanded-ex1a` dans le `playground`:en:, déplacez le 2ème élément du tableau global (celui qui représente Bob avec tous ses attributs) comme valeur dans la propriété ``…/knows`` du 1er élément (qui représente Alice), en remplacement de l'objet JSON ``{ "@id": "_:b" }``. * Constatez que les triplets générés restent les mêmes. Pour la suite, nous partirons de la forme étendue imbriquée produite par l'exercice ci-dessus, et qui est rappelée dans le `code-expanded-ex1b` ci-dessous. .. code-block:: json :name: code-expanded-ex1b :caption: Alice et Bob (forme étendue imbriquée) :playground:`table` [ { "@id": "http://example.com/#alice", "@type": [ "http://schema.org/Person" ], "http://schema.org/name": [ { "@value": "Alice" } ], "http://schema.org/birthDate": [ { "@value": "1987-06-05", "@type": "http://www.w3.org/2001/XMLSchema#date" } ], "http://schema.org/knows": [ { "@id": "_:b", "@type": [ "http://schema.org/Person" ], "http://schema.org/name": [ { "@value": "Bob", "@language": "en" }, { "@value": "ぼぶ", "@language": "ja" } ], "http://schema.org/knows": [ { "@id": "http://example.com/#alice" } ] } ] } ] La forme compacte ================= La forme étendue que nous venons de voir reflète fidèlement la structure du graphe. Elle permet aux algorithmes ``toRdf`` et ``fromRdf`` (cf. `fig-algos`) de passer facilement entre JSON-LD et les autres syntaxes RDF. Mais le document JSON résultant est très verbeux, et très peu *idiomatique*. En d'autres termes, il ne ressemble pas du tout aux documents JSON habituellement manipulés par les développeurs. L'algorithme ``compact`` permet de transformer ce document sous forme étendue dans une forme moins verbeuse et plus idiomatique. Il utilise pour cela un objet JSON particulier appelé **contexte**, qui permet de paramétrer la transformation effectuée par l'algorithme ``compact``. Une stratégie habituelle en RDF pour réduire la verbosité (que l'on peut voir à l'œuvre dans le `code-ttl-ex1`) est de définir des préfixes, qui serviront ensuite à abréger les IRIs (exemple : ``s:name`` au lieu de ``http://schema.org/name``), ou à utiliser des IRIs relatifs (exemple : ``#alice``). Voici un exemple de contexte JSON-LD définissant l'IRI de base, et un préfixe pour les IRIs du vocabulaire Schema.org. .. code-block:: json :name: code-context-prefixes :caption: Contexte simple :playground:`compacted code-expanded-ex1b` { "@context": { "@base": "http://example.com/", "s": "http://schema.org/" } } Le résultat de la compaction du `code-expanded-ex1b` avec le contexte ci-dessus, que vous pouvez vérifier en cliquant sur le lien `playground`:en: du contexte, donne le résultat suivant : .. code-block:: json :name: code-compact-ex1a :caption: Forme compacte avec des préfixes :playground:`table` { "@context": { "@base": "http://example.com/", "s": "http://schema.org/" }, "@id": "#alice", "@type": "s:Person", "s:birthDate": { "@type": "http://www.w3.org/2001/XMLSchema#date", "@value": "1987-06-05" }, "s:knows": { "@id": "_:b", "@type": "s:Person", "s:knows": { "@id": "#alice" }, "s:name": [ { "@language": "en", "@value": "Bob" }, { "@language": "ja", "@value": "ぼぶ" } ] }, "s:name": "Alice" } On constate que : * Le contexte a été intégré au document (afin que ce dernier demeure autosuffisant). * Chaque tableau ne contenant qu'un seul élément a été remplacé par cet élément (exemples : attributs ``@type``, ``s:knows``). * Chaque objets JSON contenant uniquement un attribut ``@value`` a été remplacé par la valeur de cet attribut (exemple : le nom d'Alice). * Les IRIs correspondant aux préfixes déclarés ont été abrégés en utilisant ces préfixes (en l'occurrence, ceux commençant par ``http://schema.org/``). * Les autres IRIs ont été laissés tels quels (exemple : le type de la date de naissance d'Alice). * Les IRIs valeurs de l'attribut ``@id`` ont été remplacés par des IRIs relatifs à l'IRI de base (quand c'était possible). .. admonition:: Exercice * Ajoutez un préfixe ``xsd`` pour que le type de la date de naissance soit également abrégé. * Remplacez l'IRI de base par ``http://example.com/toto/``. Que constatez vous sur les attributs ``@id``? * Remplacez l'IRI de base par ``http://autre.example.com/``. Que constatez vous sur les attributs ``@id``? Ajout d'alias +++++++++++++ La forme compacte du `code-compact-ex1a` est déjà bien moins verbeuse que la forme étendue, mais elle n'est pas encore très idiomatique. En particulier, on évite en général d'avoir des caractères spéciaux dans les noms des attributs, car ceux-ci empêchent d'accéder aux attributs avec la notation ``objet.attribut`` (en Javascript, notamment). En plus des préfixes, le contexte JSON-LD nous permet de définir des **alias**, qui remplaceront complètement l'IRI correspondant. Ceci est illustré par le `code-context-alias` ci-dessous. .. code-block:: json :name: code-context-alias :caption: Contexte définissant des alias :playground:`compacted code-expanded-ex1b` { "@context": { "@base": "http://example.com/", "s": "http://schema.org/", "xsd": "http://www.w3.org/2001/XMLSchema#", "name": "http://schema.org/name", "knows": "http://schema.org/knows" } } Ouvrez le `playground`:en: et constatez que l'attribut ``s:name`` et devenu ``name``, et que ``s:knows`` est devenu ``knows``. .. hint:: Il n'y a pas de distinction entre les préfixes et les alias. En fait, l'algorithme ``compact`` essaye de raccourcir au maximum les IRIs; s'il trouve dans le contexte une entrée qui correspond exactement à l'IRI, il remplace directement par cette entrée (alias); s'il ne trouve qu'une entrée correspondant au début de l'IRI, il remplace par une abréviation ``prefixe:suffixe``. .. admonition:: Exercice * Ouvrez le `playground`:en: pour le `code-context-alias`. * Dans le contexte, remplacez ``"name": "http://schema.org/name"`` par ``"name": "s:name"``, et constatez que le résultat reste inchangé. * Ajoutez un alias ``born`` pour l'IRI ``http://schema.org/birthDate``, et constatez que cet attribut est bien remplacé par l'alias. * Ajoutez au contexte une entrée ``"ident": "@id"``. Constatez que les attributs ``@id`` ont bien été remplacés. * Ajoutez au contexte un alias ``Person`` pour l'IRI ``http://schema.org/Person``. Que constatez-vous ? * Ajoutez les alias manquants pour qu'aucun attribut dans le document (à part ``@context`` et son contenu) ne contienne de caractères spéciaux (``@``, ``:``...). Dans l'exercice précédent, on a vu que: * on peut utiliser les préfixes à l’intérieur même du contexte ; * les alias ne doivent pas forcément correspondre à la fin de l’IRI ou au mot-clé ; * on peut définir des alias même sur les mots-clés de JSON-LD (commençant par ``@``), à part ``@context`` ; * les alias s'appliquent aux propriétés, mais également aux valeurs de ``@type``. Ceci nous donne le contexte suivant : .. code-block:: json :name: code-context-alias2 :caption: Contexte définissant des alias :playground:`compacted code-expanded-ex1b` { "@context": { "@base": "http://example.com/", "s": "http://schema.org/", "xsd": "http://www.w3.org/2001/XMLSchema#", "name": "s:name", "born": "s:birthDate", "knows": "s:knows", "Person": "s:Person", "date": "xsd:date", "ident": "@id", "type": "@type", "lang": "@language", "val": "@value" } } Coercion de types +++++++++++++++++ Le contexte ci-dessus produit désormais un objet JSON avec la forme suivante (si on « escamote » le contexte). .. code-block:: json :name: code-compact-ex1b :caption: Contexte définissant des alias { "@context": {}, "ident": "#alice", "type": "Person", "born": { "type": "date", "val": "1987-06-05" }, "knows": { "ident": "_:b", "type": "Person", "knows": { "ident": "#alice" }, "name": [ { "lang": "en", "val": "Bob" }, { "lang": "ja", "val": "ぼぶ" } ] }, "name": "Alice" } C'est déjà plus idiomatique que le `code-compact-ex1a`, mais ça n'est pas encore parfait. Il semble par exemple superflu de spécifier le type de données de la valeur de ``born``, qui sera a priori toujours une date. On peut spécifier ceci dans le contexte en remplaçant la définition de ``born`` (actuellement une simple chaîne contenant l'IRI) par un object JSON décrivant à la fois l'IRI et le type de données attendu : .. code-block:: json :name: code-termdef-born :caption: définition détaillée du terme ``born`` dans le contexte: { "@id": "s:birthDate", "@type": "xsd:date" } Il faut comprendre que ``@id`` indique l'IRI correspondant à l'attribut ainsi défini, et que ``@type`` s'applique *aux valeurs* que prend cet attribut. .. admonition:: Exercice * Dans le contexte du `code-context-alias2`, remplacez la définition de ``born`` par la définition détaillée ci-dessous. * Constatez que la valeur de ``born`` dans la forme compacte est maintenant une simple chaîne de caractère. Le type de données est maintenant implicitement porté par le contexte. * Gardez ce contexte modifié à portée de main, vous l'utiliserez dans l'exercice suivant. De la même manière, ``knows`` est toujours un lien vers une autre ressource. Lorsque cet objet est décrit en détail (comme c'est le cas pour l'attribut ``knows`` d'Alice), il est normal que la valeur de ``knows`` soit un objet JSON, mais lorsqu'il est simplement référencé (comme c'est le cas pour l'attribut ``knows`` de Bob, qui « pointe » simplement vers Alice), on pourrait se contenter d'une simple chaîne de caractère contenant son identifiant. Ceci est indiqué de manière similaire au cas précédent, en indiquant ``@id`` comme type de valeur. .. code-block:: json :name: code-termdef-knows :caption: définition détaillée du terme ``knows`` dans le contexte: { "@id": "s:knows", "@type": "@id" } .. admonition:: Exercice * Dans le contexte de l'exercice précédent, remplacez la définition de ``knows`` par la définition détaillée ci-dessous. Constatez que la valeur de ``knows`` dans la forme compacte est remplacée, quand c'est possible, par une simple chaîne de caractères. * Gardez ce contexte modifié à portée de main, vous l'utiliserez dans l'exercice suivant. Coercion de langues et alias multiples ++++++++++++++++++++++++++++++++++++++ La représentation des noms de Bob dans plusieurs langues n'est pas non plus très idiomatique. Il serait préférable, par exemple, d'avoir plusieurs attributs JSON différents, portant à la fois l'information du prédicat RDF et l'information de la langue de la valeur -- selon un processus similaire à la coercion de type vue juste avant. On peut obtenir ce résultat en ajoutant au contexte les deux définitions suivantes : .. code-block:: json :name: code-termdef-name-lang :caption: un exemple de contexte avec coercion de langue { "@context": { "name_en": { "@id": "http://schema.org/name", "@language": "en" }, "name_ja": { "@id": "http://schema.org/name", "@language": "ja" } } } .. admonition:: Exercice * Dans le contexte de l'exercice précédent, incluez les définitions de ``name_en`` et ``name_ja`` comme dans le `code-termdef-name-lang`. * Constatez que l'attribut ``name`` de Bob a été remplacé par deux attributs ``name_en`` et ``name_ja`` dont les valeurs sont de simples chaînes. En plus de la coercion de langue, l'exercice précédent démontre une propriété importante de JSON-LD: **le même IRI peut avoir plusieurs alias**. L'algorithme ``compact`` choisit le plus approprié, c'est à dire celui qui produira la forme la moins verbeuse. .. tip:: Une autre manière idiomatique, en JSON, pour représenter une même valeur dans plusieurs langues, consiste à utiliser un objet dont les clés sont les codes de langues, comme dans l'exemple ci-dessous. Il est possible de paramétrer le contexte JSON-LD pour obtenir `ce type de représentation`__, mais cela dépasse le cadre de ce tutoriel. .. code-block:: json { "ident": "_:b", "name": { "en": "Bob", "ja": "ぼぶ" } } __ https://www.w3.org/TR/json-ld11/#language-indexing À titre indicatif, le `code-compact-ex1c` contient la forme compacte (incluant le contexte) à laquelle vous devriez avoir abouti à l'issu des exercices précédents. On a clairement séparé le contexte (qui peut être ignoré par les développeurs) du reste de l'objet JSON. Ceci met bien en évidence la différence entre la forme compacte, succinte et idiomatique, et la forme étendue du `code-expanded-ex1a`. Le lien « PLAYGROUND » permet également de vérifier que l'algorithme ``expand`` permet de reconstruire, à partir de cette forme compacte munie de son contexte, la forme étendue initiale. .. code-block:: :name: code-compact-ex1c :caption: corrigé des exercices précédents :playground:`expanded` { "@context": { "@base": "http://example.com/", "s": "http://schema.org/", "xsd": "http://www.w3.org/2001/XMLSchema#", "name": "s:name", "name_en": { "@id": "name", "@language": "en" }, "name_ja": { "@id": "name", "@language": "ja" }, "born": { "@id": "s:birthDate", "@type": "xsd:date" }, "knows": { "@id": "s:knows", "@type": "@id" }, "Person": "s:Person", "date": "xsd:date", "ident": "@id", "type": "@type", "lang": "@language", "val": "@value" }, "ident": "#alice", "type": "Person", "born": "1987-06-05", "knows": { "ident": "_:b", "type": "Person", "knows": "#alice", "name_en": "Bob", "name_ja": "ぼぶ" }, "name": "Alice" } Convertir du JSON natif en RDF ============================== Dans les sections précédentes, nous sommes partis d'un graphe RDF, et nous avons montré comment JSON-LD permettait d'exprimer ce graphe sous forme d'un document JSON compact et idiomatique, à partir duquel le graphe RDF peut être reconstruit. Ceci permet d'envisager un autre usage de JSON-LD : ré-interpréter comme des graphes RDF des documents JSON existants, non conçus a priori pour représenter du RDF. Il suffit pour cela de leur ajouter un contexte appoprié, et de leur appliquer l'algorithme ``expand`` puis ``toRdf``. .. hint:: En fait, on peut appliquer ``toRdf`` directement, car de dernier accepte également des documents sous forme compacte; dans ce cas, il applique préalablement ``expand``. Il existe 3 manières d'ajouter un contexte à un document JSON quelconque : * ajouter un attribut ``@context`` contenant l'objet JSON décrivant le contexte (comme vu précédemment, par exemple dans le `code-compact-ex1c`) ; * ajouter un attribut ``@context`` contenant l'URL d'un document JSON décrivant le contexte, comme illustré ci-dessous dans le `code-compact-ex1d`) ; * sans modifier le document JSON, mais en fournissant le contexte séparément à l'algorithme ``expand`` ou ``toRdf`` (cette option, nommée ``expandContext``, n'est pas disponible dans le `playground`:en:, mais est offerte par les bibliothèques implémentant l'API JSON-LD). .. code-block:: json :name: code-compact-ex1d :caption: JSON-LD sous forme compacte, avec un contexte distant :playground:`table` { "@context": "http://champin.net/2020/json-ld-tuto/_static/context-ex1.jsonld", "ident": "#alice", "type": "Person", "name": "Alice", "born": "1987-06-05", "knows": { "ident": "_:b", "type": "Person", "name_en": "Bob", "name_ja": "ぼぶ", "knows": "#alice" } } Inverser le sens d'un arc +++++++++++++++++++++++++ Dans certaines circonstances, la direction des attributs JSON est inversée par rapport à la direction des arcs que l'on souhaite produite dans le graphe RDF. Considérons par exemple le JSON du `code-json-ex2` ci-dessous. .. code-block:: json :name: code-json-ex2 :caption: format JSON standard pour des données généalogiques { "name": "Alice", "parents": [ { "name": "Bob" }, { "name": "Charlie" } ], "children": [ { "name": "Dan" }, { "name": "Emily" } ] } Nous souhaitons extraire de ce document JSON un graph RDF utilisant le vocabulaire http://schema.org/. Pour cela, on propose le contexte du `code-context-ex2a`, qui produit le graphe illustré `fig-ex2a`. .. code-block:: json :name: code-context-ex2a :caption: un premier contexte pour les données généalogiques :playground:`table code-json-ex2` { "@context": { "s": "http://schema.org/", "name": "s:name", "parents": "s:parent" } } .. graphviz:: :name: fig-ex2a :caption: Graph généalogique partiel digraph { a [label=""] b [label=""] Alice [shape=box] c [label=""] Bob [shape=box] Charlie [shape=box] a -> b [label="s:parent"] a -> c [label="s:parent"] a -> Alice [label="s:name"] b -> Bob [label="s:name"] c -> Charlie [label="s:name"] } On peut noter deux choses sur cet exemple : * un objet JSON sans attribut ``@id`` produit un nœud vide en RDF ; * un attribut JSON n'ayant pas de correspondance dans le contexte est ignoré par l'algorithme ``expand``, et n'a donc pas de contrepartie dans le graphe. En ce qui concerne l'attribut ``children``, nous souhaiterions l'intégrer également au graphe, mais Schema.org n'a pas de prédicat ``child``, le prédicat ``parent`` est censé être suffisant. Le problème est que nous devons donc produire des arcs RDF qui sont en **sens** inverse par rapport aux attributs JSON. Ceci est possible grâce au mot-clé ``@reverse``, dont la valeur est l'IRI du prédicat *inverse* correspondant à l'attribut. Ainsi, nous pouvons créer un graphe RDF complet représentant notre graphe généalogique, à l'aide du contexte donné dans le `code-context-ex2b`. Le graphe résultant est illustré `fig-ex2b`. .. code-block:: json :name: code-context-ex2b :caption: un contexte plus complet pour les données généalogiques :playground:`table code-json-ex2` { "@context": { "s": "http://schema.org/", "name": "s:name", "parents": "s:parent", "children": { "@reverse": "s:parent" } } } .. graphviz:: :name: fig-ex2b :caption: Graphe Arbre généalogique partiel digraph { a [label=""] b [label=""] Alice [shape=box] c [label=""] d [label=""] e [label=""] Bob [shape=box] Charlie [shape=box] Dan [shape=box] Emily [shape=box] d -> a [label="s:parent"] e -> a [label="s:parent"] a -> b [label="s:parent"] a -> c [label="s:parent"] a -> Alice [label="s:name"] b -> Bob [label="s:name"] c -> Charlie [label="s:name"] d -> Dan [label="s:name"] e -> Emily [label="s:name"] } .. _CC-BY-SA: https://creativecommons.org/licenses/by-sa/4.0/deed.fr .. _RDF: https://www.w3.org/TR/rdf11-primer/ .. _JSON: https://tools.ietf.org/html/rfc8259 .. _`JSON-LD 1.1, a JSON-based serialization for Linked Data`: https://www.w3.org/TR/json-ld11/ .. _`JSON-LD 1.1 processing algorithms and API`: https://www.w3.org/TR/json-ld11-api/ .. _`JSON-LD 1.1 framing`: https://www.w3.org/TR/json-ld11-framing/ .. _JSON-LD playground: https://json-ld.org/playground/ .. _BCP 47: https://tools.ietf.org/html/bcp47