Add prefix management to RDF.Graph
This commit is contained in:
parent
8bffff7c76
commit
195b967b93
3 changed files with 214 additions and 27 deletions
|
@ -10,6 +10,9 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
|
|||
### Added
|
||||
|
||||
- `RDF.PrefixMap`
|
||||
- prefix management of `RDF.Graph`s:
|
||||
- the structure now has `prefixes` field with an optional `RDF.PrefixMap`
|
||||
- new functions `add_prefixes/2`, `delete_prefixes/2` and `clear_prefixes/1`
|
||||
- configurable RDF.default_prefixes
|
||||
|
||||
|
||||
|
@ -18,6 +21,11 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
|
|||
- the constructor functions for `RDF.Graph`s and `RDF.Dataset`s now take the
|
||||
graph name resp. dataset name through a `name` option, instead of the first
|
||||
argument
|
||||
- `RDF.Graph.new` supports an additional `prefixes` argument to initialize the
|
||||
`prefixes` field
|
||||
- when `RDF.Graph.add` and `RDF.Graph.put` are called with another graph, its
|
||||
prefixes are merged
|
||||
|
||||
|
||||
|
||||
[Compare v0.5.4...HEAD](https://github.com/marcelotto/rdf-ex/compare/v0.5.4...HEAD)
|
||||
|
|
126
lib/rdf/graph.ex
126
lib/rdf/graph.ex
|
@ -11,7 +11,7 @@ defmodule RDF.Graph do
|
|||
|
||||
"""
|
||||
|
||||
defstruct name: nil, descriptions: %{}
|
||||
defstruct name: nil, descriptions: %{}, prefixes: nil
|
||||
|
||||
@behaviour Access
|
||||
|
||||
|
@ -66,6 +66,8 @@ defmodule RDF.Graph do
|
|||
Available options:
|
||||
|
||||
- `name`: the name of the graph to be created
|
||||
- `prefixes`: some prefix mappings which should be stored alongside the graph
|
||||
and will be used for example when serializing in a format with prefix support
|
||||
|
||||
## Examples
|
||||
|
||||
|
@ -81,6 +83,7 @@ defmodule RDF.Graph do
|
|||
|
||||
def new(%RDF.Graph{} = graph, options) do
|
||||
%RDF.Graph{graph | name: options |> Keyword.get(:name) |> coerce_graph_name()}
|
||||
|> add_prefixes(Keyword.get(options, :prefixes))
|
||||
end
|
||||
|
||||
def new(data, options) do
|
||||
|
@ -107,11 +110,14 @@ defmodule RDF.Graph do
|
|||
@doc """
|
||||
Adds triples to a `RDF.Graph`.
|
||||
|
||||
Note: When the statements to be added are given as another `RDF.Graph`,
|
||||
When the statements to be added are given as another `RDF.Graph`,
|
||||
the graph name must not match graph name of the graph to which the statements
|
||||
are added. As opposed to that `RDF.Data.merge/2` will produce a `RDF.Dataset`
|
||||
containing both graphs.
|
||||
|
||||
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.
|
||||
"""
|
||||
def add(graph, triples)
|
||||
|
||||
|
@ -130,15 +136,21 @@ defmodule RDF.Graph do
|
|||
def add(%RDF.Graph{} = graph, %Description{subject: subject} = description),
|
||||
do: do_add(graph, subject, description)
|
||||
|
||||
def add(graph, %RDF.Graph{descriptions: descriptions}) do
|
||||
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
|
||||
add(graph, description)
|
||||
def add(graph, %RDF.Graph{descriptions: descriptions, prefixes: prefixes}) do
|
||||
graph =
|
||||
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
|
||||
add(graph, description)
|
||||
end
|
||||
|
||||
if prefixes do
|
||||
add_prefixes(graph, prefixes, fn _, ns, _ -> ns end)
|
||||
else
|
||||
graph
|
||||
end
|
||||
end
|
||||
|
||||
defp do_add(%RDF.Graph{name: name, descriptions: descriptions},
|
||||
subject, statements) do
|
||||
%RDF.Graph{name: name,
|
||||
defp do_add(%RDF.Graph{descriptions: descriptions} = graph, subject, statements) do
|
||||
%RDF.Graph{graph |
|
||||
descriptions:
|
||||
Map.update(descriptions, subject, Description.new(statements),
|
||||
fn description ->
|
||||
|
@ -151,6 +163,10 @@ defmodule RDF.Graph do
|
|||
@doc """
|
||||
Adds statements to a `RDF.Graph` and overwrites all existing statements with the same subjects and predicates.
|
||||
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}]) |>
|
||||
|
@ -169,9 +185,16 @@ defmodule RDF.Graph do
|
|||
def put(%RDF.Graph{} = graph, %Description{subject: subject} = description),
|
||||
do: do_put(graph, subject, description)
|
||||
|
||||
def put(graph, %RDF.Graph{descriptions: descriptions}) do
|
||||
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
|
||||
put(graph, description)
|
||||
def put(graph, %RDF.Graph{descriptions: descriptions, prefixes: prefixes}) do
|
||||
graph =
|
||||
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
|
||||
put(graph, description)
|
||||
end
|
||||
|
||||
if prefixes do
|
||||
add_prefixes(graph, prefixes, fn _, ns, _ -> ns end)
|
||||
else
|
||||
graph
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -190,11 +213,11 @@ defmodule RDF.Graph do
|
|||
"""
|
||||
def put(graph, subject, predications)
|
||||
|
||||
def put(%RDF.Graph{name: name, descriptions: descriptions}, subject, predications)
|
||||
def put(%RDF.Graph{descriptions: descriptions} = graph, subject, predications)
|
||||
when is_list(predications) do
|
||||
with subject = coerce_subject(subject) do
|
||||
# TODO: Can we reduce this case also to do_put somehow? Only the initializer of Map.update differs ...
|
||||
%RDF.Graph{name: name,
|
||||
%RDF.Graph{graph |
|
||||
descriptions:
|
||||
Map.update(descriptions, subject, Description.new(subject, predications),
|
||||
fn current ->
|
||||
|
@ -207,9 +230,8 @@ defmodule RDF.Graph do
|
|||
def put(graph, subject, {_predicate, _objects} = predications),
|
||||
do: put(graph, subject, [predications])
|
||||
|
||||
defp do_put(%RDF.Graph{name: name, descriptions: descriptions},
|
||||
subject, statements) do
|
||||
%RDF.Graph{name: name,
|
||||
defp do_put(%RDF.Graph{descriptions: descriptions} = graph, subject, statements) do
|
||||
%RDF.Graph{graph |
|
||||
descriptions:
|
||||
Map.update(descriptions, subject, Description.new(statements),
|
||||
fn current ->
|
||||
|
@ -271,12 +293,12 @@ defmodule RDF.Graph do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_delete(%RDF.Graph{name: name, descriptions: descriptions} = graph,
|
||||
defp do_delete(%RDF.Graph{descriptions: descriptions} = graph,
|
||||
subject, statements) do
|
||||
with description when not is_nil(description) <- descriptions[subject],
|
||||
new_description = Description.delete(description, statements)
|
||||
do
|
||||
%RDF.Graph{name: name,
|
||||
%RDF.Graph{graph |
|
||||
descriptions:
|
||||
if Enum.empty?(new_description) do
|
||||
Map.delete(descriptions, subject)
|
||||
|
@ -301,9 +323,9 @@ defmodule RDF.Graph do
|
|||
end
|
||||
end
|
||||
|
||||
def delete_subjects(%RDF.Graph{name: name, descriptions: descriptions}, subject) do
|
||||
def delete_subjects(%RDF.Graph{descriptions: descriptions} = graph, subject) do
|
||||
with subject = coerce_subject(subject) do
|
||||
%RDF.Graph{name: name, descriptions: Map.delete(descriptions, subject)}
|
||||
%RDF.Graph{graph | descriptions: Map.delete(descriptions, subject)}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -409,7 +431,7 @@ defmodule RDF.Graph do
|
|||
def pop(%RDF.Graph{descriptions: descriptions} = graph)
|
||||
when descriptions == %{}, do: {nil, graph}
|
||||
|
||||
def pop(%RDF.Graph{name: name, descriptions: descriptions}) do
|
||||
def pop(%RDF.Graph{descriptions: descriptions} = graph) do
|
||||
# TODO: Find a faster way ...
|
||||
[{subject, description}] = Enum.take(descriptions, 1)
|
||||
{triple, popped_description} = Description.pop(description)
|
||||
|
@ -417,7 +439,7 @@ defmodule RDF.Graph do
|
|||
do: descriptions |> Map.delete(subject),
|
||||
else: descriptions |> Map.put(subject, popped_description)
|
||||
|
||||
{triple, %RDF.Graph{name: name, descriptions: popped}}
|
||||
{triple, %RDF.Graph{graph | descriptions: popped}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -435,12 +457,12 @@ defmodule RDF.Graph do
|
|||
|
||||
"""
|
||||
@impl Access
|
||||
def pop(%RDF.Graph{name: name, descriptions: descriptions} = graph, subject) do
|
||||
def pop(%RDF.Graph{descriptions: descriptions} = graph, subject) do
|
||||
case Access.pop(descriptions, coerce_subject(subject)) do
|
||||
{nil, _} ->
|
||||
{nil, graph}
|
||||
{description, new_descriptions} ->
|
||||
{description, %RDF.Graph{name: name, descriptions: new_descriptions}}
|
||||
{description, %RDF.Graph{graph | descriptions: new_descriptions}}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -655,9 +677,59 @@ defmodule RDF.Graph do
|
|||
def values(graph, mapping \\ &RDF.Statement.default_term_mapping/1)
|
||||
|
||||
def values(%RDF.Graph{descriptions: descriptions}, mapping) do
|
||||
Map.new descriptions, fn {subject, description} ->
|
||||
{mapping.({:subject, subject}), Description.values(description, mapping)}
|
||||
end
|
||||
Map.new descriptions, fn {subject, description} ->
|
||||
{mapping.({:subject, subject}), Description.values(description, mapping)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Adds `prefixes` to the given `graph`.
|
||||
|
||||
The `prefixes` mappings can be given as any structure convertible to a
|
||||
`RDF.PrefixMap`.
|
||||
|
||||
When a prefix with another mapping already exists it will be overwritten with
|
||||
the new one. This behaviour can be customized by providing a `conflict_resolver`
|
||||
function. See `RDF.PrefixMap.merge/3` for more on that.
|
||||
"""
|
||||
def add_prefixes(graph, prefixes, conflict_resolver \\ nil)
|
||||
|
||||
def add_prefixes(%RDF.Graph{} = graph, nil, _), do: graph
|
||||
|
||||
def add_prefixes(%RDF.Graph{prefixes: nil} = graph, prefixes, _) do
|
||||
%RDF.Graph{graph | prefixes: RDF.PrefixMap.new(prefixes)}
|
||||
end
|
||||
|
||||
def add_prefixes(%RDF.Graph{} = graph, additions, nil) do
|
||||
add_prefixes(%RDF.Graph{} = graph, additions, fn _, _, ns -> ns end)
|
||||
end
|
||||
|
||||
def add_prefixes(%RDF.Graph{prefixes: prefixes} = graph, additions, conflict_resolver) do
|
||||
%RDF.Graph{graph |
|
||||
prefixes: RDF.PrefixMap.merge!(prefixes, additions, conflict_resolver)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes `prefixes` from the given `graph`.
|
||||
|
||||
The `prefixes` can be a single prefix or a list of prefixes.
|
||||
Prefixes not in prefixes of the graph are simply ignored.
|
||||
"""
|
||||
def delete_prefixes(graph, prefixes)
|
||||
|
||||
def delete_prefixes(%RDF.Graph{prefixes: nil} = graph, _), do: graph
|
||||
|
||||
def delete_prefixes(%RDF.Graph{prefixes: prefixes} = graph, deletions) do
|
||||
%RDF.Graph{graph | prefixes: RDF.PrefixMap.drop(prefixes, List.wrap(deletions))}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clears all prefixes of the given `graph`.
|
||||
"""
|
||||
def clear_prefixes(%RDF.Graph{} = graph) do
|
||||
%RDF.Graph{graph | prefixes: nil}
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ defmodule RDF.GraphTest do
|
|||
|
||||
doctest RDF.Graph
|
||||
|
||||
alias RDF.PrefixMap
|
||||
alias RDF.NS.{XSD, RDFS}
|
||||
|
||||
describe "new" do
|
||||
test "creating an empty unnamed graph" do
|
||||
|
@ -107,6 +109,25 @@ defmodule RDF.GraphTest do
|
|||
assert unnamed_graph?(g)
|
||||
assert graph_includes_statement?(g, {EX.Subject, EX.predicate, EX.Object})
|
||||
end
|
||||
|
||||
test "with prefixes" do
|
||||
assert Graph.new(prefixes: %{ex: EX}) ==
|
||||
%Graph{prefixes: PrefixMap.new(ex: EX)}
|
||||
assert Graph.new(prefixes: %{ex: EX}, name: EX.graph_name) ==
|
||||
%Graph{prefixes: PrefixMap.new(ex: EX), name: EX.graph_name}
|
||||
|
||||
assert Graph.new({EX.Subject, EX.predicate, EX.Object}, prefixes: %{ex: EX}) ==
|
||||
%Graph{Graph.new({EX.Subject, EX.predicate, EX.Object}) | prefixes: PrefixMap.new(ex: EX)}
|
||||
end
|
||||
|
||||
test "creating a graph from another graph takes the prefixes from the other graph, but overwrites if necessary" do
|
||||
prefix_map = PrefixMap.new(ex: EX)
|
||||
g = Graph.new(Graph.new(prefixes: prefix_map))
|
||||
assert g.prefixes == prefix_map
|
||||
|
||||
g = Graph.new(Graph.new(prefixes: %{ex: XSD, rdfs: RDFS}), prefixes: prefix_map)
|
||||
assert g.prefixes == PrefixMap.new(ex: EX, rdfs: RDFS)
|
||||
end
|
||||
end
|
||||
|
||||
describe "add" do
|
||||
|
@ -194,6 +215,25 @@ defmodule RDF.GraphTest do
|
|||
assert graph_includes_statement?(g, {EX.Subject3, EX.predicate3, EX.Object3})
|
||||
end
|
||||
|
||||
test "merges the prefixes of another graph" do
|
||||
graph = Graph.new(prefixes: %{xsd: XSD})
|
||||
|> Graph.add(Graph.new(prefixes: %{rdfs: RDFS}))
|
||||
assert graph.prefixes == PrefixMap.new(xsd: XSD, rdfs: RDFS)
|
||||
end
|
||||
|
||||
test "merges the prefixes of another graph and keeps the original mapping in case of conflicts" do
|
||||
graph = Graph.new(prefixes: %{ex: EX})
|
||||
|> Graph.add(Graph.new(prefixes: %{ex: XSD}))
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
|
||||
test "preserves the name and prefixes on when the data provided is not a graph" do
|
||||
graph = Graph.new(name: EX.GraphName, prefixes: %{ex: EX})
|
||||
|> Graph.add(EX.Subject, EX.predicate, EX.Object)
|
||||
assert graph.name == RDF.iri(EX.GraphName)
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
|
||||
test "non-coercible Triple elements are causing an error" do
|
||||
assert_raise RDF.IRI.InvalidError, fn ->
|
||||
Graph.add(graph(), {"not a IRI", EX.predicate, iri(EX.Object)})
|
||||
|
@ -249,6 +289,25 @@ defmodule RDF.GraphTest do
|
|||
assert graph_includes_statement?(g, {EX.S2, EX.P2, bnode(:foo)})
|
||||
assert graph_includes_statement?(g, {EX.S3, EX.P3, EX.O3})
|
||||
end
|
||||
|
||||
test "merges the prefixes of another graph" do
|
||||
graph = Graph.new(prefixes: %{xsd: XSD})
|
||||
|> Graph.put(Graph.new(prefixes: %{rdfs: RDFS}))
|
||||
assert graph.prefixes == PrefixMap.new(xsd: XSD, rdfs: RDFS)
|
||||
end
|
||||
|
||||
test "merges the prefixes of another graph and keeps the original mapping in case of conflicts" do
|
||||
graph = Graph.new(prefixes: %{ex: EX})
|
||||
|> Graph.put(Graph.new(prefixes: %{ex: XSD}))
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
|
||||
test "preserves the name and prefixes" do
|
||||
graph = Graph.new(name: EX.GraphName, prefixes: %{ex: EX})
|
||||
|> Graph.put(EX.Subject, EX.predicate, EX.Object)
|
||||
assert graph.name == RDF.iri(EX.GraphName)
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -322,6 +381,12 @@ defmodule RDF.GraphTest do
|
|||
])) == Graph.new({EX.S3, EX.p3, ~L"bar"})
|
||||
end
|
||||
|
||||
test "preserves the name and prefixes" do
|
||||
graph = Graph.new(EX.Subject, EX.predicate, EX.Object, name: EX.GraphName, prefixes: %{ex: EX})
|
||||
|> Graph.delete(EX.Subject, EX.predicate, EX.Object)
|
||||
assert graph.name == RDF.iri(EX.GraphName)
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -397,6 +462,48 @@ defmodule RDF.GraphTest do
|
|||
}
|
||||
end
|
||||
|
||||
describe "add_prefixes/2" do
|
||||
test "when prefixes already exist" do
|
||||
graph = Graph.new(prefixes: %{xsd: XSD}) |> Graph.add_prefixes(ex: EX)
|
||||
assert graph.prefixes == PrefixMap.new(xsd: XSD, ex: EX)
|
||||
end
|
||||
|
||||
test "when prefixes are not defined yet" do
|
||||
graph = Graph.new() |> Graph.add_prefixes(ex: EX)
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
|
||||
test "when prefixes have conflicting mappings, the new mapping is used" do
|
||||
graph = Graph.new(prefixes: %{ex: EX}) |> Graph.add_prefixes(ex: XSD)
|
||||
assert graph.prefixes == PrefixMap.new(ex: XSD)
|
||||
end
|
||||
|
||||
test "when prefixes have conflicting mappings and a conflict resolver function is provided" do
|
||||
graph = Graph.new(prefixes: %{ex: EX}) |> Graph.add_prefixes([ex: XSD], fn _, ns, _ -> ns end)
|
||||
assert graph.prefixes == PrefixMap.new(ex: EX)
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete_prefixes/2" do
|
||||
test "when given a single prefix" do
|
||||
graph = Graph.new(prefixes: %{ex: EX}) |> Graph.delete_prefixes(:ex)
|
||||
assert graph.prefixes == PrefixMap.new()
|
||||
end
|
||||
|
||||
test "when given a list of prefixes" do
|
||||
graph = Graph.new(prefixes: %{ex1: EX, ex2: EX}) |> Graph.delete_prefixes([:ex1, :ex2, :ex3])
|
||||
assert graph.prefixes == PrefixMap.new()
|
||||
end
|
||||
|
||||
test "when prefixes are not defined yet" do
|
||||
graph = Graph.new() |> Graph.delete_prefixes(:ex)
|
||||
assert graph.prefixes == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "clear_prefixes/1" do
|
||||
assert Graph.clear_prefixes(Graph.new(prefixes: %{ex: EX})) == Graph.new
|
||||
end
|
||||
|
||||
describe "Enumerable protocol" do
|
||||
test "Enum.count" do
|
||||
|
|
Loading…
Reference in a new issue