core: extract general Statement module from Triple and Quad

This commit is contained in:
Marcel Otto 2017-04-12 16:48:17 +02:00
parent 888ac4d69b
commit 87a3125632
6 changed files with 115 additions and 106 deletions

View file

@ -8,7 +8,8 @@ defmodule RDF.Dataset do
@behaviour Access
alias RDF.{Quad, Graph, Description}
alias RDF.{Graph, Description}
import RDF.Statement
@type t :: module
@ -88,7 +89,7 @@ defmodule RDF.Dataset do
def add(dataset, statements, graph_context \\ nil)
def add(dataset, statements, graph_context) when is_list(statements) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
Enum.reduce statements, dataset, fn (statement, dataset) ->
add(dataset, statement, graph_context)
end
@ -100,7 +101,7 @@ defmodule RDF.Dataset do
def add(%RDF.Dataset{name: name, graphs: graphs},
{subject, predicate, objects, graph_context}, _) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
updated_graphs =
Map.update(graphs, graph_context,
Graph.new(graph_context, {subject, predicate, objects}),
@ -111,7 +112,7 @@ defmodule RDF.Dataset do
def add(%RDF.Dataset{name: name, graphs: graphs},
%Description{} = description, graph_context) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
updated_graph =
Map.get(graphs, graph_context, Graph.new(graph_context))
|> Graph.add(description)
@ -124,7 +125,7 @@ defmodule RDF.Dataset do
def add(%RDF.Dataset{name: name, graphs: graphs}, %Graph{} = graph,
graph_context) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
%RDF.Dataset{name: name,
graphs:
Map.update(graphs, graph_context, Graph.new(graph_context, graph), fn current ->
@ -172,7 +173,7 @@ defmodule RDF.Dataset do
def put(%RDF.Dataset{name: name, graphs: graphs},
{subject, predicate, objects, graph_context}, _) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
new_graph =
case graphs[graph_context] do
graph = %Graph{} ->
@ -187,7 +188,7 @@ defmodule RDF.Dataset do
def put(%RDF.Dataset{} = dataset, statements, graph_context)
when is_list(statements) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
put dataset, Enum.group_by(statements,
fn
{s, _, _, nil} -> s
@ -204,7 +205,7 @@ defmodule RDF.Dataset do
def put(%RDF.Dataset{name: name, graphs: graphs},
%Description{} = description, graph_context) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
updated_graph =
Map.get(graphs, graph_context, Graph.new(graph_context))
|> Graph.put(description)
@ -217,7 +218,7 @@ defmodule RDF.Dataset do
def put(%RDF.Dataset{name: name, graphs: graphs}, %Graph{} = graph,
graph_context) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
%RDF.Dataset{name: name,
graphs:
Map.update(graphs, graph_context, Graph.new(graph_context, graph), fn current ->
@ -229,7 +230,7 @@ defmodule RDF.Dataset do
def put(%RDF.Dataset{} = dataset, statements, graph_context)
when is_map(statements) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
Enum.reduce statements, dataset,
fn ({subject_with_context, predications}, dataset) ->
put(dataset, subject_with_context, predications, graph_context)
@ -241,7 +242,7 @@ defmodule RDF.Dataset do
{subject, graph_context}, predications, default_graph_context)
when is_list(predications) do
with graph_context = graph_context || default_graph_context,
graph_context = Quad.convert_graph_context(graph_context) do
graph_context = convert_graph_name(graph_context) do
graph = Map.get(graphs, graph_context, Graph.new(graph_context))
new_graphs = graphs
|> Map.put(graph_context, Graph.put(graph, subject, predications))
@ -271,7 +272,7 @@ defmodule RDF.Dataset do
:error
"""
def fetch(%RDF.Dataset{graphs: graphs}, graph_name) do
Access.fetch(graphs, Quad.convert_graph_context(graph_name))
Access.fetch(graphs, convert_graph_name(graph_name))
end
@doc """
@ -303,7 +304,7 @@ defmodule RDF.Dataset do
The graph with given name.
"""
def graph(%RDF.Dataset{graphs: graphs}, graph_name),
do: Map.get(graphs, Quad.convert_graph_context(graph_name))
do: Map.get(graphs, convert_graph_name(graph_name))
@doc """
The default graph of a `RDF.Dataset`.
@ -335,7 +336,7 @@ defmodule RDF.Dataset do
{RDF.Graph.new(EX.Graph, {EX.S, EX.P, EX.O}), RDF.Dataset.new({EX.S, EX.P, EX.NEW, EX.Graph})}
"""
def get_and_update(%RDF.Dataset{} = dataset, graph_name, fun) do
with graph_context = Quad.convert_graph_context(graph_name) do
with graph_context = convert_graph_name(graph_name) do
case fun.(get(dataset, graph_context)) do
{old_graph, new_graph} ->
{old_graph, put(dataset, new_graph, graph_context)}
@ -364,7 +365,7 @@ defmodule RDF.Dataset do
{nil, dataset}
"""
def pop(%RDF.Dataset{name: name, graphs: graphs} = dataset, graph_name) do
case Access.pop(graphs, Quad.convert_graph_context(graph_name)) do
case Access.pop(graphs, convert_graph_name(graph_name)) do
{nil, _} ->
{nil, dataset}
{graph, new_graphs} ->
@ -511,7 +512,7 @@ defmodule RDF.Dataset do
def include?(dataset, statement, graph_context \\ nil)
def include?(%RDF.Dataset{graphs: graphs}, triple = {_, _, _}, graph_context) do
with graph_context = Quad.convert_graph_context(graph_context) do
with graph_context = convert_graph_name(graph_context) do
if graph = graphs[graph_context] do
Graph.include?(graph, triple)
else
@ -554,7 +555,7 @@ defmodule RDF.Dataset do
{{s, p, o, graph_name}, %RDF.Dataset{name: name, graphs: popped}}
end
end
defimpl Enumerable, for: RDF.Dataset do

View file

@ -8,7 +8,7 @@ defmodule RDF.Description do
@behaviour Access
alias RDF.Triple
import RDF.Statement
@type t :: module
@ -18,7 +18,7 @@ defmodule RDF.Description do
When given a triple, it must contain the subject.
When given a list of statements, the first one must contain a subject.
"""
@spec new(Triple.convertible_subject) :: RDF.Description.t
@spec new(RDF.Statement.convertible_subject) :: RDF.Description.t
def new(subject)
def new({subject, predicate, object}),
@ -26,7 +26,7 @@ defmodule RDF.Description do
def new([statement | more_statements]),
do: new(statement) |> add(more_statements)
def new(subject),
do: %RDF.Description{subject: Triple.convert_subject(subject)}
do: %RDF.Description{subject: convert_subject(subject)}
def new(subject, predicate, objects),
do: new(subject) |> add(predicate, objects)
def new(subject, statements) when is_list(statements),
@ -56,8 +56,8 @@ defmodule RDF.Description do
end
def add(%RDF.Description{subject: subject, predications: predications}, predicate, object) do
with triple_predicate = Triple.convert_predicate(predicate),
triple_object = Triple.convert_object(object),
with triple_predicate = convert_predicate(predicate),
triple_object = convert_object(object),
new_predications = Map.update(predications,
triple_predicate, %{triple_object => nil}, fn objects ->
Map.put_new(objects, triple_object, nil)
@ -76,7 +76,7 @@ defmodule RDF.Description do
do: add(description, predicate, object)
def add(description = %RDF.Description{}, {subject, predicate, object}) do
if Triple.convert_subject(subject) == description.subject,
if convert_subject(subject) == description.subject,
do: add(description, predicate, object),
else: description
end
@ -115,9 +115,9 @@ defmodule RDF.Description do
def put(%RDF.Description{subject: subject, predications: predications},
predicate, objects) when is_list(objects) do
with triple_predicate = Triple.convert_predicate(predicate),
with triple_predicate = convert_predicate(predicate),
triple_objects = Enum.reduce(objects, %{}, fn (object, acc) ->
Map.put_new(acc, Triple.convert_object(object), nil) end),
Map.put_new(acc, convert_object(object), nil) end),
do: %RDF.Description{subject: subject,
predications: Map.put(predications, triple_predicate, triple_objects)}
end
@ -148,7 +148,7 @@ defmodule RDF.Description do
do: put(desc, predicate, object)
def put(desc = %RDF.Description{}, {subject, predicate, object}) do
if Triple.convert_subject(subject) == desc.subject,
if convert_subject(subject) == desc.subject,
do: put(desc, predicate, object),
else: desc
end
@ -156,11 +156,11 @@ defmodule RDF.Description do
def put(desc = %RDF.Description{subject: subject}, statements) when is_list(statements) do
statements
|> Stream.map(fn
{p, o} -> {Triple.convert_predicate(p), o}
{^subject, p, o} -> {Triple.convert_predicate(p), o}
{p, o} -> {convert_predicate(p), o}
{^subject, p, o} -> {convert_predicate(p), o}
{s, p, o} ->
if Triple.convert_subject(s) == subject,
do: {Triple.convert_predicate(p), o}
if convert_subject(s) == subject,
do: {convert_predicate(p), o}
bad -> raise ArgumentError, "#{inspect bad} is not a valid statement"
end)
|> Stream.filter(&(&1)) # filter nil values
@ -199,7 +199,7 @@ defmodule RDF.Description do
:error
"""
def fetch(%RDF.Description{predications: predications}, predicate) do
with {:ok, objects} <- Access.fetch(predications, Triple.convert_predicate(predicate)) do
with {:ok, objects} <- Access.fetch(predications, convert_predicate(predicate)) do
{:ok, Map.keys(objects)}
end
end
@ -251,7 +251,7 @@ defmodule RDF.Description do
{[RDF.uri(EX.O1)], RDF.Description.new({EX.S, EX.P2, EX.O2})}
"""
def get_and_update(description = %RDF.Description{}, predicate, fun) do
with triple_predicate = Triple.convert_predicate(predicate) do
with triple_predicate = convert_predicate(predicate) do
case fun.(get(description, triple_predicate)) do
{objects_to_return, new_objects} ->
{objects_to_return, put(description, triple_predicate, new_objects)}
@ -274,7 +274,7 @@ defmodule RDF.Description do
{nil, RDF.Description.new({EX.S, EX.P, EX.O})}
"""
def pop(description = %RDF.Description{subject: subject, predications: predications}, predicate) do
case Access.pop(predications, Triple.convert_predicate(predicate)) do
case Access.pop(predications, convert_predicate(predicate)) do
{nil, _} ->
{nil, description}
{objects, new_predications} ->
@ -363,8 +363,8 @@ defmodule RDF.Description do
def include?(%RDF.Description{predications: predications},
{predicate, object}) do
with triple_predicate = Triple.convert_predicate(predicate),
triple_object = Triple.convert_object(object) do
with triple_predicate = convert_predicate(predicate),
triple_object = convert_object(object) do
predications
|> Map.get(triple_predicate, %{})
|> Map.has_key?(triple_object)
@ -373,7 +373,7 @@ defmodule RDF.Description do
def include?(desc = %RDF.Description{subject: desc_subject},
{subject, predicate, object}) do
Triple.convert_subject(subject) == desc_subject &&
convert_subject(subject) == desc_subject &&
include?(desc, {predicate, object})
end

View file

@ -10,7 +10,8 @@ defmodule RDF.Graph do
@behaviour Access
alias RDF.{Description, Triple, Quad}
alias RDF.Description
import RDF.Statement
@type t :: module
@ -54,7 +55,7 @@ defmodule RDF.Graph do
Creates an empty named `RDF.Graph`.
"""
def new(name),
do: %RDF.Graph{name: Quad.convert_graph_context(name)}
do: %RDF.Graph{name: convert_graph_name(name)}
@doc """
Creates a named `RDF.Graph` with an initial triple.
@ -106,7 +107,7 @@ defmodule RDF.Graph do
def add(%RDF.Graph{name: name, descriptions: descriptions},
subject, predicate, object) do
with subject = Triple.convert_subject(subject) do
with subject = convert_subject(subject) do
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject,
@ -160,7 +161,7 @@ defmodule RDF.Graph do
"""
def put(%RDF.Graph{name: name, descriptions: descriptions},
subject, predicate, objects) do
with subject = Triple.convert_subject(subject) do
with subject = convert_subject(subject) do
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject,
@ -212,7 +213,7 @@ defmodule RDF.Graph do
def put(%RDF.Graph{name: name, descriptions: descriptions}, subject, predications)
when is_list(predications) do
with subject = Triple.convert_subject(subject) do
with subject = convert_subject(subject) do
%RDF.Graph{name: name,
descriptions:
Map.update(descriptions, subject,
@ -240,7 +241,7 @@ defmodule RDF.Graph do
:error
"""
def fetch(%RDF.Graph{descriptions: descriptions}, subject) do
Access.fetch(descriptions, Triple.convert_subject(subject))
Access.fetch(descriptions, convert_subject(subject))
end
@doc """
@ -269,7 +270,7 @@ defmodule RDF.Graph do
The `RDF.Description` of the given subject.
"""
def description(%RDF.Graph{descriptions: descriptions}, subject),
do: Map.get(descriptions, Triple.convert_subject(subject))
do: Map.get(descriptions, convert_subject(subject))
@doc """
@ -295,7 +296,7 @@ defmodule RDF.Graph do
{RDF.Description.new(EX.S, EX.P, EX.O), RDF.Graph.new(EX.S, EX.P, EX.NEW)}
"""
def get_and_update(%RDF.Graph{} = graph, subject, fun) do
with subject = Triple.convert_subject(subject) do
with subject = convert_subject(subject) do
case fun.(get(graph, subject)) do
{old_description, new_description} ->
{old_description, put(graph, subject, new_description)}
@ -321,7 +322,7 @@ defmodule RDF.Graph do
{nil, RDF.Graph.new({EX.S, EX.P, EX.O})}
"""
def pop(%RDF.Graph{name: name, descriptions: descriptions} = graph, subject) do
case Access.pop(descriptions, Triple.convert_subject(subject)) do
case Access.pop(descriptions, convert_subject(subject)) do
{nil, _} ->
{nil, graph}
{description, new_descriptions} ->
@ -462,7 +463,7 @@ defmodule RDF.Graph do
def include?(%RDF.Graph{descriptions: descriptions},
triple = {subject, _, _}) do
with subject = Triple.convert_subject(subject),
with subject = convert_subject(subject),
%Description{} <- description = descriptions[subject] do
Description.include?(description, triple)
else

View file

@ -6,12 +6,7 @@ defmodule RDF.Quad do
subject, predicate, object and a graph context.
"""
alias RDF.BlankNode
import RDF.Triple, except: [new: 1, new: 3]
@type graph_context :: URI.t | BlankNode.t
@type convertible_graph_context :: graph_context | atom | String.t
alias RDF.{BlankNode, Statement}
@doc """
Creates a `RDF.Quad` with proper RDF values.
@ -27,10 +22,10 @@ defmodule RDF.Quad do
"""
def new(subject, predicate, object, graph_context) do
{
convert_subject(subject),
convert_predicate(predicate),
convert_object(object),
convert_graph_context(graph_context)
Statement.convert_subject(subject),
Statement.convert_predicate(predicate),
Statement.convert_object(object),
Statement.convert_graph_name(graph_context)
}
end
@ -49,16 +44,4 @@ defmodule RDF.Quad do
def new({subject, predicate, object, graph_context}),
do: new(subject, predicate, object, graph_context)
@doc false
def convert_graph_context(uri)
def convert_graph_context(nil), do: nil
def convert_graph_context(uri = %URI{}), do: uri
def convert_graph_context(bnode = %BlankNode{}), do: bnode
def convert_graph_context(uri) when is_atom(uri) or is_binary(uri),
do: RDF.uri(uri)
def convert_graph_context(arg),
do: raise RDF.Quad.InvalidGraphContextError, graph_context: arg
end

58
lib/rdf/statement.ex Normal file
View file

@ -0,0 +1,58 @@
defmodule RDF.Statement do
@moduledoc """
Defines a RDF Statement.
A RDF statement is either a `RDF.Triple` or a `RDF.Quad`.
"""
alias RDF.{Triple, Quad, BlankNode, Literal}
@type subject :: URI.t | BlankNode.t
@type predicate :: URI.t
@type object :: URI.t | BlankNode.t | Literal.t
@type graph_name :: URI.t | BlankNode.t
@type convertible_subject :: subject | atom | String.t
@type convertible_predicate :: predicate | atom | String.t
@type convertible_object :: object | atom | String.t # TODO: all basic Elixir types convertible to Literals
@type convertible_graph_name :: graph_name | atom | String.t
@doc false
def convert_subject(uri)
def convert_subject(uri = %URI{}), do: uri
def convert_subject(bnode = %BlankNode{}), do: bnode
def convert_subject("_:" <> identifier), do: RDF.bnode(identifier)
def convert_subject(uri) when is_atom(uri) or is_binary(uri), do: RDF.uri(uri)
def convert_subject(arg), do: raise RDF.Triple.InvalidSubjectError, subject: arg
@doc false
def convert_predicate(uri)
def convert_predicate(uri = %URI{}), do: uri
# 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 ...
def convert_predicate(bnode = %BlankNode{}), do: bnode
def convert_predicate(uri) when is_atom(uri) or is_binary(uri), do: RDF.uri(uri)
def convert_predicate(arg), do: raise RDF.Triple.InvalidPredicateError, predicate: arg
@doc false
def convert_object(uri)
def convert_object(uri = %URI{}), do: uri
def convert_object(literal = %Literal{}), do: literal
def convert_object(bnode = %BlankNode{}), do: bnode
def convert_object(atom) when is_atom(atom), do: RDF.uri(atom)
def convert_object(arg), do: Literal.new(arg)
@doc false
def convert_graph_name(uri)
def convert_graph_name(nil), do: nil
def convert_graph_name(uri = %URI{}), do: uri
def convert_graph_name(bnode = %BlankNode{}), do: bnode
def convert_graph_name("_:" <> identifier), do: RDF.bnode(identifier)
def convert_graph_name(uri) when is_atom(uri) or is_binary(uri),
do: RDF.uri(uri)
def convert_graph_name(arg),
do: raise RDF.Quad.InvalidGraphContextError, graph_context: arg
end

View file

@ -6,15 +6,7 @@ defmodule RDF.Triple do
subject, predicate and object.
"""
alias RDF.{BlankNode, Literal}
@type subject :: URI.t | BlankNode.t
@type predicate :: URI.t
@type object :: URI.t | BlankNode.t | Literal.t
@type convertible_subject :: subject | atom | String.t
@type convertible_predicate :: predicate | atom | String.t
@type convertible_object :: object | atom | String.t # TODO: all basic Elixir types convertible to Literals
alias RDF.{BlankNode, Statement}
@doc """
Creates a `RDF.Triple` with proper RDF values.
@ -30,9 +22,9 @@ defmodule RDF.Triple do
"""
def new(subject, predicate, object) do
{
convert_subject(subject),
convert_predicate(predicate),
convert_object(object)
Statement.convert_subject(subject),
Statement.convert_predicate(predicate),
Statement.convert_object(object)
}
end
@ -50,30 +42,4 @@ defmodule RDF.Triple do
"""
def new({subject, predicate, object}), do: new(subject, predicate, object)
@doc false
def convert_subject(uri)
def convert_subject(uri = %URI{}), do: uri
def convert_subject(bnode = %BlankNode{}), do: bnode
def convert_subject(uri) when is_atom(uri) or is_binary(uri), do: RDF.uri(uri)
def convert_subject(arg), do: raise RDF.Triple.InvalidSubjectError, subject: arg
@doc false
def convert_predicate(uri)
def convert_predicate(uri = %URI{}), do: uri
# 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 ...
def convert_predicate(bnode = %BlankNode{}), do: bnode
def convert_predicate(uri) when is_atom(uri) or is_binary(uri), do: RDF.uri(uri)
def convert_predicate(arg), do: raise RDF.Triple.InvalidPredicateError, predicate: arg
@doc false
def convert_object(uri)
def convert_object(uri = %URI{}), do: uri
def convert_object(literal = %Literal{}), do: literal
def convert_object(bnode = %BlankNode{}), do: bnode
def convert_object(atom) when is_atom(atom), do: RDF.uri(atom)
def convert_object(arg), do: Literal.new(arg)
end