Add a values/2 variant with a custom mapping function

This commit is contained in:
Marcel Otto 2018-11-04 22:27:25 +01:00
parent 535e5b3713
commit 4336602dcc
16 changed files with 391 additions and 30 deletions

View file

@ -16,6 +16,9 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
- `RDF.Description.values/1`, `RDF.Graph.values/1`, `RDF.Dataset.values/1` and - `RDF.Description.values/1`, `RDF.Graph.values/1`, `RDF.Dataset.values/1` and
`RDF.Data.values/1` returning a map of `RDF.Term.value/1` converted native `RDF.Data.values/1` returning a map of `RDF.Term.value/1` converted native
Elixir values from the respective structure of RDF terms Elixir values from the respective structure of RDF terms
- for all of aforementioned `values/1` functions a variant `values/2` which
allows to specify custom mapping function to be applied when creating the resp.
structure
- `RDF.Literal.compare/2`, `RDF.Literal.less_than?/2` and `RDF.Literal.greater_than?/2` - `RDF.Literal.compare/2`, `RDF.Literal.less_than?/2` and `RDF.Literal.greater_than?/2`
for `RDF.Datatype` aware comparisons of `RDF.Literal`s for `RDF.Datatype` aware comparisons of `RDF.Literal`s

View file

@ -689,6 +689,83 @@ iex> RDF.list(["foo", EX.Bar, ~B<bar>, [1, 2]]) |> RDF.List.values
%RDF.Literal{value: 2, datatype: ~I<http://www.w3.org/2001/XMLSchema#integer>}]] %RDF.Literal{value: 2, datatype: ~I<http://www.w3.org/2001/XMLSchema#integer>}]]
``` ```
### Mapping of RDF terms and structures
The `RDF.Term.value/1` function converts RDF terms to Elixir values:
```elixir
iex> RDF.Term.value(~I<http://example.com/>)
"http://example.com/"
iex> RDF.Term.value(~L"foo")
"foo"
iex> RDF.integer(42) |> RDF.Term.value()
42
```
It returns `nil` if no conversion is possible.
All structures of RDF terms also support a `values` function. On `RDF.Triple.values`, `RDF.Quad` and `RDF.Statement` the tuple of RDF terms is converted to an tuple of the resp. Elixir values. On all of the other RDF data structures (`RDF.Description`, `RDF.Graph` and `RDF.Dataset`) and the general `RDF.Data` protocol the `values` functions produces a map of the converted Elixir values.
```elixir
iex> RDF.Triple.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
{"http://example.com/S", "http://example.com/p", 42}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo"}
...> |> RDF.Description.new()
...> |> RDF.Description.values()
%{"http://example.com/p" => ["Foo"]}
iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)}
...> ]
...> |> RDF.Graph.new()
...> |> RDF.Graph.values()
%{
"http://example.com/S1" => %{"http://example.com/p" => ["Foo"]},
"http://example.com/S2" => %{"http://example.com/p" => [42]}
}
iex> [
...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>},
...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), }
...> ]
...> |> RDF.Dataset.new()
...> |> RDF.Dataset.values()
%{
"http://example.com/Graph" => %{
"http://example.com/S" => %{"http://example.com/p" => ["Foo"]}
},
nil => %{
"http://example.com/S" => %{"http://example.com/p" => [42]}
}
}
```
All of these `values` functions also support an optional second argument for a function with a custom mapping of the terms depending on their statement position. The function will be called with a tuple `{statement_position, rdf_term}` where `statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or `:graph_name`, while `rdf_term` is the RDF term to be mapped.
```elixir
iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)}
...> ]
...> |> RDF.Graph.new()
...> |> RDF.Graph.values(fn
...> {:predicate, predicate} ->
...> predicate
...> |> to_string()
...> |> String.split("/")
...> |> List.last()
...> |> String.to_atom()
...> {_, term} ->
...> RDF.Term.value(term)
...> end)
%{
"http://example.com/S1" => %{p: ["Foo"]},
"http://example.com/S2" => %{p: [42]}
}
```
### Serializations ### Serializations
@ -739,7 +816,7 @@ The `Date` and `DateTime` modules of Elixir versions < 1.7.2 don't handle negati
## Getting help ## Getting help
- [Documentation](http://hexdocs.pm/rdf) - [Documentation](http://hexdocs.pm/rdf)
- [A tutorial about working with RDF.ex vocabularies](https://medium.com/@tonyhammond/early-steps-in-elixir-and-rdf-5078a4ebfe0f) - [A tutorial about working with RDF.ex vocabularies by Tony Hammond](https://medium.com/@tonyhammond/early-steps-in-elixir-and-rdf-5078a4ebfe0f)
- [Google Group](https://groups.google.com/d/forum/rdfex) - [Google Group](https://groups.google.com/d/forum/rdfex)
@ -772,7 +849,7 @@ see [CONTRIBUTING](CONTRIBUTING.md) for details.
[RDF.ex]: https://hex.pm/packages/rdf [RDF.ex]: https://hex.pm/packages/rdf
[rdf_vocab]: https://hex.pm/packages/rdf_vocab [rdf_vocab]: https://hex.pm/packages/rdf_vocab
[JSON-LD.ex]: https://hex.pm/packages/json_ld [JSON-LD.ex]: https://hex.pm/packages/json_ld
[SPARQL.ex]: https://hex.pm/packages/sparql [SPARQL.ex]: https://hex.pm/packages/sparql
[SPARQL.Client]: https://hex.pm/packages/sparql_client [SPARQL.Client]: https://hex.pm/packages/sparql_client
[N-Triples]: https://www.w3.org/TR/n-triples/ [N-Triples]: https://www.w3.org/TR/n-triples/
[N-Quads]: https://www.w3.org/TR/n-quads/ [N-Quads]: https://www.w3.org/TR/n-quads/

View file

@ -97,6 +97,10 @@ defprotocol RDF.Data do
""" """
def values(data) def values(data)
@doc """
Returns a nested map of the native Elixir values of a RDF data structure with values mapped with the given function.
"""
def values(data, mapping)
end end
defimpl RDF.Data, for: RDF.Description do defimpl RDF.Data, for: RDF.Description do
@ -164,6 +168,7 @@ defimpl RDF.Data, for: RDF.Description do
def subject_count(_), do: 1 def subject_count(_), do: 1
def statement_count(description), do: RDF.Description.count(description) def statement_count(description), do: RDF.Description.count(description)
def values(description), do: RDF.Description.values(description) def values(description), do: RDF.Description.values(description)
def values(description, mapping), do: RDF.Description.values(description, mapping)
end end
@ -225,6 +230,7 @@ defimpl RDF.Data, for: RDF.Graph do
def subject_count(graph), do: RDF.Graph.subject_count(graph) def subject_count(graph), do: RDF.Graph.subject_count(graph)
def statement_count(graph), do: RDF.Graph.triple_count(graph) def statement_count(graph), do: RDF.Graph.triple_count(graph)
def values(graph), do: RDF.Graph.values(graph) def values(graph), do: RDF.Graph.values(graph)
def values(graph, mapping), do: RDF.Graph.values(graph, mapping)
end end
@ -279,4 +285,5 @@ 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: RDF.Dataset.statement_count(dataset) def statement_count(dataset), do: RDF.Dataset.statement_count(dataset)
def values(dataset), do: RDF.Dataset.values(dataset) def values(dataset), do: RDF.Dataset.values(dataset)
def values(dataset, mapping), do: RDF.Dataset.values(dataset, mapping)
end end

View file

@ -731,6 +731,11 @@ 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`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate`, `:object`,
or `graph_name` while `rdf_term` is the RDF term to be mapped.
## Examples ## Examples
iex> [ iex> [
@ -748,10 +753,38 @@ defmodule RDF.Dataset do
} }
} }
iex> [
...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>},
...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), }
...> ]
...> |> RDF.Dataset.new()
...> |> RDF.Dataset.values(fn
...> {:graph_name, graph_name} ->
...> graph_name
...> {:predicate, predicate} ->
...> predicate
...> |> to_string()
...> |> String.split("/")
...> |> List.last()
...> |> String.to_atom()
...> {_, term} ->
...> RDF.Term.value(term)
...> end)
%{
~I<http://example.com/Graph> => %{
"http://example.com/S" => %{p: ["Foo"]}
},
nil => %{
"http://example.com/S" => %{p: [42]}
}
}
""" """
def values(%RDF.Dataset{graphs: graphs}) do def values(dataset, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Dataset{graphs: graphs}, mapping) do
Map.new graphs, fn {graph_name, graph} -> Map.new graphs, fn {graph_name, graph} ->
{RDF.Term.value(graph_name), Graph.values(graph)} {mapping.({:graph_name, graph_name}), Graph.values(graph, mapping)}
end end
end end

View file

@ -578,6 +578,11 @@ defmodule RDF.Description do
The subject is not part of the result. It can be converted separately with The subject is not part of the result. It can be converted separately with
`RDF.Term.value/1`. `RDF.Term.value/1`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:predicate` or `:object`,
while `rdf_term` is the RDF term to be mapped.
## Examples ## Examples
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo"} iex> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo"}
@ -585,16 +590,33 @@ defmodule RDF.Description do
...> |> RDF.Description.values() ...> |> RDF.Description.values()
%{"http://example.com/p" => ["Foo"]} %{"http://example.com/p" => ["Foo"]}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo"}
...> |> RDF.Description.new()
...> |> RDF.Description.values(fn
...> {:predicate, predicate} ->
...> predicate
...> |> to_string()
...> |> String.split("/")
...> |> List.last()
...> |> String.to_atom()
...> {_, term} ->
...> RDF.Term.value(term)
...> end)
%{p: ["Foo"]}
""" """
def values(%RDF.Description{subject: subject, predications: predications}) do def values(description, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%RDF.Description{predications: predications}, mapping) do
Map.new predications, fn {predicate, objects} -> Map.new predications, fn {predicate, objects} ->
{ {
RDF.Term.value(predicate), mapping.({:predicate, predicate}),
objects |> Map.keys() |> Enum.map(&RDF.Term.value/1) objects |> Map.keys() |> Enum.map(&(mapping.({:object, &1})))
} }
end end
end end
defimpl Enumerable do defimpl Enumerable do
def member?(desc, triple), do: {:ok, RDF.Description.include?(desc, triple)} def member?(desc, triple), do: {:ok, RDF.Description.include?(desc, triple)}
def count(desc), do: {:ok, RDF.Description.count(desc)} def count(desc), do: {:ok, RDF.Description.count(desc)}

