Use also the :context opt and implicit PropertyMap formats in values/2

This commit is contained in:
Marcel Otto 2020-10-13 11:38:22 +02:00
parent cafba9f61f
commit 352b8ef1a8
13 changed files with 115 additions and 106 deletions

View file

@ -93,10 +93,10 @@ defprotocol RDF.Data do
@doc """ @doc """
Returns a nested map of the native Elixir values of a RDF data structure. Returns a nested map of the native Elixir values of a RDF data structure.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
""" """
def values(data, property_map \\ nil) def values(data, opts \\ [])
@doc """ @doc """
Returns a map representation of a RDF data structure where each element from its statements is mapped with the given function. Returns a map representation of a RDF data structure where each element from its statements is mapped with the given function.
@ -201,8 +201,8 @@ defimpl RDF.Data, for: RDF.Description do
def subject_count(_), do: 1 def subject_count(_), do: 1
def statement_count(description), do: Description.count(description) def statement_count(description), do: Description.count(description)
def values(description, property_map \\ nil), def values(description, opts \\ []),
do: Description.values(description, property_map) do: Description.values(description, opts)
def map(description, fun), do: Description.map(description, fun) def map(description, fun), do: Description.map(description, fun)
@ -292,7 +292,7 @@ defimpl RDF.Data, for: RDF.Graph do
def subject_count(graph), do: Graph.subject_count(graph) def subject_count(graph), do: Graph.subject_count(graph)
def statement_count(graph), do: Graph.triple_count(graph) def statement_count(graph), do: Graph.triple_count(graph)
def values(graph, property_map \\ nil), do: Graph.values(graph, property_map) def values(graph, opts \\ []), do: Graph.values(graph, opts)
def map(graph, fun), do: Graph.map(graph, fun) def map(graph, fun), do: Graph.map(graph, fun)
def equal?(graph, %Description{} = description), def equal?(graph, %Description{} = description),
@ -373,7 +373,7 @@ defimpl RDF.Data, for: RDF.Dataset do
def subject_count(dataset), do: dataset |> subjects |> Enum.count() def subject_count(dataset), do: dataset |> subjects |> Enum.count()
def statement_count(dataset), do: Dataset.statement_count(dataset) def statement_count(dataset), do: Dataset.statement_count(dataset)
def values(dataset, property_map \\ nil), do: Dataset.values(dataset, property_map) def values(dataset, opts \\ []), do: Dataset.values(dataset, opts)
def map(dataset, fun), do: Dataset.map(dataset, fun) def map(dataset, fun), do: Dataset.map(dataset, fun)
def equal?(dataset, %Description{} = description) do def equal?(dataset, %Description{} = description) do

View file

