Add RDF.Graph.delete_annotations/3 and delete_annotations opts

This commit is contained in:
Marcel Otto 2021-10-27 13:11:53 +02:00
parent 538663ddb5
commit 8469be877d
2 changed files with 250 additions and 9 deletions

View file

@ -182,6 +182,10 @@ defmodule RDF.Graph do
Also when the statements to be added are given as another `RDF.Graph`, the Also when the statements to be added are given as another `RDF.Graph`, the
prefixes of this graph will be added. In case of conflicting prefix mappings prefixes of this graph will be added. In case of conflicting prefix mappings
the original prefix from `graph` will be kept. the original prefix from `graph` will be kept.
RDF* annotations to be added to all of given statements can be specified with
the `:annotate` keyword option and predicate-objects pairs as a tuple, list of
tuples or a map.
""" """
@spec add(t, input, keyword) :: t @spec add(t, input, keyword) :: t
def add(graph, input, opts \\ []) def add(graph, input, opts \\ [])
@ -272,6 +276,11 @@ defmodule RDF.Graph do
of this graph will be added. In case of conflicting prefix mappings the of this graph will be added. In case of conflicting prefix mappings the
original prefix from `graph` will be kept. original prefix from `graph` will be kept.
RDF* annotations to be added to all of given statements can be specified with
the `:annotate` keyword option and predicate-objects pairs as a tuple, list of
tuples or a map. As with the actual asserted statements, the annotation will
overwrite existing annotations.
## Examples ## Examples
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}])
@ -322,6 +331,11 @@ defmodule RDF.Graph do
of this graph will be added. In case of conflicting prefix mappings the of this graph will be added. In case of conflicting prefix mappings the
original prefix from `graph` will be kept. original prefix from `graph` will be kept.
RDF* annotations to be added to all of given statements can be specified with
the `:annotate` keyword option and predicate-objects pairs as a tuple, list of
tuples or a map. All exiting annotations of the asserted statements will be
overwritten.
## Examples ## Examples
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}])
@ -365,11 +379,15 @@ defmodule RDF.Graph do
@doc """ @doc """
Deletes statements from a `RDF.Graph`. Deletes statements from a `RDF.Graph`.
Note: When the statements to be deleted are given as another `RDF.Graph`, When the statements to be deleted are given as another `RDF.Graph`,
the graph name must not match graph name of the graph from which the statements the graph name must not match graph name of the graph from which the statements
are deleted. If you want to delete only graphs with matching names, you can are deleted. If you want to delete only statements with matching graph names, you can
use `RDF.Data.delete/2`. use `RDF.Data.delete/2`.
The optional `:delete_annotations` keyword option allows to set which of
annotations of the deleted statements should be deleted also.
Any of the possible values of `delete_annotations/3` can be provided here.
By default no annotations of the deleted statements will be removed.
""" """
@spec delete(t, input, keyword) :: t @spec delete(t, input, keyword) :: t
def delete(graph, input, opts \\ []) def delete(graph, input, opts \\ [])
@ -421,28 +439,82 @@ defmodule RDF.Graph do
else else
graph graph
end end
|> do_delete_delete_annotations(subject, input, opts)
end
defp do_delete_delete_annotations(graph, subject, statements, opts) do
if delete_annotations = Keyword.get(opts, :delete_annotations, false) do
delete_annotations(graph, Description.new(subject, init: statements), delete_annotations)
else
graph
end
end
@doc """
Deletes RDF-star annotations of a given set of statements.
The `statements` can be given in any input form (see `add/3`).
If `true` is given as the third argument or is `delete_annotations/2` is used,
all annotations of the given `statements` are deleted.
If a single predicate or list of predicates is given only statements with
these predicates from the annotations of the given `statements` are deleted.
"""
@spec delete_annotations(
t,
input,
boolean | Statement.coercible_predicate() | [Statement.coercible_predicate()]
) :: t
def delete_annotations(graph, statements, delete \\ true)
def delete_annotations(graph, _, false), do: graph
def delete_annotations(graph, statements, true) do
delete_descriptions(graph, statements |> new() |> triples())
end
def delete_annotations(graph, statements, predicates) do
statements
|> new()
|> Enum.reduce(graph, fn triple, graph ->
update(graph, triple, &Description.delete_predicates(&1, predicates))
end)
end end
@doc """ @doc """
Deletes all statements with the given `subjects`. Deletes all statements with the given `subjects`.
If `subjects` contains subjects that are not in `graph`, they're simply ignored. If `subjects` contains subjects that are not in `graph`, they're simply ignored.
The optional `:delete_annotations` keyword option allows to set which of
annotations of the deleted statements should be deleted also.
Any of the possible values of `delete_annotations/3` can be provided here.
By default no annotations of the deleted statements will be removed.
""" """
@spec delete_descriptions( @spec delete_descriptions(
t, t,
Statement.coercible_subject() | [Statement.coercible_subject()] Statement.coercible_subject() | [Statement.coercible_subject()],
keyword
) :: t ) :: t
def delete_descriptions(graph, subjects) def delete_descriptions(graph, subjects, opts \\ [])
def delete_descriptions(%__MODULE__{} = graph, subjects) when is_list(subjects) do def delete_descriptions(%__MODULE__{} = graph, subjects, opts) when is_list(subjects) do
Enum.reduce(subjects, graph, &delete_descriptions(&2, &1)) Enum.reduce(subjects, graph, &delete_descriptions(&2, &1, opts))
end end
def delete_descriptions(%__MODULE__{} = graph, subject) do def delete_descriptions(%__MODULE__{} = graph, subject, opts) do
%__MODULE__{graph | descriptions: Map.delete(graph.descriptions, RDF.coerce_subject(subject))} case Map.pop(graph.descriptions, RDF.coerce_subject(subject)) do
{nil, _} ->
graph
{deleted_description, descriptions} ->
%__MODULE__{graph | descriptions: descriptions}
|> delete_annotations(deleted_description, Keyword.get(opts, :delete_annotations, false))
end
end end
defdelegate delete_subjects(graph, subjects), to: __MODULE__, as: :delete_descriptions defdelegate delete_subjects(graph, subjects), to: __MODULE__, as: :delete_descriptions
defdelegate delete_subjects(graph, subjects, opts), to: __MODULE__, as: :delete_descriptions
@doc """ @doc """
Updates the description of the `subject` in `graph` with the given function. Updates the description of the `subject` in `graph` with the given function.

View file

@ -473,14 +473,183 @@ defmodule RDF.Star.Graph.Test do
end end
end end
describe "delete_annotations/3" do
test "with false, no annotations are deleted" do
graph = Graph.add(graph(), statement(), annotate: {EX.p(), EX.O})
assert Graph.delete_annotations(graph, statement(), false) == graph
end
test "with true, all annotations are deleted (default)" do
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete_annotations(statement(), true) ==
graph() |> Graph.add(statement())
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete_annotations(statement()) ==
graph() |> Graph.add(statement())
end
test "with a single predicate" do
assert graph()
|> Graph.add(statement(), annotate: [{EX.p1(), EX.O1}, {EX.p2(), EX.O2}])
|> Graph.delete_annotations(statement(), EX.p2()) ==
Graph.add(graph(), statement(), annotate: {EX.p1(), EX.O1})
graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP, EX.AO})
|> Graph.add({{EX.S3, EX.P3, EX.O3}, EX.AP, EX.AO})
assert Graph.delete_annotations(
graph,
[{EX.S1, EX.P1, EX.O1}, {EX.S3, EX.P3, EX.O3}],
EX.AP
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
end
test "with a list of predicates" do
graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, [EX.AO1, EX.AO2]})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
|> Graph.add({{EX.S3, EX.P3, EX.O3}, EX.AP1, EX.AO})
assert Graph.delete_annotations(
graph,
[{EX.S1, EX.P1, EX.O1}, {EX.S3, EX.P3, EX.O3}],
[EX.AP1, EX.AP2]
) ==
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
end
end
test "delete/3" do test "delete/3" do
assert graph_with_annotation() |> Graph.delete(star_statement()) == graph() assert graph_with_annotation() |> Graph.delete(star_statement()) == graph()
end end
test "delete_description/3" do describe "delete_annotations option on delete/3" do
test "with false, no annotations are deleted (default)" do
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete(statement(), delete_annotations: false) ==
graph() |> Graph.add({statement(), EX.p(), EX.O})
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete(statement()) ==
graph() |> Graph.add({statement(), EX.p(), EX.O})
end
test "with true, all annotations are deleted" do
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete(statement(), delete_annotations: true) ==
graph()
end
test "annotations are even deleted, when the statements to be deleted are not present" do
assert graph_with_annotation() |> Graph.delete(statement(), delete_annotations: true) ==
graph()
end
test "with predicates" do
graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, [EX.AO1, EX.AO2]})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
|> Graph.add({{EX.S3, EX.P3, EX.O3}, EX.AP1, EX.AO})
assert Graph.delete(
graph,
[{EX.S1, EX.P1, EX.O1}, {EX.S3, EX.P3, EX.O3}],
delete_annotations: [EX.AP1, EX.AP2]
) ==
graph()
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
graph =
graph()
|> Graph.add(%{EX.S1 => %{EX.P1 => EX.O1, EX.P2 => EX.O2}},
annotate: %{EX.AP1 => EX.AO1}
)
assert Graph.delete(graph, {EX.S1, %{EX.P1 => EX.O1}}, delete_annotations: [EX.AP1, EX.AP2]) ==
graph()
|> Graph.add({EX.S1, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P2, EX.O2}, EX.AP1, EX.AO1})
end
end
test "delete_descriptions/3" do
assert graph_with_annotation() |> Graph.delete_descriptions(statement()) == graph() assert graph_with_annotation() |> Graph.delete_descriptions(statement()) == graph()
end end
describe "delete_annotations option on delete_descriptions/3" do
test "with false, no annotations are deleted (default)" do
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete_descriptions(EX.S, delete_annotations: false) ==
graph() |> Graph.add({statement(), EX.p(), EX.O})
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete_descriptions(EX.S) ==
graph() |> Graph.add({statement(), EX.p(), EX.O})
end
test "with true, all annotations are deleted" do
assert graph()
|> Graph.add(statement(), annotate: {EX.p(), EX.O})
|> Graph.delete_descriptions(EX.S, delete_annotations: true) ==
graph()
end
test "with predicates" do
graph =
graph()
|> Graph.add({EX.S1, EX.P1, EX.O1})
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({EX.S3, EX.P3, EX.O3})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP1, [EX.AO1, EX.AO2]})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP2, EX.AO})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
|> Graph.add({{EX.S3, EX.P3, EX.O3}, EX.AP1, EX.AO})
assert Graph.delete_descriptions(graph, [EX.S1, EX.S3], delete_annotations: [EX.AP1, EX.AP2]) ==
graph()
|> Graph.add({EX.S2, EX.P2, EX.O2})
|> Graph.add({{EX.S1, EX.P1, EX.O1}, EX.AP3, EX.AO})
|> Graph.add({{EX.S2, EX.P3, EX.O3}, EX.AP1, EX.AO})
end
end
test "update/3" do test "update/3" do
assert Graph.update(graph(), statement(), annotation(), fn _ -> raise "unexpected" end) == assert Graph.update(graph(), statement(), annotation(), fn _ -> raise "unexpected" end) ==
graph_with_annotation() graph_with_annotation()