Revise RDF.List

This commit is contained in:
Marcel Otto 2017-07-31 23:21:09 +02:00
parent be36ae085d
commit 2f6fa6fe34
4 changed files with 436 additions and 288 deletions

View file

@ -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/

View file

@ -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?

View file

@ -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

View file

@ -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