2017-04-12 14:48:17 +00:00
|
|
|
defmodule RDF.Statement do
|
|
|
|
@moduledoc """
|
2017-06-16 20:50:56 +00:00
|
|
|
Helper functions for RDF statements.
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2021-01-13 15:55:24 +00:00
|
|
|
An RDF statement is either a `RDF.Triple` or a `RDF.Quad`.
|
2017-04-12 14:48:17 +00:00
|
|
|
"""
|
|
|
|
|
2022-02-25 16:18:03 +00:00
|
|
|
alias RDF.{Resource, BlankNode, IRI, Literal, Quad, Term, Triple, PropertyMap}
|
2020-05-06 16:04:19 +00:00
|
|
|
import RDF.Guards
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2022-02-25 16:18:03 +00:00
|
|
|
@type subject :: Resource.t()
|
|
|
|
@type predicate :: Resource.t()
|
|
|
|
@type object :: Resource.t() | Literal.t()
|
|
|
|
@type graph_name :: Resource.t() | nil
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2022-03-13 20:39:48 +00:00
|
|
|
@type coercible_subject :: Resource.coercible()
|
|
|
|
@type coercible_predicate :: Resource.coercible()
|
2020-06-29 08:37:42 +00:00
|
|
|
@type coercible_object :: object | any
|
|
|
|
@type coercible_graph_name :: graph_name | atom | String.t()
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2020-10-10 13:45:25 +00:00
|
|
|
@type position :: :subject | :predicate | :object | :graph_name
|
|
|
|
@type qualified_term :: {position, Term.t() | nil}
|
2020-06-29 08:37:42 +00:00
|
|
|
@type term_mapping :: (qualified_term -> any | nil)
|
2020-03-02 01:07:31 +00:00
|
|
|
|
2020-06-29 08:37:42 +00:00
|
|
|
@type t :: Triple.t() | Quad.t()
|
2022-03-13 20:39:48 +00:00
|
|
|
@type coercible :: Triple.coercible() | Quad.coercible()
|
|
|
|
# deprecated: This will be removed in v0.11.
|
|
|
|
@type coercible_t :: coercible
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2021-05-10 19:41:48 +00:00
|
|
|
@doc """
|
|
|
|
Creates a `RDF.Triple` or `RDF.Quad` with proper RDF values.
|
|
|
|
|
|
|
|
An error is raised when the given elements are not coercible to RDF values.
|
|
|
|
|
|
|
|
Note: The `RDF.statement` function is a shortcut to this function.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> RDF.Statement.new({EX.S, EX.p, 42})
|
|
|
|
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
|
|
|
|
|
|
|
|
iex> RDF.Statement.new({EX.S, EX.p, 42, EX.Graph})
|
|
|
|
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
|
2021-09-04 12:18:00 +00:00
|
|
|
|
|
|
|
iex> RDF.Statement.new({EX.S, :p, 42, EX.Graph}, RDF.PropertyMap.new(p: EX.p))
|
|
|
|
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
|
2021-05-10 19:41:48 +00:00
|
|
|
"""
|
2021-09-04 12:18:00 +00:00
|
|
|
def new(tuple, property_map \\ nil)
|
|
|
|
def new({_, _, _} = tuple, property_map), do: Triple.new(tuple, property_map)
|
|
|
|
def new({_, _, _, _} = tuple, property_map), do: Quad.new(tuple, property_map)
|
2021-05-10 19:41:48 +00:00
|
|
|
|
|
|
|
defdelegate new(s, p, o), to: Triple, as: :new
|
|
|
|
defdelegate new(s, p, o, g), to: Quad, as: :new
|
|
|
|
|
2017-06-05 01:01:23 +00:00
|
|
|
@doc """
|
|
|
|
Creates a `RDF.Statement` tuple with proper RDF values.
|
|
|
|
|
2017-08-11 20:22:27 +00:00
|
|
|
An error is raised when the given elements are not coercible to RDF values.
|
2017-06-05 01:01:23 +00:00
|
|
|
|
2017-06-16 22:27:05 +00:00
|
|
|
## Examples
|
2017-06-05 01:01:23 +00:00
|
|
|
|
2018-11-04 21:27:25 +00:00
|
|
|
iex> RDF.Statement.coerce {"http://example.com/S", "http://example.com/p", 42}
|
2017-06-05 01:01:23 +00:00
|
|
|
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
|
2018-11-04 21:27:25 +00:00
|
|
|
iex> RDF.Statement.coerce {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
|
2017-06-05 01:01:23 +00:00
|
|
|
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
|
|
|
|
"""
|
2022-03-13 20:39:48 +00:00
|
|
|
@spec coerce(coercible(), PropertyMap.t() | nil) :: Triple.t() | Quad.t()
|
2021-09-04 12:18:00 +00:00
|
|
|
def coerce(statement, property_map \\ nil)
|
|
|
|
def coerce({_, _, _} = triple, property_map), do: Triple.new(triple, property_map)
|
|
|
|
def coerce({_, _, _, _} = quad, property_map), do: Quad.new(quad, property_map)
|
2017-06-05 01:01:23 +00:00
|
|
|
|
2017-04-12 14:48:17 +00:00
|
|
|
@doc false
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec coerce_subject(coercible_subject) :: subject
|
2017-08-20 20:35:14 +00:00
|
|
|
def coerce_subject(iri)
|
|
|
|
def coerce_subject(iri = %IRI{}), do: iri
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_subject(bnode = %BlankNode{}), do: bnode
|
|
|
|
def coerce_subject("_:" <> identifier), do: RDF.bnode(identifier)
|
2020-05-06 16:04:19 +00:00
|
|
|
def coerce_subject(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
|
2020-06-29 08:37:42 +00:00
|
|
|
def coerce_subject(arg), do: raise(RDF.Triple.InvalidSubjectError, subject: arg)
|
2017-04-12 14:48:17 +00:00
|
|
|
|
|
|
|
@doc false
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec coerce_predicate(coercible_predicate) :: predicate
|
2017-08-20 20:35:14 +00:00
|
|
|
def coerce_predicate(iri)
|
|
|
|
def coerce_predicate(iri = %IRI{}), do: iri
|
2017-04-12 14:48:17 +00:00
|
|
|
# Note: Although, RDF does not allow blank nodes for properties, JSON-LD allows
|
|
|
|
# them, by introducing the notion of "generalized RDF".
|
|
|
|
# TODO: Support an option `:strict_rdf` to explicitly disallow them or produce warnings or ...
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_predicate(bnode = %BlankNode{}), do: bnode
|
2020-05-06 16:04:19 +00:00
|
|
|
def coerce_predicate(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
|
2020-06-29 08:37:42 +00:00
|
|
|
def coerce_predicate(arg), do: raise(RDF.Triple.InvalidPredicateError, predicate: arg)
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2020-10-09 14:32:24 +00:00
|
|
|
@doc false
|
|
|
|
@spec coerce_predicate(coercible_predicate, PropertyMap.t()) :: predicate
|
|
|
|
def coerce_predicate(term, context)
|
|
|
|
|
2020-10-10 13:45:25 +00:00
|
|
|
def coerce_predicate(term, %PropertyMap{} = property_map) when is_atom(term) do
|
|
|
|
PropertyMap.iri(property_map, term) || coerce_predicate(term)
|
2020-10-09 14:32:24 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def coerce_predicate(term, _), do: coerce_predicate(term)
|
|
|
|
|
2017-04-12 14:48:17 +00:00
|
|
|
@doc false
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec coerce_object(coercible_object) :: object
|
2017-08-20 20:35:14 +00:00
|
|
|
def coerce_object(iri)
|
|
|
|
def coerce_object(iri = %IRI{}), do: iri
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_object(literal = %Literal{}), do: literal
|
|
|
|
def coerce_object(bnode = %BlankNode{}), do: bnode
|
|
|
|
def coerce_object(bool) when is_boolean(bool), do: Literal.new(bool)
|
2020-05-06 16:04:19 +00:00
|
|
|
def coerce_object(atom) when maybe_ns_term(atom), do: RDF.iri(atom)
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_object(arg), do: Literal.new(arg)
|
2017-04-12 14:48:17 +00:00
|
|
|
|
|
|
|
@doc false
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec coerce_graph_name(coercible_graph_name) :: graph_name
|
2017-08-20 20:35:14 +00:00
|
|
|
def coerce_graph_name(iri)
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_graph_name(nil), do: nil
|
2017-08-20 20:35:14 +00:00
|
|
|
def coerce_graph_name(iri = %IRI{}), do: iri
|
2017-08-11 20:22:27 +00:00
|
|
|
def coerce_graph_name(bnode = %BlankNode{}), do: bnode
|
|
|
|
def coerce_graph_name("_:" <> identifier), do: RDF.bnode(identifier)
|
2020-05-06 16:04:19 +00:00
|
|
|
def coerce_graph_name(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
|
2017-04-12 14:48:17 +00:00
|
|
|
|
2020-06-29 08:37:42 +00:00
|
|
|
def coerce_graph_name(arg),
|
|
|
|
do: raise(RDF.Quad.InvalidGraphContextError, graph_context: arg)
|
2018-10-21 22:52:22 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms.
|
|
|
|
|
2020-10-13 09:38:22 +00:00
|
|
|
When a `:context` option is given with a `RDF.PropertyMap`, predicates will
|
|
|
|
be mapped to the terms defined in the `RDF.PropertyMap`, if present.
|
2020-10-10 13:45:25 +00:00
|
|
|
|
|
|
|
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
|
|
|
|
{"http://example.com/S", "http://example.com/p", 42}
|
|
|
|
|
|
|
|
iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
|
|
|
|
{"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
|
|
|
|
|
|
|
|
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
|
2020-10-13 09:38:22 +00:00
|
|
|
...> |> RDF.Statement.values(context: %{p: ~I<http://example.com/p>})
|
2020-10-10 13:45:25 +00:00
|
|
|
{"http://example.com/S", :p, 42}
|
|
|
|
|
|
|
|
"""
|
2022-03-13 20:39:48 +00:00
|
|
|
@spec values(t, keyword) :: Triple.mapping_value() | Quad.mapping_value() | nil
|
2020-10-13 09:38:22 +00:00
|
|
|
def values(quad, opts \\ [])
|
|
|
|
def values({_, _, _} = triple, opts), do: Triple.values(triple, opts)
|
|
|
|
def values({_, _, _, _} = quad, opts), do: Quad.values(quad, opts)
|
2020-10-10 13:45:25 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms.
|
|
|
|
|
2018-10-21 22:52:22 +00:00
|
|
|
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
|
|
|
|
|
2018-11-04 21:27:25 +00:00
|
|
|
The optional second argument allows to specify a custom mapping with a function
|
|
|
|
which will receive a tuple `{statement_position, rdf_term}` where
|
|
|
|
`statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or
|
|
|
|
`:graph_name`, while `rdf_term` is the RDF term to be mapped. When the given
|
|
|
|
function returns `nil` this will be interpreted as an error and will become
|
|
|
|
the overhaul result of the `values/2` call.
|
|
|
|
|
2018-10-21 22:52:22 +00:00
|
|
|
## Examples
|
|
|
|
|
2018-11-04 21:27:25 +00:00
|
|
|
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
|
2020-10-10 13:45:25 +00:00
|
|
|
...> |> RDF.Statement.map(fn
|
2018-11-04 21:27:25 +00:00
|
|
|
...> {:subject, subject} ->
|
|
|
|
...> subject |> to_string() |> String.last()
|
|
|
|
...> {:predicate, predicate} ->
|
|
|
|
...> predicate |> to_string() |> String.last() |> String.to_atom()
|
|
|
|
...> {:object, object} ->
|
|
|
|
...> RDF.Term.value(object)
|
|
|
|
...> {:graph_name, graph_name} ->
|
|
|
|
...> graph_name
|
|
|
|
...> end)
|
|
|
|
{"S", :p, 42, ~I<http://example.com/Graph>}
|
|
|
|
|
2018-10-21 22:52:22 +00:00
|
|
|
"""
|
2022-03-13 20:39:48 +00:00
|
|
|
@spec map(t, term_mapping()) :: Triple.mapping_value() | Quad.mapping_value() | nil | nil
|
2020-10-10 13:45:25 +00:00
|
|
|
def map(statement, fun)
|
|
|
|
def map({_, _, _} = triple, fun), do: RDF.Triple.map(triple, fun)
|
|
|
|
def map({_, _, _, _} = quad, fun), do: RDF.Quad.map(quad, fun)
|
2018-11-04 21:27:25 +00:00
|
|
|
|
|
|
|
@doc false
|
2020-04-10 21:40:33 +00:00
|
|
|
@spec default_term_mapping(qualified_term) :: any | nil
|
2018-11-04 21:27:25 +00:00
|
|
|
def default_term_mapping(qualified_term)
|
|
|
|
def default_term_mapping({:graph_name, nil}), do: nil
|
|
|
|
def default_term_mapping({_, term}), do: RDF.Term.value(term)
|
2018-10-21 22:52:22 +00:00
|
|
|
|
2020-10-10 13:45:25 +00:00
|
|
|
@spec default_property_mapping(PropertyMap.t()) :: term_mapping
|
|
|
|
def default_property_mapping(%PropertyMap{} = property_map) do
|
|
|
|
fn
|
|
|
|
{:predicate, predicate} ->
|
|
|
|
PropertyMap.term(property_map, predicate) || default_term_mapping({:predicate, predicate})
|
|
|
|
|
|
|
|
other ->
|
|
|
|
default_term_mapping(other)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-11-11 01:30:25 +00:00
|
|
|
@doc """
|
|
|
|
Checks if the given tuple is a valid RDF statement, i.e. RDF triple or quad.
|
|
|
|
|
|
|
|
The elements of a valid RDF statement must be RDF terms. On the subject
|
|
|
|
position only IRIs and blank nodes allowed, while on the predicate and graph
|
|
|
|
context position only IRIs allowed. The object position can be any RDF term.
|
|
|
|
"""
|
2020-06-29 08:37:42 +00:00
|
|
|
@spec valid?(Triple.t() | Quad.t() | any) :: boolean
|
2018-11-11 01:30:25 +00:00
|
|
|
def valid?(tuple)
|
|
|
|
|
|
|
|
def valid?({subject, predicate, object}) do
|
|
|
|
valid_subject?(subject) && valid_predicate?(predicate) && valid_object?(object)
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?({subject, predicate, object, graph_name}) do
|
|
|
|
valid_subject?(subject) && valid_predicate?(predicate) && valid_object?(object) &&
|
|
|
|
valid_graph_name?(graph_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?(_), do: false
|
|
|
|
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec valid_subject?(subject | any) :: boolean
|
2020-06-29 08:37:42 +00:00
|
|
|
def valid_subject?(%IRI{}), do: true
|
2018-11-11 01:30:25 +00:00
|
|
|
def valid_subject?(%BlankNode{}), do: true
|
2020-06-29 08:37:42 +00:00
|
|
|
def valid_subject?(_), do: false
|
2018-11-11 01:30:25 +00:00
|
|
|
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec valid_predicate?(predicate | any) :: boolean
|
2020-06-29 08:37:42 +00:00
|
|
|
def valid_predicate?(%IRI{}), do: true
|
|
|
|
def valid_predicate?(_), do: false
|
2018-11-11 01:30:25 +00:00
|
|
|
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec valid_object?(object | any) :: boolean
|
2020-06-29 08:37:42 +00:00
|
|
|
def valid_object?(%IRI{}), do: true
|
|
|
|
def valid_object?(%BlankNode{}), do: true
|
|
|
|
def valid_object?(%Literal{}), do: true
|
|
|
|
def valid_object?(_), do: false
|
2018-11-11 01:30:25 +00:00
|
|
|
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec valid_graph_name?(graph_name | any) :: boolean
|
2020-06-29 08:37:42 +00:00
|
|
|
def valid_graph_name?(%IRI{}), do: true
|
|
|
|
def valid_graph_name?(_), do: false
|
2017-04-12 14:48:17 +00:00
|
|
|
end
|