A Zotonic module for retrieving, working with, and producing RDF triples, based on the RDF 1.2 specification.
Enable zotonic_mod_driebit_rdf in Zotonic, then request any page with content
negotiation, using the proper Accept header to get an RDF representation of the
resource at its URI (where <id> is the id of some resource):
curl -L -H Accept:application/ld+json https://yoursite.com/id/<id>
curl -L -H Accept:text/turtle https://yoursite.com/id/<id>Both of these use the default ontology, schema.org, and are also available (e.g. for viewing in the browser) on separate endpoints:
curl -L http://yoursite.com/rdf/json_ld/<id>
curl -L http://yoursite.com/rdf/turtle/<id>Additionally, the module supports custom ontologies, representations and namespaces (see below), whose result can be obtained with the generic endpoints:
curl -L http://yoursite.com/rdf/<ser>/<ont>/<id>?namespace=<ns>
curl -L http://yoursite.com/rdf/<ser>/<id>?ontology=<ont>&namespace=<ns>
curl -L http://yoursite.com/rdf/<id>?ontology=<ont>&serialization=<ser>&namespace=<ns>
curl -L http://yoursite.com/rdf?id=<id>&ontology=<ont>&serialization=<ser>&namespace=<ns>where:
<ser>,<ont>and<ns>are the name of the serialization, ontology and namespace used, respectively;- the
?ontology,?namespaceand?idquery parameters can be specified multiple times.
The RDF export follows some rules.
- ACL: regardless of ontologies and serialization:
- users can only see the RDF representation of resources that they can view.
Otherwise, they receive a
403"forbidden" error code. - the RDF representation is calculated from the fields and edges visible to the current user, any other is excluded.
- users can only see the RDF representation of resources that they can view.
Otherwise, they receive a
- CORS headers:
controller_rdfignores configuration to ease sharing content and allows any origin in its Access-Control-Allow-Origin header. - Ontologies and serializations are separate steps in making an RDF representation:
- supported ontologies produce an RDF Graph without needing to know how they'll be serialized (or in which namespace).
- supported serializations produce a concrete representation starting from an RDF Graph and (optionally) namespaces, without needing to know which ontologies were used.
- The default representations use the Schema.org ontology, as recommended by NDE and either the Turtle or JSON-LD serialization.
To enable rich search results, Google recommends embedded JSON-LD.
This module includes an embedded JSON-LD snippet:
<script type="application/ld+json">
{
"@id": "https://example.com/id/380",
"@type": ...
...
}
</script>which is included automatically in the head of every resource page if the site
use a base template with an {% all include "_html_head.tpl" %}, as it's the
case for zotonic_mod_base.
The embedded representation is identical to the exported one and you can test your pages using Google’s Rich Result Test.
This module uses Zotonic Notifications which can be used to change or expand the supported:
- ontologies
- serializations
- content types
- namespace prefixes
The types for RDF structures and the notifications used here can all be found in
the driebit_rdf header file and included with:
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").The first step to represent a resource with RDF is to calculate its RDF Graph.
For this purpose, mod_driebit_rdf uses an rsc_to_rdf_graph notification in
search of a module that implements each of the requested ontologies for that
resource, also passing the resource category for convenience.
This can be observed by other modules to decide how to build an RDF graph from scratch:
-export([
observe_rsc_to_rdf_graph/3
]).
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").
observe_rsc_to_rdf_graph(#rsc_to_rdf_graph{rsc_id = RscId, category = Category, ontology = Ontology},Context) ->
RdfGraph = sets:new();
% ... code to build the AST of the graph here ...
{ok, RdfGraph};
observe_rsc_to_rdf_graph(#rsc_to_rdf_graph{}, _Context) ->
undefined.however, if nobody picks this notification up, mod_driebit_rdf has its own
observer, implementing the default logic for building an RDF graph: the set of
all the triples built from every (visible) field and edge connected to the resource.
For ease of customization, these too are notifications, triple_to_rdf which provide:
rsc_idcategoryof the resourcelink_typewhich can beproperty,outgoing_edgeorincoming_edgelink_namewhich is the name of the field or predicate used, depending onlink_typevaluewhich is either the ID of the resource linked by edge or the value of the field, depending onlink_type
Observing these notifications allows to override or implement part of an ontology without having to reimplement all of it, e.g.
-export([
observe_triple_to_rdf/3
]).
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").
% support a non-standard category by giving it a schema.org type:
observe_triple_to_rdf(
#triple_to_rdf{
rsc_id = RscId,
category = remark,
link_type = property,
link_name = <<"id">>,
value = RscId,
ontology = schema_org
},
Context
) ->
{ok, rdf_schema_org:type_triple(RscId, <<"Comment">>, Context)};
% add a new property to the RDF graph of articles from a new predicate:
observe_triple_to_rdf(
#triple_to_rdf{
rsc_id = RscId,
category = article,
link_type = outgoing_edge,
link_name = <<"has_comment">>,
value = CommentId,
ontology = schema_org
},
Context
) ->
{ok, rdf_utils:resolve_triple(
RscId,
rdf_schema_org:namespace_iri(comment),
ObjectId,
Context
)};
% don't forget to return 'undefined' otherwise, so that others can pick this up:
observe_triple_to_rdf(#triple_to_rdf{}, _Context) ->
undefined.Note: the rdf_utils module makes creating triples much easier, see also its
usage in rdf_schema_org to see examples.
After an RDF graph has been obtained from a resource, this needs to be serialized by a concrete syntax.
Another notification, serialize_rdf is used to do this, simply carrying the
graph to serialize and the name of the serialization to be used.
This module handles the ones for json_ld and turtle already, but any other
module can override these or add more by observing the notification and returning
a binary result string:
-export([
observe_serialize_rdf/3
]).
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").
observe_serialize_rdf(#serialize_rdf{rdf_graph = RdfGraph, serialization = trig, namespace_map = NSMap}, Context) ->
Result = <<"">>,
% ... code to serialize the graph to TriG ...
{ok, Result};
observe_serialize_rdf(#serialize_rdf{}, _Context) ->
undefined.Serializations are usually associated with one or more specific content types,
controller_rdf decides which ones to use using the serialization_content_type
notification.
For example, this modules already defines the two it supports:
-export([
observe_serialization_content_type/2
]).
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").
observe_serialization_content_type(#serialization_content_type{serialization = turtle}, _Context) ->
{<<"text">>, <<"turtle">>, []};
observe_serialization_content_type(#serialization_content_type{serialization = json_ld}, _Context) ->
{<<"application">>, <<"ld+json">>, []};
observe_serialization_content_type(#serialization_content_type{}, _Context) ->
undefined.Serializations (can) also support the optional namespace prefixes
which are given as a map to the serialize_rdf notification (see above).
The namespaces to be prefixed (selected with the namespace query parameter)
can also be overridden or extended with a notification:
-export([
observe_expand_namespace/2
]).
-include("zotonic_mod_driebit_rdf/include/driebit_rdf.hrl").
-spec observe_expand_namespace(#expand_namespace{}, #context{}) ->
{binary() | undefined, iri()} | undefined.
observe_expand_namespace(#expand_namespace{name = site}, Context) ->
{<<"site">>, z_context:site_url(undefined, Context)};
observe_expand_namespace(#expand_namespace{name = schema_org}, _Context) ->
{undefined, rdf_schema_org:namespace_iri()};
observe_expand_namespace(#expand_namespace{}, _Context) ->
undefined.where one can return:
{undefined, NamespaceIri}to define a base IRI{Prefix, NamespaceIri}to define a namespace prefix)undefinedto let someone else handle the notification