Revise RDF.List
This commit is contained in:
parent
be36ae085d
commit
2f6fa6fe34
4 changed files with 436 additions and 288 deletions
53
README.md
53
README.md
|
@ -16,10 +16,8 @@ An implementation of the [RDF](https://www.w3.org/TR/rdf11-primer/) data model i
|
|||
- XML schema datatypes for RDF literals (not yet all supported)
|
||||
- sigils for the most common types of nodes, i.e. URIs, literals, blank nodes and lists
|
||||
- a description DSL resembling Turtle in Elixir
|
||||
- foundation for RDF serialization readers and writers
|
||||
- implementations for the [N-Triples] and [N-Quads] serialization formats
|
||||
- other formats will be available as dedicated separate Hex packages
|
||||
- currently only [JSON-LD] is available via the [JSON-LD.ex] package
|
||||
- implementations for the [N-Triples], [N-Quads] and [Turtle] serialization formats
|
||||
- [JSON-LD] is implemented in the separate [JSON-LD.ex] package (as every format requiring additional dependencies will be published in separate packages)
|
||||
|
||||
|
||||
## Installation
|
||||
|
@ -622,35 +620,47 @@ Beyond that, there is
|
|||
|
||||
### Lists
|
||||
|
||||
The `RDF.List` module provides some functions for working with RDF lists.
|
||||
RDF lists can be represented with the `RDF.List` structure.
|
||||
|
||||
`RDF.List.new` or its alias `RDF.list` takes a native Elixir list with values of all types that are allowed for objects of statements or nested lists, and creates a graph with statements constituting the list in RDF.
|
||||
An existing `RDF.List` in a given graph can be created with `RDF.List.new` or its alias `RDF.list`, passing it the head node of a list and the graph containing the statements constituting the list.
|
||||
|
||||
```elixir
|
||||
{node, graph} = RDF.list(["foo", EX.bar, ~B<bar>, [1, 2, 3]])
|
||||
graph = Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(EX.Foo))
|
||||
|> Graph.add(
|
||||
EX.Foo
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
)
|
||||
list = RDF.List.new(~B<Foo>, graph)
|
||||
```
|
||||
|
||||
It returns a tuple `{node, graph}`, where `node` is the name of the head node of the list and `graph` the `RDF.Graph` with the list statements. If your only interested in the graph, you can use `RDF.List.new!` or its alias `RDF.list!`.
|
||||
If the given head node does not refer to a well-formed RDF list in the graph, `nil` is returned.
|
||||
|
||||
An entirely new `RDF.List` can be created with `RDF.List.from` or `RDF.list` and a native Elixir list or an Elixir `Enumerable` with values of all types that are allowed for objects of statements (including nested lists).
|
||||
|
||||
```elixir
|
||||
list = RDF.list(["foo", EX.bar, ~B<bar>, [1, 2, 3]])
|
||||
```
|
||||
If you want to add the graph statements to an existing graph, you can do that via the `graph` option.
|
||||
|
||||
```elixir
|
||||
existing_graph = RDF.Graph.new({EX.S, EX.p, EX.O})
|
||||
graph = RDF.list!([1, 2, 3], graph: existing_graph)
|
||||
RDF.list([1, 2, 3], graph: existing_graph)
|
||||
```
|
||||
|
||||
The function `RDF.List.to_native/2` allows to get the values of a RDF list from an existing graph as a native Elixir list. It expects to head list node and the graph with the statements. Except for nested lists, the values of the list are not further converted, but returned as RDF types, i.e. `RDF.Literal`s etc.
|
||||
The `head` option also allows to specify a custom node for the head of the list.
|
||||
|
||||
The function `RDF.List.values/1` allows to get the values of a RDF list (including nested lists) as a native Elixir list.
|
||||
|
||||
```elixir
|
||||
{head_node, graph} = RDF.list(["foo", EX.bar, ~B<bar>, [1, 2]])
|
||||
RDF.List.to_native(head_node, graph)
|
||||
# [~L"foo", EX.bar, ~B<bar>, [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
RDF.list(["foo", EX.Bar, ~B<bar>, [1, 2]])
|
||||
|> RDF.List.values
|
||||
# [~L"foo", RDF.uri(EX.bar), ~B<bar>, [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
```
|
||||
|
||||
When the given node does not refer to a valid list in the given graph the function returns `nil`.
|
||||
|
||||
If a list in a given graph is a valid RDF list can be checked with the function `RDF.List.valid?/2`.
|
||||
|
||||
|
||||
### Serializations
|
||||
|
||||
|
@ -663,8 +673,9 @@ RDF graphs and datasets can be read and written to files or strings in a RDF ser
|
|||
|
||||
All of the read and write functions are also available in bang variants which will fail in error cases.
|
||||
|
||||
The RDF.ex package only comes with implementations of the [N-Triples] and [N-Quads] serialization formats. Other formats are and should be implemented in separate Hex packages.
|
||||
Currently only [JSON-LD] is available with the [JSON-LD.ex] package.
|
||||
The RDF.ex package comes with implementations of the [N-Triples], [N-Quads] and [Turtle] serialization formats.
|
||||
Formats which require additional dependencies should be implemented in separate Hex packages.
|
||||
The [JSON-LD] for example is available with the [JSON-LD.ex] package.
|
||||
|
||||
|
||||
## Getting help
|
||||
|
@ -703,10 +714,10 @@ see [CONTRIBUTING](CONTRIBUTING.md) for details.
|
|||
[JSON-LD.ex]: https://hex.pm/packages/json_ld
|
||||
[N-Triples]: https://www.w3.org/TR/n-triples/
|
||||
[N-Quads]: https://www.w3.org/TR/n-quads/
|
||||
[JSON-LD]: http://www.w3.org/TR/json-ld/
|
||||
[Turtle]: https://www.w3.org/TR/turtle/
|
||||
[RDFa]: https://www.w3.org/TR/rdfa-syntax/
|
||||
[N3]: https://www.w3.org/TeamSubmission/n3/
|
||||
[JSON-LD]: http://www.w3.org/TR/json-ld/
|
||||
[RDFa]: https://www.w3.org/TR/rdfa-syntax/
|
||||
[RDF-XML]: https://www.w3.org/TR/rdf-syntax-grammar/
|
||||
[BCP47]: https://tools.ietf.org/html/bcp47
|
||||
[XML schema datatype]: https://www.w3.org/TR/xmlschema11-2/
|
||||
|
|
13
lib/rdf.ex
13
lib/rdf.ex
|
@ -130,6 +130,15 @@ defmodule RDF do
|
|||
end
|
||||
end
|
||||
|
||||
def list(native_list),
|
||||
do: RDF.List.from(native_list)
|
||||
|
||||
def list(head, %Graph{} = graph),
|
||||
do: RDF.List.new(head, graph)
|
||||
|
||||
def list(native_list, opts),
|
||||
do: RDF.List.from(native_list, opts)
|
||||
|
||||
|
||||
defdelegate bnode(), to: BlankNode, as: :new
|
||||
defdelegate bnode(id), to: BlankNode, as: :new
|
||||
|
@ -157,10 +166,6 @@ defmodule RDF do
|
|||
defdelegate dataset(arg), to: Dataset, as: :new
|
||||
defdelegate dataset(arg1, arg2), to: Dataset, as: :new
|
||||
|
||||
defdelegate list(list), to: RDF.List, as: :new
|
||||
defdelegate list(list, opts), to: RDF.List, as: :new
|
||||
defdelegate list!(list), to: RDF.List, as: :new!
|
||||
defdelegate list!(list, opts), to: RDF.List, as: :new!
|
||||
defdelegate list?(resource, graph), to: RDF.List, as: :node?
|
||||
defdelegate list?(description), to: RDF.List, as: :node?
|
||||
|
||||
|
|
229
lib/rdf/list.ex
229
lib/rdf/list.ex
|
@ -1,55 +1,93 @@
|
|||
defmodule RDF.List do
|
||||
@moduledoc """
|
||||
Functions for working with RDF lists.
|
||||
A structure for RDF lists.
|
||||
|
||||
see
|
||||
- <https://www.w3.org/TR/rdf-schema/#ch_collectionvocab>
|
||||
- <https://www.w3.org/TR/rdf11-mt/#rdf-collections>
|
||||
"""
|
||||
|
||||
defstruct [:head, :graph]
|
||||
|
||||
alias RDF.{Graph, Description, BlankNode}
|
||||
|
||||
@rdf_nil RDF.nil
|
||||
|
||||
@doc """
|
||||
Creates a RDF list.
|
||||
Creates a `RDF.List` for a given RDF list node of a given `RDF.Graph`.
|
||||
|
||||
The function returns a tuple `{node, graph}`, where `node` is the name of the
|
||||
head node of the list and `graph` is the `RDF.Graph` with the statements
|
||||
constituting the list.
|
||||
If the given node does not refer to a well-formed list in the graph, `nil` is
|
||||
returned. A well-formed list
|
||||
|
||||
By default the statements these statements are added to an empty graph. An
|
||||
- consists of list nodes which have exactly one `rdf:first` and `rdf:rest`
|
||||
statement each
|
||||
- does not contain cycles, i.e. `rdf:rest` statements don't refer to
|
||||
preceding list nodes
|
||||
"""
|
||||
def new(head, graph)
|
||||
|
||||
def new(head, graph) when is_atom(head) and not head in ~w[true false nil]a,
|
||||
do: new(RDF.uri(head), graph)
|
||||
|
||||
def new(head, graph) do
|
||||
with list = %RDF.List{head: head, graph: graph} do
|
||||
if well_formed?(list) do
|
||||
list
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp well_formed?(list) do
|
||||
Enum.reduce_while(list, MapSet.new, fn node_description, preceding_nodes ->
|
||||
with head = node_description.subject do
|
||||
if MapSet.member?(preceding_nodes, head) do
|
||||
{:halt, false}
|
||||
else
|
||||
{:cont, MapSet.put(preceding_nodes, head)}
|
||||
end
|
||||
end
|
||||
end) && true
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Creates a `RDF.List` from a native Elixir list or any other `Enumerable` with convertible RDF values.
|
||||
|
||||
By default the statements constituting the `Enumerable` are added to an empty graph. An
|
||||
already existing graph to which the statements are added can be specified with
|
||||
the `graph` option.
|
||||
|
||||
The name of the head node can be specified with the `head` option
|
||||
(default: `RDF.bnode()`, i.e. an arbitrary unique name).
|
||||
Note: When the given list is empty, the `name` option will be ignored - the
|
||||
head node of the empty list is always `RDF.nil`.
|
||||
Note: When the given `Enumerable` is empty, the `name` option will be ignored -
|
||||
the head node of the empty list is always `RDF.nil`.
|
||||
|
||||
"""
|
||||
def new(list, opts \\ []) do
|
||||
def from(list, opts \\ []) do
|
||||
with head = Keyword.get(opts, :head, RDF.bnode),
|
||||
graph = Keyword.get(opts, :graph, RDF.graph),
|
||||
do: do_new(list, head, graph, opts)
|
||||
end
|
||||
|
||||
defp do_new([], _, graph, _) do
|
||||
{RDF.nil, graph}
|
||||
end
|
||||
|
||||
defp do_new(list, head, graph, opts) when is_atom(head) do
|
||||
do_new(list, RDF.uri(head), graph, opts)
|
||||
end
|
||||
|
||||
defp do_new([list | rest], head, graph, opts) when is_list(list) do
|
||||
with {nested_list_node, graph} = do_new(list, RDF.bnode, graph, opts) do
|
||||
do_new([nested_list_node | rest], head, graph, opts)
|
||||
{head, graph} = do_from(list, head, graph, opts)
|
||||
do
|
||||
%RDF.List{head: head, graph: graph}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_new([first | rest], head, graph, opts) do
|
||||
with {next, graph} = do_new(rest, RDF.bnode, graph, opts) do
|
||||
defp do_from([], _, graph, _) do
|
||||
{RDF.nil, graph}
|
||||
end
|
||||
|
||||
defp do_from(list, head, graph, opts) when is_atom(head) do
|
||||
do_from(list, RDF.uri(head), graph, opts)
|
||||
end
|
||||
|
||||
defp do_from([list | rest], head, graph, opts) when is_list(list) do
|
||||
with {nested_list_node, graph} = do_from(list, RDF.bnode, graph, opts) do
|
||||
do_from([nested_list_node | rest], head, graph, opts)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_from([first | rest], head, graph, opts) do
|
||||
with {next, graph} = do_from(rest, RDF.bnode, graph, opts) do
|
||||
{
|
||||
head,
|
||||
Graph.add(graph,
|
||||
|
@ -61,52 +99,67 @@ defmodule RDF.List do
|
|||
end
|
||||
end
|
||||
|
||||
def new!(list, opts \\ []) do
|
||||
with {_, graph} = new(list, opts), do: graph
|
||||
defp do_from(enumerable, head, graph, opts) do
|
||||
enumerable
|
||||
|> Enum.into([])
|
||||
|> do_from(head, graph, opts)
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Converts a RDF list from a graph to native Elixir list.
|
||||
The values of a `RDF.List` as an Elixir list.
|
||||
|
||||
Except for nested lists, the values of the list are not further converted, but
|
||||
returned as RDF types, i.e. `RDF.Literal`s etc.
|
||||
|
||||
When the given node does not refer to a valid list in the given graph the
|
||||
function returns `nil`.
|
||||
Nested lists are converted recursively.
|
||||
"""
|
||||
def to_native(list_node, graph)
|
||||
def to_native(@rdf_nil, _), do: []
|
||||
|
||||
def to_native(list_node, graph) do
|
||||
if valid?(list_node, graph) do # TODO: This is not very efficient, we're traversing the list twice ...
|
||||
do_to_native(list_node, graph)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp do_to_native(list_node, graph, acc \\ []) do
|
||||
with description when not is_nil(description) <-
|
||||
Graph.description(graph, list_node),
|
||||
[first] <- Description.get(description, RDF.first),
|
||||
[rest] <- Description.get(description, RDF.rest)
|
||||
do
|
||||
first = if node?(first, graph), do: to_native(first, graph), else: first
|
||||
if rest == RDF.nil do
|
||||
[first | acc] |> Enum.reverse
|
||||
def values(%RDF.List{graph: graph} = list) do
|
||||
Enum.map list, fn node_description ->
|
||||
value = Description.first(node_description, RDF.first)
|
||||
if node?(value, graph) do
|
||||
value
|
||||
|> new(graph)
|
||||
|> values
|
||||
else
|
||||
do_to_native(rest, graph, [first | acc])
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the given resource is a RDF list node in the given graph.
|
||||
The RDF nodes constituting a `RDF.List`` as an Elixir list.
|
||||
"""
|
||||
def nodes(%RDF.List{} = list) do
|
||||
Enum.map list, fn node_description -> node_description.subject end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if a list is the empty list.
|
||||
"""
|
||||
def empty?(%RDF.List{head: @rdf_nil}), do: true
|
||||
def empty?(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the given list consists of list nodes which are all blank nodes.
|
||||
"""
|
||||
def valid?(%RDF.List{head: @rdf_nil}), do: true
|
||||
|
||||
def valid?(list) do
|
||||
Enum.all? list, fn node_description ->
|
||||
RDF.bnode?(node_description.subject)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if a given resource is a RDF list node in a given `RDF.Graph`.
|
||||
|
||||
Although, technically a resource is a list, if it uses at least one `rdf:first`
|
||||
or `rdf:rest`, we pragmatically require the usage of both.
|
||||
|
||||
Note: This function doesn't indicate if the list is valid. See `valid?/2` for that.
|
||||
Note: This function doesn't indicate if the list is valid.
|
||||
See `new/2` and valid?/2` for validations.
|
||||
"""
|
||||
def node?(list_node, graph)
|
||||
|
||||
|
@ -141,39 +194,49 @@ defmodule RDF.List do
|
|||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if the given resource is a valid RDF list in the given graph.
|
||||
defimpl Enumerable do
|
||||
@rdf_nil RDF.nil
|
||||
|
||||
A valid list
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
|
||||
- consists of list nodes which are all blank nodes and have exactly one
|
||||
`rdf:first` and `rdf:rest` statement each
|
||||
- does not contain any circles, i.e. `rdf:rest` statements don't refer to
|
||||
preceding list nodes
|
||||
"""
|
||||
def valid?(list_node, graph)
|
||||
def valid?(@rdf_nil, _), do: true
|
||||
def valid?(list_node, graph), do: do_valid?(list_node, graph)
|
||||
def reduce(%RDF.List{head: @rdf_nil}, {:cont, acc}, _fun),
|
||||
do: {:done, acc}
|
||||
|
||||
defp do_valid?(list, graph, preceding_nodes \\ MapSet.new)
|
||||
def reduce(%RDF.List{head: %BlankNode{}} = list, acc, fun),
|
||||
do: do_reduce(list, acc, fun)
|
||||
|
||||
defp do_valid?(%BlankNode{} = list, graph, preceding_nodes) do
|
||||
with description when not is_nil(description) <-
|
||||
Graph.description(graph, list),
|
||||
[first] <- Description.get(description, RDF.first),
|
||||
[rest] <- Description.get(description, RDF.rest)
|
||||
do
|
||||
cond do
|
||||
rest == @rdf_nil -> true
|
||||
MapSet.member?(preceding_nodes, list) -> false
|
||||
true -> do_valid?(rest, graph, MapSet.put(preceding_nodes, list))
|
||||
def reduce(%RDF.List{head: %URI{}} = list, acc, fun),
|
||||
do: do_reduce(list, acc, fun)
|
||||
|
||||
def reduce(_, _, _), do: {:halted, nil}
|
||||
|
||||
defp do_reduce(%RDF.List{head: head, graph: graph},
|
||||
{:cont, acc}, fun) do
|
||||
with description when not is_nil(description) <-
|
||||
Graph.description(graph, head),
|
||||
[_] <- Description.get(description, RDF.first),
|
||||
[rest] <- Description.get(description, RDF.rest),
|
||||
acc = fun.(description, acc)
|
||||
do
|
||||
if rest == @rdf_nil do
|
||||
case acc do
|
||||
{:cont, acc} -> {:done, acc}
|
||||
# TODO: Is the :suspend case handled properly
|
||||
_ -> reduce(%RDF.List{head: rest, graph: graph}, acc, fun)
|
||||
end
|
||||
else
|
||||
reduce(%RDF.List{head: rest, graph: graph}, acc, fun)
|
||||
end
|
||||
else
|
||||
nil ->
|
||||
{:halted, nil}
|
||||
values when is_list(values) ->
|
||||
{:halted, nil}
|
||||
end
|
||||
else
|
||||
nil -> false
|
||||
list when is_list(list) -> false
|
||||
end
|
||||
|
||||
def member?(_, _), do: {:error, __MODULE__}
|
||||
def count(_), do: {:error, __MODULE__}
|
||||
end
|
||||
|
||||
defp do_valid?(_, _, _), do: false
|
||||
|
||||
end
|
||||
|
|
|
@ -7,8 +7,6 @@ defmodule RDF.ListTest do
|
|||
|
||||
alias RDF.{BlankNode, Literal, Graph}
|
||||
|
||||
# alias RDF.NS.{XSD}
|
||||
|
||||
use RDF.Vocabulary.Namespace
|
||||
|
||||
defvocab EX,
|
||||
|
@ -17,20 +15,153 @@ defmodule RDF.ListTest do
|
|||
|
||||
setup do
|
||||
{:ok,
|
||||
one: RDF.list!([EX.element], head: ~B<one>),
|
||||
abc: RDF.list!(~w[a b c], head: ~B<abc>),
|
||||
ten: RDF.list!(Enum.to_list(1..10), head: ~B<ten>),
|
||||
nested: RDF.list!(["foo", [1, 2], "bar"], head: ~B<nested>),
|
||||
empty: RDF.List.new(RDF.nil, Graph.new),
|
||||
one: RDF.List.from([EX.element], head: ~B<one>),
|
||||
abc: RDF.List.from(~w[a b c], head: ~B<abc>),
|
||||
ten: RDF.List.from(Enum.to_list(1..10), head: ~B<ten>),
|
||||
nested: RDF.List.from(["foo", [1, 2], "bar"], head: ~B<nested>),
|
||||
}
|
||||
end
|
||||
|
||||
describe "new/1" do
|
||||
test "an empty anonymous list" do
|
||||
assert RDF.List.new([]) == {RDF.nil, Graph.new}
|
||||
|
||||
describe "new/2" do
|
||||
|
||||
#######################################################################
|
||||
# success cases
|
||||
|
||||
test "valid head list node" do
|
||||
graph = Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
assert %RDF.List{} = list = RDF.List.new(~B<Foo>, graph)
|
||||
assert list.head == ~B<Foo>
|
||||
assert list.graph == graph
|
||||
end
|
||||
|
||||
test "an empty named list" do
|
||||
assert RDF.List.new([], name: ~B<foo>) == {RDF.nil, Graph.new}
|
||||
test "with non-blank list nodes" do
|
||||
graph = Graph.new(
|
||||
EX.Foo
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(RDF.nil))
|
||||
assert %RDF.List{} = list = RDF.List.new(EX.Foo, graph)
|
||||
assert list.head == RDF.uri(EX.Foo)
|
||||
end
|
||||
|
||||
test "with other properties on its nodes" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> EX.other(EX.Property)
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> EX.other(EX.Property2)
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
)
|
||||
|> RDF.List.valid? == true
|
||||
end
|
||||
|
||||
#######################################################################
|
||||
# failure cases
|
||||
|
||||
test "when given list node doesn't exist in the given graph" do
|
||||
assert RDF.List.new(RDF.bnode, RDF.Graph.new) == nil
|
||||
end
|
||||
|
||||
test "When the given head node is not a list" do
|
||||
assert RDF.List.new(42, RDF.Graph.new) == nil
|
||||
assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar, EX.Baz})) == nil
|
||||
assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first, EX.Baz})) == nil
|
||||
end
|
||||
|
||||
|
||||
test "when list nodes are incomplete" do
|
||||
assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first, EX.Baz})) == nil
|
||||
assert RDF.List.new(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest, RDF.nil})) == nil
|
||||
end
|
||||
|
||||
test "when head node has multiple rdf:first objects" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1, 2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == nil
|
||||
end
|
||||
|
||||
test "when later list nodes have multiple rdf:first objects" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2, 3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == nil
|
||||
end
|
||||
|
||||
test "when list nodes have multiple rdf:rest objects" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>, ~B<Baz>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
|> Graph.add(
|
||||
~B<Baz>
|
||||
|> RDF.first(3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == nil
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil, ~B<Baz>))
|
||||
|> Graph.add(
|
||||
~B<Baz>
|
||||
|> RDF.first(3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == nil
|
||||
end
|
||||
|
||||
test "when the list is cyclic" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(~B<Foo>))
|
||||
) == nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "from/1" do
|
||||
test "an empty list", %{empty: empty} do
|
||||
assert RDF.List.from([]) == empty
|
||||
end
|
||||
|
||||
test "an empty list with named head node", %{empty: empty} do
|
||||
assert RDF.List.from([], name: ~B<foo>) == empty
|
||||
end
|
||||
|
||||
%{
|
||||
|
@ -48,19 +179,22 @@ defmodule RDF.ListTest do
|
|||
@tag element: element
|
||||
test "list with #{type} element", %{element: element} do
|
||||
with {bnode, graph_with_list} = one_element_list(element) do
|
||||
assert {bnode, graph_with_list} == RDF.List.new([element], head: bnode)
|
||||
assert RDF.List.from([element], head: bnode) ==
|
||||
RDF.List.new(bnode, graph_with_list)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
test "nested list" do
|
||||
assert {bnode, graph_with_list} = RDF.List.new([[1]])
|
||||
assert %RDF.List{head: bnode, graph: graph_with_list} =
|
||||
RDF.List.from([[1]])
|
||||
assert [nested] = get_in(graph_with_list, [bnode, RDF.first])
|
||||
assert get_in(graph_with_list, [bnode, RDF.rest]) == [RDF.nil]
|
||||
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.Integer.new(1)]
|
||||
assert get_in(graph_with_list, [nested, RDF.rest]) == [RDF.nil]
|
||||
|
||||
assert {bnode, graph_with_list} = RDF.List.new(["foo", [1, 2], "bar"])
|
||||
assert %RDF.List{head: bnode, graph: graph_with_list} =
|
||||
RDF.List.from(["foo", [1, 2], "bar"])
|
||||
assert get_in(graph_with_list, [bnode, RDF.first]) == [~L"foo"]
|
||||
assert [second] = get_in(graph_with_list, [bnode, RDF.rest])
|
||||
assert [nested] = get_in(graph_with_list, [second, RDF.first])
|
||||
|
@ -80,7 +214,8 @@ defmodule RDF.ListTest do
|
|||
|> Enum.each(fn {desc, list} ->
|
||||
@tag list: list
|
||||
test "list with multiple elements: #{desc}", %{list: list} do
|
||||
assert {bnode, graph_with_list} = RDF.List.new(list)
|
||||
assert %RDF.List{head: bnode, graph: graph_with_list} =
|
||||
RDF.List.from(list)
|
||||
assert RDF.nil ==
|
||||
Enum.reduce list, bnode, fn element, list_node ->
|
||||
case element do
|
||||
|
@ -106,80 +241,129 @@ defmodule RDF.ListTest do
|
|||
end
|
||||
end)
|
||||
|
||||
test "an enumerable" do
|
||||
assert RDF.List.from(MapSet.new([42]), head: ~B<foo>) ==
|
||||
RDF.List.from([42], head: ~B<foo>)
|
||||
end
|
||||
|
||||
test "head option with unresolved namespace-qualified name" do
|
||||
assert {uri, _} = RDF.List.new([42], head: EX.Foo)
|
||||
assert uri == RDF.uri(EX.Foo)
|
||||
assert RDF.List.from([42], head: EX.Foo).head == RDF.uri(EX.Foo)
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_native/1" do
|
||||
test "the empty list" do
|
||||
assert RDF.List.to_native(RDF.nil, RDF.Graph.new) == []
|
||||
|
||||
describe "values/1" do
|
||||
test "the empty list", %{empty: empty} do
|
||||
assert RDF.List.values(empty) == []
|
||||
end
|
||||
|
||||
test "list with one element", %{one: one} do
|
||||
assert RDF.List.to_native(~B<one>, one) == [EX.element]
|
||||
assert RDF.List.values(one) == [EX.element]
|
||||
end
|
||||
|
||||
test "list with multiple elements", %{abc: abc, ten: ten} do
|
||||
assert RDF.List.to_native(~B<abc>, abc) == ~w[a b c] |> Enum.map(&Literal.new/1)
|
||||
assert RDF.List.to_native(~B<ten>, ten) == 1..10 |> Enum.to_list |> Enum.map(&Literal.new/1)
|
||||
assert RDF.List.values(abc) == ~w[a b c] |> Enum.map(&Literal.new/1)
|
||||
assert RDF.List.values(ten) == 1..10 |> Enum.to_list |> Enum.map(&Literal.new/1)
|
||||
end
|
||||
|
||||
test "list with non-blank list nodes" do
|
||||
assert RDF.List.from([EX.element], head: EX.Foo)
|
||||
|> RDF.List.values == [EX.element]
|
||||
end
|
||||
|
||||
test "nested list", %{nested: nested} do
|
||||
assert RDF.List.to_native(~B<nested>, nested) ==
|
||||
assert RDF.List.values(nested) ==
|
||||
[~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)], ~L"bar"]
|
||||
|
||||
{head, graph} = RDF.list(["foo", [1, 2]])
|
||||
assert RDF.List.to_native(head, graph) ==
|
||||
assert RDF.list(["foo", [1, 2]]) |> RDF.List.values ==
|
||||
[~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
|
||||
{head, graph} = RDF.list([[1, 2], "foo"])
|
||||
assert RDF.List.to_native(head, graph) ==
|
||||
assert RDF.list([[1, 2], "foo"]) |> RDF.List.values ==
|
||||
[[RDF.Integer.new(1), RDF.Integer.new(2)], ~L"foo"]
|
||||
end
|
||||
|
||||
test "when given list node doesn't exist in the given graph it returns nil" do
|
||||
assert RDF.List.to_native(RDF.bnode, RDF.Graph.new) == nil
|
||||
end
|
||||
|
||||
test "When the given list node is not a list it returns nil" do
|
||||
assert RDF.List.to_native(42, RDF.Graph.new) == nil
|
||||
assert RDF.List.to_native(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar, EX.Baz})) == nil
|
||||
assert RDF.List.to_native(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first, EX.Baz})) == nil
|
||||
end
|
||||
|
||||
test "When the given list node is not a valid list it returns nil" do
|
||||
assert RDF.List.to_native(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1, 2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == nil
|
||||
assert RDF.List.to_native(RDF.uri(EX.Foo), RDF.list!([EX.element], head: EX.Foo)) == nil
|
||||
inner_list = RDF.list([1, 2], head: ~B<inner>)
|
||||
assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph)
|
||||
|> RDF.List.values == [~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
end
|
||||
end
|
||||
|
||||
describe "node?" do
|
||||
test "the empty list" do
|
||||
assert RDF.List.node?(RDF.nil, Graph.new) == true
|
||||
|
||||
describe "nodes/1" do
|
||||
test "the empty list", %{empty: empty} do
|
||||
assert RDF.List.nodes(empty) == []
|
||||
end
|
||||
|
||||
test "list with one element", %{one: one} do
|
||||
assert RDF.List.node?(~B<one>, one) == true
|
||||
end
|
||||
|
||||
test "list with multiple elements", %{abc: abc, ten: ten} do
|
||||
assert RDF.List.node?(~B<abc>, abc) == true
|
||||
assert RDF.List.node?(~B<ten>, ten) == true
|
||||
test "non-empty list", %{one: one} do
|
||||
assert RDF.List.nodes(one) == [~B<one>]
|
||||
end
|
||||
|
||||
test "nested list", %{nested: nested} do
|
||||
assert RDF.List.node?(~B<nested>, nested) == true
|
||||
assert RDF.list([[1, 2, 3]], head: ~B<outer>)
|
||||
|> RDF.List.nodes == [~B<outer>]
|
||||
assert [~B<nested>, _, _] = RDF.List.nodes(nested)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "valid?/2" do
|
||||
test "the empty list", %{empty: empty} do
|
||||
assert RDF.List.valid?(empty)
|
||||
end
|
||||
|
||||
test "valid list with one element", %{one: one} do
|
||||
assert RDF.List.valid?(one) == true
|
||||
end
|
||||
|
||||
test "valid list with multiple elements", %{abc: abc, ten: ten} do
|
||||
assert RDF.List.valid?(abc) == true
|
||||
assert RDF.List.valid?(ten) == true
|
||||
end
|
||||
|
||||
test "valid nested list", %{nested: nested} do
|
||||
assert RDF.List.valid?(nested) == true
|
||||
end
|
||||
|
||||
test "a non-blank list node is not valid" do
|
||||
assert RDF.list([EX.element], head: EX.Foo) |> RDF.List.valid? == false
|
||||
end
|
||||
|
||||
test "a non-blank list node on later nodes makes the whole list invalid" do
|
||||
assert RDF.List.new(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(EX.Foo))
|
||||
|> Graph.add(
|
||||
EX.Foo
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
)
|
||||
|> RDF.List.valid? == false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "node?" do
|
||||
test "the empty list", %{empty: empty} do
|
||||
assert RDF.List.node?(empty.head, empty.graph) == true
|
||||
end
|
||||
|
||||
test "list with one element", %{one: one} do
|
||||
assert RDF.List.node?(one.head, one.graph) == true
|
||||
end
|
||||
|
||||
test "list with multiple elements", %{abc: abc, ten: ten} do
|
||||
assert RDF.List.node?(abc.head, abc.graph) == true
|
||||
assert RDF.List.node?(ten.head, ten.graph) == true
|
||||
end
|
||||
|
||||
test "nested list", %{nested: nested} do
|
||||
assert RDF.List.node?(nested.head, nested.graph) == true
|
||||
end
|
||||
|
||||
test "unresolved namespace-qualified name" do
|
||||
assert RDF.List.node?(EX.Foo, RDF.list!([EX.element], head: EX.Foo)) == true
|
||||
assert RDF.List.node?(EX.Foo,
|
||||
RDF.List.from([EX.element], head: EX.Foo).graph) == true
|
||||
end
|
||||
|
||||
test "when given list node doesn't exist in the given graph" do
|
||||
|
@ -205,131 +389,16 @@ defmodule RDF.ListTest do
|
|||
end
|
||||
|
||||
|
||||
describe "valid?/2" do
|
||||
test "the empty list" do
|
||||
assert RDF.List.valid?(RDF.nil, Graph.new)
|
||||
describe "Enumerable.reduce" do
|
||||
test "the empty list", %{empty: empty} do
|
||||
assert Enum.reduce(empty, [], fn description, acc -> [description | acc] end) == []
|
||||
end
|
||||
|
||||
test "valid list with one element", %{one: one} do
|
||||
assert RDF.List.valid?(~B<one>, one) == true
|
||||
test "a valid list", %{one: one} do
|
||||
assert [one.graph[one.head]] ==
|
||||
Enum.reduce(one, [], fn description, acc -> [description | acc] end)
|
||||
end
|
||||
|
||||
test "valid list with multiple elements", %{abc: abc, ten: ten} do
|
||||
assert RDF.List.valid?(~B<abc>, abc) == true
|
||||
assert RDF.List.valid?(~B<ten>, ten) == true
|
||||
end
|
||||
|
||||
test "valid nested list", %{nested: nested} do
|
||||
assert RDF.List.valid?(~B<nested>, nested) == true
|
||||
end
|
||||
|
||||
test "a list with other properties on its nodes is valid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> EX.other(EX.Property)
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> EX.other(EX.Property2)
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == true
|
||||
end
|
||||
|
||||
test "incomplete list nodes are invalid" do
|
||||
assert RDF.List.valid?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first, EX.Baz})) == false
|
||||
assert RDF.List.valid?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest, RDF.nil})) == false
|
||||
end
|
||||
|
||||
test "a list with multiple rdf:first object is not valid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1, 2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == false
|
||||
end
|
||||
|
||||
test "a list with multiple rdf:first object on later nodes is not valid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2, 3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == false
|
||||
end
|
||||
|
||||
test "a list with multiple rdf:rest object is not valid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>, ~B<Baz>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
|> Graph.add(
|
||||
~B<Baz>
|
||||
|> RDF.first(3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == false
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil, ~B<Baz>))
|
||||
|> Graph.add(
|
||||
~B<Baz>
|
||||
|> RDF.first(3)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == false
|
||||
end
|
||||
|
||||
test "a list with circles is not valid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(~B<Bar>))
|
||||
|> Graph.add(
|
||||
~B<Bar>
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(~B<Foo>))
|
||||
) == false
|
||||
end
|
||||
|
||||
test "a non-blank list node is not valid" do
|
||||
assert RDF.List.valid?(RDF.uri(EX.Foo), RDF.list!([EX.element], head: EX.Foo)) == false
|
||||
end
|
||||
|
||||
test "a non-blank list node on later nodes makes the whole list invalid" do
|
||||
assert RDF.List.valid?(~B<Foo>,
|
||||
Graph.new(
|
||||
~B<Foo>
|
||||
|> RDF.first(1)
|
||||
|> RDF.rest(EX.Foo))
|
||||
|> Graph.add(
|
||||
EX.Foo
|
||||
|> RDF.first(2)
|
||||
|> RDF.rest(RDF.nil))
|
||||
) == false
|
||||
end
|
||||
|
||||
test "when the given list node doesn't exsist in the given graph" do
|
||||
assert RDF.List.valid?(RDF.bnode, RDF.Graph.new) == false
|
||||
assert RDF.List.valid?(RDF.bnode, RDF.Graph.new) == false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue