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
prefixes of this graph will be added. In case of conflicting prefix mappings
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
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
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
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
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
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 """
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
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`.
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
def delete(graph, input, opts \\ [])
@ -421,28 +439,82 @@ defmodule RDF.Graph do
else
graph
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
@doc """
Deletes all statements with the given `subjects`.
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(
t,
Statement.coercible_subject() | [Statement.coercible_subject()]
Statement.coercible_subject() | [Statement.coercible_subject()],
keyword
) :: t
def delete_descriptions(graph, subjects)
def delete_descriptions(graph, subjects, opts \\ [])
def delete_descriptions(%__MODULE__{} = graph, subjects) when is_list(subjects) do
Enum.reduce(subjects, graph, &delete_descriptions(&2, &1))
def delete_descriptions(%__MODULE__{} = graph, subjects, opts) when is_list(subjects) do
Enum.reduce(subjects, graph, &delete_descriptions(&2, &1, opts))
end
def delete_descriptions(%__MODULE__{} = graph, subject) do
%__MODULE__{graph | descriptions: Map.delete(graph.descriptions, RDF.coerce_subject(subject))}
def delete_descriptions(%__MODULE__{} = graph, subject, opts) do
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
defdelegate delete_subjects(graph, subjects), to: __MODULE__, as: :delete_descriptions
defdelegate delete_subjects(graph, subjects, opts), to: __MODULE__, as: :delete_descriptions
@doc """
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
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
assert graph_with_annotation() |> Graph.delete(star_statement()) == graph()
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()
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
assert Graph.update(graph(), statement(), annotation(), fn _ -> raise "unexpected" end) ==
graph_with_annotation()