@ -761,8 +761,8 @@ defmodule RDF.Dataset do
@doc """ @doc """
Returns a nested map of the native Elixir values of a `RDF.Dataset`. Returns a nested map of the native Elixir values of a `RDF.Dataset`.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
## Examples ## Examples
@ -782,15 +782,13 @@ defmodule RDF.Dataset do
} }
""" """
@spec values(t, PropertyMap.t() | nil) :: map @spec values(t, keyword) :: map
def values(dataset, property_map \\ nil) def values(%__MODULE__{} = dataset, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
def values(%__MODULE__{} = dataset, nil) do map(dataset, Statement.default_property_mapping(property_map))
map(dataset, &Statement.default_term_mapping/1) else
end map(dataset, &Statement.default_term_mapping/1)
end
def values(%__MODULE__{} = dataset, %PropertyMap{} = property_map) do
map(dataset, Statement.default_property_mapping(property_map))
end end
@doc """ @doc """

View file

@ -94,14 +94,6 @@ defmodule RDF.Description do
%__MODULE__{description | subject: coerce_subject(new_subject)} %__MODULE__{description | subject: coerce_subject(new_subject)}
end end
defp context(nil), do: nil
defp context(opts) do
if property_map = Keyword.get(opts, :context) do
PropertyMap.new(property_map)
end
end
@doc """ @doc """
Add statements to a `RDF.Description`. Add statements to a `RDF.Description`.
@ -149,7 +141,7 @@ defmodule RDF.Description do
| predications: | predications:
Map.update( Map.update(
description.predications, description.predications,
coerce_predicate(predicate, context(opts)), coerce_predicate(predicate, PropertyMap.from_opts(opts)),
normalized_objects, normalized_objects,
fn objects -> fn objects ->
Map.merge(objects, normalized_objects) Map.merge(objects, normalized_objects)
@ -256,7 +248,7 @@ defmodule RDF.Description do
end end
def delete(%__MODULE__{} = description, {predicate, objects}, opts) do def delete(%__MODULE__{} = description, {predicate, objects}, opts) do
predicate = coerce_predicate(predicate, context(opts)) predicate = coerce_predicate(predicate, PropertyMap.from_opts(opts))
if current_objects = Map.get(description.predications, predicate) do if current_objects = Map.get(description.predications, predicate) do
normalized_objects = normalized_objects =
@ -661,7 +653,8 @@ defmodule RDF.Description do
end end
def include?(%__MODULE__{} = description, {predicate, objects}, opts) do def include?(%__MODULE__{} = description, {predicate, objects}, opts) do
if existing_objects = description.predications[coerce_predicate(predicate, context(opts))] do if existing_objects =
description.predications[coerce_predicate(predicate, PropertyMap.from_opts(opts))] do
objects objects
|> List.wrap() |> List.wrap()
|> Enum.map(&coerce_object/1) |> Enum.map(&coerce_object/1)
@ -726,8 +719,8 @@ defmodule RDF.Description do
`RDF.Term.value/1`, or, if you want the subject in an outer map, just put the `RDF.Term.value/1`, or, if you want the subject in an outer map, just put the
the description in a graph and use `RDF.Graph.values/2`. the description in a graph and use `RDF.Graph.values/2`.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
## Examples ## Examples
@ -736,19 +729,17 @@ defmodule RDF.Description do
%{"http://example.com/p" => ["Foo"]} %{"http://example.com/p" => ["Foo"]}
iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"}) iex> RDF.Description.new(~I<http://example.com/S>, init: {~I<http://example.com/p>, ~L"Foo"})
...> |> RDF.Description.values(PropertyMap.new(p: ~I<http://example.com/p>)) ...> |> RDF.Description.values(context: %{p: ~I<http://example.com/p>})
%{p: ["Foo"]} %{p: ["Foo"]}
""" """
@spec values(t, PropertyMap.t() | nil) :: map @spec values(t, keyword) :: map
def values(description, property_map \\ nil) def values(%__MODULE__{} = description, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
def values(%__MODULE__{} = description, nil) do map(description, Statement.default_property_mapping(property_map))
map(description, &Statement.default_term_mapping/1) else
end map(description, &Statement.default_term_mapping/1)
end
def values(%__MODULE__{} = description, %PropertyMap{} = property_map) do
map(description, Statement.default_property_mapping(property_map))
end end
@doc """ @doc """

View file

@ -853,8 +853,8 @@ defmodule RDF.Graph do
@doc """ @doc """
Returns a nested map of the native Elixir values of a `RDF.Graph`. Returns a nested map of the native Elixir values of a `RDF.Graph`.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
## Examples ## Examples
@ -872,22 +872,20 @@ defmodule RDF.Graph do
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"}, ...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.XSD.integer(42)} ...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.XSD.integer(42)}
...> ]) ...> ])
...> |> RDF.Graph.values(PropertyMap.new(p: ~I<http://example.com/p>)) ...> |> RDF.Graph.values(context: [p: ~I<http://example.com/p>])
%{ %{
"http://example.com/S1" => %{p: ["Foo"]}, "http://example.com/S1" => %{p: ["Foo"]},
"http://example.com/S2" => %{p: [42]} "http://example.com/S2" => %{p: [42]}
} }
""" """
@spec values(t, PropertyMap.t() | nil) :: map @spec values(t, keyword) :: map
def values(graph, property_map \\ nil) def values(%__MODULE__{} = graph, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
def values(%__MODULE__{} = graph, nil) do map(graph, Statement.default_property_mapping(property_map))
map(graph, &Statement.default_term_mapping/1) else
end map(graph, &Statement.default_term_mapping/1)
end
def values(%__MODULE__{} = graph, %PropertyMap{} = property_map) do
map(graph, Statement.default_property_mapping(property_map))
end end
@doc """ @doc """

View file

@ -23,6 +23,11 @@ defmodule RDF.PropertyMap do
property_map property_map
end end
@doc false
def from_opts(opts)
def from_opts(nil), do: nil
def from_opts(opts), do: if(property_map = Keyword.get(opts, :context), do: new(property_map))
def iri(%__MODULE__{} = property_map, term) do def iri(%__MODULE__{} = property_map, term) do
Map.get(property_map.iris, coerce_term(term)) Map.get(property_map.iris, coerce_term(term))
end end

View file

@ -97,8 +97,8 @@ defmodule RDF.Quad do
@doc """ @doc """
Returns a tuple of native Elixir values from a `RDF.Quad` of RDF terms. Returns a tuple of native Elixir values from a `RDF.Quad` of RDF terms.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`. Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
@ -108,19 +108,17 @@ defmodule RDF.Quad do
{"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"} {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>} iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
...> |> RDF.Quad.values(PropertyMap.new(p: ~I<http://example.com/p>)) ...> |> RDF.Quad.values(context: %{p: ~I<http://example.com/p>})
{"http://example.com/S", :p, 42, "http://example.com/Graph"} {"http://example.com/S", :p, 42, "http://example.com/Graph"}
""" """
@spec values(t, PropertyMap.t() | nil) :: t_values | nil @spec values(t, keyword) :: t_values | nil
def values(quad, property_map \\ nil) def values(quad, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
def values(quad, nil) do map(quad, Statement.default_property_mapping(property_map))
map(quad, &Statement.default_term_mapping/1) else
end map(quad, &Statement.default_term_mapping/1)
end
def values(quad, %PropertyMap{} = property_map) do
map(quad, Statement.default_property_mapping(property_map))
end end
@doc """ @doc """

View file

@ -99,8 +99,8 @@ defmodule RDF.Statement do
@doc """ @doc """
Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms. Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`. Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
@ -113,14 +113,14 @@ defmodule RDF.Statement do
{"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"} {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)} iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
...> |> RDF.Statement.values(PropertyMap.new(p: ~I<http://example.com/p>)) ...> |> RDF.Statement.values(context: %{p: ~I<http://example.com/p>})
{"http://example.com/S", :p, 42} {"http://example.com/S", :p, 42}
""" """
@spec values(t, PropertyMap.t() | nil) :: Triple.t_values() | Quad.t_values() | nil @spec values(t, keyword) :: Triple.t_values() | Quad.t_values() | nil
def values(quad, property_map \\ nil) def values(quad, opts \\ [])
def values({_, _, _} = triple, property_map), do: Triple.values(triple, property_map) def values({_, _, _} = triple, opts), do: Triple.values(triple, opts)
def values({_, _, _, _} = quad, property_map), do: Quad.values(quad, property_map) def values({_, _, _, _} = quad, opts), do: Quad.values(quad, opts)
@doc """ @doc """
Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms. Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms.

View file

@ -87,8 +87,8 @@ defmodule RDF.Triple do
@doc """ @doc """
Returns a tuple of native Elixir values from a `RDF.Triple` of RDF terms. Returns a tuple of native Elixir values from a `RDF.Triple` of RDF terms.
When the optional `property_map` argument is given, predicates will be mapped When a `:context` option is given with a `RDF.PropertyMap`, predicates will
to the terms defined in the `RDF.PropertyMap` if present. be mapped to the terms defined in the `RDF.PropertyMap`, if present.
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`. Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
@ -98,20 +98,17 @@ defmodule RDF.Triple do
{"http://example.com/S", "http://example.com/p", 42} {"http://example.com/S", "http://example.com/p", 42}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)} iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
...> |> RDF.Triple.values(PropertyMap.new(p: ~I<http://example.com/p>)) ...> |> RDF.Triple.values(context: %{p: ~I<http://example.com/p>})
{"http://example.com/S", :p, 42} {"http://example.com/S", :p, 42}
""" """
@spec values(t, PropertyMap.t() | nil) :: t_values | nil @spec values(t, keyword) :: t_values | nil
def values(triple, property_map \\ nil) def values(triple, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
def values(triple, nil) do map(triple, Statement.default_property_mapping(property_map))
map(triple, &Statement.default_term_mapping/1) else
end map(triple, &Statement.default_term_mapping/1)
end
def values(triple, %PropertyMap{} = property_map) do
map(triple, Statement.default_property_mapping(property_map))
end end
@doc """ @doc """

View file

@ -1681,16 +1681,22 @@ defmodule RDF.DatasetTest do
end end
test "values/2" do test "values/2" do
expected_result = %{
nil => %{
RDF.Term.value(EX.s1()) => %{p: [RDF.Term.value(EX.o1())]}
},
RDF.Term.value(EX.graph()) => %{
RDF.Term.value(EX.s2()) => %{p: [RDF.Term.value(EX.o2())]}
}
}
assert Dataset.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2(), EX.graph()}]) assert Dataset.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2(), EX.graph()}])
|> Dataset.values(PropertyMap.new(p: EX.p())) == |> Dataset.values(context: PropertyMap.new(p: EX.p())) ==
%{ expected_result
nil => %{
RDF.Term.value(EX.s1()) => %{p: [RDF.Term.value(EX.o1())]} assert Dataset.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2(), EX.graph()}])
}, |> Dataset.values(context: %{p: EX.p()}) ==
RDF.Term.value(EX.graph()) => %{ expected_result
RDF.Term.value(EX.s2()) => %{p: [RDF.Term.value(EX.o2())]}
}
}
end end
test "map/2" do test "map/2" do

View file

@ -844,7 +844,11 @@ defmodule RDF.DescriptionTest do
test "values/2" do test "values/2" do
assert Description.new(EX.s(), init: {EX.s(), EX.p(), ~L"Foo"}) assert Description.new(EX.s(), init: {EX.s(), EX.p(), ~L"Foo"})
|> Description.values(PropertyMap.new(p: EX.p())) == |> Description.values(context: PropertyMap.new(p: EX.p())) ==
%{p: ["Foo"]}
assert Description.new(EX.s(), init: {EX.s(), EX.p(), ~L"Foo"})
|> Description.values(context: %{p: EX.p()}) ==
%{p: ["Foo"]} %{p: ["Foo"]}
end end

View file

@ -1252,12 +1252,18 @@ defmodule RDF.GraphTest do
end end
test "values/2" do test "values/2" do
expected_result = %{
RDF.Term.value(EX.s1()) => %{p: [RDF.Term.value(EX.o1())]},
RDF.Term.value(EX.s2()) => %{p: [RDF.Term.value(EX.o2())]}
}
assert Graph.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2()}]) assert Graph.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2()}])
|> Graph.values(PropertyMap.new(p: EX.p())) == |> Graph.values(context: PropertyMap.new(p: EX.p())) ==
%{ expected_result
RDF.Term.value(EX.s1()) => %{p: [RDF.Term.value(EX.o1())]},
RDF.Term.value(EX.s2()) => %{p: [RDF.Term.value(EX.o2())]} assert Graph.new([{EX.s1(), EX.p(), EX.o1()}, {EX.s2(), EX.p(), EX.o2()}])
} |> Graph.values(context: [p: EX.p()]) ==
expected_result
end end
test "map/2" do test "map/2" do

View file

@ -29,20 +29,20 @@ defmodule RDF.QuadTest do
assert Quad.values( assert Quad.values(
{~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42), {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42),
~I<http://example.com/Graph>}, ~I<http://example.com/Graph>},
PropertyMap.new(p: ~I<http://example.com/p>) context: %{p: ~I<http://example.com/p>}
) == ) ==
{"http://example.com/S", :p, 42, "http://example.com/Graph"} {"http://example.com/S", :p, 42, "http://example.com/Graph"}
assert Quad.values( assert Quad.values(
{~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42), {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42),
~I<http://example.com/Graph>}, ~I<http://example.com/Graph>},
PropertyMap.new() context: PropertyMap.new()
) == ) ==
{"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"} {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
end end
test "with an invalid RDF.Triple" do test "with an invalid RDF.Triple" do
refute Quad.values({self(), self(), self(), self()}, PropertyMap.new()) refute Quad.values({self(), self(), self(), self()}, context: PropertyMap.new())
end end
end end

View file

@ -20,19 +20,25 @@ defmodule RDF.TripleTest do
test "with a valid RDF.Triple and RDF.PropertyMap" do test "with a valid RDF.Triple and RDF.PropertyMap" do
assert Triple.values( assert Triple.values(
{~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)}, {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)},
PropertyMap.new(p: ~I<http://example.com/p>) context: PropertyMap.new(p: ~I<http://example.com/p>)
) == ) ==
{"http://example.com/S", :p, 42} {"http://example.com/S", :p, 42}
assert Triple.values( assert Triple.values(
{~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)}, {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)},
PropertyMap.new() context: [p: ~I<http://example.com/p>]
) ==
{"http://example.com/S", :p, 42}
assert Triple.values(
{~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)},
context: []
) == ) ==
{"http://example.com/S", "http://example.com/p", 42} {"http://example.com/S", "http://example.com/p", 42}
end end
test "with an invalid RDF.Triple" do test "with an invalid RDF.Triple" do
refute Triple.values({self(), self(), self()}, PropertyMap.new()) refute Triple.values({self(), self(), self()}, context: PropertyMap.new())
end end
end end