Add annotate option on RDF.Graph.add/3, put/3 and put_properties/3

This commit is contained in:
Marcel Otto 2021-10-25 21:54:32 +02:00
parent e9102252ae
commit 36a51cc7a2
3 changed files with 389 additions and 4 deletions

View file

@ -18,7 +18,7 @@ defmodule RDF.Graph do
alias RDF.{Description, IRI, PrefixMap, PropertyMap}
alias RDF.Star.Statement
import RDF.Utils
import RDF.{Sigils, Utils}
@type graph_description :: %{Statement.subject() => Description.t()}
@ -202,6 +202,14 @@ defmodule RDF.Graph do
end
def add(graph, %__MODULE__{descriptions: descriptions, prefixes: prefixes}, opts) do
# normalize the annotations here, so we don't have to do this repeatedly in do_add/4
opts =
if annotation = Keyword.get(opts, :annotate) do
Keyword.put(opts, :annotate, normalize_annotation(annotation))
else
opts
end
graph =
Enum.reduce(descriptions, graph, fn {_, description}, graph ->
add(graph, description, opts)
@ -239,8 +247,22 @@ defmodule RDF.Graph do
fn description -> Description.add(description, statements, opts) end
)
}
|> add_annotations(subject, statements, opts)
end
defp add_annotations(graph, subject, statements, opts) do
if annotation = Keyword.get(opts, :annotate) |> normalize_annotation() do
Description.new(subject, init: statements)
|> Enum.reduce(graph, &add(&2, Description.change_subject(annotation, &1)))
else
graph
end
end
defp normalize_annotation(nil), do: nil
defp normalize_annotation(%Description{} = annotation), do: annotation
defp normalize_annotation(annotation), do: Description.new(~B<placeholder>, init: annotation)
@doc """
Adds statements to a `RDF.Graph` overwriting existing statements with the subjects given in the `input` data.
@ -258,7 +280,7 @@ defmodule RDF.Graph do
@spec put(t, input, keyword) :: t
def put(graph, input, opts \\ [])
def put(%__MODULE__{} = graph, %__MODULE__{} = input, _opts) do
def put(%__MODULE__{} = graph, %__MODULE__{} = input, opts) do
graph = %__MODULE__{
graph
| descriptions:
@ -276,10 +298,19 @@ defmodule RDF.Graph do
else
graph
end
|> put_annotations(input, opts)
end
def put(%__MODULE__{} = graph, input, opts) do
put(graph, new() |> add(input, opts), opts)
put(graph, new() |> add(input, Keyword.delete_first(opts, :annotate)), opts)
end
defp put_annotations(graph, input, opts) do
if annotation = Keyword.get(opts, :annotate) |> normalize_annotation() do
Enum.reduce(input, graph, &put(&2, Description.change_subject(annotation, &1)))
else
graph
end
end
@doc """
@ -322,10 +353,11 @@ defmodule RDF.Graph do
else
graph
end
|> put_annotations(input, opts)
end
def put_properties(%__MODULE__{} = graph, input, opts) do
put_properties(graph, new() |> add(input, opts), opts)
put_properties(graph, new() |> add(input, Keyword.delete_first(opts, :annotate)), opts)
end
@doc """

View file

@ -213,6 +213,19 @@ defmodule RDF.GraphTest do
assert graph_includes_statement?(g, {EX.S, EX.p(), EX.O})
end
test "a graph map" do
g =
Graph.new(%{
EX.Subject1 => [{EX.predicate1(), EX.Object1}, {EX.predicate2(), [EX.Object2]}],
EX.Subject3 => %{EX.predicate3() => EX.Object3}
})
assert Graph.triple_count(g) == 3
assert graph_includes_statement?(g, {EX.Subject1, EX.predicate1(), EX.Object1})
assert graph_includes_statement?(g, {EX.Subject1, EX.predicate2(), EX.Object2})
assert graph_includes_statement?(g, {EX.Subject3, EX.predicate3(), EX.Object3})
end
test "with an initializer function" do
g = Graph.new(init: fn -> {EX.S, EX.p(), EX.O} end)
assert unnamed_graph?(g)

View file

@ -62,6 +62,69 @@ defmodule RDF.Star.Graph.Test do
end
end
describe "annotate option on add/3" do
test "with a predicate-object pair" do
assert Graph.add(graph(), statement(), annotate: {EX.AP, EX.AO}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO})
assert Graph.add(graph(), [{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}],
annotate: {EX.AP, EX.AO}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S2, EX.P2, EX.O2}, EX.AP, EX.AO})
expected_graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
assert Graph.add(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert Graph.add(graph(), %{EX.S1 => %{EX.P1 => [EX.O1, EX.O2]}}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert Graph.add(graph(), Description.new(EX.S1, init: %{EX.P1 => [EX.O1, EX.O2]}),
annotate: {EX.AP, EX.AO}
) ==
expected_graph
end
test "with multiple annotations" do
assert Graph.add(graph(), statement(), annotate: [{EX.AP1, EX.AO1}, {EX.AP2, EX.AO2}]) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
end
test "with a description graph" do
assert Graph.add(graph(), statement(), annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
assert Graph.add(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]},
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO2})
end
end
describe "put/3" do
test "with a proper triple as a subject" do
graph =
@ -115,6 +178,131 @@ defmodule RDF.Star.Graph.Test do
end
end
describe "annotate option on put/3" do
test "with a predicate-object pair" do
assert Graph.put(graph(), statement(), annotate: {EX.AP, EX.AO}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO})
assert Graph.put(graph(), [{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}],
annotate: {EX.AP, EX.AO}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S2, EX.P2, EX.O2}, EX.AP, EX.AO})
expected_graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
assert Graph.put(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert Graph.put(graph(), %{EX.S1 => %{EX.P1 => [EX.O1, EX.O2]}}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert Graph.put(graph(), Description.new(EX.S1, init: %{EX.P1 => [EX.O1, EX.O2]}),
annotate: {EX.AP, EX.AO}
) ==
expected_graph
end
test "with multiple annotations" do
assert Graph.put(graph(), statement(), annotate: [{EX.AP1, EX.AO1}, {EX.AP2, EX.AO2}]) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
end
test "with a description graph" do
assert Graph.put(graph(), statement(), annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
assert Graph.put(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]},
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO2})
end
test "with a RDF.Graph" do
assert Graph.put(graph(), Graph.new({EX.S1, EX.P1, [EX.O1, EX.O2]}),
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO2})
end
test "when an annotation exists" do
assert graph()
|> Graph.add(statement(), annotate: {EX.AP1, EX.AO1})
|> Graph.put(statement(), annotate: {EX.AP, EX.AO}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO})
base_graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
expected_graph =
base_graph
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP, EX.AO1})
|> Graph.put({EX.S1, EX.P1, [EX.O1, EX.O2]}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP1, EX.AO})
|> Graph.put(%{EX.S1 => %{EX.P1 => [EX.O1, EX.O2]}}, annotate: {EX.AP, EX.AO}) ==
expected_graph
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP1, EX.AO1})
|> Graph.put(Description.new(EX.S1, init: %{EX.P1 => [EX.O1, EX.O2]}),
annotate: {EX.AP, EX.AO}
) ==
expected_graph
assert graph()
|> Graph.add(base_graph, annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2})
|> Graph.put({EX.S1, EX.P1, [EX.O1, EX.O2]},
annotate: %{EX.AP3 => EX.AO3, EX.AP2 => EX.AO4}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO3})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP3, EX.AO3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO4})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO4})
end
end
test "put_properties/3" do
graph =
graph()
@ -133,6 +321,158 @@ defmodule RDF.Star.Graph.Test do
assert graph_includes_statement?(graph, {statement(), EX.ap(), EX.ao2()})
end
describe "annotate option on put_properties/3" do
test "with a predicate-object pair" do
assert Graph.put_properties(graph(), statement(), annotate: {EX.AP, EX.AO}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO})
assert Graph.put_properties(graph(), [{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}],
annotate: {EX.AP, EX.AO}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S2, EX.P2, EX.O2}, EX.AP, EX.AO})
expected_graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
assert Graph.put_properties(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]},
annotate: {EX.AP, EX.AO}
) ==
expected_graph
assert Graph.put_properties(graph(), %{EX.S1 => %{EX.P1 => [EX.O1, EX.O2]}},
annotate: {EX.AP, EX.AO}
) ==
expected_graph
assert Graph.put_properties(
graph(),
Description.new(EX.S1, init: %{EX.P1 => [EX.O1, EX.O2]}),
annotate: {EX.AP, EX.AO}
) ==
expected_graph
end
test "with multiple annotations" do
assert Graph.put_properties(graph(), statement(),
annotate: [{EX.AP1, EX.AO1}, {EX.AP2, EX.AO2}]
) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
end
test "with a description graph" do
assert Graph.put_properties(graph(), statement(),
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP1, EX.AO1})
|> Graph.add({statement(), EX.AP2, EX.AO2})
assert Graph.put_properties(graph(), {EX.S1, EX.P1, [EX.O1, EX.O2]},
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO2})
end
test "with a RDF.Graph" do
assert Graph.put_properties(graph(), Graph.new({EX.S1, EX.P1, [EX.O1, EX.O2]}),
annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO2})
end
test "when an annotation exists" do
assert graph()
|> Graph.add(statement(), annotate: {EX.AP1, EX.AO1})
|> Graph.put_properties(statement(), annotate: {EX.AP, EX.AO}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO})
assert graph()
|> Graph.add(statement(), annotate: {EX.AP, EX.AO1})
|> Graph.put_properties(statement(), annotate: {EX.AP, EX.AO2}) ==
graph()
|> Graph.add(statement())
|> Graph.add({statement(), EX.AP, EX.AO2})
base_graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S1, EX.P2, EX.O2})
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP, EX.AO1})
|> Graph.put_properties({EX.S1, EX.P1, EX.O1}, annotate: {EX.AP, EX.AO})
|> Graph.put_properties({EX.S1, EX.P2, EX.O2}, annotate: {EX.AP, EX.AO}) ==
base_graph
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP, EX.AO})
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP1, EX.AO})
|> Graph.put_properties(%{EX.S1 => %{EX.P1 => [EX.O1, EX.O2]}},
annotate: {EX.AP, EX.AO}
) ==
base_graph
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP1, EX.AO})
assert graph()
|> Graph.add(base_graph, annotate: {EX.AP1, EX.AO1})
|> Graph.put_properties(Description.new(EX.S1, init: %{EX.P1 => [EX.O1, EX.O2]}),
annotate: {EX.AP, EX.AO}
) ==
base_graph
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP, EX.AO})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP1, EX.AO1})
assert graph()
|> Graph.add(base_graph, annotate: %{EX.AP1 => EX.AO1, EX.AP2 => EX.AO2})
|> Graph.put_properties({EX.S1, EX.P1, EX.O2},
annotate: %{EX.AP3 => EX.AO3, EX.AP2 => EX.AO4}
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O2})
|> Graph.add({EX.S1, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP3, EX.AO3})
|> Graph.add({{EX.S1, EX.P1, EX.O2}, EX.AP2, EX.AO4})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP2, EX.AO2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, EX.AO1})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO2})
end
end
test "delete/3" do
assert graph_with_annotation() |> Graph.delete(star_statement()) == graph()
end