View file

@ -605,6 +605,11 @@ 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`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate` or `:object`,
while `rdf_term` is the RDF term to be mapped.
## Examples ## Examples
iex> [ iex> [
@ -618,10 +623,32 @@ defmodule RDF.Graph do
"http://example.com/S2" => %{"http://example.com/p" => [42]} "http://example.com/S2" => %{"http://example.com/p" => [42]}
} }
iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)}
...> ]
...> |> RDF.Graph.new()
...> |> RDF.Graph.values(fn
...> {:predicate, predicate} ->
...> predicate
...> |> to_string()
...> |> String.split("/")
...> |> List.last()
...> |> String.to_atom()
...> {_, term} ->
...> RDF.Term.value(term)
...> end)
%{
"http://example.com/S1" => %{p: ["Foo"]},
"http://example.com/S2" => %{p: [42]}
}
""" """
def values(%RDF.Graph{descriptions: descriptions}) 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} -> Map.new descriptions, fn {subject, description} ->
{RDF.Term.value(subject), Description.values(description)} {mapping.({:subject, subject}), Description.values(description, mapping)}
end end
end end

View file

@ -54,23 +54,44 @@ defmodule RDF.Quad do
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`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or
`:graph_name`, while `rdf_term` is the RDF term to be mapped. When the given
function returns `nil` this will be interpreted as an error and will become
the overhaul result of the `values/2` call.
## Examples ## Examples
iex> RDF.Quad.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>} iex> RDF.Quad.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
{"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>}
...> |> RDF.Quad.values(fn
...> {:object, object} ->
...> RDF.Term.value(object)
...> {:graph_name, graph_name} ->
...> graph_name
...> {_, resource} ->
...> resource |> to_string() |> String.last() |> String.to_atom()
...> end)
{:S, :p, 42, ~I<http://example.com/Graph>}
""" """
def values({subject, predicate, object, graph_context}) do def values(quad, mapping \\ &Statement.default_term_mapping/1)
with subject_value when not is_nil(subject_value) <- Term.value(subject),
predicate_value when not is_nil(predicate_value) <- Term.value(predicate), def values({subject, predicate, object, graph_context}, mapping) do
object_value when not is_nil(object_value) <- Term.value(object), with subject_value when not is_nil(subject_value) <- mapping.({:subject, subject}),
graph_context_value when not is_nil(graph_context_value) or is_nil(graph_context) <- predicate_value when not is_nil(predicate_value) <- mapping.({:predicate, predicate}),
Term.value(graph_context) object_value when not is_nil(object_value) <- mapping.({:object, object}),
graph_context_value <- mapping.({:graph_name, graph_context})
do do
{subject_value, predicate_value, object_value, graph_context_value} {subject_value, predicate_value, object_value, graph_context_value}
else
_ -> nil
end end
end end
def values(_), do: nil def values(_, _), do: nil
end end

View file

@ -25,9 +25,9 @@ defmodule RDF.Statement do
## Examples ## Examples
iex> RDF.Statement.new {"http://example.com/S", "http://example.com/p", 42} iex> RDF.Statement.coerce {"http://example.com/S", "http://example.com/p", 42}
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)} {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
iex> RDF.Statement.new {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"} iex> RDF.Statement.coerce {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>} {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
""" """
def coerce(statement) def coerce(statement)
@ -77,6 +77,13 @@ defmodule RDF.Statement do
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`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or
`:graph_name`, while `rdf_term` is the RDF term to be mapped. When the given
function returns `nil` this will be interpreted as an error and will become
the overhaul result of the `values/2` call.
## Examples ## Examples
iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)} iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
@ -84,9 +91,28 @@ defmodule RDF.Statement do
iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>} iex> RDF.Statement.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
{"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>}
...> |> RDF.Statement.values(fn
...> {:subject, subject} ->
...> subject |> to_string() |> String.last()
...> {:predicate, predicate} ->
...> predicate |> to_string() |> String.last() |> String.to_atom()
...> {:object, object} ->
...> RDF.Term.value(object)
...> {:graph_name, graph_name} ->
...> graph_name
...> end)
{"S", :p, 42, ~I<http://example.com/Graph>}
""" """
def values({_, _, _} = triple), do: RDF.Triple.values(triple) def values(statement, mapping \\ &default_term_mapping/1)
def values({_, _, _, _} = quad), do: RDF.Quad.values(quad) def values({_, _, _} = triple, mapping), do: RDF.Triple.values(triple, mapping)
def values(_), do: nil def values({_, _, _, _} = quad, mapping), do: RDF.Quad.values(quad, mapping)
def values(_, _), do: nil
@doc false
def default_term_mapping(qualified_term)
def default_term_mapping({:graph_name, nil}), do: nil
def default_term_mapping({_, term}), do: RDF.Term.value(term)
end end

View file

@ -52,21 +52,39 @@ defmodule RDF.Triple do
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`.
The optional second argument allows to specify a custom mapping with a function
which will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate` or `:object`,
while `rdf_term` is the RDF term to be mapped. When the given function returns
`nil` this will be interpreted as an error and will become the overhaul result
of the `values/2` call.
## Examples ## Examples
iex> RDF.Triple.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)} iex> RDF.Triple.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
{"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)}
...> |> RDF.Triple.values(fn
...> {:object, object} -> RDF.Term.value(object)
...> {_, term} -> term |> to_string() |> String.last()
...> end)
{"S", "p", 42}
""" """
def values({subject, predicate, object}) do def values(triple, mapping \\ &Statement.default_term_mapping/1)
with subject_value when not is_nil(subject_value) <- Term.value(subject),
predicate_value when not is_nil(predicate_value) <- Term.value(predicate), def values({subject, predicate, object}, mapping) do
object_value when not is_nil(object_value) <- Term.value(object) with subject_value when not is_nil(subject_value) <- mapping.({:subject, subject}),
predicate_value when not is_nil(predicate_value) <- mapping.({:predicate, predicate}),
object_value when not is_nil(object_value) <- mapping.({:object, object})
do do
{subject_value, predicate_value, object_value} {subject_value, predicate_value, object_value}
else
_ -> nil
end end
end end
def values(_), do: nil def values(_, _), do: nil
end end

View file

@ -423,7 +423,7 @@ defmodule RDF.DataTest do
assert RDF.Data.statement_count(dataset) == 14 assert RDF.Data.statement_count(dataset) == 14
end end
test "values", %{dataset: dataset} do test "values/1", %{dataset: dataset} do
assert RDF.Data.values(dataset) == assert RDF.Data.values(dataset) ==
%{ %{
nil => %{ nil => %{
@ -459,6 +459,52 @@ defmodule RDF.DataTest do
} }
} }
end end
test "values/2", %{dataset: dataset} do
mapping = fn
{:graph_name, graph_name} ->
graph_name
{:predicate, predicate} ->
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
{_, term} ->
RDF.Term.value(term)
end
assert RDF.Data.values(dataset, mapping) ==
%{
nil => %{
RDF.Term.value(RDF.iri(EX.S)) => %{
p1: [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
p2: [RDF.Term.value(RDF.iri(EX.O3))],
p3: ["_:foo", "bar"],
},
RDF.Term.value(RDF.iri(EX.S2)) => %{
p2: [
RDF.Term.value(RDF.iri(EX.O3)),
RDF.Term.value(RDF.iri(EX.O4))
],
},
},
RDF.iri(EX.NamedGraph) => %{
RDF.Term.value(RDF.iri(EX.S)) => %{
p1: [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
p2: [RDF.Term.value(RDF.iri(EX.O3))],
p3: ["_:foo", RDF.Term.value(RDF.iri(EX.O5)), "bar"],
},
RDF.Term.value(RDF.iri(EX.S3)) => %{
p3: [
RDF.Term.value(RDF.iri(EX.O5))
],
},
}
}
end
end end
end end

View file

@ -715,6 +715,29 @@ defmodule RDF.DatasetTest do
} }
end end
test "values/2" do
mapping = fn
{:graph_name, graph_name} ->
graph_name
{:predicate, predicate} ->
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
{_, term} ->
RDF.Term.value(term)
end
assert Dataset.new() |> Dataset.values(mapping) == %{}
assert Dataset.new([{EX.s1, EX.p, EX.o1}, {EX.s2, EX.p, EX.o2, EX.graph}])
|> Dataset.values(mapping) ==
%{
nil => %{
RDF.Term.value(EX.s1) => %{p: [RDF.Term.value(EX.o1)]}
},
EX.graph => %{
RDF.Term.value(EX.s2) => %{p: [RDF.Term.value(EX.o2)]},
}
}
end
describe "Enumerable protocol" do describe "Enumerable protocol" do
test "Enum.count" do test "Enum.count" do

View file

@ -340,9 +340,22 @@ defmodule RDF.DescriptionTest do
end end
test "values/1" do test "values/1" do
assert Description.new(EX.s) |> Description.values() == %{} assert Description.new(EX.s) |> Description.values() == %{}
assert Description.new({EX.s, EX.p, ~L"Foo"}) |> Description.values() == assert Description.new({EX.s, EX.p, ~L"Foo"}) |> Description.values() ==
%{RDF.Term.value(EX.p) => ["Foo"]} %{RDF.Term.value(EX.p) => ["Foo"]}
end
test "values/2" do
mapping = fn
{:predicate, predicate} ->
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
{_, term} ->
RDF.Term.value(term)
end
assert Description.new(EX.s) |> Description.values(mapping) == %{}
assert Description.new({EX.s, EX.p, ~L"Foo"}) |> Description.values(mapping) ==
%{p: ["Foo"]}
end end
describe "Enumerable protocol" do describe "Enumerable protocol" do

View file

@ -374,6 +374,23 @@ defmodule RDF.GraphTest do
} }
end end
test "values/2" do
mapping = fn
{:predicate, predicate} ->
predicate |> to_string() |> String.split("/") |> List.last() |> String.to_atom()
{_, term} ->
RDF.Term.value(term)
end
assert Graph.new() |> Graph.values(mapping) == %{}
assert Graph.new([{EX.s1, EX.p, EX.o1}, {EX.s2, EX.p, EX.o2}])
|> Graph.values(mapping) ==
%{
RDF.Term.value(EX.s1) => %{p: [RDF.Term.value(EX.o1)]},
RDF.Term.value(EX.s2) => %{p: [RDF.Term.value(EX.o2)]},
}
end
describe "Enumerable protocol" do describe "Enumerable protocol" do
test "Enum.count" do test "Enum.count" do

View file

@ -18,4 +18,16 @@ defmodule RDF.QuadTest do
refute Quad.values({self(), self(), self(), self()}) refute Quad.values({self(), self(), self(), self()})
end end
end end
test "values/2" do
assert {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), ~I<http://example.com/Graph>}
|> Quad.values(fn
{:subject, subject} -> subject |> to_string() |> String.last() |> String.to_atom()
{:predicate, _} -> :p
{:object, object} -> object |> RDF.Term.value() |> Kernel.+(1)
{:graph_name, _} -> nil
end)
== {:S, :p, 43, nil}
end
end end

View file

@ -0,0 +1,6 @@
defmodule RDF.StatementTest do
use RDF.Test.Case
doctest RDF.Statement
end

View file

@ -16,4 +16,14 @@ defmodule RDF.TripleTest do
refute Triple.values({self(), self(), self()}) refute Triple.values({self(), self(), self()})
end end
end end
test "values/2" do
assert {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42)}
|> Triple.values(fn
{:object, object} -> object |> RDF.Term.value() |> Kernel.+(1)
{_, term} -> term |> to_string() |> String.last()
end)
== {"S", "p", 43}
end
end end