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>
where:
<ser>
,<ont>
and<ns>
are the name of the serialization, ontology and namespace used, respectively;- the
?ontology
and?namespace
query parameter 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_rdf
ignores 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_id
category
of the resourcelink_type
which can beproperty
,outgoing_edge
orincoming_edge
link_name
which is the name of the field or predicate used, depending onlink_type
value
which 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)undefined
to let someone else handle the notification