Add RDF-star support on RDF.Description and RDF.Graph
This commit is contained in:
parent
4f57fda00f
commit
2819092586
7 changed files with 784 additions and 176 deletions
78
lib/rdf.ex
78
lib/rdf.ex
|
@ -45,19 +45,23 @@ defmodule RDF do
|
|||
Namespace,
|
||||
Literal,
|
||||
BlankNode,
|
||||
Triple,
|
||||
Quad,
|
||||
Statement,
|
||||
Description,
|
||||
Graph,
|
||||
Dataset,
|
||||
Serialization,
|
||||
PrefixMap
|
||||
}
|
||||
|
||||
import RDF.Guards
|
||||
import RDF.Utils.Bootstrapping
|
||||
|
||||
defdelegate default_base_iri(), to: RDF.IRI, as: :default_base
|
||||
@star? Application.get_env(:rdf, :star, true)
|
||||
@doc """
|
||||
Returns whether RDF-star support is enabled.
|
||||
"""
|
||||
def star?(), do: @star?
|
||||
|
||||
defdelegate default_base_iri(), to: IRI, as: :default_base
|
||||
|
||||
@standard_prefixes PrefixMap.new(
|
||||
xsd: xsd_iri_base(),
|
||||
|
@ -136,17 +140,17 @@ defmodule RDF do
|
|||
default_prefixes() |> PrefixMap.merge!(prefix_mappings)
|
||||
end
|
||||
|
||||
defdelegate read_string(string, opts), to: RDF.Serialization
|
||||
defdelegate read_string!(string, opts), to: RDF.Serialization
|
||||
defdelegate read_stream(stream, opts \\ []), to: RDF.Serialization
|
||||
defdelegate read_stream!(stream, opts \\ []), to: RDF.Serialization
|
||||
defdelegate read_file(filename, opts \\ []), to: RDF.Serialization
|
||||
defdelegate read_file!(filename, opts \\ []), to: RDF.Serialization
|
||||
defdelegate write_string(data, opts), to: RDF.Serialization
|
||||
defdelegate write_string!(data, opts), to: RDF.Serialization
|
||||
defdelegate write_stream(data, opts), to: RDF.Serialization
|
||||
defdelegate write_file(data, filename, opts \\ []), to: RDF.Serialization
|
||||
defdelegate write_file!(data, filename, opts \\ []), to: RDF.Serialization
|
||||
defdelegate read_string(string, opts), to: Serialization
|
||||
defdelegate read_string!(string, opts), to: Serialization
|
||||
defdelegate read_stream(stream, opts \\ []), to: Serialization
|
||||
defdelegate read_stream!(stream, opts \\ []), to: Serialization
|
||||
defdelegate read_file(filename, opts \\ []), to: Serialization
|
||||
defdelegate read_file!(filename, opts \\ []), to: Serialization
|
||||
defdelegate write_string(data, opts), to: Serialization
|
||||
defdelegate write_string!(data, opts), to: Serialization
|
||||
defdelegate write_stream(data, opts), to: Serialization
|
||||
defdelegate write_file(data, filename, opts \\ []), to: Serialization
|
||||
defdelegate write_file!(data, filename, opts \\ []), to: Serialization
|
||||
|
||||
@doc """
|
||||
Checks if the given value is a RDF resource.
|
||||
|
@ -181,6 +185,10 @@ defmodule RDF do
|
|||
end
|
||||
end
|
||||
|
||||
if @star? do
|
||||
def resource?({_, _, _} = triple), do: RDF.Triple.valid?(triple)
|
||||
end
|
||||
|
||||
def resource?(_), do: false
|
||||
|
||||
@doc """
|
||||
|
@ -243,15 +251,41 @@ defmodule RDF do
|
|||
defdelegate literal(value), to: Literal, as: :new
|
||||
defdelegate literal(value, opts), to: Literal, as: :new
|
||||
|
||||
defdelegate triple(s, p, o, property_map \\ nil), to: Triple, as: :new
|
||||
defdelegate triple(tuple, property_map \\ nil), to: Triple, as: :new
|
||||
if @star? do
|
||||
alias RDF.Star.{Triple, Quad, Statement}
|
||||
|
||||
defdelegate quad(s, p, o, g, property_map \\ nil), to: Quad, as: :new
|
||||
defdelegate quad(tuple, property_map \\ nil), to: Quad, as: :new
|
||||
defdelegate triple(s, p, o, property_map \\ nil), to: Triple, as: :new
|
||||
defdelegate triple(tuple, property_map \\ nil), to: Triple, as: :new
|
||||
|
||||
defdelegate statement(s, p, o), to: Statement, as: :new
|
||||
defdelegate statement(s, p, o, g), to: Statement, as: :new
|
||||
defdelegate statement(tuple, property_map \\ nil), to: Statement, as: :new
|
||||
defdelegate quad(s, p, o, g, property_map \\ nil), to: Quad, as: :new
|
||||
defdelegate quad(tuple, property_map \\ nil), to: Quad, as: :new
|
||||
|
||||
defdelegate statement(s, p, o), to: Statement, as: :new
|
||||
defdelegate statement(s, p, o, g), to: Statement, as: :new
|
||||
defdelegate statement(tuple, property_map \\ nil), to: Statement, as: :new
|
||||
|
||||
defdelegate coerce_subject(subject, property_map \\ nil), to: Statement
|
||||
defdelegate coerce_predicate(predicate, property_map \\ nil), to: Statement
|
||||
defdelegate coerce_object(object, property_map \\ nil), to: Statement
|
||||
defdelegate coerce_graph_name(graph_name), to: Statement
|
||||
else
|
||||
alias RDF.{Triple, Quad, Statement}
|
||||
|
||||
defdelegate triple(s, p, o, property_map \\ nil), to: Triple, as: :new
|
||||
defdelegate triple(tuple, property_map \\ nil), to: Triple, as: :new
|
||||
|
||||
defdelegate quad(s, p, o, g, property_map \\ nil), to: Quad, as: :new
|
||||
defdelegate quad(tuple, property_map \\ nil), to: Quad, as: :new
|
||||
|
||||
defdelegate statement(s, p, o), to: Statement, as: :new
|
||||
defdelegate statement(s, p, o, g), to: Statement, as: :new
|
||||
defdelegate statement(tuple, property_map \\ nil), to: Statement, as: :new
|
||||
|
||||
defdelegate coerce_subject(subject), to: Statement
|
||||
defdelegate coerce_predicate(predicate, property_map \\ nil), to: Statement
|
||||
defdelegate coerce_object(object), to: Statement
|
||||
defdelegate coerce_graph_name(graph_name), to: Statement
|
||||
end
|
||||
|
||||
defdelegate description(subject, opts \\ []), to: Description, as: :new
|
||||
|
||||
|
|
|
@ -15,10 +15,8 @@ defmodule RDF.Description do
|
|||
|
||||
@behaviour Access
|
||||
|
||||
import RDF.Statement,
|
||||
only: [coerce_subject: 1, coerce_predicate: 1, coerce_predicate: 2, coerce_object: 1]
|
||||
|
||||
alias RDF.{Statement, Triple, PropertyMap}
|
||||
alias RDF.PropertyMap
|
||||
alias RDF.Star.{Statement, Triple}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
subject: Statement.subject(),
|
||||
|
@ -72,7 +70,7 @@ defmodule RDF.Description do
|
|||
def new(subject, opts) do
|
||||
{data, opts} = Keyword.pop(opts, :init)
|
||||
|
||||
%__MODULE__{subject: coerce_subject(subject)}
|
||||
%__MODULE__{subject: RDF.coerce_subject(subject)}
|
||||
|> init(data, opts)
|
||||
end
|
||||
|
||||
|
@ -91,7 +89,7 @@ defmodule RDF.Description do
|
|||
"""
|
||||
@spec change_subject(t, Statement.coercible_subject()) :: t
|
||||
def change_subject(%__MODULE__{} = description, new_subject) do
|
||||
%__MODULE__{description | subject: coerce_subject(new_subject)}
|
||||
%__MODULE__{description | subject: RDF.coerce_subject(new_subject)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -121,7 +119,7 @@ defmodule RDF.Description do
|
|||
end
|
||||
|
||||
def add(%__MODULE__{} = description, {subject, predicate, objects}, opts) do
|
||||
if coerce_subject(subject) == description.subject do
|
||||
if RDF.coerce_subject(subject) == description.subject do
|
||||
add(description, {predicate, objects}, opts)
|
||||
else
|
||||
description
|
||||
|
@ -132,7 +130,7 @@ defmodule RDF.Description do
|
|||
normalized_objects =
|
||||
objects
|
||||
|> List.wrap()
|
||||
|> Map.new(&{coerce_object(&1), nil})
|
||||
|> Map.new(&{RDF.coerce_object(&1), nil})
|
||||
|
||||
if Enum.empty?(normalized_objects) do
|
||||
description
|
||||
|
@ -142,11 +140,9 @@ defmodule RDF.Description do
|
|||
| predications:
|
||||
Map.update(
|
||||
description.predications,
|
||||
coerce_predicate(predicate, PropertyMap.from_opts(opts)),
|
||||
RDF.coerce_predicate(predicate, PropertyMap.from_opts(opts)),
|
||||
normalized_objects,
|
||||
fn objects ->
|
||||
Map.merge(objects, normalized_objects)
|
||||
end
|
||||
&Map.merge(&1, normalized_objects)
|
||||
)
|
||||
}
|
||||
end
|
||||
|
@ -237,7 +233,7 @@ defmodule RDF.Description do
|
|||
def delete(description, input, opts \\ [])
|
||||
|
||||
def delete(%__MODULE__{} = description, {subject, predicate, objects}, opts) do
|
||||
if coerce_subject(subject) == description.subject do
|
||||
if RDF.coerce_subject(subject) == description.subject do
|
||||
delete(description, {predicate, objects}, opts)
|
||||
else
|
||||
description
|
||||
|
@ -249,13 +245,13 @@ defmodule RDF.Description do
|
|||
end
|
||||
|
||||
def delete(%__MODULE__{} = description, {predicate, objects}, opts) do
|
||||
predicate = coerce_predicate(predicate, PropertyMap.from_opts(opts))
|
||||
predicate = RDF.coerce_predicate(predicate, PropertyMap.from_opts(opts))
|
||||
|
||||
if current_objects = Map.get(description.predications, predicate) do
|
||||
normalized_objects =
|
||||
objects
|
||||
|> List.wrap()
|
||||
|> Enum.map(&coerce_object/1)
|
||||
|> Enum.map(&RDF.coerce_object/1)
|
||||
|
||||
rest = Map.drop(current_objects, normalized_objects)
|
||||
|
||||
|
@ -329,7 +325,7 @@ defmodule RDF.Description do
|
|||
def delete_predicates(%__MODULE__{} = description, property) do
|
||||
%__MODULE__{
|
||||
description
|
||||
| predications: Map.delete(description.predications, coerce_predicate(property))
|
||||
| predications: Map.delete(description.predications, RDF.coerce_predicate(property))
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -352,7 +348,7 @@ defmodule RDF.Description do
|
|||
@spec fetch(t, Statement.coercible_predicate()) :: {:ok, [Statement.object()]} | :error
|
||||
def fetch(%__MODULE__{} = description, predicate) do
|
||||
with {:ok, objects} <-
|
||||
Access.fetch(description.predications, coerce_predicate(predicate)) do
|
||||
Access.fetch(description.predications, RDF.coerce_predicate(predicate)) do
|
||||
{:ok, Map.keys(objects)}
|
||||
end
|
||||
end
|
||||
|
@ -427,15 +423,14 @@ defmodule RDF.Description do
|
|||
([Statement.Object] -> [Statement.Object])
|
||||
) :: t
|
||||
def update(%__MODULE__{} = description, predicate, initial \\ nil, fun) do
|
||||
predicate = coerce_predicate(predicate)
|
||||
predicate = RDF.coerce_predicate(predicate)
|
||||
|
||||
case get(description, predicate) do
|
||||
nil when is_nil(initial) ->
|
||||
description
|
||||
|
||||
nil ->
|
||||
if initial do
|
||||
put(description, {predicate, initial})
|
||||
else
|
||||
description
|
||||
end
|
||||
put(description, {predicate, initial})
|
||||
|
||||
objects ->
|
||||
objects
|
||||
|
@ -481,7 +476,7 @@ defmodule RDF.Description do
|
|||
([Statement.Object] -> {[Statement.Object], t} | :pop)
|
||||
) :: {[Statement.Object], t}
|
||||
def get_and_update(%__MODULE__{} = description, predicate, fun) do
|
||||
triple_predicate = coerce_predicate(predicate)
|
||||
triple_predicate = RDF.coerce_predicate(predicate)
|
||||
|
||||
case fun.(get(description, triple_predicate)) do
|
||||
{objects_to_return, new_objects} ->
|
||||
|
@ -533,7 +528,7 @@ defmodule RDF.Description do
|
|||
"""
|
||||
@impl Access
|
||||
def pop(%__MODULE__{} = description, predicate) do
|
||||
case Access.pop(description.predications, coerce_predicate(predicate)) do
|
||||
case Access.pop(description.predications, RDF.coerce_predicate(predicate)) do
|
||||
{nil, _} ->
|
||||
{nil, description}
|
||||
|
||||
|
@ -613,22 +608,37 @@ defmodule RDF.Description do
|
|||
"""
|
||||
@spec resources(t) :: MapSet.t()
|
||||
def resources(%__MODULE__{} = description) do
|
||||
description
|
||||
|> objects()
|
||||
objects(description)
|
||||
|> MapSet.union(predicates(description))
|
||||
end
|
||||
|
||||
@doc """
|
||||
The list of all triples within a `RDF.Description`.
|
||||
|
||||
When the optional `:filter_star` flag is set to `true` RDF-star triples with a triple as subject or object
|
||||
will be filtered. So, for a description with a triple as a subject you'll always get an empty list.
|
||||
"""
|
||||
@spec triples(t) :: keyword
|
||||
def triples(%__MODULE__{subject: s} = description) do
|
||||
Enum.flat_map(description.predications, fn {p, os} ->
|
||||
Enum.map(os, fn {o, _} -> {s, p, o} end)
|
||||
end)
|
||||
@spec triples(t, keyword) :: list(Triple.t())
|
||||
def triples(%__MODULE__{subject: s} = description, opts \\ []) do
|
||||
filter_star = Keyword.get(opts, :filter_star, false)
|
||||
|
||||
cond do
|
||||
filter_star and is_tuple(s) ->
|
||||
[]
|
||||
|
||||
filter_star ->
|
||||
for {p, os} <- description.predications, {o, _} when not is_tuple(o) <- os do
|
||||
{s, p, o}
|
||||
end
|
||||
|
||||
true ->
|
||||
for {p, os} <- description.predications, {o, _} <- os do
|
||||
{s, p, o}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defdelegate statements(description), to: __MODULE__, as: :triples
|
||||
defdelegate statements(description, opts \\ []), to: __MODULE__, as: :triples
|
||||
|
||||
@doc """
|
||||
Returns the number of statements of a `RDF.Description`.
|
||||
|
@ -649,7 +659,7 @@ defmodule RDF.Description do
|
|||
def include?(description, input, opts \\ [])
|
||||
|
||||
def include?(%__MODULE__{} = description, {subject, predicate, objects}, opts) do
|
||||
coerce_subject(subject) == description.subject &&
|
||||
RDF.coerce_subject(subject) == description.subject &&
|
||||
include?(description, {predicate, objects}, opts)
|
||||
end
|
||||
|
||||
|
@ -659,10 +669,10 @@ defmodule RDF.Description do
|
|||
|
||||
def include?(%__MODULE__{} = description, {predicate, objects}, opts) do
|
||||
if existing_objects =
|
||||
description.predications[coerce_predicate(predicate, PropertyMap.from_opts(opts))] do
|
||||
description.predications[RDF.coerce_predicate(predicate, PropertyMap.from_opts(opts))] do
|
||||
objects
|
||||
|> List.wrap()
|
||||
|> Enum.map(&coerce_object/1)
|
||||
|> Enum.map(&RDF.coerce_object/1)
|
||||
|> Enum.all?(fn object -> Map.has_key?(existing_objects, object) end)
|
||||
else
|
||||
false
|
||||
|
@ -714,7 +724,7 @@ defmodule RDF.Description do
|
|||
"""
|
||||
@spec describes?(t, Statement.subject()) :: boolean
|
||||
def describes?(%__MODULE__{subject: subject}, other_subject) do
|
||||
subject == coerce_subject(other_subject)
|
||||
subject == RDF.coerce_subject(other_subject)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -727,6 +737,8 @@ defmodule RDF.Description do
|
|||
When a `:context` option is given with a `RDF.PropertyMap`, predicates will
|
||||
be mapped to the terms defined in the `RDF.PropertyMap`, if present.
|
||||
|
||||
Note: RDF-star statements where the object is a triple will be ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
|
||||
|
@ -741,9 +753,9 @@ defmodule RDF.Description do
|
|||
@spec values(t, keyword) :: map
|
||||
def values(%__MODULE__{} = description, opts \\ []) do
|
||||
if property_map = PropertyMap.from_opts(opts) do
|
||||
map(description, Statement.default_property_mapping(property_map))
|
||||
map(description, RDF.Statement.default_property_mapping(property_map))
|
||||
else
|
||||
map(description, &Statement.default_term_mapping/1)
|
||||
map(description, &RDF.Statement.default_term_mapping/1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -759,6 +771,8 @@ defmodule RDF.Description do
|
|||
`nil` this will be interpreted as an error and will become the overhaul result
|
||||
of the `map/2` call.
|
||||
|
||||
Note: RDF-star statements where the object is a triple will be ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
|
||||
|
@ -779,11 +793,21 @@ defmodule RDF.Description do
|
|||
def map(description, fun)
|
||||
|
||||
def map(%__MODULE__{} = description, fun) do
|
||||
Map.new(description.predications, fn {predicate, objects} ->
|
||||
{
|
||||
fun.({:predicate, predicate}),
|
||||
objects |> Map.keys() |> Enum.map(&fun.({:object, &1}))
|
||||
}
|
||||
Enum.reduce(description.predications, %{}, fn {predicate, objects}, map ->
|
||||
objects
|
||||
|> Map.keys()
|
||||
|> Enum.reject(&is_tuple/1)
|
||||
|> case do
|
||||
[] ->
|
||||
map
|
||||
|
||||
objects ->
|
||||
Map.put(
|
||||
map,
|
||||
fun.({:predicate, predicate}),
|
||||
Enum.map(objects, &fun.({:object, &1}))
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
@ -803,7 +827,7 @@ defmodule RDF.Description do
|
|||
%__MODULE__{
|
||||
description
|
||||
| predications:
|
||||
Map.take(description.predications, Enum.map(predicates, &coerce_predicate/1))
|
||||
Map.take(description.predications, Enum.map(predicates, &RDF.coerce_predicate/1))
|
||||
}
|
||||
end
|
||||
|
||||
|
|
211
lib/rdf/graph.ex
211
lib/rdf/graph.ex
|
@ -15,9 +15,10 @@ defmodule RDF.Graph do
|
|||
|
||||
@behaviour Access
|
||||
|
||||
import RDF.Statement, only: [coerce_subject: 1, coerce_graph_name: 1]
|
||||
alias RDF.{Description, IRI, PrefixMap, PropertyMap}
|
||||
alias RDF.Star.Statement
|
||||
|
||||
import RDF.Utils
|
||||
alias RDF.{Description, IRI, PrefixMap, Statement, PropertyMap}
|
||||
|
||||
@type graph_description :: %{Statement.subject() => Description.t()}
|
||||
|
||||
|
@ -120,7 +121,7 @@ defmodule RDF.Graph do
|
|||
def new(data, opts)
|
||||
|
||||
def new(%__MODULE__{} = graph, opts) do
|
||||
%__MODULE__{graph | name: opts |> Keyword.get(:name) |> coerce_graph_name()}
|
||||
%__MODULE__{graph | name: opts |> Keyword.get(:name) |> RDF.coerce_graph_name()}
|
||||
|> add_prefixes(Keyword.get(opts, :prefixes))
|
||||
|> set_base_iri(Keyword.get(opts, :base_iri))
|
||||
end
|
||||
|
@ -158,7 +159,7 @@ defmodule RDF.Graph do
|
|||
"""
|
||||
@spec change_name(t, Statement.coercible_graph_name()) :: t
|
||||
def change_name(%__MODULE__{} = graph, new_name) do
|
||||
%__MODULE__{graph | name: coerce_graph_name(new_name)}
|
||||
%__MODULE__{graph | name: RDF.coerce_graph_name(new_name)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -184,10 +185,10 @@ defmodule RDF.Graph do
|
|||
def add(graph, input, opts \\ [])
|
||||
|
||||
def add(%__MODULE__{} = graph, {subject, predications}, opts),
|
||||
do: do_add(graph, coerce_subject(subject), predications, opts)
|
||||
do: do_add(graph, RDF.coerce_subject(subject), predications, opts)
|
||||
|
||||
def add(%__MODULE__{} = graph, {subject, _, _} = triple, opts),
|
||||
do: do_add(graph, coerce_subject(subject), triple, opts)
|
||||
do: do_add(graph, RDF.coerce_subject(subject), triple, opts)
|
||||
|
||||
def add(graph, {subject, predicate, object, _}, opts),
|
||||
do: add(graph, {subject, predicate, object}, opts)
|
||||
|
@ -340,10 +341,10 @@ defmodule RDF.Graph do
|
|||
def delete(graph, input, opts \\ [])
|
||||
|
||||
def delete(%__MODULE__{} = graph, {subject, _, _} = triple, opts),
|
||||
do: do_delete(graph, coerce_subject(subject), triple, opts)
|
||||
do: do_delete(graph, RDF.coerce_subject(subject), triple, opts)
|
||||
|
||||
def delete(%__MODULE__{} = graph, {subject, predications}, opts),
|
||||
do: do_delete(graph, coerce_subject(subject), predications, opts)
|
||||
do: do_delete(graph, RDF.coerce_subject(subject), predications, opts)
|
||||
|
||||
def delete(graph, {subject, predicate, object, _}, opts),
|
||||
do: delete(graph, {subject, predicate, object}, opts)
|
||||
|
@ -404,7 +405,7 @@ defmodule RDF.Graph do
|
|||
end
|
||||
|
||||
def delete_descriptions(%__MODULE__{} = graph, subject) do
|
||||
%__MODULE__{graph | descriptions: Map.delete(graph.descriptions, coerce_subject(subject))}
|
||||
%__MODULE__{graph | descriptions: Map.delete(graph.descriptions, RDF.coerce_subject(subject))}
|
||||
end
|
||||
|
||||
defdelegate delete_subjects(graph, subjects), to: __MODULE__, as: :delete_descriptions
|
||||
|
@ -449,7 +450,7 @@ defmodule RDF.Graph do
|
|||
update_description_fun
|
||||
) :: t
|
||||
def update(%__MODULE__{} = graph, subject, initial \\ nil, fun) do
|
||||
subject = coerce_subject(subject)
|
||||
subject = RDF.coerce_subject(subject)
|
||||
|
||||
case get(graph, subject) do
|
||||
nil ->
|
||||
|
@ -491,31 +492,7 @@ defmodule RDF.Graph do
|
|||
@impl Access
|
||||
@spec fetch(t, Statement.coercible_subject()) :: {:ok, Description.t()} | :error
|
||||
def fetch(%__MODULE__{} = graph, subject) do
|
||||
Access.fetch(graph.descriptions, coerce_subject(subject))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute the given `query` against the given `graph`.
|
||||
|
||||
This is just a convenience delegator function to `RDF.Query.execute!/3` with
|
||||
the first two arguments swapped so it can be used in a pipeline on a `RDF.Graph`.
|
||||
|
||||
See `RDF.Query.execute/3` and `RDF.Query.execute!/3` for more information and examples.
|
||||
"""
|
||||
def query(graph, query, opts \\ []) do
|
||||
RDF.Query.execute!(query, graph, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `Stream` for the execution of the given `query` against the given `graph`.
|
||||
|
||||
This is just a convenience delegator function to `RDF.Query.stream!/3` with
|
||||
the first two arguments swapped so it can be used in a pipeline on a `RDF.Graph`.
|
||||
|
||||
See `RDF.Query.stream/3` and `RDF.Query.stream!/3` for more information and examples.
|
||||
"""
|
||||
def query_stream(graph, query, opts \\ []) do
|
||||
RDF.Query.stream!(query, graph, opts)
|
||||
Access.fetch(graph.descriptions, RDF.coerce_subject(subject))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -542,13 +519,7 @@ defmodule RDF.Graph do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
The `RDF.Description` of the given subject.
|
||||
"""
|
||||
@spec description(t, Statement.coercible_subject()) :: Description.t() | nil
|
||||
def description(%__MODULE__{} = graph, subject) do
|
||||
Map.get(graph.descriptions, coerce_subject(subject))
|
||||
end
|
||||
defdelegate description(graph, subject), to: __MODULE__, as: :get
|
||||
|
||||
@doc """
|
||||
All `RDF.Description`s within a `RDF.Graph`.
|
||||
|
@ -585,7 +556,7 @@ defmodule RDF.Graph do
|
|||
@spec get_and_update(t, Statement.coercible_subject(), get_and_update_description_fun) ::
|
||||
{Description.t(), input}
|
||||
def get_and_update(%__MODULE__{} = graph, subject, fun) do
|
||||
subject = coerce_subject(subject)
|
||||
subject = RDF.coerce_subject(subject)
|
||||
|
||||
case fun.(get(graph, subject)) do
|
||||
{old_description, new_description} ->
|
||||
|
@ -639,7 +610,7 @@ defmodule RDF.Graph do
|
|||
@impl Access
|
||||
@spec pop(t, Statement.coercible_subject()) :: {Description.t() | nil, t}
|
||||
def pop(%__MODULE__{} = graph, subject) do
|
||||
case Access.pop(graph.descriptions, coerce_subject(subject)) do
|
||||
case Access.pop(graph.descriptions, RDF.coerce_subject(subject)) do
|
||||
{nil, _} ->
|
||||
{nil, graph}
|
||||
|
||||
|
@ -786,13 +757,20 @@ defmodule RDF.Graph do
|
|||
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
|
||||
"""
|
||||
@spec triples(t) :: [Statement.t()]
|
||||
def triples(%__MODULE__{} = graph) do
|
||||
Enum.flat_map(graph.descriptions, fn {_, description} ->
|
||||
Description.triples(description)
|
||||
end)
|
||||
def triples(%__MODULE__{} = graph, opts \\ []) do
|
||||
if Keyword.get(opts, :filter_star, false) do
|
||||
Enum.flat_map(graph.descriptions, fn
|
||||
{subject, _} when is_tuple(subject) -> []
|
||||
{_, description} -> Description.triples(description, opts)
|
||||
end)
|
||||
else
|
||||
Enum.flat_map(graph.descriptions, fn {_, description} ->
|
||||
Description.triples(description, opts)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defdelegate statements(graph), to: __MODULE__, as: :triples
|
||||
defdelegate statements(graph, opts \\ []), to: __MODULE__, as: :triples
|
||||
|
||||
@doc """
|
||||
Checks if the given `input` statements exist within `graph`.
|
||||
|
@ -801,13 +779,13 @@ defmodule RDF.Graph do
|
|||
def include?(graph, input, opts \\ [])
|
||||
|
||||
def include?(%__MODULE__{} = graph, {subject, _, _} = triple, opts),
|
||||
do: do_include?(graph, coerce_subject(subject), triple, opts)
|
||||
do: do_include?(graph, RDF.coerce_subject(subject), triple, opts)
|
||||
|
||||
def include?(graph, {subject, predicate, object, _}, opts),
|
||||
do: include?(graph, {subject, predicate, object}, opts)
|
||||
|
||||
def include?(%__MODULE__{} = graph, {subject, predications}, opts),
|
||||
do: do_include?(graph, coerce_subject(subject), predications, opts)
|
||||
do: do_include?(graph, RDF.coerce_subject(subject), predications, opts)
|
||||
|
||||
def include?(%__MODULE__{} = graph, %Description{subject: subject} = description, opts),
|
||||
do: do_include?(graph, subject, description, opts)
|
||||
|
@ -851,7 +829,68 @@ defmodule RDF.Graph do
|
|||
"""
|
||||
@spec describes?(t, Statement.coercible_subject()) :: boolean
|
||||
def describes?(%__MODULE__{} = graph, subject) do
|
||||
Map.has_key?(graph.descriptions, coerce_subject(subject))
|
||||
Map.has_key?(graph.descriptions, RDF.coerce_subject(subject))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a graph from another one by limiting its statements to those using one of the given `subjects`.
|
||||
|
||||
If `subjects` contains IRIs that are not used in the `graph`, they're simply ignored.
|
||||
|
||||
The optional `properties` argument allows to limit also properties of the subject descriptions.
|
||||
|
||||
If `nil` is passed as the `subjects`, the subjects will not be limited.
|
||||
"""
|
||||
@spec take(
|
||||
t,
|
||||
[Statement.coercible_subject()] | Enum.t() | nil,
|
||||
[Statement.coercible_predicate()] | Enum.t() | nil
|
||||
) :: t
|
||||
def take(graph, subjects, properties \\ nil)
|
||||
|
||||
def take(%__MODULE__{} = graph, nil, nil), do: graph
|
||||
|
||||
def take(%__MODULE__{descriptions: descriptions} = graph, subjects, nil) do
|
||||
%__MODULE__{
|
||||
graph
|
||||
| descriptions: Map.take(descriptions, Enum.map(subjects, &RDF.coerce_subject/1))
|
||||
}
|
||||
end
|
||||
|
||||
def take(%__MODULE__{} = graph, subjects, properties) do
|
||||
graph = take(graph, subjects, nil)
|
||||
|
||||
%__MODULE__{
|
||||
graph
|
||||
| descriptions:
|
||||
Map.new(graph.descriptions, fn {subject, description} ->
|
||||
{subject, Description.take(description, properties)}
|
||||
end)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Execute the given `query` against the given `graph`.
|
||||
|
||||
This is just a convenience delegator function to `RDF.Query.execute!/3` with
|
||||
the first two arguments swapped so it can be used in a pipeline on a `RDF.Graph`.
|
||||
|
||||
See `RDF.Query.execute/3` and `RDF.Query.execute!/3` for more information and examples.
|
||||
"""
|
||||
def query(graph, query, opts \\ []) do
|
||||
RDF.Query.execute!(query, graph, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a `Stream` for the execution of the given `query` against the given `graph`.
|
||||
|
||||
This is just a convenience delegator function to `RDF.Query.stream!/3` with
|
||||
the first two arguments swapped so it can be used in a pipeline on a `RDF.Graph`.
|
||||
|
||||
See `RDF.Query.stream/3` and `RDF.Query.stream!/3` for more information and examples.
|
||||
"""
|
||||
def query_stream(graph, query, opts \\ []) do
|
||||
RDF.Query.stream!(query, graph, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -886,9 +925,9 @@ defmodule RDF.Graph do
|
|||
@spec values(t, keyword) :: map
|
||||
def values(%__MODULE__{} = graph, opts \\ []) do
|
||||
if property_map = PropertyMap.from_opts(opts) do
|
||||
map(graph, Statement.default_property_mapping(property_map))
|
||||
map(graph, RDF.Statement.default_property_mapping(property_map))
|
||||
else
|
||||
map(graph, &Statement.default_term_mapping/1)
|
||||
map(graph, &RDF.Statement.default_term_mapping/1)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -901,6 +940,8 @@ defmodule RDF.Graph do
|
|||
`nil` this will be interpreted as an error and will become the overhaul result
|
||||
of the `map/2` call.
|
||||
|
||||
Note: RDF-star statements where the subject or object is a triple will be ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Graph.new([
|
||||
|
@ -914,8 +955,8 @@ defmodule RDF.Graph do
|
|||
...> |> String.split("/")
|
||||
...> |> List.last()
|
||||
...> |> String.to_atom()
|
||||
...> {_, term} ->
|
||||
...> RDF.Term.value(term)
|
||||
...> {_, term} ->
|
||||
...> RDF.Term.value(term)
|
||||
...> end)
|
||||
%{
|
||||
"http://example.com/S1" => %{p: ["Foo"]},
|
||||
|
@ -927,51 +968,25 @@ defmodule RDF.Graph do
|
|||
def map(description, fun)
|
||||
|
||||
def map(%__MODULE__{} = graph, fun) do
|
||||
Map.new(graph.descriptions, fn {subject, description} ->
|
||||
{
|
||||
fun.({:subject, subject}),
|
||||
Description.map(description, fun)
|
||||
}
|
||||
Enum.reduce(graph.descriptions, %{}, fn
|
||||
{subject, _}, map when is_tuple(subject) ->
|
||||
map
|
||||
|
||||
{subject, description}, map ->
|
||||
case Description.map(description, fun) do
|
||||
mapped_objects when map_size(mapped_objects) == 0 ->
|
||||
map
|
||||
|
||||
mapped_objects ->
|
||||
Map.put(
|
||||
map,
|
||||
fun.({:subject, subject}),
|
||||
mapped_objects
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a graph from another one by limiting its statements to those using one of the given `subjects`.
|
||||
|
||||
If `subjects` contains IRIs that are not used in the `graph`, they're simply ignored.
|
||||
|
||||
The optional `properties` argument allows to limit also properties of the subject descriptions.
|
||||
|
||||
If `nil` is passed as the `subjects`, the subjects will not be limited.
|
||||
"""
|
||||
@spec take(
|
||||
t,
|
||||
[Statement.coercible_subject()] | Enum.t() | nil,
|
||||
[Statement.coercible_predicate()] | Enum.t() | nil
|
||||
) :: t
|
||||
def take(graph, subjects, properties \\ nil)
|
||||
|
||||
def take(%__MODULE__{} = graph, nil, nil), do: graph
|
||||
|
||||
def take(%__MODULE__{descriptions: descriptions} = graph, subjects, nil) do
|
||||
%__MODULE__{
|
||||
graph
|
||||
| descriptions: Map.take(descriptions, Enum.map(subjects, &coerce_subject/1))
|
||||
}
|
||||
end
|
||||
|
||||
def take(%__MODULE__{} = graph, subjects, properties) do
|
||||
graph = take(graph, subjects, nil)
|
||||
|
||||
%__MODULE__{
|
||||
graph
|
||||
| descriptions:
|
||||
Map.new(graph.descriptions, fn {subject, description} ->
|
||||
{subject, Description.take(description, properties)}
|
||||
end)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if two `RDF.Graph`s are equal.
|
||||
|
||||
|
|
|
@ -71,13 +71,8 @@ defmodule RDF.Star.Statement do
|
|||
def coerce_subject({_, _, _} = triple, property_map), do: Triple.new(triple, property_map)
|
||||
def coerce_subject(subject, _), do: RDF.Statement.coerce_subject(subject)
|
||||
|
||||
@doc false
|
||||
@spec coerce_predicate(coercible_predicate) :: predicate
|
||||
def coerce_predicate(iri), do: RDF.Statement.coerce_predicate(iri)
|
||||
|
||||
@doc false
|
||||
@spec coerce_predicate(coercible_predicate, PropertyMap.t()) :: predicate
|
||||
def coerce_predicate(term, context), do: RDF.Statement.coerce_predicate(term, context)
|
||||
defdelegate coerce_predicate(coercible_predicate), to: RDF.Statement
|
||||
defdelegate coerce_predicate(term, context), to: RDF.Statement
|
||||
|
||||
@doc false
|
||||
@spec coerce_object(coercible_object, PropertyMap.t() | nil) :: object
|
||||
|
@ -85,9 +80,7 @@ defmodule RDF.Star.Statement do
|
|||
def coerce_object({_, _, _} = triple, property_map), do: Triple.new(triple, property_map)
|
||||
def coerce_object(object, _), do: RDF.Statement.coerce_object(object)
|
||||
|
||||
@doc false
|
||||
@spec coerce_graph_name(coercible_graph_name) :: graph_name
|
||||
def coerce_graph_name(iri), do: RDF.Statement.coerce_graph_name(iri)
|
||||
defdelegate coerce_graph_name(iri), to: RDF.Statement
|
||||
|
||||
@doc """
|
||||
Checks if the given tuple is a valid RDF-star statement, i.e. RDF-star triple or quad.
|
||||
|
@ -123,4 +116,25 @@ defmodule RDF.Star.Statement do
|
|||
|
||||
@spec valid_graph_name?(graph_name | any) :: boolean
|
||||
def valid_graph_name?(any), do: RDF.Statement.valid_graph_name?(any)
|
||||
|
||||
@doc """
|
||||
Checks if the given tuple is a RDF-star statement annotating a triple on subject or object position.
|
||||
|
||||
Note: This function won't check if the given tuple or the annotated triple is valid.
|
||||
Use `valid?/1` for this purpose.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Star.Statement.annotation?({EX.S, EX.P, EX.O})
|
||||
false
|
||||
iex> RDF.Star.Statement.annotation?({EX.AS, EX.AP, {EX.S, EX.P, EX.O}})
|
||||
true
|
||||
iex> RDF.Star.Statement.annotation?({{EX.S, EX.P, EX.O}, EX.AP, EX.AO})
|
||||
true
|
||||
|
||||
"""
|
||||
@spec annotation?(Triple.t() | Quad.t() | any) :: boolean
|
||||
def annotation?({{_, _, _}, _, _}), do: true
|
||||
def annotation?({_, _, {_, _, _}}), do: true
|
||||
def annotation?(_), do: false
|
||||
end
|
||||
|
|
|
@ -54,6 +54,12 @@ defmodule RDF.Test.Case do
|
|||
###############################
|
||||
# RDF.Statement
|
||||
|
||||
@statement {RDF.iri(EX.S), RDF.iri(EX.P), RDF.literal("Foo")}
|
||||
def statement(), do: @statement
|
||||
|
||||
@coercible_statement {EX.S, EX.P, "Foo"}
|
||||
def coercible_statement(), do: @coercible_statement
|
||||
|
||||
@valid_triple {RDF.iri(EX.S), EX.p(), RDF.iri(EX.O)}
|
||||
def valid_triple(), do: @valid_triple
|
||||
|
||||
|
@ -157,8 +163,10 @@ defmodule RDF.Test.Case do
|
|||
do: descriptions == %{}
|
||||
|
||||
def graph_includes_statement?(graph, {subject, _, _} = statement) do
|
||||
subject = if is_tuple(subject), do: subject, else: iri(subject)
|
||||
|
||||
graph.descriptions
|
||||
|> Map.get(iri(subject), %{})
|
||||
|> Map.get(subject, %{})
|
||||
|> Enum.member?(statement)
|
||||
end
|
||||
|
||||
|
@ -198,4 +206,25 @@ defmodule RDF.Test.Case do
|
|||
|> Map.get(iri(graph_context), named_graph(graph_context))
|
||||
|> graph_includes_statement?({subject, predicate, objects})
|
||||
end
|
||||
|
||||
###############################
|
||||
# RDF.Star annotations
|
||||
|
||||
@star_statement {@statement, EX.ap(), EX.ao()}
|
||||
def star_statement(), do: @star_statement
|
||||
|
||||
@empty_annotation Description.new(@statement)
|
||||
def empty_annotation(), do: @empty_annotation
|
||||
|
||||
@annotation Description.new(@statement, init: {EX.ap(), EX.ao()})
|
||||
def annotation(), do: @annotation
|
||||
|
||||
@object_annotation Description.new(EX.As, init: {EX.ap(), @statement})
|
||||
def object_annotation(), do: @object_annotation
|
||||
|
||||
@graph_with_annotation Graph.new(init: @annotation)
|
||||
def graph_with_annotation(), do: @graph_with_annotation
|
||||
|
||||
@graph_with_annotations Graph.new(init: [@annotation, @object_annotation])
|
||||
def graph_with_annotations(), do: @graph_with_annotations
|
||||
end
|
||||
|
|
237
test/unit/star/description_test.exs
Normal file
237
test/unit/star/description_test.exs
Normal file
|
@ -0,0 +1,237 @@
|
|||
defmodule RDF.Star.Description.Test do
|
||||
use RDF.Test.Case
|
||||
|
||||
describe "new/1" do
|
||||
test "with a valid triple as subject" do
|
||||
assert description_of_subject(
|
||||
Description.new(statement()),
|
||||
statement()
|
||||
)
|
||||
|
||||
assert Description.new(statement(), init: {EX.ap(), EX.ao()})
|
||||
|> description_includes_predication({EX.ap(), EX.ao()})
|
||||
end
|
||||
|
||||
test "with a coercible triple as subject" do
|
||||
assert description_of_subject(
|
||||
Description.new(coercible_statement()),
|
||||
statement()
|
||||
)
|
||||
|
||||
assert Description.new(statement(), init: {EX.ap(), EX.ao()})
|
||||
|> description_includes_predication({EX.ap(), EX.ao()})
|
||||
end
|
||||
end
|
||||
|
||||
test "subject/1" do
|
||||
assert Description.subject(empty_annotation()) == statement()
|
||||
end
|
||||
|
||||
test "change_subject/2" do
|
||||
changed = Description.change_subject(description(), coercible_statement())
|
||||
assert changed.subject == statement()
|
||||
assert Description.change_subject(changed, description().subject) == description()
|
||||
end
|
||||
|
||||
describe "add/3" do
|
||||
test "with a proper triple as a subject" do
|
||||
assert empty_annotation()
|
||||
|> Description.add({statement(), EX.ap(), EX.ao()})
|
||||
|> description_includes_predication({EX.ap(), EX.ao()})
|
||||
end
|
||||
|
||||
test "with a proper triple as a object" do
|
||||
assert description()
|
||||
|> Description.add({EX.Subject, EX.ap(), statement()})
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with a proper triple as a subject and object" do
|
||||
assert empty_annotation()
|
||||
|> Description.add({statement(), EX.ap(), statement()})
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with a list of proper objects" do
|
||||
description =
|
||||
description()
|
||||
|> Description.add({EX.Subject, EX.ap(), [statement(), {EX.s(), EX.p(), EX.o()}]})
|
||||
|
||||
assert description_includes_predication(description, {EX.ap(), statement()})
|
||||
assert description_includes_predication(description, {EX.ap(), {EX.s(), EX.p(), EX.o()}})
|
||||
end
|
||||
|
||||
test "with a list of predicate-object tuples" do
|
||||
assert empty_annotation()
|
||||
|> Description.add([{EX.ap(), statement()}])
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with a description map" do
|
||||
assert empty_annotation()
|
||||
|> Description.add(%{EX.ap() => statement()})
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with coercible triples" do
|
||||
assert empty_annotation()
|
||||
|> Description.add({coercible_statement(), EX.ap(), coercible_statement()})
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
end
|
||||
|
||||
test "put/3" do
|
||||
assert annotation()
|
||||
|> Description.put({statement(), EX.ap(), EX.ao2()})
|
||||
|> description_includes_predication({EX.ap(), EX.ao2()})
|
||||
|
||||
assert annotation()
|
||||
|> Description.put({statement(), EX.ap(), statement()})
|
||||
|> description_includes_predication({EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "delete/3" do
|
||||
assert Description.delete(annotation(), {statement(), EX.ap(), EX.ao()}) ==
|
||||
empty_annotation()
|
||||
|
||||
assert Description.delete(object_annotation(), {EX.As, EX.ap(), statement()}) ==
|
||||
Description.new(EX.As)
|
||||
|
||||
assert Description.delete(object_annotation(), {EX.ap(), statement()}) ==
|
||||
Description.new(EX.As)
|
||||
end
|
||||
|
||||
test "delete_predicates/2" do
|
||||
assert Description.delete_predicates(annotation(), EX.ap()) ==
|
||||
empty_annotation()
|
||||
|
||||
assert Description.delete_predicates(object_annotation(), EX.ap()) ==
|
||||
Description.new(EX.As)
|
||||
end
|
||||
|
||||
test "fetch/2" do
|
||||
assert Description.fetch(annotation(), EX.ap()) == {:ok, [EX.ao()]}
|
||||
assert Description.fetch(object_annotation(), EX.ap()) == {:ok, [statement()]}
|
||||
end
|
||||
|
||||
test "get/2" do
|
||||
assert Description.get(annotation(), EX.ap()) == [EX.ao()]
|
||||
assert Description.get(object_annotation(), EX.ap()) == [statement()]
|
||||
end
|
||||
|
||||
test "first/2" do
|
||||
assert Description.first(annotation(), EX.ap()) == EX.ao()
|
||||
assert Description.first(object_annotation(), EX.ap()) == statement()
|
||||
end
|
||||
|
||||
test "pop/2" do
|
||||
assert Description.pop(annotation(), EX.ap()) == {[EX.ao()], empty_annotation()}
|
||||
|
||||
assert Description.pop(object_annotation(), EX.ap()) ==
|
||||
{[statement()], Description.new(EX.As)}
|
||||
end
|
||||
|
||||
test "update/4" do
|
||||
assert (description =
|
||||
Description.update(empty_annotation(), EX.ap(), statement(), fn _ ->
|
||||
raise "unexpected"
|
||||
end)) ==
|
||||
empty_annotation()
|
||||
|> Description.add(%{EX.ap() => statement()})
|
||||
|
||||
assert Description.update(description, EX.ap(), statement(), fn
|
||||
[{s, p, _} = statement] ->
|
||||
assert statement == statement()
|
||||
[statement, {s, p, EX.O}]
|
||||
end) ==
|
||||
empty_annotation()
|
||||
|> Description.add(%{EX.ap() => statement()})
|
||||
|> Description.add(%{EX.ap() => {EX.S, EX.P, EX.O}})
|
||||
end
|
||||
|
||||
test "objects/1" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.objects() == MapSet.new([statement()])
|
||||
end
|
||||
|
||||
test "resources/1" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.resources() == MapSet.new([statement(), EX.ap()])
|
||||
end
|
||||
|
||||
describe "statements/1" do
|
||||
test "without the filter_star flag" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.statements() == [{statement(), EX.ap(), statement()}]
|
||||
end
|
||||
|
||||
test "with the filter_star flag" do
|
||||
assert Description.new(statement(),
|
||||
init: [
|
||||
{EX.ap(), EX.ao()},
|
||||
{EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Description.statements(filter_star: true) == []
|
||||
|
||||
assert Description.new(EX.s(),
|
||||
init: [
|
||||
{EX.p(), EX.o()},
|
||||
{EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Description.statements(filter_star: true) == [{EX.s(), EX.p(), EX.o()}]
|
||||
end
|
||||
end
|
||||
|
||||
test "statement_count/1" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.statement_count() == 1
|
||||
end
|
||||
|
||||
test "include?/2" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.include?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "describes?/2" do
|
||||
assert Description.describes?(annotation(), statement())
|
||||
assert Description.describes?(annotation(), coercible_statement())
|
||||
end
|
||||
|
||||
test "values/2" do
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.values() == %{}
|
||||
|
||||
assert Description.new(EX.s(),
|
||||
init: [
|
||||
{EX.p(), ~L"Foo"},
|
||||
{EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Description.values() ==
|
||||
%{RDF.Term.value(EX.p()) => ["Foo"]}
|
||||
end
|
||||
|
||||
test "map/2" do
|
||||
mapping = fn
|
||||
{:predicate, predicate} ->
|
||||
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
|
||||
|
||||
{_, term} ->
|
||||
RDF.Term.value(term)
|
||||
end
|
||||
|
||||
assert Description.new(statement(), init: {EX.ap(), statement()})
|
||||
|> Description.map(mapping) == %{}
|
||||
|
||||
assert Description.new(EX.s(),
|
||||
init: [
|
||||
{EX.p(), ~L"Foo"},
|
||||
{EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Description.map(mapping) ==
|
||||
%{p: ["Foo"]}
|
||||
end
|
||||
end
|
255
test/unit/star/graph_test.exs
Normal file
255
test/unit/star/graph_test.exs
Normal file
|
@ -0,0 +1,255 @@
|
|||
defmodule RDF.Star.Graph.Test do
|
||||
use RDF.Test.Case
|
||||
|
||||
test "new/1" do
|
||||
assert Graph.new(init: {statement(), EX.ap(), EX.ao()})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), EX.ao()})
|
||||
|
||||
assert Graph.new(init: annotation())
|
||||
|> graph_includes_statement?({statement(), EX.ap(), EX.ao()})
|
||||
end
|
||||
|
||||
describe "add/3" do
|
||||
test "with a proper triple as a subject" do
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.add({statement(), EX.ap(), EX.ao1()})
|
||||
|> Graph.add({statement(), EX.ap(), EX.ao2()})
|
||||
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao1()})
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao2()})
|
||||
end
|
||||
|
||||
test "with a proper triple as a object" do
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.add({EX.as(), EX.ap(), statement()})
|
||||
|> Graph.add({EX.as(), EX.ap(), {EX.s(), EX.p(), EX.o2()}})
|
||||
|
||||
assert graph_includes_statement?(graph, {EX.as(), EX.ap(), statement()})
|
||||
assert graph_includes_statement?(graph, {EX.as(), EX.ap(), {EX.s(), EX.p(), EX.o2()}})
|
||||
end
|
||||
|
||||
test "with a proper triple as a subject and object" do
|
||||
assert graph()
|
||||
|> Graph.add({statement(), EX.ap(), statement()})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with a list of triples" do
|
||||
graph =
|
||||
Graph.add(graph(), [
|
||||
{statement(), EX.ap(), EX.ao()},
|
||||
{EX.as(), EX.ap(), statement()},
|
||||
{EX.s(), EX.p(), EX.o()}
|
||||
])
|
||||
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao()})
|
||||
assert graph_includes_statement?(graph, {EX.as(), EX.ap(), statement()})
|
||||
assert graph_includes_statement?(graph, {EX.s(), EX.p(), EX.o()})
|
||||
end
|
||||
|
||||
test "with a graph map" do
|
||||
assert graph()
|
||||
|> Graph.add(%{statement() => %{EX.ap() => statement()}})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with coercible triples" do
|
||||
assert graph()
|
||||
|> Graph.add({coercible_statement(), EX.ap(), coercible_statement()})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
end
|
||||
|
||||
describe "put/3" do
|
||||
test "with a proper triple as a subject" do
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.put({statement(), EX.ap(), EX.ao1()})
|
||||
|> Graph.put({statement(), EX.ap(), EX.ao2()})
|
||||
|
||||
refute graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao1()})
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao2()})
|
||||
end
|
||||
|
||||
test "with a proper triple as a object" do
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.put({EX.as(), EX.ap(), statement()})
|
||||
|> Graph.put({EX.as(), EX.ap(), {EX.s(), EX.p(), EX.o2()}})
|
||||
|
||||
refute graph_includes_statement?(graph, {EX.as(), EX.ap(), statement()})
|
||||
assert graph_includes_statement?(graph, {EX.as(), EX.ap(), {EX.s(), EX.p(), EX.o2()}})
|
||||
end
|
||||
|
||||
test "with a proper triple as a subject and object" do
|
||||
assert graph()
|
||||
|> Graph.put({statement(), EX.ap(), statement()})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with a list of triples" do
|
||||
graph =
|
||||
Graph.put(graph(), [
|
||||
{statement(), EX.ap(), EX.ao()},
|
||||
{EX.as(), EX.ap(), statement()},
|
||||
{EX.s(), EX.p(), EX.o()}
|
||||
])
|
||||
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao()})
|
||||
assert graph_includes_statement?(graph, {EX.as(), EX.ap(), statement()})
|
||||
assert graph_includes_statement?(graph, {EX.s(), EX.p(), EX.o()})
|
||||
end
|
||||
|
||||
test "with a graph map" do
|
||||
assert graph()
|
||||
|> Graph.put(%{statement() => %{EX.ap() => statement()}})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "with coercible triples" do
|
||||
assert graph()
|
||||
|> Graph.put({coercible_statement(), EX.ap(), coercible_statement()})
|
||||
|> graph_includes_statement?({statement(), EX.ap(), statement()})
|
||||
end
|
||||
end
|
||||
|
||||
test "put_properties/3" do
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.put_properties({statement(), EX.ap(), EX.ao1()})
|
||||
|> Graph.put_properties({statement(), EX.ap(), EX.ao2()})
|
||||
|
||||
refute graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao1()})
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao2()})
|
||||
|
||||
graph =
|
||||
graph()
|
||||
|> Graph.put_properties(Graph.new(init: {statement(), EX.ap(), EX.ao1()}))
|
||||
|> Graph.put_properties(Graph.new(init: {statement(), EX.ap(), EX.ao2()}))
|
||||
|
||||
refute graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao1()})
|
||||
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao2()})
|
||||
end
|
||||
|
||||
test "delete/3" do
|
||||
assert graph_with_annotation() |> Graph.delete(star_statement()) == graph()
|
||||
end
|
||||
|
||||
test "delete_description/3" do
|
||||
assert graph_with_annotation() |> Graph.delete_descriptions(statement()) == graph()
|
||||
end
|
||||
|
||||
test "update/3" do
|
||||
assert Graph.update(graph(), statement(), annotation(), fn _ -> raise "unexpected" end) ==
|
||||
graph_with_annotation()
|
||||
|
||||
assert graph()
|
||||
|> Graph.add({statement(), EX.foo(), EX.bar()})
|
||||
|> Graph.update(statement(), fn _ -> annotation() end) ==
|
||||
graph_with_annotation()
|
||||
end
|
||||
|
||||
test "fetch/2" do
|
||||
assert graph_with_annotation() |> Graph.fetch(statement()) == {:ok, annotation()}
|
||||
end
|
||||
|
||||
test "get/3" do
|
||||
assert graph_with_annotation() |> Graph.get(statement()) == annotation()
|
||||
end
|
||||
|
||||
test "get_and_update/3" do
|
||||
assert Graph.get_and_update(graph_with_annotation(), statement(), fn description ->
|
||||
{description, object_annotation()}
|
||||
end) ==
|
||||
{annotation(), Graph.new(init: {statement(), EX.ap(), statement()})}
|
||||
end
|
||||
|
||||
test "pop/2" do
|
||||
assert Graph.pop(graph_with_annotation(), statement()) == {annotation(), graph()}
|
||||
end
|
||||
|
||||
test "subject_count/1" do
|
||||
assert Graph.subject_count(graph_with_annotations()) == 2
|
||||
end
|
||||
|
||||
test "subjects/1" do
|
||||
assert Graph.subjects(graph_with_annotations()) == MapSet.new([statement(), RDF.iri(EX.As)])
|
||||
end
|
||||
|
||||
test "objects/1" do
|
||||
assert Graph.objects(graph_with_annotations()) == MapSet.new([statement(), EX.ao()])
|
||||
end
|
||||
|
||||
describe "statements/1" do
|
||||
test "without the filter_star flag" do
|
||||
assert Graph.statements(graph_with_annotations()) == [
|
||||
star_statement(),
|
||||
{RDF.iri(EX.As), EX.ap(), statement()}
|
||||
]
|
||||
end
|
||||
|
||||
test "with the filter_star flag" do
|
||||
assert Graph.statements(graph_with_annotations(), filter_star: true) == []
|
||||
assert Graph.statements(graph_with_annotations(), filter_star: true) == []
|
||||
|
||||
assert Graph.new(
|
||||
init: [
|
||||
{statement(), EX.ap(), EX.ao()},
|
||||
{statement(), EX.ap(), statement()},
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{EX.s(), EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Graph.statements(filter_star: true) == [{EX.s(), EX.p(), EX.o()}]
|
||||
end
|
||||
end
|
||||
|
||||
test "include?/3" do
|
||||
assert Graph.include?(graph_with_annotations(), star_statement())
|
||||
assert Graph.include?(graph_with_annotations(), {EX.As, EX.ap(), statement()})
|
||||
end
|
||||
|
||||
test "describes?/2" do
|
||||
assert Graph.describes?(graph_with_annotations(), statement())
|
||||
end
|
||||
|
||||
test "values/2" do
|
||||
assert graph_with_annotations() |> Graph.values() == %{}
|
||||
|
||||
assert Graph.new(
|
||||
init: [
|
||||
annotation(),
|
||||
{EX.s(), EX.p(), ~L"Foo"},
|
||||
{EX.s(), EX.ap(), statement()}
|
||||
]
|
||||
)
|
||||
|> Graph.values() ==
|
||||
%{RDF.Term.value(EX.s()) => %{RDF.Term.value(EX.p()) => ["Foo"]}}
|
||||
end
|
||||
|
||||
test "map/2" do
|
||||
mapping = fn
|
||||
{:predicate, predicate} ->
|
||||
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
|
||||
|
||||
{_, term} ->
|
||||
RDF.Term.value(term)
|
||||
end
|
||||
|
||||
assert graph_with_annotations() |> Graph.map(mapping) == %{}
|
||||
|
||||
assert Graph.new([
|
||||
annotation(),
|
||||
{EX.s1(), EX.p(), EX.o1()},
|
||||
{EX.s2(), EX.p(), EX.o2()},
|
||||
object_annotation()
|
||||
])
|
||||
|> Graph.map(mapping) ==
|
||||
%{
|
||||
RDF.Term.value(EX.s1()) => %{p: [RDF.Term.value(EX.o1())]},
|
||||
RDF.Term.value(EX.s2()) => %{p: [RDF.Term.value(EX.o2())]}
|
||||
}
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue