Apply mix formatter

This commit is contained in:
Marcel Otto 2020-06-29 10:37:42 +02:00
parent c880026224
commit 0e81f4c02c
129 changed files with 7078 additions and 5763 deletions

View file

@ -1,5 +1,7 @@
locals_without_parens = [
defvocab: 2,
def_facet_constraint: 2,
def_applicable_facet: 1,
bgp: 1
]

View file

@ -2,46 +2,63 @@ defmodule NS do
use RDF.Vocabulary.Namespace
defvocab MF,
base_iri: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
terms: [], strict: false
base_iri: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
terms: [],
strict: false
defvocab RDFT,
base_iri: "http://www.w3.org/ns/rdftest#",
terms: [], strict: false
defvocab RDFT, base_iri: "http://www.w3.org/ns/rdftest#", terms: [], strict: false
end
alias NS.{MF, RDFT}
alias RDF.NS.RDFS
alias RDF.Query.BGP
test_graph = RDF.Turtle.read_file!("test/data/TURTLE-TESTS/manifest.ttl", base: "http://www.w3.org/2013/TurtleTests/")
test_graph =
RDF.Turtle.read_file!("test/data/TURTLE-TESTS/manifest.ttl",
base: "http://www.w3.org/2013/TurtleTests/"
)
all_query = %BGP{triple_patterns: [{:s, :p, :o}]}
Benchee.run(%{
"take 1 from BGP.Simple" => fn -> BGP.Simple.stream(all_query, test_graph) |> Enum.take(1) end,
"take 1 from BGP.Stream" => fn -> BGP.Stream.stream(all_query, test_graph) |> Enum.take(1) end,
"take 1 from BGP.Stream" => fn -> BGP.Stream.stream(all_query, test_graph) |> Enum.take(1) end
})
# rdft:approval rdft:Approved - count: 287
approved_query = %BGP{triple_patterns: [
{:test_case, RDFT.approval, RDF.iri(RDFT.Approved)},
{:test_case, MF.name, :name},
{:test_case, RDFS.comment, :comment},
]}
approved_query = %BGP{
triple_patterns: [
{:test_case, RDFT.approval(), RDF.iri(RDFT.Approved)},
{:test_case, MF.name(), :name},
{:test_case, RDFS.comment(), :comment}
]
}
# rdft:approval rdft:Proposed - count: 4
proposed_query = %BGP{triple_patterns: [
{:test_case, RDFT.approval, RDF.iri(RDFT.Proposed)},
{:test_case, MF.name, :name},
{:test_case, RDFS.comment, :comment},
]}
proposed_query = %BGP{
triple_patterns: [
{:test_case, RDFT.approval(), RDF.iri(RDFT.Proposed)},
{:test_case, MF.name(), :name},
{:test_case, RDFS.comment(), :comment}
]
}
Benchee.run(%{
"APPROVED from BGP.Simple" => fn -> BGP.Simple.execute(approved_query, test_graph) end,
"PROPOSED from BGP.Simple" => fn -> BGP.Simple.execute(proposed_query, test_graph) end,
"APPROVED from BGP.Stream (consumed)" => fn -> BGP.Stream.execute(approved_query, test_graph) end,
"PROPOSED from BGP.Stream (consumed)" => fn -> BGP.Stream.execute(proposed_query, test_graph) end,
"APPROVED from BGP.Stream (unconsumed)" => fn -> BGP.Stream.stream(approved_query, test_graph) end,
"PROPOSED from BGP.Stream (unconsumed)" => fn -> BGP.Stream.stream(proposed_query, test_graph) end,
"APPROVED from BGP.Stream (1 consumed)" => fn -> BGP.Stream.stream(approved_query, test_graph) |> Enum.take(1) end
"APPROVED from BGP.Stream (consumed)" => fn ->
BGP.Stream.execute(approved_query, test_graph)
end,
"PROPOSED from BGP.Stream (consumed)" => fn ->
BGP.Stream.execute(proposed_query, test_graph)
end,
"APPROVED from BGP.Stream (unconsumed)" => fn ->
BGP.Stream.stream(approved_query, test_graph)
end,
"PROPOSED from BGP.Stream (unconsumed)" => fn ->
BGP.Stream.stream(proposed_query, test_graph)
end,
"APPROVED from BGP.Stream (1 consumed)" => fn ->
BGP.Stream.stream(approved_query, test_graph) |> Enum.take(1)
end
})

View file

@ -40,18 +40,27 @@ defmodule RDF do
For a general introduction you may refer to the guides on the [homepage](https://rdf-elixir.dev).
"""
alias RDF.{IRI, Namespace, Literal, BlankNode, Triple, Quad,
Description, Graph, Dataset, PrefixMap}
alias RDF.{
IRI,
Namespace,
Literal,
BlankNode,
Triple,
Quad,
Description,
Graph,
Dataset,
PrefixMap
}
import RDF.Guards
import RDF.Utils.Bootstrapping
defdelegate default_base_iri(), to: RDF.IRI, as: :default_base
@standard_prefixes PrefixMap.new(
xsd: xsd_iri_base(),
rdf: rdf_iri_base(),
xsd: xsd_iri_base(),
rdf: rdf_iri_base(),
rdfs: rdfs_iri_base()
)
@ -67,7 +76,6 @@ defmodule RDF do
"""
def standard_prefixes(), do: @standard_prefixes
@doc """
A user-defined `RDF.PrefixMap` of prefixes to IRI namespaces.
@ -114,16 +122,15 @@ defmodule RDF do
default_prefixes() |> PrefixMap.merge!(prefix_mappings)
end
defdelegate read_string(content, opts), to: RDF.Serialization
defdelegate read_string!(content, opts), to: RDF.Serialization
defdelegate read_file(filename, opts \\ []), to: RDF.Serialization
defdelegate read_file!(filename, opts \\ []), to: RDF.Serialization
defdelegate write_string(content, opts), to: RDF.Serialization
defdelegate write_string!(content, opts), to: RDF.Serialization
defdelegate write_file(content, filename, opts \\ []), to: RDF.Serialization
defdelegate read_string(content, opts), to: RDF.Serialization
defdelegate read_string!(content, opts), to: RDF.Serialization
defdelegate read_file(filename, opts \\ []), to: RDF.Serialization
defdelegate read_file!(filename, opts \\ []), to: RDF.Serialization
defdelegate write_string(content, opts), to: RDF.Serialization
defdelegate write_string!(content, opts), to: RDF.Serialization
defdelegate write_file(content, filename, opts \\ []), to: RDF.Serialization
defdelegate write_file!(content, filename, opts \\ []), to: RDF.Serialization
@doc """
Checks if the given value is a RDF resource.
@ -149,6 +156,7 @@ defmodule RDF do
def resource?(value)
def resource?(%IRI{}), do: true
def resource?(%BlankNode{}), do: true
def resource?(qname) when maybe_ns_term(qname) do
case Namespace.resolve_term(qname) do
{:ok, iri} -> resource?(iri)
@ -182,12 +190,12 @@ defmodule RDF do
"""
def term?(value)
def term?(%Literal{}), do: true
def term?(value), do: resource?(value)
def term?(value), do: resource?(value)
defdelegate uri?(value), to: IRI, as: :valid?
defdelegate iri?(value), to: IRI, as: :valid?
defdelegate uri(value), to: IRI, as: :new
defdelegate iri(value), to: IRI, as: :new
defdelegate uri(value), to: IRI, as: :new
defdelegate iri(value), to: IRI, as: :new
defdelegate uri!(value), to: IRI, as: :new!
defdelegate iri!(value), to: IRI, as: :new!
@ -206,7 +214,7 @@ defmodule RDF do
def bnode?(%BlankNode{}), do: true
def bnode?(_), do: false
defdelegate bnode(), to: BlankNode, as: :new
defdelegate bnode(), to: BlankNode, as: :new
defdelegate bnode(id), to: BlankNode, as: :new
@doc """
@ -215,59 +223,59 @@ defmodule RDF do
def literal?(%Literal{}), do: true
def literal?(_), do: false
defdelegate literal(value), to: Literal, as: :new
defdelegate literal(value), to: Literal, as: :new
defdelegate literal(value, opts), to: Literal, as: :new
defdelegate triple(s, p, o), to: Triple, as: :new
defdelegate triple(tuple), to: Triple, as: :new
defdelegate triple(s, p, o), to: Triple, as: :new
defdelegate triple(tuple), to: Triple, as: :new
defdelegate quad(s, p, o, g), to: Quad, as: :new
defdelegate quad(tuple), to: Quad, as: :new
defdelegate quad(tuple), to: Quad, as: :new
defdelegate description(arg), to: Description, as: :new
defdelegate description(arg1, arg2), to: Description, as: :new
defdelegate description(arg1, arg2, arg3), to: Description, as: :new
defdelegate description(arg), to: Description, as: :new
defdelegate description(arg1, arg2), to: Description, as: :new
defdelegate description(arg1, arg2, arg3), to: Description, as: :new
defdelegate graph(), to: Graph, as: :new
defdelegate graph(arg), to: Graph, as: :new
defdelegate graph(arg1, arg2), to: Graph, as: :new
defdelegate graph(arg1, arg2, arg3), to: Graph, as: :new
defdelegate graph(arg1, arg2, arg3, arg4), to: Graph, as: :new
defdelegate graph(), to: Graph, as: :new
defdelegate graph(arg), to: Graph, as: :new
defdelegate graph(arg1, arg2), to: Graph, as: :new
defdelegate graph(arg1, arg2, arg3), to: Graph, as: :new
defdelegate graph(arg1, arg2, arg3, arg4), to: Graph, as: :new
defdelegate dataset(), to: Dataset, as: :new
defdelegate dataset(arg), to: Dataset, as: :new
defdelegate dataset(arg1, arg2), to: Dataset, as: :new
defdelegate dataset(), to: Dataset, as: :new
defdelegate dataset(arg), to: Dataset, as: :new
defdelegate dataset(arg1, arg2), to: Dataset, as: :new
defdelegate diff(arg1, arg2), to: RDF.Diff
defdelegate list?(resource, graph), to: RDF.List, as: :node?
defdelegate list?(description), to: RDF.List, as: :node?
defdelegate list?(description), to: RDF.List, as: :node?
def list(native_list), do: RDF.List.from(native_list)
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)
def list(native_list, opts), do: RDF.List.from(native_list, opts)
defdelegate prefix_map(prefixes), to: RDF.PrefixMap, as: :new
defdelegate langString(value, opts), to: RDF.LangString, as: :new
defdelegate langString(value, opts), to: RDF.LangString, as: :new
defdelegate lang_string(value, opts), to: RDF.LangString, as: :new
for term <- ~w[type subject predicate object first rest value]a do
defdelegate unquote(term)(), to: RDF.NS.RDF
defdelegate unquote(term)(), to: RDF.NS.RDF
@doc false
defdelegate unquote(term)(s, o), to: RDF.NS.RDF
defdelegate unquote(term)(s, o), to: RDF.NS.RDF
@doc false
defdelegate unquote(term)(s, o1, o2), to: RDF.NS.RDF
defdelegate unquote(term)(s, o1, o2), to: RDF.NS.RDF
@doc false
defdelegate unquote(term)(s, o1, o2, o3), to: RDF.NS.RDF
defdelegate unquote(term)(s, o1, o2, o3), to: RDF.NS.RDF
@doc false
defdelegate unquote(term)(s, o1, o2, o3, o4), to: RDF.NS.RDF
defdelegate unquote(term)(s, o1, o2, o3, o4), to: RDF.NS.RDF
@doc false
defdelegate unquote(term)(s, o1, o2, o3, o4, o5), to: RDF.NS.RDF
end
defdelegate langString(), to: RDF.NS.RDF
defdelegate lang_string(), to: RDF.NS.RDF, as: :langString
defdelegate langString(), to: RDF.NS.RDF
defdelegate lang_string(), to: RDF.NS.RDF, as: :langString
defdelegate unquote(nil)(), to: RDF.NS.RDF
defdelegate __base_iri__(), to: RDF.NS.RDF

View file

@ -7,8 +7,8 @@ defmodule RDF.BlankNode do
"""
@type t :: %__MODULE__{
id: String.t
}
id: String.t()
}
@enforce_keys [:id]
defstruct [:id]
@ -28,19 +28,18 @@ defmodule RDF.BlankNode do
iex> RDF.bnode(:foo)
%RDF.BlankNode{id: "foo"}
"""
@spec new(reference | String.t | atom | integer) :: t
@spec new(reference | String.t() | atom | integer) :: t
def new(id)
def new(id) when is_binary(id),
do: %__MODULE__{id: id}
def new(id) when is_reference(id),
do: id |> :erlang.ref_to_list |> to_string |> String.replace(~r/\<|\>/, "") |> new
do: id |> :erlang.ref_to_list() |> to_string |> String.replace(~r/\<|\>/, "") |> new
def new(id) when is_atom(id) or is_integer(id),
do: id |> to_string |> new
@doc """
Tests for value equality of blank nodes.
@ -55,9 +54,7 @@ defmodule RDF.BlankNode do
def equal_value?(_, _),
do: nil
defimpl String.Chars do
def to_string(%RDF.BlankNode{id: id}), do: "_:#{id}"
end
end

View file

@ -5,7 +5,6 @@ defmodule RDF.BlankNode.Generator do
use GenServer
# Client API ###############################################################
@doc """
@ -30,7 +29,6 @@ defmodule RDF.BlankNode.Generator do
defp convert_opts(opts) when is_list(opts), do: Map.new(opts)
defp convert_opts(opts) when is_map(opts), do: opts
@doc """
Synchronously stops the blank node generator with the given `reason`.
@ -45,7 +43,6 @@ defmodule RDF.BlankNode.Generator do
GenServer.stop(pid, reason, timeout)
end
@doc """
Generates a new blank node according to the `RDF.BlankNode.Generator.Algorithm` set up.
"""
@ -53,7 +50,6 @@ defmodule RDF.BlankNode.Generator do
GenServer.call(pid, :generate)
end
@doc """
Generates a blank node for a given value according to the `RDF.BlankNode.Generator.Algorithm` set up.
"""
@ -61,7 +57,6 @@ defmodule RDF.BlankNode.Generator do
GenServer.call(pid, {:generate_for, value})
end
# Server Callbacks #########################################################
@impl GenServer
@ -69,7 +64,6 @@ defmodule RDF.BlankNode.Generator do
{:ok, {generation_mod, generation_mod.init(init_opts)}}
end
@impl GenServer
def handle_call(:generate, _from, {generation_mod, state}) do
with {bnode, new_state} = generation_mod.generate(state) do
@ -83,5 +77,4 @@ defmodule RDF.BlankNode.Generator do
{:reply, bnode, {generation_mod, new_state}}
end
end
end

View file

@ -16,7 +16,7 @@ defmodule RDF.BlankNode.Generator.Algorithm do
An implementation should compute a blank node from the given state and return
a tuple consisting of the generated blank node and the new state.
"""
@callback generate(state :: map) :: {RDF.BlankNode.t, map}
@callback generate(state :: map) :: {RDF.BlankNode.t(), map}
@doc """
Generates a blank node for a given string.
@ -27,6 +27,5 @@ defmodule RDF.BlankNode.Generator.Algorithm do
given state and return a tuple consisting of the generated blank node and the
new state.
"""
@callback generate_for(value :: any, state :: map) :: {RDF.BlankNode.t, map}
@callback generate_for(value :: any, state :: map) :: {RDF.BlankNode.t(), map}
end

View file

@ -40,7 +40,8 @@ defmodule RDF.BlankNode.Increment do
case Map.get(map, value) do
nil ->
{bnode(counter, state),
%{state | map: Map.put(map, value, counter), counter: counter + 1}}
%{state | map: Map.put(map, value, counter), counter: counter + 1}}
previous ->
{bnode(previous, state), state}
end
@ -53,5 +54,4 @@ defmodule RDF.BlankNode.Increment do
defp bnode(counter, _) do
BlankNode.new(counter)
end
end

View file

@ -137,10 +137,12 @@ defimpl RDF.Data, for: RDF.Description do
def merge(description, {_, _, _, _} = quad),
do: RDF.Dataset.new(description) |> RDF.Dataset.add(quad)
def merge(%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject} = other_description)
when other_subject == subject,
do: RDF.Description.add(description, other_description)
def merge(
%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject} = other_description
)
when other_subject == subject,
do: RDF.Description.add(description, other_description)
def merge(description, %RDF.Description{} = other_description),
do: RDF.Graph.new(description) |> RDF.Graph.add(other_description)
@ -151,14 +153,16 @@ defimpl RDF.Data, for: RDF.Description do
def merge(description, %RDF.Dataset{} = dataset),
do: RDF.Data.merge(dataset, description)
def delete(
%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject}
)
when subject != other_subject,
do: description
def delete(%RDF.Description{subject: subject} = description,
%RDF.Description{subject: other_subject})
when subject != other_subject, do: description
def delete(description, statements), do: RDF.Description.delete(description, statements)
def pop(description), do: RDF.Description.pop(description)
def pop(description), do: RDF.Description.pop(description)
def include?(description, statements),
do: RDF.Description.include?(description, statements)
@ -180,14 +184,14 @@ defimpl RDF.Data, for: RDF.Description do
def subjects(%RDF.Description{subject: subject}), do: MapSet.new([subject])
def predicates(description), do: RDF.Description.predicates(description)
def objects(description), do: RDF.Description.objects(description)
def objects(description), do: RDF.Description.objects(description)
def resources(%RDF.Description{subject: subject} = description),
do: RDF.Description.resources(description) |> MapSet.put(subject)
def subject_count(_), do: 1
def subject_count(_), do: 1
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)
def equal?(description, %RDF.Description{} = other_description) do
@ -209,7 +213,6 @@ defimpl RDF.Data, for: RDF.Description do
def equal?(_, _), do: false
end
defimpl RDF.Data, for: RDF.Graph do
def merge(%RDF.Graph{name: name} = graph, {_, _, _, graph_context} = quad) do
with ^name <- RDF.Statement.coerce_graph_name(graph_context) do
@ -230,10 +233,12 @@ defimpl RDF.Data, for: RDF.Graph do
def merge(graph, %RDF.Description{} = description),
do: RDF.Graph.add(graph, description)
def merge(%RDF.Graph{name: name} = graph,
%RDF.Graph{name: other_name} = other_graph)
when other_name == name,
do: RDF.Graph.add(graph, other_graph)
def merge(
%RDF.Graph{name: name} = graph,
%RDF.Graph{name: other_name} = other_graph
)
when other_name == name,
do: RDF.Graph.add(graph, other_graph)
def merge(graph, %RDF.Graph{} = other_graph),
do: RDF.Dataset.new(graph) |> RDF.Dataset.add(other_graph)
@ -241,12 +246,13 @@ defimpl RDF.Data, for: RDF.Graph do
def merge(graph, %RDF.Dataset{} = dataset),
do: RDF.Data.merge(dataset, graph)
def delete(%RDF.Graph{name: name} = graph, %RDF.Graph{name: other_name})
when name != other_name, do: graph
when name != other_name,
do: graph
def delete(graph, statements), do: RDF.Graph.delete(graph, statements)
def pop(graph), do: RDF.Graph.pop(graph)
def pop(graph), do: RDF.Graph.pop(graph)
def include?(graph, statements), do: RDF.Graph.include?(graph, statements)
@ -260,22 +266,25 @@ defimpl RDF.Data, for: RDF.Graph do
def statements(graph), do: RDF.Graph.statements(graph)
def subjects(graph), do: RDF.Graph.subjects(graph)
def subjects(graph), do: RDF.Graph.subjects(graph)
def predicates(graph), do: RDF.Graph.predicates(graph)
def objects(graph), do: RDF.Graph.objects(graph)
def resources(graph), do: RDF.Graph.resources(graph)
def objects(graph), do: RDF.Graph.objects(graph)
def resources(graph), do: RDF.Graph.resources(graph)
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 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)
def equal?(graph, %RDF.Description{} = description),
do: RDF.Data.equal?(description, graph)
def equal?(graph, %RDF.Graph{} = other_graph),
do: RDF.Graph.equal?(%RDF.Graph{graph | name: nil},
%RDF.Graph{other_graph | name: nil})
do:
RDF.Graph.equal?(
%RDF.Graph{graph | name: nil},
%RDF.Graph{other_graph | name: nil}
)
def equal?(graph, %RDF.Dataset{} = dataset),
do: RDF.Data.equal?(dataset, graph)
@ -283,25 +292,29 @@ defimpl RDF.Data, for: RDF.Graph do
def equal?(_, _), do: false
end
defimpl RDF.Data, for: RDF.Dataset do
def merge(dataset, {_, _, _} = triple),
do: RDF.Dataset.add(dataset, triple)
def merge(dataset, {_, _, _, _} = quad),
do: RDF.Dataset.add(dataset, quad)
def merge(dataset, %RDF.Description{} = description),
do: RDF.Dataset.add(dataset, description)
def merge(dataset, %RDF.Graph{} = graph),
do: RDF.Dataset.add(dataset, graph)
def merge(dataset, %RDF.Dataset{} = other_dataset),
do: RDF.Dataset.add(dataset, other_dataset)
def delete(%RDF.Dataset{name: name} = dataset, %RDF.Dataset{name: other_name})
when name != other_name, do: dataset
when name != other_name,
do: dataset
def delete(dataset, statements), do: RDF.Dataset.delete(dataset, statements)
def pop(dataset), do: RDF.Dataset.pop(dataset)
def pop(dataset), do: RDF.Dataset.pop(dataset)
def include?(dataset, statements), do: RDF.Dataset.include?(dataset, statements)
@ -310,31 +323,32 @@ defimpl RDF.Data, for: RDF.Dataset do
def description(dataset, subject) do
with subject = RDF.Statement.coerce_subject(subject) do
Enum.reduce RDF.Dataset.graphs(dataset), RDF.Description.new(subject), fn
Enum.reduce(RDF.Dataset.graphs(dataset), RDF.Description.new(subject), fn
%RDF.Graph{descriptions: %{^subject => graph_description}}, description ->
RDF.Description.add(description, graph_description)
_, description ->
description
end
end)
end
end
def descriptions(dataset) do
dataset
|> subjects
|> Enum.map(&(description(dataset, &1)))
|> Enum.map(&description(dataset, &1))
end
def statements(dataset), do: RDF.Dataset.statements(dataset)
def subjects(dataset), do: RDF.Dataset.subjects(dataset)
def subjects(dataset), do: RDF.Dataset.subjects(dataset)
def predicates(dataset), do: RDF.Dataset.predicates(dataset)
def objects(dataset), do: RDF.Dataset.objects(dataset)
def resources(dataset), do: RDF.Dataset.resources(dataset)
def objects(dataset), do: RDF.Dataset.objects(dataset)
def resources(dataset), do: RDF.Dataset.resources(dataset)
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 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)
def equal?(dataset, %RDF.Description{} = description) do
@ -354,8 +368,10 @@ defimpl RDF.Data, for: RDF.Dataset do
end
def equal?(dataset, %RDF.Dataset{} = other_dataset) do
RDF.Dataset.equal?(%RDF.Dataset{dataset | name: nil},
%RDF.Dataset{other_dataset | name: nil})
RDF.Dataset.equal?(
%RDF.Dataset{dataset | name: nil},
%RDF.Dataset{other_dataset | name: nil}
)
end
def equal?(_, _), do: false

View file

@ -18,20 +18,19 @@ defmodule RDF.Dataset do
alias RDF.{Description, Graph, IRI, Statement}
import RDF.Statement
@type graph_name :: IRI.t | nil
@type graph_name :: IRI.t() | nil
@type t :: %__MODULE__{
name: graph_name,
graphs: %{graph_name => Graph.t}
}
graphs: %{graph_name => Graph.t()}
}
@type input :: Graph.input | t
@type input :: Graph.input() | t
@type update_graph_fun :: (Graph.t -> {Graph.t, input} | :pop)
@type update_graph_fun :: (Graph.t() -> {Graph.t(), input} | :pop)
defstruct name: nil, graphs: %{}
@doc """
Creates an empty unnamed `RDF.Dataset`.
"""
@ -96,7 +95,6 @@ defmodule RDF.Dataset do
|> add(data)
end
@doc """
Adds triples and quads to a `RDF.Dataset`.
@ -109,9 +107,9 @@ defmodule RDF.Dataset do
def add(dataset, statements, graph_context) when is_list(statements) do
with graph_context = graph_context && coerce_graph_name(graph_context) do
Enum.reduce statements, dataset, fn (statement, dataset) ->
Enum.reduce(statements, dataset, fn statement, dataset ->
add(dataset, statement, graph_context)
end
end)
end
end
@ -121,13 +119,20 @@ defmodule RDF.Dataset do
def add(dataset, {subject, predicate, objects}, graph_context),
do: add(dataset, {subject, predicate, objects, graph_context})
def add(%__MODULE__{name: name, graphs: graphs},
{subject, predicate, objects, graph_context}, false) do
def add(
%__MODULE__{name: name, graphs: graphs},
{subject, predicate, objects, graph_context},
false
) do
with graph_context = coerce_graph_name(graph_context) do
updated_graphs =
Map.update(graphs, graph_context,
Map.update(
graphs,
graph_context,
Graph.new({subject, predicate, objects}, name: graph_context),
fn graph -> Graph.add(graph, {subject, predicate, objects}) end)
fn graph -> Graph.add(graph, {subject, predicate, objects}) end
)
%__MODULE__{name: name, graphs: updated_graphs}
end
end
@ -138,21 +143,22 @@ defmodule RDF.Dataset do
def add(%__MODULE__{} = dataset, %Description{} = description, false),
do: add(dataset, description, nil)
def add(%__MODULE__{name: name, graphs: graphs},
%Description{} = description, graph_context) do
def add(%__MODULE__{name: name, graphs: graphs}, %Description{} = description, graph_context) do
with graph_context = coerce_graph_name(graph_context) do
updated_graph =
Map.get(graphs, graph_context, Graph.new(name: graph_context))
|> Graph.add(description)
%__MODULE__{
name: name,
name: name,
graphs: Map.put(graphs, graph_context, updated_graph)
}
end
end
def add(%__MODULE__{name: name, graphs: graphs}, %Graph{} = graph, false) do
%__MODULE__{name: name,
%__MODULE__{
name: name,
graphs:
Map.update(graphs, graph.name, graph, fn current ->
Graph.add(current, graph)
@ -165,13 +171,12 @@ defmodule RDF.Dataset do
def add(%__MODULE__{} = dataset, %__MODULE__{} = other_dataset, graph_context) do
with graph_context = graph_context && coerce_graph_name(graph_context) do
Enum.reduce graphs(other_dataset), dataset, fn (graph, dataset) ->
Enum.reduce(graphs(other_dataset), dataset, fn graph, dataset ->
add(dataset, graph, graph_context)
end
end)
end
end
@doc """
Adds statements to a `RDF.Dataset` and overwrites all existing statements with the same subjects and predicates in the specified graph context.
@ -186,7 +191,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.put([{EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
RDF.Dataset.new([{EX.S1, EX.P1, EX.O1}, {EX.S1, EX.P2, EX.O3}, {EX.S2, EX.P2, EX.O3}])
"""
@spec put(t, input | [input], Statement.coercible_graph_name | boolean | nil) :: t
@spec put(t, input | [input], Statement.coercible_graph_name() | boolean | nil) :: t
def put(dataset, statements, graph_context \\ false)
def put(%__MODULE__{} = dataset, {subject, predicate, objects}, false),
@ -195,18 +200,22 @@ defmodule RDF.Dataset do
def put(%__MODULE__{} = dataset, {subject, predicate, objects}, graph_context),
do: put(dataset, {subject, predicate, objects, graph_context})
def put(%__MODULE__{name: name, graphs: graphs},
{subject, predicate, objects, graph_context}, false) do
def put(
%__MODULE__{name: name, graphs: graphs},
{subject, predicate, objects, graph_context},
false
) do
with graph_context = coerce_graph_name(graph_context) do
new_graph =
case graphs[graph_context] do
graph = %Graph{} ->
Graph.put(graph, {subject, predicate, objects})
nil ->
Graph.new({subject, predicate, objects}, name: graph_context)
end
%__MODULE__{name: name,
graphs: Map.put(graphs, graph_context, new_graph)}
%__MODULE__{name: name, graphs: Map.put(graphs, graph_context, new_graph)}
end
end
@ -214,50 +223,61 @@ defmodule RDF.Dataset do
do: put(dataset, {subject, predicate, objects, graph_context}, false)
def put(%__MODULE__{} = dataset, statements, false) when is_list(statements) do
do_put dataset, Enum.group_by(statements,
do_put(
dataset,
Enum.group_by(
statements,
fn
{s, _, _} -> {s, nil}
{s, _, _, nil} -> {s, nil}
{s, _, _, c} -> {s, coerce_graph_name(c)}
{s, _, _} -> {s, nil}
{s, _, _, nil} -> {s, nil}
{s, _, _, c} -> {s, coerce_graph_name(c)}
end,
fn
{_, p, o, _} -> {p, o}
{_, p, o} -> {p, o}
end)
{_, p, o} -> {p, o}
end
)
)
end
def put(%__MODULE__{} = dataset, statements, graph_context) when is_list(statements) do
with graph_context = coerce_graph_name(graph_context) do
do_put dataset, Enum.group_by(statements,
do_put(
dataset,
Enum.group_by(
statements,
fn
{s, _, _, _} -> {s, graph_context}
{s, _, _} -> {s, graph_context}
{s, _, _} -> {s, graph_context}
end,
fn
{_, p, o, _} -> {p, o}
{_, p, o} -> {p, o}
end)
{_, p, o} -> {p, o}
end
)
)
end
end
def put(%__MODULE__{} = dataset, %Description{} = description, false),
do: put(dataset, description, nil)
def put(%__MODULE__{name: name, graphs: graphs},
%Description{} = description, graph_context) do
def put(%__MODULE__{name: name, graphs: graphs}, %Description{} = description, graph_context) do
with graph_context = coerce_graph_name(graph_context) do
updated_graph =
Map.get(graphs, graph_context, Graph.new(name: graph_context))
|> Graph.put(description)
%__MODULE__{
name: name,
name: name,
graphs: Map.put(graphs, graph_context, updated_graph)
}
end
end
def put(%__MODULE__{name: name, graphs: graphs}, %Graph{} = graph, false) do
%__MODULE__{name: name,
%__MODULE__{
name: name,
graphs:
Map.update(graphs, graph.name, graph, fn current ->
Graph.put(current, graph)
@ -270,31 +290,31 @@ defmodule RDF.Dataset do
def put(%__MODULE__{} = dataset, %__MODULE__{} = other_dataset, graph_context) do
with graph_context = graph_context && coerce_graph_name(graph_context) do
Enum.reduce graphs(other_dataset), dataset, fn (graph, dataset) ->
Enum.reduce(graphs(other_dataset), dataset, fn graph, dataset ->
put(dataset, graph, graph_context)
end
end)
end
end
defp do_put(%__MODULE__{} = dataset, statements) when is_map(statements) do
Enum.reduce statements, dataset,
fn ({subject_with_context, predications}, dataset) ->
do_put(dataset, subject_with_context, predications)
end
Enum.reduce(statements, dataset, fn {subject_with_context, predications}, dataset ->
do_put(dataset, subject_with_context, predications)
end)
end
defp do_put(%__MODULE__{name: name, graphs: graphs},
{subject, graph_context}, predications)
when is_list(predications) do
defp do_put(%__MODULE__{name: name, graphs: graphs}, {subject, graph_context}, predications)
when is_list(predications) do
with graph_context = coerce_graph_name(graph_context) do
graph = Map.get(graphs, graph_context, Graph.new(name: graph_context))
new_graphs = graphs
new_graphs =
graphs
|> Map.put(graph_context, Graph.put(graph, subject, predications))
%__MODULE__{name: name, graphs: new_graphs}
end
end
@doc """
Deletes statements from a `RDF.Dataset`.
@ -307,14 +327,14 @@ defmodule RDF.Dataset do
are deleted. If you want to delete only datasets with matching names, you can
use `RDF.Data.delete/2`.
"""
@spec delete(t, input | [input], Statement.coercible_graph_name | boolean | nil) :: t
@spec delete(t, input | [input], Statement.coercible_graph_name() | boolean | nil) :: t
def delete(dataset, statements, graph_context \\ false)
def delete(%__MODULE__{} = dataset, statements, graph_context) when is_list(statements) do
with graph_context = graph_context && coerce_graph_name(graph_context) do
Enum.reduce statements, dataset, fn (statement, dataset) ->
Enum.reduce(statements, dataset, fn statement, dataset ->
delete(dataset, statement, graph_context)
end
end)
end
end
@ -343,18 +363,17 @@ defmodule RDF.Dataset do
do: do_delete(dataset, graph_context, graph)
def delete(%__MODULE__{} = dataset, %__MODULE__{graphs: graphs}, graph_context) do
Enum.reduce graphs, dataset, fn ({_, graph}, dataset) ->
Enum.reduce(graphs, dataset, fn {_, graph}, dataset ->
delete(dataset, graph, graph_context)
end
end)
end
defp do_delete(%__MODULE__{name: name, graphs: graphs} = dataset,
graph_context, statements) do
defp do_delete(%__MODULE__{name: name, graphs: graphs} = dataset, graph_context, statements) do
with graph_context = coerce_graph_name(graph_context),
graph when not is_nil(graph) <- graphs[graph_context],
new_graph = Graph.delete(graph, statements)
do
%__MODULE__{name: name,
new_graph = Graph.delete(graph, statements) do
%__MODULE__{
name: name,
graphs:
if Enum.empty?(new_graph) do
Map.delete(graphs, graph_context)
@ -367,17 +386,16 @@ defmodule RDF.Dataset do
end
end
@doc """
Deletes the given graph.
"""
@spec delete_graph(t, Statement.graph_name | [Statement.graph_name] | nil) :: t
@spec delete_graph(t, Statement.graph_name() | [Statement.graph_name()] | nil) :: t
def delete_graph(graph, graph_names)
def delete_graph(%__MODULE__{} = dataset, graph_names) when is_list(graph_names) do
Enum.reduce graph_names, dataset, fn (graph_name, dataset) ->
Enum.reduce(graph_names, dataset, fn graph_name, dataset ->
delete_graph(dataset, graph_name)
end
end)
end
def delete_graph(%__MODULE__{name: name, graphs: graphs}, graph_name) do
@ -393,7 +411,6 @@ defmodule RDF.Dataset do
def delete_default_graph(%__MODULE__{} = graph),
do: delete_graph(graph, nil)
@doc """
Fetches the `RDF.Graph` with the given name.
@ -410,7 +427,7 @@ defmodule RDF.Dataset do
:error
"""
@impl Access
@spec fetch(t, Statement.graph_name | nil) :: {:ok, Graph.t} | :error
@spec fetch(t, Statement.graph_name() | nil) :: {:ok, Graph.t()} | :error
def fetch(%__MODULE__{graphs: graphs}, graph_name) do
Access.fetch(graphs, coerce_graph_name(graph_name))
end
@ -433,36 +450,34 @@ defmodule RDF.Dataset do
iex> RDF.Dataset.get(dataset, EX.Foo, :bar)
:bar
"""
@spec get(t, Statement.graph_name | nil, Graph.t | nil) :: Graph.t | nil
@spec get(t, Statement.graph_name() | nil, Graph.t() | nil) :: Graph.t() | nil
def get(%__MODULE__{} = dataset, graph_name, default \\ nil) do
case fetch(dataset, graph_name) do
{:ok, value} -> value
:error -> default
:error -> default
end
end
@doc """
The graph with given name.
"""
@spec graph(t, Statement.graph_name | nil) :: Graph.t
@spec graph(t, Statement.graph_name() | nil) :: Graph.t()
def graph(%__MODULE__{graphs: graphs}, graph_name),
do: Map.get(graphs, coerce_graph_name(graph_name))
@doc """
The default graph of a `RDF.Dataset`.
"""
@spec default_graph(t) :: Graph.t
@spec default_graph(t) :: Graph.t()
def default_graph(%__MODULE__{graphs: graphs}),
do: Map.get(graphs, nil, Graph.new)
do: Map.get(graphs, nil, Graph.new())
@doc """
The set of all graphs.
"""
@spec graphs(t) :: [Graph.t]
@spec graphs(t) :: [Graph.t()]
def graphs(%__MODULE__{graphs: graphs}), do: Map.values(graphs)
@doc """
Gets and updates the graph with the given name, in a single pass.
@ -486,37 +501,43 @@ defmodule RDF.Dataset do
{RDF.Graph.new({EX.S, EX.P, EX.O}, name: EX.Graph), RDF.Dataset.new({EX.S, EX.P, EX.NEW, EX.Graph})}
"""
@impl Access
@spec get_and_update(t, Statement.graph_name | nil, update_graph_fun) :: {Graph.t, input}
@spec get_and_update(t, Statement.graph_name() | nil, update_graph_fun) :: {Graph.t(), input}
def get_and_update(%__MODULE__{} = dataset, graph_name, fun) do
with graph_context = coerce_graph_name(graph_name) do
case fun.(get(dataset, graph_context)) do
{old_graph, new_graph} ->
{old_graph, put(dataset, new_graph, graph_context)}
:pop ->
pop(dataset, graph_context)
other ->
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
raise "the given function must return a two-element tuple or :pop, got: #{
inspect(other)
}"
end
end
end
@doc """
Pops an arbitrary statement from a `RDF.Dataset`.
"""
@spec pop(t) :: {Statement.t | nil, t}
@spec pop(t) :: {Statement.t() | nil, t}
def pop(dataset)
def pop(%__MODULE__{graphs: graphs} = dataset)
when graphs == %{}, do: {nil, dataset}
when graphs == %{},
do: {nil, dataset}
def pop(%__MODULE__{name: name, graphs: graphs}) do
# TODO: Find a faster way ...
[{graph_name, graph}] = Enum.take(graphs, 1)
{{s, p, o}, popped_graph} = Graph.pop(graph)
popped = if Enum.empty?(popped_graph),
do: graphs |> Map.delete(graph_name),
else: graphs |> Map.put(graph_name, popped_graph)
popped =
if Enum.empty?(popped_graph),
do: graphs |> Map.delete(graph_name),
else: graphs |> Map.put(graph_name, popped_graph)
{{s, p, o, graph_name}, %__MODULE__{name: name, graphs: popped}}
end
@ -538,18 +559,17 @@ defmodule RDF.Dataset do
{nil, dataset}
"""
@impl Access
@spec pop(t, Statement.coercible_graph_name) :: {Statement.t | nil, t}
@spec pop(t, Statement.coercible_graph_name()) :: {Statement.t() | nil, t}
def pop(%__MODULE__{name: name, graphs: graphs} = dataset, graph_name) do
case Access.pop(graphs, coerce_graph_name(graph_name)) do
{nil, _} ->
{nil, dataset}
{graph, new_graphs} ->
{graph, %__MODULE__{name: name, graphs: new_graphs}}
end
end
@doc """
The number of statements within a `RDF.Dataset`.
@ -564,9 +584,9 @@ defmodule RDF.Dataset do
"""
@spec statement_count(t) :: non_neg_integer
def statement_count(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, 0, fn ({_, graph}, count) ->
Enum.reduce(graphs, 0, fn {_, graph}, count ->
count + Graph.triple_count(graph)
end
end)
end
@doc """
@ -582,9 +602,9 @@ defmodule RDF.Dataset do
MapSet.new([RDF.iri(EX.S1), RDF.iri(EX.S2)])
"""
def subjects(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, MapSet.new, fn ({_, graph}, subjects) ->
Enum.reduce(graphs, MapSet.new(), fn {_, graph}, subjects ->
MapSet.union(subjects, Graph.subjects(graph))
end
end)
end
@doc """
@ -600,9 +620,9 @@ defmodule RDF.Dataset do
MapSet.new([EX.p1, EX.p2])
"""
def predicates(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, MapSet.new, fn ({_, graph}, predicates) ->
Enum.reduce(graphs, MapSet.new(), fn {_, graph}, predicates ->
MapSet.union(predicates, Graph.predicates(graph))
end
end)
end
@doc """
@ -622,9 +642,9 @@ defmodule RDF.Dataset do
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)])
"""
def objects(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, MapSet.new, fn ({_, graph}, objects) ->
Enum.reduce(graphs, MapSet.new(), fn {_, graph}, objects ->
MapSet.union(objects, Graph.objects(graph))
end
end)
end
@doc """
@ -642,9 +662,9 @@ defmodule RDF.Dataset do
RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2])
"""
def resources(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, MapSet.new, fn ({_, graph}, resources) ->
Enum.reduce(graphs, MapSet.new(), fn {_, graph}, resources ->
MapSet.union(resources, Graph.resources(graph))
end
end)
end
@doc """
@ -661,19 +681,19 @@ defmodule RDF.Dataset do
{RDF.iri(EX.S1), RDF.iri(EX.p2), RDF.iri(EX.O3)},
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
"""
@spec statements(t) :: [Statement.t]
@spec statements(t) :: [Statement.t()]
def statements(%__MODULE__{graphs: graphs}) do
Enum.reduce graphs, [], fn ({_, graph}, all_statements) ->
Enum.reduce(graphs, [], fn {_, graph}, all_statements ->
statements = Graph.triples(graph)
if graph.name do
Enum.map statements, fn {s, p, o} -> {s, p, o, graph.name} end
Enum.map(statements, fn {s, p, o} -> {s, p, o, graph.name} end)
else
statements
end ++ all_statements
end
end)
end
@doc """
Returns if a given statement is in a `RDF.Dataset`.
@ -686,7 +706,7 @@ defmodule RDF.Dataset do
...> RDF.Dataset.include?(dataset, {EX.S1, EX.p1, EX.O1, EX.Graph})
true
"""
@spec include?(t, Statement.t, Statement.coercible_graph_name | nil) :: boolean
@spec include?(t, Statement.t(), Statement.coercible_graph_name() | nil) :: boolean
def include?(dataset, statement, graph_context \\ nil)
def include?(%__MODULE__{graphs: graphs}, triple = {_, _, _}, graph_context) do
@ -702,7 +722,6 @@ defmodule RDF.Dataset do
def include?(%__MODULE__{} = dataset, {subject, predicate, object, graph_context}, _),
do: include?(dataset, {subject, predicate, object}, graph_context)
@doc """
Checks if a graph of a `RDF.Dataset` contains statements about the given resource.
@ -713,7 +732,7 @@ defmodule RDF.Dataset do
iex> RDF.Dataset.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Dataset.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.t, Statement.coercible_graph_name | nil) :: boolean
@spec describes?(t, Statement.t(), Statement.coercible_graph_name() | nil) :: boolean
def describes?(%__MODULE__{graphs: graphs}, subject, graph_context \\ nil) do
with graph_context = coerce_graph_name(graph_context) do
if graph = graphs[graph_context] do
@ -736,17 +755,16 @@ defmodule RDF.Dataset do
...> RDF.Dataset.who_describes(dataset, EX.S1)
[nil, RDF.iri(EX.Graph1)]
"""
@spec who_describes(t, Statement.coercible_subject) :: [Graph.t]
@spec who_describes(t, Statement.coercible_subject()) :: [Graph.t()]
def who_describes(%__MODULE__{graphs: graphs}, subject) do
with subject = coerce_subject(subject) do
graphs
|> Map.values
|> Map.values()
|> Stream.filter(&Graph.describes?(&1, subject))
|> Enum.map(&(&1.name))
|> Enum.map(& &1.name)
end
end
@doc """
Returns a nested map of the native Elixir values of a `RDF.Dataset`.
@ -799,16 +817,15 @@ defmodule RDF.Dataset do
}
"""
@spec values(t, Statement.term_mapping) :: map
@spec values(t, Statement.term_mapping()) :: map
def values(dataset, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%__MODULE__{graphs: graphs}, mapping) do
Map.new graphs, fn {graph_name, graph} ->
Map.new(graphs, fn {graph_name, graph} ->
{mapping.({:graph_name, graph_name}), Graph.values(graph, mapping)}
end
end)
end
@doc """
Checks if two `RDF.Dataset`s are equal.
@ -825,24 +842,25 @@ defmodule RDF.Dataset do
def equal?(_, _), do: false
defp clear_metadata(%__MODULE__{graphs: graphs} = dataset) do
%__MODULE__{dataset |
graphs:
Map.new(graphs, fn {name, graph} ->
{name, RDF.Graph.clear_metadata(graph)}
end)
%__MODULE__{
dataset
| graphs:
Map.new(graphs, fn {name, graph} ->
{name, RDF.Graph.clear_metadata(graph)}
end)
}
end
defimpl Enumerable do
alias RDF.Dataset
def member?(dataset, statement), do: {:ok, Dataset.include?(dataset, statement)}
def count(dataset), do: {:ok, Dataset.statement_count(dataset)}
def slice(_dataset), do: {:error, __MODULE__}
def count(dataset), do: {:ok, Dataset.statement_count(dataset)}
def slice(_dataset), do: {:error, __MODULE__}
def reduce(%Dataset{graphs: graphs}, {:cont, acc}, _fun)
when map_size(graphs) == 0, do: {:done, acc}
when map_size(graphs) == 0,
do: {:done, acc}
def reduce(%Dataset{} = dataset, {:cont, acc}, fun) do
{statement, rest} = Dataset.pop(dataset)
@ -850,26 +868,31 @@ defmodule RDF.Dataset do
end
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(dataset = %Dataset{}, {:suspend, acc}, fun) do
{:suspended, acc, &reduce(dataset, &1, fun)}
end
end
defimpl Collectable do
alias RDF.Dataset
def into(original) do
collector_fun = fn
dataset, {:cont, list} when is_list(list)
-> Dataset.add(dataset, List.to_tuple(list))
dataset, {:cont, elem} -> Dataset.add(dataset, elem)
dataset, :done -> dataset
_dataset, :halt -> :ok
dataset, {:cont, list} when is_list(list) ->
Dataset.add(dataset, List.to_tuple(list))
dataset, {:cont, elem} ->
Dataset.add(dataset, elem)
dataset, :done ->
dataset
_dataset, :halt ->
:ok
end
{original, collector_fun}
end
end
end

View file

@ -15,51 +15,56 @@ defmodule RDF.Description do
import RDF.Statement
alias RDF.{Statement, Triple}
@type predications :: %{Statement.predicate => %{Statement.object => nil}}
@type predications :: %{Statement.predicate() => %{Statement.object() => nil}}
@type statements ::
{Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_predicate]}
| Statement.t
{Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_predicate()]}
| Statement.t()
| predications
| t
@type t :: %__MODULE__{
subject: Statement.subject,
subject: Statement.subject(),
predications: predications
}
}
@enforce_keys [:subject]
defstruct subject: nil, predications: %{}
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
When given a list of statements, the first one must contain a subject.
"""
@spec new(Statement.coercible_subject | statements | [statements]) :: t
@spec new(Statement.coercible_subject() | statements | [statements]) :: t
def new(subject)
def new({subject, predicate, object}),
do: new(subject) |> add(predicate, object)
def new([statement | more_statements]),
do: new(statement) |> add(more_statements)
def new(%__MODULE__{} = description),
do: description
def new(subject),
do: %__MODULE__{subject: coerce_subject(subject)}
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(Statement.coercible_subject, statements | [statements]) :: t
@spec new(Statement.coercible_subject(), statements | [statements]) :: t
def new(subject, {predicate, objects}),
do: new(subject) |> add(predicate, objects)
def new(subject, statements) when is_list(statements),
do: new(subject) |> add(statements)
def new(subject, %__MODULE__{predications: predications}),
do: %__MODULE__{new(subject) | predications: predications}
def new(subject, predications = %{}),
do: new(subject) |> add(predications)
@ -67,16 +72,16 @@ defmodule RDF.Description do
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(
Statement.coercible_subject | statements | [statements],
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_subject() | statements | [statements],
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def new(%__MODULE__{} = description, predicate, objects),
do: add(description, predicate, objects)
def new(subject, predicate, objects),
do: new(subject) |> add(predicate, objects)
@doc """
Add objects to a predicate of a `RDF.Description`.
@ -89,29 +94,28 @@ defmodule RDF.Description do
"""
@spec add(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def add(description, predicate, objects)
def add(description, predicate, objects) when is_list(objects) do
Enum.reduce objects, description, fn (object, description) ->
Enum.reduce(objects, description, fn object, description ->
add(description, predicate, object)
end
end)
end
def add(%__MODULE__{subject: subject, predications: predications}, predicate, object) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object),
new_predications = Map.update(predications,
triple_predicate, %{triple_object => nil}, fn objects ->
new_predications =
Map.update(predications, triple_predicate, %{triple_object => nil}, fn objects ->
Map.put_new(objects, triple_object, nil)
end) do
%__MODULE__{subject: subject, predications: new_predications}
end
end
@doc """
Adds statements to a `RDF.Description`.
@ -128,7 +132,7 @@ defmodule RDF.Description do
def add(description = %__MODULE__{}, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: add(description, predicate, object),
do: add(description, predicate, object),
else: description
end
@ -136,25 +140,29 @@ defmodule RDF.Description do
do: add(description, {subject, predicate, object})
def add(description, statements) when is_list(statements) do
Enum.reduce statements, description, fn (statement, description) ->
Enum.reduce(statements, description, fn statement, description ->
add(description, statement)
end
end)
end
def add(%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}) do
merged_predications = Map.merge predications, other_predications,
fn (_, objects, other_objects) -> Map.merge(objects, other_objects) end
def add(
%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}
) do
merged_predications =
Map.merge(predications, other_predications, fn _, objects, other_objects ->
Map.merge(objects, other_objects)
end)
%__MODULE__{subject: subject, predications: merged_predications}
end
def add(description = %__MODULE__{}, predications = %{}) do
Enum.reduce predications, description, fn ({predicate, objects}, description) ->
Enum.reduce(predications, description, fn {predicate, objects}, description ->
add(description, predicate, objects)
end
end)
end
@doc """
Puts objects to a predicate of a `RDF.Description`, overwriting all existing objects.
@ -167,18 +175,22 @@ defmodule RDF.Description do
"""
@spec put(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def put(description, predicate, objects)
def put(%__MODULE__{subject: subject, predications: predications},
predicate, objects) when is_list(objects) do
def put(%__MODULE__{subject: subject, predications: predications}, predicate, objects)
when is_list(objects) do
with triple_predicate = coerce_predicate(predicate),
triple_objects = Enum.reduce(objects, %{}, fn (object, acc) ->
Map.put_new(acc, coerce_object(object), nil) end),
do: %__MODULE__{subject: subject,
predications: Map.put(predications, triple_predicate, triple_objects)}
triple_objects =
Enum.reduce(objects, %{}, fn object, acc ->
Map.put_new(acc, coerce_object(object), nil)
end),
do: %__MODULE__{
subject: subject,
predications: Map.put(predications, triple_predicate, triple_objects)
}
end
def put(%__MODULE__{} = description, predicate, object),
@ -209,7 +221,7 @@ defmodule RDF.Description do
def put(%__MODULE__{} = description, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: put(description, predicate, object),
do: put(description, predicate, object),
else: description
end
@ -219,53 +231,62 @@ defmodule RDF.Description do
def put(%__MODULE__{subject: subject} = description, statements) when is_list(statements) do
statements
|> Stream.map(fn
{p, o} -> {coerce_predicate(p), o}
{^subject, p, o} -> {coerce_predicate(p), o}
{s, p, o} ->
if coerce_subject(s) == subject,
do: {coerce_predicate(p), o}
bad -> raise ArgumentError, "#{inspect bad} is not a valid statement"
end)
|> Stream.filter(&(&1)) # filter nil values
|> Enum.group_by(&(elem(&1, 0)), &(elem(&1, 1)))
|> Enum.reduce(description, fn ({predicate, objects}, description) ->
put(description, predicate, objects)
end)
{p, o} ->
{coerce_predicate(p), o}
{^subject, p, o} ->
{coerce_predicate(p), o}
{s, p, o} ->
if coerce_subject(s) == subject,
do: {coerce_predicate(p), o}
bad ->
raise ArgumentError, "#{inspect(bad)} is not a valid statement"
end)
# filter nil values
|> Stream.filter(& &1)
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|> Enum.reduce(description, fn {predicate, objects}, description ->
put(description, predicate, objects)
end)
end
def put(%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}) do
merged_predications = Map.merge predications, other_predications,
fn (_, _, other_objects) -> other_objects end
def put(
%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}
) do
merged_predications =
Map.merge(predications, other_predications, fn _, _, other_objects -> other_objects end)
%__MODULE__{subject: subject, predications: merged_predications}
end
def put(description = %__MODULE__{}, predications = %{}) do
Enum.reduce predications, description, fn ({predicate, objects}, description) ->
Enum.reduce(predications, description, fn {predicate, objects}, description ->
put(description, predicate, objects)
end
end)
end
@doc """
Deletes statements from a `RDF.Description`.
"""
@spec delete(
t,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def delete(description, predicate, objects)
def delete(description, predicate, objects) when is_list(objects) do
Enum.reduce objects, description, fn (object, description) ->
Enum.reduce(objects, description, fn object, description ->
delete(description, predicate, object)
end
end)
end
def delete(%__MODULE__{subject: subject, predications: predications} = descr, predicate, object) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object) do
triple_object = coerce_object(object) do
if (objects = predications[triple_predicate]) && Map.has_key?(objects, triple_object) do
%__MODULE__{
subject: subject,
@ -274,10 +295,10 @@ defmodule RDF.Description do
Map.delete(predications, triple_predicate)
else
Map.update!(predications, triple_predicate, fn objects ->
Map.delete(objects, triple_object)
end)
Map.delete(objects, triple_object)
end)
end
}
}
else
descr
end
@ -300,7 +321,7 @@ defmodule RDF.Description do
def delete(description = %__MODULE__{}, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: delete(description, predicate, object),
do: delete(description, predicate, object),
else: description
end
@ -308,34 +329,34 @@ defmodule RDF.Description do
do: delete(description, {subject, predicate, object})
def delete(description, statements) when is_list(statements) do
Enum.reduce statements, description, fn (statement, description) ->
Enum.reduce(statements, description, fn statement, description ->
delete(description, statement)
end
end)
end
def delete(description = %__MODULE__{}, other_description = %__MODULE__{}) do
Enum.reduce other_description, description, fn ({_, predicate, object}, description) ->
Enum.reduce(other_description, description, fn {_, predicate, object}, description ->
delete(description, predicate, object)
end
end)
end
def delete(description = %__MODULE__{}, predications = %{}) do
Enum.reduce predications, description, fn ({predicate, objects}, description) ->
Enum.reduce(predications, description, fn {predicate, objects}, description ->
delete(description, predicate, objects)
end
end)
end
@doc """
Deletes all statements with the given properties.
"""
@spec delete_predicates(t, Statement.coercible_predicate | [Statement.coercible_predicate]) :: t
@spec delete_predicates(t, Statement.coercible_predicate() | [Statement.coercible_predicate()]) ::
t
def delete_predicates(description, properties)
def delete_predicates(%__MODULE__{} = description, properties) when is_list(properties) do
Enum.reduce properties, description, fn (property, description) ->
Enum.reduce(properties, description, fn property, description ->
delete_predicates(description, property)
end
end)
end
def delete_predicates(%__MODULE__{subject: subject, predications: predications}, property) do
@ -344,7 +365,6 @@ defmodule RDF.Description do
end
end
@doc """
Fetches the objects for the given predicate of a Description.
@ -361,7 +381,7 @@ defmodule RDF.Description do
:error
"""
@impl Access
@spec fetch(t, Statement.coercible_predicate) :: {:ok, [Statement.object]} | :error
@spec fetch(t, Statement.coercible_predicate()) :: {:ok, [Statement.object()]} | :error
def fetch(%__MODULE__{predications: predications}, predicate) do
with {:ok, objects} <- Access.fetch(predications, coerce_predicate(predicate)) do
{:ok, Map.keys(objects)}
@ -382,11 +402,11 @@ defmodule RDF.Description do
iex> RDF.Description.get(RDF.Description.new(EX.S), EX.foo, :bar)
:bar
"""
@spec get(t, Statement.coercible_predicate, any) :: [Statement.object] | any
@spec get(t, Statement.coercible_predicate(), any) :: [Statement.object()] | any
def get(description = %__MODULE__{}, predicate, default \\ nil) do
case fetch(description, predicate) do
{:ok, value} -> value
:error -> default
:error -> default
end
end
@ -402,11 +422,11 @@ defmodule RDF.Description do
iex> RDF.Description.first(RDF.Description.new(EX.S), EX.foo)
nil
"""
@spec first(t, Statement.coercible_predicate) :: Statement.object | nil
@spec first(t, Statement.coercible_predicate()) :: Statement.object() | nil
def first(description = %__MODULE__{}, predicate) do
description
|> get(predicate, [])
|> List.first
|> List.first()
end
@doc """
@ -433,8 +453,8 @@ defmodule RDF.Description do
"""
@spec update(
t,
Statement.coercible_predicate,
Statement.coercible_object | nil,
Statement.coercible_predicate(),
Statement.coercible_object() | nil,
([Statement.Object] -> [Statement.Object])
) :: t
def update(description = %__MODULE__{}, predicate, initial \\ nil, fun) do
@ -453,13 +473,12 @@ defmodule RDF.Description do
|> fun.()
|> List.wrap()
|> case do
[] -> delete_predicates(description, predicate)
objects -> put(description, predicate, objects)
end
[] -> delete_predicates(description, predicate)
objects -> put(description, predicate, objects)
end
end
end
@doc """
Gets and updates the objects of the given predicate of a Description, in a single pass.
@ -488,7 +507,7 @@ defmodule RDF.Description do
@impl Access
@spec get_and_update(
t,
Statement.coercible_predicate,
Statement.coercible_predicate(),
([Statement.Object] -> {[Statement.Object], t} | :pop)
) :: {[Statement.Object], t}
def get_and_update(description = %__MODULE__{}, predicate, fun) do
@ -496,32 +515,34 @@ defmodule RDF.Description do
case fun.(get(description, triple_predicate)) do
{objects_to_return, new_objects} ->
{objects_to_return, put(description, triple_predicate, new_objects)}
:pop -> pop(description, triple_predicate)
:pop ->
pop(description, triple_predicate)
end
end
end
@doc """
Pops an arbitrary triple from a `RDF.Description`.
"""
@spec pop(t) :: {Triple.t | [Statement.Object] | nil, t}
@spec pop(t) :: {Triple.t() | [Statement.Object] | nil, t}
def pop(description)
def pop(description = %__MODULE__{predications: predications})
when predications == %{}, do: {nil, description}
when predications == %{},
do: {nil, description}
def pop(%__MODULE__{subject: subject, predications: predications}) do
# TODO: Find a faster way ...
predicate = List.first(Map.keys(predications))
[{object, _}] = Enum.take(objects = predications[predicate], 1)
popped = if Enum.count(objects) == 1,
do: elem(Map.pop(predications, predicate), 1),
else: elem(pop_in(predications, [predicate, object]), 1)
popped =
if Enum.count(objects) == 1,
do: elem(Map.pop(predications, predicate), 1),
else: elem(pop_in(predications, [predicate, object]), 1)
{{subject, predicate, object},
%__MODULE__{subject: subject, predications: popped}}
{{subject, predicate, object}, %__MODULE__{subject: subject, predications: popped}}
end
@doc """
@ -541,12 +562,12 @@ defmodule RDF.Description do
case Access.pop(predications, coerce_predicate(predicate)) do
{nil, _} ->
{nil, description}
{objects, new_predications} ->
{Map.keys(objects), %__MODULE__{subject: subject, predications: new_predications}}
end
end
@doc """
The set of all properties used in the predicates within a `RDF.Description`.
@ -559,9 +580,9 @@ defmodule RDF.Description do
...> RDF.Description.predicates
MapSet.new([EX.p1, EX.p2])
"""
@spec predicates(t) :: MapSet.t
@spec predicates(t) :: MapSet.t()
def predicates(%__MODULE__{predications: predications}),
do: predications |> Map.keys |> MapSet.new
do: predications |> Map.keys() |> MapSet.new()
@doc """
The set of all resources used in the objects within a `RDF.Description`.
@ -579,22 +600,22 @@ defmodule RDF.Description do
...> ]) |> RDF.Description.objects
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)])
"""
@spec objects(t) :: MapSet.t
@spec objects(t) :: MapSet.t()
def objects(%__MODULE__{} = description),
do: objects(description, &RDF.resource?/1)
@doc """
The set of all resources used in the objects within a `RDF.Description` satisfying the given filter criterion.
"""
@spec objects(t, (Statement.object -> boolean)) :: MapSet.t
@spec objects(t, (Statement.object() -> boolean)) :: MapSet.t()
def objects(%__MODULE__{predications: predications}, filter_fn) do
Enum.reduce predications, MapSet.new, fn ({_, objects}, acc) ->
Enum.reduce(predications, MapSet.new(), fn {_, objects}, acc ->
objects
|> Map.keys
|> Map.keys()
|> Enum.filter(filter_fn)
|> MapSet.new
|> MapSet.new()
|> MapSet.union(acc)
end
end)
end
@doc """
@ -611,7 +632,7 @@ defmodule RDF.Description do
...> ]) |> RDF.Description.resources
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2, EX.p3])
"""
@spec resources(t) :: MapSet.t
@spec resources(t) :: MapSet.t()
def resources(description) do
description
|> objects
@ -626,42 +647,42 @@ defmodule RDF.Description do
defdelegate statements(description), to: __MODULE__, as: :triples
@doc """
Returns the number of statements of a `RDF.Description`.
"""
@spec count(t) :: non_neg_integer
def count(%__MODULE__{predications: predications}) do
Enum.reduce predications, 0,
fn ({_, objects}, count) -> count + Enum.count(objects) end
Enum.reduce(predications, 0, fn {_, objects}, count -> count + Enum.count(objects) end)
end
@doc """
Checks if the given statement exists within a `RDF.Description`.
"""
@spec include?(t, statements) :: boolean
def include?(description, statement)
def include?(%__MODULE__{predications: predications},
{predicate, object}) do
def include?(
%__MODULE__{predications: predications},
{predicate, object}
) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object) do
triple_object = coerce_object(object) do
predications
|> Map.get(triple_predicate, %{})
|> Map.has_key?(triple_object)
end
end
def include?(desc = %__MODULE__{subject: desc_subject},
{subject, predicate, object}) do
def include?(
desc = %__MODULE__{subject: desc_subject},
{subject, predicate, object}
) do
coerce_subject(subject) == desc_subject &&
include?(desc, {predicate, object})
end
def include?(%__MODULE__{}, _), do: false
@doc """
Checks if a `RDF.Description` has the given resource as subject.
@ -672,7 +693,7 @@ defmodule RDF.Description do
iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.subject) :: boolean
@spec describes?(t, Statement.subject()) :: boolean
def describes?(%__MODULE__{subject: subject}, other_subject) do
with other_subject = coerce_subject(other_subject) do
subject == other_subject
@ -712,16 +733,16 @@ defmodule RDF.Description do
%{p: ["Foo"]}
"""
@spec values(t, Statement.term_mapping) :: map
@spec values(t, Statement.term_mapping()) :: map
def values(description, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%__MODULE__{predications: predications}, mapping) do
Map.new predications, fn {predicate, objects} ->
Map.new(predications, fn {predicate, objects} ->
{
mapping.({:predicate, predicate}),
objects |> Map.keys() |> Enum.map(&(mapping.({:object, &1})))
objects |> Map.keys() |> Enum.map(&mapping.({:object, &1}))
}
end
end)
end
@doc """
@ -731,13 +752,13 @@ defmodule RDF.Description do
If `nil` is passed, the description is left untouched.
"""
@spec take(t, [Statement.coercible_predicate] | Enum.t | nil) :: t
@spec take(t, [Statement.coercible_predicate()] | Enum.t() | nil) :: t
def take(description, predicates)
def take(%__MODULE__{} = description, nil), do: description
def take(%__MODULE__{predications: predications} = description, predicates) do
predicates = Enum.map(predicates, &(coerce_predicate/1))
predicates = Enum.map(predicates, &coerce_predicate/1)
%__MODULE__{description | predications: Map.take(predications, predicates)}
end
@ -755,43 +776,48 @@ defmodule RDF.Description do
def equal?(_, _), do: false
defimpl Enumerable do
alias RDF.Description
def member?(desc, triple), do: {:ok, Description.include?(desc, triple)}
def count(desc), do: {:ok, Description.count(desc)}
def slice(_desc), do: {:error, __MODULE__}
def count(desc), do: {:ok, Description.count(desc)}
def slice(_desc), do: {:error, __MODULE__}
def reduce(%Description{predications: predications}, {:cont, acc}, _fun)
when map_size(predications) == 0, do: {:done, acc}
when map_size(predications) == 0,
do: {:done, acc}
def reduce(description = %Description{}, {:cont, acc}, fun) do
{triple, rest} = Description.pop(description)
reduce(rest, fun.(triple, acc), fun)
end
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(description = %Description{}, {:suspend, acc}, fun) do
{:suspended, acc, &reduce(description, &1, fun)}
end
end
defimpl Collectable do
alias RDF.Description
def into(original) do
collector_fun = fn
description, {:cont, list} when is_list(list)
-> Description.add(description, List.to_tuple(list))
description, {:cont, elem} -> Description.add(description, elem)
description, :done -> description
_description, :halt -> :ok
description, {:cont, list} when is_list(list) ->
Description.add(description, List.to_tuple(list))
description, {:cont, elem} ->
Description.add(description, elem)
description, :done ->
description
_description, :halt ->
:ok
end
{original, collector_fun}
end
end
end

View file

@ -9,13 +9,12 @@ defmodule RDF.Diff do
alias RDF.{Description, Graph}
@type t :: %__MODULE__{
additions: Graph.t,
deletions: Graph.t
}
additions: Graph.t(),
deletions: Graph.t()
}
defstruct [:additions, :deletions]
@doc """
Creates a `RDF.Diff` struct.
@ -32,8 +31,10 @@ defmodule RDF.Diff do
end
defp coerce_graph(nil), do: Graph.new()
defp coerce_graph(%Description{} = description),
do: if Enum.empty?(description), do: Graph.new(), else: Graph.new(description)
do: if(Enum.empty?(description), do: Graph.new(), else: Graph.new(description))
defp coerce_graph(data), do: Graph.new(data)
@doc """
@ -59,43 +60,49 @@ defmodule RDF.Diff do
deletions: RDF.graph({EX.S1, EX.p1, EX.O1})
}
"""
@spec diff(Description.t | Graph.t, Description.t | Graph.t) :: t
@spec diff(Description.t() | Graph.t(), Description.t() | Graph.t()) :: t
def diff(original_rdf_data, new_rdf_data)
def diff(%Description{} = description, description), do: new()
def diff(%Description{subject: subject} = original_description,
%Description{subject: subject} = new_description) do
def diff(
%Description{subject: subject} = original_description,
%Description{subject: subject} = new_description
) do
{additions, deletions} =
original_description
|> Description.predicates()
|> Enum.reduce({new_description, Description.new(subject)},
fn property, {additions, deletions} ->
original_objects = Description.get(original_description, property)
case Description.get(new_description, property) do
nil ->
{
additions,
Description.add(deletions, property, original_objects)
}
|> Enum.reduce(
{new_description, Description.new(subject)},
fn property, {additions, deletions} ->
original_objects = Description.get(original_description, property)
new_objects ->
{unchanged_objects, deleted_objects} =
Enum.reduce(original_objects, {[], []}, fn
original_object, {unchanged_objects, deleted_objects} ->
if original_object in new_objects do
{[original_object | unchanged_objects], deleted_objects}
else
{unchanged_objects, [original_object | deleted_objects]}
end
end)
case Description.get(new_description, property) do
nil ->
{
additions,
Description.add(deletions, property, original_objects)
}
new_objects ->
{unchanged_objects, deleted_objects} =
Enum.reduce(original_objects, {[], []}, fn
original_object, {unchanged_objects, deleted_objects} ->
if original_object in new_objects do
{[original_object | unchanged_objects], deleted_objects}
else
{unchanged_objects, [original_object | deleted_objects]}
end
end)
{
Description.delete(additions, property, unchanged_objects),
Description.add(deletions, property, deleted_objects)
}
end
end
)
{
Description.delete(additions, property, unchanged_objects),
Description.add(deletions, property, deleted_objects),
}
end
end)
new(additions: additions, deletions: deletions)
end
@ -111,16 +118,20 @@ defmodule RDF.Diff do
graph1_subjects
|> MapSet.intersection(graph2_subjects)
|> Enum.reduce(
new(
additions: Graph.take(graph2, added_subjects),
deletions: Graph.take(graph1, deleted_subjects)
),
fn subject, diff ->
merge(diff, diff(
Graph.description(graph1, subject),
Graph.description(graph2, subject)
))
end)
new(
additions: Graph.take(graph2, added_subjects),
deletions: Graph.take(graph1, deleted_subjects)
),
fn subject, diff ->
merge(
diff,
diff(
Graph.description(graph1, subject),
Graph.description(graph2, subject)
)
)
end
)
end
def diff(%Description{} = description, %Graph{} = graph) do
@ -139,10 +150,7 @@ defmodule RDF.Diff do
def diff(%Graph{} = graph, %Description{} = description) do
diff = diff(description, graph)
%__MODULE__{ diff |
additions: diff.deletions,
deletions: diff.additions
}
%__MODULE__{diff | additions: diff.deletions, deletions: diff.additions}
end
@doc """
@ -178,7 +186,7 @@ defmodule RDF.Diff do
The result of an application is always a `RDF.Graph`, even if a `RDF.Description`
is given and the additions from the diff are all about the subject of this description.
"""
@spec apply(t, Description.t | Graph.t) :: Graph.t
@spec apply(t, Description.t() | Graph.t()) :: Graph.t()
def apply(diff, rdf_data)
def apply(%__MODULE__{} = diff, %Graph{} = graph) do

View file

@ -38,7 +38,6 @@ defmodule RDF.Quad.InvalidGraphContextError do
end
end
defmodule RDF.Namespace.InvalidVocabBaseIRIError do
defexception [:message]
end
@ -55,7 +54,6 @@ defmodule RDF.Namespace.UndefinedTermError do
defexception [:message]
end
defmodule RDF.Query.InvalidError do
defexception [:message]
end

View file

@ -16,24 +16,23 @@ defmodule RDF.Graph do
import RDF.Statement
alias RDF.{Description, IRI, PrefixMap, Statement}
@type graph_description :: %{Statement.subject => Description.t}
@type graph_description :: %{Statement.subject() => Description.t()}
@type t :: %__MODULE__{
name: IRI.t | nil,
name: IRI.t() | nil,
descriptions: graph_description,
prefixes: PrefixMap.t | nil,
base_iri: IRI.t | nil
}
prefixes: PrefixMap.t() | nil,
base_iri: IRI.t() | nil
}
@type input :: Statement.t | Description.t | t
@type input :: Statement.t() | Description.t() | t
@type update_description_fun :: (Description.t -> Description.t)
@type update_description_fun :: (Description.t() -> Description.t())
@type get_and_update_description_fun :: (Description.t -> {Description.t, input} | :pop)
@type get_and_update_description_fun :: (Description.t() -> {Description.t(), input} | :pop)
defstruct name: nil, descriptions: %{}, prefixes: nil, base_iri: nil
@doc """
Creates an empty unnamed `RDF.Graph`.
"""
@ -119,15 +118,14 @@ defmodule RDF.Graph do
See `new/2` for available arguments.
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object],
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()],
keyword
) :: t
def new(subject, predicate, objects, options \\ []),
do: new([], options) |> add(subject, predicate, objects)
@doc """
Removes all triples from `graph`.
@ -140,15 +138,14 @@ defmodule RDF.Graph do
%__MODULE__{graph | descriptions: %{}}
end
@doc """
Adds triples to a `RDF.Graph`.
"""
@spec add(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def add(%__MODULE__{} = graph, subject, predicate, objects),
do: add(graph, {subject, predicate, objects})
@ -175,9 +172,9 @@ defmodule RDF.Graph do
do: add(graph, {subject, predicate, object})
def add(graph, triples) when is_list(triples) do
Enum.reduce triples, graph, fn (triple, graph) ->
Enum.reduce(triples, graph, fn triple, graph ->
add(graph, triple)
end
end)
end
def add(%__MODULE__{} = graph, %Description{subject: subject} = description),
@ -185,9 +182,9 @@ defmodule RDF.Graph do
def add(graph, %__MODULE__{descriptions: descriptions, prefixes: prefixes}) do
graph =
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
Enum.reduce(descriptions, graph, fn {_, description}, graph ->
add(graph, description)
end
end)
if prefixes do
add_prefixes(graph, prefixes, fn _, ns, _ -> ns end)
@ -197,16 +194,15 @@ defmodule RDF.Graph do
end
defp do_add(%__MODULE__{descriptions: descriptions} = graph, subject, statements) do
%__MODULE__{graph |
descriptions:
Map.update(descriptions, subject, Description.new(statements),
fn description ->
%__MODULE__{
graph
| descriptions:
Map.update(descriptions, subject, Description.new(statements), fn description ->
Description.add(description, statements)
end)
}
end
@doc """
Adds statements to a `RDF.Graph` and overwrites all existing statements with the same subjects and predicates.
@ -235,9 +231,9 @@ defmodule RDF.Graph do
def put(graph, %__MODULE__{descriptions: descriptions, prefixes: prefixes}) do
graph =
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
Enum.reduce(descriptions, graph, fn {_, description}, graph ->
put(graph, description)
end
end)
if prefixes do
add_prefixes(graph, prefixes, fn _, ns, _ -> ns end)
@ -247,31 +243,40 @@ defmodule RDF.Graph do
end
def put(%__MODULE__{} = graph, statements) when is_map(statements) do
Enum.reduce statements, graph, fn ({subject, predications}, graph) ->
Enum.reduce(statements, graph, fn {subject, predications}, graph ->
put(graph, subject, predications)
end
end)
end
def put(%__MODULE__{} = graph, statements) when is_list(statements) do
put(graph, Enum.group_by(statements, &(elem(&1, 0)), fn {_, p, o} -> {p, o} end))
put(graph, Enum.group_by(statements, &elem(&1, 0), fn {_, p, o} -> {p, o} end))
end
@doc """
Add statements to a `RDF.Graph`, overwriting all statements with the same subject and predicate.
"""
@spec put(t, Statement.coercible_subject, Description.statements | [Description.statements]) :: t
@spec put(
t,
Statement.coercible_subject(),
Description.statements() | [Description.statements()]
) :: t
def put(graph, subject, predications)
def put(%__MODULE__{descriptions: descriptions} = graph, subject, predications)
when is_list(predications) do
when is_list(predications) do
with subject = coerce_subject(subject) do
# TODO: Can we reduce this case also to do_put somehow? Only the initializer of Map.update differs ...
%__MODULE__{graph |
descriptions:
Map.update(descriptions, subject, Description.new(subject, predications),
fn current ->
Description.put(current, predications)
end)
%__MODULE__{
graph
| descriptions:
Map.update(
descriptions,
subject,
Description.new(subject, predications),
fn current ->
Description.put(current, predications)
end
)
}
end
end
@ -280,10 +285,10 @@ defmodule RDF.Graph do
do: put(graph, subject, [predications])
defp do_put(%__MODULE__{descriptions: descriptions} = graph, subject, statements) do
%__MODULE__{graph |
descriptions:
Map.update(descriptions, subject, Description.new(statements),
fn current ->
%__MODULE__{
graph
| descriptions:
Map.update(descriptions, subject, Description.new(statements), fn current ->
Description.put(current, statements)
end)
}
@ -302,22 +307,21 @@ defmodule RDF.Graph do
"""
@spec put(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def put(%__MODULE__{} = graph, subject, predicate, objects),
do: put(graph, {subject, predicate, objects})
@doc """
Deletes statements from a `RDF.Graph`.
"""
@spec delete(
t,
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object | [Statement.coercible_object]
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def delete(graph, subject, predicate, object),
do: delete(graph, {subject, predicate, object})
@ -341,52 +345,50 @@ defmodule RDF.Graph do
do: delete(graph, {subject, predicate, object})
def delete(%__MODULE__{} = graph, triples) when is_list(triples) do
Enum.reduce triples, graph, fn (triple, graph) ->
Enum.reduce(triples, graph, fn triple, graph ->
delete(graph, triple)
end
end)
end
def delete(%__MODULE__{} = graph, %Description{subject: subject} = description),
do: do_delete(graph, subject, description)
def delete(%__MODULE__{} = graph, %__MODULE__{descriptions: descriptions}) do
Enum.reduce descriptions, graph, fn ({_, description}, graph) ->
Enum.reduce(descriptions, graph, fn {_, description}, graph ->
delete(graph, description)
end
end)
end
defp do_delete(%__MODULE__{descriptions: descriptions} = graph,
subject, statements) do
defp do_delete(%__MODULE__{descriptions: descriptions} = graph, subject, statements) do
with description when not is_nil(description) <- descriptions[subject],
new_description = Description.delete(description, statements)
do
%__MODULE__{graph |
descriptions:
if Enum.empty?(new_description) do
Map.delete(descriptions, subject)
else
Map.put(descriptions, subject, new_description)
end
new_description = Description.delete(description, statements) do
%__MODULE__{
graph
| descriptions:
if Enum.empty?(new_description) do
Map.delete(descriptions, subject)
else
Map.put(descriptions, subject, new_description)
end
}
else
nil -> graph
end
end
@doc """
Deletes all statements with the given subjects.
"""
@spec delete_subjects(
t,
Statement.coercible_subject | [Statement.coercible_subject]
) :: t
t,
Statement.coercible_subject() | [Statement.coercible_subject()]
) :: t
def delete_subjects(graph, subjects)
def delete_subjects(%__MODULE__{} = graph, subjects) when is_list(subjects) do
Enum.reduce subjects, graph, fn (subject, graph) ->
Enum.reduce(subjects, graph, fn subject, graph ->
delete_subjects(graph, subject)
end
end)
end
def delete_subjects(%__MODULE__{descriptions: descriptions} = graph, subject) do
@ -395,7 +397,6 @@ defmodule RDF.Graph do
end
end
@doc """
Updates the description of the `subject` in `graph` with the given function.
@ -428,8 +429,8 @@ defmodule RDF.Graph do
"""
@spec update(
t,
Statement.coercible_subject,
Description.statements | [Description.statements] | nil,
Statement.coercible_subject(),
Description.statements() | [Description.statements()] | nil,
update_description_fun
) :: t
def update(graph = %__MODULE__{}, subject, initial \\ nil, fun) do
@ -447,18 +448,17 @@ defmodule RDF.Graph do
description
|> fun.()
|> case do
nil ->
delete_subjects(graph, subject)
nil ->
delete_subjects(graph, subject)
new_description ->
graph
|> delete_subjects(subject)
|> add(Description.new(subject, new_description))
end
new_description ->
graph
|> delete_subjects(subject)
|> add(Description.new(subject, new_description))
end
end
end
@doc """
Fetches the description of the given subject.
@ -474,7 +474,7 @@ defmodule RDF.Graph do
"""
@impl Access
@spec fetch(t, Statement.coercible_subject) :: {:ok, Description.t} | :error
@spec fetch(t, Statement.coercible_subject()) :: {:ok, Description.t()} | :error
def fetch(%__MODULE__{descriptions: descriptions}, subject) do
Access.fetch(descriptions, coerce_subject(subject))
end
@ -519,29 +519,28 @@ defmodule RDF.Graph do
:bar
"""
@spec get(t, Statement.coercible_subject, Description.t | nil) :: Description.t | nil
@spec get(t, Statement.coercible_subject(), Description.t() | nil) :: Description.t() | nil
def get(%__MODULE__{} = graph, subject, default \\ nil) do
case fetch(graph, subject) do
{:ok, value} -> value
:error -> default
:error -> default
end
end
@doc """
The `RDF.Description` of the given subject.
"""
@spec description(t, Statement.coercible_subject) :: Description.t | nil
@spec description(t, Statement.coercible_subject()) :: Description.t() | nil
def description(%__MODULE__{descriptions: descriptions}, subject),
do: Map.get(descriptions, coerce_subject(subject))
@doc """
All `RDF.Description`s within a `RDF.Graph`.
"""
@spec descriptions(t) :: [Description.t]
@spec descriptions(t) :: [Description.t()]
def descriptions(%__MODULE__{descriptions: descriptions}),
do: Map.values(descriptions)
@doc """
Gets and updates the description of the given subject, in a single pass.
@ -566,38 +565,44 @@ defmodule RDF.Graph do
"""
@impl Access
@spec get_and_update(t, Statement.coercible_subject, get_and_update_description_fun) ::
{Description.t, input}
@spec get_and_update(t, Statement.coercible_subject(), get_and_update_description_fun) ::
{Description.t(), input}
def get_and_update(%__MODULE__{} = graph, subject, fun) do
with subject = coerce_subject(subject) do
case fun.(get(graph, subject)) do
{old_description, new_description} ->
{old_description, put(graph, subject, new_description)}
:pop ->
pop(graph, subject)
other ->
raise "the given function must return a two-element tuple or :pop, got: #{inspect(other)}"
raise "the given function must return a two-element tuple or :pop, got: #{
inspect(other)
}"
end
end
end
@doc """
Pops an arbitrary triple from a `RDF.Graph`.
"""
@spec pop(t) :: {Statement.t | nil, t}
@spec pop(t) :: {Statement.t() | nil, t}
def pop(graph)
def pop(%__MODULE__{descriptions: descriptions} = graph)
when descriptions == %{}, do: {nil, graph}
when descriptions == %{},
do: {nil, graph}
def pop(%__MODULE__{descriptions: descriptions} = graph) do
# TODO: Find a faster way ...
[{subject, description}] = Enum.take(descriptions, 1)
{triple, popped_description} = Description.pop(description)
popped = if Enum.empty?(popped_description),
do: descriptions |> Map.delete(subject),
else: descriptions |> Map.put(subject, popped_description)
popped =
if Enum.empty?(popped_description),
do: descriptions |> Map.delete(subject),
else: descriptions |> Map.put(subject, popped_description)
{triple, %__MODULE__{graph | descriptions: popped}}
end
@ -617,17 +622,17 @@ defmodule RDF.Graph do
"""
@impl Access
@spec pop(t, Statement.coercible_subject) :: {Description.t | nil, t}
@spec pop(t, Statement.coercible_subject()) :: {Description.t() | nil, t}
def pop(%__MODULE__{descriptions: descriptions} = graph, subject) do
case Access.pop(descriptions, coerce_subject(subject)) do
{nil, _} ->
{nil, graph}
{description, new_descriptions} ->
{description, %__MODULE__{graph | descriptions: new_descriptions}}
end
end
@doc """
The number of subjects within a `RDF.Graph`.
@ -660,9 +665,9 @@ defmodule RDF.Graph do
"""
@spec triple_count(t) :: non_neg_integer
def triple_count(%__MODULE__{descriptions: descriptions}) do
Enum.reduce descriptions, 0, fn ({_subject, description}, count) ->
Enum.reduce(descriptions, 0, fn {_subject, description}, count ->
count + Description.count(description)
end
end)
end
@doc """
@ -678,7 +683,7 @@ defmodule RDF.Graph do
MapSet.new([RDF.iri(EX.S1), RDF.iri(EX.S2)])
"""
def subjects(%__MODULE__{descriptions: descriptions}),
do: descriptions |> Map.keys |> MapSet.new
do: descriptions |> Map.keys() |> MapSet.new()
@doc """
The set of all properties used in the predicates of the statements within a `RDF.Graph`.
@ -693,11 +698,11 @@ defmodule RDF.Graph do
MapSet.new([EX.p1, EX.p2])
"""
def predicates(%__MODULE__{descriptions: descriptions}) do
Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) ->
Enum.reduce(descriptions, MapSet.new(), fn {_, description}, acc ->
description
|> Description.predicates
|> Description.predicates()
|> MapSet.union(acc)
end
end)
end
@doc """
@ -717,11 +722,11 @@ defmodule RDF.Graph do
MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)])
"""
def objects(%__MODULE__{descriptions: descriptions}) do
Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) ->
Enum.reduce(descriptions, MapSet.new(), fn {_, description}, acc ->
description
|> Description.objects
|> Description.objects()
|> MapSet.union(acc)
end
end)
end
@doc """
@ -739,11 +744,12 @@ defmodule RDF.Graph do
RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2])
"""
def resources(graph = %__MODULE__{descriptions: descriptions}) do
Enum.reduce(descriptions, MapSet.new, fn ({_, description}, acc) ->
Enum.reduce(descriptions, MapSet.new(), fn {_, description}, acc ->
description
|> Description.resources
|> Description.resources()
|> MapSet.union(acc)
end) |> MapSet.union(subjects(graph))
end)
|> MapSet.union(subjects(graph))
end
@doc """
@ -760,18 +766,19 @@ defmodule RDF.Graph do
{RDF.iri(EX.S1), RDF.iri(EX.p2), RDF.iri(EX.O3)},
{RDF.iri(EX.S2), RDF.iri(EX.p2), RDF.iri(EX.O2)}]
"""
@spec triples(t) :: [Statement.t]
@spec triples(t) :: [Statement.t()]
def triples(%__MODULE__{} = graph), do: Enum.to_list(graph)
defdelegate statements(graph), to: RDF.Graph, as: :triples
@doc """
Checks if the given statement exists within a `RDF.Graph`.
"""
@spec include?(t, Statement.t) :: boolean
def include?(%__MODULE__{descriptions: descriptions},
triple = {subject, _, _}) do
@spec include?(t, Statement.t()) :: boolean
def include?(
%__MODULE__{descriptions: descriptions},
triple = {subject, _, _}
) do
with subject = coerce_subject(subject),
%Description{} <- description = descriptions[subject] do
Description.include?(description, triple)
@ -790,14 +797,13 @@ defmodule RDF.Graph do
iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.coercible_subject) :: boolean
@spec describes?(t, Statement.coercible_subject()) :: boolean
def describes?(%__MODULE__{descriptions: descriptions}, subject) do
with subject = coerce_subject(subject) do
Map.has_key?(descriptions, subject)
end
end
@doc """
Returns a nested map of the native Elixir values of a `RDF.Graph`.
@ -840,13 +846,13 @@ defmodule RDF.Graph do
}
"""
@spec values(t, Statement.term_mapping) :: map
@spec values(t, Statement.term_mapping()) :: map
def values(graph, mapping \\ &RDF.Statement.default_term_mapping/1)
def values(%__MODULE__{descriptions: descriptions}, mapping) do
Map.new descriptions, fn {subject, description} ->
Map.new(descriptions, fn {subject, description} ->
{mapping.({:subject, subject}), Description.values(description, mapping)}
end
end)
end
@doc """
@ -858,26 +864,32 @@ defmodule RDF.Graph do
If `nil` is passed as the `subjects`, the subjects will not be limited.
"""
@spec take(t, [Statement.coercible_subject] | Enum.t | nil, [Statement.coercible_predicate] | Enum.t | nil) :: t
@spec take(
t,
[Statement.coercible_subject()] | Enum.t() | nil,
[Statement.coercible_predicate()] | Enum.t() | nil
) :: t
def take(graph, subjects, properties \\ nil)
def take(%__MODULE__{} = graph, nil, nil), do: graph
def take(%__MODULE__{descriptions: descriptions} = graph, subjects, nil) do
subjects = Enum.map(subjects, &(coerce_subject/1))
subjects = Enum.map(subjects, &coerce_subject/1)
%__MODULE__{graph | descriptions: Map.take(descriptions, subjects)}
end
def take(%__MODULE__{} = graph, subjects, properties) do
graph = take(graph, subjects, nil)
%__MODULE__{graph |
descriptions: Map.new(graph.descriptions, fn {subject, description} ->
{subject, Description.take(description, properties)}
end)
%__MODULE__{
graph
| descriptions:
Map.new(graph.descriptions, fn {subject, description} ->
{subject, Description.take(description, properties)}
end)
}
end
@doc """
Checks if two `RDF.Graph`s are equal.
@ -893,7 +905,6 @@ defmodule RDF.Graph do
def equal?(_, _), do: false
@doc """
Adds `prefixes` to the given `graph`.
@ -906,8 +917,8 @@ defmodule RDF.Graph do
"""
@spec add_prefixes(
t,
PrefixMap.t | map | keyword | nil,
PrefixMap.conflict_resolver | nil
PrefixMap.t() | map | keyword | nil,
PrefixMap.conflict_resolver() | nil
) :: t
def add_prefixes(graph, prefixes, conflict_resolver \\ nil)
@ -922,9 +933,7 @@ defmodule RDF.Graph do
end
def add_prefixes(%__MODULE__{prefixes: prefixes} = graph, additions, conflict_resolver) do
%__MODULE__{graph |
prefixes: RDF.PrefixMap.merge!(prefixes, additions, conflict_resolver)
}
%__MODULE__{graph | prefixes: RDF.PrefixMap.merge!(prefixes, additions, conflict_resolver)}
end
@doc """
@ -933,7 +942,7 @@ defmodule RDF.Graph do
The `prefixes` can be a single prefix or a list of prefixes.
Prefixes not in prefixes of the graph are simply ignored.
"""
@spec delete_prefixes(t, PrefixMap.t) :: t
@spec delete_prefixes(t, PrefixMap.t()) :: t
def delete_prefixes(graph, prefixes)
def delete_prefixes(%__MODULE__{prefixes: nil} = graph, _), do: graph
@ -955,7 +964,7 @@ defmodule RDF.Graph do
The `base_iri` can be given as anything accepted by `RDF.IRI.coerce_base/1`.
"""
@spec set_base_iri(t, IRI.t | nil) :: t
@spec set_base_iri(t, IRI.t() | nil) :: t
def set_base_iri(graph, base_iri)
def set_base_iri(%__MODULE__{} = graph, nil) do
@ -984,16 +993,16 @@ defmodule RDF.Graph do
|> clear_prefixes()
end
defimpl Enumerable do
alias RDF.Graph
def member?(graph, triple), do: {:ok, Graph.include?(graph, triple)}
def count(graph), do: {:ok, Graph.triple_count(graph)}
def slice(_graph), do: {:error, __MODULE__}
def member?(graph, triple), do: {:ok, Graph.include?(graph, triple)}
def count(graph), do: {:ok, Graph.triple_count(graph)}
def slice(_graph), do: {:error, __MODULE__}
def reduce(%Graph{descriptions: descriptions}, {:cont, acc}, _fun)
when map_size(descriptions) == 0, do: {:done, acc}
when map_size(descriptions) == 0,
do: {:done, acc}
def reduce(%Graph{} = graph, {:cont, acc}, fun) do
{triple, rest} = Graph.pop(graph)
@ -1001,6 +1010,7 @@ defmodule RDF.Graph do
end
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(%Graph{} = graph, {:suspend, acc}, fun) do
{:suspended, acc, &reduce(graph, &1, fun)}
end
@ -1011,11 +1021,17 @@ defmodule RDF.Graph do
def into(original) do
collector_fun = fn
graph, {:cont, list} when is_list(list)
-> Graph.add(graph, List.to_tuple(list))
graph, {:cont, elem} -> Graph.add(graph, elem)
graph, :done -> graph
_graph, :halt -> :ok
graph, {:cont, list} when is_list(list) ->
Graph.add(graph, List.to_tuple(list))
graph, {:cont, elem} ->
Graph.add(graph, elem)
graph, :done ->
graph
_graph, :halt ->
:ok
end
{original, collector_fun}

View file

@ -3,35 +3,34 @@ defmodule RDF.InspectHelper do
import Inspect.Algebra
def objects_doc(objects, opts) do
objects
|> Enum.map(fn {object, _} -> to_doc(object, opts) end)
|> fold_doc(fn(object, acc) -> line(object, acc) end)
|> Enum.map(fn {object, _} -> to_doc(object, opts) end)
|> fold_doc(fn object, acc -> line(object, acc) end)
end
def predications_doc(predications, opts) do
predications
|> Enum.map(fn {predicate, objects} ->
to_doc(predicate, opts)
|> line(objects_doc(objects, opts))
|> nest(4)
end)
|> fold_doc(fn(predication, acc) ->
line(predication, acc)
end)
to_doc(predicate, opts)
|> line(objects_doc(objects, opts))
|> nest(4)
end)
|> fold_doc(fn predication, acc ->
line(predication, acc)
end)
end
def descriptions_doc(descriptions, opts) do
descriptions
|> Enum.map(fn {subject, description} ->
to_doc(subject, opts)
|> line(predications_doc(description.predications, opts))
|> nest(4)
end)
|> fold_doc(fn(predication, acc) ->
line(predication, acc)
end)
to_doc(subject, opts)
|> line(predications_doc(description.predications, opts))
|> nest(4)
end)
|> fold_doc(fn predication, acc ->
line(predication, acc)
end)
end
def surround_doc(left, doc, right) do
@ -53,7 +52,7 @@ end
defimpl Inspect, for: RDF.Literal do
def inspect(literal, _opts) do
"%RDF.Literal{literal: #{inspect literal.literal}, valid: #{RDF.Literal.valid?(literal)}}"
"%RDF.Literal{literal: #{inspect(literal.literal)}, valid: #{RDF.Literal.valid?(literal)}}"
end
end
@ -66,6 +65,7 @@ defimpl Inspect, for: RDF.Description do
space("subject:", to_doc(subject, opts))
|> line(predications_doc(predications, opts))
|> nest(4)
surround_doc("#RDF.Description{", doc, "}")
end
end
@ -79,6 +79,7 @@ defimpl Inspect, for: RDF.Graph do
space("name:", to_doc(name, opts))
|> line(descriptions_doc(descriptions, opts))
|> nest(4)
surround_doc("#RDF.Graph{", doc, "}")
end
end
@ -92,12 +93,13 @@ defimpl Inspect, for: RDF.Dataset do
space("name:", to_doc(name, opts))
|> line(graphs_doc(RDF.Dataset.graphs(dataset), opts))
|> nest(4)
surround_doc("#RDF.Dataset{", doc, "}")
end
defp graphs_doc(graphs, opts) do
graphs
|> Enum.map(fn graph -> to_doc(graph, opts) end)
|> fold_doc(fn(graph, acc) -> line(graph, acc) end)
|> Enum.map(fn graph -> to_doc(graph, opts) end)
|> fold_doc(fn graph, acc -> line(graph, acc) end)
end
end

View file

@ -20,10 +20,10 @@ defmodule RDF.IRI do
import RDF.Guards
@type t :: %__MODULE__{
value: String.t
}
value: String.t()
}
@type coercible :: String.t | URI.t | module | t
@type coercible :: String.t() | URI.t() | module | t
@enforce_keys [:value]
defstruct [:value]
@ -44,16 +44,15 @@ defmodule RDF.IRI do
@default_base Application.get_env(:rdf, :default_base_iri)
def default_base, do: @default_base
@doc """
Creates a `RDF.IRI`.
"""
@spec new(coercible) :: t
def new(iri)
def new(iri) when is_binary(iri), do: %__MODULE__{value: iri}
def new(iri) when is_binary(iri), do: %__MODULE__{value: iri}
def new(qname) when maybe_ns_term(qname), do: Namespace.resolve_term!(qname)
def new(%URI{} = uri), do: uri |> URI.to_string |> new
def new(%__MODULE__{} = iri), do: iri
def new(%URI{} = uri), do: uri |> URI.to_string() |> new
def new(%__MODULE__{} = iri), do: iri
@doc """
Creates a `RDF.IRI`, but checks if the given IRI is valid.
@ -64,11 +63,11 @@ defmodule RDF.IRI do
"""
@spec new!(coercible) :: t
def new!(iri)
def new!(iri) when is_binary(iri), do: iri |> valid!() |> new()
def new!(qname) when maybe_ns_term(qname), do: new(qname) # since terms of a namespace are already validated
def new!(%URI{} = uri), do: uri |> valid!() |> new()
def new!(%__MODULE__{} = iri), do: valid!(iri)
def new!(iri) when is_binary(iri), do: iri |> valid!() |> new()
# since terms of a namespace are already validated
def new!(qname) when maybe_ns_term(qname), do: new(qname)
def new!(%URI{} = uri), do: uri |> valid!() |> new()
def new!(%__MODULE__{} = iri), do: valid!(iri)
@doc """
Coerces an IRI serving as a base IRI.
@ -87,8 +86,8 @@ defmodule RDF.IRI do
new(module)
end
end
def coerce_base(base_iri), do: new(base_iri)
def coerce_base(base_iri), do: new(base_iri)
@doc """
Returns the given value unchanged if it's a valid IRI, otherwise raises an exception.
@ -104,11 +103,10 @@ defmodule RDF.IRI do
"""
@spec valid!(coercible) :: coercible
def valid!(iri) do
if not valid?(iri), do: raise RDF.IRI.InvalidError, "Invalid IRI: #{inspect iri}"
if not valid?(iri), do: raise(RDF.IRI.InvalidError, "Invalid IRI: #{inspect(iri)}")
iri
end
@doc """
Checks if the given IRI is valid.
@ -122,8 +120,8 @@ defmodule RDF.IRI do
false
"""
@spec valid?(coercible) :: boolean
def valid?(iri), do: absolute?(iri) # TODO: Provide a more elaborate validation
# TODO: Provide a more elaborate validation
def valid?(iri), do: absolute?(iri)
@doc """
Checks if the given value is an absolute IRI.
@ -135,17 +133,18 @@ defmodule RDF.IRI do
def absolute?(iri)
def absolute?(value) when is_binary(value), do: not is_nil(scheme(value))
def absolute?(%__MODULE__{value: value}), do: absolute?(value)
def absolute?(%URI{scheme: nil}), do: false
def absolute?(%URI{scheme: _}), do: true
def absolute?(%__MODULE__{value: value}), do: absolute?(value)
def absolute?(%URI{scheme: nil}), do: false
def absolute?(%URI{scheme: _}), do: true
def absolute?(qname) when maybe_ns_term(qname) do
case Namespace.resolve_term(qname) do
{:ok, iri} -> absolute?(iri)
_ -> false
end
end
def absolute?(_), do: false
def absolute?(_), do: false
@doc """
Resolves a relative IRI against a base IRI.
@ -162,13 +161,12 @@ defmodule RDF.IRI do
@spec absolute(coercible, coercible) :: t | nil
def absolute(iri, base) do
cond do
absolute?(iri) -> new(iri)
absolute?(iri) -> new(iri)
not absolute?(base) -> nil
true -> merge(base, iri)
true -> merge(base, iri)
end
end
@doc """
Merges two IRIs.
@ -183,7 +181,6 @@ defmodule RDF.IRI do
|> new()
end
@doc """
Returns the scheme of the given IRI
@ -196,28 +193,27 @@ defmodule RDF.IRI do
iex> RDF.IRI.scheme("not an iri")
nil
"""
@spec scheme(coercible) :: String.t | nil
@spec scheme(coercible) :: String.t() | nil
def scheme(iri)
def scheme(%__MODULE__{value: value}), do: scheme(value)
def scheme(%URI{scheme: scheme}), do: scheme
def scheme(%__MODULE__{value: value}), do: scheme(value)
def scheme(%URI{scheme: scheme}), do: scheme
def scheme(qname) when maybe_ns_term(qname), do: Namespace.resolve_term!(qname) |> scheme()
def scheme(iri) when is_binary(iri) do
with [_, scheme] <- Regex.run(@scheme_regex, iri) do
scheme
end
end
@doc """
Parses an IRI into its components and returns them as an `URI` struct.
"""
@spec parse(coercible) :: URI.t
@spec parse(coercible) :: URI.t()
def parse(iri)
def parse(iri) when is_binary(iri), do: URI.parse(iri)
def parse(iri) when is_binary(iri), do: URI.parse(iri)
def parse(qname) when maybe_ns_term(qname), do: Namespace.resolve_term!(qname) |> parse()
def parse(%__MODULE__{value: value}), do: URI.parse(value)
def parse(%URI{} = uri), do: uri
def parse(%__MODULE__{value: value}), do: URI.parse(value)
def parse(%URI{} = uri), do: uri
@doc """
Tests for value equality of IRIs.
@ -226,7 +222,8 @@ defmodule RDF.IRI do
see <https://www.w3.org/TR/rdf-concepts/#section-Graph-URIref>
"""
@spec equal_value?(t | RDF.Literal.t | atom, t | RDF.Literal.t | URI.t | atom) :: boolean | nil
@spec equal_value?(t | RDF.Literal.t() | atom, t | RDF.Literal.t() | URI.t() | atom) ::
boolean | nil
def equal_value?(left, right)
def equal_value?(%__MODULE__{value: left}, %__MODULE__{value: right}),
@ -251,7 +248,6 @@ defmodule RDF.IRI do
def equal_value?(_, _),
do: nil
@doc """
Returns the given IRI as a string.
@ -267,7 +263,7 @@ defmodule RDF.IRI do
"http://example.com/#Foo"
"""
@spec to_string(t | module) :: String.t
@spec to_string(t | module) :: String.t()
def to_string(iri)
def to_string(%__MODULE__{value: value}),

View file

@ -12,16 +12,15 @@ defmodule RDF.List do
import RDF.Guards
@type t :: %__MODULE__{
head: IRI.t,
graph: Graph.t
}
head: IRI.t(),
graph: Graph.t()
}
@enforce_keys [:head]
defstruct [:head, :graph]
@rdf_nil RDF.Utils.Bootstrapping.rdf_iri("nil")
@doc """
Creates a `RDF.List` for a given RDF list node of a given `RDF.Graph`.
@ -33,7 +32,7 @@ defmodule RDF.List do
- does not contain cycles, i.e. `rdf:rest` statements don't refer to
preceding list nodes
"""
@spec new(IRI.coercible, Graph.t) :: t
@spec new(IRI.coercible(), Graph.t()) :: t
def new(head, graph)
def new(head, graph) when maybe_ns_term(head),
@ -48,7 +47,7 @@ defmodule RDF.List do
end
defp well_formed?(list) do
Enum.reduce_while(list, MapSet.new, fn node_description, preceding_nodes ->
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}
@ -59,7 +58,6 @@ defmodule RDF.List do
end) && true
end
@doc """
Creates a `RDF.List` from a native Elixir list or any other `Enumerable` with coercible RDF values.
@ -73,18 +71,17 @@ defmodule RDF.List do
the head node of the empty list is always `RDF.nil`.
"""
@spec from(Enumerable.t, keyword) :: t
@spec from(Enumerable.t(), keyword) :: t
def from(list, opts \\ []) do
with head = Keyword.get(opts, :head, RDF.bnode),
graph = Keyword.get(opts, :graph, RDF.graph),
{head, graph} = do_from(list, head, graph, opts)
do
with head = Keyword.get(opts, :head, RDF.bnode()),
graph = Keyword.get(opts, :graph, RDF.graph()),
{head, graph} = do_from(list, head, graph, opts) do
%__MODULE__{head: head, graph: graph}
end
end
defp do_from([], _, graph, _) do
{RDF.nil, graph}
{RDF.nil(), graph}
end
defp do_from(list, head, graph, opts) when maybe_ns_term(head) do
@ -92,16 +89,17 @@ defmodule RDF.List do
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
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
with {next, graph} = do_from(rest, RDF.bnode(), graph, opts) do
{
head,
Graph.add(graph,
Graph.add(
graph,
head
|> RDF.first(first)
|> RDF.rest(next)
@ -116,16 +114,16 @@ defmodule RDF.List do
|> do_from(head, graph, opts)
end
@doc """
The values of a `RDF.List` as an Elixir list.
Nested lists are converted recursively.
"""
@spec values(t) :: Enumerable.t
@spec values(t) :: Enumerable.t()
def values(%__MODULE__{graph: graph} = list) do
Enum.map list, fn node_description ->
value = Description.first(node_description, RDF.first)
Enum.map(list, fn node_description ->
value = Description.first(node_description, RDF.first())
if node?(value, graph) do
value
|> new(graph)
@ -133,26 +131,23 @@ defmodule RDF.List do
else
value
end
end
end)
end
@doc """
The RDF nodes constituting a `RDF.List` as an Elixir list.
"""
@spec nodes(t) :: [BlankNode.t]
@spec nodes(t) :: [BlankNode.t()]
def nodes(%__MODULE__{} = list) do
Enum.map list, fn node_description -> node_description.subject end
Enum.map(list, fn node_description -> node_description.subject end)
end
@doc """
Checks if a list is the empty list.
"""
@spec empty?(t) :: boolean
def empty?(%__MODULE__{head: @rdf_nil}), do: true
def empty?(%__MODULE__{}), do: false
def empty?(%__MODULE__{}), do: false
@doc """
Checks if the given list consists of list nodes which are all blank nodes.
@ -161,12 +156,11 @@ defmodule RDF.List do
def valid?(%__MODULE__{head: @rdf_nil}), do: true
def valid?(%__MODULE__{} = list) do
Enum.all? list, fn node_description ->
Enum.all?(list, fn node_description ->
RDF.bnode?(node_description.subject)
end
end)
end
@doc """
Checks if a given resource is a RDF list node in a given `RDF.Graph`.
@ -176,7 +170,7 @@ defmodule RDF.List do
Note: This function doesn't indicate if the list is valid.
See `new/2` and `valid?/2` for validations.
"""
@spec node?(any, Graph.t) :: boolean
@spec node?(any, Graph.t()) :: boolean
def node?(list_node, graph)
def node?(@rdf_nil, _),
@ -204,15 +198,14 @@ defmodule RDF.List do
def node?(nil), do: false
def node?(%Description{predications: predications}) do
Map.has_key?(predications, RDF.first) and
Map.has_key?(predications, RDF.rest)
Map.has_key?(predications, RDF.first()) and
Map.has_key?(predications, RDF.rest())
end
defimpl Enumerable do
@rdf_nil RDF.Utils.Bootstrapping.rdf_iri("nil")
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
def reduce(%RDF.List{head: @rdf_nil}, {:cont, acc}, _fun),
@ -226,19 +219,17 @@ defmodule RDF.List do
def reduce(_, _, _), do: {:halted, nil}
defp do_reduce(%RDF.List{head: head, graph: graph},
{:cont, acc}, fun) do
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
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)
_ -> reduce(%RDF.List{head: rest, graph: graph}, acc, fun)
end
else
reduce(%RDF.List{head: rest, graph: graph}, acc, fun)
@ -246,13 +237,14 @@ defmodule RDF.List do
else
nil ->
{:halted, nil}
values when is_list(values) ->
{:halted, nil}
end
end
def member?(_, _), do: {:error, __MODULE__}
def count(_), do: {:error, __MODULE__}
def slice(_), do: {:error, __MODULE__}
def member?(_, _), do: {:error, __MODULE__}
def count(_), do: {:error, __MODULE__}
def slice(_), do: {:error, __MODULE__}
end
end

View file

@ -35,8 +35,10 @@ defmodule RDF.Literal do
def new(value) do
case coerce(value) do
nil ->
raise RDF.Literal.InvalidError, "#{inspect value} not convertible to a RDF.Literal"
literal -> literal
raise RDF.Literal.InvalidError, "#{inspect(value)} not convertible to a RDF.Literal"
literal ->
literal
end
end
@ -58,7 +60,7 @@ defmodule RDF.Literal do
datatype = Keyword.get(opts, :datatype) ->
case Datatype.get(datatype) do
nil -> Generic.new(value, opts)
nil -> Generic.new(value, opts)
datatype -> datatype.new(value, opts)
end
@ -98,16 +100,16 @@ defmodule RDF.Literal do
def coerce(%__MODULE__{} = literal), do: literal
def coerce(value) when is_binary(value), do: RDF.XSD.String.new(value)
def coerce(value) when is_binary(value), do: RDF.XSD.String.new(value)
def coerce(value) when is_boolean(value), do: RDF.XSD.Boolean.new(value)
def coerce(value) when is_integer(value), do: RDF.XSD.Integer.new(value)
def coerce(value) when is_float(value), do: RDF.XSD.Double.new(value)
def coerce(%Decimal{} = value), do: RDF.XSD.Decimal.new(value)
def coerce(%Date{} = value), do: RDF.XSD.Date.new(value)
def coerce(%Time{} = value), do: RDF.XSD.Time.new(value)
def coerce(%DateTime{} = value), do: RDF.XSD.DateTime.new(value)
def coerce(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value)
def coerce(%URI{} = value), do: RDF.XSD.AnyURI.new(value)
def coerce(value) when is_float(value), do: RDF.XSD.Double.new(value)
def coerce(%Decimal{} = value), do: RDF.XSD.Decimal.new(value)
def coerce(%Date{} = value), do: RDF.XSD.Date.new(value)
def coerce(%Time{} = value), do: RDF.XSD.Time.new(value)
def coerce(%DateTime{} = value), do: RDF.XSD.DateTime.new(value)
def coerce(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value)
def coerce(%URI{} = value), do: RDF.XSD.AnyURI.new(value)
def coerce(value) when maybe_ns_term(value) do
case RDF.Namespace.resolve_term(value) do
@ -132,7 +134,6 @@ defmodule RDF.Literal do
def coerce(_), do: nil
@doc """
Creates a new `RDF.Literal`, but fails if it's not valid.
@ -154,10 +155,11 @@ defmodule RDF.Literal do
@spec new!(t | any, keyword) :: t
def new!(value, opts \\ []) do
literal = new(value, opts)
if valid?(literal) do
literal
else
raise RDF.Literal.InvalidError, "invalid RDF.Literal: #{inspect literal}"
raise RDF.Literal.InvalidError, "invalid RDF.Literal: #{inspect(literal)}"
end
end
@ -223,8 +225,7 @@ defmodule RDF.Literal do
"""
@spec simple?(t) :: boolean
def simple?(%__MODULE__{literal: %RDF.XSD.String{}}), do: true
def simple?(%__MODULE__{}), do: false
def simple?(%__MODULE__{}), do: false
@doc """
Returns if a literal is a plain literal.
@ -263,7 +264,7 @@ defmodule RDF.Literal do
@doc """
Returns the lexical form of the given `literal`.
"""
@spec lexical(t) :: String.t
@spec lexical(t) :: String.t()
def lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.lexical(literal)
@doc """
@ -275,8 +276,9 @@ defmodule RDF.Literal do
@doc """
Returns the canonical lexical of the given `literal`.
"""
@spec canonical_lexical(t) :: String.t | nil
def canonical_lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical_lexical(literal)
@spec canonical_lexical(t) :: String.t() | nil
def canonical_lexical(%__MODULE__{literal: %datatype{} = literal}),
do: datatype.canonical_lexical(literal)
@doc """
Returns if the lexical of the given `literal` has the canonical form.
@ -311,7 +313,7 @@ defmodule RDF.Literal do
def equal_value?(_, _), do: nil
@spec compare(t, t) :: Datatype.comparison_result | :indeterminate | nil
@spec compare(t, t) :: Datatype.comparison_result() | :indeterminate | nil
def compare(%__MODULE__{literal: %datatype{} = left}, right) do
datatype.compare(left, right)
end
@ -339,16 +341,21 @@ defmodule RDF.Literal do
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@spec matches?(t | String.t, pattern :: t | String.t, flags :: t | String.t) :: boolean
@spec matches?(t | String.t(), pattern :: t | String.t(), flags :: t | String.t()) :: boolean
def matches?(value, pattern, flags \\ "")
def matches?(%__MODULE__{} = literal, pattern, flags),
do: matches?(lexical(literal), pattern, flags)
def matches?(value, %__MODULE__{literal: %RDF.XSD.String{}} = pattern, flags),
do: matches?(value, lexical(pattern), flags)
def matches?(value, pattern, %__MODULE__{literal: %RDF.XSD.String{}} = flags),
do: matches?(value, pattern, lexical(flags))
def matches?(value, pattern, flags) when is_binary(value) and is_binary(pattern) and is_binary(flags),
do: RDF.XSD.Utils.Regex.matches?(value, pattern, flags)
def matches?(value, pattern, flags)
when is_binary(value) and is_binary(pattern) and is_binary(flags),
do: RDF.XSD.Utils.Regex.matches?(value, pattern, flags)
@doc """
Updates the value of a `RDF.Literal` without changing everything else.

View file

@ -53,7 +53,7 @@ defmodule RDF.Literal.Datatype do
A final catch-all clause should delegate to `super`. For example `RDF.XSD.Datatype`s will handle casting from derived
datatypes in the default implementation.
"""
@callback do_cast(literal | RDF.IRI.t | RDF.BlankNode.t) :: Literal.t() | nil
@callback do_cast(literal | RDF.IRI.t() | RDF.BlankNode.t()) :: Literal.t() | nil
@doc """
Checks if the given `RDF.Literal` has the datatype for which the `RDF.Literal.Datatype` is implemented or is derived from it.
@ -64,24 +64,24 @@ defmodule RDF.Literal.Datatype do
true
"""
@callback datatype?(Literal.t | t | literal) :: boolean
@callback datatype?(Literal.t() | t | literal) :: boolean
@doc """
The datatype IRI of the given `RDF.Literal`.
"""
@callback datatype_id(Literal.t | literal) :: IRI.t()
@callback datatype_id(Literal.t() | literal) :: IRI.t()
@doc """
The language of the given `RDF.Literal` if present.
"""
@callback language(Literal.t | literal) :: String.t() | nil
@callback language(Literal.t() | literal) :: String.t() | nil
@doc """
Returns the value of a `RDF.Literal`.
This function also accepts literals of derived datatypes.
"""
@callback value(Literal.t | literal) :: any
@callback value(Literal.t() | literal) :: any
@doc """
Returns the lexical form of a `RDF.Literal`.
@ -178,7 +178,7 @@ defmodule RDF.Literal.Datatype do
iex> RDF.literal("foo", datatype: "http://example.com/dt") |> RDF.Literal.Generic.update(fn _ -> "bar" end)
RDF.literal("bar", datatype: "http://example.com/dt")
"""
@callback update(Literal.t() | literal, fun()) :: Literal.t
@callback update(Literal.t() | literal, fun()) :: Literal.t()
@doc """
Updates the value of a `RDF.Literal` without changing anything else.
@ -192,7 +192,7 @@ defmodule RDF.Literal.Datatype do
...> fn value -> value <> "1" end, as: :lexical)
RDF.XSD.integer(421)
"""
@callback update(Literal.t() | literal, fun(), keyword) :: Literal.t
@callback update(Literal.t() | literal, fun(), keyword) :: Literal.t()
@doc """
Returns the `RDF.Literal.Datatype` for a datatype IRI.
@ -200,14 +200,13 @@ defmodule RDF.Literal.Datatype do
defdelegate get(id), to: Literal.Datatype.Registry, as: :datatype
@doc !"""
As opposed to RDF.Literal.valid?/1 this function operates on the datatype structs ...
As opposed to RDF.Literal.valid?/1 this function operates on the datatype structs ...
It's meant for internal use only and doesn't perform checks if the struct
passed is actually a `RDF.Literal.Datatype` struct.
"""
It's meant for internal use only and doesn't perform checks if the struct
passed is actually a `RDF.Literal.Datatype` struct.
"""
def valid?(%datatype{} = datatype_literal), do: datatype.valid?(datatype_literal)
defmacro __using__(opts) do
name = Keyword.fetch!(opts, :name)
id = Keyword.fetch!(opts, :id)
@ -227,10 +226,10 @@ defmodule RDF.Literal.Datatype do
@behaviour unquote(__MODULE__)
@doc !"""
This function is just used to check if a module is a RDF.Literal.Datatype.
This function is just used to check if a module is a RDF.Literal.Datatype.
See `RDF.Literal.Datatype.Registry.is_rdf_literal_datatype?/1`.
"""
See `RDF.Literal.Datatype.Registry.is_rdf_literal_datatype?/1`.
"""
def __rdf_literal_datatype_indicator__, do: true
@name unquote(name)
@ -274,6 +273,7 @@ defmodule RDF.Literal.Datatype do
literal |> canonical() |> lexical()
end
end
def canonical_lexical(_), do: nil
@doc """
@ -284,15 +284,17 @@ defmodule RDF.Literal.Datatype do
Implementations define the casting for a given value with the `c:RDF.Literal.Datatype.do_cast/1` callback.
"""
@spec cast(Literal.Datatype.literal | RDF.Term.t) :: Literal.t() | nil
@spec cast(Literal.Datatype.literal() | RDF.Term.t()) :: Literal.t() | nil
@dialyzer {:nowarn_function, cast: 1}
def cast(literal_or_value)
def cast(%Literal{literal: literal}), do: cast(literal)
def cast(%__MODULE__{} = datatype_literal),
do: if(valid?(datatype_literal), do: literal(datatype_literal))
do: if(valid?(datatype_literal), do: literal(datatype_literal))
def cast(%struct{} = datatype_literal) do
if (Literal.datatype?(struct) and Literal.Datatype.valid?(datatype_literal)) or
struct in [RDF.IRI, RDF.BlankNode] do
struct in [RDF.IRI, RDF.BlankNode] do
case do_cast(datatype_literal) do
%__MODULE__{} = literal -> if valid?(literal), do: literal(literal)
%Literal{literal: %__MODULE__{}} = literal -> if valid?(literal), do: literal
@ -325,10 +327,15 @@ defmodule RDF.Literal.Datatype do
def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(nil, _), do: nil
def equal_value?(_, nil), do: nil
def equal_value?(left, right) do
cond do
not Literal.datatype?(right) and not resource?(right) -> equal_value?(left, Literal.coerce(right))
not Literal.datatype?(left) and not resource?(left) -> equal_value?(Literal.coerce(left), right)
not Literal.datatype?(right) and not resource?(right) ->
equal_value?(left, Literal.coerce(right))
not Literal.datatype?(left) and not resource?(left) ->
equal_value?(Literal.coerce(left), right)
true ->
left_datatype = left.__struct__
right_datatype = right.__struct__
@ -343,6 +350,7 @@ defmodule RDF.Literal.Datatype do
case equality_path(left_datatype, right_datatype) do
{:same_or_derived, datatype} ->
datatype.do_equal_value_same_or_derived_datatypes?(left, right)
{:different, datatype} ->
datatype.do_equal_value_different_datatypes?(left, right)
end
@ -381,14 +389,15 @@ defmodule RDF.Literal.Datatype do
# RDF.XSD.Datatypes offers another default implementation, but since it is
# still in a macro implementation defoverridable doesn't work
unless RDF.XSD.Datatype in @behaviour do
@spec compare(RDF.Literal.t() | any, RDF.Literal.t() | any) :: RDF.Literal.Datatype.comparison_result | :indeterminate | nil
@spec compare(RDF.Literal.t() | any, RDF.Literal.t() | any) ::
RDF.Literal.Datatype.comparison_result() | :indeterminate | nil
def compare(left, right)
def compare(left, %RDF.Literal{literal: right}), do: compare(left, right)
def compare(%RDF.Literal{literal: left}, right), do: compare(left, right)
def compare(left, right) do
if RDF.Literal.datatype?(left) and RDF.Literal.datatype?(right) and
RDF.Literal.Datatype.valid?(left) and RDF.Literal.Datatype.valid?(right) do
RDF.Literal.Datatype.valid?(left) and RDF.Literal.Datatype.valid?(right) do
do_compare(left, right)
end
end
@ -396,8 +405,12 @@ defmodule RDF.Literal.Datatype do
@impl RDF.Literal.Datatype
def do_compare(%datatype{} = left, %datatype{} = right) do
case {datatype.value(left), datatype.value(right)} do
{left_value, right_value} when left_value < right_value -> :lt
{left_value, right_value} when left_value > right_value -> :gt
{left_value, right_value} when left_value < right_value ->
:lt
{left_value, right_value} when left_value > right_value ->
:gt
_ ->
if datatype.equal_value?(left, right), do: :eq
end
@ -415,6 +428,7 @@ defmodule RDF.Literal.Datatype do
@impl unquote(__MODULE__)
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, opts) do
case Keyword.get(opts, :as) do
:lexical -> lexical(literal)
@ -449,7 +463,7 @@ defmodule RDF.Literal.Datatype do
import ProtocolEx
defimpl_ex Registration, unquote(unquoted_id),
for: RDF.Literal.Datatype.Registry.Registration do
for: RDF.Literal.Datatype.Registry.Registration do
@moduledoc false
def datatype(id), do: unquote(datatype)

View file

@ -13,37 +13,38 @@ defmodule RDF.Literal.Datatype.Registry do
RDF.XSD.Double
]
@builtin_numeric_datatypes @primitive_numeric_datatypes ++ [
RDF.XSD.Long,
RDF.XSD.Int,
RDF.XSD.Short,
RDF.XSD.Byte,
RDF.XSD.NonNegativeInteger,
RDF.XSD.PositiveInteger,
RDF.XSD.UnsignedLong,
RDF.XSD.UnsignedInt,
RDF.XSD.UnsignedShort,
RDF.XSD.UnsignedByte,
RDF.XSD.NonPositiveInteger,
RDF.XSD.NegativeInteger,
RDF.XSD.Float
]
@builtin_numeric_datatypes @primitive_numeric_datatypes ++
[
RDF.XSD.Long,
RDF.XSD.Int,
RDF.XSD.Short,
RDF.XSD.Byte,
RDF.XSD.NonNegativeInteger,
RDF.XSD.PositiveInteger,
RDF.XSD.UnsignedLong,
RDF.XSD.UnsignedInt,
RDF.XSD.UnsignedShort,
RDF.XSD.UnsignedByte,
RDF.XSD.NonPositiveInteger,
RDF.XSD.NegativeInteger,
RDF.XSD.Float
]
@builtin_xsd_datatypes [
XSD.Boolean,
XSD.String,
XSD.Date,
XSD.Time,
XSD.DateTime,
XSD.AnyURI
] ++ @builtin_numeric_datatypes
XSD.Boolean,
XSD.String,
XSD.Date,
XSD.Time,
XSD.DateTime,
XSD.AnyURI
] ++ @builtin_numeric_datatypes
@builtin_datatypes [RDF.LangString | @builtin_xsd_datatypes]
@doc """
Returns a list of all builtin `RDF.Literal.Datatype` modules.
"""
@spec builtin_datatypes :: [RDF.Literal.Datatype.t]
@spec builtin_datatypes :: [RDF.Literal.Datatype.t()]
def builtin_datatypes, do: @builtin_datatypes
@doc """
@ -63,7 +64,7 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Checks if the given module is a builtin datatype or a registered custom datatype implementing the `RDF.Literal.Datatype` behaviour.
"""
@spec datatype?(Literal.t | Literal.Datatype.literal | module) :: boolean
@spec datatype?(Literal.t() | Literal.Datatype.literal() | module) :: boolean
def datatype?(value)
# We assume literals were created properly which means they have a proper RDF.Literal.Datatype
@ -71,7 +72,7 @@ defmodule RDF.Literal.Datatype.Registry do
def datatype?(value), do: datatype_struct?(value)
@doc false
@spec datatype_struct?(Literal.Datatype.literal | module) :: boolean
@spec datatype_struct?(Literal.Datatype.literal() | module) :: boolean
def datatype_struct?(value)
def datatype_struct?(%datatype{}), do: datatype_struct?(datatype)
@ -87,7 +88,7 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Returns a list of all builtin `RDF.XSD.Datatype` modules.
"""
@spec builtin_xsd_datatypes :: [RDF.Literal.Datatype.t]
@spec builtin_xsd_datatypes :: [RDF.Literal.Datatype.t()]
def builtin_xsd_datatypes, do: @builtin_xsd_datatypes
@doc false
@ -103,13 +104,13 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Checks if the given module is a builtin XSD datatype or a registered custom datatype implementing the `RDF.XSD.Datatype` behaviour.
"""
@spec xsd_datatype?(Literal.t | XSD.Datatype.literal | module) :: boolean
@spec xsd_datatype?(Literal.t() | XSD.Datatype.literal() | module) :: boolean
def xsd_datatype?(value)
def xsd_datatype?(%Literal{literal: datatype_struct}), do: xsd_datatype?(datatype_struct)
def xsd_datatype?(value), do: xsd_datatype_struct?(value)
@doc false
@spec xsd_datatype_struct?(RDF.Literal.t() | XSD.Datatype.literal | module) :: boolean
@spec xsd_datatype_struct?(RDF.Literal.t() | XSD.Datatype.literal() | module) :: boolean
def xsd_datatype_struct?(value)
def xsd_datatype_struct?(%datatype{}), do: xsd_datatype_struct?(datatype)
@ -123,13 +124,13 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Returns a list of all numeric datatype modules.
"""
@spec builtin_numeric_datatypes() :: [RDF.Literal.Datatype.t]
@spec builtin_numeric_datatypes() :: [RDF.Literal.Datatype.t()]
def builtin_numeric_datatypes(), do: @builtin_numeric_datatypes
@doc """
The set of all primitive numeric datatypes.
"""
@spec primitive_numeric_datatypes() :: [RDF.Literal.Datatype.t]
@spec primitive_numeric_datatypes() :: [RDF.Literal.Datatype.t()]
def primitive_numeric_datatypes(), do: @primitive_numeric_datatypes
@doc false
@ -142,7 +143,6 @@ defmodule RDF.Literal.Datatype.Registry do
def builtin_numeric_datatype?(_), do: false
@doc """
Returns if a given literal or datatype has or is a numeric datatype.
"""
@ -152,12 +152,11 @@ defmodule RDF.Literal.Datatype.Registry do
def numeric_datatype?(%datatype{}), do: numeric_datatype?(datatype)
def numeric_datatype?(datatype) when maybe_module(datatype) do
builtin_numeric_datatype?(datatype) or (
xsd_datatype?(datatype) and
Enum.any?(@primitive_numeric_datatypes, fn numeric_primitive ->
datatype.derived_from?(numeric_primitive)
end)
)
builtin_numeric_datatype?(datatype) or
(xsd_datatype?(datatype) and
Enum.any?(@primitive_numeric_datatypes, fn numeric_primitive ->
datatype.derived_from?(numeric_primitive)
end))
end
def numeric_datatype?(_), do: false
@ -165,7 +164,7 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Returns the `RDF.Literal.Datatype` for a datatype IRI.
"""
@spec datatype(Literal.t | IRI.t | String.t) :: Literal.Datatype.t
@spec datatype(Literal.t() | IRI.t() | String.t()) :: Literal.Datatype.t()
def datatype(%Literal{} = literal), do: literal.literal.__struct__
def datatype(%IRI{} = id), do: id |> to_string() |> datatype()
def datatype(id) when maybe_ns_term(id), do: id |> Namespace.resolve_term!() |> datatype()
@ -174,7 +173,7 @@ defmodule RDF.Literal.Datatype.Registry do
@doc """
Returns the `RDF.XSD.Datatype` for a datatype IRI.
"""
@spec xsd_datatype(Literal.t | IRI.t | String.t) :: XSD.Datatype.t
@spec xsd_datatype(Literal.t() | IRI.t() | String.t()) :: XSD.Datatype.t()
def xsd_datatype(id) do
datatype = datatype(id)

View file

@ -14,16 +14,18 @@ defmodule RDF.Literal.Generic do
import RDF.Guards
@type t :: %__MODULE__{
value: String.t,
datatype: String.t
}
value: String.t(),
datatype: String.t()
}
@impl Datatype
@spec new(any, String.t | IRI.t | keyword) :: Literal.t
@spec new(any, String.t() | IRI.t() | keyword) :: Literal.t()
def new(value, datatype_or_opts \\ [])
def new(value, %IRI{} = datatype), do: new(value, datatype: datatype)
def new(value, datatype) when is_binary(datatype) or maybe_ns_term(datatype),
do: new(value, datatype: datatype)
def new(value, opts) do
%Literal{
literal: %__MODULE__{
@ -36,18 +38,24 @@ defmodule RDF.Literal.Generic do
defp normalize_datatype(nil), do: nil
defp normalize_datatype(""), do: nil
defp normalize_datatype(%IRI{} = datatype), do: to_string(datatype)
defp normalize_datatype(datatype) when maybe_ns_term(datatype), do: datatype |> RDF.iri() |> to_string()
defp normalize_datatype(datatype) when maybe_ns_term(datatype),
do: datatype |> RDF.iri() |> to_string()
defp normalize_datatype(datatype), do: datatype
@impl Datatype
@spec new!(any, String.t | IRI.t | keyword) :: Literal.t
@spec new!(any, String.t() | IRI.t() | keyword) :: Literal.t()
def new!(value, datatype_or_opts \\ []) do
literal = new(value, datatype_or_opts)
if valid?(literal) do
literal
else
raise ArgumentError, "#{inspect(value)} with datatype #{inspect literal.literal.datatype} is not a valid #{inspect(__MODULE__)}"
raise ArgumentError,
"#{inspect(value)} with datatype #{inspect(literal.literal.datatype)} is not a valid #{
inspect(__MODULE__)
}"
end
end
@ -88,15 +96,23 @@ defmodule RDF.Literal.Generic do
def do_equal_value_same_or_derived_datatypes?(
%{datatype: datatype} = left,
%{datatype: datatype} = right
), do: left == right
),
do: left == right
def do_equal_value_same_or_derived_datatypes?(_, _), do: nil
@impl Datatype
def do_compare(%__MODULE__{datatype: datatype} = left_literal,
%__MODULE__{datatype: datatype} = right_literal) do
def do_compare(
%__MODULE__{datatype: datatype} = left_literal,
%__MODULE__{datatype: datatype} = right_literal
) do
case {left_literal.value, right_literal.value} do
{left_value, right_value} when left_value < right_value -> :lt
{left_value, right_value} when left_value > right_value -> :gt
{left_value, right_value} when left_value < right_value ->
:lt
{left_value, right_value} when left_value > right_value ->
:gt
_ ->
if equal_value?(left_literal, right_literal), do: :eq
end
@ -107,6 +123,7 @@ defmodule RDF.Literal.Generic do
@impl Datatype
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, _opts) do
literal
|> value()

View file

@ -6,8 +6,8 @@ defmodule RDF.LangString do
defstruct [:value, :language]
use RDF.Literal.Datatype,
name: "langString",
id: RDF.Utils.Bootstrapping.rdf_iri("langString")
name: "langString",
id: RDF.Utils.Bootstrapping.rdf_iri("langString")
import RDF.Utils.Guards
@ -15,18 +15,19 @@ defmodule RDF.LangString do
alias RDF.Literal
@type t :: %__MODULE__{
value: String.t,
language: String.t
}
value: String.t(),
language: String.t()
}
@doc """
Creates a new `RDF.Literal` with this datatype and the given `value` and `language`.
"""
@impl RDF.Literal.Datatype
@spec new(any, String.t | atom | keyword) :: Literal.t
@spec new(any, String.t() | atom | keyword) :: Literal.t()
def new(value, language_or_opts \\ [])
def new(value, language) when is_binary(language), do: new(value, language: language)
def new(value, language) when is_ordinary_atom(language), do: new(value, language: language)
def new(value, opts) do
%Literal{
literal: %__MODULE__{
@ -38,18 +39,24 @@ defmodule RDF.LangString do
defp normalize_language(nil), do: nil
defp normalize_language(""), do: nil
defp normalize_language(language) when is_ordinary_atom(language), do: language |> to_string() |> normalize_language()
defp normalize_language(language) when is_ordinary_atom(language),
do: language |> to_string() |> normalize_language()
defp normalize_language(language), do: String.downcase(language)
@impl RDF.Literal.Datatype
@spec new!(any, String.t | atom | keyword) :: Literal.t
@spec new!(any, String.t() | atom | keyword) :: Literal.t()
def new!(value, language_or_opts \\ []) do
literal = new(value, language_or_opts)
if valid?(literal) do
literal
else
raise ArgumentError, "#{inspect(value)} with language #{inspect literal.literal.language} is not a valid #{inspect(__MODULE__)}"
raise ArgumentError,
"#{inspect(value)} with language #{inspect(literal.literal.language)} is not a valid #{
inspect(__MODULE__)
}"
end
end
@ -84,6 +91,7 @@ defmodule RDF.LangString do
@impl Datatype
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, _opts) do
literal
|> value()
@ -102,12 +110,14 @@ defmodule RDF.LangString do
see <https://www.w3.org/TR/sparql11-query/#func-langMatches>
"""
@spec match_language?(Literal.t | t() | String.t, String.t) :: boolean
@spec match_language?(Literal.t() | t() | String.t(), String.t()) :: boolean
def match_language?(language_tag, language_range)
def match_language?(%Literal{literal: literal}, language_range),
do: match_language?(literal, language_range)
def match_language?(%__MODULE__{language: nil}, _), do: false
def match_language?(%__MODULE__{language: language_tag}, language_range),
do: match_language?(language_tag, language_range)
@ -121,7 +131,7 @@ defmodule RDF.LangString do
case String.split(language_tag, language_range, parts: 2) do
[_, rest] -> rest == "" or String.starts_with?(rest, "-")
_ -> false
_ -> false
end
end

View file

@ -14,14 +14,13 @@ defmodule RDF.Namespace do
@doc """
Resolves a term to a `RDF.IRI`.
"""
@callback __resolve_term__(atom) :: {:ok, IRI.t} | {:error, Exception.t}
@callback __resolve_term__(atom) :: {:ok, IRI.t()} | {:error, Exception.t()}
@doc """
All terms of a `RDF.Namespace`.
"""
@callback __terms__() :: [atom]
@doc """
Resolves a qualified term to a `RDF.IRI`.
@ -29,7 +28,7 @@ defmodule RDF.Namespace do
delegates to remaining part of the term to `__resolve_term__/1` of this
determined namespace.
"""
@spec resolve_term(IRI.t | module) :: {:ok, IRI.t} | {:error, Exception.t}
@spec resolve_term(IRI.t() | module) :: {:ok, IRI.t()} | {:error, Exception.t()}
def resolve_term(expr)
def resolve_term(%IRI{} = iri), do: {:ok, iri}
@ -45,7 +44,7 @@ defmodule RDF.Namespace do
See `resolve_term/1` for more.
"""
@spec resolve_term!(IRI.t | module) :: IRI.t
@spec resolve_term!(IRI.t() | module) :: IRI.t()
def resolve_term!(expr) do
with {:ok, iri} <- resolve_term(expr) do
iri
@ -57,7 +56,7 @@ defmodule RDF.Namespace do
defp do_resolve_term("Elixir." <> _ = namespaced_term) do
{term, namespace} =
namespaced_term
|> Module.split
|> Module.split()
|> List.pop_at(-1)
do_resolve_term(Module.concat(namespace), String.to_atom(term))
@ -65,20 +64,18 @@ defmodule RDF.Namespace do
defp do_resolve_term(namespaced_term) do
{:error,
%RDF.Namespace.UndefinedTermError{
message: "#{namespaced_term} is not a term on a RDF.Namespace"
}
}
%RDF.Namespace.UndefinedTermError{
message: "#{namespaced_term} is not a term on a RDF.Namespace"
}}
end
defp do_resolve_term(RDF, term), do: do_resolve_term(RDF.NS.RDF, term)
defp do_resolve_term(Elixir, term) do
{:error,
%RDF.Namespace.UndefinedTermError{message:
"#{term} is not a RDF.Namespace; top-level modules can't be RDF.Namespaces"
}
}
%RDF.Namespace.UndefinedTermError{
message: "#{term} is not a RDF.Namespace; top-level modules can't be RDF.Namespaces"
}}
end
defp do_resolve_term(namespace, term) do
@ -91,9 +88,7 @@ defmodule RDF.Namespace do
if is_module and Keyword.has_key?(namespace.__info__(:functions), :__resolve_term__) do
namespace.__resolve_term__(term)
else
{:error,
%RDF.Namespace.UndefinedTermError{message: "#{namespace} is not a RDF.Namespace"}
}
{:error, %RDF.Namespace.UndefinedTermError{message: "#{namespace} is not a RDF.Namespace"}}
end
end
end

View file

@ -26,7 +26,7 @@ defmodule RDF.NS do
base_iri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
file: "rdf.ttl",
alias: [
Nil: "nil",
Nil: "nil",
LangString: "langString"
]

View file

@ -7,11 +7,11 @@ defmodule RDF.PrefixMap do
alias RDF.IRI
@type prefix :: atom
@type namespace :: IRI.t
@type prefix :: atom
@type namespace :: IRI.t()
@type coercible_prefix :: atom | String.t
@type coercible_namespace :: atom | String.t | IRI.t
@type coercible_prefix :: atom | String.t()
@type coercible_namespace :: atom | String.t() | IRI.t()
@type prefix_map :: %{prefix => namespace}
@ -20,11 +20,10 @@ defmodule RDF.PrefixMap do
@type t :: %__MODULE__{
map: prefix_map
}
}
defstruct map: %{}
@doc """
Creates an empty `RDF.PrefixMap`.
"""
@ -67,7 +66,7 @@ defmodule RDF.PrefixMap do
Unless a mapping of the given prefix to a different namespace already exists,
an ok tuple is returned, other an error tuple.
"""
@spec add(t, coercible_prefix, coercible_namespace) :: {:ok, t} | {:error, String.t}
@spec add(t, coercible_prefix, coercible_namespace) :: {:ok, t} | {:error, String.t()}
def add(prefix_map, prefix, namespace)
def add(%__MODULE__{map: map}, prefix, %IRI{} = namespace) when is_atom(prefix) do
@ -109,7 +108,7 @@ defmodule RDF.PrefixMap do
See also `merge/3` which allows you to resolve conflicts with a function.
"""
@spec merge(t, t | map | keyword) :: {:ok, t} | {:error, [atom | String.t]}
@spec merge(t, t | map | keyword) :: {:ok, t} | {:error, [atom | String.t()]}
def merge(prefix_map1, prefix_map2)
def merge(%__MODULE__{map: map1}, %__MODULE__{map: map2}) do
@ -149,7 +148,8 @@ defmodule RDF.PrefixMap do
If everything could be merged, an `:ok` tuple is returned.
"""
@spec merge(t, t | map | keyword, conflict_resolver | nil) :: {:ok, t} | {:error, [atom | String.t]}
@spec merge(t, t | map | keyword, conflict_resolver | nil) ::
{:ok, t} | {:error, [atom | String.t()]}
def merge(prefix_map1, prefix_map2, conflict_resolver)
def merge(%__MODULE__{map: map1}, %__MODULE__{map: map2}, conflict_resolver)

View file

@ -8,14 +8,14 @@ defmodule RDF.Quad do
alias RDF.Statement
@type t :: {Statement.subject, Statement.predicate, Statement.object, Statement.graph_name}
@type t ::
{Statement.subject(), Statement.predicate(), Statement.object(), Statement.graph_name()}
@type coercible_t ::
{Statement.coercible_subject, Statement.coercible_predicate,
Statement.coercible_object, Statement.coercible_graph_name}
@type t_values :: {String.t, String.t, any, String.t}
{Statement.coercible_subject(), Statement.coercible_predicate(),
Statement.coercible_object(), Statement.coercible_graph_name()}
@type t_values :: {String.t(), String.t(), any, String.t()}
@doc """
Creates a `RDF.Quad` with proper RDF values.
@ -32,10 +32,10 @@ defmodule RDF.Quad do
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object,
Statement.coercible_graph_name
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object(),
Statement.coercible_graph_name()
) :: t
def new(subject, predicate, object, graph_context) do
{
@ -64,7 +64,6 @@ defmodule RDF.Quad do
def new({subject, predicate, object, graph_context}),
do: new(subject, predicate, object, graph_context)
@doc """
Returns a tuple of native Elixir values from a `RDF.Quad` of RDF terms.
@ -94,15 +93,14 @@ defmodule RDF.Quad do
{:S, :p, 42, ~I<http://example.com/Graph>}
"""
@spec values(t | any, Statement.term_mapping) :: t_values | nil
@spec values(t | any, Statement.term_mapping()) :: t_values | nil
def values(quad, mapping \\ &Statement.default_term_mapping/1)
def values({subject, predicate, object, graph_context}, mapping) do
with subject_value when not is_nil(subject_value) <- mapping.({:subject, subject}),
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}),
graph_context_value <- mapping.({:graph_name, graph_context})
do
object_value when not is_nil(object_value) <- mapping.({:object, object}),
graph_context_value <- mapping.({:graph_name, graph_context}) do
{subject_value, predicate_value, object_value, graph_context_value}
else
_ -> nil
@ -111,7 +109,6 @@ defmodule RDF.Quad do
def values(_, _), do: nil
@doc """
Checks if the given tuple is a valid RDF quad.
@ -123,5 +120,4 @@ defmodule RDF.Quad do
def valid?(tuple)
def valid?({_, _, _, _} = quad), do: Statement.valid?(quad)
def valid?(_), do: false
end

View file

@ -84,7 +84,7 @@ defmodule RDF.Query do
As opposed to `execute/3` this returns the results directly or fails with an
exception.
"""
def execute!(query, graph, opts \\ []) do
def execute!(query, graph, opts \\ []) do
case execute(query, graph, opts) do
{:ok, results} -> results
{:error, error} -> raise error
@ -152,7 +152,7 @@ defmodule RDF.Query do
As opposed to `stream/3` this returns the stream directly or fails with an
exception.
"""
def stream!(query, graph, opts \\ []) do
def stream!(query, graph, opts \\ []) do
case stream(query, graph, opts) do
{:ok, results} -> results
{:error, error} -> raise error

View file

@ -9,13 +9,12 @@ defmodule RDF.Query.BGP do
@enforce_keys [:triple_patterns]
defstruct [:triple_patterns]
@type variable :: String.t
@type variable :: String.t()
@type triple_pattern :: {
subject :: variable | RDF.Term.t,
predicate :: variable | RDF.Term.t,
object :: variable | RDF.Term.t
}
subject :: variable | RDF.Term.t(),
predicate :: variable | RDF.Term.t(),
object :: variable | RDF.Term.t()
}
@type triple_patterns :: list(triple_pattern)
@type t :: %__MODULE__{triple_patterns: triple_patterns}

View file

@ -4,7 +4,11 @@ defmodule RDF.Query.BGP.BlankNodeHandler do
alias RDF.Query.BGP
alias RDF.BlankNode
@default_remove_bnode_query_variables Application.get_env(:rdf, :default_remove_bnode_query_variables, true)
@default_remove_bnode_query_variables Application.get_env(
:rdf,
:default_remove_bnode_query_variables,
true
)
def preprocess(triple_patterns) do
Enum.reduce(triple_patterns, {false, []}, fn
@ -14,22 +18,30 @@ defmodule RDF.Query.BGP.BlankNodeHandler do
end)
end
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {bnode_var(s), bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({s, %BlankNode{} = p, %BlankNode{} = o}), do: {true, {s, bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, p, %BlankNode{} = o}), do: {true, {bnode_var(s), p, bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, o}), do: {true, {bnode_var(s), bnode_var(p), o}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}),
do: {true, {bnode_var(s), bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({s, %BlankNode{} = p, %BlankNode{} = o}),
do: {true, {s, bnode_var(p), bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, p, %BlankNode{} = o}),
do: {true, {bnode_var(s), p, bnode_var(o)}}
defp convert_blank_nodes({%BlankNode{} = s, %BlankNode{} = p, o}),
do: {true, {bnode_var(s), bnode_var(p), o}}
defp convert_blank_nodes({%BlankNode{} = s, p, o}), do: {true, {bnode_var(s), p, o}}
defp convert_blank_nodes({s, %BlankNode{} = p, o}), do: {true, {s, bnode_var(p), o}}
defp convert_blank_nodes({s, p, %BlankNode{} = o}), do: {true, {s, p, bnode_var(o)}}
defp convert_blank_nodes(triple_pattern), do: {false, triple_pattern}
defp convert_blank_nodes(triple_pattern), do: {false, triple_pattern}
defp bnode_var(bnode), do: bnode |> to_string() |> String.to_atom()
def postprocess(solutions, bgp, has_blank_nodes, opts) do
if has_blank_nodes and
Keyword.get(opts, :remove_bnode_query_variables, @default_remove_bnode_query_variables) do
Keyword.get(opts, :remove_bnode_query_variables, @default_remove_bnode_query_variables) do
bnode_vars = bgp |> bnodes() |> Enum.map(&bnode_var/1)
Enum.map(solutions, &(Map.drop(&1, bnode_vars)))
Enum.map(solutions, &Map.drop(&1, bnode_vars))
else
solutions
end
@ -45,7 +57,7 @@ defmodule RDF.Query.BGP.BlankNodeHandler do
defp bnodes({%BlankNode{} = s, %BlankNode{} = p, %BlankNode{} = o}), do: [s, p, o]
defp bnodes({%BlankNode{} = s, %BlankNode{} = p, _}), do: [s, p]
defp bnodes({%BlankNode{} = s, _, %BlankNode{} = o}) , do: [s, o]
defp bnodes({%BlankNode{} = s, _, %BlankNode{} = o}), do: [s, o]
defp bnodes({_, %BlankNode{} = p, %BlankNode{} = o}), do: [p, o]
defp bnodes({%BlankNode{} = s, _, _}), do: [s]
defp bnodes({_, %BlankNode{} = p, _}), do: [p]

View file

@ -1,7 +1,7 @@
defmodule RDF.Query.BGP.Matcher do
@moduledoc !"""
An interface for various BGP matching algorithm implementations.
"""
An interface for various BGP matching algorithm implementations.
"""
alias RDF.Query.BGP
alias RDF.Graph
@ -9,8 +9,7 @@ defmodule RDF.Query.BGP.Matcher do
@type solution :: map
@type solutions :: [solution]
@callback execute(BGP.t, Graph.t, opts :: Keyword.t) :: solutions
@callback stream(BGP.t, Graph.t, opts :: Keyword.t) :: Enumerable.t()
@callback execute(BGP.t(), Graph.t(), opts :: Keyword.t()) :: solutions
@callback stream(BGP.t(), Graph.t(), opts :: Keyword.t()) :: Enumerable.t()
end

View file

@ -14,28 +14,30 @@ defmodule RDF.Query.BGP.QueryPlanner do
query_plan(
mark_solved_variables(rest, new_solved),
new_solved,
[next_best | plan])
[next_best | plan]
)
end
defp triple_priority({v, v, v}), do: triple_priority({v, "p", "o"})
defp triple_priority({v, v, o}), do: triple_priority({v, "p", o})
defp triple_priority({v, p, v}), do: triple_priority({v, p, "o"})
defp triple_priority({s, v, v}), do: triple_priority({s, v, "o"})
defp triple_priority({s, p, o}) do
{sp, pp, op} = {value_priority(s), value_priority(p), value_priority(o)}
<<(sp + pp + op) :: size(2), sp :: size(1), pp :: size(1), op :: size(1)>>
<<sp + pp + op::size(2), sp::size(1), pp::size(1), op::size(1)>>
end
defp value_priority(value) when is_atom(value), do: 1
defp value_priority(_), do: 0
defp value_priority(_), do: 0
defp mark_solved_variables(triple_patterns, solved) do
Enum.map triple_patterns, fn {s, p, o} ->
Enum.map(triple_patterns, fn {s, p, o} ->
{
(if is_atom(s) and s in solved, do: {s}, else: s),
(if is_atom(p) and p in solved, do: {p}, else: p),
(if is_atom(o) and o in solved, do: {o}, else: o)
if(is_atom(s) and s in solved, do: {s}, else: s),
if(is_atom(p) and p in solved, do: {p}, else: p),
if(is_atom(o) and o in solved, do: {o}, else: o)
}
end
end)
end
end

View file

@ -10,11 +10,11 @@ defmodule RDF.Query.BGP.Simple do
@impl RDF.Query.BGP.Matcher
def execute(bgp, graph, opts \\ [])
def execute(%BGP{triple_patterns: []}, _, _), do: [%{}] # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
# https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def execute(%BGP{triple_patterns: []}, _, _), do: [%{}]
def execute(%BGP{triple_patterns: triple_patterns}, %Graph{} = graph, opts) do
{bnode_state, preprocessed_triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns)
{bnode_state, preprocessed_triple_patterns} = BlankNodeHandler.preprocess(triple_patterns)
preprocessed_triple_patterns
|> QueryPlanner.query_plan()
@ -28,7 +28,6 @@ defmodule RDF.Query.BGP.Simple do
|> Stream.into([])
end
defp do_execute([triple_pattern | remaining], graph) do
do_execute(remaining, graph, match(graph, triple_pattern))
end
@ -43,19 +42,18 @@ defmodule RDF.Query.BGP.Simple do
do_execute(remaining, graph, match_with_solutions(graph, triple_pattern, solutions))
end
defp match_with_solutions(graph, {s, p, o} = triple_pattern, existing_solutions)
when is_tuple(s) or is_tuple(p) or is_tuple(o) do
triple_pattern
|> apply_solutions(existing_solutions)
|> Enum.flat_map(&(merging_match(&1, graph)))
|> Enum.flat_map(&merging_match(&1, graph))
end
defp match_with_solutions(graph, triple_pattern, existing_solutions) do
graph
|> match(triple_pattern)
|> Enum.flat_map(fn solution ->
Enum.map(existing_solutions, &(Map.merge(solution, &1)))
Enum.map(existing_solutions, &Map.merge(solution, &1))
end)
end
@ -63,14 +61,15 @@ defmodule RDF.Query.BGP.Simple do
apply_solution =
case triple_pattern do
{{s}, {p}, {o}} -> fn solution -> {solution, {solution[s], solution[p], solution[o]}} end
{{s}, {p}, o } -> fn solution -> {solution, {solution[s], solution[p], o}} end
{{s}, p , {o}} -> fn solution -> {solution, {solution[s], p , solution[o]}} end
{{s}, p , o } -> fn solution -> {solution, {solution[s], p , o}} end
{ s , {p}, {o}} -> fn solution -> {solution, {s , solution[p], solution[o]}} end
{ s , {p} , o } -> fn solution -> {solution, {s , solution[p], o}} end
{ s , p , {o}} -> fn solution -> {solution, {s , p , solution[o]}} end
{{s}, {p}, o} -> fn solution -> {solution, {solution[s], solution[p], o}} end
{{s}, p, {o}} -> fn solution -> {solution, {solution[s], p, solution[o]}} end
{{s}, p, o} -> fn solution -> {solution, {solution[s], p, o}} end
{s, {p}, {o}} -> fn solution -> {solution, {s, solution[p], solution[o]}} end
{s, {p}, o} -> fn solution -> {solution, {s, solution[p], o}} end
{s, p, {o}} -> fn solution -> {solution, {s, p, solution[o]}} end
_ -> nil
end
if apply_solution do
Stream.map(solutions, apply_solution)
else
@ -80,7 +79,9 @@ defmodule RDF.Query.BGP.Simple do
defp merging_match({dependent_solution, triple_pattern}, graph) do
case match(graph, triple_pattern) do
nil -> []
nil ->
[]
solutions ->
Enum.map(solutions, fn solution ->
Map.merge(dependent_solution, solution)
@ -88,12 +89,13 @@ defmodule RDF.Query.BGP.Simple do
end
end
defp match(%Graph{descriptions: descriptions}, {subject_variable, _, _} = triple_pattern)
when is_atom(subject_variable) do
Enum.reduce(descriptions, [], fn ({subject, description}, acc) ->
Enum.reduce(descriptions, [], fn {subject, description}, acc ->
case match(description, solve_variables(subject_variable, subject, triple_pattern)) do
nil -> acc
nil ->
acc
solutions ->
Enum.map(solutions, fn solution ->
Map.put(solution, subject_variable, subject)
@ -104,14 +106,14 @@ defmodule RDF.Query.BGP.Simple do
defp match(%Graph{} = graph, {subject, _, _} = triple_pattern) do
case graph[subject] do
nil -> []
nil -> []
description -> match(description, triple_pattern)
end
end
defp match(%Description{predications: predications}, {_, variable, variable})
when is_atom(variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
Enum.reduce(predications, [], fn {predicate, objects}, solutions ->
if Map.has_key?(objects, predicate) do
[%{variable => predicate} | solutions]
else
@ -122,17 +124,20 @@ defmodule RDF.Query.BGP.Simple do
defp match(%Description{predications: predications}, {_, predicate_variable, object_variable})
when is_atom(predicate_variable) and is_atom(object_variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
Enum.reduce(predications, [], fn {predicate, objects}, solutions ->
solutions ++
Enum.map(objects, fn {object, _} ->
%{predicate_variable => predicate, object_variable => object}
end)
Enum.map(objects, fn {object, _} ->
%{predicate_variable => predicate, object_variable => object}
end)
end)
end
defp match(%Description{predications: predications},
{_, predicate_variable, object}) when is_atom(predicate_variable) do
Enum.reduce(predications, [], fn ({predicate, objects}, solutions) ->
defp match(
%Description{predications: predications},
{_, predicate_variable, object}
)
when is_atom(predicate_variable) do
Enum.reduce(predications, [], fn {predicate, objects}, solutions ->
if Map.has_key?(objects, object) do
[%{predicate_variable => predicate} | solutions]
else
@ -141,10 +146,14 @@ defmodule RDF.Query.BGP.Simple do
end)
end
defp match(%Description{predications: predications},
{_, predicate, object_or_variable}) do
defp match(
%Description{predications: predications},
{_, predicate, object_or_variable}
) do
case predications[predicate] do
nil -> []
nil ->
[]
objects ->
cond do
# object_or_variable is a variable
@ -165,11 +174,11 @@ defmodule RDF.Query.BGP.Simple do
end
defp solve_variables(var, val, {var, var, var}), do: {val, val, val}
defp solve_variables(var, val, {s, var, var}), do: {s, val, val}
defp solve_variables(var, val, {var, p, var}), do: {val, p, val}
defp solve_variables(var, val, {var, var, o}), do: {val, val, o}
defp solve_variables(var, val, {var, p, o}), do: {val, p, o}
defp solve_variables(var, val, {s, var, o}), do: {s, val, o}
defp solve_variables(var, val, {s, p, var}), do: {s, p, val}
defp solve_variables(_, _, pattern), do: pattern
defp solve_variables(var, val, {s, var, var}), do: {s, val, val}
defp solve_variables(var, val, {var, p, var}), do: {val, p, val}
defp solve_variables(var, val, {var, var, o}), do: {val, val, o}
defp solve_variables(var, val, {var, p, o}), do: {val, p, o}
defp solve_variables(var, val, {s, var, o}), do: {s, val, o}
defp solve_variables(var, val, {s, p, var}), do: {s, p, val}
defp solve_variables(_, _, pattern), do: pattern
end

View file

@ -7,15 +7,14 @@ defmodule RDF.Query.BGP.Stream do
alias RDF.Query.BGP.{QueryPlanner, BlankNodeHandler}
alias RDF.{Graph, Description}
@impl RDF.Query.BGP.Matcher
def stream(bgp, graph, opts \\ [])
def stream(%BGP{triple_patterns: []}, _, _), do: to_stream([%{}]) # https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
# https://www.w3.org/TR/sparql11-query/#emptyGroupPattern
def stream(%BGP{triple_patterns: []}, _, _), do: to_stream([%{}])
def stream(%BGP{triple_patterns: triple_patterns}, %Graph{} = graph, opts) do
{bnode_state, preprocessed_triple_patterns} =
BlankNodeHandler.preprocess(triple_patterns)
{bnode_state, preprocessed_triple_patterns} = BlankNodeHandler.preprocess(triple_patterns)
preprocessed_triple_patterns
|> QueryPlanner.query_plan()
@ -47,18 +46,17 @@ defmodule RDF.Query.BGP.Stream do
do_execute(remaining, graph, match_with_solutions(graph, triple_pattern, solutions))
end
defp match_with_solutions(graph, {s, p, o} = triple_pattern, existing_solutions)
when is_tuple(s) or is_tuple(p) or is_tuple(o) do
triple_pattern
|> apply_solutions(existing_solutions)
|> Stream.flat_map(&(merging_match(&1, graph)))
|> Stream.flat_map(&merging_match(&1, graph))
end
defp match_with_solutions(graph, triple_pattern, existing_solutions) do
if solutions = match(graph, triple_pattern) do
Stream.flat_map(solutions, fn solution ->
Stream.map(existing_solutions, &(Map.merge(solution, &1)))
Stream.map(existing_solutions, &Map.merge(solution, &1))
end)
end
end
@ -67,14 +65,15 @@ defmodule RDF.Query.BGP.Stream do
apply_solution =
case triple_pattern do
{{s}, {p}, {o}} -> fn solution -> {solution, {solution[s], solution[p], solution[o]}} end
{{s}, {p}, o } -> fn solution -> {solution, {solution[s], solution[p], o}} end
{{s}, p , {o}} -> fn solution -> {solution, {solution[s], p , solution[o]}} end
{{s}, p , o } -> fn solution -> {solution, {solution[s], p , o}} end
{ s , {p}, {o}} -> fn solution -> {solution, {s , solution[p], solution[o]}} end
{ s , {p} , o } -> fn solution -> {solution, {s , solution[p], o}} end
{ s , p , {o}} -> fn solution -> {solution, {s , p , solution[o]}} end
{{s}, {p}, o} -> fn solution -> {solution, {solution[s], solution[p], o}} end
{{s}, p, {o}} -> fn solution -> {solution, {solution[s], p, solution[o]}} end
{{s}, p, o} -> fn solution -> {solution, {solution[s], p, o}} end
{s, {p}, {o}} -> fn solution -> {solution, {s, solution[p], solution[o]}} end
{s, {p}, o} -> fn solution -> {solution, {s, solution[p], o}} end
{s, p, {o}} -> fn solution -> {solution, {s, p, solution[o]}} end
_ -> nil
end
if apply_solution do
Stream.map(solutions, apply_solution)
else
@ -84,20 +83,23 @@ defmodule RDF.Query.BGP.Stream do
defp merging_match({dependent_solution, triple_pattern}, graph) do
case match(graph, triple_pattern) do
nil -> []
nil ->
[]
solutions ->
Stream.map solutions, fn solution ->
Stream.map(solutions, fn solution ->
Map.merge(dependent_solution, solution)
end
end)
end
end
defp match(%Graph{descriptions: descriptions}, {subject_variable, _, _} = triple_pattern)
when is_atom(subject_variable) do
Stream.flat_map(descriptions, fn {subject, description} ->
case match(description, solve_variables(subject_variable, subject, triple_pattern)) do
nil -> []
nil ->
[]
solutions ->
Stream.map(solutions, fn solution ->
Map.put(solution, subject_variable, subject)
@ -108,7 +110,7 @@ defmodule RDF.Query.BGP.Stream do
defp match(%Graph{} = graph, {subject, _, _} = triple_pattern) do
case graph[subject] do
nil -> nil
nil -> nil
description -> match(description, triple_pattern)
end
end
@ -132,20 +134,26 @@ defmodule RDF.Query.BGP.Stream do
end)
end
defp match(%Description{predications: predications},
{_, predicate_variable, object}) when is_atom(predicate_variable) do
matches =
Stream.filter(predications, fn {_, objects} -> Map.has_key?(objects, object) end)
defp match(
%Description{predications: predications},
{_, predicate_variable, object}
)
when is_atom(predicate_variable) do
matches = Stream.filter(predications, fn {_, objects} -> Map.has_key?(objects, object) end)
unless Enum.empty?(matches) do
Stream.map(matches, fn {predicate, _} -> %{predicate_variable => predicate} end)
end
end
defp match(%Description{predications: predications},
{_, predicate, object_or_variable}) do
defp match(
%Description{predications: predications},
{_, predicate, object_or_variable}
) do
case predications[predicate] do
nil -> nil
nil ->
nil
objects ->
cond do
# object_or_variable is a variable
@ -162,17 +170,17 @@ defmodule RDF.Query.BGP.Stream do
true ->
nil
end
end
end
end
defp solve_variables(var, val, {var, var, var}), do: {val, val, val}
defp solve_variables(var, val, {s, var, var}), do: {s, val, val}
defp solve_variables(var, val, {var, p, var}), do: {val, p, val}
defp solve_variables(var, val, {var, var, o}), do: {val, val, o}
defp solve_variables(var, val, {var, p, o}), do: {val, p, o}
defp solve_variables(var, val, {s, var, o}), do: {s, val, o}
defp solve_variables(var, val, {s, p, var}), do: {s, p, val}
defp solve_variables(_, _, pattern), do: pattern
defp solve_variables(var, val, {s, var, var}), do: {s, val, val}
defp solve_variables(var, val, {var, p, var}), do: {val, p, val}
defp solve_variables(var, val, {var, var, o}), do: {val, val, o}
defp solve_variables(var, val, {var, p, o}), do: {val, p, o}
defp solve_variables(var, val, {s, var, o}), do: {s, val, o}
defp solve_variables(var, val, {s, p, var}), do: {s, p, val}
defp solve_variables(_, _, pattern), do: pattern
defp to_stream(enum), do: Stream.into(enum, [])
end

View file

@ -32,7 +32,7 @@ defmodule RDF.Query.Builder do
end
defp triple_patterns(triple_pattern) when is_tuple(triple_pattern),
do: triple_patterns([triple_pattern])
do: triple_patterns([triple_pattern])
defp triple_pattern({subject, predicate, object})
when not is_list(predicate) and not is_list(object) do
@ -43,7 +43,8 @@ defmodule RDF.Query.Builder do
end
end
defp triple_pattern(combined_objects_triple_pattern) when is_tuple(combined_objects_triple_pattern) do
defp triple_pattern(combined_objects_triple_pattern)
when is_tuple(combined_objects_triple_pattern) do
[subject | rest] = Tuple.to_list(combined_objects_triple_pattern)
case rest do
@ -53,9 +54,10 @@ defmodule RDF.Query.Builder do
|> Enum.map(fn object -> {subject, predicate, object} end)
|> triple_patterns()
else
{:error, %RDF.Query.InvalidError{
message: "Invalid use of predicate-object pair brackets"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid use of predicate-object pair brackets"
}}
end
predicate_object_pairs ->
@ -66,9 +68,10 @@ defmodule RDF.Query.Builder do
end)
|> triple_patterns()
else
{:error, %RDF.Query.InvalidError{
message: "Invalid use of predicate-object pair brackets"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid use of predicate-object pair brackets"
}}
end
end
end
@ -79,9 +82,10 @@ defmodule RDF.Query.Builder do
if value do
{:ok, value}
else
{:error, %RDF.Query.InvalidError{
message: "Invalid subject term in BGP triple pattern: #{inspect subject}"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid subject term in BGP triple pattern: #{inspect(subject)}"
}}
end
end
@ -91,9 +95,10 @@ defmodule RDF.Query.Builder do
if value do
{:ok, value}
else
{:error, %RDF.Query.InvalidError{
message: "Invalid predicate term in BGP triple pattern: #{inspect predicate}"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid predicate term in BGP triple pattern: #{inspect(predicate)}"
}}
end
end
@ -103,9 +108,10 @@ defmodule RDF.Query.Builder do
if value do
{:ok, value}
else
{:error, %RDF.Query.InvalidError{
message: "Invalid object term in BGP triple pattern: #{inspect object}"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid object term in BGP triple pattern: #{inspect(object)}"
}}
end
end
@ -146,13 +152,13 @@ defmodule RDF.Query.Builder do
defp literal(%Literal{} = literal), do: literal
defp literal(value), do: Literal.coerce(value)
def path(query, opts \\ [])
def path(query, _) when is_list(query) and length(query) < 3 do
{:error, %RDF.Query.InvalidError{
message: "Invalid path expression: must have at least three elements"}
}
{:error,
%RDF.Query.InvalidError{
message: "Invalid path expression: must have at least three elements"
}}
end
def path([subject | rest], opts) do
@ -175,6 +181,12 @@ defmodule RDF.Query.Builder do
defp path_pattern(subject, [predicate | rest], triple_patterns, count, with_elements) do
object = if with_elements, do: :"el#{count}?", else: RDF.bnode(count)
path_pattern(object, rest, [{subject, predicate, object} | triple_patterns], count + 1, with_elements)
path_pattern(
object,
rest,
[{subject, predicate, object} | triple_patterns],
count + 1,
with_elements
)
end
end

View file

@ -11,7 +11,7 @@ defmodule RDF.Serialization.Decoder do
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@callback decode(String.t, keyword | map) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@callback decode(String.t(), keyword | map) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
@doc """
Decodes a serialized `RDF.Graph` or `RDF.Dataset` from the given string.
@ -21,24 +21,22 @@ defmodule RDF.Serialization.Decoder do
Note: The `__using__` macro automatically provides an overridable default
implementation based on the non-bang `decode` function.
"""
@callback decode!(String.t, keyword | map) :: RDF.Graph.t | RDF.Dataset.t
@callback decode!(String.t(), keyword | map) :: RDF.Graph.t() | RDF.Dataset.t()
defmacro __using__(_) do
quote bind_quoted: [], unquote: true do
@behaviour unquote(__MODULE__)
@impl unquote(__MODULE__)
@spec decode!(String.t, keyword | map) :: RDF.Graph.t | RDF.Dataset.t
@spec decode!(String.t(), keyword | map) :: RDF.Graph.t() | RDF.Dataset.t()
def decode!(content, opts \\ []) do
case decode(content, opts) do
{:ok, data} -> data
{:ok, data} -> data
{:error, reason} -> raise reason
end
end
defoverridable [decode!: 2]
defoverridable decode!: 2
end
end
end

View file

@ -12,7 +12,7 @@ defmodule RDF.Serialization.Encoder do
It returns an `{:ok, string}` tuple, with `string` being the serialized
`RDF.Graph` or `RDF.Dataset`, or `{:error, reason}` if an error occurs.
"""
@callback encode(Graph.t | Dataset.t, keyword | map) :: {:ok, String.t} | {:error, any}
@callback encode(Graph.t() | Dataset.t(), keyword | map) :: {:ok, String.t()} | {:error, any}
@doc """
Encodes a `RDF.Graph` or `RDF.Dataset`.
@ -22,8 +22,7 @@ defmodule RDF.Serialization.Encoder do
Note: The `__using__` macro automatically provides an overridable default
implementation based on the non-bang `encode` function.
"""
@callback encode!(Graph.t | Dataset.t, keyword | map) :: String.t
@callback encode!(Graph.t() | Dataset.t(), keyword | map) :: String.t()
defmacro __using__(_) do
quote bind_quoted: [], unquote: true do
@ -31,17 +30,16 @@ defmodule RDF.Serialization.Encoder do
@impl unquote(__MODULE__)
@dialyzer {:nowarn_function, encode!: 2}
@spec encode!(Graph.t | Dataset.t, keyword) :: String.t
@spec encode!(Graph.t() | Dataset.t(), keyword) :: String.t()
def encode!(data, opts \\ []) do
case encode(data, opts) do
{:ok, data} -> data
{:ok, data} -> data
{:error, reason} -> raise reason
end
end
defoverridable [encode!: 1]
defoverridable [encode!: 2]
defoverridable encode!: 1
defoverridable encode!: 2
end
end
end

View file

@ -33,7 +33,7 @@ defmodule RDF.Serialization.Format do
@doc """
An IRI of the serialization format.
"""
@callback id :: RDF.IRI.t
@callback id :: RDF.IRI.t()
@doc """
An name atom of the serialization format.
@ -43,12 +43,12 @@ defmodule RDF.Serialization.Format do
@doc """
The usual file extension for the serialization format.
"""
@callback extension :: String.t
@callback extension :: String.t()
@doc """
The MIME type of the serialization format.
"""
@callback media_type :: String.t
@callback media_type :: String.t()
@doc """
A map with the supported options of the `Encoder` and `Decoder` for the serialization format.
@ -65,7 +65,6 @@ defmodule RDF.Serialization.Format do
"""
@callback encoder :: module
defmacro __using__(_) do
quote bind_quoted: [], unquote: true do
@behaviour unquote(__MODULE__)
@ -82,31 +81,37 @@ defmodule RDF.Serialization.Format do
@impl unquote(__MODULE__)
def options, do: %{}
defoverridable [decoder: 0, encoder: 0, options: 0]
defoverridable decoder: 0, encoder: 0, options: 0
@spec read_string(String.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_string(String.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_string(content, opts \\ []),
do: RDF.Serialization.Reader.read_string(decoder(), content, opts)
@spec read_string!(String.t, keyword) :: Graph.t | Dataset.t
@spec read_string!(String.t(), keyword) :: Graph.t() | Dataset.t()
def read_string!(content, opts \\ []),
do: RDF.Serialization.Reader.read_string!(decoder(), content, opts)
@spec read_file(Path.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_file(Path.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_file(file, opts \\ []),
do: RDF.Serialization.Reader.read_file(decoder(), file, opts)
@spec read_file!(Path.t, keyword) :: Graph.t | Dataset.t
@spec read_file!(Path.t(), keyword) :: Graph.t() | Dataset.t()
def read_file!(file, opts \\ []),
do: RDF.Serialization.Reader.read_file!(decoder(), file, opts)
@spec write_string(Graph.t | Dataset.t, keyword) :: {:ok, String.t} | {:error, any}
@spec write_string(Graph.t() | Dataset.t(), keyword) :: {:ok, String.t()} | {:error, any}
def write_string(data, opts \\ []),
do: RDF.Serialization.Writer.write_string(encoder(), data, opts)
@spec write_string!(Graph.t | Dataset.t, keyword) :: String.t
@spec write_string!(Graph.t() | Dataset.t(), keyword) :: String.t()
def write_string!(data, opts \\ []),
do: RDF.Serialization.Writer.write_string!(encoder(), data, opts)
@spec write_file(Graph.t | Dataset.t, Path.t, keyword) :: :ok | {:error, any}
@spec write_file(Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok | {:error, any}
def write_file(data, path, opts \\ []),
do: RDF.Serialization.Writer.write_file(encoder(), data, path, opts)
@spec write_file!(Graph.t | Dataset.t, Path.t, keyword) :: :ok
@spec write_file!(Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok
def write_file!(data, path, opts \\ []),
do: RDF.Serialization.Writer.write_file!(encoder(), data, path, opts)
@ -117,26 +122,28 @@ defmodule RDF.Serialization.Format do
defmacro __before_compile__(_env) do
quote do
if !Module.defines?(__MODULE__, {:id, 0}) &&
Module.get_attribute(__MODULE__, :id) do
Module.get_attribute(__MODULE__, :id) do
@impl unquote(__MODULE__)
def id, do: @id
end
if !Module.defines?(__MODULE__, {:name, 0}) &&
Module.get_attribute(__MODULE__, :name) do
Module.get_attribute(__MODULE__, :name) do
@impl unquote(__MODULE__)
def name, do: @name
end
if !Module.defines?(__MODULE__, {:extension, 0}) &&
Module.get_attribute(__MODULE__, :extension) do
Module.get_attribute(__MODULE__, :extension) do
@impl unquote(__MODULE__)
def extension, do: @extension
end
if !Module.defines?(__MODULE__, {:media_type, 0}) &&
Module.get_attribute(__MODULE__, :media_type) do
Module.get_attribute(__MODULE__, :media_type) do
@impl unquote(__MODULE__)
def media_type, do: @media_type
end
end
end
end

View file

@ -6,7 +6,6 @@ defmodule RDF.Serialization.ParseHelper do
@rdf_type RDF.Utils.Bootstrapping.rdf_iri("type")
def rdf_type, do: @rdf_type
def to_iri_string({:iriref, _line, value}), do: value |> iri_unescape
def to_iri({:iriref, line, value}) do
@ -29,65 +28,69 @@ defmodule RDF.Serialization.ParseHelper do
end
end
def to_bnode({:blank_node_label, _line, value}), do: RDF.bnode(value)
def to_bnode({:anon, _line}), do: RDF.bnode
def to_bnode({:anon, _line}), do: RDF.bnode()
def to_literal({:string_literal_quote, _line, value}),
do: value |> string_unescape |> RDF.literal
do: value |> string_unescape |> RDF.literal()
def to_literal({:integer, _line, value}), do: RDF.literal(value)
def to_literal({:decimal, _line, value}), do: RDF.literal(value)
def to_literal({:double, _line, value}), do: RDF.literal(value)
def to_literal({:boolean, _line, value}), do: RDF.literal(value)
def to_literal({:double, _line, value}), do: RDF.literal(value)
def to_literal({:boolean, _line, value}), do: RDF.literal(value)
def to_literal({:string_literal_quote, _line, value}, {:language, language}),
do: value |> string_unescape |> RDF.literal(language: language)
def to_literal({:string_literal_quote, _line, value}, {:datatype, %IRI{} = type}),
do: value |> string_unescape |> RDF.literal(datatype: type)
def to_literal(string_literal_quote_ast, type),
do: {string_literal_quote_ast, type}
def integer(value), do: RDF.XSD.Integer.new(List.to_string(value))
def decimal(value), do: RDF.XSD.Decimal.new(List.to_string(value))
def double(value), do: RDF.XSD.Double.new(List.to_string(value))
def boolean('true'), do: true
def integer(value), do: RDF.XSD.Integer.new(List.to_string(value))
def decimal(value), do: RDF.XSD.Decimal.new(List.to_string(value))
def double(value), do: RDF.XSD.Double.new(List.to_string(value))
def boolean('true'), do: true
def boolean('false'), do: false
def to_langtag({:langtag, _line, value}), do: value
def to_langtag({:"@prefix", 1}), do: "prefix"
def to_langtag({:"@base", 1}), do: "base"
def to_langtag({:"@base", 1}), do: "base"
def bnode_str('_:' ++ value), do: List.to_string(value)
def langtag_str('@' ++ value), do: List.to_string(value)
def quoted_content_str(value), do: value |> List.to_string |> String.slice(1..-2)
def long_quoted_content_str(value), do: value |> List.to_string |> String.slice(3..-4)
def bnode_str('_:' ++ value), do: List.to_string(value)
def langtag_str('@' ++ value), do: List.to_string(value)
def quoted_content_str(value), do: value |> List.to_string() |> String.slice(1..-2)
def long_quoted_content_str(value), do: value |> List.to_string() |> String.slice(3..-4)
def prefix_ns(value), do: value |> List.to_string |> String.slice(0..-2)
def prefix_ln(value), do: value |> List.to_string |> String.split(":", parts: 2) |> List.to_tuple
def prefix_ns(value), do: value |> List.to_string() |> String.slice(0..-2)
def prefix_ln(value),
do: value |> List.to_string() |> String.split(":", parts: 2) |> List.to_tuple()
def string_unescape(string),
do: string |> unescape_8digit_unicode_seq |> Macro.unescape_string(&string_unescape_map(&1))
def iri_unescape(string),
do: string |> unescape_8digit_unicode_seq |> Macro.unescape_string(&iri_unescape_map(&1))
defp string_unescape_map(?b), do: ?\b
defp string_unescape_map(?f), do: ?\f
defp string_unescape_map(?n), do: ?\n
defp string_unescape_map(?r), do: ?\r
defp string_unescape_map(?t), do: ?\t
defp string_unescape_map(?u), do: true
defp string_unescape_map(:unicode), do: true
defp string_unescape_map(e), do: e
defp string_unescape_map(?b), do: ?\b
defp string_unescape_map(?f), do: ?\f
defp string_unescape_map(?n), do: ?\n
defp string_unescape_map(?r), do: ?\r
defp string_unescape_map(?t), do: ?\t
defp string_unescape_map(?u), do: true
defp string_unescape_map(:unicode), do: true
defp string_unescape_map(e), do: e
defp iri_unescape_map(?u), do: true
defp iri_unescape_map(:unicode), do: true
defp iri_unescape_map(e), do: e
defp iri_unescape_map(?u), do: true
defp iri_unescape_map(:unicode), do: true
defp iri_unescape_map(e), do: e
def unescape_8digit_unicode_seq(string) do
String.replace(string, ~r/\\U([0-9]|[A-F]|[a-f]){2}(([0-9]|[A-F]|[a-f]){6})/, "\\u{\\2}")
end
def error_description(error_descriptor) when is_list(error_descriptor) do
error_descriptor
|> Stream.map(&to_string/1)

View file

@ -15,7 +15,7 @@ defmodule RDF.Serialization.Reader do
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_string(module, String.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_string(module, String.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_string(decoder, content, opts \\ []) do
decoder.decode(content, opts)
end
@ -25,7 +25,7 @@ defmodule RDF.Serialization.Reader do
As opposed to `read_string`, it raises an exception if an error occurs.
"""
@spec read_string!(module, String.t, keyword) :: Graph.t | Dataset.t
@spec read_string!(module, String.t(), keyword) :: Graph.t() | Dataset.t()
def read_string!(decoder, content, opts \\ []) do
decoder.decode!(content, opts)
end
@ -36,10 +36,10 @@ defmodule RDF.Serialization.Reader do
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_file(module, Path.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_file(module, Path.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_file(decoder, file, opts \\ []) do
case File.read(file) do
{:ok, content} -> read_string(decoder, content, opts)
{:ok, content} -> read_string(decoder, content, opts)
{:error, reason} -> {:error, reason}
end
end
@ -49,7 +49,7 @@ defmodule RDF.Serialization.Reader do
As opposed to `read_file`, it raises an exception if an error occurs.
"""
@spec read_file!(module, Path.t, keyword) :: Graph.t | Dataset.t
@spec read_file!(module, Path.t(), keyword) :: Graph.t() | Dataset.t()
def read_file!(decoder, file, opts \\ []) do
with content = File.read!(file) do
read_string!(decoder, content, opts)

View file

@ -11,7 +11,7 @@ defmodule RDF.Serialization do
RDF.Turtle,
JSON.LD,
RDF.NTriples,
RDF.NQuads,
RDF.NQuads
]
@doc """
@ -43,7 +43,7 @@ defmodule RDF.Serialization do
"""
@spec available_formats :: [format]
def available_formats do
Enum.filter @formats, &Code.ensure_loaded?/1
Enum.filter(@formats, &Code.ensure_loaded?/1)
end
@doc """
@ -58,12 +58,12 @@ defmodule RDF.Serialization do
iex> RDF.Serialization.format(:jsonld)
nil # unless json_ld is defined as a dependency of the application
"""
@spec format(String.t | atom) :: format | nil
@spec format(String.t() | atom) :: format | nil
def format(name)
def format(name) when is_binary(name) do
name
|> String.to_existing_atom
|> String.to_existing_atom()
|> format()
rescue
ArgumentError -> nil
@ -73,7 +73,6 @@ defmodule RDF.Serialization do
format_where(fn format -> format.name == name end)
end
@doc """
Returns the `RDF.Serialization.Format` with the given media type, if available.
@ -84,7 +83,7 @@ defmodule RDF.Serialization do
iex> RDF.Serialization.format_by_media_type("application/ld+json")
nil # unless json_ld is defined as a dependency of the application
"""
@spec format_by_media_type(String.t) :: format | nil
@spec format_by_media_type(String.t()) :: format | nil
def format_by_media_type(media_type) do
format_where(fn format -> format.media_type == media_type end)
end
@ -101,7 +100,7 @@ defmodule RDF.Serialization do
iex> RDF.Serialization.format_by_extension("jsonld")
nil # unless json_ld is defined as a dependency of the application
"""
@spec format_by_extension(String.t) :: format | nil
@spec format_by_extension(String.t()) :: format | nil
def format_by_extension(extension)
def format_by_extension("." <> extension), do: format_by_extension(extension)
@ -116,7 +115,6 @@ defmodule RDF.Serialization do
|> Enum.find(fun)
end
@doc """
Reads and decodes a serialized graph or dataset from a string.
@ -126,7 +124,7 @@ defmodule RDF.Serialization do
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_string(String.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_string(String.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_string(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string(content, opts)
@ -141,7 +139,7 @@ defmodule RDF.Serialization do
As opposed to `read_string`, it raises an exception if an error occurs.
"""
@spec read_string!(String.t, keyword) :: Graph.t | Dataset.t
@spec read_string!(String.t(), keyword) :: Graph.t() | Dataset.t()
def read_string!(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string!(content, opts)
@ -160,7 +158,7 @@ defmodule RDF.Serialization do
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec read_file(Path.t, keyword) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec read_file(Path.t(), keyword) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def read_file(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file(file, opts)
@ -176,7 +174,7 @@ defmodule RDF.Serialization do
As opposed to `read_file`, it raises an exception if an error occurs.
"""
@spec read_file!(Path.t, keyword) :: Graph.t | Dataset.t
@spec read_file!(Path.t(), keyword) :: Graph.t() | Dataset.t()
def read_file!(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file!(file, opts)
@ -194,7 +192,7 @@ defmodule RDF.Serialization do
It returns an `{:ok, string}` tuple, with `string` being the serialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec write_string(Graph.t | Dataset.t, keyword) :: {:ok, String.t} | {:error, any}
@spec write_string(Graph.t() | Dataset.t(), keyword) :: {:ok, String.t()} | {:error, any}
def write_string(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string(data, opts)
@ -209,7 +207,7 @@ defmodule RDF.Serialization do
As opposed to `write_string`, it raises an exception if an error occurs.
"""
@spec write_string!(Graph.t | Dataset.t, keyword) :: String.t
@spec write_string!(Graph.t() | Dataset.t(), keyword) :: String.t()
def write_string!(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string!(data, opts)
@ -234,7 +232,7 @@ defmodule RDF.Serialization do
It returns `:ok` if successful or `{:error, reason}` if an error occurs.
"""
@spec write_file(Graph.t | Dataset.t, Path.t, keyword) :: :ok | {:error, any}
@spec write_file(Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok | {:error, any}
def write_file(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file(data, path, opts)
@ -252,7 +250,7 @@ defmodule RDF.Serialization do
As opposed to `write_file`, it raises an exception if an error occurs.
"""
@spec write_file!(Graph.t | Dataset.t, Path.t, keyword) :: :ok
@spec write_file!(Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok
def write_file!(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file!(data, path, opts)
@ -261,12 +259,10 @@ defmodule RDF.Serialization do
end
end
defp string_format(opts) do
if format =
(opts |> Keyword.get(:format) |> format()) ||
(opts |> Keyword.get(:media_type) |> format_by_media_type())
do
opts |> Keyword.get(:format) |> format() ||
opts |> Keyword.get(:media_type) |> format_by_media_type() do
{:ok, format}
else
{:error, "unable to detect serialization format"}
@ -276,7 +272,7 @@ defmodule RDF.Serialization do
defp file_format(filename, opts) do
case string_format(opts) do
{:ok, format} -> {:ok, format}
_ -> format_by_file_name(filename)
_ -> format_by_file_name(filename)
end
end
@ -287,5 +283,4 @@ defmodule RDF.Serialization do
{:error, "unable to detect serialization format"}
end
end
end

View file

@ -15,7 +15,8 @@ defmodule RDF.Serialization.Writer do
It returns an `{:ok, string}` tuple, with `string` being the serialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
@spec write_string(module, Graph.t | Dataset.t, keyword) :: {:ok, String.t} | {:error, any}
@spec write_string(module, Graph.t() | Dataset.t(), keyword) ::
{:ok, String.t()} | {:error, any}
def write_string(encoder, data, opts \\ []) do
encoder.encode(data, opts)
end
@ -25,7 +26,7 @@ defmodule RDF.Serialization.Writer do
As opposed to `write_string`, it raises an exception if an error occurs.
"""
@spec write_string!(module, Graph.t | Dataset.t, keyword) :: String.t
@spec write_string!(module, Graph.t() | Dataset.t(), keyword) :: String.t()
def write_string!(encoder, data, opts \\ []) do
encoder.encode!(data, opts)
end
@ -42,7 +43,7 @@ defmodule RDF.Serialization.Writer do
It returns `:ok` if successful or `{:error, reason}` if an error occurs.
"""
@spec write_file(module, Graph.t | Dataset.t, Path.t, keyword) :: :ok | {:error, any}
@spec write_file(module, Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok | {:error, any}
def write_file(encoder, data, path, opts \\ []) do
with {:ok, encoded_string} <- write_string(encoder, data, opts) do
File.write(path, encoded_string, file_mode(encoder, opts))
@ -56,7 +57,7 @@ defmodule RDF.Serialization.Writer do
As opposed to `write_file`, it raises an exception if an error occurs.
"""
@spec write_file!(module, Graph.t | Dataset.t, Path.t, keyword) :: :ok
@spec write_file!(module, Graph.t() | Dataset.t(), Path.t(), keyword) :: :ok
def write_file!(encoder, data, path, opts \\ []) do
with encoded_string = write_string!(encoder, data, opts) do
File.write!(path, encoded_string, file_mode(encoder, opts))

View file

@ -17,9 +17,8 @@ defmodule RDF.NQuads do
import RDF.Sigils
@id ~I<http://www.w3.org/ns/formats/N-Quads>
@name :nquads
@extension "nq"
@id ~I<http://www.w3.org/ns/formats/N-Quads>
@name :nquads
@extension "nq"
@media_type "application/n-quads"
end

View file

@ -8,26 +8,29 @@ defmodule RDF.NQuads.Decoder do
alias RDF.{Dataset, Graph}
@impl RDF.Serialization.Decoder
@spec decode(String.t, keyword | map) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec decode(String.t(), keyword | map) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def decode(content, _opts \\ []) do
with {:ok, tokens, _} <- tokenize(content),
{:ok, ast} <- parse(tokens) do
{:ok, ast} <- parse(tokens) do
{:ok, build_dataset(ast)}
else
{:error, {error_line, :ntriples_lexer, error_descriptor}, _error_line_again} ->
{:error, "N-Quad scanner error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"N-Quad scanner error on line #{error_line}: #{error_description(error_descriptor)}"}
{:error, {error_line, :nquads_parser, error_descriptor}} ->
{:error, "N-Quad parser error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"N-Quad parser error on line #{error_line}: #{error_description(error_descriptor)}"}
end
end
defp tokenize(content), do: content |> to_charlist |> :ntriples_lexer.string
defp tokenize(content), do: content |> to_charlist |> :ntriples_lexer.string()
defp parse(tokens), do: tokens |> :nquads_parser.parse
defp parse(tokens), do: tokens |> :nquads_parser.parse()
defp build_dataset(ast) do
Enum.reduce ast, RDF.Dataset.new, fn(quad, dataset) ->
Enum.reduce(ast, RDF.Dataset.new(), fn quad, dataset ->
RDF.Dataset.add(dataset, quad)
end
end)
end
end

View file

@ -6,31 +6,32 @@ defmodule RDF.NQuads.Encoder do
alias RDF.{Dataset, Graph, Quad, Statement, Triple}
@impl RDF.Serialization.Encoder
@callback encode(Graph.t | Dataset.t, keyword | map) :: {:ok, String.t} | {:error, any}
@callback encode(Graph.t() | Dataset.t(), keyword | map) :: {:ok, String.t()} | {:error, any}
def encode(data, _opts \\ []) do
result =
data
|> Enum.reduce([], fn (statement, result) -> [statement(statement) | result] end)
|> Enum.reverse
|> Enum.reduce([], fn statement, result -> [statement(statement) | result] end)
|> Enum.reverse()
|> Enum.join("\n")
{:ok, (if result == "", do: result, else: "#{result}\n")}
{:ok, if(result == "", do: result, else: "#{result}\n")}
end
@spec statement({Statement.subject, Statement.predicate, Statement.object, nil}) :: String.t
@spec statement({Statement.subject(), Statement.predicate(), Statement.object(), nil}) ::
String.t()
def statement({subject, predicate, object, nil}) do
statement({subject, predicate, object})
end
@spec statement(Quad.t) :: String.t
@spec statement(Quad.t()) :: String.t()
def statement({subject, predicate, object, graph}) do
"#{term(subject)} #{term(predicate)} #{term(object)} #{term(graph)} ."
end
@spec statement(Triple.t) :: String.t
@spec statement(Triple.t()) :: String.t()
def statement({subject, predicate, object}) do
"#{term(subject)} #{term(predicate)} #{term(object)} ."
end
defdelegate term(value), to: RDF.NTriples.Encoder
end

View file

@ -19,9 +19,8 @@ defmodule RDF.NTriples do
import RDF.Sigils
@id ~I<http://www.w3.org/ns/formats/N-Triples>
@name :ntriples
@extension "nt"
@id ~I<http://www.w3.org/ns/formats/N-Triples>
@name :ntriples
@extension "nt"
@media_type "application/n-triples"
end

View file

@ -8,26 +8,29 @@ defmodule RDF.NTriples.Decoder do
alias RDF.{Dataset, Graph}
@impl RDF.Serialization.Decoder
@spec decode(String.t, keyword | map) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec decode(String.t(), keyword | map) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def decode(content, _opts \\ []) do
with {:ok, tokens, _} <- tokenize(content),
{:ok, ast} <- parse(tokens) do
{:ok, ast} <- parse(tokens) do
{:ok, build_graph(ast)}
else
{:error, {error_line, :ntriples_lexer, error_descriptor}, _error_line_again} ->
{:error, "N-Triple scanner error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"N-Triple scanner error on line #{error_line}: #{error_description(error_descriptor)}"}
{:error, {error_line, :ntriples_parser, error_descriptor}} ->
{:error, "N-Triple parser error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"N-Triple parser error on line #{error_line}: #{error_description(error_descriptor)}"}
end
end
defp tokenize(content), do: content |> to_charlist |> :ntriples_lexer.string
defp tokenize(content), do: content |> to_charlist |> :ntriples_lexer.string()
defp parse(tokens), do: tokens |> :ntriples_parser.parse
defp parse(tokens), do: tokens |> :ntriples_parser.parse()
defp build_graph(ast) do
Enum.reduce ast, RDF.Graph.new, fn(triple, graph) ->
Enum.reduce(ast, RDF.Graph.new(), fn triple, graph ->
RDF.Graph.add(graph, triple)
end
end)
end
end

View file

@ -6,24 +6,25 @@ defmodule RDF.NTriples.Encoder do
alias RDF.{BlankNode, Dataset, Graph, IRI, XSD, Literal, Statement, Triple, LangString}
@impl RDF.Serialization.Encoder
@callback encode(Graph.t | Dataset.t, keyword | map) :: {:ok, String.t} | {:error, any}
@callback encode(Graph.t() | Dataset.t(), keyword | map) :: {:ok, String.t()} | {:error, any}
def encode(data, _opts \\ []) do
result =
data
|> Enum.reduce([], fn (statement, result) ->
[statement(statement) | result]
end)
|> Enum.reverse
|> Enum.reduce([], fn statement, result ->
[statement(statement) | result]
end)
|> Enum.reverse()
|> Enum.join("\n")
{:ok, (if result == "", do: result, else: result <> "\n")}
{:ok, if(result == "", do: result, else: result <> "\n")}
end
@spec statement(Triple.t) :: String.t
@spec statement(Triple.t()) :: String.t()
def statement({subject, predicate, object}) do
"#{term(subject)} #{term(predicate)} #{term(object)} ."
end
@spec term(Statement.subject | Statement.predicate | Statement.object) :: String.t
@spec term(Statement.subject() | Statement.predicate() | Statement.object()) :: String.t()
def term(%IRI{} = iri) do
"<#{to_string(iri)}>"
end
@ -43,5 +44,4 @@ defmodule RDF.NTriples.Encoder do
def term(%BlankNode{} = bnode) do
to_string(bnode)
end
end

View file

@ -10,9 +10,8 @@ defmodule RDF.Turtle do
import RDF.Sigils
@id ~I<http://www.w3.org/ns/formats/Turtle>
@name :turtle
@extension "ttl"
@id ~I<http://www.w3.org/ns/formats/Turtle>
@name :turtle
@extension "ttl"
@media_type "text/turtle"
end

View file

@ -19,13 +19,12 @@ defmodule RDF.Turtle.Decoder do
end
def next_bnode(%State{bnode_counter: bnode_counter} = state) do
{RDF.bnode("b#{bnode_counter}"),
%State{state | bnode_counter: bnode_counter + 1}}
{RDF.bnode("b#{bnode_counter}"), %State{state | bnode_counter: bnode_counter + 1}}
end
end
@impl RDF.Serialization.Decoder
@spec decode(String.t, keyword | map) :: {:ok, Graph.t | Dataset.t} | {:error, any}
@spec decode(String.t(), keyword | map) :: {:ok, Graph.t() | Dataset.t()} | {:error, any}
def decode(content, opts \\ %{})
def decode(content, opts) when is_list(opts),
@ -33,25 +32,28 @@ defmodule RDF.Turtle.Decoder do
def decode(content, opts) do
with {:ok, tokens, _} <- tokenize(content),
{:ok, ast} <- parse(tokens),
{:ok, ast} <- parse(tokens),
base_iri = Map.get(opts, :base, Map.get(opts, :base_iri, RDF.default_base_iri())) do
build_graph(ast, base_iri && RDF.iri(base_iri))
else
{:error, {error_line, :turtle_lexer, error_descriptor}, _error_line_again} ->
{:error, "Turtle scanner error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"Turtle scanner error on line #{error_line}: #{error_description(error_descriptor)}"}
{:error, {error_line, :turtle_parser, error_descriptor}} ->
{:error, "Turtle parser error on line #{error_line}: #{error_description error_descriptor}"}
{:error,
"Turtle parser error on line #{error_line}: #{error_description(error_descriptor)}"}
end
end
def tokenize(content), do: content |> to_charlist |> :turtle_lexer.string
def tokenize(content), do: content |> to_charlist |> :turtle_lexer.string()
def parse([]), do: {:ok, []}
def parse(tokens), do: tokens |> :turtle_parser.parse
def parse([]), do: {:ok, []}
def parse(tokens), do: tokens |> :turtle_parser.parse()
defp build_graph(ast, base_iri) do
{graph, %State{namespaces: namespaces, base_iri: base_iri}} =
Enum.reduce ast, {RDF.Graph.new, %State{base_iri: base_iri}}, fn
Enum.reduce(ast, {RDF.Graph.new(), %State{base_iri: base_iri}}, fn
{:triples, triples_ast}, {graph, state} ->
with {statements, state} = triples(triples_ast, state) do
{RDF.Graph.add(graph, statements), state}
@ -59,16 +61,15 @@ defmodule RDF.Turtle.Decoder do
{:directive, directive_ast}, {graph, state} ->
{graph, directive(directive_ast, state)}
end
end)
{:ok,
if Enum.empty?(namespaces) do
graph
else
RDF.Graph.add_prefixes(graph, namespaces)
end
|> RDF.Graph.set_base_iri(base_iri)
}
if Enum.empty?(namespaces) do
graph
else
RDF.Graph.add_prefixes(graph, namespaces)
end
|> RDF.Graph.set_base_iri(base_iri)}
rescue
error -> {:error, Exception.message(error)}
end
@ -87,16 +88,17 @@ defmodule RDF.Turtle.Decoder do
cond do
IRI.absolute?(iri) ->
%State{state | base_iri: RDF.iri(iri)}
base_iri != nil ->
with absolute_iri = IRI.absolute(iri, base_iri) do
%State{state | base_iri: absolute_iri}
end
true ->
raise "Could not resolve relative IRI '#{iri}', no base iri provided"
end
end
defp triples({:blankNodePropertyList, _} = ast, state) do
with {_, statements, state} = resolve_node(ast, [], state) do
{statements, state}
@ -105,15 +107,16 @@ defmodule RDF.Turtle.Decoder do
defp triples({subject, predications}, state) do
with {subject, statements, state} = resolve_node(subject, [], state) do
Enum.reduce predications, {statements, state}, fn {predicate, objects}, {statements, state} ->
Enum.reduce(predications, {statements, state}, fn {predicate, objects},
{statements, state} ->
with {predicate, statements, state} = resolve_node(predicate, statements, state) do
Enum.reduce objects, {statements, state}, fn object, {statements, state} ->
Enum.reduce(objects, {statements, state}, fn object, {statements, state} ->
with {object, statements, state} = resolve_node(object, statements, state) do
{[{subject, predicate, object} | statements], state}
end
end
end)
end
end
end)
end
end
@ -121,7 +124,7 @@ defmodule RDF.Turtle.Decoder do
if ns = State.ns(state, prefix) do
{RDF.iri(ns <> local_name_unescape(name)), statements, state}
else
raise "line #{line_number}: undefined prefix #{inspect prefix}"
raise "line #{line_number}: undefined prefix #{inspect(prefix)}"
end
end
@ -129,7 +132,7 @@ defmodule RDF.Turtle.Decoder do
if ns = State.ns(state, prefix) do
{RDF.iri(ns), statements, state}
else
raise "line #{line_number}: undefined prefix #{inspect prefix}"
raise "line #{line_number}: undefined prefix #{inspect(prefix)}"
end
end
@ -154,35 +157,43 @@ defmodule RDF.Turtle.Decoder do
end
end
defp resolve_node({{:string_literal_quote, _line, value}, {:datatype, datatype}}, statements, state) do
defp resolve_node(
{{:string_literal_quote, _line, value}, {:datatype, datatype}},
statements,
state
) do
with {datatype, statements, state} = resolve_node(datatype, statements, state) do
{RDF.literal(value, datatype: datatype), statements, state}
end
end
defp resolve_node({:collection, []}, statements, state) do
{RDF.nil, statements, state}
{RDF.nil(), statements, state}
end
defp resolve_node({:collection, elements}, statements, state) do
with {first_list_node, state} = State.next_bnode(state),
[first_element | rest_elements] = elements,
{first_element_node, statements, state} =
resolve_node(first_element, statements, state),
first_statement = [{first_list_node, RDF.first, first_element_node}] do
{first_element_node, statements, state} = resolve_node(first_element, statements, state),
first_statement = [{first_list_node, RDF.first(), first_element_node}] do
{last_list_node, statements, state} =
Enum.reduce rest_elements, {first_list_node, statements ++ first_statement, state},
Enum.reduce(
rest_elements,
{first_list_node, statements ++ first_statement, state},
fn element, {list_node, statements, state} ->
with {element_node, statements, state} =
resolve_node(element, statements, state),
with {element_node, statements, state} = resolve_node(element, statements, state),
{next_list_node, state} = State.next_bnode(state) do
{next_list_node, statements ++ [
{list_node, RDF.rest, next_list_node},
{next_list_node, RDF.first, element_node},
], state}
{next_list_node,
statements ++
[
{list_node, RDF.rest(), next_list_node},
{next_list_node, RDF.first(), element_node}
], state}
end
end
{first_list_node, statements ++ [{last_list_node, RDF.rest, RDF.nil}], state}
)
{first_list_node, statements ++ [{last_list_node, RDF.rest(), RDF.nil()}], state}
end
end
@ -195,5 +206,4 @@ defmodule RDF.Turtle.Decoder do
defp local_name_unescape_map(e) when e in @reserved_characters, do: e
defp local_name_unescape_map(_), do: false
end

View file

@ -29,23 +29,22 @@ defmodule RDF.Turtle.Encoder do
]
@ordered_properties MapSet.new(@predicate_order)
@impl RDF.Serialization.Encoder
@callback encode(Graph.t | Dataset.t, keyword | map) :: {:ok, String.t} | {:error, any}
@callback encode(Graph.t() | Dataset.t(), keyword | map) :: {:ok, String.t()} | {:error, any}
def encode(data, opts \\ []) do
with base = Keyword.get(opts, :base, Keyword.get(opts, :base_iri))
|> base_iri(data) |> init_base_iri(),
prefixes = Keyword.get(opts, :prefixes)
|> prefixes(data) |> init_prefixes(),
with base =
Keyword.get(opts, :base, Keyword.get(opts, :base_iri))
|> base_iri(data)
|> init_base_iri(),
prefixes = Keyword.get(opts, :prefixes) |> prefixes(data) |> init_prefixes(),
{:ok, state} = State.start_link(data, base, prefixes) do
try do
State.preprocess(state)
{:ok,
base_directive(base) <>
prefix_directives(prefixes) <>
graph_statements(state)
}
base_directive(base) <>
prefix_directives(prefixes) <>
graph_statements(state)}
after
State.stop(state)
end
@ -60,6 +59,7 @@ defmodule RDF.Turtle.Encoder do
defp init_base_iri(base_iri) do
base_iri = to_string(base_iri)
if String.ends_with?(base_iri, ~w[/ #]) do
{:ok, base_iri}
else
@ -73,28 +73,26 @@ defmodule RDF.Turtle.Encoder do
defp prefixes(prefixes, _), do: RDF.PrefixMap.new(prefixes)
defp init_prefixes(prefixes) do
Enum.reduce prefixes, %{}, fn {prefix, iri}, reverse ->
Enum.reduce(prefixes, %{}, fn {prefix, iri}, reverse ->
Map.put(reverse, iri, to_string(prefix))
end
end)
end
defp base_directive(nil), do: ""
defp base_directive({_, base}), do: "@base <#{base}> .\n"
defp base_directive(nil), do: ""
defp base_directive({_, base}), do: "@base <#{base}> .\n"
defp prefix_directive({ns, prefix}), do: "@prefix #{prefix}: <#{to_string(ns)}> .\n"
defp prefix_directives(prefixes) do
case Enum.map(prefixes, &prefix_directive/1) do
[] -> ""
[] -> ""
prefixes -> Enum.join(prefixes, "") <> "\n"
end
end
defp graph_statements(state) do
State.data(state)
|> RDF.Data.descriptions
|> RDF.Data.descriptions()
|> order_descriptions(state)
|> Enum.map(&description_statements(&1, state))
|> Enum.reject(&is_nil/1)
@ -103,49 +101,54 @@ defmodule RDF.Turtle.Encoder do
defp order_descriptions(descriptions, state) do
base_iri = State.base_iri(state)
group =
Enum.group_by descriptions, fn
Enum.group_by(descriptions, fn
%Description{subject: ^base_iri} ->
:base
description ->
with types when not is_nil(types) <- description.predications[@rdf_type] do
Enum.find @top_classes, :other, fn top_class ->
Enum.find(@top_classes, :other, fn top_class ->
Map.has_key?(types, top_class)
end
end)
else
_ -> :other
end
end
ordered_descriptions = (
@top_classes
|> Stream.map(fn top_class -> group[top_class] end)
|> Stream.reject(&is_nil/1)
|> Stream.map(&sort_description_group/1)
|> Enum.reduce([], fn class_group, ordered_descriptions ->
ordered_descriptions ++ class_group
end)
) ++ (group |> Map.get(:other, []) |> sort_description_group())
end)
ordered_descriptions =
(@top_classes
|> Stream.map(fn top_class -> group[top_class] end)
|> Stream.reject(&is_nil/1)
|> Stream.map(&sort_description_group/1)
|> Enum.reduce([], fn class_group, ordered_descriptions ->
ordered_descriptions ++ class_group
end)) ++ (group |> Map.get(:other, []) |> sort_description_group())
case group[:base] do
[base] -> [base | ordered_descriptions]
_ -> ordered_descriptions
_ -> ordered_descriptions
end
end
defp sort_description_group(descriptions) do
Enum.sort descriptions, fn
%Description{subject: %IRI{}}, %Description{subject: %BlankNode{}} -> true
%Description{subject: %BlankNode{}}, %Description{subject: %IRI{}} -> false
Enum.sort(descriptions, fn
%Description{subject: %IRI{}}, %Description{subject: %BlankNode{}} ->
true
%Description{subject: %BlankNode{}}, %Description{subject: %IRI{}} ->
false
%Description{subject: s1}, %Description{subject: s2} ->
to_string(s1) < to_string(s2)
end
end)
end
defp description_statements(description, state, nesting \\ 0) do
with %BlankNode{} <- description.subject,
ref_count when ref_count < 2 <-
State.bnode_ref_counter(state, description.subject)
do
State.bnode_ref_counter(state, description.subject) do
unrefed_bnode_subject_term(description, ref_count, state, nesting)
else
_ -> full_description_statements(description, state, nesting)
@ -154,9 +157,7 @@ defmodule RDF.Turtle.Encoder do
defp full_description_statements(subject, description, state, nesting) do
with nesting = nesting + @indentation do
subject <> newline_indent(nesting) <> (
predications(description, state, nesting)
) <> " .\n"
subject <> newline_indent(nesting) <> predications(description, state, nesting) <> " .\n"
end
end
@ -167,7 +168,8 @@ defmodule RDF.Turtle.Encoder do
defp blank_node_property_list(description, state, nesting) do
with indented = nesting + @indentation do
"[" <> newline_indent(indented) <>
"[" <>
newline_indent(indented) <>
predications(description, state, indented) <>
newline_indent(nesting) <> "]"
end
@ -196,14 +198,14 @@ defmodule RDF.Turtle.Encoder do
end
defp predication({predicate, objects}, state, nesting) do
term(predicate, state, :predicate, nesting) <> " " <> (
objects
term(predicate, state, :predicate, nesting) <>
" " <>
(objects
|> Enum.map(fn {object, _} -> term(object, state, :object, nesting) end)
|> Enum.join(", ") # TODO: split if the line gets too long
)
# TODO: split if the line gets too long
|> Enum.join(", "))
end
defp unrefed_bnode_subject_term(bnode_description, ref_count, state, nesting) do
if valid_list_node?(bnode_description.subject, state) do
case ref_count do
@ -211,9 +213,14 @@ defmodule RDF.Turtle.Encoder do
bnode_description.subject
|> list_term(state, nesting)
|> full_description_statements(
list_subject_description(bnode_description), state, nesting)
list_subject_description(bnode_description),
state,
nesting
)
1 ->
nil
_ ->
raise "Internal error: This shouldn't happen. Please raise an issue in the RDF.ex project with the input document causing this error."
end
@ -221,8 +228,10 @@ defmodule RDF.Turtle.Encoder do
case ref_count do
0 ->
blank_node_property_list(bnode_description, state, nesting) <> " .\n"
1 ->
nil
_ ->
raise "Internal error: This shouldn't happen. Please raise an issue in the RDF.ex project with the input document causing this error."
end
@ -231,7 +240,7 @@ defmodule RDF.Turtle.Encoder do
@dialyzer {:nowarn_function, list_subject_description: 1}
defp list_subject_description(description) do
with description = Description.delete_predicates(description, [RDF.first, RDF.rest]) do
with description = Description.delete_predicates(description, [RDF.first(), RDF.rest()]) do
if Enum.count(description.predications) == 0 do
# since the Turtle grammar doesn't allow bare lists, we add a statement
description |> RDF.type(RDF.List)
@ -256,7 +265,7 @@ defmodule RDF.Turtle.Encoder do
end
defp valid_list_node?(bnode, state) do
MapSet.member?(State.list_nodes(state), bnode)
MapSet.member?(State.list_nodes(state), bnode)
end
defp list_term(head, state, nesting) do
@ -265,9 +274,8 @@ defmodule RDF.Turtle.Encoder do
|> term(state, :list, nesting)
end
defp term(@rdf_type, _, :predicate, _), do: "a"
defp term(@rdf_nil, _, _, _), do: "()"
defp term(@rdf_nil, _, _, _), do: "()"
defp term(%IRI{} = iri, state, _, _) do
based_name(iri, State.base(state)) ||
@ -276,7 +284,7 @@ defmodule RDF.Turtle.Encoder do
end
defp term(%BlankNode{} = bnode, state, position, nesting)
when position in ~w[object list]a do
when position in ~w[object list]a do
if (ref_count = State.bnode_ref_counter(state, bnode)) <= 1 do
unrefed_bnode_object_term(bnode, ref_count, state, nesting)
else
@ -296,7 +304,7 @@ defmodule RDF.Turtle.Encoder do
end
defp term(%Literal{literal: %datatype{}} = literal, state, _, nesting)
when datatype in @native_supported_datatypes do
when datatype in @native_supported_datatypes do
if Literal.valid?(literal) do
Literal.canonical_lexical(literal)
else
@ -309,15 +317,14 @@ defmodule RDF.Turtle.Encoder do
defp term(list, state, _, nesting) when is_list(list) do
"(" <>
(
list
|> Enum.map(&term(&1, state, :list, nesting))
|> Enum.join(" ")
) <>
(list
|> Enum.map(&term(&1, state, :list, nesting))
|> Enum.join(" ")) <>
")"
end
defp based_name(%IRI{} = iri, base), do: based_name(to_string(iri), base)
defp based_name(iri, {:ok, base}) do
if String.starts_with?(iri, base) do
"<#{String.slice(iri, String.length(base)..-1)}>"
@ -326,22 +333,23 @@ defmodule RDF.Turtle.Encoder do
defp based_name(_, _), do: nil
defp typed_literal_term(%Literal{} = literal, state, nesting),
do: ~s["#{Literal.lexical(literal)}"^^#{literal |> Literal.datatype_id() |> term(state, :datatype, nesting)}]
do:
~s["#{Literal.lexical(literal)}"^^#{
literal |> Literal.datatype_id() |> term(state, :datatype, nesting)
}]
def prefixed_name(iri, prefixes) do
with {ns, name} <- split_iri(iri) do
case prefixes[ns] do
nil -> nil
nil -> nil
prefix -> prefix <> ":" <> name
end
end
end
defp split_iri(%IRI{} = iri),
do: iri |> IRI.parse |> split_iri()
do: iri |> IRI.parse() |> split_iri()
defp split_iri(%URI{fragment: fragment} = uri) when not is_nil(fragment),
do: {RDF.iri(%URI{uri | fragment: ""}), fragment}
@ -375,7 +383,6 @@ defmodule RDF.Turtle.Encoder do
|> String.replace("\"", ~S[\"])
end
defp newline_indent(nesting),
do: "\n" <> String.duplicate(@indentation_char, nesting)
end

View file

@ -3,7 +3,6 @@ defmodule RDF.Turtle.Encoder.State do
alias RDF.{BlankNode, Description}
def start_link(data, base, prefixes) do
Agent.start_link(fn -> %{data: data, base: base, prefixes: prefixes} end)
end
@ -12,11 +11,11 @@ defmodule RDF.Turtle.Encoder.State do
Agent.stop(state)
end
def data(state), do: Agent.get(state, &(&1.data))
def base(state), do: Agent.get(state, &(&1.base))
def prefixes(state), do: Agent.get(state, &(&1.prefixes))
def list_nodes(state), do: Agent.get(state, &(&1.list_nodes))
def bnode_ref_counter(state), do: Agent.get(state, &(&1.bnode_ref_counter))
def data(state), do: Agent.get(state, & &1.data)
def base(state), do: Agent.get(state, & &1.base)
def prefixes(state), do: Agent.get(state, & &1.prefixes)
def list_nodes(state), do: Agent.get(state, & &1.list_nodes)
def bnode_ref_counter(state), do: Agent.get(state, & &1.bnode_ref_counter)
def bnode_ref_counter(state, bnode) do
bnode_ref_counter(state) |> Map.get(bnode, 0)
@ -30,13 +29,12 @@ defmodule RDF.Turtle.Encoder.State do
end
end
def list_values(head, state), do: Agent.get(state, &(&1.list_values[head]))
def list_values(head, state), do: Agent.get(state, & &1.list_values[head])
def preprocess(state) do
with data = data(state),
{bnode_ref_counter, list_parents} = bnode_info(data),
{list_nodes, list_values} = valid_lists(list_parents, bnode_ref_counter, data)
do
{list_nodes, list_values} = valid_lists(list_parents, bnode_ref_counter, data) do
Agent.update(state, &Map.put(&1, :bnode_ref_counter, bnode_ref_counter))
Agent.update(state, &Map.put(&1, :list_nodes, list_nodes))
Agent.update(state, &Map.put(&1, :list_values, list_values))
@ -45,45 +43,50 @@ defmodule RDF.Turtle.Encoder.State do
defp bnode_info(data) do
data
|> RDF.Data.descriptions
|> Enum.reduce({%{}, %{}},
fn %Description{subject: subject} = description,
{bnode_ref_counter, list_parents} ->
|> RDF.Data.descriptions()
|> Enum.reduce(
{%{}, %{}},
fn %Description{subject: subject} = description, {bnode_ref_counter, list_parents} ->
list_parents =
if match?(%BlankNode{}, subject) and
to_list?(description, Map.get(bnode_ref_counter, subject, 0)),
do: Map.put_new(list_parents, subject, nil),
else: list_parents
list_parents =
if match?(%BlankNode{}, subject) and
to_list?(description, Map.get(bnode_ref_counter, subject, 0)),
do: Map.put_new(list_parents, subject, nil),
else: list_parents
Enum.reduce(description.predications, {bnode_ref_counter, list_parents}, fn
{predicate, objects}, {bnode_ref_counter, list_parents} ->
Enum.reduce(Map.keys(objects), {bnode_ref_counter, list_parents}, fn
%BlankNode{} = object, {bnode_ref_counter, list_parents} ->
{
# Note: The following conditional produces imprecise results
# (sometimes the occurrence in the subject counts, sometimes it doesn't),
# but is sufficient for the current purpose of handling the
# case of a statement with the same subject and object bnode.
Map.update(
bnode_ref_counter,
object,
if(subject == object, do: 2, else: 1),
&(&1 + 1)
),
if predicate == RDF.rest() do
Map.put_new(list_parents, object, subject)
else
list_parents
end
}
Enum.reduce(description.predications, {bnode_ref_counter, list_parents}, fn
({predicate, objects}, {bnode_ref_counter, list_parents}) ->
Enum.reduce(Map.keys(objects), {bnode_ref_counter, list_parents}, fn
(%BlankNode{} = object, {bnode_ref_counter, list_parents}) ->
{
# Note: The following conditional produces imprecise results
# (sometimes the occurrence in the subject counts, sometimes it doesn't),
# but is sufficient for the current purpose of handling the
# case of a statement with the same subject and object bnode.
Map.update(bnode_ref_counter, object,
(if subject == object, do: 2, else: 1), &(&1 + 1)),
if predicate == RDF.rest do
Map.put_new(list_parents, object, subject)
else
list_parents
end
}
(_, {bnode_ref_counter, list_parents}) ->
{bnode_ref_counter, list_parents}
end)
end)
end)
_, {bnode_ref_counter, list_parents} ->
{bnode_ref_counter, list_parents}
end)
end)
end
)
end
@list_properties MapSet.new([
RDF.Utils.Bootstrapping.rdf_iri("first"),
RDF.Utils.Bootstrapping.rdf_iri("rest")
])
RDF.Utils.Bootstrapping.rdf_iri("first"),
RDF.Utils.Bootstrapping.rdf_iri("rest")
])
@dialyzer {:nowarn_function, to_list?: 2}
defp to_list?(%Description{} = description, 1) do
@ -97,39 +100,37 @@ defmodule RDF.Turtle.Encoder.State do
defp to_list?(_, _),
do: false
defp valid_lists(list_parents, bnode_ref_counter, data) do
head_nodes = for {list_node, nil} <- list_parents, do: list_node
all_list_nodes = MapSet.new(
for {list_node, _} <- list_parents, Map.get(bnode_ref_counter, list_node, 0) < 2 do
list_node
end)
Enum.reduce head_nodes, {MapSet.new, %{}},
fn head_node, {valid_list_nodes, list_values} ->
with list when not is_nil(list) <-
RDF.List.new(head_node, data),
list_nodes =
RDF.List.nodes(list),
true <-
Enum.all?(list_nodes, fn
%BlankNode{} = list_node ->
MapSet.member?(all_list_nodes, list_node)
_ ->
false
end)
do
{
Enum.reduce(list_nodes, valid_list_nodes, fn list_node, valid_list_nodes ->
MapSet.put(valid_list_nodes, list_node)
end),
Map.put(list_values, head_node, RDF.List.values(list)),
}
else
_ -> {valid_list_nodes, list_values}
all_list_nodes =
MapSet.new(
for {list_node, _} <- list_parents, Map.get(bnode_ref_counter, list_node, 0) < 2 do
list_node
end
end
end
)
Enum.reduce(head_nodes, {MapSet.new(), %{}}, fn head_node, {valid_list_nodes, list_values} ->
with list when not is_nil(list) <-
RDF.List.new(head_node, data),
list_nodes = RDF.List.nodes(list),
true <-
Enum.all?(list_nodes, fn
%BlankNode{} = list_node ->
MapSet.member?(all_list_nodes, list_node)
_ ->
false
end) do
{
Enum.reduce(list_nodes, valid_list_nodes, fn list_node, valid_list_nodes ->
MapSet.put(valid_list_nodes, list_node)
end),
Map.put(list_values, head_node, RDF.List.values(list))
}
else
_ -> {valid_list_nodes, list_values}
end
end)
end
end

View file

@ -3,7 +3,6 @@ defmodule RDF.Sigils do
Sigils for the most common types of RDF nodes.
"""
@doc ~S"""
Handles the sigil `~I` for IRIs.
@ -34,7 +33,6 @@ defmodule RDF.Sigils do
Macro.escape(RDF.BlankNode.new(bnode))
end
@doc ~S"""
Handles the sigil `~L` for plain Literals.

View file

@ -8,21 +8,20 @@ defmodule RDF.Statement do
alias RDF.{BlankNode, IRI, Literal, Quad, Term, Triple}
import RDF.Guards
@type subject :: IRI.t | BlankNode.t
@type predicate :: IRI.t | BlankNode.t
@type object :: IRI.t | BlankNode.t | Literal.t
@type graph_name :: IRI.t | BlankNode.t
@type subject :: IRI.t() | BlankNode.t()
@type predicate :: IRI.t() | BlankNode.t()
@type object :: IRI.t() | BlankNode.t() | Literal.t()
@type graph_name :: IRI.t() | BlankNode.t()
@type coercible_subject :: subject | atom | String.t
@type coercible_predicate :: predicate | atom | String.t
@type coercible_object :: object | any
@type coercible_graph_name :: graph_name | atom | String.t
@type coercible_subject :: subject | atom | String.t()
@type coercible_predicate :: predicate | atom | String.t()
@type coercible_object :: object | any
@type coercible_graph_name :: graph_name | atom | String.t()
@type qualified_term :: {atom, Term.t | nil}
@type term_mapping :: (qualified_term -> any | nil)
@type t :: Triple.t | Quad.t
@type qualified_term :: {atom, Term.t() | nil}
@type term_mapping :: (qualified_term -> any | nil)
@type t :: Triple.t() | Quad.t()
@doc """
Creates a `RDF.Statement` tuple with proper RDF values.
@ -36,10 +35,10 @@ defmodule RDF.Statement do
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>}
"""
@spec coerce(Triple.coercible_t) :: Triple.t
@spec coerce(Quad.coercible_t) :: Quad.t
@spec coerce(Triple.coercible_t()) :: Triple.t()
@spec coerce(Quad.coercible_t()) :: Quad.t()
def coerce(statement)
def coerce({_, _, _} = triple), do: Triple.new(triple)
def coerce({_, _, _} = triple), do: Triple.new(triple)
def coerce({_, _, _, _} = quad), do: Quad.new(quad)
@doc false
@ -49,7 +48,7 @@ defmodule RDF.Statement do
def coerce_subject(bnode = %BlankNode{}), do: bnode
def coerce_subject("_:" <> identifier), do: RDF.bnode(identifier)
def coerce_subject(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
def coerce_subject(arg), do: raise RDF.Triple.InvalidSubjectError, subject: arg
def coerce_subject(arg), do: raise(RDF.Triple.InvalidSubjectError, subject: arg)
@doc false
@spec coerce_predicate(coercible_predicate) :: predicate
@ -60,7 +59,7 @@ defmodule RDF.Statement do
# TODO: Support an option `:strict_rdf` to explicitly disallow them or produce warnings or ...
def coerce_predicate(bnode = %BlankNode{}), do: bnode
def coerce_predicate(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
def coerce_predicate(arg), do: raise RDF.Triple.InvalidPredicateError, predicate: arg
def coerce_predicate(arg), do: raise(RDF.Triple.InvalidPredicateError, predicate: arg)
@doc false
@spec coerce_object(coercible_object) :: object
@ -80,9 +79,9 @@ defmodule RDF.Statement do
def coerce_graph_name(bnode = %BlankNode{}), do: bnode
def coerce_graph_name("_:" <> identifier), do: RDF.bnode(identifier)
def coerce_graph_name(iri) when maybe_ns_term(iri) or is_binary(iri), do: RDF.iri!(iri)
def coerce_graph_name(arg),
do: raise RDF.Quad.InvalidGraphContextError, graph_context: arg
def coerce_graph_name(arg),
do: raise(RDF.Quad.InvalidGraphContextError, graph_context: arg)
@doc """
Returns a tuple of native Elixir values from a `RDF.Statement` of RDF terms.
@ -117,9 +116,9 @@ defmodule RDF.Statement do
{"S", :p, 42, ~I<http://example.com/Graph>}
"""
@spec values(t | any, term_mapping) :: Triple.t_values | Quad.t_values | nil
@spec values(t | any, term_mapping) :: Triple.t_values() | Quad.t_values() | nil
def values(statement, mapping \\ &default_term_mapping/1)
def values({_, _, _} = triple, mapping), do: RDF.Triple.values(triple, mapping)
def values({_, _, _} = triple, mapping), do: RDF.Triple.values(triple, mapping)
def values({_, _, _, _} = quad, mapping), do: RDF.Quad.values(quad, mapping)
def values(_, _), do: nil
@ -129,7 +128,6 @@ defmodule RDF.Statement do
def default_term_mapping({:graph_name, nil}), do: nil
def default_term_mapping({_, term}), do: RDF.Term.value(term)
@doc """
Checks if the given tuple is a valid RDF statement, i.e. RDF triple or quad.
@ -137,7 +135,7 @@ defmodule RDF.Statement do
position only IRIs and blank nodes allowed, while on the predicate and graph
context position only IRIs allowed. The object position can be any RDF term.
"""
@spec valid?(Triple.t | Quad.t | any) :: boolean
@spec valid?(Triple.t() | Quad.t() | any) :: boolean
def valid?(tuple)
def valid?({subject, predicate, object}) do
@ -152,22 +150,21 @@ defmodule RDF.Statement do
def valid?(_), do: false
@spec valid_subject?(subject | any) :: boolean
def valid_subject?(%IRI{}), do: true
def valid_subject?(%IRI{}), do: true
def valid_subject?(%BlankNode{}), do: true
def valid_subject?(_), do: false
def valid_subject?(_), do: false
@spec valid_predicate?(predicate | any) :: boolean
def valid_predicate?(%IRI{}), do: true
def valid_predicate?(_), do: false
def valid_predicate?(%IRI{}), do: true
def valid_predicate?(_), do: false
@spec valid_object?(object | any) :: boolean
def valid_object?(%IRI{}), do: true
def valid_object?(%BlankNode{}), do: true
def valid_object?(%Literal{}), do: true
def valid_object?(_), do: false
def valid_object?(%IRI{}), do: true
def valid_object?(%BlankNode{}), do: true
def valid_object?(%Literal{}), do: true
def valid_object?(_), do: false
@spec valid_graph_name?(graph_name | any) :: boolean
def valid_graph_name?(%IRI{}), do: true
def valid_graph_name?(_), do: false
def valid_graph_name?(%IRI{}), do: true
def valid_graph_name?(_), do: false
end

View file

@ -11,8 +11,7 @@ defprotocol RDF.Term do
see <https://www.w3.org/TR/sparql11-query/#defn_RDFTerm>
"""
@type t :: RDF.IRI.t | RDF.BlankNode.t | RDF.Literal.t
@type t :: RDF.IRI.t() | RDF.BlankNode.t() | RDF.Literal.t()
@doc """
Checks if the given value is a RDF term.
@ -43,7 +42,6 @@ defprotocol RDF.Term do
@fallback_to_any true
def equal?(term1, term2)
@doc """
Tests for equality of values.
@ -89,52 +87,52 @@ defprotocol RDF.Term do
"""
def value(term)
end
defimpl RDF.Term, for: RDF.IRI do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.IRI.equal_value?(term1, term2)
def coerce(term), do: term
def value(term), do: term.value
def term?(_), do: true
def coerce(term), do: term
def value(term), do: term.value
def term?(_), do: true
end
defimpl RDF.Term, for: RDF.BlankNode do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.BlankNode.equal_value?(term1, term2)
def coerce(term), do: term
def value(term), do: to_string(term)
def term?(_), do: true
def coerce(term), do: term
def value(term), do: to_string(term)
def term?(_), do: true
end
defimpl RDF.Term, for: Reference do
@dialyzer {:nowarn_function, equal_value?: 2}
@dialyzer {:nowarn_function, coerce: 1}
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.BlankNode.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.BlankNode.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: RDF.Literal do
def equal?(term1, term2), do: RDF.Literal.equal?(term1, term2)
def equal?(term1, term2), do: RDF.Literal.equal?(term1, term2)
def equal_value?(term1, term2), do: RDF.Literal.equal_value?(term1, term2)
def coerce(term), do: term
def value(term), do: RDF.Literal.value(term) || RDF.Literal.lexical(term)
def term?(_), do: true
def coerce(term), do: term
def value(term), do: RDF.Literal.value(term) || RDF.Literal.lexical(term)
def term?(_), do: true
end
defimpl RDF.Term, for: Atom do
def equal?(term1, term2), do: term1 == term2
def equal_value?(nil, _), do: nil
def equal_value?(nil, _), do: nil
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(true), do: RDF.XSD.true
def coerce(false), do: RDF.XSD.false
def coerce(nil), do: nil
def coerce(true), do: RDF.XSD.true()
def coerce(false), do: RDF.XSD.false()
def coerce(nil), do: nil
def coerce(term) do
case RDF.Namespace.resolve_term(term) do
{:ok, iri} -> iri
@ -142,90 +140,90 @@ defimpl RDF.Term, for: Atom do
end
end
def value(true), do: true
def value(true), do: true
def value(false), do: false
def value(nil), do: nil
def value(term), do: RDF.Term.value(coerce(term))
def value(nil), do: nil
def value(term), do: RDF.Term.value(coerce(term))
def term?(_), do: false
end
defimpl RDF.Term, for: BitString do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.String.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.String.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Integer do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.Integer.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.Integer.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Float do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.Double.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.Double.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Decimal do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.Decimal.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.Decimal.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: DateTime do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.DateTime.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.DateTime.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: NaiveDateTime do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.DateTime.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.DateTime.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Date do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.Date.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.Date.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Time do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.Time.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.Time.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: URI do
def equal?(term1, term2), do: term1 == term2
def equal?(term1, term2), do: term1 == term2
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(term), do: RDF.XSD.AnyURI.new(term)
def value(term), do: term
def term?(_), do: false
def coerce(term), do: RDF.XSD.AnyURI.new(term)
def value(term), do: term
def term?(_), do: false
end
defimpl RDF.Term, for: Any do
def equal?(term1, term2), do: term1 == term2
def equal_value?(_, _), do: nil
def coerce(_), do: nil
def value(_), do: nil
def term?(_), do: false
def equal_value?(_, _), do: nil
def coerce(_), do: nil
def value(_), do: nil
def term?(_), do: false
end

View file

@ -8,14 +8,13 @@ defmodule RDF.Triple do
alias RDF.Statement
@type t :: {Statement.subject, Statement.predicate, Statement.object}
@type t :: {Statement.subject(), Statement.predicate(), Statement.object()}
@type coercible_t ::
{Statement.coercible_subject, Statement.coercible_predicate,
Statement.coercible_object}
@type t_values :: {String.t, String.t, any}
{Statement.coercible_subject(), Statement.coercible_predicate(),
Statement.coercible_object()}
@type t_values :: {String.t(), String.t(), any}
@doc """
Creates a `RDF.Triple` with proper RDF values.
@ -32,9 +31,9 @@ defmodule RDF.Triple do
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
"""
@spec new(
Statement.coercible_subject,
Statement.coercible_predicate,
Statement.coercible_object
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object()
) :: t
def new(subject, predicate, object) do
{
@ -61,7 +60,6 @@ defmodule RDF.Triple do
@spec new(coercible_t) :: t
def new({subject, predicate, object}), do: new(subject, predicate, object)
@doc """
Returns a tuple of native Elixir values from a `RDF.Triple` of RDF terms.
@ -87,14 +85,13 @@ defmodule RDF.Triple do
{"S", "p", 42}
"""
@spec values(t | any, Statement.term_mapping) :: t_values | nil
@spec values(t | any, Statement.term_mapping()) :: t_values | nil
def values(triple, mapping \\ &Statement.default_term_mapping/1)
def values({subject, predicate, object}, mapping) do
with subject_value when not is_nil(subject_value) <- mapping.({:subject, subject}),
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
object_value when not is_nil(object_value) <- mapping.({:object, object}) do
{subject_value, predicate_value, object_value}
else
_ -> nil
@ -103,7 +100,6 @@ defmodule RDF.Triple do
def values(_, _), do: nil
@doc """
Checks if the given tuple is a valid RDF triple.
@ -115,5 +111,4 @@ defmodule RDF.Triple do
def valid?(tuple)
def valid?({_, _, _} = triple), do: Statement.valid?(triple)
def valid?(_), do: false
end

View file

@ -1,7 +1,7 @@
defmodule RDF.Utils.Bootstrapping do
@moduledoc !"""
This module holds functions to circumvent circular dependency problems.
"""
This module holds functions to circumvent circular dependency problems.
"""
@xsd_base_iri "http://www.w3.org/2001/XMLSchema#"
@rdf_base_iri "http://www.w3.org/1999/02/22-rdf-syntax-ns#"

View file

@ -1,6 +1,6 @@
defmodule RDF.Utils.Guards do
defguard is_ordinary_atom(term)
when is_atom(term) and term not in [nil, true, false]
when is_atom(term) and term not in [nil, true, false]
defguard maybe_module(term) when is_ordinary_atom(term)
end

View file

@ -13,35 +13,41 @@ defmodule RDF.Utils.ResourceClassifier do
def property?(resource, data) do
with %Description{} = description <- RDF.Data.description(data, resource) do
property_by_domain?(description) or
property_by_rdf_type?(Description.get(description, @rdf_type))
property_by_rdf_type?(Description.get(description, @rdf_type))
end
# || property_by_predicate_usage?(resource, data)
# || property_by_predicate_usage?(resource, data)
end
@property_properties Enum.map(~w[
@property_properties (Enum.map(
~w[
domain
range
subPropertyOf
], &rdfs_iri/1) ++
Enum.map(~w[
],
&rdfs_iri/1
) ++
Enum.map(
~w[
equivalentProperty
inverseOf
propertyDisjointWith
], &owl_iri/1)
|> MapSet.new
],
&owl_iri/1
))
|> MapSet.new()
defp property_by_domain?(description) do
Enum.any? @property_properties, fn property ->
Enum.any?(@property_properties, fn property ->
description[property]
end
end)
end
@property_classes [
rdf_iri("Property"),
rdfs_iri("ContainerMembershipProperty")
|
Enum.map(~w[
| Enum.map(
~w[
ObjectProperty
DatatypeProperty
AnnotationProperty
@ -53,23 +59,22 @@ defmodule RDF.Utils.ResourceClassifier do
IrreflexiveProperty
TransitiveProperty
DeprecatedProperty
], &owl_iri/1)
],
&owl_iri/1
)
]
|> MapSet.new
|> MapSet.new()
@dialyzer {:nowarn_function, property_by_rdf_type?: 1}
defp property_by_rdf_type?(nil), do: nil
defp property_by_rdf_type?(types) do
not (
types
|> MapSet.new
|> MapSet.disjoint?(@property_classes)
)
not (types
|> MapSet.new()
|> MapSet.disjoint?(@property_classes))
end
# defp property_by_predicate_usage?(resource, data) do
# resource in Graph.predicates(data) || nil
# end
# defp property_by_predicate_usage?(resource, data) do
# resource in Graph.predicates(data) || nil
# end
end

View file

@ -23,13 +23,14 @@ defmodule RDF.Vocabulary.Namespace do
Defines a `RDF.Namespace` module for a RDF vocabulary.
"""
defmacro defvocab(name, opts) do
strict = strict?(opts)
strict = strict?(opts)
base_iri = base_iri!(opts)
file = filename!(opts)
file = filename!(opts)
{terms, data} =
case source!(opts) do
{:terms, terms} -> {terms, nil}
{:data, data} -> {rdf_data_vocab_terms(data, base_iri), data}
{:data, data} -> {rdf_data_vocab_terms(data, base_iri), data}
end
unless Mix.env() == :test do
@ -37,6 +38,7 @@ defmodule RDF.Vocabulary.Namespace do
end
ignored_terms = ignored_terms!(opts)
terms =
terms
|> term_mapping!(opts)
@ -44,8 +46,9 @@ defmodule RDF.Vocabulary.Namespace do
|> validate_terms!
|> validate_characters!(opts)
|> validate_case!(data, base_iri, opts)
case_separated_terms = group_terms_by_case(terms)
lowercased_terms = Map.get(case_separated_terms, :lowercased, %{})
lowercased_terms = Map.get(case_separated_terms, :lowercased, %{})
quote do
vocabdoc = Module.delete_attribute(__MODULE__, :vocabdoc)
@ -60,7 +63,7 @@ defmodule RDF.Vocabulary.Namespace do
end
@base_iri unquote(base_iri)
@spec __base_iri__ :: String.t
@spec __base_iri__ :: String.t()
def __base_iri__, do: @base_iri
@strict unquote(strict)
@ -69,24 +72,24 @@ defmodule RDF.Vocabulary.Namespace do
@terms unquote(Macro.escape(terms))
@impl Elixir.RDF.Namespace
def __terms__, do: @terms |> Map.keys
def __terms__, do: @terms |> Map.keys()
@ignored_terms unquote(Macro.escape(ignored_terms))
@doc """
Returns all known IRIs of the vocabulary.
"""
@spec __iris__ :: [Elixir.RDF.IRI.t]
@spec __iris__ :: [Elixir.RDF.IRI.t()]
def __iris__ do
@terms
|> Enum.map(fn
{term, true} -> term_to_iri(@base_iri, term)
{_alias, term} -> term_to_iri(@base_iri, term)
end)
|> Enum.uniq
{term, true} -> term_to_iri(@base_iri, term)
{_alias, term} -> term_to_iri(@base_iri, term)
end)
|> Enum.uniq()
end
define_vocab_terms unquote(lowercased_terms), unquote(base_iri)
define_vocab_terms(unquote(lowercased_terms), unquote(base_iri))
@impl Elixir.RDF.Namespace
@dialyzer {:nowarn_function, __resolve_term__: 1}
@ -95,15 +98,16 @@ defmodule RDF.Vocabulary.Namespace do
nil ->
if @strict or MapSet.member?(@ignored_terms, term) do
{:error,
%Elixir.RDF.Namespace.UndefinedTermError{
message: "undefined term #{term} in strict vocabulary #{__MODULE__}"
}
}
%Elixir.RDF.Namespace.UndefinedTermError{
message: "undefined term #{term} in strict vocabulary #{__MODULE__}"
}}
else
{:ok, term_to_iri(@base_iri, term)}
end
true ->
{:ok, term_to_iri(@base_iri, term)}
original_term ->
{:ok, term_to_iri(@base_iri, original_term)}
end
@ -134,39 +138,43 @@ defmodule RDF.Vocabulary.Namespace do
defmacro define_vocab_terms(terms, base_iri) do
terms
|> Stream.filter(fn
{term, true} -> valid_term?(term)
{_, _} -> true
end)
{term, true} -> valid_term?(term)
{_, _} -> true
end)
|> Stream.map(fn
{term, true} -> {term, term}
{term, original_term} -> {term, original_term}
end)
{term, true} -> {term, term}
{term, original_term} -> {term, original_term}
end)
|> Enum.map(fn {term, iri_suffix} ->
iri = term_to_iri(base_iri, iri_suffix)
quote do
@doc "<#{unquote(to_string(iri))}>"
def unquote(term)(), do: unquote(Macro.escape(iri))
iri = term_to_iri(base_iri, iri_suffix)
@doc "`RDF.Description` builder for `#{unquote(term)}/0`"
def unquote(term)(subject, object) do
RDF.Description.new(subject, unquote(Macro.escape(iri)), object)
end
quote do
@doc "<#{unquote(to_string(iri))}>"
def unquote(term)(), do: unquote(Macro.escape(iri))
# Is there a better way to support multiple objects via arguments?
@doc false
def unquote(term)(subject, o1, o2),
do: unquote(term)(subject, [o1, o2])
@doc false
def unquote(term)(subject, o1, o2, o3),
do: unquote(term)(subject, [o1, o2, o3])
@doc false
def unquote(term)(subject, o1, o2, o3, o4),
do: unquote(term)(subject, [o1, o2, o3, o4])
@doc false
def unquote(term)(subject, o1, o2, o3, o4, o5),
do: unquote(term)(subject, [o1, o2, o3, o4, o5])
@doc "`RDF.Description` builder for `#{unquote(term)}/0`"
def unquote(term)(subject, object) do
RDF.Description.new(subject, unquote(Macro.escape(iri)), object)
end
end)
# Is there a better way to support multiple objects via arguments?
@doc false
def unquote(term)(subject, o1, o2),
do: unquote(term)(subject, [o1, o2])
@doc false
def unquote(term)(subject, o1, o2, o3),
do: unquote(term)(subject, [o1, o2, o3])
@doc false
def unquote(term)(subject, o1, o2, o3, o4),
do: unquote(term)(subject, [o1, o2, o3, o4])
@doc false
def unquote(term)(subject, o1, o2, o3, o4, o5),
do: unquote(term)(subject, [o1, o2, o3, o4, o5])
end
end)
end
defp strict?(opts),
@ -174,9 +182,10 @@ defmodule RDF.Vocabulary.Namespace do
defp base_iri!(opts) do
base_iri = Keyword.fetch!(opts, :base_iri)
unless is_binary(base_iri) and String.ends_with?(base_iri, ["/", "#"]) do
raise RDF.Namespace.InvalidVocabBaseIRIError,
"a base_iri without a trailing '/' or '#' is invalid"
"a base_iri without a trailing '/' or '#' is invalid"
else
base_iri
end
@ -184,9 +193,15 @@ defmodule RDF.Vocabulary.Namespace do
defp source!(opts) do
cond do
Keyword.has_key?(opts, :file) -> {:data, filename!(opts) |> RDF.read_file!()}
rdf_data = Keyword.get(opts, :data) -> {:data, raw_rdf_data(rdf_data)}
terms = Keyword.get(opts, :terms) -> {:terms, terms_from_user_input!(terms)}
Keyword.has_key?(opts, :file) ->
{:data, filename!(opts) |> RDF.read_file!()}
rdf_data = Keyword.get(opts, :data) ->
{:data, raw_rdf_data(rdf_data)}
terms = Keyword.get(opts, :terms) ->
{:terms, terms_from_user_input!(terms)}
true ->
raise KeyError, key: ~w[terms data file], term: opts
end
@ -194,81 +209,89 @@ defmodule RDF.Vocabulary.Namespace do
defp terms_from_user_input!(terms) do
# TODO: find an alternative to Code.eval_quoted - We want to support that the terms can be given as sigils ...
{terms, _ } = Code.eval_quoted(terms, [], rdf_data_env())
Enum.map terms, fn
term when is_atom(term) -> term
term when is_binary(term) -> String.to_atom(term)
{terms, _} = Code.eval_quoted(terms, [], rdf_data_env())
Enum.map(terms, fn
term when is_atom(term) ->
term
term when is_binary(term) ->
String.to_atom(term)
term ->
raise RDF.Namespace.InvalidTermError,
"'#{term}' is not a valid vocabulary term"
end
"'#{term}' is not a valid vocabulary term"
end)
end
defp raw_rdf_data(%RDF.Description{} = rdf_data), do: rdf_data
defp raw_rdf_data(%RDF.Graph{} = rdf_data), do: rdf_data
defp raw_rdf_data(%RDF.Dataset{} = rdf_data), do: rdf_data
defp raw_rdf_data(rdf_data) do
# TODO: find an alternative to Code.eval_quoted
{rdf_data, _} = Code.eval_quoted(rdf_data, [], rdf_data_env())
rdf_data
end
defp ignored_terms!(opts) do
# TODO: find an alternative to Code.eval_quoted - We want to support that the terms can be given as sigils ...
with terms = Keyword.get(opts, :ignore, []) do
{terms, _ } = Code.eval_quoted(terms, [], rdf_data_env())
{terms, _} = Code.eval_quoted(terms, [], rdf_data_env())
terms
|> Enum.map(fn
term when is_atom(term) -> term
term when is_binary(term) -> String.to_atom(term)
term -> raise RDF.Namespace.InvalidTermError, inspect(term)
end)
|> MapSet.new
term when is_atom(term) -> term
term when is_binary(term) -> String.to_atom(term)
term -> raise RDF.Namespace.InvalidTermError, inspect(term)
end)
|> MapSet.new()
end
end
defp term_mapping!(terms, opts) do
terms = Map.new terms, fn
term when is_atom(term) -> {term, true}
term -> {String.to_atom(term), true}
end
terms =
Map.new(terms, fn
term when is_atom(term) -> {term, true}
term -> {String.to_atom(term), true}
end)
Keyword.get(opts, :alias, [])
|> Enum.reduce(terms, fn {alias, original_term}, terms ->
term = String.to_atom(original_term)
cond do
not valid_characters?(alias) ->
raise RDF.Namespace.InvalidAliasError,
"alias '#{alias}' contains invalid characters"
term = String.to_atom(original_term)
Map.get(terms, alias) == true ->
raise RDF.Namespace.InvalidAliasError,
"alias '#{alias}' already defined"
cond do
not valid_characters?(alias) ->
raise RDF.Namespace.InvalidAliasError,
"alias '#{alias}' contains invalid characters"
strict?(opts) and not Map.has_key?(terms, term) ->
raise RDF.Namespace.InvalidAliasError,
Map.get(terms, alias) == true ->
raise RDF.Namespace.InvalidAliasError,
"alias '#{alias}' already defined"
strict?(opts) and not Map.has_key?(terms, term) ->
raise RDF.Namespace.InvalidAliasError,
"term '#{original_term}' is not a term in this vocabulary"
Map.get(terms, term, true) != true ->
raise RDF.Namespace.InvalidAliasError,
Map.get(terms, term, true) != true ->
raise RDF.Namespace.InvalidAliasError,
"'#{original_term}' is already an alias"
true ->
Map.put(terms, alias, to_string(original_term))
end
end)
true ->
Map.put(terms, alias, to_string(original_term))
end
end)
end
defp aliased_terms(terms) do
terms
|> Map.values
|> MapSet.new
|> Map.values()
|> MapSet.new()
|> MapSet.delete(true)
|> Enum.map(&String.to_atom/1)
end
@invalid_terms MapSet.new ~w[
@invalid_terms MapSet.new(~w[
and
or
xor
@ -288,7 +311,7 @@ defmodule RDF.Vocabulary.Namespace do
require
super
__aliases__
]a
]a)
def invalid_terms, do: @invalid_terms
@ -309,18 +332,17 @@ defmodule RDF.Vocabulary.Namespace do
defp handle_invalid_terms!(invalid_terms) do
raise RDF.Namespace.InvalidTermError, """
The following terms can not be used, because they conflict with the Elixir semantics:
The following terms can not be used, because they conflict with the Elixir semantics:
- #{Enum.join(invalid_terms, "\n- ")}
- #{Enum.join(invalid_terms, "\n- ")}
You have the following options:
You have the following options:
- define an alias with the :alias option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
- define an alias with the :alias option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
end
defp validate_characters!(terms, opts) do
if (handling = Keyword.get(opts, :invalid_characters, :fail)) == :ignore do
terms
@ -333,8 +355,7 @@ defmodule RDF.Vocabulary.Namespace do
defp detect_invalid_characters(terms) do
with aliased_terms = aliased_terms(terms) do
for {term, _} <- terms, term not in aliased_terms and not valid_characters?(term),
do: term
for {term, _} <- terms, term not in aliased_terms and not valid_characters?(term), do: term
end
end
@ -342,32 +363,35 @@ defmodule RDF.Vocabulary.Namespace do
defp handle_invalid_characters(invalid_terms, :fail, _) do
raise RDF.Namespace.InvalidTermError, """
The following terms contain invalid characters:
The following terms contain invalid characters:
- #{Enum.join(invalid_terms, "\n- ")}
- #{Enum.join(invalid_terms, "\n- ")}
You have the following options:
You have the following options:
- if you are in control of the vocabulary, consider renaming the resource
- define an alias with the :alias option on defvocab
- change the handling of invalid characters with the :invalid_characters option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
- if you are in control of the vocabulary, consider renaming the resource
- define an alias with the :alias option on defvocab
- change the handling of invalid characters with the :invalid_characters option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
end
defp handle_invalid_characters(invalid_terms, :warn, terms) do
Enum.each invalid_terms, fn term ->
IO.warn "'#{term}' is not valid term, since it contains invalid characters"
end
Enum.each(invalid_terms, fn term ->
IO.warn("'#{term}' is not valid term, since it contains invalid characters")
end)
terms
end
defp valid_characters?(term) when is_atom(term),
do: valid_characters?(Atom.to_string(term))
defp valid_characters?(term),
do: Regex.match?(~r/^[a-zA-Z_]\w*$/, term)
defp validate_case!(terms, nil, _, _), do: terms
defp validate_case!(terms, data, base_iri, opts) do
if (handling = Keyword.get(opts, :case_violations, :warn)) == :ignore do
terms
@ -381,40 +405,43 @@ defmodule RDF.Vocabulary.Namespace do
defp detect_case_violations(terms, data, base_iri) do
aliased_terms = aliased_terms(terms)
terms
|> Enum.filter(fn {term, _} ->
not(Atom.to_string(term) |> String.starts_with?("_"))
end)
not (Atom.to_string(term) |> String.starts_with?("_"))
end)
|> Enum.filter(fn
{term, true} ->
if term not in aliased_terms do
proper_case?(term, base_iri, Atom.to_string(term), data)
end
{term, original_term} ->
proper_case?(term, base_iri, original_term, data)
end)
{term, true} ->
if term not in aliased_terms do
proper_case?(term, base_iri, Atom.to_string(term), data)
end
{term, original_term} ->
proper_case?(term, base_iri, original_term, data)
end)
end
defp proper_case?(term, base_iri, iri_suffix, data) do
case ResourceClassifier.property?(term_to_iri(base_iri, iri_suffix), data) do
true -> not lowercase?(term)
true -> not lowercase?(term)
false -> lowercase?(term)
nil -> lowercase?(term)
nil -> lowercase?(term)
end
end
defp group_case_violations(violations) do
violations
|> Enum.group_by(fn
{term, true} ->
if lowercase?(term),
do: :lowercased_term,
else: :capitalized_term
{term, _original} ->
if lowercase?(term),
do: :lowercased_alias,
else: :capitalized_alias
end)
{term, true} ->
if lowercase?(term),
do: :lowercased_term,
else: :capitalized_term
{term, _original} ->
if lowercase?(term),
do: :lowercased_alias,
else: :capitalized_alias
end)
end
defp handle_case_violations(%{} = violations, _, terms, _, _) when map_size(violations) == 0,
@ -427,101 +454,106 @@ defmodule RDF.Vocabulary.Namespace do
|> Enum.map(&to_string/1)
|> Enum.join("\n- ")
end
alias_violations = fn violations ->
violations
|> Enum.map(fn {term, original} ->
"alias #{term} for #{term_to_iri(base_iri, original)}"
end)
"alias #{term} for #{term_to_iri(base_iri, original)}"
end)
|> Enum.join("\n- ")
end
violation_error_lines =
violations
|> Enum.map(fn
{:capitalized_term, violations} ->
"""
Terms for properties should be lowercased, but the following properties are
capitalized:
{:capitalized_term, violations} ->
"""
Terms for properties should be lowercased, but the following properties are
capitalized:
- #{resource_name_violations.(violations)}
- #{resource_name_violations.(violations)}
"""
{:lowercased_term, violations} ->
"""
Terms for non-property resource should be capitalized, but the following
non-properties are lowercased:
"""
- #{resource_name_violations.(violations)}
{:lowercased_term, violations} ->
"""
Terms for non-property resource should be capitalized, but the following
non-properties are lowercased:
"""
{:capitalized_alias, violations} ->
"""
Terms for properties should be lowercased, but the following aliases for
properties are capitalized:
- #{resource_name_violations.(violations)}
- #{alias_violations.(violations)}
"""
"""
{:lowercased_alias, violations} ->
"""
Terms for non-property resource should be capitalized, but the following
aliases for non-properties are lowercased:
{:capitalized_alias, violations} ->
"""
Terms for properties should be lowercased, but the following aliases for
properties are capitalized:
- #{alias_violations.(violations)}
- #{alias_violations.(violations)}
"""
end)
|> Enum.join
"""
{:lowercased_alias, violations} ->
"""
Terms for non-property resource should be capitalized, but the following
aliases for non-properties are lowercased:
- #{alias_violations.(violations)}
"""
end)
|> Enum.join()
raise RDF.Namespace.InvalidTermError, """
Case violations detected
Case violations detected
#{violation_error_lines}
You have the following options:
#{violation_error_lines}
You have the following options:
- if you are in control of the vocabulary, consider renaming the resource
- define a properly cased alias with the :alias option on defvocab
- change the handling of case violations with the :case_violations option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
- if you are in control of the vocabulary, consider renaming the resource
- define a properly cased alias with the :alias option on defvocab
- change the handling of case violations with the :case_violations option on defvocab
- ignore the resource with the :ignore option on defvocab
"""
end
defp handle_case_violations(violations, :warn, terms, base_iri, _) do
for {type, violations} <- violations,
{term, original} <- violations do
{term, original} <- violations do
case_violation_warning(type, term, original, base_iri)
end
terms
end
defp case_violation_warning(:capitalized_term, term, _, base_iri) do
IO.warn "'#{term_to_iri(base_iri, term)}' is a capitalized property"
IO.warn("'#{term_to_iri(base_iri, term)}' is a capitalized property")
end
defp case_violation_warning(:lowercased_term, term, _, base_iri) do
IO.warn "'#{term_to_iri(base_iri, term)}' is a lowercased non-property resource"
IO.warn("'#{term_to_iri(base_iri, term)}' is a lowercased non-property resource")
end
defp case_violation_warning(:capitalized_alias, term, _, _) do
IO.warn "capitalized alias '#{term}' for a property"
IO.warn("capitalized alias '#{term}' for a property")
end
defp case_violation_warning(:lowercased_alias, term, _, _) do
IO.warn "lowercased alias '#{term}' for a non-property resource"
IO.warn("lowercased alias '#{term}' for a non-property resource")
end
defp filename!(opts) do
if filename = Keyword.get(opts, :file) do
cond do
File.exists?(filename) ->
filename
File.exists?(expanded_filename = Path.expand(filename, @vocabs_dir)) ->
expanded_filename
true ->
raise File.Error, path: filename, action: "find", reason: :enoent
end
end
end
end
@ -532,13 +564,13 @@ defmodule RDF.Vocabulary.Namespace do
defp rdf_data_vocab_terms(data, base_iri) do
data
|> RDF.Data.resources
|> RDF.Data.resources()
|> Stream.filter(fn
%RDF.IRI{} -> true
_ -> false
end)
%RDF.IRI{} -> true
_ -> false
end)
|> Stream.map(&to_string/1)
|> Stream.map(&(strip_base_iri(&1, base_iri)))
|> Stream.map(&strip_base_iri(&1, base_iri))
|> Stream.filter(&vocab_term?/1)
|> Enum.map(&String.to_atom/1)
end
@ -546,17 +578,18 @@ defmodule RDF.Vocabulary.Namespace do
defp group_terms_by_case(terms) do
terms
|> Enum.group_by(fn {term, _} ->
if lowercase?(term),
do: :lowercased,
else: :capitalized
end)
if lowercase?(term),
do: :lowercased,
else: :capitalized
end)
|> Map.new(fn {group, term_mapping} ->
{group, Map.new(term_mapping)}
end)
{group, Map.new(term_mapping)}
end)
end
defp lowercase?(term) when is_atom(term),
do: Atom.to_string(term) |> lowercase?
defp lowercase?(term),
do: term =~ ~r/^(_|\p{Ll})/u
@ -567,15 +600,18 @@ defmodule RDF.Vocabulary.Namespace do
end
defp vocab_term?(""), do: false
defp vocab_term?(term) when is_binary(term) do
not String.contains?(term, "/")
end
defp vocab_term?(_), do: false
@doc false
@spec term_to_iri(String.t, String.t | atom) :: RDF.IRI.t
@spec term_to_iri(String.t(), String.t() | atom) :: RDF.IRI.t()
def term_to_iri(base_iri, term) when is_atom(term),
do: term_to_iri(base_iri, Atom.to_string(term))
def term_to_iri(base_iri, term),
do: RDF.iri(base_iri <> term)
@ -587,5 +623,4 @@ defmodule RDF.Vocabulary.Namespace do
_ -> false
end
end
end

View file

@ -49,6 +49,7 @@ defmodule RDF.XSD do
defdelegate unquote(String.to_atom(datatype.name))(value, opts), to: datatype, as: :new
elixir_name = Macro.underscore(datatype.name)
unless datatype.name == elixir_name do
defdelegate unquote(String.to_atom(elixir_name))(value), to: datatype, as: :new
defdelegate unquote(String.to_atom(elixir_name))(value, opts), to: datatype, as: :new
@ -58,6 +59,6 @@ defmodule RDF.XSD do
defdelegate datetime(value), to: XSD.DateTime, as: :new
defdelegate datetime(value, opts), to: XSD.DateTime, as: :new
defdelegate unquote(true)(), to: XSD.Boolean.Value
defdelegate unquote(true)(), to: XSD.Boolean.Value
defdelegate unquote(false)(), to: XSD.Boolean.Value
end

View file

@ -174,6 +174,7 @@ defmodule RDF.XSD.Datatype do
@doc false
def most_specific(left, right)
def most_specific(datatype, datatype), do: datatype
def most_specific(left, right) do
cond do
left.datatype?(right) -> right
@ -182,7 +183,6 @@ defmodule RDF.XSD.Datatype do
end
end
defmacro __using__(opts) do
quote do
defstruct [:value, :uncanonical_lexical]
@ -201,10 +201,10 @@ defmodule RDF.XSD.Datatype do
}
@doc !"""
This function is just used to check if a module is a RDF.XSD.Datatype.
This function is just used to check if a module is a RDF.XSD.Datatype.
See `RDF.Literal.Datatype.Registry.is_xsd_datatype?/1`.
"""
See `RDF.Literal.Datatype.Registry.is_xsd_datatype?/1`.
"""
def __xsd_datatype_indicator__, do: true
@doc """
@ -214,19 +214,23 @@ defmodule RDF.XSD.Datatype do
def datatype?(%RDF.Literal{literal: literal}), do: datatype?(literal)
def datatype?(%datatype{}), do: datatype?(datatype)
def datatype?(__MODULE__), do: true
def datatype?(datatype) when maybe_module(datatype) do
RDF.XSD.datatype?(datatype) and datatype.derived_from?(__MODULE__)
end
def datatype?(_), do: false
@doc false
def datatype!(%__MODULE__{}), do: true
def datatype!((%datatype{} = literal)) do
def datatype!(%datatype{} = literal) do
datatype?(datatype) ||
raise RDF.XSD.Datatype.Mismatch, value: literal, expected_type: __MODULE__
end
def datatype!(value),
do: raise RDF.XSD.Datatype.Mismatch, value: value, expected_type: __MODULE__
do: raise(RDF.XSD.Datatype.Mismatch, value: value, expected_type: __MODULE__)
@doc """
Creates a new `RDF.Literal` with this datatype and the given `value`.
@ -288,7 +292,8 @@ defmodule RDF.XSD.Datatype do
end
@doc false
@spec build_valid(any, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) :: RDF.Literal.t()
@spec build_valid(any, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) ::
RDF.Literal.t()
def build_valid(value, lexical, opts) do
if Keyword.get(opts, :canonicalize) do
literal(%__MODULE__{value: value})
@ -310,7 +315,6 @@ defmodule RDF.XSD.Datatype do
literal(%__MODULE__{uncanonical_lexical: init_invalid_lexical(lexical, opts)})
end
@doc """
Returns the value of a `RDF.Literal` of this or a derived datatype.
"""
@ -342,7 +346,10 @@ defmodule RDF.XSD.Datatype do
"""
@impl RDF.Literal.Datatype
def canonical_lexical(%RDF.Literal{literal: literal}), do: canonical_lexical(literal)
def canonical_lexical(%__MODULE__{value: value}) when not is_nil(value), do: canonical_mapping(value)
def canonical_lexical(%__MODULE__{value: value}) when not is_nil(value),
do: canonical_mapping(value)
def canonical_lexical(_), do: nil
@doc """
@ -380,13 +387,16 @@ defmodule RDF.XSD.Datatype do
def valid?(%RDF.Literal{literal: literal}), do: valid?(literal)
def valid?(%__MODULE__{value: @invalid_value}), do: false
def valid?(%__MODULE__{}), do: true
def valid?((%datatype{} = literal)),
def valid?(%datatype{} = literal),
do: datatype?(datatype) and datatype.valid?(literal)
def valid?(_), do: false
@doc false
defp equality_path(left_datatype, right_datatype)
defp equality_path(datatype, datatype), do: {:same_or_derived, datatype}
defp equality_path(left_datatype, right_datatype) do
if RDF.XSD.datatype?(left_datatype) and RDF.XSD.datatype?(right_datatype) do
if datatype = RDF.XSD.Datatype.most_specific(left_datatype, right_datatype) do
@ -399,7 +409,6 @@ defmodule RDF.XSD.Datatype do
end
end
@doc """
Compares two `RDF.Literal`s.
@ -409,14 +418,15 @@ defmodule RDF.XSD.Datatype do
due to their datatype, or `:indeterminate` is returned, when the order of the given values is
not defined on only partially ordered datatypes.
"""
@spec compare(RDF.Literal.t() | any, RDF.Literal.t() | any) :: RDF.Literal.Datatype.comparison_result | :indeterminate | nil
@spec compare(RDF.Literal.t() | any, RDF.Literal.t() | any) ::
RDF.Literal.Datatype.comparison_result() | :indeterminate | nil
def compare(left, right)
def compare(left, %RDF.Literal{literal: right}), do: compare(left, right)
def compare(%RDF.Literal{literal: left}, right), do: compare(left, right)
def compare(left, right) do
if RDF.XSD.datatype?(left) and RDF.XSD.datatype?(right) and
RDF.Literal.Datatype.valid?(left) and RDF.Literal.Datatype.valid?(right) do
RDF.Literal.Datatype.valid?(left) and RDF.Literal.Datatype.valid?(right) do
do_compare(left, right)
end
end

View file

@ -58,25 +58,33 @@ defmodule RDF.XSD.Datatype.Primitive do
@impl RDF.Literal.Datatype
def do_cast(value) do
if datatype?(value) do # i.e. derived datatype
# i.e. derived datatype
if datatype?(value) do
build_valid(value.value, value.uncanonical_lexical, [])
end
end
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(%left_datatype{} = left, %right_datatype{} = right) do
def do_equal_value_same_or_derived_datatypes?(
%left_datatype{} = left,
%right_datatype{} = right
) do
left_datatype.value(left) == right_datatype.value(right)
end
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: nil
@impl RDF.Literal.Datatype
@impl RDF.Literal.Datatype
def do_compare(%left_datatype{} = left, %right_datatype{} = right) do
if left_datatype.datatype?(right_datatype) or right_datatype.datatype?(left_datatype) do
case {left_datatype.value(left), right_datatype.value(right)} do
{left_value, right_value} when left_value < right_value -> :lt
{left_value, right_value} when left_value > right_value -> :gt
{left_value, right_value} when left_value < right_value ->
:lt
{left_value, right_value} when left_value > right_value ->
:gt
_ ->
if left_datatype.equal_value?(left, right), do: :eq
end

View file

@ -15,7 +15,6 @@ defmodule RDF.XSD.AnyURI do
import RDF.Guards
def_applicable_facet XSD.Facets.MinLength
def_applicable_facet XSD.Facets.MaxLength
def_applicable_facet XSD.Facets.Length
@ -41,7 +40,6 @@ defmodule RDF.XSD.AnyURI do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value
def lexical_mapping(lexical, _), do: URI.parse(lexical)

View file

@ -12,7 +12,6 @@ defmodule RDF.XSD.Boolean do
alias RDF.XSD
def_applicable_facet XSD.Facets.Pattern
@doc false
@ -20,7 +19,6 @@ defmodule RDF.XSD.Boolean do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, _) do
with lexical do
@ -142,6 +140,7 @@ defmodule RDF.XSD.Boolean do
@spec fn_not(input_value) :: t() | nil
def fn_not(value)
def fn_not(%RDF.Literal{literal: literal}), do: fn_not(literal)
def fn_not(value) do
case ebv(value) do
%RDF.Literal{literal: %__MODULE__{value: true}} -> XSD.Boolean.Value.false()
@ -177,6 +176,7 @@ defmodule RDF.XSD.Boolean do
def logical_and(left, right)
def logical_and(%RDF.Literal{literal: left}, right), do: logical_and(left, right)
def logical_and(left, %RDF.Literal{literal: right}), do: logical_and(left, right)
def logical_and(left, right) do
case ebv(left) do
%RDF.Literal{literal: %__MODULE__{value: false}} ->
@ -223,6 +223,7 @@ defmodule RDF.XSD.Boolean do
def logical_or(left, right)
def logical_or(%RDF.Literal{literal: left}, right), do: logical_or(left, right)
def logical_or(left, %RDF.Literal{literal: right}), do: logical_or(left, right)
def logical_or(left, right) do
case ebv(left) do
%RDF.Literal{literal: %__MODULE__{value: true}} ->

View file

@ -1,14 +1,14 @@
defmodule RDF.XSD.Boolean.Value do
@moduledoc !"""
This module holds the two boolean value literals, so they can be accessed
directly without needing to construct them every time.
They can't be defined in the RDF.XSD.Boolean module, because we can not use
the `RDF.XSD.Boolean.new` function without having it compiled first.
"""
This module holds the two boolean value literals, so they can be accessed
directly without needing to construct them every time.
They can't be defined in the RDF.XSD.Boolean module, because we can not use
the `RDF.XSD.Boolean.new` function without having it compiled first.
"""
@xsd_true RDF.XSD.Boolean.new(true)
@xsd_true RDF.XSD.Boolean.new(true)
@xsd_false RDF.XSD.Boolean.new(false)
def unquote(:true)(), do: @xsd_true
def unquote(:false)(), do: @xsd_false
def unquote(true)(), do: @xsd_true
def unquote(false)(), do: @xsd_false
end

View file

@ -16,7 +16,6 @@ defmodule RDF.XSD.Date do
alias RDF.XSD
def_applicable_facet XSD.Facets.ExplicitTimezone
def_applicable_facet XSD.Facets.Pattern
@ -32,7 +31,6 @@ defmodule RDF.XSD.Date do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
# TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ...
@grammar ~r/\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@tz_grammar ~r/\A((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)\Z/
@ -165,7 +163,6 @@ defmodule RDF.XSD.Date do
end
end
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right) do
XSD.DateTime.equal_value?(

View file

@ -11,7 +11,6 @@ defmodule RDF.XSD.DateTime do
alias RDF.XSD
def_applicable_facet XSD.Facets.ExplicitTimezone
def_applicable_facet XSD.Facets.Pattern
@ -27,7 +26,6 @@ defmodule RDF.XSD.DateTime do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case DateTime.from_iso8601(lexical) do
@ -120,6 +118,7 @@ defmodule RDF.XSD.DateTime do
def tz(xsd_datetime)
def tz(%RDF.Literal{literal: xsd_datetime}), do: tz(xsd_datetime)
def tz(%__MODULE__{value: %NaiveDateTime{}}), do: ""
def tz(date_time_literal) do
if valid?(date_time_literal) do
date_time_literal
@ -134,6 +133,7 @@ defmodule RDF.XSD.DateTime do
@spec canonical_lexical_with_zone(RDF.Literal.t() | t()) :: String.t() | nil
def canonical_lexical_with_zone(%RDF.Literal{literal: xsd_datetime}),
do: canonical_lexical_with_zone(xsd_datetime)
def canonical_lexical_with_zone(%__MODULE__{} = xsd_datetime) do
case tz(xsd_datetime) do
nil ->

View file

@ -12,7 +12,6 @@ defmodule RDF.XSD.Decimal do
alias RDF.XSD
alias Elixir.Decimal, as: D
def_applicable_facet XSD.Facets.MinInclusive
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
@ -56,7 +55,6 @@ defmodule RDF.XSD.Decimal do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
if String.contains?(lexical, ~w[e E]) do
@ -136,7 +134,7 @@ defmodule RDF.XSD.Decimal do
XSD.Boolean.datatype?(literal) ->
case literal.value do
false -> new(0.0)
true -> new(1.0)
true -> new(1.0)
end
XSD.Integer.datatype?(literal) ->
@ -151,13 +149,13 @@ defmodule RDF.XSD.Decimal do
end
end
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
def do_equal_value_different_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_compare(left, right), do: XSD.Numeric.do_compare(left, right)
@ -179,7 +177,8 @@ defmodule RDF.XSD.Decimal do
|> datatype.canonical_lexical()
|> do_digit_count()
true -> nil
true ->
nil
end
end
@ -195,17 +194,21 @@ defmodule RDF.XSD.Decimal do
The number of digits to the right of the decimal point in the XML Schema canonical form of the literal value.
"""
@spec fraction_digit_count(RDF.Literal.t()) :: non_neg_integer | nil
def fraction_digit_count(%RDF.Literal{literal: datatype_literal}), do: fraction_digit_count(datatype_literal)
def fraction_digit_count(%RDF.Literal{literal: datatype_literal}),
do: fraction_digit_count(datatype_literal)
def fraction_digit_count(%datatype{} = literal) do
cond do
XSD.Integer.datatype?(literal) -> 0
XSD.Integer.datatype?(literal) ->
0
datatype?(literal) and datatype.valid?(literal) ->
literal
|> datatype.canonical_lexical()
|> do_fraction_digit_count()
true -> nil
true ->
nil
end
end

View file

@ -14,7 +14,6 @@ defmodule RDF.XSD.Double do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinInclusive
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
@ -46,7 +45,6 @@ defmodule RDF.XSD.Double do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case Float.parse(lexical) do
@ -143,7 +141,7 @@ defmodule RDF.XSD.Double do
XSD.Boolean.datatype?(literal) ->
case literal.value do
false -> new(0.0)
true -> new(1.0)
true -> new(1.0)
end
XSD.Integer.datatype?(literal) ->
@ -159,13 +157,13 @@ defmodule RDF.XSD.Double do
end
end
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
def do_equal_value_different_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_compare(left, right), do: XSD.Numeric.do_compare(left, right)

View file

@ -14,7 +14,6 @@ defmodule RDF.XSD.Integer do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinInclusive
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
@ -52,7 +51,6 @@ defmodule RDF.XSD.Integer do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, _) do
case Integer.parse(lexical) do
@ -80,7 +78,7 @@ defmodule RDF.XSD.Integer do
XSD.Boolean.datatype?(literal) ->
case literal.value do
false -> new(0)
true -> new(1)
true -> new(1)
end
XSD.Decimal.datatype?(literal) ->
@ -89,7 +87,8 @@ defmodule RDF.XSD.Integer do
|> Decimal.to_integer()
|> new()
is_float(literal.value) and XSD.Double.datatype?(literal) -> # we're catching the XSD.Floats with this too
# we're catching the XSD.Floats with this too
is_float(literal.value) and XSD.Double.datatype?(literal) ->
literal.value
|> trunc()
|> new()
@ -100,10 +99,12 @@ defmodule RDF.XSD.Integer do
end
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
def do_equal_value_same_or_derived_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_equal_value_different_datatypes?(left, right), do: XSD.Numeric.do_equal_value?(left, right)
def do_equal_value_different_datatypes?(left, right),
do: XSD.Numeric.do_equal_value?(left, right)
@impl RDF.Literal.Datatype
def do_compare(left, right), do: XSD.Numeric.do_compare(left, right)

View file

@ -13,13 +13,13 @@ defmodule RDF.XSD.Numeric do
defdelegate datatype?(value), to: Literal.Datatype.Registry, as: :numeric_datatype?
@doc !"""
Tests for numeric value equality of two numeric XSD datatyped literals.
Tests for numeric value equality of two numeric XSD datatyped literals.
see:
see:
- <https://www.w3.org/TR/sparql11-query/#OperatorMapping>
- <https://www.w3.org/TR/xpath-functions/#func-numeric-equal>
"""
- <https://www.w3.org/TR/sparql11-query/#OperatorMapping>
- <https://www.w3.org/TR/xpath-functions/#func-numeric-equal>
"""
@spec do_equal_value?(t() | any, t() | any) :: boolean
def do_equal_value?(left, right)
@ -52,14 +52,14 @@ defmodule RDF.XSD.Numeric do
defp new_decimal(value), do: D.new(value)
@doc !"""
Compares two numeric XSD literals.
Compares two numeric XSD literals.
Returns `:gt` if first literal is greater than the second and `:lt` for vice
versa. If the two literals are equal `:eq` is returned.
Returns `:gt` if first literal is greater than the second and `:lt` for vice
versa. If the two literals are equal `:eq` is returned.
Returns `nil` when the given arguments are not comparable datatypes.
Returns `nil` when the given arguments are not comparable datatypes.
"""
"""
@spec do_compare(t, t) :: Literal.Datatype.comparison_result() | nil
def do_compare(left, right)
@ -68,9 +68,15 @@ defmodule RDF.XSD.Numeric do
cond do
XSD.Decimal.datatype?(left_datatype) or XSD.Decimal.datatype?(right_datatype) ->
compare_decimal_value(left, right)
left < right -> :lt
left > right -> :gt
true -> :eq
left < right ->
:lt
left > right ->
:gt
true ->
:eq
end
end
end
@ -273,8 +279,9 @@ defmodule RDF.XSD.Numeric do
|> datatype.base_primitive().new()
value ->
target_datatype = if XSD.Float.datatype?(datatype),
do: XSD.Float, else: datatype.base_primitive()
target_datatype =
if XSD.Float.datatype?(datatype), do: XSD.Float, else: datatype.base_primitive()
value
|> Kernel.abs()
|> target_datatype.new()
@ -337,7 +344,7 @@ defmodule RDF.XSD.Numeric do
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
@ -400,7 +407,7 @@ defmodule RDF.XSD.Numeric do
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
@ -457,7 +464,7 @@ defmodule RDF.XSD.Numeric do
|> XSD.Decimal.new()
(float_datatype = XSD.Float.datatype?(datatype)) or
XSD.Double.datatype?(datatype) ->
XSD.Double.datatype?(datatype) ->
if literal_value in ~w[nan positive_infinity negative_infinity]a do
literal(value)
else
@ -483,11 +490,15 @@ defmodule RDF.XSD.Numeric do
end
end
defp arithmetic_operation(op, %Literal{literal: literal1}, literal2, fun), do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, literal1, %Literal{literal: literal2}, fun), do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, %Literal{literal: literal1}, literal2, fun),
do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, literal1, %Literal{literal: literal2}, fun),
do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, %datatype1{} = literal1, %datatype2{} = literal2, fun) do
if datatype?(datatype1) and datatype?(datatype2) and
Literal.Datatype.valid?(literal1) and Literal.Datatype.valid?(literal2) do
Literal.Datatype.valid?(literal1) and Literal.Datatype.valid?(literal2) do
result_type = result_type(op, datatype1, datatype2)
{arg1, arg2} = type_conversion(literal1, literal2, result_type)
result = fun.(arg1.value, arg2.value, result_type)
@ -538,7 +549,8 @@ defmodule RDF.XSD.Numeric do
defp type_conversion(left, right, _), do: {left, right}
@doc false
def result_type(op, left, right), do: do_result_type(op, base_primitive(left), base_primitive(right))
def result_type(op, left, right),
do: do_result_type(op, base_primitive(left), base_primitive(right))
defp do_result_type(_, XSD.Double, _), do: XSD.Double
defp do_result_type(_, _, XSD.Double), do: XSD.Double
@ -551,9 +563,10 @@ defmodule RDF.XSD.Numeric do
defp base_primitive(datatype) do
primitive = datatype.base_primitive()
if primitive == XSD.Double and XSD.Float.datatype?(datatype),
do: XSD.Float,
else: primitive
do: XSD.Float,
else: primitive
end
defp literal(value), do: %Literal{literal: value}

View file

@ -11,7 +11,6 @@ defmodule RDF.XSD.String do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinLength
def_applicable_facet XSD.Facets.MaxLength
def_applicable_facet XSD.Facets.Length
@ -37,7 +36,6 @@ defmodule RDF.XSD.String do
XSD.Facets.Pattern.conform?(pattern, value)
end
@impl XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value
def lexical_mapping(lexical, _), do: to_string(lexical)

View file

@ -20,7 +20,6 @@ defmodule RDF.XSD.Time do
@grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@tz_number_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/
def_applicable_facet XSD.Facets.ExplicitTimezone
def_applicable_facet XSD.Facets.Pattern
@ -36,7 +35,6 @@ defmodule RDF.XSD.Time do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case Regex.run(@grammar, lexical) do
@ -196,8 +194,15 @@ defmodule RDF.XSD.Time do
@impl RDF.Literal.Datatype
def do_equal_value_same_or_derived_datatypes?(left, right)
def do_equal_value_same_or_derived_datatypes?(%{value: %{}}, %{value: tz_tuple}) when is_tuple(tz_tuple), do: nil
def do_equal_value_same_or_derived_datatypes?(%{value: tz_tuple}, %{value: %{}}) when is_tuple(tz_tuple), do: nil
def do_equal_value_same_or_derived_datatypes?(%{value: %{}}, %{value: tz_tuple})
when is_tuple(tz_tuple),
do: nil
def do_equal_value_same_or_derived_datatypes?(%{value: tz_tuple}, %{value: %{}})
when is_tuple(tz_tuple),
do: nil
def do_equal_value_same_or_derived_datatypes?(left, right), do: super(left, right)
@doc """
@ -216,7 +221,8 @@ defmodule RDF.XSD.Time do
"""
@spec canonical_lexical_with_zone(RDF.Literal.t() | t()) :: String.t() | nil
def canonical_lexical_with_zone(%RDF.Literal{literal: xsd_time}),
do: canonical_lexical_with_zone(xsd_time)
do: canonical_lexical_with_zone(xsd_time)
def canonical_lexical_with_zone(%__MODULE__{} = xsd_time) do
case tz(xsd_time) do
nil ->

View file

@ -1,13 +1,13 @@
defmodule RDF.XSD.Facets.Pattern do
use RDF.XSD.Facet, name: :pattern, type: String.t | [String.t]
use RDF.XSD.Facet, name: :pattern, type: String.t() | [String.t()]
@doc !"""
A generic implementation for the `pattern_conform?/3` on the datatypes.
"""
A generic implementation for the `pattern_conform?/3` on the datatypes.
"""
def conform?(pattern, lexical)
def conform?(patterns, lexical) when is_list(patterns) do
Enum.any?(patterns, &(conform?(&1, lexical)))
Enum.any?(patterns, &conform?(&1, lexical))
end
def conform?(pattern, lexical) do

View file

@ -1,11 +1,11 @@
defmodule RDF.XSD.Utils.Regex do
@moduledoc !"""
XSD-flavoured regex matching.
XSD-flavoured regex matching.
This is not intended to be used directly.
Use `c:RDF.XSD.Datatype.matches?/3` implementations on the datatypes or
`RDF.Literal.matches?/3` instead.
"""
This is not intended to be used directly.
Use `c:RDF.XSD.Datatype.matches?/3` implementations on the datatypes or
`RDF.Literal.matches?/3` instead.
"""
@doc """
Matches the string representation of the given value against a XPath and XQuery regular expression pattern.

28
mix.exs
View file

@ -3,18 +3,18 @@ defmodule RDF.Mixfile do
@repo_url "https://github.com/rdf-elixir/rdf-ex"
@version File.read!("VERSION") |> String.trim
@version File.read!("VERSION") |> String.trim()
def project do
[
app: :rdf,
version: @version,
elixir: "~> 1.8",
build_embedded: Mix.env == :prod,
start_permanent: Mix.env == :prod,
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env() == :prod,
deps: deps(),
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers ++ [:protocol_ex],
compilers: Mix.compilers() ++ [:protocol_ex],
# Dialyzer
dialyzer: dialyzer(),
@ -29,7 +29,7 @@ defmodule RDF.Mixfile do
main: "RDF",
source_url: @repo_url,
source_ref: "v#{@version}",
extras: ["CHANGELOG.md"],
extras: ["CHANGELOG.md"]
],
# ExCoveralls
@ -39,7 +39,7 @@ defmodule RDF.Mixfile do
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test
],
]
]
end
@ -56,7 +56,7 @@ defmodule RDF.Mixfile do
links: %{
"Homepage" => "https://rdf-elixir.dev",
"GitHub" => @repo_url,
"Changelog" => @repo_url <> "/blob/master/CHANGELOG.md",
"Changelog" => @repo_url <> "/blob/master/CHANGELOG.md"
},
files: ~w[lib src/*.xrl src/*.yrl priv mix.exs .formatter.exs VERSION *.md]
]
@ -70,13 +70,11 @@ defmodule RDF.Mixfile do
[
{:decimal, "~> 1.5"},
{:protocol_ex, "~> 0.4"},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: :dev, runtime: false},
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:excoveralls, "~> 0.13", only: :test},
{:benchee, "~> 1.0", only: :bench},
{:credo, "~> 1.4", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: :dev, runtime: false},
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:excoveralls, "~> 0.13", only: :test},
{:benchee, "~> 1.0", only: :bench}
]
end
@ -89,5 +87,5 @@ defmodule RDF.Mixfile do
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp elixirc_paths(_), do: ["lib"]
end

View file

@ -7,15 +7,14 @@ defmodule RDF.NQuads.W3C.TestSuite do
use ExUnit.Case, async: false
@w3c_nquads_test_suite Path.join(RDF.TestData.dir, "N-QUADS-TESTS")
@w3c_nquads_test_suite Path.join(RDF.TestData.dir(), "N-QUADS-TESTS")
ExUnit.Case.register_attribute __ENV__, :nq_test
ExUnit.Case.register_attribute(__ENV__, :nq_test)
@w3c_nquads_test_suite
|> File.ls!
|> Enum.filter(fn (file) -> Path.extname(file) == ".nq" end)
|> Enum.each(fn (file) ->
|> File.ls!()
|> Enum.filter(fn file -> Path.extname(file) == ".nq" end)
|> Enum.each(fn file ->
@nq_test file: Path.join(@w3c_nquads_test_suite, file)
if file |> String.contains?("-bad-") do
test "Negative syntax test: #{file}", context do
@ -27,5 +26,4 @@ defmodule RDF.NQuads.W3C.TestSuite do
end
end
end)
end

View file

@ -7,14 +7,14 @@ defmodule RDF.NTriples.W3C.TestSuite do
use ExUnit.Case, async: false
@w3c_ntriples_test_suite Path.join(RDF.TestData.dir, "N-TRIPLES-TESTS")
@w3c_ntriples_test_suite Path.join(RDF.TestData.dir(), "N-TRIPLES-TESTS")
ExUnit.Case.register_attribute __ENV__, :nt_test
ExUnit.Case.register_attribute(__ENV__, :nt_test)
@w3c_ntriples_test_suite
|> File.ls!
|> Enum.filter(fn (file) -> Path.extname(file) == ".nt" end)
|> Enum.each(fn (file) ->
|> File.ls!()
|> Enum.filter(fn file -> Path.extname(file) == ".nt" end)
|> Enum.each(fn file ->
@nt_test file: Path.join(@w3c_ntriples_test_suite, file)
if file |> String.contains?("-bad-") do
test "Negative syntax test: #{file}", context do
@ -26,5 +26,4 @@ defmodule RDF.NTriples.W3C.TestSuite do
end
end
end)
end

View file

@ -8,7 +8,7 @@ defmodule RDF.Turtle.W3C.Test do
"""
use ExUnit.Case, async: false
ExUnit.Case.register_attribute __ENV__, :test_case
ExUnit.Case.register_attribute(__ENV__, :test_case)
alias RDF.{Turtle, TestSuite, NTriples}
alias TestSuite.NS.RDFT
@ -17,8 +17,8 @@ defmodule RDF.Turtle.W3C.Test do
TestSuite.test_cases("Turtle", RDFT.TestTurtleEval, base: @base)
|> Enum.each(fn test_case ->
@tag test_case: test_case
if TestSuite.test_name(test_case) in ~w[
@tag test_case: test_case
if TestSuite.test_name(test_case) in ~w[
anonymous_blank_node_subject
anonymous_blank_node_object
labeled_blank_node_subject
@ -45,58 +45,61 @@ defmodule RDF.Turtle.W3C.Test do
turtle-subm-10
turtle-subm-14
] do
@tag skip: """
The produced graphs are correct, but have different blank node labels than the result graph.
TODO: Implement a graph isomorphism algorithm.
"""
end
@tag skip: """
The produced graphs are correct, but have different blank node labels than the result graph.
TODO: Implement a graph isomorphism algorithm.
"""
end
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert RDF.Graph.equal?(
(TestSuite.test_input_file_path(test_case, "Turtle")
|> Turtle.read_file!(base: base)),
(TestSuite.test_result_file_path(test_case, "Turtle")
|> NTriples.read_file!)
)
end
end
end)
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert RDF.Graph.equal?(
TestSuite.test_input_file_path(test_case, "Turtle")
|> Turtle.read_file!(base: base),
TestSuite.test_result_file_path(test_case, "Turtle")
|> NTriples.read_file!()
)
end
end
end)
TestSuite.test_cases("Turtle", RDFT.TestTurtlePositiveSyntax, base: @base)
|> Enum.each(fn test_case ->
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:ok, _} =
TestSuite.test_input_file_path(test_case, "Turtle") |> Turtle.read_file(base: base)
end
end
end)
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:ok, _} =
TestSuite.test_input_file_path(test_case, "Turtle")
|> Turtle.read_file(base: base)
end
end
end)
TestSuite.test_cases("Turtle", RDFT.TestTurtleNegativeSyntax, base: @base)
|> Enum.each(fn test_case ->
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:error, _} =
TestSuite.test_input_file_path(test_case, "Turtle") |> Turtle.read_file(base: base)
end
end
end)
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:error, _} =
TestSuite.test_input_file_path(test_case, "Turtle")
|> Turtle.read_file(base: base)
end
end
end)
TestSuite.test_cases("Turtle", RDFT.TestTurtleNegativeEval, base: @base)
|> Enum.each(fn test_case ->
if TestSuite.test_name(test_case) in ~w[turtle-eval-bad-01 turtle-eval-bad-02 turtle-eval-bad-03] do
@tag skip: "TODO: IRI validation"
end
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:error, _} =
TestSuite.test_input_file_path(test_case, "Turtle") |> Turtle.read_file(base: base)
end
end
end)
if TestSuite.test_name(test_case) in ~w[turtle-eval-bad-01 turtle-eval-bad-02 turtle-eval-bad-03] do
@tag skip: "TODO: IRI validation"
end
@tag test_case: test_case
test TestSuite.test_title(test_case), %{test_case: test_case} do
with base = to_string(TestSuite.test_input_file(test_case)) do
assert {:error, _} =
TestSuite.test_input_file_path(test_case, "Turtle")
|> Turtle.read_file(base: base)
end
end
end)
end

View file

@ -2,13 +2,9 @@ defmodule RDF.Test.Case do
use ExUnit.CaseTemplate
use RDF.Vocabulary.Namespace
defvocab EX,
base_iri: "http://example.com/",
terms: [], strict: false
defvocab EX, base_iri: "http://example.com/", terms: [], strict: false
defvocab FOAF,
base_iri: "http://xmlns.com/foaf/0.1/",
terms: [], strict: false
defvocab FOAF, base_iri: "http://xmlns.com/foaf/0.1/", terms: [], strict: false
alias RDF.{Dataset, Graph, Description, IRI}
import RDF, only: [iri: 1]
@ -31,11 +27,12 @@ defmodule RDF.Test.Case do
###############################
# RDF.Description
def description, do: Description.new(EX.Subject)
def description, do: Description.new(EX.Subject)
def description(content), do: Description.add(description(), content)
def description_of_subject(%Description{subject: subject}, subject),
do: true
def description_of_subject(_, _),
do: false
@ -53,17 +50,17 @@ defmodule RDF.Test.Case do
def graph, do: unnamed_graph()
def unnamed_graph, do: Graph.new
def unnamed_graph, do: Graph.new()
def named_graph(name \\ EX.GraphName), do: Graph.new(name: name)
def unnamed_graph?(%Graph{name: nil}), do: true
def unnamed_graph?(_), do: false
def unnamed_graph?(_), do: false
def named_graph?(%Graph{name: %IRI{}}), do: true
def named_graph?(_), do: false
def named_graph?(%Graph{name: %IRI{}}), do: true
def named_graph?(_), do: false
def named_graph?(%Graph{name: name}, name), do: true
def named_graph?(_, _), do: false
def named_graph?(_, _), do: false
def empty_graph?(%Graph{descriptions: descriptions}),
do: descriptions == %{}
@ -74,40 +71,40 @@ defmodule RDF.Test.Case do
|> Enum.member?(statement)
end
###############################
# RDF.Dataset
def dataset, do: unnamed_dataset()
def unnamed_dataset, do: Dataset.new
def unnamed_dataset, do: Dataset.new()
def named_dataset(name \\ EX.DatasetName), do: Dataset.new(name: name)
def unnamed_dataset?(%Dataset{name: nil}), do: true
def unnamed_dataset?(_), do: false
def unnamed_dataset?(_), do: false
def named_dataset?(%Dataset{name: %IRI{}}), do: true
def named_dataset?(_), do: false
def named_dataset?(%Dataset{name: %IRI{}}), do: true
def named_dataset?(_), do: false
def named_dataset?(%Dataset{name: name}, name), do: true
def named_dataset?(_, _), do: false
def named_dataset?(_, _), do: false
def empty_dataset?(%Dataset{graphs: graphs}), do: graphs == %{}
def dataset_includes_statement?(dataset, {_, _, _} = statement) do
dataset
|> Dataset.default_graph
|> Dataset.default_graph()
|> graph_includes_statement?(statement)
end
def dataset_includes_statement?(dataset, {subject, predicate, objects, nil}),
do: dataset_includes_statement?(dataset, {subject, predicate, objects})
def dataset_includes_statement?(dataset,
{subject, predicate, objects, graph_context}) do
def dataset_includes_statement?(
dataset,
{subject, predicate, objects, graph_context}
) do
dataset.graphs
|> Map.get(iri(graph_context), named_graph(graph_context))
|> graph_includes_statement?({subject, predicate, objects})
end
end

View file

@ -8,18 +8,18 @@ defmodule RDF.Query.Test.Case do
alias RDF.Query.BGP
import unquote(__MODULE__)
end
end
alias RDF.Query.BGP
def bgp_struct(), do: %BGP{triple_patterns: []}
def bgp_struct(triple_patterns) when is_list(triple_patterns),
do: %BGP{triple_patterns: triple_patterns}
def bgp_struct({_, _, _} = triple_pattern),
do: %BGP{triple_patterns: [triple_pattern]}
def ok_bgp_struct(triple_patterns), do: {:ok, bgp_struct(triple_patterns)}
end

View file

@ -1,14 +1,12 @@
defmodule RDF.TestData do
@dir Path.join(File.cwd!, "test/data/")
@dir Path.join(File.cwd!(), "test/data/")
def dir, do: @dir
def file(name) do
if (path = Path.join(@dir, name)) |> File.exists? do
if (path = Path.join(@dir, name)) |> File.exists?() do
path
else
raise "Test data file '#{name}' not found"
end
end
end

View file

@ -1,39 +1,39 @@
defmodule RDF.TestDatatypes do
defmodule Initials do
use RDF.XSD.Datatype.Restriction,
name: "initials",
id: "http://example.com/initials",
base: RDF.XSD.String
name: "initials",
id: "http://example.com/initials",
base: RDF.XSD.String
def_facet_constraint RDF.XSD.Facets.Length, 2
end
defmodule UsZipcode do
use RDF.XSD.Datatype.Restriction,
name: "us_zipcode",
id: "http://example.com/us-zipcode",
base: RDF.XSD.String
name: "us_zipcode",
id: "http://example.com/us-zipcode",
base: RDF.XSD.String
def_facet_constraint RDF.XSD.Facets.Pattern, "[0-9]{5}(-[0-9]{4})?"
end
defmodule AltUsZipcode do
use RDF.XSD.Datatype.Restriction,
name: "alt_us_zipcode",
id: "http://example.com/alt-us-zipcode",
base: RDF.XSD.String
name: "alt_us_zipcode",
id: "http://example.com/alt-us-zipcode",
base: RDF.XSD.String
def_facet_constraint RDF.XSD.Facets.Pattern, [
"[0-9]{5}",
"[0-9]{5}-[0-9]{4}",
"[0-9]{5}-[0-9]{4}"
]
end
defmodule Age do
use RDF.XSD.Datatype.Restriction,
name: "age",
id: "http://example.com/Age",
base: RDF.XSD.PositiveInteger
name: "age",
id: "http://example.com/Age",
base: RDF.XSD.PositiveInteger
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 150
@ -43,9 +43,9 @@ defmodule RDF.TestDatatypes do
defmodule DecimalUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "decimal_unit_interval",
id: "http://example.com/decimalUnitInterval",
base: RDF.XSD.Decimal
name: "decimal_unit_interval",
id: "http://example.com/decimalUnitInterval",
base: RDF.XSD.Decimal
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
@ -53,9 +53,9 @@ defmodule RDF.TestDatatypes do
defmodule DoubleUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "double_unit_interval",
id: "http://example.com/doubleUnitInterval",
base: RDF.XSD.Double
name: "double_unit_interval",
id: "http://example.com/doubleUnitInterval",
base: RDF.XSD.Double
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
@ -63,9 +63,9 @@ defmodule RDF.TestDatatypes do
defmodule FloatUnitInterval do
use RDF.XSD.Datatype.Restriction,
name: "float_unit_interval",
id: "http://example.com/floatUnitInterval",
base: RDF.XSD.Float
name: "float_unit_interval",
id: "http://example.com/floatUnitInterval",
base: RDF.XSD.Float
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 1
@ -73,27 +73,27 @@ defmodule RDF.TestDatatypes do
defmodule DateTimeWithTz do
use RDF.XSD.Datatype.Restriction,
name: "datetime_with_tz",
id: "http://example.com/datetime-with-tz",
base: RDF.XSD.DateTime
name: "datetime_with_tz",
id: "http://example.com/datetime-with-tz",
base: RDF.XSD.DateTime
def_facet_constraint RDF.XSD.Facets.ExplicitTimezone, :required
end
defmodule DateWithoutTz do
use RDF.XSD.Datatype.Restriction,
name: "date_with_tz",
id: "http://example.com/date-with-tz",
base: RDF.XSD.Date
name: "date_with_tz",
id: "http://example.com/date-with-tz",
base: RDF.XSD.Date
def_facet_constraint RDF.XSD.Facets.ExplicitTimezone, :prohibited
end
defmodule CustomTime do
use RDF.XSD.Datatype.Restriction,
name: "time_with_tz",
id: "http://example.com/time-with-tz",
base: RDF.XSD.Time
name: "time_with_tz",
id: "http://example.com/time-with-tz",
base: RDF.XSD.Time
def_facet_constraint RDF.XSD.Facets.ExplicitTimezone, :optional
end

View file

@ -1,49 +1,53 @@
defmodule RDF.TestLiterals do
alias RDF.Literal
alias RDF.NS.XSD
def value(:empty), do: [""]
def value(:plain), do: ["Hello"]
def value(:empty_lang), do: ["", [language: "en"]]
def value(:plain_lang), do: ["Hello", [language: "en"]]
def value(:typed_string), do: ["String", [datatype: XSD.string]]
def value(:uri), do: [URI.parse("http://example.com")]
def value(:true), do: [true]
def value(:false), do: [false]
def value(:int), do: [123]
def value(:neg_int), do: [-123]
def value(:decimal), do: [Decimal.from_float(3.14)]
def value(:long), do: [9223372036854775807]
def value(:double), do: [3.1415]
def value(:date), do: [~D[2017-04-13]]
def value(:empty), do: [""]
def value(:plain), do: ["Hello"]
def value(:empty_lang), do: ["", [language: "en"]]
def value(:plain_lang), do: ["Hello", [language: "en"]]
def value(:typed_string), do: ["String", [datatype: XSD.string()]]
def value(:uri), do: [URI.parse("http://example.com")]
def value(true), do: [true]
def value(false), do: [false]
def value(:int), do: [123]
def value(:neg_int), do: [-123]
def value(:decimal), do: [Decimal.from_float(3.14)]
def value(:long), do: [9_223_372_036_854_775_807]
def value(:double), do: [3.1415]
def value(:date), do: [~D[2017-04-13]]
def value(:naive_datetime), do: [~N[2017-04-14 15:32:07]]
def value(:datetime), do: ["2017-04-14 15:32:07Z" |> DateTime.from_iso8601 |> elem(1)]
def value(:time), do: [~T[01:02:03]]
def value(:datetime), do: ["2017-04-14 15:32:07Z" |> DateTime.from_iso8601() |> elem(1)]
def value(:time), do: [~T[01:02:03]]
def value(selector) do
raise "unexpected literal: :#{selector}"
end
def values(:all_simple),
do: Enum.map(~W(empty plain typed_string)a, &value/1)
def values(:all_plain_lang),
do: Enum.map(~W[empty_lang plain_lang]a, &value/1)
def values(:all_native),
do: Enum.map(~W[false true int long double time date datetime naive_datetime]a, &value/1)
def values(:all_plain),
do: values(~W[all_simple all_plain_lang]a)
def values(:all),
do: values(~W[all_native all_plain]a)
def values(selectors) when is_list(selectors) do
Enum.reduce selectors, [], fn(selector, values) ->
Enum.reduce(selectors, [], fn selector, values ->
values ++ values(selector)
end
end)
end
def literal(selector),
do: apply(Literal, :new, value(selector))
def literals(selectors),
do: Enum.map values(selectors), fn value -> apply(Literal, :new, value) end
do: Enum.map(values(selectors), fn value -> apply(Literal, :new, value) end)
end

View file

@ -1,11 +1,11 @@
defmodule RDF.TestSuite do
defmodule NS do
use RDF.Vocabulary.Namespace
defvocab MF,
base_iri: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
terms: [], strict: false
terms: [],
strict: false
defvocab RDFT,
base_iri: "http://www.w3.org/ns/rdftest#",
@ -25,10 +25,9 @@ defmodule RDF.TestSuite do
alias RDF.{Turtle, Graph, Description, IRI}
def dir(format), do: Path.join(RDF.TestData.dir, String.upcase(format) <> "-TESTS")
def dir(format), do: Path.join(RDF.TestData.dir(), String.upcase(format) <> "-TESTS")
def file(filename, format), do: format |> dir |> Path.join(filename)
def manifest_path(format), do: file("manifest.ttl", format)
def manifest_path(format), do: file("manifest.ttl", format)
def manifest_graph(format, opts \\ []) do
format
@ -39,34 +38,32 @@ defmodule RDF.TestSuite do
def test_cases(format, test_type, opts) do
format
|> manifest_graph(opts)
|> Graph.descriptions
|> Graph.descriptions()
|> Enum.filter(fn description ->
RDF.iri(test_type) in Description.get(description, RDF.type, [])
end)
RDF.iri(test_type) in Description.get(description, RDF.type(), [])
end)
end
def test_name(test_case), do: value(test_case, MF.name)
def test_name(test_case), do: value(test_case, MF.name())
def test_title(test_case),
# Unfortunately OTP < 20 doesn't support unicode characters in atoms,
# so we can't put the description in the test name
# do: test_name(test_case) <> ": " <> value(test_case, RDFS.comment)
# Unfortunately OTP < 20 doesn't support unicode characters in atoms,
# so we can't put the description in the test name
# do: test_name(test_case) <> ": " <> value(test_case, RDFS.comment)
do: test_name(test_case)
def test_input_file(test_case),
do: test_case |> Description.first(MF.action) |> IRI.parse
do: test_case |> Description.first(MF.action()) |> IRI.parse()
def test_output_file(test_case),
do: test_case |> Description.first(MF.result) |> IRI.parse
do: test_case |> Description.first(MF.result()) |> IRI.parse()
def test_input_file_path(test_case, format),
do: test_input_file(test_case).path |> Path.basename |> file(format)
do: test_input_file(test_case).path |> Path.basename() |> file(format)
def test_result_file_path(test_case, format),
do: test_output_file(test_case).path |> Path.basename |> file(format)
do: test_output_file(test_case).path |> Path.basename() |> file(format)
defp value(description, property),
do: Description.first(description, property) |> to_string
end

View file

@ -7,8 +7,7 @@ defmodule RDF.XSD.Datatype.Test.Case do
datatype = Keyword.fetch!(opts, :datatype)
datatype_name = Keyword.fetch!(opts, :name)
datatype_iri =
Keyword.get(opts, :iri, RDF.NS.XSD.__base_iri__ <> datatype_name)
datatype_iri = Keyword.get(opts, :iri, RDF.NS.XSD.__base_iri__() <> datatype_name)
valid = Keyword.get(opts, :valid)
invalid = Keyword.get(opts, :invalid)
@ -116,45 +115,48 @@ defmodule RDF.XSD.Datatype.Test.Case do
describe "general datatype?/1" do
test "on the exact same datatype" do
assert (unquote(datatype).datatype?(unquote(datatype))) == true
assert unquote(datatype).datatype?(unquote(datatype)) == true
Enum.each(@valid, fn {input, _} ->
literal = unquote(datatype).new(input)
assert (unquote(datatype).datatype?(literal)) == true
assert (unquote(datatype).datatype?(literal.literal)) == true
assert unquote(datatype).datatype?(literal) == true
assert unquote(datatype).datatype?(literal.literal) == true
end)
end
unless unquote(primitive) do
test "on the base datatype" do
assert (unquote(base).datatype?(unquote(datatype))) == true
assert unquote(base).datatype?(unquote(datatype)) == true
Enum.each(@valid, fn {input, _} ->
literal = unquote(datatype).new(input)
assert (unquote(base).datatype?(literal)) == true
assert (unquote(base).datatype?(literal.literal)) == true
assert unquote(base).datatype?(literal) == true
assert unquote(base).datatype?(literal.literal) == true
end)
end
test "on the base primitive datatype" do
assert (unquote(base_primitive).datatype?(unquote(datatype))) == true
assert unquote(base_primitive).datatype?(unquote(datatype)) == true
Enum.each(@valid, fn {input, _} ->
literal = unquote(datatype).new(input)
assert (unquote(base_primitive).datatype?(literal)) == true
assert (unquote(base_primitive).datatype?(literal.literal)) == true
assert unquote(base_primitive).datatype?(literal) == true
assert unquote(base_primitive).datatype?(literal.literal) == true
end)
end
end
end
test "datatype_id/1" do
Enum.each(@valid, fn {input, _} ->
assert (unquote(datatype).new(input) |> unquote(datatype).datatype_id()) == RDF.iri(unquote(datatype_iri))
assert unquote(datatype).new(input) |> unquote(datatype).datatype_id() ==
RDF.iri(unquote(datatype_iri))
end)
end
test "language/1" do
Enum.each(@valid, fn {input, _} ->
assert (unquote(datatype).new(input) |> unquote(datatype).language()) == nil
assert unquote(datatype).new(input) |> unquote(datatype).language() == nil
end)
end
@ -288,9 +290,9 @@ defmodule RDF.XSD.Datatype.Test.Case do
@tag example: %{input: input, canonicalized: canonicalized}
test "canonical? for #{unquote(datatype)} #{inspect(input)}", %{example: example} do
literal = unquote(datatype).new(example.input)
assert unquote(datatype).canonical?(literal) == (
unquote(datatype).lexical(literal) ==example.canonicalized
)
assert unquote(datatype).canonical?(literal) ==
(unquote(datatype).lexical(literal) == example.canonicalized)
end
end)

View file

@ -5,40 +5,38 @@ defmodule RDF.BlankNode.IncrementTest do
alias RDF.BlankNode.{Generator, Increment}
describe "generate/1" do
test "without prefix" do
assert Increment.generate(%{counter: 0, map: %{}}) ==
{bnode(0), (%{counter: 1, map: %{}})}
{bnode(0), %{counter: 1, map: %{}}}
end
test "with prefix" do
assert Increment.generate(%{counter: 0, map: %{}, prefix: "b"}) ==
{bnode("b0"), (%{counter: 1, map: %{}, prefix: "b"})}
{bnode("b0"), %{counter: 1, map: %{}, prefix: "b"}}
end
end
describe "generate_for/2" do
test "when the given string not exists in the map" do
assert Increment.generate_for("bar", %{counter: 1, map: %{"foo" => 0}}) ==
{bnode(1), (%{counter: 2, map: %{"foo" => 0, "bar" => 1}})}
{bnode(1), %{counter: 2, map: %{"foo" => 0, "bar" => 1}}}
end
test "when the given string exists in the map" do
assert Increment.generate_for("foo", %{counter: 1, map: %{"foo" => 0}}) ==
{bnode(0), (%{counter: 1, map: %{"foo" => 0}})}
{bnode(0), %{counter: 1, map: %{"foo" => 0}}}
end
test "with prefix" do
assert Increment.generate_for("bar", %{counter: 1, map: %{"foo" => 0}, prefix: "b"}) ==
{bnode("b1"), (%{counter: 2, map: %{"foo" => 0, "bar" => 1}, prefix: "b"})}
{bnode("b1"), %{counter: 2, map: %{"foo" => 0, "bar" => 1}, prefix: "b"}}
assert Increment.generate_for("foo", %{counter: 1, map: %{"foo" => 0}, prefix: "b"}) ==
{bnode("b0"), (%{counter: 1, map: %{"foo" => 0}, prefix: "b"})}
{bnode("b0"), %{counter: 1, map: %{"foo" => 0}, prefix: "b"}}
end
end
test "generator without prefix" do
{:ok, generator} = Generator.start_link(Increment)
@ -77,5 +75,4 @@ defmodule RDF.BlankNode.IncrementTest do
assert Generator.generate_for(generator, {:foo, 42}) == bnode("b2")
assert Generator.generate(generator) == bnode("b6")
end
end

View file

@ -7,88 +7,92 @@ defmodule RDF.DataTest do
|> EX.p1(EX.O1, EX.O2)
|> EX.p2(EX.O3)
|> EX.p3(~B<foo>, ~L"bar")
graph =
Graph.new
Graph.new()
|> Graph.add(description)
|> Graph.add(
EX.S2
|> EX.p2(EX.O3, EX.O4)
EX.S2
|> EX.p2(EX.O3, EX.O4)
)
named_graph = %Graph{graph | name: iri(EX.NamedGraph)}
dataset =
Dataset.new
Dataset.new()
|> Dataset.add(graph)
|> Dataset.add(
Graph.new(name: EX.NamedGraph)
|> Graph.add(description)
|> Graph.add({EX.S3, EX.p3, EX.O5})
|> Graph.add({EX.S, EX.p3, EX.O5}))
{:ok,
description: description,
graph: graph, named_graph: named_graph,
dataset: dataset,
}
end
Graph.new(name: EX.NamedGraph)
|> Graph.add(description)
|> Graph.add({EX.S3, EX.p3(), EX.O5})
|> Graph.add({EX.S, EX.p3(), EX.O5})
)
{:ok, description: description, graph: graph, named_graph: named_graph, dataset: dataset}
end
describe "RDF.Data protocol implementation of RDF.Description" do
test "merge of a single triple with different subject", %{description: description} do
assert RDF.Data.merge(description, {EX.Other, EX.p1, EX.O3}) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(description, {EX.Other, EX.p1(), EX.O3}) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1(), EX.O3})
end
test "merge of a single triple with same subject", %{description: description} do
assert RDF.Data.merge(description, {EX.S, EX.p1, EX.O3}) ==
Description.add(description, {EX.S, EX.p1, EX.O3})
assert RDF.Data.merge(description, {EX.S, EX.p1(), EX.O3}) ==
Description.add(description, {EX.S, EX.p1(), EX.O3})
end
test "merge of a single quad", %{description: description} do
assert RDF.Data.merge(description, {EX.Other, EX.p1, EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.Other, EX.p1, EX.O3, EX.Graph})
assert RDF.Data.merge(description, {EX.S, EX.p1, EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.S, EX.p1, EX.O3, EX.Graph})
assert RDF.Data.merge(description, {EX.Other, EX.p1(), EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.Other, EX.p1(), EX.O3, EX.Graph})
assert RDF.Data.merge(description, {EX.S, EX.p1(), EX.O3, EX.Graph}) ==
Dataset.new(description) |> Dataset.add({EX.S, EX.p1(), EX.O3, EX.Graph})
end
test "merge of a description with different subject", %{description: description} do
assert RDF.Data.merge(description, Description.new({EX.Other, EX.p1, EX.O3})) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(description, Description.new({EX.Other, EX.p1(), EX.O3})) ==
Graph.new(description) |> Graph.add({EX.Other, EX.p1(), EX.O3})
end
test "merge of a description with same subject", %{description: description} do
assert RDF.Data.merge(description, Description.new({EX.S, EX.p1, EX.O3})) ==
Description.add(description, {EX.S, EX.p1, EX.O3})
assert RDF.Data.merge(description, Description.new({EX.S, EX.p1(), EX.O3})) ==
Description.add(description, {EX.S, EX.p1(), EX.O3})
end
test "merge of a graph", %{graph: graph} do
assert RDF.Data.merge(Description.new({EX.Other, EX.p1, EX.O3}), graph) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(Description.new({EX.Other, EX.p1(), EX.O3}), graph) ==
Graph.add(graph, {EX.Other, EX.p1(), EX.O3})
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(Description.new({EX.Other, EX.p1, EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(Description.new({EX.Other, EX.p1(), EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
end
test "delete", %{description: description} do
assert RDF.Data.delete(description, {EX.S, EX.p1, EX.O2}) ==
Description.delete(description, {EX.S, EX.p1, EX.O2})
assert RDF.Data.delete(description, {EX.Other, EX.p1, EX.O2}) == description
assert RDF.Data.delete(description, {EX.S, EX.p1(), EX.O2}) ==
Description.delete(description, {EX.S, EX.p1(), EX.O2})
assert RDF.Data.delete(description, {EX.Other, EX.p1(), EX.O2}) == description
end
test "deleting a Description with a different subject does nothing", %{description: description} do
assert RDF.Data.delete(description,
%Description{description | subject: EX.Other}) == description
test "deleting a Description with a different subject does nothing", %{
description: description
} do
assert RDF.Data.delete(
description,
%Description{description | subject: EX.Other}
) == description
end
test "pop", %{description: description} do
assert RDF.Data.pop(description) == Description.pop(description)
end
test "include?", %{description: description} do
assert RDF.Data.include?(description, {EX.S, EX.p1, EX.O2})
refute RDF.Data.include?(description, {EX.Other, EX.p1, EX.O2})
assert RDF.Data.include?(description, {EX.S, EX.p1(), EX.O2})
refute RDF.Data.include?(description, {EX.Other, EX.p1(), EX.O2})
end
test "describes?", %{description: description} do
@ -97,14 +101,14 @@ defmodule RDF.DataTest do
end
test "description when the requested subject matches the Description.subject",
%{description: description} do
%{description: description} do
assert RDF.Data.description(description, description.subject) == description
assert RDF.Data.description(description, to_string(description.subject)) == description
assert RDF.Data.description(description, EX.S) == description
end
test "description when the requested subject does not match the Description.subject",
%{description: description} do
%{description: description} do
assert RDF.Data.description(description, iri(EX.Other)) == Description.new(EX.Other)
end
@ -121,17 +125,26 @@ defmodule RDF.DataTest do
end
test "predicates", %{description: description} do
assert RDF.Data.predicates(description) == MapSet.new([EX.p1, EX.p2, EX.p3])
assert RDF.Data.predicates(description) == MapSet.new([EX.p1(), EX.p2(), EX.p3()])
end
test "objects", %{description: description} do
assert RDF.Data.objects(description) ==
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), ~B<foo>])
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), ~B<foo>])
end
test "resources", %{description: description} do
assert RDF.Data.resources(description) ==
MapSet.new([iri(EX.S), EX.p1, EX.p2, EX.p3, iri(EX.O1), iri(EX.O2), iri(EX.O3), ~B<foo>])
MapSet.new([
iri(EX.S),
EX.p1(),
EX.p2(),
EX.p3(),
iri(EX.O1),
iri(EX.O2),
iri(EX.O3),
~B<foo>
])
end
test "subject_count", %{description: description} do
@ -145,12 +158,12 @@ defmodule RDF.DataTest do
test "values", %{description: description} do
assert RDF.Data.values(description) ==
%{
RDF.Term.value(EX.p1) => [
RDF.Term.value(EX.p1()) => [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
RDF.Term.value(EX.p2) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3) => ["_:foo", "bar"],
RDF.Term.value(EX.p2()) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3()) => ["_:foo", "bar"]
}
end
@ -166,71 +179,79 @@ defmodule RDF.DataTest do
end
end
describe "RDF.Data protocol implementation of RDF.Graph" do
test "merge of a single triple", %{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.Other, EX.p, EX.O}) ==
Graph.add(graph, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p, EX.O}) ==
Graph.add(named_graph, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(graph, {EX.Other, EX.p(), EX.O}) ==
Graph.add(graph, {EX.Other, EX.p(), EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p(), EX.O}) ==
Graph.add(named_graph, {EX.Other, EX.p(), EX.O})
end
test "merge of a single quad with the same graph context",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.Other, EX.p, EX.O, nil}) ==
Graph.add(graph, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p, EX.O, EX.NamedGraph}) ==
Graph.add(named_graph, {EX.Other, EX.p, EX.O})
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.Other, EX.p(), EX.O, nil}) ==
Graph.add(graph, {EX.Other, EX.p(), EX.O})
assert RDF.Data.merge(named_graph, {EX.Other, EX.p(), EX.O, EX.NamedGraph}) ==
Graph.add(named_graph, {EX.Other, EX.p(), EX.O})
end
test "merge of a single quad with a different graph context",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.S, EX.p1, EX.O3, EX.NamedGraph}) ==
Dataset.new(graph) |> Dataset.add({EX.S, EX.p1, EX.O3, EX.NamedGraph})
assert RDF.Data.merge(named_graph, {EX.S, EX.p1, EX.O3, nil}) ==
Dataset.new(named_graph) |> Dataset.add({EX.S, EX.p1, EX.O3, nil})
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, {EX.S, EX.p1(), EX.O3, EX.NamedGraph}) ==
Dataset.new(graph) |> Dataset.add({EX.S, EX.p1(), EX.O3, EX.NamedGraph})
assert RDF.Data.merge(named_graph, {EX.S, EX.p1(), EX.O3, nil}) ==
Dataset.new(named_graph) |> Dataset.add({EX.S, EX.p1(), EX.O3, nil})
end
test "merge of a description", %{graph: graph} do
assert RDF.Data.merge(graph, Description.new({EX.Other, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(graph, Description.new({EX.S, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.S, EX.p1, EX.O3})
assert RDF.Data.merge(graph, Description.new({EX.Other, EX.p1(), EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1(), EX.O3})
assert RDF.Data.merge(graph, Description.new({EX.S, EX.p1(), EX.O3})) ==
Graph.add(graph, {EX.S, EX.p1(), EX.O3})
end
test "merge of a graph with the same name",
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, Graph.add(graph, {EX.Other, EX.p1, EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(named_graph, Graph.add(named_graph, {EX.Other, EX.p1, EX.O3})) ==
Graph.add(named_graph, {EX.Other, EX.p1, EX.O3})
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, Graph.add(graph, {EX.Other, EX.p1(), EX.O3})) ==
Graph.add(graph, {EX.Other, EX.p1(), EX.O3})
assert RDF.Data.merge(named_graph, Graph.add(named_graph, {EX.Other, EX.p1(), EX.O3})) ==
Graph.add(named_graph, {EX.Other, EX.p1(), EX.O3})
end
test "merge of a graph with a different name",
%{graph: graph, named_graph: named_graph} do
%{graph: graph, named_graph: named_graph} do
assert RDF.Data.merge(graph, named_graph) ==
Dataset.new(graph) |> Dataset.add(named_graph)
Dataset.new(graph) |> Dataset.add(named_graph)
assert RDF.Data.merge(named_graph, graph) ==
Dataset.new(named_graph) |> Dataset.add(graph)
Dataset.new(named_graph) |> Dataset.add(graph)
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(Graph.new({EX.Other, EX.p1, EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(Graph.new({EX.Other, EX.p1, EX.O3}, name: EX.NamedGraph), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3, EX.NamedGraph})
assert RDF.Data.merge(Graph.new({EX.Other, EX.p1(), EX.O3}), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
assert RDF.Data.merge(Graph.new({EX.Other, EX.p1(), EX.O3}, name: EX.NamedGraph), dataset) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3, EX.NamedGraph})
end
test "delete", %{graph: graph} do
assert RDF.Data.delete(graph, {EX.S, EX.p1, EX.O2}) ==
Graph.delete(graph, {EX.S, EX.p1, EX.O2})
assert RDF.Data.delete(graph, {EX.Other, EX.p1, EX.O2}) == graph
assert RDF.Data.delete(graph, {EX.S, EX.p1(), EX.O2}) ==
Graph.delete(graph, {EX.S, EX.p1(), EX.O2})
assert RDF.Data.delete(graph, {EX.Other, EX.p1(), EX.O2}) == graph
end
test "deleting a Graph with a different name does nothing", %{graph: graph} do
assert RDF.Data.delete(graph,
%Graph{graph | name: EX.OtherGraph}) == graph
assert RDF.Data.delete(
graph,
%Graph{graph | name: EX.OtherGraph}
) == graph
end
test "pop", %{graph: graph} do
@ -238,9 +259,9 @@ defmodule RDF.DataTest do
end
test "include?", %{graph: graph} do
assert RDF.Data.include?(graph, {EX.S, EX.p1, EX.O2})
assert RDF.Data.include?(graph, {EX.S2, EX.p2, EX.O3})
refute RDF.Data.include?(graph, {EX.Other, EX.p1, EX.O2})
assert RDF.Data.include?(graph, {EX.S, EX.p1(), EX.O2})
assert RDF.Data.include?(graph, {EX.S2, EX.p2(), EX.O3})
refute RDF.Data.include?(graph, {EX.Other, EX.p1(), EX.O2})
end
test "describes?", %{graph: graph} do
@ -250,7 +271,7 @@ defmodule RDF.DataTest do
end
test "description when a description is present",
%{graph: graph, description: description} do
%{graph: graph, description: description} do
assert RDF.Data.description(graph, iri(EX.S)) == description
assert RDF.Data.description(graph, EX.S) == description
end
@ -261,7 +282,7 @@ defmodule RDF.DataTest do
test "descriptions", %{graph: graph, description: description} do
assert RDF.Data.descriptions(graph) ==
[description, EX.S2 |> EX.p2(EX.O3, EX.O4)]
[description, EX.S2 |> EX.p2(EX.O3, EX.O4)]
end
test "statements", %{graph: graph} do
@ -273,19 +294,28 @@ defmodule RDF.DataTest do
end
test "predicates", %{graph: graph} do
assert RDF.Data.predicates(graph) == MapSet.new([EX.p1, EX.p2, EX.p3])
assert RDF.Data.predicates(graph) == MapSet.new([EX.p1(), EX.p2(), EX.p3()])
end
test "objects", %{graph: graph} do
assert RDF.Data.objects(graph) ==
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), ~B<foo>])
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), ~B<foo>])
end
test "resources", %{graph: graph} do
assert RDF.Data.resources(graph) == MapSet.new([
iri(EX.S), iri(EX.S2), EX.p1, EX.p2, EX.p3,
iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), ~B<foo>
])
assert RDF.Data.resources(graph) ==
MapSet.new([
iri(EX.S),
iri(EX.S2),
EX.p1(),
EX.p2(),
EX.p3(),
iri(EX.O1),
iri(EX.O2),
iri(EX.O3),
iri(EX.O4),
~B<foo>
])
end
test "subject_count", %{graph: graph} do
@ -300,19 +330,19 @@ defmodule RDF.DataTest do
assert RDF.Data.values(graph) ==
%{
RDF.Term.value(RDF.iri(EX.S)) => %{
RDF.Term.value(EX.p1) => [
RDF.Term.value(EX.p1()) => [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
RDF.Term.value(EX.p2) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3) => ["_:foo", "bar"],
RDF.Term.value(EX.p2()) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3()) => ["_:foo", "bar"]
},
RDF.Term.value(RDF.iri(EX.S2)) => %{
RDF.Term.value(EX.p2) => [
RDF.Term.value(EX.p2()) => [
RDF.Term.value(RDF.iri(EX.O3)),
RDF.Term.value(RDF.iri(EX.O4))
],
},
]
}
}
end
@ -330,51 +360,59 @@ defmodule RDF.DataTest do
end
end
describe "RDF.Data protocol implementation of RDF.Dataset" do
test "merge of a single triple", %{dataset: dataset} do
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(dataset, {EX.Other, EX.p(), EX.O}) ==
Dataset.add(dataset, {EX.Other, EX.p(), EX.O})
end
test "merge of a single quad", %{dataset: dataset} do
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O, nil}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O})
assert RDF.Data.merge(dataset, {EX.Other, EX.p, EX.O, EX.NamedGraph}) ==
Dataset.add(dataset, {EX.Other, EX.p, EX.O, EX.NamedGraph})
assert RDF.Data.merge(dataset, {EX.Other, EX.p(), EX.O, nil}) ==
Dataset.add(dataset, {EX.Other, EX.p(), EX.O})
assert RDF.Data.merge(dataset, {EX.Other, EX.p(), EX.O, EX.NamedGraph}) ==
Dataset.add(dataset, {EX.Other, EX.p(), EX.O, EX.NamedGraph})
end
test "merge of a description", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Description.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Description.new({EX.Other, EX.p1(), EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
end
test "merge of a graph", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Graph.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Graph.new({EX.Other, EX.p1, EX.O3}, name: EX.NamedGraph)) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3, EX.NamedGraph})
assert RDF.Data.merge(dataset, Graph.new({EX.Other, EX.p1(), EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
assert RDF.Data.merge(dataset, Graph.new({EX.Other, EX.p1(), EX.O3}, name: EX.NamedGraph)) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3, EX.NamedGraph})
end
test "merge of a dataset", %{dataset: dataset} do
assert RDF.Data.merge(dataset, Dataset.new({EX.Other, EX.p1, EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Dataset.new({EX.Other, EX.p1, EX.O3}, name: EX.NamedDataset)) ==
Dataset.add(dataset, {EX.Other, EX.p1, EX.O3})
assert RDF.Data.merge(dataset, Dataset.new({EX.Other, EX.p1(), EX.O3})) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
assert RDF.Data.merge(
dataset,
Dataset.new({EX.Other, EX.p1(), EX.O3}, name: EX.NamedDataset)
) ==
Dataset.add(dataset, {EX.Other, EX.p1(), EX.O3})
end
test "delete", %{dataset: dataset} do
assert RDF.Data.delete(dataset, {EX.S, EX.p1, EX.O2}) ==
Dataset.delete(dataset, {EX.S, EX.p1, EX.O2})
assert RDF.Data.delete(dataset, {EX.S3, EX.p3, EX.O5, EX.NamedGraph}) ==
Dataset.delete(dataset, {EX.S3, EX.p3, EX.O5, EX.NamedGraph})
assert RDF.Data.delete(dataset, {EX.Other, EX.p1, EX.O2}) == dataset
assert RDF.Data.delete(dataset, {EX.S, EX.p1(), EX.O2}) ==
Dataset.delete(dataset, {EX.S, EX.p1(), EX.O2})
assert RDF.Data.delete(dataset, {EX.S3, EX.p3(), EX.O5, EX.NamedGraph}) ==
Dataset.delete(dataset, {EX.S3, EX.p3(), EX.O5, EX.NamedGraph})
assert RDF.Data.delete(dataset, {EX.Other, EX.p1(), EX.O2}) == dataset
end
test "deleting a Dataset with a different name does nothing", %{dataset: dataset} do
assert RDF.Data.delete(dataset,
%Dataset{dataset | name: EX.OtherDataset}) == dataset
assert RDF.Data.delete(
dataset,
%Dataset{dataset | name: EX.OtherDataset}
) == dataset
end
test "pop", %{dataset: dataset} do
@ -382,10 +420,10 @@ defmodule RDF.DataTest do
end
test "include?", %{dataset: dataset} do
assert RDF.Data.include?(dataset, {EX.S, EX.p1, EX.O2})
assert RDF.Data.include?(dataset, {EX.S2, EX.p2, EX.O3})
assert RDF.Data.include?(dataset, {EX.S3, EX.p3, EX.O5, EX.NamedGraph})
refute RDF.Data.include?(dataset, {EX.Other, EX.p1, EX.O2})
assert RDF.Data.include?(dataset, {EX.S, EX.p1(), EX.O2})
assert RDF.Data.include?(dataset, {EX.S2, EX.p2(), EX.O3})
assert RDF.Data.include?(dataset, {EX.S3, EX.p3(), EX.O5, EX.NamedGraph})
refute RDF.Data.include?(dataset, {EX.Other, EX.p1(), EX.O2})
end
test "describes?", %{dataset: dataset} do
@ -396,8 +434,8 @@ defmodule RDF.DataTest do
end
test "description when a description is present",
%{dataset: dataset, description: description} do
description_aggregate = Description.add(description, {EX.S, EX.p3, EX.O5})
%{dataset: dataset, description: description} do
description_aggregate = Description.add(description, {EX.S, EX.p3(), EX.O5})
assert RDF.Data.description(dataset, iri(EX.S)) == description_aggregate
assert RDF.Data.description(dataset, EX.S) == description_aggregate
end
@ -407,12 +445,13 @@ defmodule RDF.DataTest do
end
test "descriptions", %{dataset: dataset, description: description} do
description_aggregate = Description.add(description, {EX.S, EX.p3, EX.O5})
description_aggregate = Description.add(description, {EX.S, EX.p3(), EX.O5})
assert RDF.Data.descriptions(dataset) == [
description_aggregate,
(EX.S2 |> EX.p2(EX.O3, EX.O4)),
(EX.S3 |> EX.p3(EX.O5))
]
description_aggregate,
EX.S2 |> EX.p2(EX.O3, EX.O4),
EX.S3 |> EX.p3(EX.O5)
]
end
test "statements", %{dataset: dataset} do
@ -424,19 +463,30 @@ defmodule RDF.DataTest do
end
test "predicates", %{dataset: dataset} do
assert RDF.Data.predicates(dataset) == MapSet.new([EX.p1, EX.p2, EX.p3])
assert RDF.Data.predicates(dataset) == MapSet.new([EX.p1(), EX.p2(), EX.p3()])
end
test "objects", %{dataset: dataset} do
assert RDF.Data.objects(dataset) ==
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), iri(EX.O5), ~B<foo>])
MapSet.new([iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), iri(EX.O5), ~B<foo>])
end
test "resources", %{dataset: dataset} do
assert RDF.Data.resources(dataset) == MapSet.new([
iri(EX.S), iri(EX.S2), iri(EX.S3), EX.p1, EX.p2, EX.p3,
iri(EX.O1), iri(EX.O2), iri(EX.O3), iri(EX.O4), iri(EX.O5), ~B<foo>
])
assert RDF.Data.resources(dataset) ==
MapSet.new([
iri(EX.S),
iri(EX.S2),
iri(EX.S3),
EX.p1(),
EX.p2(),
EX.p3(),
iri(EX.O1),
iri(EX.O2),
iri(EX.O3),
iri(EX.O4),
iri(EX.O5),
~B<foo>
])
end
test "subject_count", %{dataset: dataset} do
@ -452,34 +502,34 @@ defmodule RDF.DataTest do
%{
nil => %{
RDF.Term.value(RDF.iri(EX.S)) => %{
RDF.Term.value(EX.p1) => [
RDF.Term.value(EX.p1()) => [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
RDF.Term.value(EX.p2) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3) => ["_:foo", "bar"],
RDF.Term.value(EX.p2()) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3()) => ["_:foo", "bar"]
},
RDF.Term.value(RDF.iri(EX.S2)) => %{
RDF.Term.value(EX.p2) => [
RDF.Term.value(EX.p2()) => [
RDF.Term.value(RDF.iri(EX.O3)),
RDF.Term.value(RDF.iri(EX.O4))
],
},
]
}
},
RDF.Term.value(RDF.iri(EX.NamedGraph)) => %{
RDF.Term.value(RDF.iri(EX.S)) => %{
RDF.Term.value(EX.p1) => [
RDF.Term.value(EX.p1()) => [
RDF.Term.value(RDF.iri(EX.O1)),
RDF.Term.value(RDF.iri(EX.O2))
],
RDF.Term.value(EX.p2) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3) => ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))],
RDF.Term.value(EX.p2()) => [RDF.Term.value(RDF.iri(EX.O3))],
RDF.Term.value(EX.p3()) => ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))]
},
RDF.Term.value(RDF.iri(EX.S3)) => %{
RDF.Term.value(EX.p3) => [
RDF.Term.value(EX.p3()) => [
RDF.Term.value(RDF.iri(EX.O5))
],
},
]
}
}
}
end
@ -488,8 +538,10 @@ defmodule RDF.DataTest 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
@ -503,14 +555,14 @@ defmodule RDF.DataTest do
RDF.Term.value(RDF.iri(EX.O2))
],
p2: [RDF.Term.value(RDF.iri(EX.O3))],
p3: ["_:foo", "bar"],
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)) => %{
@ -519,13 +571,13 @@ defmodule RDF.DataTest do
RDF.Term.value(RDF.iri(EX.O2))
],
p2: [RDF.Term.value(RDF.iri(EX.O3))],
p3: ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))],
p3: ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))]
},
RDF.Term.value(RDF.iri(EX.S3)) => %{
p3: [
RDF.Term.value(RDF.iri(EX.O5))
],
},
]
}
}
}
end
@ -536,20 +588,26 @@ defmodule RDF.DataTest do
assert RDF.Data.equal?(Dataset.new(description), description)
assert RDF.Data.equal?(Dataset.new(graph), graph)
assert RDF.Data.equal?(Dataset.new(graph), RDF.Graph.add_prefixes(graph, %{ex: EX}))
assert RDF.Data.equal?((Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1, prefixes: %{ex: EX}))),
(Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1, prefixes: %{ex: RDF}))))
assert RDF.Data.equal?(
Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1, prefixes: %{ex: EX})),
Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1, prefixes: %{ex: RDF}))
)
refute RDF.Data.equal?(dataset, dataset |> Dataset.delete_graph(EX.NamedGraph))
refute RDF.Data.equal?(dataset |> Dataset.delete_graph(EX.NamedGraph), dataset)
refute RDF.Data.equal?((Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1))),
(Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph2))))
refute RDF.Data.equal?(
Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph1)),
Dataset.new(graph)
|> Dataset.add(Graph.new(description, name: EX.Graph2))
)
refute RDF.Data.equal?(dataset, description)
refute RDF.Data.equal?(dataset, graph)
end
end
end

File diff suppressed because it is too large Load diff

View file

@ -6,192 +6,211 @@ defmodule RDF.Literal.GenericTest do
@valid %{
# input => { value , datatype }
"foo" => { "foo" , "http://example.com/datatype" },
"foo" => {"foo", "http://example.com/datatype"}
}
describe "new" do
test "with value and datatype" do
Enum.each @valid, fn {input, {value, datatype}} ->
Enum.each(@valid, fn {input, {value, datatype}} ->
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype}} =
Generic.new(input, datatype: datatype)
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype}} =
Generic.new(input, datatype: RDF.iri(datatype))
end
end)
end
test "with datatype directly" do
Enum.each @valid, fn {input, {_, datatype}} ->
Enum.each(@valid, fn {input, {_, datatype}} ->
datatype_iri = RDF.iri(datatype)
assert Generic.new(input, datatype) == Generic.new(input, datatype: datatype)
assert Generic.new(input, datatype_iri) == Generic.new(input, datatype: datatype_iri)
end
end)
end
test "with datatype as a vocabulary term" do
datatype = EX.Datatype |> RDF.iri() |> to_string()
assert %Literal{literal: %Generic{value: "foo", datatype: ^datatype}} =
Generic.new("foo", datatype: EX.Datatype)
assert Generic.new("foo", EX.Datatype) == Generic.new("foo", datatype: EX.Datatype)
end
test "with canonicalize opts" do
Enum.each @valid, fn {input, {value, datatype}} ->
Enum.each(@valid, fn {input, {value, datatype}} ->
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype}} =
Generic.new(input, datatype: datatype, canonicalize: true)
end
end)
end
test "without a datatype it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
literal = Generic.new(input, [])
assert Generic.valid?(literal) == false
end
end)
end
test "with nil as a datatype it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
literal = Generic.new(input, datatype: nil)
assert Generic.valid?(literal) == false
end
end)
end
test "with the empty string as a datatype it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
literal = Generic.new(input, datatype: "")
assert Generic.valid?(literal) == false
end
end)
end
end
describe "new!" do
test "with valid values, it behaves the same as new" do
Enum.each @valid, fn {input, {_, datatype}} ->
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new!(input, datatype: datatype) ==
Generic.new(input, datatype: datatype)
assert Generic.new!(input, datatype: datatype, canonicalize: true) ==
Generic.new(input, datatype: datatype, canonicalize: true)
end
end)
end
test "without a datatype it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> Generic.new!(input, []) end
end
end)
end
test "with nil as a datatype it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> Generic.new!(input, datatype: nil) end
end
end)
end
test "with the empty string as a datatype it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> Generic.new!(input, datatype: "") end
end
end)
end
end
test "datatype?/1" do
assert Generic.datatype?(Generic) == true
Enum.each @valid, fn {input, {_, datatype}} ->
Enum.each(@valid, fn {input, {_, datatype}} ->
literal = Generic.new(input, datatype: datatype)
assert Generic.datatype?(literal) == true
assert Generic.datatype?(literal.literal) == true
end
end)
end
test "datatype_id/1" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.datatype_id()) == RDF.iri(datatype)
end
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.datatype_id() == RDF.iri(datatype)
end)
end
test "language/1" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.language()) == nil
end
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.language() == nil
end)
end
test "value/1" do
Enum.each @valid, fn {input, {value, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.value()) == value
end
Enum.each(@valid, fn {input, {value, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.value() == value
end)
end
test "lexical/1" do
Enum.each @valid, fn {input, {value, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.lexical()) == value
end
Enum.each(@valid, fn {input, {value, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.lexical() == value
end)
end
test "canonical/1" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.canonical()) ==
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.canonical() ==
Generic.new(input, datatype: datatype)
end
end)
end
test "canonical?/1" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.canonical?()) == true
end
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.canonical?() == true
end)
end
describe "valid?/1" do
test "with a datatype" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.valid?()) == true
end
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.valid?() == true
end)
end
test "without a datatype" do
Enum.each @valid, fn {input, _} ->
assert (Generic.new(input, datatype: nil) |> Generic.valid?()) == false
assert (Generic.new(input, datatype: "") |> Generic.valid?()) == false
end
Enum.each(@valid, fn {input, _} ->
assert Generic.new(input, datatype: nil) |> Generic.valid?() == false
assert Generic.new(input, datatype: "") |> Generic.valid?() == false
end)
end
end
describe "cast/1" do
test "always return nil (RDF.Literal.Generic does not support cast)" do
Enum.each @valid, fn {input, {_, datatype}} ->
assert (Generic.new(input, datatype: datatype) |> Generic.cast()) == nil
end
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.new(input, datatype: datatype) |> Generic.cast() == nil
end)
end
end
test "equal_value?/2" do
Enum.each @valid, fn {input, {_, datatype}} ->
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.equal_value?(
Generic.new(input, datatype: datatype),
Generic.new(input, datatype: datatype)) == true
end
Generic.new(input, datatype: datatype)
) == true
end)
assert Generic.equal_value?(
Generic.new("foo", datatype: "http://example.com/foo"),
Generic.new("foo", datatype: "http://example.com/bar")) == nil
Generic.new("foo", datatype: "http://example.com/bar")
) == nil
assert Generic.equal_value?(Generic.new("foo", []), Generic.new("foo", [])) == true
assert Generic.equal_value?(Generic.new("foo", []), Generic.new("bar", [])) == false
assert Generic.equal_value?(Generic.new("foo", datatype: "foo"), RDF.XSD.String.new("foo")) == nil
assert Generic.equal_value?(Generic.new("foo", datatype: "foo"), RDF.XSD.String.new("foo")) ==
nil
end
test "compare/2" do
Enum.each @valid, fn {input, {_, datatype}} ->
Enum.each(@valid, fn {input, {_, datatype}} ->
assert Generic.compare(
Generic.new(input, datatype: datatype),
Generic.new(input, datatype: datatype)) == :eq
end
Generic.new(input, datatype: datatype)
) == :eq
end)
assert Generic.compare(Generic.new("foo", datatype: "en"), Generic.new("bar", datatype: "en")) == :gt
assert Generic.compare(Generic.new("bar", datatype: "en"), Generic.new("baz", datatype: "en")) == :lt
assert Generic.compare(Generic.new("foo", datatype: "en"), Generic.new("bar", datatype: "en")) ==
:gt
assert Generic.compare(Generic.new("bar", datatype: "en"), Generic.new("baz", datatype: "en")) ==
:lt
assert Generic.compare(
Generic.new("foo", datatype: "en"),
Generic.new("foo", datatype: "de")) == nil
Generic.new("foo", datatype: "de")
) == nil
assert Generic.compare(Generic.new("foo", []), Generic.new("foo", [])) == nil
assert Generic.compare(Generic.new("foo", []), RDF.XSD.String.new("foo")) == nil
end

View file

@ -5,171 +5,179 @@ defmodule RDF.LangStringTest do
alias RDF.XSD
@valid %{
# input => { value , language }
"foo" => { "foo" , "en" },
0 => { "0" , "en" },
42 => { "42" , "en" },
3.14 => { "3.14" , "en" },
true => { "true" , "en" },
false => { "false" , "en" },
# input => { value, language }
"foo" => {"foo", "en"},
0 => {"0", "en"},
42 => {"42", "en"},
3.14 => {"3.14", "en"},
true => {"true", "en"},
false => {"false", "en"}
}
describe "new" do
test "with value and language" do
Enum.each @valid, fn {input, {value, language}} ->
Enum.each(@valid, fn {input, {value, language}} ->
assert %Literal{literal: %LangString{value: ^value, language: ^language}} =
LangString.new(input, language: language)
assert %Literal{literal: %LangString{value: ^value, language: ^language}} =
LangString.new(input, language: String.to_atom(language))
end
end)
end
test "with language directly" do
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language) == LangString.new(input, language: language)
assert LangString.new(input, String.to_atom(language)) ==
LangString.new(input, language: String.to_atom(language))
end
end)
end
test "language get normalized to downcase" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %LangString{value: ^value, language: "de"}} =
LangString.new(input, language: "DE")
end
end)
end
test "with canonicalize opts" do
Enum.each @valid, fn {input, {value, language}} ->
Enum.each(@valid, fn {input, {value, language}} ->
assert %Literal{literal: %LangString{value: ^value, language: ^language}} =
LangString.new(input, language: language, canonicalize: true)
end
end)
end
test "without a language it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
literal = LangString.new(input, [])
assert LangString.valid?(literal) == false
end
end)
end
test "with nil as a language it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
literal = LangString.new(input, language: nil)
assert LangString.valid?(literal) == false
end
end)
end
test "with the empty string as a language it produces an invalid literal" do
Enum.each @valid, fn {input, {value, _}} ->
Enum.each(@valid, fn {input, {value, _}} ->
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
literal = LangString.new(input, language: "")
assert LangString.valid?(literal) == false
end
end)
end
end
describe "new!" do
test "with valid values, it behaves the same as new" do
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new!(input, language: language) ==
LangString.new(input, language: language)
LangString.new(input, language: language)
assert LangString.new!(input, language: language, canonicalize: true) ==
LangString.new(input, language: language, canonicalize: true)
end
LangString.new(input, language: language, canonicalize: true)
end)
end
test "without a language it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> LangString.new!(input, []) end
end
end)
end
test "with nil as a language it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> LangString.new!(input, language: nil) end
end
end)
end
test "with the empty string as a language it raises an error" do
Enum.each @valid, fn {input, _} ->
Enum.each(@valid, fn {input, _} ->
assert_raise ArgumentError, fn -> LangString.new!(input, language: "") end
end
end)
end
end
test "datatype?/1" do
assert LangString.datatype?(LangString) == true
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
literal = LangString.new(input, language: language)
assert LangString.datatype?(literal) == true
assert LangString.datatype?(literal.literal) == true
end
end)
end
test "datatype_id/1" do
Enum.each @valid, fn {input, {_, language}} ->
assert (LangString.new(input, language: language) |> LangString.datatype_id()) == RDF.iri(LangString.id())
end
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.datatype_id() ==
RDF.iri(LangString.id())
end)
end
test "language/1" do
Enum.each @valid, fn {input, {_, language}} ->
assert (LangString.new(input, language: language) |> LangString.language()) == language
end
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.language() == language
end)
assert (LangString.new("foo", language: nil) |> LangString.language()) == nil
assert (LangString.new("foo", language: "") |> LangString.language()) == nil
assert LangString.new("foo", language: nil) |> LangString.language() == nil
assert LangString.new("foo", language: "") |> LangString.language() == nil
end
test "value/1" do
Enum.each @valid, fn {input, {value, language}} ->
assert (LangString.new(input, language: language) |> LangString.value()) == value
end
Enum.each(@valid, fn {input, {value, language}} ->
assert LangString.new(input, language: language) |> LangString.value() == value
end)
end
test "lexical/1" do
Enum.each @valid, fn {input, {value, language}} ->
assert (LangString.new(input, language: language) |> LangString.lexical()) == value
end
Enum.each(@valid, fn {input, {value, language}} ->
assert LangString.new(input, language: language) |> LangString.lexical() == value
end)
end
test "canonical/1" do
Enum.each @valid, fn {input, {_, language}} ->
assert (LangString.new(input, language: language) |> LangString.canonical()) ==
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.canonical() ==
LangString.new(input, language: language)
end
end)
end
test "canonical?/1" do
Enum.each @valid, fn {input, {_, language}} ->
assert (LangString.new(input, language: language) |> LangString.canonical?()) == true
end
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.canonical?() == true
end)
end
describe "valid?/1" do
test "with a language" do
Enum.each @valid, fn {input, {_, language}} ->
assert (LangString.new(input, language: language) |> LangString.valid?()) == true
end
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.valid?() == true
end)
end
test "without a language" do
Enum.each @valid, fn {input, _} ->
assert (LangString.new(input, language: nil) |> LangString.valid?()) == false
assert (LangString.new(input, language: "") |> LangString.valid?()) == false
end
Enum.each(@valid, fn {input, _} ->
assert LangString.new(input, language: nil) |> LangString.valid?() == false
assert LangString.new(input, language: "") |> LangString.valid?() == false
end)
end
end
describe "cast/1" do
test "when given a valid RDF.LangString literal" do
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.new(input, language: language) |> LangString.cast() ==
LangString.new(input, language: language)
end
end)
end
test "when given an valid RDF.LangString literal" do
@ -192,15 +200,18 @@ defmodule RDF.LangStringTest do
end
test "equal_value?/2" do
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.equal_value?(
LangString.new(input, language: language),
LangString.new(input, language: language)) == true
end
LangString.new(input, language: language)
) == true
end)
assert LangString.equal_value?(
LangString.new("foo", language: "en"),
LangString.new("foo", language: "de")) == false
LangString.new("foo", language: "de")
) == false
assert LangString.equal_value?(LangString.new("foo", []), LangString.new("foo", [])) == true
assert LangString.equal_value?(LangString.new("foo", []), LangString.new("bar", [])) == false
assert LangString.equal_value?(LangString.new("foo", []), RDF.XSD.String.new("foo")) == nil
@ -208,18 +219,28 @@ defmodule RDF.LangStringTest do
end
test "compare/2" do
Enum.each @valid, fn {input, {_, language}} ->
Enum.each(@valid, fn {input, {_, language}} ->
assert LangString.compare(
LangString.new(input, language: language),
LangString.new(input, language: language)) == :eq
end
assert LangString.compare(LangString.new("foo", language: "en"), LangString.new("bar", language: "en")) == :gt
assert LangString.compare(LangString.new("bar", language: "en"), LangString.new("baz", language: "en")) == :lt
LangString.new(input, language: language)
) == :eq
end)
assert LangString.compare(
LangString.new("foo", language: "en"),
LangString.new("foo", language: "de")) == nil
LangString.new("bar", language: "en")
) == :gt
assert LangString.compare(
LangString.new("bar", language: "en"),
LangString.new("baz", language: "en")
) == :lt
assert LangString.compare(
LangString.new("foo", language: "en"),
LangString.new("foo", language: "de")
) == nil
assert LangString.compare(LangString.new("foo", []), LangString.new("foo", [])) == nil
assert LangString.compare(LangString.new("foo", []), RDF.XSD.String.new("foo")) == nil
end
@ -231,35 +252,39 @@ defmodule RDF.LangStringTest do
{"de-DE", "de"},
{"de-CH", "de"},
{"de-CH", "de-ch"},
{"de-DE-1996", "de-de"},
{"de-DE-1996", "de-de"}
]
@negative_examples [
{"en", "de"},
{"de", "de-CH"},
{"de-Deva", "de-de"},
{"de-Latn-DE", "de-de"},
{"de-Latn-DE", "de-de"}
]
test "with a language tag and a matching non-'*' language range" do
Enum.each @positive_examples, fn {language_tag, language_range} ->
Enum.each(@positive_examples, fn {language_tag, language_range} ->
assert LangString.match_language?(language_tag, language_range),
"expected language range #{inspect language_range} to match language tag #{inspect language_tag}, but it didn't"
end
"expected language range #{inspect(language_range)} to match language tag #{
inspect(language_tag)
}, but it didn't"
end)
end
test "with a language tag and a non-matching non-'*' language range" do
Enum.each @negative_examples, fn {language_tag, language_range} ->
Enum.each(@negative_examples, fn {language_tag, language_range} ->
refute LangString.match_language?(language_tag, language_range),
"expected language range #{inspect language_range} to not match language tag #{inspect language_tag}, but it did"
end
"expected language range #{inspect(language_range)} to not match language tag #{
inspect(language_tag)
}, but it did"
end)
end
test "with a language tag and '*' language range" do
Enum.each @positive_examples ++ @negative_examples, fn {language_tag, _} ->
Enum.each(@positive_examples ++ @negative_examples, fn {language_tag, _} ->
assert LangString.match_language?(language_tag, "*"),
~s[expected language range "*" to match language tag #{inspect language_tag}, but it didn't]
end
~s[expected language range "*" to match language tag #{inspect(language_tag)}, but it didn't]
end)
end
test "with the empty string as language tag" do
@ -272,16 +297,22 @@ defmodule RDF.LangStringTest do
end
test "with a RDF.LangString literal and a language range" do
Enum.each @positive_examples, fn {language_tag, language_range} ->
Enum.each(@positive_examples, fn {language_tag, language_range} ->
literal = LangString.new("foo", language: language_tag)
assert LangString.match_language?(literal, language_range),
"expected language range #{inspect language_range} to match #{inspect literal}, but it didn't"
end
Enum.each @negative_examples, fn {language_tag, language_range} ->
"expected language range #{inspect(language_range)} to match #{inspect(literal)}, but it didn't"
end)
Enum.each(@negative_examples, fn {language_tag, language_range} ->
literal = LangString.new("foo", language: language_tag)
refute LangString.match_language?(literal, language_range),
"expected language range #{inspect language_range} to not match #{inspect literal}, but it did"
end
"expected language range #{inspect(language_range)} to not match #{
inspect(literal)
}, but it did"
end)
refute LangString.match_language?(LangString.new("foo", language: ""), "de")
refute LangString.match_language?(LangString.new("foo", language: ""), "*")
refute LangString.match_language?(LangString.new("foo", language: nil), "de")

View file

@ -3,16 +3,19 @@ defmodule RDF.DescriptionTest do
doctest RDF.Description
describe "new" do
test "with a subject IRI" do
assert description_of_subject(Description.new(~I<http://example.com/description/subject>),
~I<http://example.com/description/subject>)
assert description_of_subject(
Description.new(~I<http://example.com/description/subject>),
~I<http://example.com/description/subject>
)
end
test "with a raw subject IRI string" do
assert description_of_subject(Description.new("http://example.com/description/subject"),
~I<http://example.com/description/subject>)
assert description_of_subject(
Description.new("http://example.com/description/subject"),
~I<http://example.com/description/subject>
)
end
test "with an unresolved subject IRI term atom" do
@ -24,34 +27,38 @@ defmodule RDF.DescriptionTest do
end
test "with a single initial triple" do
desc = Description.new({EX.Subject, EX.predicate, EX.Object})
desc = Description.new({EX.Subject, EX.predicate(), EX.Object})
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object)})
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object)})
desc = Description.new(EX.Subject, EX.predicate, 42)
desc = Description.new(EX.Subject, EX.predicate(), 42)
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate, literal(42)})
assert description_includes_predication(desc, {EX.predicate(), literal(42)})
end
test "with a list of initial triples" do
desc = Description.new([{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate2, EX.Object2}])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
desc =
Description.new([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2}
])
desc = Description.new(EX.Subject, EX.predicate, [EX.Object, bnode(:foo), "bar"])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object)})
assert description_includes_predication(desc, {EX.predicate, bnode(:foo)})
assert description_includes_predication(desc, {EX.predicate, literal("bar")})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
desc = Description.new(EX.Subject, EX.predicate(), [EX.Object, bnode(:foo), "bar"])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object)})
assert description_includes_predication(desc, {EX.predicate(), bnode(:foo)})
assert description_includes_predication(desc, {EX.predicate(), literal("bar")})
end
test "from another description" do
desc1 = Description.new({EX.Other, EX.predicate, EX.Object})
desc1 = Description.new({EX.Other, EX.predicate(), EX.Object})
desc2 = Description.new(EX.Subject, desc1)
assert description_of_subject(desc2, iri(EX.Subject))
assert description_includes_predication(desc2, {EX.predicate, iri(EX.Object)})
assert description_includes_predication(desc2, {EX.predicate(), iri(EX.Object)})
end
test "from a map with coercible RDF term" do
@ -61,123 +68,150 @@ defmodule RDF.DescriptionTest do
end
test "with another description as subject, it performs and add " do
desc = Description.new({EX.S, EX.p, EX.O})
desc = Description.new({EX.S, EX.p(), EX.O})
assert Description.new(desc, EX.p2, EX.O2) ==
Description.add(desc, EX.p2, EX.O2)
assert Description.new(desc, EX.p, [EX.O1, EX.O2]) ==
Description.add(desc, EX.p, [EX.O1, EX.O2])
assert Description.new(desc, EX.p2(), EX.O2) ==
Description.add(desc, EX.p2(), EX.O2)
assert Description.new(desc, EX.p(), [EX.O1, EX.O2]) ==
Description.add(desc, EX.p(), [EX.O1, EX.O2])
end
end
describe "add" do
test "a predicate-object-pair of proper RDF terms" do
assert Description.add(description(), EX.predicate, iri(EX.Object))
|> description_includes_predication({EX.predicate, iri(EX.Object)})
assert Description.add(description(), {EX.predicate, iri(EX.Object)})
|> description_includes_predication({EX.predicate, iri(EX.Object)})
assert Description.add(description(), EX.predicate(), iri(EX.Object))
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(description(), {EX.predicate(), iri(EX.Object)})
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
end
test "a predicate-object-pair of coercible RDF terms" do
assert Description.add(description(),
"http://example.com/predicate", iri(EX.Object))
|> description_includes_predication({EX.predicate, iri(EX.Object)})
assert Description.add(description(), "http://example.com/predicate", iri(EX.Object))
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(description(),
{"http://example.com/predicate", 42})
|> description_includes_predication({EX.predicate, literal(42)})
assert Description.add(
description(),
{"http://example.com/predicate", 42}
)
|> description_includes_predication({EX.predicate(), literal(42)})
assert Description.add(description(),
{"http://example.com/predicate", true})
|> description_includes_predication({EX.predicate, literal(true)})
assert Description.add(
description(),
{"http://example.com/predicate", true}
)
|> description_includes_predication({EX.predicate(), literal(true)})
assert Description.add(description(),
{"http://example.com/predicate", bnode(:foo)})
|> description_includes_predication({EX.predicate, bnode(:foo)})
assert Description.add(
description(),
{"http://example.com/predicate", bnode(:foo)}
)
|> description_includes_predication({EX.predicate(), bnode(:foo)})
end
test "a proper triple" do
assert Description.add(description(),
{iri(EX.Subject), EX.predicate, iri(EX.Object)})
|> description_includes_predication({EX.predicate, iri(EX.Object)})
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), iri(EX.Object)}
)
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(description(),
{iri(EX.Subject), EX.predicate, literal(42)})
|> description_includes_predication({EX.predicate, literal(42)})
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), literal(42)}
)
|> description_includes_predication({EX.predicate(), literal(42)})
assert Description.add(description(),
{iri(EX.Subject), EX.predicate, bnode(:foo)})
|> description_includes_predication({EX.predicate, bnode(:foo)})
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), bnode(:foo)}
)
|> description_includes_predication({EX.predicate(), bnode(:foo)})
end
test "add ignores triples not about the subject of the Description struct" do
assert empty_description(
Description.add(description(), {EX.Other, EX.predicate, iri(EX.Object)}))
Description.add(description(), {EX.Other, EX.predicate(), iri(EX.Object)})
)
end
test "a list of predicate-object-pairs" do
desc = Description.add(description(),
[{EX.predicate, EX.Object1}, {EX.predicate, EX.Object2}])
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object2)})
desc =
Description.add(
description(),
[{EX.predicate(), EX.Object1}, {EX.predicate(), EX.Object2}]
)
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object2)})
end
test "a list of triples" do
desc = Description.add(description(), [
{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate2, EX.Object2}
])
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
desc =
Description.add(description(), [
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2}
])
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
end
test "a list of mixed triples and predicate-object-pairs" do
desc = Description.add(description(), [
{EX.predicate, EX.Object1},
{EX.Subject, EX.predicate, EX.Object2},
{EX.Other, EX.predicate, EX.Object3}
])
desc =
Description.add(description(), [
{EX.predicate(), EX.Object1},
{EX.Subject, EX.predicate(), EX.Object2},
{EX.Other, EX.predicate(), EX.Object3}
])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate, iri(EX.Object2)})
refute description_includes_predication(desc, {EX.predicate, iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object2)})
refute description_includes_predication(desc, {EX.predicate(), iri(EX.Object3)})
end
test "another description" do
desc = description([{EX.predicate1, EX.Object1}, {EX.predicate2, EX.Object2}])
|> Description.add(Description.new({EX.Other, EX.predicate3, EX.Object3}))
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.add(Description.new({EX.Other, EX.predicate3(), EX.Object3}))
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3, iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
desc = Description.add(desc, Description.new({EX.Other, EX.predicate1, EX.Object4}))
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3, iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object4)})
desc = Description.add(desc, Description.new({EX.Other, EX.predicate1(), EX.Object4}))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object4)})
end
test "a map of predications with coercible RDF terms" do
desc = description([{EX.predicate1, EX.Object1}, {EX.predicate2, EX.Object2}])
|> Description.add(%{EX.predicate3 => EX.Object3})
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.add(%{EX.predicate3() => EX.Object3})
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3, iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
desc =
Description.add(desc, %{
EX.predicate1() => EX.Object1,
EX.predicate2() => [EX.Object2, 42],
EX.predicate3() => [bnode(:foo)]
})
desc = Description.add(desc, %{EX.predicate1 => EX.Object1,
EX.predicate2 => [EX.Object2, 42],
EX.predicate3 => [bnode(:foo)]})
assert Description.count(desc) == 5
assert description_includes_predication(desc, {EX.predicate1, iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2, iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate2, literal(42)})
assert description_includes_predication(desc, {EX.predicate3, iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate3, bnode(:foo)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate2(), literal(42)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate3(), bnode(:foo)})
end
test "a map of predications with non-coercible RDF terms" do
@ -186,17 +220,17 @@ defmodule RDF.DescriptionTest do
end
assert_raise RDF.Literal.InvalidError, fn ->
Description.add(description(), %{EX.prop => self()})
Description.add(description(), %{EX.prop() => self()})
end
end
test "duplicates are ignored" do
desc = Description.add(description(), {EX.predicate, EX.Object})
assert Description.add(desc, {EX.predicate, EX.Object}) == desc
assert Description.add(desc, {EX.Subject, EX.predicate, EX.Object}) == desc
desc = Description.add(description(), {EX.predicate(), EX.Object})
assert Description.add(desc, {EX.predicate(), EX.Object}) == desc
assert Description.add(desc, {EX.Subject, EX.predicate(), EX.Object}) == desc
desc = Description.add(description(), {EX.predicate, 42})
assert Description.add(desc, {EX.predicate, literal(42)}) == desc
desc = Description.add(description(), {EX.predicate(), 42})
assert Description.add(desc, {EX.predicate(), literal(42)}) == desc
end
test "non-coercible Triple elements are causing an error" do
@ -205,7 +239,7 @@ defmodule RDF.DescriptionTest do
end
assert_raise RDF.Literal.InvalidError, fn ->
Description.add(description(), {EX.prop, self()})
Description.add(description(), {EX.prop(), self()})
end
end
end
@ -213,115 +247,164 @@ defmodule RDF.DescriptionTest do
describe "delete" do
setup do
{:ok,
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p, EX.O),
description2: Description.new(EX.S, EX.p, [EX.O1, EX.O2]),
description3: Description.new(EX.S, [
{EX.p1, [EX.O1, EX.O2]},
{EX.p2, EX.O3},
{EX.p3, [~B<foo>, ~L"bar"]},
])
}
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p(), EX.O),
description2: Description.new(EX.S, EX.p(), [EX.O1, EX.O2]),
description3:
Description.new(EX.S, [
{EX.p1(), [EX.O1, EX.O2]},
{EX.p2(), EX.O3},
{EX.p3(), [~B<foo>, ~L"bar"]}
])}
end
test "a single statement as a predicate object",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete(empty_description, EX.p, EX.O) == empty_description
assert Description.delete(description1, EX.p, EX.O) == empty_description
assert Description.delete(description2, EX.p, EX.O1) == Description.new(EX.S, EX.p, EX.O2)
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, EX.p(), EX.O) == empty_description
assert Description.delete(description1, EX.p(), EX.O) == empty_description
assert Description.delete(description2, EX.p(), EX.O1) ==
Description.new(EX.S, EX.p(), EX.O2)
end
test "a single statement as a predicate-object tuple",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete(empty_description, {EX.p, EX.O}) == empty_description
assert Description.delete(description1, {EX.p, EX.O}) == empty_description
assert Description.delete(description2, {EX.p, EX.O2}) == Description.new(EX.S, EX.p, EX.O1)
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, {EX.p(), EX.O}) == empty_description
assert Description.delete(description1, {EX.p(), EX.O}) == empty_description
assert Description.delete(description2, {EX.p(), EX.O2}) ==
Description.new(EX.S, EX.p(), EX.O1)
end
test "a single statement as a subject-predicate-object tuple and the proper description subject",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete(empty_description, {EX.S, EX.p, EX.O}) == empty_description
assert Description.delete(description1, {EX.S, EX.p, EX.O}) == empty_description
assert Description.delete(description2, {EX.S, EX.p, EX.O2}) == Description.new(EX.S, EX.p, EX.O1)
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, {EX.S, EX.p(), EX.O}) == empty_description
assert Description.delete(description1, {EX.S, EX.p(), EX.O}) == empty_description
assert Description.delete(description2, {EX.S, EX.p(), EX.O2}) ==
Description.new(EX.S, EX.p(), EX.O1)
end
test "a single statement as a subject-predicate-object tuple and another description subject",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete(empty_description, {EX.Other, EX.p, EX.O}) == empty_description
assert Description.delete(description1, {EX.Other, EX.p, EX.O}) == description1
assert Description.delete(description2, {EX.Other, EX.p, EX.O2}) == description2
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, {EX.Other, EX.p(), EX.O}) == empty_description
assert Description.delete(description1, {EX.Other, EX.p(), EX.O}) == description1
assert Description.delete(description2, {EX.Other, EX.p(), EX.O2}) == description2
end
test "multiple statements via predicate-objects tuple",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete(empty_description, {EX.p, [EX.O1, EX.O2]}) == empty_description
assert Description.delete(description1, {EX.p, [EX.O, EX.O2]}) == empty_description
assert Description.delete(description2, {EX.p, [EX.O1, EX.O2]}) == empty_description
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, {EX.p(), [EX.O1, EX.O2]}) == empty_description
assert Description.delete(description1, {EX.p(), [EX.O, EX.O2]}) == empty_description
assert Description.delete(description2, {EX.p(), [EX.O1, EX.O2]}) == empty_description
end
test "multiple statements with a list",
%{empty_description: empty_description, description3: description3} do
assert Description.delete(empty_description, [{EX.p, [EX.O1, EX.O2]}]) == empty_description
%{empty_description: empty_description, description3: description3} do
assert Description.delete(empty_description, [{EX.p(), [EX.O1, EX.O2]}]) ==
empty_description
assert Description.delete(description3, [
{EX.p1, EX.O1},
{EX.p2, [EX.O2, EX.O3]},
{EX.S, EX.p3, [~B<foo>, ~L"bar"]},
]) == Description.new(EX.S, EX.p1, EX.O2)
{EX.p1(), EX.O1},
{EX.p2(), [EX.O2, EX.O3]},
{EX.S, EX.p3(), [~B<foo>, ~L"bar"]}
]) == Description.new(EX.S, EX.p1(), EX.O2)
end
test "multiple statements with a map of predications",
%{empty_description: empty_description, description3: description3} do
assert Description.delete(empty_description, %{EX.p => EX.O1}) == empty_description
%{empty_description: empty_description, description3: description3} do
assert Description.delete(empty_description, %{EX.p() => EX.O1}) == empty_description
assert Description.delete(description3, %{
EX.p1 => EX.O1,
EX.p2 => [EX.O2, EX.O3],
EX.p3 => [~B<foo>, ~L"bar"],
}) == Description.new(EX.S, EX.p1, EX.O2)
EX.p1() => EX.O1,
EX.p2() => [EX.O2, EX.O3],
EX.p3() => [~B<foo>, ~L"bar"]
}) == Description.new(EX.S, EX.p1(), EX.O2)
end
test "multiple statements with another description",
%{empty_description: empty_description, description1: description1, description3: description3} do
%{
empty_description: empty_description,
description1: description1,
description3: description3
} do
assert Description.delete(empty_description, description1) == empty_description
assert Description.delete(description3, Description.new(EX.S, %{
EX.p1 => EX.O1,
EX.p2 => [EX.O2, EX.O3],
EX.p3 => [~B<foo>, ~L"bar"],
})) == Description.new(EX.S, EX.p1, EX.O2)
assert Description.delete(
description3,
Description.new(EX.S, %{
EX.p1() => EX.O1,
EX.p2() => [EX.O2, EX.O3],
EX.p3() => [~B<foo>, ~L"bar"]
})
) == Description.new(EX.S, EX.p1(), EX.O2)
end
end
describe "delete_predicates" do
setup do
{:ok,
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p, [EX.O1, EX.O2]),
description2: Description.new(EX.S, [
{EX.P1, [EX.O1, EX.O2]},
{EX.p2, [~B<foo>, ~L"bar"]},
])
}
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p(), [EX.O1, EX.O2]),
description2:
Description.new(EX.S, [
{EX.P1, [EX.O1, EX.O2]},
{EX.p2(), [~B<foo>, ~L"bar"]}
])}
end
test "a single property",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete_predicates(description1, EX.p) == empty_description
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete_predicates(description1, EX.p()) == empty_description
assert Description.delete_predicates(description2, EX.P1) ==
Description.new(EX.S, EX.p2, [~B<foo>, ~L"bar"])
Description.new(EX.S, EX.p2(), [~B<foo>, ~L"bar"])
end
test "a list of properties",
%{empty_description: empty_description, description1: description1, description2: description2} do
assert Description.delete_predicates(description1, [EX.p]) == empty_description
assert Description.delete_predicates(description2, [EX.P1, EX.p2, EX.p3]) == empty_description
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete_predicates(description1, [EX.p()]) == empty_description
assert Description.delete_predicates(description2, [EX.P1, EX.p2(), EX.p3()]) ==
empty_description
end
end
describe "update/4" do
test "list values returned from the update function become new coerced objects of the predicate" do
assert Description.new(EX.S, EX.P, [EX.O1, EX.O2])
|> Description.update(EX.P,
fn [_object | other] -> [EX.O3 | other] end) ==
|> Description.update(
EX.P,
fn [_object | other] -> [EX.O3 | other] end
) ==
Description.new(EX.S, EX.P, [EX.O3, EX.O2])
end
@ -332,18 +415,22 @@ defmodule RDF.DescriptionTest do
end
test "returning an empty list or nil from the update function causes a removal of the predications" do
description = EX.S
|> EX.p(EX.O1, EX.O2)
description =
EX.S
|> EX.p(EX.O1, EX.O2)
assert description
|> Description.update(EX.p, fn _ -> [] end) ==
Description.new(EX.S, {EX.p, []})
|> Description.update(EX.p(), fn _ -> [] end) ==
Description.new(EX.S, {EX.p(), []})
assert description
|> Description.update(EX.p, fn _ -> nil end) ==
Description.new(EX.S, {EX.p, []})
|> Description.update(EX.p(), fn _ -> nil end) ==
Description.new(EX.S, {EX.p(), []})
end
test "when the property is not present the initial object value is added for the predicate and the update function not called" do
fun = fn _ -> raise "should not be called" end
assert Description.new(EX.S)
|> Description.update(EX.P, EX.O, fun) ==
Description.new(EX.S, EX.P, EX.O)
@ -357,143 +444,174 @@ defmodule RDF.DescriptionTest do
test "pop" do
assert Description.pop(Description.new(EX.S)) == {nil, Description.new(EX.S)}
{triple, desc} = Description.new({EX.S, EX.p, EX.O}) |> Description.pop
assert {iri(EX.S), iri(EX.p), iri(EX.O)} == triple
{triple, desc} = Description.new({EX.S, EX.p(), EX.O}) |> Description.pop()
assert {iri(EX.S), iri(EX.p()), iri(EX.O)} == triple
assert Enum.count(desc.predications) == 0
{{subject, predicate, _}, desc} =
Description.new([{EX.S, EX.p, EX.O1}, {EX.S, EX.p, EX.O2}])
|> Description.pop
assert {subject, predicate} == {iri(EX.S), iri(EX.p)}
Description.new([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])
|> Description.pop()
assert {subject, predicate} == {iri(EX.S), iri(EX.p())}
assert Enum.count(desc.predications) == 1
{{subject, _, _}, desc} =
Description.new([{EX.S, EX.p1, EX.O1}, {EX.S, EX.p2, EX.O2}])
|> Description.pop
Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.pop()
assert subject == iri(EX.S)
assert Enum.count(desc.predications) == 1
end
test "values/1" do
assert Description.new(EX.s) |> Description.values() == %{}
assert Description.new({EX.s, EX.p, ~L"Foo"}) |> Description.values() ==
%{RDF.Term.value(EX.p) => ["Foo"]}
assert Description.new(EX.s()) |> Description.values() == %{}
assert Description.new({EX.s(), EX.p(), ~L"Foo"}) |> Description.values() ==
%{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) ==
assert Description.new(EX.s()) |> Description.values(mapping) == %{}
assert Description.new({EX.s(), EX.p(), ~L"Foo"}) |> Description.values(mapping) ==
%{p: ["Foo"]}
end
describe "take/2" do
test "with a non-empty property list" do
assert Description.new([{EX.S, EX.p1, EX.O1}, {EX.S, EX.p2, EX.O2}])
|> Description.take([EX.p2, EX.p3]) ==
Description.new({EX.S, EX.p2, EX.O2})
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take([EX.p2(), EX.p3()]) ==
Description.new({EX.S, EX.p2(), EX.O2})
end
test "with an empty property list" do
assert Description.new([{EX.S, EX.p1, EX.O1}, {EX.S, EX.p2, EX.O2}])
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take([]) == Description.new(EX.S)
end
test "with nil" do
assert Description.new([{EX.S, EX.p1, EX.O1}, {EX.S, EX.p2, EX.O2}])
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take(nil) ==
Description.new([{EX.S, EX.p1, EX.O1}, {EX.S, EX.p2, EX.O2}])
Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
end
end
test "equal/2" do
assert Description.new({EX.S, EX.p, EX.O}) |> Description.equal?(Description.new({EX.S, EX.p, EX.O}))
refute Description.new({EX.S, EX.p, EX.O}) |> Description.equal?(Description.new({EX.S, EX.p, EX.O2}))
assert Description.new({EX.S, EX.p(), EX.O})
|> Description.equal?(Description.new({EX.S, EX.p(), EX.O}))
refute Description.new({EX.S, EX.p(), EX.O})
|> Description.equal?(Description.new({EX.S, EX.p(), EX.O2}))
end
describe "Enumerable protocol" do
test "Enum.count" do
assert Enum.count(Description.new EX.foo) == 0
assert Enum.count(Description.new {EX.S, EX.p, EX.O}) == 1
assert Enum.count(Description.new [{EX.S, EX.p, EX.O1}, {EX.S, EX.p, EX.O2}]) == 2
assert Enum.count(Description.new(EX.foo())) == 0
assert Enum.count(Description.new({EX.S, EX.p(), EX.O})) == 1
assert Enum.count(Description.new([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])) == 2
end
test "Enum.member?" do
refute Enum.member?(Description.new(EX.S), {iri(EX.S), EX.p, iri(EX.O)})
assert Enum.member?(Description.new({EX.S, EX.p, EX.O}), {EX.S, EX.p, EX.O})
refute Enum.member?(Description.new(EX.S), {iri(EX.S), EX.p(), iri(EX.O)})
assert Enum.member?(Description.new({EX.S, EX.p(), EX.O}), {EX.S, EX.p(), EX.O})
desc = Description.new([
{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate2, EX.Object2},
{EX.predicate2, EX.Object3}])
assert Enum.member?(desc, {EX.Subject, EX.predicate1, EX.Object1})
assert Enum.member?(desc, {EX.Subject, EX.predicate2, EX.Object2})
assert Enum.member?(desc, {EX.Subject, EX.predicate2, EX.Object3})
refute Enum.member?(desc, {EX.Subject, EX.predicate1, EX.Object2})
desc =
Description.new([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2},
{EX.predicate2(), EX.Object3}
])
assert Enum.member?(desc, {EX.Subject, EX.predicate1(), EX.Object1})
assert Enum.member?(desc, {EX.Subject, EX.predicate2(), EX.Object2})
assert Enum.member?(desc, {EX.Subject, EX.predicate2(), EX.Object3})
refute Enum.member?(desc, {EX.Subject, EX.predicate1(), EX.Object2})
end
test "Enum.reduce" do
desc = Description.new([
{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate2, EX.Object2},
{EX.predicate2, EX.Object3}])
assert desc == Enum.reduce(desc, description(),
fn(triple, acc) -> acc |> Description.add(triple) end)
desc =
Description.new([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2},
{EX.predicate2(), EX.Object3}
])
assert desc ==
Enum.reduce(desc, description(), fn triple, acc ->
acc |> Description.add(triple)
end)
end
end
describe "Collectable protocol" do
test "with a map" do
map = %{
EX.predicate1 => EX.Object1,
EX.predicate2 => EX.Object2
}
EX.predicate1() => EX.Object1,
EX.predicate2() => EX.Object2
}
assert Enum.into(map, Description.new(EX.Subject)) == Description.new(EX.Subject, map)
end
test "with a list of triples" do
triples = [
{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate2, EX.Object2}
]
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2}
]
assert Enum.into(triples, Description.new(EX.Subject)) == Description.new(triples)
end
test "with a list of predicate-object pairs" do
pairs = [
{EX.predicate1, EX.Object1},
{EX.predicate2, EX.Object2}
]
{EX.predicate1(), EX.Object1},
{EX.predicate2(), EX.Object2}
]
assert Enum.into(pairs, Description.new(EX.Subject)) == Description.new(EX.Subject, pairs)
end
test "with a list of lists" do
lists = [
[EX.Subject, EX.predicate1, EX.Object1],
[EX.Subject, EX.predicate2, EX.Object2]
]
[EX.Subject, EX.predicate1(), EX.Object1],
[EX.Subject, EX.predicate2(), EX.Object2]
]
assert Enum.into(lists, Description.new(EX.Subject)) ==
Description.new(Enum.map(lists, &List.to_tuple/1))
Description.new(Enum.map(lists, &List.to_tuple/1))
end
end
describe "Access behaviour" do
test "access with the [] operator" do
assert Description.new(EX.Subject)[EX.predicate] == nil
assert Description.new(EX.Subject, EX.predicate, EX.Object)[EX.predicate] == [iri(EX.Object)]
assert Description.new(EX.Subject, EX.Predicate, EX.Object)[EX.Predicate] == [iri(EX.Object)]
assert Description.new(EX.Subject, EX.predicate, EX.Object)["http://example.com/predicate"] == [iri(EX.Object)]
assert Description.new([{EX.Subject, EX.predicate1, EX.Object1},
{EX.Subject, EX.predicate1, EX.Object2},
{EX.Subject, EX.predicate2, EX.Object3}])[EX.predicate1] ==
[iri(EX.Object1), iri(EX.Object2)]
assert Description.new(EX.Subject)[EX.predicate()] == nil
assert Description.new(EX.Subject, EX.predicate(), EX.Object)[EX.predicate()] == [
iri(EX.Object)
]
assert Description.new(EX.Subject, EX.Predicate, EX.Object)[EX.Predicate] == [
iri(EX.Object)
]
assert Description.new(EX.Subject, EX.predicate(), EX.Object)[
"http://example.com/predicate"
] == [iri(EX.Object)]
assert Description.new([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate1(), EX.Object2},
{EX.Subject, EX.predicate2(), EX.Object3}
])[EX.predicate1()] ==
[iri(EX.Object1), iri(EX.Object2)]
end
end
end

View file

@ -8,12 +8,16 @@ defmodule RDF.DiffTest do
test "new" do
assert Diff.new() ==
%Diff{additions: Graph.new(), deletions: Graph.new()}
assert Diff.new(additions: [], deletions: []) ==
%Diff{additions: Graph.new(), deletions: Graph.new()}
assert Diff.new(additions: Graph.new(), deletions: Graph.new) ==
assert Diff.new(additions: Graph.new(), deletions: Graph.new()) ==
%Diff{additions: Graph.new(), deletions: Graph.new()}
description = Description.new({EX.S, EX.p, EX.O1})
graph = Graph.new({EX.S, EX.p, EX.O2})
description = Description.new({EX.S, EX.p(), EX.O1})
graph = Graph.new({EX.S, EX.p(), EX.O2})
assert Diff.new(additions: description, deletions: graph) ==
%Diff{additions: Graph.new(description), deletions: graph}
end
@ -26,43 +30,54 @@ defmodule RDF.DiffTest do
end
test "with two descriptions with different subjects" do
description1 = Description.new({EX.S1, EX.p, EX.O})
description2 = Description.new({EX.S2, EX.p, EX.O})
description1 = Description.new({EX.S1, EX.p(), EX.O})
description2 = Description.new({EX.S2, EX.p(), EX.O})
assert Diff.diff(description1, description2) ==
Diff.new(additions: Graph.new(description2),
deletions: Graph.new(description1))
Diff.new(
additions: Graph.new(description2),
deletions: Graph.new(description1)
)
end
test "with two descriptions when the second description has additional statements" do
description1 = Description.new({EX.S, EX.p, EX.O})
description1 = Description.new({EX.S, EX.p(), EX.O})
description2 =
description1
|> EX.p(EX.O2)
|> EX.p2(EX.O)
assert Diff.diff(description1, description2) ==
Diff.new(additions: Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
),
deletions: Graph.new())
Diff.new(
additions:
Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
),
deletions: Graph.new()
)
end
test "with two descriptions when the first description has additional statements" do
description1 = Description.new({EX.S, EX.p, EX.O})
description1 = Description.new({EX.S, EX.p(), EX.O})
description2 =
description1
|> EX.p(EX.O2)
|> EX.p2(EX.O)
assert Diff.diff(description2, description1) ==
Diff.new(additions: Graph.new,
deletions: Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
))
Diff.new(
additions: Graph.new(),
deletions:
Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
)
)
end
end
@ -71,6 +86,7 @@ defmodule RDF.DiffTest do
EX.S
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O)
description2 =
EX.S
|> EX.p(EX.O1, EX.O3)
@ -78,17 +94,19 @@ defmodule RDF.DiffTest do
assert Diff.diff(description1, description2) ==
Diff.new(
additions: Graph.new(
EX.S
|> EX.p(EX.O3)
|> EX.p3(EX.O)
),
deletions: Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
))
additions:
Graph.new(
EX.S
|> EX.p(EX.O3)
|> EX.p3(EX.O)
),
deletions:
Graph.new(
EX.S
|> EX.p(EX.O2)
|> EX.p2(EX.O)
)
)
end
test "with one description and a graph" do
@ -96,112 +114,141 @@ defmodule RDF.DiffTest do
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O)
graph = Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
graph =
Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
assert Diff.diff(description, graph) ==
Diff.new(
additions: Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions: Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
]))
additions:
Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O)
])
)
assert Diff.diff(graph, description) ==
Diff.new(
additions: Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
]),
deletions: Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
additions:
Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O)
]),
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
)
disjoint_description =
EX.S
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O)
assert Diff.diff(disjoint_description, graph) ==
Diff.new(
additions: graph,
deletions: Graph.new(disjoint_description))
deletions: Graph.new(disjoint_description)
)
assert Diff.diff(graph, disjoint_description) ==
Diff.new(
additions: Graph.new(disjoint_description),
deletions: graph)
deletions: graph
)
end
test "with two graphs with additions and deletions" do
graph1 = Graph.new([
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])
graph2 = Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
graph1 =
Graph.new([
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])
graph2 =
Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
])
assert Diff.diff(graph1, graph2) ==
Diff.new(
additions: Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions: Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
]))
additions:
Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])
)
end
test "merge/2" do
assert Diff.merge(
Diff.new(additions: Graph.new({EX.S, EX.p, EX.O1}),
deletions: Graph.new({EX.S1, EX.p, EX.O})),
Diff.new(additions: Graph.new({EX.S, EX.p, EX.O2}),
deletions: Graph.new({EX.S2, EX.p, EX.O}))
Diff.new(
additions: Graph.new({EX.S, EX.p(), EX.O1}),
deletions: Graph.new({EX.S1, EX.p(), EX.O})
),
Diff.new(
additions: Graph.new({EX.S, EX.p(), EX.O2}),
deletions: Graph.new({EX.S2, EX.p(), EX.O})
)
) ==
Diff.new(
additions: Graph.new({EX.S, EX.p, [EX.O1, EX.O2]}),
deletions: Graph.new([
{EX.S1, EX.p, EX.O},
{EX.S2, EX.p, EX.O}
])
additions: Graph.new({EX.S, EX.p(), [EX.O1, EX.O2]}),
deletions:
Graph.new([
{EX.S1, EX.p(), EX.O},
{EX.S2, EX.p(), EX.O}
])
)
end
test "empty?/1" do
assert Diff.empty?(Diff.new()) == true
assert Diff.empty?(Diff.new(additions: EX.p(EX.S, EX.O),
deletions: EX.p(EX.S, EX.O))) == false
assert Diff.empty?(
Diff.new(
additions: EX.p(EX.S, EX.O),
deletions: EX.p(EX.S, EX.O)
)
) == false
assert Diff.empty?(Diff.new(additions: EX.p(EX.S, EX.O))) == false
assert Diff.empty?(Diff.new(deletions: EX.p(EX.S, EX.O))) == false
end
@ -209,27 +256,32 @@ defmodule RDF.DiffTest do
describe "apply/2" do
test "on a graph" do
assert Diff.new(
additions: Graph.new([
additions:
Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions: Graph.new([
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
]))
|> Diff.apply(Graph.new([
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])) ==
])
)
|> Diff.apply(
Graph.new([
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])
) ==
Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
@ -241,23 +293,26 @@ defmodule RDF.DiffTest do
test "on a description" do
assert Diff.new(
additions: Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions: Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O),
]))
additions:
Graph.new([
EX.S1
|> EX.p(EX.O3)
|> EX.p3(EX.O),
EX.S3
|> EX.p(EX.O)
]),
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O1)
|> EX.p2(EX.O)
])
)
|> Diff.apply(
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O)
) ==
EX.S1
|> EX.p(EX.O1, EX.O2)
|> EX.p2(EX.O)
) ==
Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
@ -269,21 +324,26 @@ defmodule RDF.DiffTest do
test "when the statements to be deleted are not present" do
assert Diff.new(
additions: Graph.new(
additions:
Graph.new(
EX.S1
|> EX.p(EX.O4)
),
deletions:
Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
])
)
|> Diff.apply(
Graph.new(
EX.S1
|> EX.p(EX.O4)
),
deletions: Graph.new([
EX.S1
|> EX.p(EX.O2, EX.O3)
|> EX.p2(EX.O),
EX.S2
|> EX.p(EX.O)
]))
|> Diff.apply(Graph.new(
EX.S1
|> EX.p(EX.O1, EX.O2)
)) ==
|> EX.p(EX.O1, EX.O2)
)
) ==
Graph.new(
EX.S1
|> EX.p(EX.O1, EX.O4)

View file

@ -1,36 +1,44 @@
defmodule RDF.EqualityTest do
use RDF.Test.Case
alias RDF.TestDatatypes.{Initials, CustomTime, DateWithoutTz, DateTimeWithTz, Age,
DecimalUnitInterval, DoubleUnitInterval, FloatUnitInterval}
alias RDF.TestDatatypes.{
Initials,
CustomTime,
DateWithoutTz,
DateTimeWithTz,
Age,
DecimalUnitInterval,
DoubleUnitInterval,
FloatUnitInterval
}
describe "RDF.IRI and XSD.AnyURI" do
@term_equal_iris [
{RDF.iri("http://example.com/"), RDF.iri("http://example.com/")},
{XSD.anyURI("http://example.com/"), XSD.anyURI("http://example.com/")},
{XSD.anyURI("http://example.com/"), XSD.anyURI("http://example.com/")}
]
@value_equal_iris [
{RDF.iri("http://example.com/"), XSD.anyURI("http://example.com/")},
{RDF.iri("http://example.com/"), XSD.anyURI("http://example.com/")}
]
@unequal_iris [
{RDF.iri("http://example.com/foo"), RDF.iri("http://example.com/bar")},
{RDF.iri("http://example.com/foo"), XSD.anyURI("http://example.com/bar")},
{RDF.iri("http://example.com/foo"), XSD.anyURI("http://example.com/bar")}
]
@equal_iris_by_coercion [
{RDF.iri("http://example.com/"), URI.parse("http://example.com/")},
{XSD.anyURI("http://example.com/"), URI.parse("http://example.com/")},
{RDF.iri("http://example.com/Foo"), EX.Foo},
{XSD.anyURI("http://example.com/Foo"), EX.Foo},
{XSD.anyURI("http://example.com/Foo"), EX.Foo}
]
@unequal_iris_by_coercion [
{RDF.iri("http://example.com/foo"), URI.parse("http://example.com/bar")},
{XSD.anyURI("http://example.com/foo"), URI.parse("http://example.com/bar")},
{RDF.iri("http://example.com/Bar"), EX.Foo},
{XSD.anyURI("http://example.com/Bar"), EX.Foo},
{XSD.anyURI("http://example.com/Bar"), EX.Foo}
]
@incomparable_iris [
{RDF.iri("http://example.com/"), XSD.string("http://example.com/")},
{XSD.anyURI("http://example.com/"), XSD.string("http://example.com/")},
{XSD.anyURI("http://example.com/"), XSD.string("http://example.com/")}
]
test "term equality", do: assert_term_equal(@term_equal_iris)
@ -43,26 +51,25 @@ defmodule RDF.EqualityTest do
describe "RDF.BlankNode" do
@term_equal_bnodes [
{RDF.bnode("foo"), RDF.bnode("foo")},
]
@value_equal_bnodes [
{RDF.bnode("foo"), RDF.bnode("foo")}
]
@value_equal_bnodes []
@unequal_bnodes [
{RDF.bnode("foo"), RDF.bnode("bar")},
{RDF.bnode("foo"), RDF.bnode("bar")}
]
@equal_bnodes_by_coercion []
@unequal_bnodes_by_coercion []
@incomparable_bnodes [
{RDF.bnode("foo"), XSD.string("foo")},
{XSD.string("foo"), RDF.bnode("foo")},
{RDF.bnode("foo"), XSD.string("foo")},
{XSD.string("foo"), RDF.bnode("foo")}
]
test "term equality", do: assert_term_equal @term_equal_bnodes
test "value equality", do: assert_value_equal @value_equal_bnodes
test "inequality", do: assert_unequal @unequal_bnodes
test "term equality", do: assert_term_equal(@term_equal_bnodes)
test "value equality", do: assert_value_equal(@value_equal_bnodes)
test "inequality", do: assert_unequal(@unequal_bnodes)
test "coerced value equality", do: assert_coerced_equal(@equal_bnodes_by_coercion)
test "coerced value inequality", do: assert_coerced_unequal(@unequal_bnodes_by_coercion)
test "incomparability", do: assert_incomparable @incomparable_bnodes
test "incomparability", do: assert_incomparable(@incomparable_bnodes)
end
describe "XSD.String and RDF.LangString" do
@ -88,7 +95,7 @@ defmodule RDF.EqualityTest do
{RDF.lang_string("foo", language: "de"), "foo"},
{XSD.string("foo"), RDF.lang_string("foo", language: "de")},
{RDF.lang_string("foo", language: "de"), XSD.string("foo")},
{XSD.string("foo"), RDF.bnode("foo")},
{XSD.string("foo"), RDF.bnode("foo")}
]
test "term equality", do: assert_term_equal(@term_equal_strings)
@ -159,7 +166,7 @@ defmodule RDF.EqualityTest do
{XSD.decimal("-42.0"), XSD.decimal(-42.0)},
{XSD.decimal("1.0"), XSD.decimal(1.0)},
{Age.new("42"), Age.new("42")},
{DecimalUnitInterval.new("0.1"), DecimalUnitInterval.new("0.1")},
{DecimalUnitInterval.new("0.1"), DecimalUnitInterval.new("0.1")}
]
@value_equal_numerics [
{XSD.integer("42"), XSD.non_negative_integer("42")},
@ -200,7 +207,7 @@ defmodule RDF.EqualityTest do
{XSD.integer(1), XSD.integer(2)},
{XSD.integer("1"), XSD.double("1.1")},
{XSD.integer("1"), XSD.decimal("1.1")},
{DecimalUnitInterval.new(0.1), DoubleUnitInterval.new(0.2)},
{DecimalUnitInterval.new(0.1), DoubleUnitInterval.new(0.2)}
]
@equal_numerics_by_coercion [
{XSD.integer(42), 42},
@ -228,7 +235,7 @@ defmodule RDF.EqualityTest do
{XSD.float("foo"), XSD.float("foo")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("foo")},
{XSD.positive_integer("foo"), XSD.positive_integer("foo")},
{DecimalUnitInterval.new(1.1), DecimalUnitInterval.new(1.1)},
{DecimalUnitInterval.new(1.1), DecimalUnitInterval.new(1.1)}
]
@unequal_invalid_numerics [
{XSD.integer("foo"), XSD.integer("bar")},
@ -239,7 +246,7 @@ defmodule RDF.EqualityTest do
{XSD.float("foo"), XSD.float("bar")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("bar")},
{XSD.positive_integer("foo"), XSD.positive_integer("bar")},
{DecimalUnitInterval.new(1.1), DoubleUnitInterval.new(1.2)},
{DecimalUnitInterval.new(1.1), DoubleUnitInterval.new(1.2)}
]
@incomparable_numerics [
{XSD.integer("42"), nil},
@ -266,7 +273,7 @@ defmodule RDF.EqualityTest do
@term_equal_datetimes [
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")},
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")},
{DateTimeWithTz.new("2002-04-02T12:00:00Z"), DateTimeWithTz.new("2002-04-02T12:00:00Z")},
{DateTimeWithTz.new("2002-04-02T12:00:00Z"), DateTimeWithTz.new("2002-04-02T12:00:00Z")}
]
@value_equal_datetimes [
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T17:00:00+04:00")},
@ -278,10 +285,10 @@ defmodule RDF.EqualityTest do
{XSD.datetime("2002-04-02T23:00:00+00:00"), XSD.datetime("2002-04-02T23:00:00-00:00")},
{XSD.datetime("2010-01-01T00:00:00.0000Z"), XSD.datetime("2010-01-01T00:00:00Z")},
{XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-05T00:00:00")},
{DateTimeWithTz.new("2002-04-02T12:00:00-01:00"), DateTimeWithTz.new("2002-04-02T17:00:00+04:00")},
{DateTimeWithTz.new("2002-04-02T12:00:00-01:00"),
DateTimeWithTz.new("2002-04-02T17:00:00+04:00")},
{DateTimeWithTz.new("2002-04-02T23:00:00Z"), XSD.datetime("2002-04-02T23:00:00+00:00")},
{XSD.datetime("2002-04-02T23:00:00+00:00"), DateTimeWithTz.new("2002-04-02T23:00:00-00:00")},
{XSD.datetime("2002-04-02T23:00:00+00:00"), DateTimeWithTz.new("2002-04-02T23:00:00-00:00")}
]
@unequal_datetimes [
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T17:00:00")},
@ -290,20 +297,20 @@ defmodule RDF.EqualityTest do
]
@equal_datetimes_by_coercion [
{XSD.datetime("2002-04-02T12:00:00-01:00"),
elem(DateTime.from_iso8601("2002-04-02T12:00:00-01:00"), 1)},
elem(DateTime.from_iso8601("2002-04-02T12:00:00-01:00"), 1)},
{XSD.datetime("2002-04-02T12:00:00"), ~N"2002-04-02T12:00:00"},
{XSD.datetime("2002-04-02T23:00:00Z"),
elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)},
elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)},
{XSD.datetime("2002-04-02T23:00:00+00:00"),
elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)},
elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)},
{XSD.datetime("2002-04-02T23:00:00-00:00"),
elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)},
elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)},
{XSD.datetime("2002-04-02T23:00:00-00:00"),
elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)}
elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)}
]
@unequal_datetimes_by_coercion [
{XSD.datetime("2002-04-02T12:00:00-01:00"),
elem(DateTime.from_iso8601("2002-04-02T12:00:00+00:00"), 1)}
elem(DateTime.from_iso8601("2002-04-02T12:00:00+00:00"), 1)}
]
@equal_invalid_datetimes [
{XSD.datetime("foo"), XSD.datetime("foo")},
@ -320,7 +327,7 @@ defmodule RDF.EqualityTest do
{XSD.string("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")},
# These are incomparable because of indeterminacy due to missing timezone
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T23:00:00+00:00")},
{XSD.datetime("2002-04-02T12:00:00"), DateTimeWithTz.new("2002-04-02T12:00:00Z")},
{XSD.datetime("2002-04-02T12:00:00"), DateTimeWithTz.new("2002-04-02T12:00:00Z")}
]
test "term equality", do: assert_term_equal(@term_equal_datetimes)
@ -337,17 +344,17 @@ defmodule RDF.EqualityTest do
@term_equal_dates [
{XSD.date("2002-04-02-01:00"), XSD.date("2002-04-02-01:00")},
{XSD.date("2002-04-02"), XSD.date("2002-04-02")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-02")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-02")}
]
@value_equal_dates [
{XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02+00:00")},
{XSD.date("2002-04-02Z"), XSD.date("2002-04-02+00:00")},
{XSD.date("2002-04-02Z"), XSD.date("2002-04-02-00:00")},
{XSD.date("2002-04-02"), DateWithoutTz.new("2002-04-02")},
{XSD.date("2002-04-02"), DateWithoutTz.new("2002-04-02")}
]
@unequal_dates [
{XSD.date("2002-04-01"), XSD.date("2002-04-02")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-01")},
{DateWithoutTz.new("2002-04-02"), DateWithoutTz.new("2002-04-01")}
]
@equal_dates_by_coercion [
{XSD.date("2002-04-02"), Date.from_iso8601!("2002-04-02")}
@ -357,13 +364,13 @@ defmodule RDF.EqualityTest do
]
@equal_invalid_dates [
{XSD.date("foo"), XSD.date("foo")},
{DateWithoutTz.new("foo"), DateWithoutTz.new("foo")},
{DateWithoutTz.new("foo"), DateWithoutTz.new("foo")}
]
@unequal_invalid_dates [
{XSD.date("2002.04.02"), XSD.date("2002-04-02")},
{XSD.date("foo"), XSD.date("bar")},
{DateWithoutTz.new("foo"), DateWithoutTz.new("bar")},
{XSD.date("foo"), DateWithoutTz.new("bar")},
{XSD.date("foo"), DateWithoutTz.new("bar")}
]
@incomparable_dates [
{XSD.date("2002-04-02"), XSD.string("2002-04-02")},
@ -434,17 +441,17 @@ defmodule RDF.EqualityTest do
@term_equal_times [
{XSD.time("12:00:00+01:00"), XSD.time("12:00:00+01:00")},
{XSD.time("12:00:00"), XSD.time("12:00:00")},
{CustomTime.new("00:00:00Z"), CustomTime.new("00:00:00Z")},
{CustomTime.new("00:00:00Z"), CustomTime.new("00:00:00Z")}
]
@value_equal_times [
{XSD.time("00:00:00+00:00"), XSD.time("00:00:00Z")},
{XSD.time("00:00:00+00:00"), CustomTime.new("00:00:00Z")},
{CustomTime.new("00:00:00+00:00"), CustomTime.new("00:00:00Z")},
{CustomTime.new("00:00:00+00:00"), CustomTime.new("00:00:00Z")}
]
@unequal_times [
{XSD.time("12:00:00"), XSD.time("13:00:00")},
{XSD.time("00:00:00.0000Z"), XSD.time("00:00:00Z")},
{XSD.time("00:00:00.0000Z"), CustomTime.new("00:00:00Z")},
{XSD.time("00:00:00.0000Z"), CustomTime.new("00:00:00Z")}
]
@equal_times_by_coercion [
{XSD.time("12:00:00"), Time.from_iso8601!("12:00:00")}
@ -454,11 +461,11 @@ defmodule RDF.EqualityTest do
]
@equal_invalid_times [
{XSD.time("foo"), XSD.time("foo")},
{CustomTime.new("foo"), CustomTime.new("foo")},
{CustomTime.new("foo"), CustomTime.new("foo")}
]
@unequal_invalid_times [
{XSD.time("foo"), XSD.time("bar")},
{XSD.time("foo"), CustomTime.new("bar")},
{XSD.time("foo"), CustomTime.new("bar")}
]
@incomparable_times [
{XSD.time("12:00:00"), XSD.string("12:00:00")},
@ -508,20 +515,20 @@ defmodule RDF.EqualityTest do
describe "RDF.Literal.Generics" do
@equal_literals [
{RDF.literal("foo", datatype: "http://example.com/datatype"),
RDF.literal("foo", datatype: "http://example.com/datatype")},
RDF.literal("foo", datatype: "http://example.com/datatype")}
]
@unequal_literals [
{RDF.literal("foo", datatype: "http://example.com/datatype"),
RDF.literal("bar", datatype: "http://example.com/datatype")},
RDF.literal("bar", datatype: "http://example.com/datatype")}
]
@incomparable_literals [
{RDF.literal("foo", datatype: "http://example.com/datatype1"),
RDF.literal("foo", datatype: "http://example.com/datatype2")},
RDF.literal("foo", datatype: "http://example.com/datatype2")}
]
test "equality", do: assert_term_equal @equal_literals
test "inequality", do: assert_unequal @unequal_literals
test "incomparability", do: assert_incomparable @incomparable_literals
test "equality", do: assert_term_equal(@equal_literals)
test "inequality", do: assert_unequal(@unequal_literals)
test "incomparability", do: assert_incomparable(@incomparable_literals)
end
defp assert_term_equal(examples) do

File diff suppressed because it is too large Load diff

View file

@ -3,39 +3,37 @@ defmodule RDF.IRITest do
use RDF.Vocabulary.Namespace
defvocab EX,
base_iri: "http://example.com/#",
terms: [], strict: false
defvocab EX, base_iri: "http://example.com/#", terms: [], strict: false
doctest RDF.IRI
alias RDF.IRI
@absolute_iris [
"http://www.example.com/foo/",
%IRI{value: "http://www.example.com/foo/"},
URI.parse("http://www.example.com/foo/"),
"http://www.example.com/foo#",
%IRI{value: "http://www.example.com/foo#"},
URI.parse("http://www.example.com/foo#"),
"https://en.wiktionary.org/wiki/Ῥόδος",
%IRI{value: "https://en.wiktionary.org/wiki/Ῥόδος"},
URI.parse("https://en.wiktionary.org/wiki/Ῥόδος"),
]
"http://www.example.com/foo/",
%IRI{value: "http://www.example.com/foo/"},
URI.parse("http://www.example.com/foo/"),
"http://www.example.com/foo#",
%IRI{value: "http://www.example.com/foo#"},
URI.parse("http://www.example.com/foo#"),
"https://en.wiktionary.org/wiki/Ῥόδος",
%IRI{value: "https://en.wiktionary.org/wiki/Ῥόδος"},
URI.parse("https://en.wiktionary.org/wiki/Ῥόδος")
]
@relative_iris [
"/relative/",
%IRI{value: "/relative/"},
URI.parse("/relative/"),
"/Ῥόδος/",
%IRI{value: "/Ῥόδος/"},
URI.parse("/Ῥόδος/"),
]
"/relative/",
%IRI{value: "/relative/"},
URI.parse("/relative/"),
"/Ῥόδος/",
%IRI{value: "/Ῥόδος/"},
URI.parse("/Ῥόδος/")
]
def absolute_iris, do: @absolute_iris
def relative_iris, do: @relative_iris
def valid_iris, do: @absolute_iris
def invalid_iris, do: nil # TODO:
def valid_iris, do: @absolute_iris
# TODO:
def invalid_iris, do: nil
describe "new/1" do
test "with a string" do
@ -65,7 +63,6 @@ defmodule RDF.IRITest do
end
end
describe "new!/1" do
test "with valid iris" do
Enum.each(valid_iris(), fn valid_iri ->
@ -105,7 +102,6 @@ defmodule RDF.IRITest do
end
end
describe "coerce_base/1" do
test "with a string" do
assert IRI.coerce_base("http://example.com/") == IRI.new("http://example.com/")
@ -135,7 +131,7 @@ defmodule RDF.IRITest do
end
test "with a RDF.Vocabulary.Namespace module" do
assert IRI.coerce_base(EX) == IRI.new(EX.__base_iri__)
assert IRI.coerce_base(EX) == IRI.new(EX.__base_iri__())
end
test "with a RDF.Vocabulary.Namespace module which is not loaded yet" do
@ -143,7 +139,6 @@ defmodule RDF.IRITest do
end
end
describe "valid!/1" do
test "with valid iris" do
Enum.each(valid_iris(), fn valid_iri ->
@ -180,7 +175,6 @@ defmodule RDF.IRITest do
end
end
describe "valid?/1" do
test "with valid iris" do
Enum.each(valid_iris(), fn valid_iri ->
@ -213,7 +207,6 @@ defmodule RDF.IRITest do
end
end
describe "absolute?/1" do
test "with absolute iris" do
Enum.each(absolute_iris(), fn absolute_iri ->
@ -246,7 +239,6 @@ defmodule RDF.IRITest do
end
end
describe "absolute/2" do
test "with an already absolute iri" do
for absolute_iri <- absolute_iris(),
@ -258,7 +250,7 @@ defmodule RDF.IRITest do
test "with a relative iri" do
for relative_iri <- relative_iris(), base_iri <- absolute_iris() do
assert IRI.absolute(relative_iri, base_iri) ==
IRI.merge(base_iri, relative_iri)
IRI.merge(base_iri, relative_iri)
end
end
@ -269,28 +261,25 @@ defmodule RDF.IRITest do
end
end
describe "merge/2" do
test "with a valid absolute base iri and a valid relative iri" do
for base_iri <- absolute_iris(), relative_iri <- relative_iris() do
assert IRI.merge(base_iri, relative_iri) == (
base_iri
|> to_string
|> URI.merge(to_string(relative_iri))
|> IRI.new
)
end
assert IRI.merge(base_iri, relative_iri) ==
base_iri
|> to_string
|> URI.merge(to_string(relative_iri))
|> IRI.new()
end
end
test "with a valid absolute base iri and a valid absolute iri" do
for base_iri <- absolute_iris(), absolute_iri <- absolute_iris() do
assert IRI.merge(base_iri, absolute_iri) == (
base_iri
|> to_string
|> URI.merge(to_string(absolute_iri))
|> IRI.new
)
end
assert IRI.merge(base_iri, absolute_iri) ==
base_iri
|> to_string
|> URI.merge(to_string(absolute_iri))
|> IRI.new()
end
end
test "with a relative base iri" do
@ -302,7 +291,7 @@ defmodule RDF.IRITest do
end
test "with empty fragments" do
assert IRI.merge("http://example.com/","foo#") == IRI.new("http://example.com/foo#")
assert IRI.merge("http://example.com/", "foo#") == IRI.new("http://example.com/foo#")
end
@tag skip: "TODO: proper validation"
@ -316,17 +305,16 @@ defmodule RDF.IRITest do
describe "parse/1" do
test "with absolute and relative iris" do
Enum.each(absolute_iris() ++ relative_iris(), fn iri ->
assert IRI.parse(iri) == (
iri
|> IRI.new
|> to_string()
|> URI.parse
)
assert IRI.parse(iri) ==
iri
|> IRI.new()
|> to_string()
|> URI.parse()
end)
end
test "with a resolvable atom" do
assert IRI.parse(EX.Foo) == (EX.Foo |> IRI.new |> IRI.parse)
assert IRI.parse(EX.Foo) == EX.Foo |> IRI.new() |> IRI.parse()
end
test "with empty fragments" do
@ -354,7 +342,7 @@ defmodule RDF.IRITest do
test "with IRI resolvable namespace terms" do
assert IRI.to_string(EX.Foo) == "http://example.com/#Foo"
assert IRI.to_string(EX.foo) == "http://example.com/#foo"
assert IRI.to_string(EX.foo()) == "http://example.com/#foo"
end
test "with non-resolvable atoms" do
@ -369,5 +357,4 @@ defmodule RDF.IRITest do
test "Inspect protocol implementation" do
assert inspect(IRI.new("http://example.com/")) == "~I<http://example.com/>"
end
end

View file

@ -9,152 +9,172 @@ defmodule RDF.ListTest do
use RDF.Vocabulary.Namespace
defvocab EX,
base_iri: "http://example.org/#",
terms: [], strict: false
defvocab EX, base_iri: "http://example.org/#", terms: [], strict: false
setup do
{:ok,
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>),
}
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/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))
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 "with non-blank list nodes" do
graph = Graph.new(
EX.Foo
|> RDF.first(1)
|> RDF.rest(RDF.nil))
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 == iri(EX.Foo)
end
test "with other properties on its nodes" do
assert RDF.List.new(~B<Foo>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> EX.other(EX.Property)
|> RDF.first(1)
|> RDF.rest(~B<Bar>))
~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))
~B<Bar>
|> EX.other(EX.Property2)
|> RDF.first(2)
|> RDF.rest(RDF.nil())
)
)
|> RDF.List.valid? == true
|> 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
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
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
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>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1, 2)
|> RDF.rest(RDF.nil))
~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>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>))
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>)
)
|> Graph.add(
~B<Bar>
|> RDF.first(2, 3)
|> RDF.rest(RDF.nil))
~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>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>, ~B<Baz>))
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>, ~B<Baz>)
)
|> Graph.add(
~B<Bar>
|> RDF.first(2)
|> RDF.rest(RDF.nil))
~B<Bar>
|> RDF.first(2)
|> RDF.rest(RDF.nil())
)
|> Graph.add(
~B<Baz>
|> RDF.first(3)
|> RDF.rest(RDF.nil))
~B<Baz>
|> RDF.first(3)
|> RDF.rest(RDF.nil())
)
) == nil
assert RDF.List.new(~B<Foo>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>))
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>)
)
|> Graph.add(
~B<Bar>
|> RDF.first(2)
|> RDF.rest(RDF.nil, ~B<Baz>))
~B<Bar>
|> RDF.first(2)
|> RDF.rest(RDF.nil(), ~B<Baz>)
)
|> Graph.add(
~B<Baz>
|> RDF.first(3)
|> RDF.rest(RDF.nil))
~B<Baz>
|> RDF.first(3)
|> RDF.rest(RDF.nil())
)
) == nil
end
test "when the list is cyclic" do
assert RDF.List.new(~B<Foo>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>))
~B<Foo>
|> RDF.first(1)
|> RDF.rest(~B<Bar>)
)
|> Graph.add(
~B<Bar>
|> RDF.first(2)
|> RDF.rest(~B<Foo>))
~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
@ -165,85 +185,99 @@ defmodule RDF.ListTest do
end
%{
"IRI" => iri(EX.Foo),
"IRI" => iri(EX.Foo),
"blank node" => ~B<Foo>,
"literal" => ~L<Foo>,
"string" => "Foo",
"integer" => 42,
"float" => 3.14,
"true" => true,
"false" => false,
"unresolved namespace-qualified name" => EX.Foo,
"literal" => ~L<Foo>,
"string" => "Foo",
"integer" => 42,
"float" => 3.14,
"true" => true,
"false" => false,
"unresolved namespace-qualified name" => EX.Foo
}
|> Enum.each(fn {type, element} ->
@tag element: element
test "list with #{type} element", %{element: element} do
with {bnode, graph_with_list} = one_element_list(element) do
assert RDF.List.from([element], head: bnode) ==
RDF.List.new(bnode, graph_with_list)
end
@tag element: element
test "list with #{type} element", %{element: element} do
with {bnode, graph_with_list} = one_element_list(element) do
assert RDF.List.from([element], head: bnode) ==
RDF.List.new(bnode, graph_with_list)
end
end)
end
end)
test "nested list" do
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]) == [XSD.integer(1)]
assert get_in(graph_with_list, [nested, RDF.rest]) == [RDF.nil]
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()]) == [XSD.integer(1)]
assert get_in(graph_with_list, [nested, RDF.rest()]) == [RDF.nil()]
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])
assert get_in(graph_with_list, [nested, RDF.first]) == [XSD.integer(1)]
assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest])
assert get_in(graph_with_list, [nested_second, RDF.first]) == [XSD.integer(2)]
assert get_in(graph_with_list, [nested_second, RDF.rest]) == [RDF.nil]
assert [third] = get_in(graph_with_list, [second, RDF.rest])
assert get_in(graph_with_list, [third, RDF.first]) == [~L"bar"]
assert get_in(graph_with_list, [third, RDF.rest]) == [RDF.nil]
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()])
assert get_in(graph_with_list, [nested, RDF.first()]) == [XSD.integer(1)]
assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest()])
assert get_in(graph_with_list, [nested_second, RDF.first()]) == [XSD.integer(2)]
assert get_in(graph_with_list, [nested_second, RDF.rest()]) == [RDF.nil()]
assert [third] = get_in(graph_with_list, [second, RDF.rest()])
assert get_in(graph_with_list, [third, RDF.first()]) == [~L"bar"]
assert get_in(graph_with_list, [third, RDF.rest()]) == [RDF.nil()]
end
%{
"preserve order" => [3, 2, 1],
"different types" => [1, "foo", true, false, 3.14, EX.foo, EX.Foo, ~B<Foo>],
"preserve order" => [3, 2, 1],
"different types" => [1, "foo", true, false, 3.14, EX.foo(), EX.Foo, ~B<Foo>]
}
|> Enum.each(fn {desc, list} ->
@tag list: list
test "list with multiple elements: #{desc}", %{list: list} do
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
%IRI{} ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [element]
%BlankNode{} ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [element]
%Literal{} ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [element]
element when is_boolean(element) ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [RDF.Literal.new(element)]
element when is_atom(element) ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [RDF.iri(element)]
_ ->
assert get_in(graph_with_list, [list_node, RDF.first]) == [RDF.Literal.new(element)]
end
[next] = get_in(graph_with_list, [list_node, RDF.rest])
unless next == RDF.nil do
assert %BlankNode{} = next
end
next
end
end
end)
@tag list: list
test "list with multiple elements: #{desc}", %{list: list} do
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
%IRI{} ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]
%BlankNode{} ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]
%Literal{} ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [element]
element when is_boolean(element) ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [
RDF.Literal.new(element)
]
element when is_atom(element) ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [
RDF.iri(element)
]
_ ->
assert get_in(graph_with_list, [list_node, RDF.first()]) == [
RDF.Literal.new(element)
]
end
[next] = get_in(graph_with_list, [list_node, RDF.rest()])
unless next == RDF.nil() do
assert %BlankNode{} = next
end
next
end)
end
end)
test "an enumerable" do
assert RDF.List.from(MapSet.new([42]), head: ~B<foo>) ==
RDF.List.from([42], head: ~B<foo>)
RDF.List.from([42], head: ~B<foo>)
end
test "head option with unresolved namespace-qualified name" do
@ -251,43 +285,42 @@ defmodule RDF.ListTest do
end
end
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.values(one) == [EX.element]
assert RDF.List.values(one) == [EX.element()]
end
test "list with multiple elements", %{abc: abc, ten: ten} do
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)
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]
assert RDF.List.from([EX.element()], head: EX.Foo)
|> RDF.List.values() == [EX.element()]
end
test "nested list", %{nested: nested} do
assert RDF.List.values(nested) ==
[~L"foo", [XSD.integer(1), XSD.integer(2)], ~L"bar"]
[~L"foo", [XSD.integer(1), XSD.integer(2)], ~L"bar"]
assert RDF.list(["foo", [1, 2]]) |> RDF.List.values ==
[~L"foo", [XSD.integer(1), XSD.integer(2)]]
assert RDF.list(["foo", [1, 2]]) |> RDF.List.values() ==
[~L"foo", [XSD.integer(1), XSD.integer(2)]]
assert RDF.list([[1, 2], "foo"]) |> RDF.List.values ==
[[XSD.integer(1), XSD.integer(2)], ~L"foo"]
assert RDF.list([[1, 2], "foo"]) |> RDF.List.values() ==
[[XSD.integer(1), XSD.integer(2)], ~L"foo"]
inner_list = RDF.list([1, 2], head: ~B<inner>)
assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph)
|> RDF.List.values == [~L"foo", [XSD.integer(1), XSD.integer(2)]]
|> RDF.List.values() == [~L"foo", [XSD.integer(1), XSD.integer(2)]]
end
end
describe "nodes/1" do
test "the empty list", %{empty: empty} do
assert RDF.List.nodes(empty) == []
@ -299,12 +332,12 @@ defmodule RDF.ListTest do
test "nested list", %{nested: nested} do
assert RDF.list([[1, 2, 3]], head: ~B<outer>)
|> RDF.List.nodes == [~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)
@ -324,25 +357,27 @@ defmodule RDF.ListTest do
end
test "a non-blank list node is not valid" do
assert RDF.list([EX.element], head: EX.Foo) |> RDF.List.valid? == false
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>,
assert RDF.List.new(
~B<Foo>,
Graph.new(
~B<Foo>
|> RDF.first(1)
|> RDF.rest(EX.Foo))
~B<Foo>
|> RDF.first(1)
|> RDF.rest(EX.Foo)
)
|> Graph.add(
EX.Foo
|> RDF.first(2)
|> RDF.rest(RDF.nil))
EX.Foo
|> RDF.first(2)
|> RDF.rest(RDF.nil())
)
)
|> RDF.List.valid? == false
|> 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
@ -362,33 +397,34 @@ defmodule RDF.ListTest do
end
test "unresolved namespace-qualified name" do
assert RDF.List.node?(EX.Foo,
RDF.List.from([EX.element], head: EX.Foo).graph) == 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
assert RDF.List.node?(RDF.bnode, RDF.Graph.new) == false
assert RDF.List.node?(RDF.bnode(), RDF.Graph.new()) == false
end
test "literal" do
assert RDF.List.node?(~L"Foo", RDF.Graph.new) == false
assert RDF.List.node?(42, RDF.Graph.new) == false
assert RDF.List.node?(true, RDF.Graph.new) == false
assert RDF.List.node?(false, RDF.Graph.new) == false
assert RDF.List.node?(nil, RDF.Graph.new) == false
assert RDF.List.node?(~L"Foo", RDF.Graph.new()) == false
assert RDF.List.node?(42, RDF.Graph.new()) == false
assert RDF.List.node?(true, RDF.Graph.new()) == false
assert RDF.List.node?(false, RDF.Graph.new()) == false
assert RDF.List.node?(nil, RDF.Graph.new()) == false
end
test "non-list node" do
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar, EX.Baz})) == false
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, EX.bar(), EX.Baz})) == false
end
test "incomplete list nodes" do
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first, EX.Baz})) == false
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest, RDF.nil})) == false
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.first(), EX.Baz})) == false
assert RDF.List.node?(EX.Foo, RDF.Graph.new({EX.Foo, RDF.rest(), RDF.nil()})) == false
end
end
describe "Enumerable.reduce" do
test "the empty list", %{empty: empty} do
assert Enum.reduce(empty, [], fn description, acc -> [description | acc] end) == []
@ -396,23 +432,19 @@ defmodule RDF.ListTest do
test "a valid list", %{one: one} do
assert [one.graph[one.head]] ==
Enum.reduce(one, [], fn description, acc -> [description | acc] end)
Enum.reduce(one, [], fn description, acc -> [description | acc] end)
end
end
defp one_element_list(element),
do: one_element_list(element, RDF.bnode)
do: one_element_list(element, RDF.bnode())
defp one_element_list(element, bnode) do
{bnode,
Graph.new(
bnode
|> RDF.first(element)
|> RDF.rest(RDF.nil)
)
}
Graph.new(
bnode
|> RDF.first(element)
|> RDF.rest(RDF.nil())
)}
end
end

View file

@ -32,11 +32,12 @@ defmodule RDF.Literal.Datatype.RegistryTest do
gMonth
gMonthDay
]
|> Enum.map(fn xsd_datatype_name -> RDF.iri(NS.XSD.__base_iri__ <> xsd_datatype_name) end)
|> Enum.map(fn xsd_datatype_name ->
RDF.iri(NS.XSD.__base_iri__() <> xsd_datatype_name)
end)
@supported_xsd_datatypes RDF.NS.XSD.__iris__() -- @unsupported_xsd_datatypes
describe "datatype/1" do
test "builtin datatypes" do
Enum.each(Datatype.Registry.builtin_datatypes(), fn datatype ->
@ -60,7 +61,7 @@ defmodule RDF.Literal.Datatype.RegistryTest do
end
test "with IRI of custom datatype" do
assert Age == Datatype.Registry.datatype(Age.id)
assert Age == Datatype.Registry.datatype(Age.id())
end
test "with namespace terms" do
@ -72,7 +73,9 @@ defmodule RDF.Literal.Datatype.RegistryTest do
assert XSD.Integer == Datatype.Registry.datatype(XSD.integer(42))
assert XSD.Byte == Datatype.Registry.datatype(XSD.byte(42))
assert RDF.LangString == Datatype.Registry.datatype(~L"foo"en)
assert RDF.Literal.Generic == Datatype.Registry.datatype(RDF.literal("foo", datatype: "http://example.com"))
assert RDF.Literal.Generic ==
Datatype.Registry.datatype(RDF.literal("foo", datatype: "http://example.com"))
end
end
@ -100,7 +103,6 @@ defmodule RDF.Literal.Datatype.RegistryTest do
refute Datatype.Registry.xsd_datatype?(42)
end
test "numeric_datatype?/1" do
assert Datatype.Registry.numeric_datatype?(XSD.integer(42))
assert Datatype.Registry.numeric_datatype?(XSD.byte(42))

View file

@ -12,29 +12,31 @@ defmodule RDF.LiteralTest do
alias RDF.NS
@examples %{
XSD.String => ["foo"],
XSD.String => ["foo"],
XSD.Integer => [42],
XSD.Double => [3.14],
XSD.Double => [3.14],
XSD.Decimal => [Decimal.from_float(3.14)],
XSD.Boolean => [true, false],
XSD.Boolean => [true, false]
}
describe "new/1" do
Enum.each @examples, fn {datatype, example_values} ->
Enum.each(@examples, fn {datatype, example_values} ->
@tag example: %{datatype: datatype, values: example_values}
test "coercion from #{datatype |> Module.split |> List.last |> to_string}", %{example: example} do
Enum.each example.values, fn example_value ->
test "coercion from #{datatype |> Module.split() |> List.last() |> to_string}", %{
example: example
} do
Enum.each(example.values, fn example_value ->
assert Literal.new(example_value) == example.datatype.new(example_value)
assert Literal.new!(example_value) == example.datatype.new!(example_value)
end
end)
end
end
end)
test "with builtin datatype literals" do
Enum.each Datatype.Registry.builtin_datatypes(), fn datatype ->
Enum.each(Datatype.Registry.builtin_datatypes(), fn datatype ->
datatype_literal = datatype.new("foo").literal
assert %Literal{literal: ^datatype_literal} = Literal.new(datatype_literal)
end
end)
end
test "with custom datatype literals" do
@ -44,44 +46,45 @@ defmodule RDF.LiteralTest do
test "when options without datatype given" do
assert Literal.new(true, []) == XSD.Boolean.new(true)
assert Literal.new(42, []) == XSD.Integer.new(42)
assert Literal.new(42, []) == XSD.Integer.new(42)
assert Literal.new!(true, []) == XSD.Boolean.new!(true)
assert Literal.new!(42, []) == XSD.Integer.new!(42)
assert Literal.new!(42, []) == XSD.Integer.new!(42)
end
end
describe "typed construction" do
test "boolean" do
assert Literal.new(true, datatype: NS.XSD.boolean) == XSD.Boolean.new(true)
assert Literal.new(false, datatype: NS.XSD.boolean) == XSD.Boolean.new(false)
assert Literal.new("true", datatype: NS.XSD.boolean) == XSD.Boolean.new("true")
assert Literal.new("false", datatype: NS.XSD.boolean) == XSD.Boolean.new("false")
assert Literal.new(true, datatype: NS.XSD.boolean()) == XSD.Boolean.new(true)
assert Literal.new(false, datatype: NS.XSD.boolean()) == XSD.Boolean.new(false)
assert Literal.new("true", datatype: NS.XSD.boolean()) == XSD.Boolean.new("true")
assert Literal.new("false", datatype: NS.XSD.boolean()) == XSD.Boolean.new("false")
end
test "integer" do
assert Literal.new(42, datatype: NS.XSD.integer) == XSD.Integer.new(42)
assert Literal.new("42", datatype: NS.XSD.integer) == XSD.Integer.new("42")
assert Literal.new(42, datatype: NS.XSD.integer()) == XSD.Integer.new(42)
assert Literal.new("42", datatype: NS.XSD.integer()) == XSD.Integer.new("42")
end
test "double" do
assert Literal.new(3.14, datatype: NS.XSD.double) == XSD.Double.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.double) == XSD.Double.new("3.14")
assert Literal.new(3.14, datatype: NS.XSD.double()) == XSD.Double.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.double()) == XSD.Double.new("3.14")
end
test "decimal" do
assert Literal.new(3.14, datatype: NS.XSD.decimal) == XSD.Decimal.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.decimal) == XSD.Decimal.new("3.14")
assert Literal.new(Decimal.from_float(3.14), datatype: NS.XSD.decimal) ==
assert Literal.new(3.14, datatype: NS.XSD.decimal()) == XSD.Decimal.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.decimal()) == XSD.Decimal.new("3.14")
assert Literal.new(Decimal.from_float(3.14), datatype: NS.XSD.decimal()) ==
XSD.Decimal.new(Decimal.from_float(3.14))
end
test "unsignedInt" do
assert Literal.new(42, datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new(42)
assert Literal.new("42", datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new("42")
assert Literal.new(42, datatype: NS.XSD.unsignedInt()) == XSD.UnsignedInt.new(42)
assert Literal.new("42", datatype: NS.XSD.unsignedInt()) == XSD.UnsignedInt.new("42")
end
test "string" do
assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo")
assert Literal.new("foo", datatype: NS.XSD.string()) == XSD.String.new("foo")
end
test "registered custom datatype" do
@ -106,17 +109,19 @@ defmodule RDF.LiteralTest do
end
test "construction of an other than rdf:langString typed and language-tagged literal fails" do
assert Literal.new("Eule", datatype: RDF.langString, language: "de") ==
assert Literal.new("Eule", datatype: RDF.langString(), language: "de") ==
LangString.new("Eule", language: "de")
assert_raise ArgumentError, fn ->
Literal.new("Eule", datatype: NS.XSD.string, language: "de")
Literal.new("Eule", datatype: NS.XSD.string(), language: "de")
end
end
test "construction of a rdf:langString works, but results in an invalid literal" do
assert Literal.new("Eule", datatype: RDF.langString) == LangString.new("Eule", [])
assert Literal.new("Eule", datatype: RDF.langString()) == LangString.new("Eule", [])
assert_raise RDF.Literal.InvalidError, fn ->
Literal.new!("Eule", datatype: RDF.langString)
Literal.new!("Eule", datatype: RDF.langString())
end
end
end
@ -197,7 +202,10 @@ defmodule RDF.LiteralTest do
assert ~L"foo"en |> Literal.is_a?(RDF.LangString)
assert XSD.integer(42) |> Literal.is_a?(XSD.Integer)
assert XSD.byte(42) |> Literal.is_a?(XSD.Integer)
assert RDF.literal("foo", datatype: "http://example.com/dt") |> RDF.Literal.is_a?(RDF.Literal.Generic)
assert RDF.literal("foo", datatype: "http://example.com/dt")
|> RDF.Literal.is_a?(RDF.Literal.Generic)
refute XSD.float(3.14) |> Literal.is_a?(XSD.Integer)
end
@ -230,91 +238,94 @@ defmodule RDF.LiteralTest do
end
describe "has_datatype?" do
Enum.each literals(~W[all_simple all_plain_lang]a), fn literal ->
Enum.each(literals(~W[all_simple all_plain_lang]a), fn literal ->
@tag literal: literal
test "#{inspect literal} has no datatype", %{literal: literal} do
test "#{inspect(literal)} has no datatype", %{literal: literal} do
refute Literal.has_datatype?(literal)
end
end
end)
Enum.each literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
Enum.each(literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} has a datatype", %{literal: literal} do
test "Literal for #{inspect(literal)} has a datatype", %{literal: literal} do
assert Literal.has_datatype?(literal)
end
end
end)
end
describe "plain?" do
Enum.each literals(:all_plain), fn literal ->
Enum.each(literals(:all_plain), fn literal ->
@tag literal: literal
test "#{inspect literal} is plain", %{literal: literal} do
test "#{inspect(literal)} is plain", %{literal: literal} do
assert Literal.plain?(literal)
end
end
Enum.each literals(:all) -- literals(:all_plain), fn literal ->
end)
Enum.each(literals(:all) -- literals(:all_plain), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} is not plain", %{literal: literal} do
test "Literal for #{inspect(literal)} is not plain", %{literal: literal} do
refute Literal.plain?(literal)
end
end
end)
end
describe "simple?" do
Enum.each literals(:all_simple), fn literal ->
Enum.each(literals(:all_simple), fn literal ->
@tag literal: literal
test "#{inspect literal} is simple", %{literal: literal} do
test "#{inspect(literal)} is simple", %{literal: literal} do
assert Literal.simple?(literal)
end
end
Enum.each literals(:all) -- literals(:all_simple), fn literal ->
end)
Enum.each(literals(:all) -- literals(:all_simple), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} is not simple", %{literal: literal} do
test "Literal for #{inspect(literal)} is not simple", %{literal: literal} do
refute Literal.simple?(literal)
end
end
end)
end
describe "datatype_id/1" do
Enum.each literals(:all_simple), fn literal ->
Enum.each(literals(:all_simple), fn literal ->
@tag literal: literal
test "simple literal #{inspect literal} has datatype xsd:string", %{literal: literal} do
assert Literal.datatype_id(literal) == NS.XSD.string
test "simple literal #{inspect(literal)} has datatype xsd:string", %{literal: literal} do
assert Literal.datatype_id(literal) == NS.XSD.string()
end
end
end)
%{
123 => "integer",
true => "boolean",
false => "boolean",
9223372036854775807 => "integer",
3.1415 => "double",
~D[2017-04-13] => "date",
~N[2017-04-14 15:32:07] => "dateTime",
~T[01:02:03] => "time"
123 => "integer",
true => "boolean",
false => "boolean",
9_223_372_036_854_775_807 => "integer",
3.1415 => "double",
~D[2017-04-13] => "date",
~N[2017-04-14 15:32:07] => "dateTime",
~T[01:02:03] => "time"
}
|> Enum.each(fn {value, type} ->
@tag data: %{literal: literal = Literal.new(value), type: type}
test "Literal for #{inspect literal} has datatype xsd:#{type}",
%{data: %{literal: literal, type: type}} do
assert Literal.datatype_id(literal) == apply(NS.XSD, String.to_atom(type), [])
end
end)
@tag data: %{literal: literal = Literal.new(value), type: type}
test "Literal for #{inspect(literal)} has datatype xsd:#{type}",
%{data: %{literal: literal, type: type}} do
assert Literal.datatype_id(literal) == apply(NS.XSD, String.to_atom(type), [])
end
end)
end
describe "language" do
Enum.each literals(:all_plain_lang), fn literal ->
Enum.each(literals(:all_plain_lang), fn literal ->
@tag literal: literal
test "#{inspect literal} has correct language", %{literal: literal} do
test "#{inspect(literal)} has correct language", %{literal: literal} do
assert Literal.language(literal) == "en"
end
end
Enum.each literals(:all) -- literals(:all_plain_lang), fn literal ->
end)
Enum.each(literals(:all) -- literals(:all_plain_lang), fn literal ->
@tag literal: literal
test "Literal for #{inspect literal} has no language", %{literal: literal} do
test "Literal for #{inspect(literal)} has no language", %{literal: literal} do
assert is_nil(Literal.language(literal))
end
end
end)
test "with RDF.LangString literal" do
assert Literal.new("Upper", language: "en") |> Literal.language() == "en"
@ -358,15 +369,17 @@ defmodule RDF.LiteralTest do
test "with XSD.Datatype literal" do
[
XSD.String.new("foo"),
XSD.Byte.new(42),
XSD.Byte.new(42)
]
|> Enum.each(fn
canonical_literal ->
assert Literal.canonical(canonical_literal) == canonical_literal
end)
assert XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42)
assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> XSD.Double.canonical()
assert Literal.new(3.14) |> Literal.canonical() ==
Literal.new(3.14) |> XSD.Double.canonical()
end
test "with RDF.LangString literal" do
@ -425,16 +438,24 @@ defmodule RDF.LiteralTest do
end
test "with RDF.LangString literal" do
assert Literal.equal_value?(Literal.new("foo", language: "en"),
Literal.new("foo", language: "en")) == true
assert Literal.equal_value?(
Literal.new("foo", language: "en"),
Literal.new("foo", language: "en")
) == true
assert Literal.equal_value?(Literal.new("foo", language: "en"), Literal.new("foo")) == nil
end
test "with generic literal" do
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo", datatype: "http://example.com/dt")) == true
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo")) == nil
assert Literal.equal_value?(
Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo", datatype: "http://example.com/dt")
) == true
assert Literal.equal_value?(
Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("foo")
) == nil
end
end
@ -445,57 +466,62 @@ defmodule RDF.LiteralTest do
end
test "with RDF.LangString literal" do
assert Literal.compare(Literal.new("foo", language: "en"),
Literal.new("bar", language: "en")) == :gt
assert Literal.compare(
Literal.new("foo", language: "en"),
Literal.new("bar", language: "en")
) == :gt
end
test "with generic literal" do
assert Literal.compare(Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("bar", datatype: "http://example.com/dt")) == :gt
assert Literal.compare(
Literal.new("foo", datatype: "http://example.com/dt"),
Literal.new("bar", datatype: "http://example.com/dt")
) == :gt
end
end
@poem XSD.String.new """
<poem author="Wilhelm Busch">
Kaum hat dies der Hahn gesehen,
Fängt er auch schon an zu krähen:
Kikeriki! Kikikerikih!!
Tak, tak, tak! - da kommen sie.
</poem>
"""
@poem XSD.String.new("""
<poem author="Wilhelm Busch">
Kaum hat dies der Hahn gesehen,
Fängt er auch schon an zu krähen:
Kikeriki! Kikikerikih!!
Tak, tak, tak! - da kommen sie.
</poem>
""")
describe "matches?" do
test "without flags" do
[
{~L"abracadabra", ~L"bra", true},
{~L"abracadabra", ~L"bra", true},
{~L"abracadabra", ~L"^a.*a$", true},
{~L"abracadabra", ~L"^bra", false},
{@poem, ~L"Kaum.*krähen", false},
{~L"abracadabra", ~L"^bra", false},
{@poem, ~L"Kaum.*krähen", false},
{@poem, ~L"^Kaum.*gesehen,$", false},
{~L"foobar", ~L"foo$", false},
{~L"foobar", ~L"foo$", false},
{~L"noe\u0308l", ~L"noe\\u0308l", true},
{~L"noe\\u0308l", ~L"noe\\\\u0308l", true},
{~L"\u{01D4B8}", ~L"\\U0001D4B8", true},
{~L"\\U0001D4B8", ~L"\\\U0001D4B8", true},
{~L"abracadabra"en, ~L"bra", true},
{"abracadabra", "bra", true},
{XSD.Integer.new("42"), ~L"4", true},
{XSD.Integer.new("42"), ~L"en", false},
{"abracadabra", "bra", true},
{XSD.Integer.new("42"), ~L"4", true},
{XSD.Integer.new("42"), ~L"en", false}
]
|> Enum.each(fn {literal, pattern, expected_result} ->
result = Literal.matches?(literal, pattern)
assert result == expected_result,
"expected RDF.Literal.matches?(#{inspect literal}, #{inspect pattern}) to return #{inspect expected_result}, but got #{result}"
"expected RDF.Literal.matches?(#{inspect(literal)}, #{inspect(pattern)}) to return #{
inspect(expected_result)
}, but got #{result}"
end)
end
test "with flags" do
[
{@poem, ~L"Kaum.*krähen", ~L"s", true},
{@poem, ~L"Kaum.*krähen", ~L"s", true},
{@poem, ~L"^Kaum.*gesehen,$", ~L"m", true},
{@poem, ~L"kiki", ~L"i", true},
{@poem, ~L"kiki", ~L"i", true}
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Literal.matches?(literal, pattern, flags) == result
@ -504,13 +530,13 @@ defmodule RDF.LiteralTest do
test "with q flag" do
[
{~L"abcd", ~L".*", ~L"q", false},
{~L"abcd", ~L".*", ~L"q", false},
{~L"Mr. B. Obama", ~L"B. OBAMA", ~L"iq", true},
# If the q flag is used together with the m, s, or x flag, that flag has no effect.
{~L"abcd", ~L".*", ~L"mq", true},
{~L"abcd", ~L".*", ~L"qim", true},
{~L"abcd", ~L".*", ~L"xqm", true},
{~L"abcd", ~L".*", ~L"mq", true},
{~L"abcd", ~L".*", ~L"qim", true},
{~L"abcd", ~L".*", ~L"xqm", true}
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Literal.matches?(literal, pattern, flags) == result
@ -523,10 +549,13 @@ defmodule RDF.LiteralTest do
assert XSD.string("foo")
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
XSD.string("foobar")
assert XSD.integer(1) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
XSD.integer(2)
assert XSD.byte(42) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
XSD.byte(43)
assert XSD.integer(1)
|> Literal.update(fn i when is_integer(i) -> "0" <> to_string(i) end) ==
XSD.integer("01")
@ -546,7 +575,7 @@ defmodule RDF.LiteralTest do
test "with as: :lexical opt it passes the lexical form" do
assert XSD.integer(1)
|> Literal.update(fn i when is_binary(i) -> "0" <> i end, as: :lexical) ==
|> Literal.update(fn i when is_binary(i) -> "0" <> i end, as: :lexical) ==
XSD.integer("01")
end
end

View file

@ -2,5 +2,4 @@ defmodule RDF.NamespaceTest do
use ExUnit.Case
doctest RDF.Namespace
end

View file

@ -7,148 +7,164 @@ defmodule RDF.NQuads.DecoderTest do
import RDF.Sigils
use RDF.Vocabulary.Namespace
defvocab EX,
base_iri: "http://example.org/#",
terms: [], strict: false
defvocab P,
base_iri: "http://www.perceive.net/schemas/relationship/",
terms: [], strict: false
defvocab EX, base_iri: "http://example.org/#", terms: [], strict: false
defvocab P, base_iri: "http://www.perceive.net/schemas/relationship/", terms: [], strict: false
test "an empty string is deserialized to an empty graph" do
assert RDF.NQuads.Decoder.decode!("") == Dataset.new
assert RDF.NQuads.Decoder.decode!(" \n\r\r\n ") == Dataset.new
assert RDF.NQuads.Decoder.decode!("") == Dataset.new()
assert RDF.NQuads.Decoder.decode!(" \n\r\r\n ") == Dataset.new()
end
test "decoding comments" do
assert RDF.NQuads.Decoder.decode!("# just a comment") == Dataset.new
assert RDF.NQuads.Decoder.decode!("# just a comment") == Dataset.new()
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> _:1 <http://example.org/#G>. # a comment
""") == Dataset.new({EX.S, EX.p, RDF.bnode("1"), EX.G})
<http://example.org/#S> <http://example.org/#p> _:1 <http://example.org/#G>. # a comment
""") == Dataset.new({EX.S, EX.p(), RDF.bnode("1"), EX.G})
assert RDF.NQuads.Decoder.decode!("""
# a comment
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> <http://example.org/#G>.
""") == Dataset.new({EX.S, EX.p, EX.O, EX.G})
# a comment
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> <http://example.org/#G>.
""") == Dataset.new({EX.S, EX.p(), EX.O, EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> <http://example.org/#G>.
# a comment
""") == Dataset.new({EX.S, EX.p, EX.O, EX.G})
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> <http://example.org/#G>.
# a comment
""") == Dataset.new({EX.S, EX.p(), EX.O, EX.G})
assert RDF.NQuads.Decoder.decode!("""
# Header line 1
# Header line 2
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
# 1st comment
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> . # 2nd comment
# last comment
""") == Dataset.new([
{EX.S1, EX.p1, EX.O1, EX.G},
{EX.S1, EX.p2, EX.O2},
])
# Header line 1
# Header line 2
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
# 1st comment
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> . # 2nd comment
# last comment
""") ==
Dataset.new([
{EX.S1, EX.p1(), EX.O1, EX.G},
{EX.S1, EX.p2(), EX.O2}
])
end
test "empty lines" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman> .
""") == Dataset.new({EX.spiderman, P.enemyOf, EX.green_goblin, ~I<http://example.org/graphs/spiderman>})
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman> .
""") ==
Dataset.new(
{EX.spiderman(), P.enemyOf(), EX.green_goblin(),
~I<http://example.org/graphs/spiderman>}
)
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman> .
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman> .
""") == Dataset.new({EX.spiderman, P.enemyOf, EX.green_goblin, ~I<http://example.org/graphs/spiderman>})
""") ==
Dataset.new(
{EX.spiderman(), P.enemyOf(), EX.green_goblin(),
~I<http://example.org/graphs/spiderman>}
)
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
""") == Dataset.new([
{EX.S1, EX.p1, EX.O1},
{EX.S1, EX.p2, EX.O2, EX.G},
])
""") ==
Dataset.new([
{EX.S1, EX.p1(), EX.O1},
{EX.S1, EX.p2(), EX.O2, EX.G}
])
end
test "decoding a single statement with iris" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> .
""") == Dataset.new({EX.spiderman, P.enemyOf, EX.green_goblin})
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> .
""") == Dataset.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman>.
""") == Dataset.new({EX.spiderman, P.enemyOf, EX.green_goblin, ~I<http://example.org/graphs/spiderman>})
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> <http://example.org/graphs/spiderman>.
""") ==
Dataset.new(
{EX.spiderman(), P.enemyOf(), EX.green_goblin(),
~I<http://example.org/graphs/spiderman>}
)
end
test "decoding a single statement with a blank node" do
assert RDF.NQuads.Decoder.decode!("""
_:foo <http://example.org/#p> <http://example.org/#O> <http://example.org/#G> .
""") == Dataset.new({RDF.bnode("foo"), EX.p, EX.O, EX.G})
_:foo <http://example.org/#p> <http://example.org/#O> <http://example.org/#G> .
""") == Dataset.new({RDF.bnode("foo"), EX.p(), EX.O, EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> _:1 <http://example.org/#G> .
""") == Dataset.new({EX.S, EX.p, RDF.bnode("1"), EX.G})
<http://example.org/#S> <http://example.org/#p> _:1 <http://example.org/#G> .
""") == Dataset.new({EX.S, EX.p(), RDF.bnode("1"), EX.G})
assert RDF.NQuads.Decoder.decode!("""
_:foo <http://example.org/#p> _:bar <http://example.org/#G> .
""") == Dataset.new({RDF.bnode("foo"), EX.p, RDF.bnode("bar"), EX.G})
_:foo <http://example.org/#p> _:bar <http://example.org/#G> .
""") == Dataset.new({RDF.bnode("foo"), EX.p(), RDF.bnode("bar"), EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> _:1 _:G .
""") == Dataset.new({EX.S, EX.p, RDF.bnode("1"), RDF.bnode("G")})
<http://example.org/#S> <http://example.org/#p> _:1 _:G .
""") == Dataset.new({EX.S, EX.p(), RDF.bnode("1"), RDF.bnode("G")})
end
test "decoding a single statement with an untyped string literal" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/realname> "Peter Parker" <http://example.org/#G> .
""") == Dataset.new({EX.spiderman, P.realname, RDF.literal("Peter Parker"), EX.G})
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/realname> "Peter Parker" <http://example.org/#G> .
""") == Dataset.new({EX.spiderman(), P.realname(), RDF.literal("Peter Parker"), EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/realname> "Peter Parker" .
""") == Dataset.new({EX.spiderman, P.realname, RDF.literal("Peter Parker")})
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/realname> "Peter Parker" .
""") == Dataset.new({EX.spiderman(), P.realname(), RDF.literal("Peter Parker")})
end
test "decoding a single statement with a typed literal" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://example.org/#p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/#G> .
""") == Dataset.new({EX.spiderman, EX.p, RDF.literal(42), EX.G})
<http://example.org/#spiderman> <http://example.org/#p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> <http://example.org/#G> .
""") == Dataset.new({EX.spiderman(), EX.p(), RDF.literal(42), EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#spiderman> <http://example.org/#p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> .
""") == Dataset.new({EX.spiderman, EX.p, RDF.literal(42)})
<http://example.org/#spiderman> <http://example.org/#p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> .
""") == Dataset.new({EX.spiderman(), EX.p(), RDF.literal(42)})
end
test "decoding a single statement with a language tagged literal" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> "foo"@en <http://example.org/#G> .
""") == Dataset.new({EX.S, EX.p, RDF.literal("foo", language: "en"), EX.G})
<http://example.org/#S> <http://example.org/#p> "foo"@en <http://example.org/#G> .
""") == Dataset.new({EX.S, EX.p(), RDF.literal("foo", language: "en"), EX.G})
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> "foo"@en .
""") == Dataset.new({EX.S, EX.p, RDF.literal("foo", language: "en")})
<http://example.org/#S> <http://example.org/#p> "foo"@en .
""") == Dataset.new({EX.S, EX.p(), RDF.literal("foo", language: "en")})
end
test "decoding multiple statements" do
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
""") == Dataset.new([
{EX.S1, EX.p1, EX.O1, EX.G},
{EX.S1, EX.p2, EX.O2, EX.G},
])
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
<http://example.org/#S2> <http://example.org/#p3> <http://example.org/#O3> <http://example.org/#G> .
<http://example.org/#S2> <http://example.org/#p3> <http://example.org/#O3> .
""") == Dataset.new([
{EX.S1, EX.p1, EX.O1, EX.G},
{EX.S1, EX.p2, EX.O2, EX.G},
{EX.S2, EX.p3, EX.O3, EX.G},
{EX.S2, EX.p3, EX.O3}
])
end
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
""") ==
Dataset.new([
{EX.S1, EX.p1(), EX.O1, EX.G},
{EX.S1, EX.p2(), EX.O2, EX.G}
])
assert RDF.NQuads.Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#G> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> <http://example.org/#G> .
<http://example.org/#S2> <http://example.org/#p3> <http://example.org/#O3> <http://example.org/#G> .
<http://example.org/#S2> <http://example.org/#p3> <http://example.org/#O3> .
""") ==
Dataset.new([
{EX.S1, EX.p1(), EX.O1, EX.G},
{EX.S1, EX.p2(), EX.O2, EX.G},
{EX.S2, EX.p3(), EX.O3, EX.G},
{EX.S2, EX.p3(), EX.O3}
])
end
end

Some files were not shown because too many files have changed in this diff Show more