Re-integrate XSD.ex

It turned out that the costs of separating the XSD datatypes are too high
and probably not worth the effort, since with its limited scope
probably nobody would want to use XSD.ex outside of the RDF.ex context
anyway.
This commit is contained in:
Marcel Otto 2020-05-05 23:58:44 +02:00
parent adb1c12634
commit 5819eec0cf
94 changed files with 6990 additions and 1337 deletions

View file

@ -47,7 +47,7 @@ defmodule RDF do
@standard_prefixes PrefixMap.new( @standard_prefixes PrefixMap.new(
xsd: IRI.new(XSD.namespace()), xsd: xsd_iri_base(),
rdf: rdf_iri_base(), rdf: rdf_iri_base(),
rdfs: rdfs_iri_base() rdfs: rdfs_iri_base()
) )
@ -138,7 +138,7 @@ defmodule RDF do
false false
iex> RDF.resource?(RDF.bnode) iex> RDF.resource?(RDF.bnode)
true true
iex> RDF.resource?(RDF.integer(42)) iex> RDF.resource?(RDF.XSD.integer(42))
false false
iex> RDF.resource?(42) iex> RDF.resource?(42)
false false
@ -171,7 +171,7 @@ defmodule RDF do
false false
iex> RDF.term?(RDF.bnode) iex> RDF.term?(RDF.bnode)
true true
iex> RDF.term?(RDF.integer(42)) iex> RDF.term?(RDF.XSD.integer(42))
true true
iex> RDF.term?(42) iex> RDF.term?(42)
false false
@ -208,6 +208,11 @@ defmodule RDF do
defdelegate literal(value), to: Literal, as: :new defdelegate literal(value), to: Literal, as: :new
defdelegate literal(value, opts), to: Literal, as: :new defdelegate literal(value, opts), to: Literal, as: :new
def literal?(%Literal{}), do: true
def literal?(%RDF.Literal.Generic{}), do: true
def literal?(%datatype{}), do: Literal.Datatype.Registry.datatype?(datatype)
def literal?(_), do: false
defdelegate triple(s, p, o), to: Triple, as: :new defdelegate triple(s, p, o), to: Triple, as: :new
defdelegate triple(tuple), to: Triple, as: :new defdelegate triple(tuple), to: Triple, as: :new
@ -237,24 +242,11 @@ defmodule RDF do
def list(head, %Graph{} = graph), do: RDF.List.new(head, graph) 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)
for datatype <- RDF.Literal.Datatype.Registry.datatypes() -- [RDF.LangString] do
defdelegate unquote(String.to_atom(datatype.name))(value), to: datatype, as: :new
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
end
end
defdelegate langString(value, opts), to: RDF.LangString, as: :new
defdelegate lang_string(value, opts), to: RDF.LangString, as: :new
defdelegate datetime(value), to: RDF.XSD.DateTime, as: :new
defdelegate datetime(value, opts), to: RDF.XSD.DateTime, as: :new
defdelegate prefix_map(prefixes), to: RDF.PrefixMap, as: :new defdelegate prefix_map(prefixes), to: RDF.PrefixMap, 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 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
defdelegate unquote(term)(s, o), to: RDF.NS.RDF defdelegate unquote(term)(s, o), to: RDF.NS.RDF
@ -264,10 +256,8 @@ defmodule RDF do
defdelegate unquote(term)(s, o1, o2, o3, o4, o5), to: RDF.NS.RDF defdelegate unquote(term)(s, o1, o2, o3, o4, o5), to: RDF.NS.RDF
end end
defdelegate unquote(:true)(), to: RDF.XSD.Boolean.Value
defdelegate unquote(:false)(), to: RDF.XSD.Boolean.Value
defdelegate langString(), to: RDF.NS.RDF defdelegate langString(), to: RDF.NS.RDF
defdelegate lang_string(), to: RDF.NS.RDF, as: :langString
defdelegate unquote(nil)(), to: RDF.NS.RDF defdelegate unquote(nil)(), to: RDF.NS.RDF
defdelegate __base_iri__(), to: RDF.NS.RDF defdelegate __base_iri__(), to: RDF.NS.RDF

View file

@ -759,7 +759,7 @@ defmodule RDF.Dataset do
iex> [ iex> [
...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>}, ...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>},
...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), } ...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.XSD.integer(42), }
...> ] ...> ]
...> |> RDF.Dataset.new() ...> |> RDF.Dataset.new()
...> |> RDF.Dataset.values() ...> |> RDF.Dataset.values()
@ -774,7 +774,7 @@ defmodule RDF.Dataset do
iex> [ iex> [
...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>}, ...> {~I<http://example.com/S>, ~I<http://example.com/p>, ~L"Foo", ~I<http://example.com/Graph>},
...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), } ...> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.XSD.integer(42), }
...> ] ...> ]
...> |> RDF.Dataset.new() ...> |> RDF.Dataset.new()
...> |> RDF.Dataset.values(fn ...> |> RDF.Dataset.values(fn

View file

@ -786,7 +786,7 @@ defmodule RDF.Graph do
iex> [ iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"}, ...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)} ...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.XSD.integer(42)}
...> ] ...> ]
...> |> RDF.Graph.new() ...> |> RDF.Graph.new()
...> |> RDF.Graph.values() ...> |> RDF.Graph.values()
@ -797,7 +797,7 @@ defmodule RDF.Graph do
iex> [ iex> [
...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"}, ...> {~I<http://example.com/S1>, ~I<http://example.com/p>, ~L"Foo"},
...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.integer(42)} ...> {~I<http://example.com/S2>, ~I<http://example.com/p>, RDF.XSD.integer(42)}
...> ] ...> ]
...> |> RDF.Graph.new() ...> |> RDF.Graph.new()
...> |> RDF.Graph.values(fn ...> |> RDF.Graph.values(fn

View file

@ -8,8 +8,6 @@ defmodule RDF.Literal do
alias RDF.{IRI, LangString} alias RDF.{IRI, LangString}
alias RDF.Literal.{Generic, Datatype} alias RDF.Literal.{Generic, Datatype}
import RDF.Literal.Helper.Macros
@type t :: %__MODULE__{:literal => Datatype.literal()} @type t :: %__MODULE__{:literal => Datatype.literal()}
@rdf_lang_string RDF.Utils.Bootstrapping.rdf_iri("langString") @rdf_lang_string RDF.Utils.Bootstrapping.rdf_iri("langString")
@ -17,52 +15,23 @@ defmodule RDF.Literal do
@doc """ @doc """
Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype. Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype.
See `coerce/1` for applied mapping of Elixir types to XSD datatypes.
Note: The `RDF.literal` function is a shortcut to this function. Note: The `RDF.literal` function is a shortcut to this function.
The following mapping of Elixir types to XSD datatypes is applied:
| Elixir datatype | XSD datatype |
| :-------------- | :------------- |
| `string` | `xsd:string` |
| `boolean` | `xsd:boolean` |
| `integer` | `xsd:integer` |
| `float` | `xsd:double` |
| `Decimal` | `xsd:decimal` |
| `Time` | `xsd:time` |
| `Date` | `xsd:date` |
| `DateTime` | `xsd:dateTime` |
| `NaiveDateTime` | `xsd:dateTime` |
| `URI` | `xsd:AnyURI` |
## Examples ## Examples
iex> RDF.Literal.new(42) iex> RDF.Literal.new(42)
%RDF.Literal{literal: %XSD.Integer{value: 42}} %RDF.Literal{literal: %RDF.XSD.Integer{value: 42}}
""" """
@spec new(t | any) :: t | nil @spec new(t | any) :: t | nil
def new(value)
def new(%__MODULE__{} = literal), do: literal
def new(value) when is_binary(value), do: RDF.XSD.String.new(value)
def new(value) when is_boolean(value), do: RDF.XSD.Boolean.new(value)
def new(value) when is_integer(value), do: RDF.XSD.Integer.new(value)
def new(value) when is_float(value), do: RDF.XSD.Double.new(value)
def new(%Decimal{} = value), do: RDF.XSD.Decimal.new(value)
def new(%Date{} = value), do: RDF.XSD.Date.new(value)
def new(%Time{} = value), do: RDF.XSD.Time.new(value)
def new(%DateTime{} = value), do: RDF.XSD.DateTime.new(value)
def new(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value)
def new(%URI{} = value), do: RDF.XSD.AnyURI.new(value)
Enum.each(Datatype.Registry.datatypes(), fn datatype ->
def new(%unquote(datatype.literal_type()){} = literal) do
%__MODULE__{literal: literal}
end
end)
def new(value) do def new(value) do
raise RDF.Literal.InvalidError, "#{inspect value} not convertible to a RDF.Literal" case coerce(value) do
nil ->
raise RDF.Literal.InvalidError, "#{inspect value} not convertible to a RDF.Literal"
literal -> literal
end
end end
@doc """ @doc """
@ -92,6 +61,56 @@ defmodule RDF.Literal do
end end
end end
@doc """
Coerces a new `RDF.Literal` from another value.
The following mapping of Elixir types to XSD datatypes is applied:
| Elixir datatype | XSD datatype |
| :-------------- | :------------- |
| `string` | `xsd:string` |
| `boolean` | `xsd:boolean` |
| `integer` | `xsd:integer` |
| `float` | `xsd:double` |
| `Decimal` | `xsd:decimal` |
| `Time` | `xsd:time` |
| `Date` | `xsd:date` |
| `DateTime` | `xsd:dateTime` |
| `NaiveDateTime` | `xsd:dateTime` |
| `URI` | `xsd:AnyURI` |
When an `RDF.Literal` can not be coerced, `nil` is returned
(as opposed to `new/1` which fails in this case).
## Examples
iex> RDF.Literal.coerce(42)
%RDF.Literal{literal: %RDF.XSD.Integer{value: 42}}
"""
def coerce(value)
def coerce(%__MODULE__{} = literal), do: literal
Enum.each(Datatype.Registry.datatypes(), fn datatype ->
def coerce(%unquote(datatype){} = literal) do
%__MODULE__{literal: literal}
end
end)
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(_), do: nil
@doc """ @doc """
Creates a new `RDF.Literal`, but fails if it's not valid. Creates a new `RDF.Literal`, but fails if it's not valid.
@ -101,10 +120,10 @@ defmodule RDF.Literal do
## Examples ## Examples
iex> RDF.Literal.new("foo") iex> RDF.Literal.new("foo")
%RDF.Literal{literal: %XSD.String{value: "foo"}} %RDF.Literal{literal: %RDF.XSD.String{value: "foo"}}
iex> RDF.Literal.new!("foo", datatype: RDF.NS.XSD.integer) iex> RDF.Literal.new!("foo", datatype: RDF.NS.XSD.integer)
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %XSD.Integer{value: nil, lexical: "foo"}, valid: false} ** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.XSD.Integer{value: nil, lexical: "foo"}, valid: false}
iex> RDF.Literal.new!("foo", datatype: RDF.langString) iex> RDF.Literal.new!("foo", datatype: RDF.langString)
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.LangString{language: nil, value: "foo"}, valid: false} ** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.LangString{language: nil, value: "foo"}, valid: false}
@ -149,7 +168,7 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/sparql11-query/#simple_literal> see <http://www.w3.org/TR/sparql11-query/#simple_literal>
""" """
@spec simple?(t) :: boolean @spec simple?(t) :: boolean
def simple?(%__MODULE__{literal: %XSD.String{}}), do: true def simple?(%__MODULE__{literal: %RDF.XSD.String{}}), do: true
def simple?(%__MODULE__{} = _), do: false def simple?(%__MODULE__{} = _), do: false
@ -162,7 +181,7 @@ defmodule RDF.Literal do
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal> see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
""" """
@spec plain?(t) :: boolean @spec plain?(t) :: boolean
def plain?(%__MODULE__{literal: %XSD.String{}}), do: true def plain?(%__MODULE__{literal: %RDF.XSD.String{}}), do: true
def plain?(%__MODULE__{literal: %LangString{}}), do: true def plain?(%__MODULE__{literal: %LangString{}}), do: true
def plain?(%__MODULE__{} = _), do: false def plain?(%__MODULE__{} = _), do: false
@ -171,13 +190,13 @@ defmodule RDF.Literal do
############################################################################ ############################################################################
# functions delegating to the RDF.Datatype of a RDF.Literal # functions delegating to the RDF.Literal.Datatype of a RDF.Literal
@spec datatype(t) :: IRI.t() @spec datatype(t) :: IRI.t()
defdelegate_to_rdf_datatype :datatype def datatype(%__MODULE__{literal: %datatype{} = literal}), do: datatype.datatype(literal)
@spec language(t) :: String.t() | nil @spec language(t) :: String.t() | nil
defdelegate_to_rdf_datatype :language def language(%__MODULE__{literal: %datatype{} = literal}), do: datatype.language(literal)
@spec value(t) :: any @spec value(t) :: any
def value(%__MODULE__{literal: %datatype{} = literal}), do: datatype.value(literal) def value(%__MODULE__{literal: %datatype{} = literal}), do: datatype.value(literal)
@ -186,7 +205,7 @@ defmodule RDF.Literal do
def lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.lexical(literal) def lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.lexical(literal)
@spec canonical(t) :: t @spec canonical(t) :: t
defdelegate_to_rdf_datatype :canonical def canonical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical(literal)
@spec canonical?(t) :: boolean @spec canonical?(t) :: boolean
def canonical?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical?(literal) def canonical?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical?(literal)
@ -195,44 +214,38 @@ defmodule RDF.Literal do
def valid?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.valid?(literal) def valid?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.valid?(literal)
def valid?(_), do: false def valid?(_), do: false
@spec equal?(any, any) :: boolean
def equal?(left, right), do: left == right
@spec equal_value?(t, t | any) :: boolean @spec equal_value?(t, t | any) :: boolean
def equal_value?(%__MODULE__{literal: %datatype{} = left}, right) do def equal_value?(%__MODULE__{literal: %datatype{} = left}, right),
Datatype.Registry.rdf_datatype(datatype).equal_value?(left, right) do: datatype.equal_value?(left, right)
end
def equal_value?(left, right) when not is_nil(left),
do: equal_value?(coerce(left), right)
def equal_value?(_, _), do: nil 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 def compare(%__MODULE__{literal: %datatype{} = left}, right) do
Datatype.Registry.rdf_datatype(datatype).compare(left, right) datatype.compare(left, right)
end end
@doc """ @doc """
Checks if the first of two `RDF.Literal`s is smaller then the other. Checks if the first of two `RDF.Literal`s is smaller then the other.
Returns `nil` when the given arguments are not comparable datatypes.
""" """
@spec less_than?(t, t) :: boolean | nil @spec less_than?(t, t) :: boolean
def less_than?(literal1, literal2) do def less_than?(left, right) do
case compare(literal1, literal2) do compare(left, right) == :lt
:lt -> true
nil -> nil
_ -> false
end
end end
@doc """ @doc """
Checks if the first of two `RDF.Literal`s is greater then the other. Checks if the first of two `RDF.Literal`s is greater then the other.
Returns `nil` when the given arguments are not comparable datatypes.
""" """
@spec greater_than?(t, t) :: boolean | nil @spec greater_than?(t, t) :: boolean
def greater_than?(literal1, literal2) do def greater_than?(left, right) do
case compare(literal1, literal2) do compare(left, right) == :gt
:gt -> true
nil -> nil
_ -> false
end
end end
@ -247,15 +260,15 @@ defmodule RDF.Literal do
def matches?(value, pattern, flags \\ "") def matches?(value, pattern, flags \\ "")
def matches?(%__MODULE__{} = literal, pattern, flags), def matches?(%__MODULE__{} = literal, pattern, flags),
do: matches?(lexical(literal), pattern, flags) do: matches?(lexical(literal), pattern, flags)
def matches?(value, %__MODULE__{literal: %XSD.String{}} = pattern, flags), def matches?(value, %__MODULE__{literal: %RDF.XSD.String{}} = pattern, flags),
do: matches?(value, lexical(pattern), flags) do: matches?(value, lexical(pattern), flags)
def matches?(value, pattern, %__MODULE__{literal: %XSD.String{}} = flags), def matches?(value, pattern, %__MODULE__{literal: %RDF.XSD.String{}} = flags),
do: matches?(value, pattern, lexical(flags)) do: matches?(value, pattern, lexical(flags))
def matches?(value, pattern, flags) when is_binary(value) and is_binary(pattern) and is_binary(flags), def matches?(value, pattern, flags) when is_binary(value) and is_binary(pattern) and is_binary(flags),
do: XSD.Utils.Regex.matches?(value, pattern, flags) do: RDF.XSD.Utils.Regex.matches?(value, pattern, flags)
def update(%__MODULE__{literal: %datatype{} = literal}, fun, opts \\ []) do def update(%__MODULE__{literal: %datatype{} = literal}, fun, opts \\ []) do
Datatype.Registry.rdf_datatype(datatype).update(literal, fun, opts) datatype.update(literal, fun, opts)
end end
defimpl String.Chars do defimpl String.Chars do

View file

@ -7,9 +7,6 @@ defmodule RDF.Literal.Datatype do
@type comparison_result :: :lt | :gt | :eq @type comparison_result :: :lt | :gt | :eq
@doc false
@callback literal_type :: module
@doc """ @doc """
The name of the datatype. The name of the datatype.
""" """
@ -18,35 +15,52 @@ defmodule RDF.Literal.Datatype do
@doc """ @doc """
The IRI of the datatype. The IRI of the datatype.
""" """
@callback id :: String.t() | nil @callback id :: IRI.t() | nil
@callback new(any) :: Literal.t()
@callback new(any, Keyword.t()) :: Literal.t()
@callback new!(any) :: Literal.t()
@callback new!(any, Keyword.t()) :: Literal.t()
@doc """ @doc """
The datatype IRI of the given datatype literal. Casts a datatype literal or coercible value of one type into a datatype literal of another type.
If the given literal or value is invalid or can not be converted into this datatype an
implementation should return `nil`.
This function is called by auto-generated `cast/1` function on the implementations,
which already deals with basic cases and coercion.
"""
@callback do_cast(literal | any) :: Literal.t() | nil
@doc """
The datatype IRI of the given `RDF.Literal`.
""" """
@callback datatype(Literal.t | literal) :: IRI.t() @callback datatype(Literal.t | literal) :: IRI.t()
@doc """ @doc """
The language of the given datatype literal if present. 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 """ @doc """
Returns the value of a datatype literal. Returns the value of a `RDF.Literal`.
""" """
@callback value(Literal.t | literal) :: any @callback value(Literal.t | literal) :: any
@doc """ @doc """
Returns the lexical form of a datatype literal. Returns the lexical form of a `RDF.Literal`.
""" """
@callback lexical(Literal.t() | literal) :: String.t() @callback lexical(Literal.t() | literal) :: String.t()
@doc """ @doc """
Produces the canonical representation of a datatype literal. Produces the canonical representation of a `RDF.Literal`.
""" """
@callback canonical(Literal.t() | literal) :: Literal.t() @callback canonical(Literal.t() | literal) :: Literal.t()
@doc """ @doc """
Determines if the lexical form of a datatype literal is the canonical form. Determines if the lexical form of a `RDF.Literal` is the canonical form.
Note: For `RDF.Literal.Generic` literals with the canonical form not defined, Note: For `RDF.Literal.Generic` literals with the canonical form not defined,
this always return `true`. this always return `true`.
@ -54,27 +68,26 @@ defmodule RDF.Literal.Datatype do
@callback canonical?(Literal.t() | literal | any) :: boolean @callback canonical?(Literal.t() | literal | any) :: boolean
@doc """ @doc """
Determines if the lexical form of a datatype literal is a member of its lexical value space. Determines if the lexical form of a `RDF.Literal` is a member of its lexical value space.
""" """
@callback valid?(Literal.t() | literal | any) :: boolean @callback valid?(Literal.t() | literal | any) :: boolean
@doc """
Casts a datatype literal or coercible value of one type into a datatype literal of another type.
If the given literal or value is invalid or can not be converted into this datatype an
implementation should return `nil`.
"""
@callback cast(Literal.t() | literal | any) :: Literal.t() | nil
@doc """ @doc """
Checks if two datatype literals are equal in terms of the values of their value space. Checks if two datatype literals are equal in terms of the values of their value space.
Non-`RDF.Literal.Datatype` literals are tried to be coerced via `RDF.Term.coerce/1` before comparison. Should return `nil` when the given arguments are not comparable as literals of this
datatype. This behaviour is particularly important for SPARQL.ex where this
function is used for the `=` operator, where comparisons between incomparable
terms are treated as errors and immediately leads to a rejection of a possible
match.
This function is called by auto-generated `equal_value?/2` function on the
implementations, which already deals with basic cases and coercion.
""" """
@callback equal_value?(Literal.t() | literal, Literal.t() | literal | any) :: boolean @callback do_equal_value?(literal, literal) :: boolean
@doc """ @doc """
Compares two datatype literals. Compares two `RDF.Literal`s.
Returns `:gt` if value of the first literal is greater than the value of the second in Returns `:gt` if value of the first literal is greater than the value of the second in
terms of their datatype and `:lt` for vice versa. If the two literals are equal `:eq` is returned. terms of their datatype and `:lt` for vice versa. If the two literals are equal `:eq` is returned.
@ -83,9 +96,156 @@ defmodule RDF.Literal.Datatype do
Returns `nil` when the given arguments are not comparable datatypes or if one Returns `nil` when the given arguments are not comparable datatypes or if one
them is invalid. them is invalid.
The default implementation of the `_using__` macro of `RDF.XSD.Datatype`s
compares the values of the `canonical/1` forms of the given literals of this datatype.
""" """
@callback compare(Literal.t() | literal, Literal.t() | literal) :: comparison_result | :indeterminate | nil @callback compare(Literal.t() | literal, Literal.t() | literal) :: comparison_result | :indeterminate | nil
@doc """
Updates the value of a `RDF.Literal` without changing everything else.
## Example
iex> RDF.XSD.integer(42) |> RDF.XSD.Integer.update(fn value -> value + 1 end)
RDF.XSD.integer(42)
iex> RDF.literal("foo", language: "de") |> RDF.LangString.update(fn _ -> "bar" end)
RDF.literal("bar", language: "de")
iex> RDF.literal("foo", datatype: "http://example.com/dt") |> RDF.LangString.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 everything else.
This variant of `c:update/2` allows with the `:as` option to specify what will
be passed to `fun`, eg. with `as: :lexical` the lexical is passed to the function.
## Example
iex> RDF.XSD.integer(42) |> RDF.XSD.Integer.update(
...> 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
defmacro __using__(opts) do
name = Keyword.fetch!(opts, :name)
id = Keyword.fetch!(opts, :id)
quote do
@behaviour unquote(__MODULE__)
@name unquote(name)
@impl unquote(__MODULE__)
def name, do: @name
@id if unquote(id), do: RDF.IRI.new(unquote(id))
@impl unquote(__MODULE__)
def id, do: @id
@impl unquote(__MODULE__)
def datatype(%Literal{literal: literal}), do: datatype(literal)
def datatype(%__MODULE__{}), do: @id
@impl unquote(__MODULE__)
def language(%Literal{literal: literal}), do: language(literal)
def language(%__MODULE__{}), do: nil
@doc """
Casts a datatype literal or coercible value of one type into a datatype literal of another type.
Returns `nil` when the given arguments are not comparable as literals of this
datatype or when the given argument is an invalid literal.
Implementations define the casting for a given value with the `c:do_cast/1` callback.
"""
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))
def cast(nil), do: nil
def cast(value) do
case do_cast(value) do
%__MODULE__{} = literal -> if valid?(literal), do: literal(literal)
%Literal{literal: %__MODULE__{}} = literal -> if valid?(literal), do: literal
_ -> nil
end
end
@impl unquote(__MODULE__)
def do_cast(value) do
value |> Literal.coerce() |> cast()
end
@doc """
Checks if two datatype literals are equal in terms of the values of their value space.
Non-`RDF.Literal`s are tried to be coerced via `RDF.Literal.coerce/1` before comparison.
Returns `nil` when the given arguments are not comparable as literals of this
datatype.
Implementations define this equivalence relation via the `c:do_equal_value?/2` callback.
"""
def equal_value?(left, right)
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
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 RDF.literal?(right) -> equal_value?(left, Literal.coerce(right))
not RDF.literal?(left) -> equal_value?(Literal.coerce(left), right)
true -> do_equal_value?(left, right)
end
end
# 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
@impl unquote(__MODULE__)
def do_equal_value?(left, right)
def do_equal_value?(%__MODULE__{} = left, %__MODULE__{} = right), do: left == right
def do_equal_value?(_, _), do: nil
defoverridable do_equal_value?: 2
end
@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)
nil -> value(literal)
end
|> fun.()
|> new()
end
@spec less_than?(t, t) :: boolean
def less_than?(literal1, literal2), do: Literal.less_than?(literal1, literal2)
@spec greater_than?(t, t) :: boolean
def greater_than?(literal1, literal2), do: Literal.greater_than?(literal1, literal2)
defp literal(datatype_literal), do: %Literal{literal: datatype_literal}
defoverridable datatype: 1,
language: 1,
cast: 1,
do_cast: 1,
equal_value?: 2,
update: 2,
update: 3
defimpl String.Chars do
def to_string(literal) do
literal.__struct__.lexical(literal)
end
end
end
end
end end

View file

@ -1,63 +0,0 @@
defmodule RDF.Literal.Datatype.NS do
@moduledoc !"""
Since the capability of RDF.Vocabulary.Namespaces requires the compilation
of the RDF.NTriples.Decoder and the RDF.NTriples.Decoder depends on RDF.Literals,
we can't define the XSD namespace in RDF.NS.
TODO: This is no longer the case. Why can't we remove it resp. move it to RDF.NS now?
"""
use RDF.Vocabulary.Namespace
@vocabdoc false
defvocab XSD,
base_iri: "http://www.w3.org/2001/XMLSchema#",
terms: ~w[
string
normalizedString
token
language
Name
NCName
ID
IDREF
IDREFS
ENTITY
ENTITIES
NMTOKEN
NMTOKENS
boolean
float
double
decimal
integer
long
int
short
byte
nonPositiveInteger
negativeInteger
nonNegativeInteger
positiveInteger
unsignedLong
unsignedInt
unsignedShort
unsignedByte
duration
dayTimeDuration
yearMonthDuration
dateTime
time
date
gYearMonth
gYear
gMonthDay
gDay
gMonth
base64Binary
hexBinary
anyURI
QName
NOTATION
]
end

View file

@ -2,15 +2,11 @@
defmodule RDF.Literal.Datatype.Registry do defmodule RDF.Literal.Datatype.Registry do
@moduledoc false @moduledoc false
alias RDF.Literal alias RDF.{Literal, IRI, XSD}
alias RDF.IRI
@datatypes [ @datatypes [RDF.LangString | Enum.to_list(XSD.datatypes())]
RDF.LangString
| Enum.map(XSD.datatypes(), &Literal.XSD.datatype_module_name/1)
]
@mapping Map.new(@datatypes, fn datatype -> {RDF.IRI.new(datatype.id), datatype} end) @mapping Map.new(@datatypes, fn datatype -> {IRI.new(datatype.id), datatype} end)
@doc """ @doc """
The mapping of IRIs of datatypes to their `RDF.Literal.Datatype`. The mapping of IRIs of datatypes to their `RDF.Literal.Datatype`.
@ -40,13 +36,4 @@ defmodule RDF.Literal.Datatype.Registry do
def get(%Literal{} = literal), do: Literal.datatype(literal) def get(%Literal{} = literal), do: Literal.datatype(literal)
def get(id) when is_binary(id), do: id |> IRI.new() |> get() def get(id) when is_binary(id), do: id |> IRI.new() |> get()
def get(id), do: @mapping[id] def get(id), do: @mapping[id]
@doc false
def rdf_datatype(datatype)
Enum.each XSD.datatypes(), fn xsd_datatype ->
def rdf_datatype(unquote(xsd_datatype)) do
unquote(Literal.XSD.datatype_module_name(xsd_datatype))
end
end
def rdf_datatype(datatype), do: datatype
end end

View file

@ -5,7 +5,9 @@ defmodule RDF.Literal.Generic do
defstruct [:value, :datatype] defstruct [:value, :datatype]
@behaviour RDF.Literal.Datatype use RDF.Literal.Datatype,
name: "generic",
id: nil
alias RDF.Literal.Datatype alias RDF.Literal.Datatype
alias RDF.{Literal, IRI} alias RDF.{Literal, IRI}
@ -16,16 +18,8 @@ defmodule RDF.Literal.Generic do
} }
@impl Datatype @impl Datatype
def literal_type, do: __MODULE__
@impl Datatype
def name, do: "generic"
@impl Datatype
def id, do: nil
@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, datatype_or_opts \\ [])
def new(value, datatype) when is_binary(datatype), do: new(value, datatype: datatype) def new(value, datatype) when is_binary(datatype), do: new(value, datatype: datatype)
def new(value, %IRI{} = datatype), do: new(value, datatype: datatype) def new(value, %IRI{} = datatype), do: new(value, datatype: datatype)
def new(value, opts) do def new(value, opts) do
@ -41,8 +35,9 @@ defmodule RDF.Literal.Generic do
defp normalize_datatype(""), do: nil defp normalize_datatype(""), do: nil
defp normalize_datatype(datatype), do: IRI.new(datatype) defp normalize_datatype(datatype), do: IRI.new(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 def new!(value, datatype_or_opts \\ []) do
literal = new(value, datatype_or_opts) literal = new(value, datatype_or_opts)
if valid?(literal) do if valid?(literal) do
@ -56,10 +51,6 @@ defmodule RDF.Literal.Generic do
def datatype(%Literal{literal: literal}), do: datatype(literal) def datatype(%Literal{literal: literal}), do: datatype(literal)
def datatype(%__MODULE__{} = literal), do: literal.datatype def datatype(%__MODULE__{} = literal), do: literal.datatype
@impl Datatype
def language(%Literal{literal: literal}), do: language(literal)
def language(%__MODULE__{}), do: nil
@impl Datatype @impl Datatype
def value(%Literal{literal: literal}), do: value(literal) def value(%Literal{literal: literal}), do: value(literal)
def value(%__MODULE__{} = literal), do: literal.value def value(%__MODULE__{} = literal), do: literal.value
@ -70,7 +61,7 @@ defmodule RDF.Literal.Generic do
@impl Datatype @impl Datatype
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
def canonical(%__MODULE__{} = literal), do: %Literal{literal: literal} def canonical(%__MODULE__{} = literal), do: literal(literal)
@impl Datatype @impl Datatype
def canonical?(%Literal{literal: literal}), do: canonical?(literal) def canonical?(%Literal{literal: literal}), do: canonical?(literal)
@ -81,16 +72,18 @@ defmodule RDF.Literal.Generic do
def valid?(%__MODULE__{datatype: %IRI{}}), do: true def valid?(%__MODULE__{datatype: %IRI{}}), do: true
def valid?(_), do: false def valid?(_), do: false
@impl Datatype @doc """
Since generic literals don't support casting, always returns `nil`.
"""
def cast(_), do: nil def cast(_), do: nil
@impl Datatype @impl Datatype
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right) def do_cast(_), do: nil
def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(%__MODULE__{datatype: datatype} = left, @impl Datatype
%__MODULE__{datatype: datatype} = right), def do_equal_value?(%__MODULE__{datatype: datatype} = left,
do: left == right %__MODULE__{datatype: datatype} = right), do: left == right
def equal_value?(_, _), do: nil def do_equal_value?(_, _), do: nil
@impl Datatype @impl Datatype
def compare(left, %Literal{literal: right}), do: compare(left, right) def compare(left, %Literal{literal: right}), do: compare(left, right)
@ -118,10 +111,4 @@ defmodule RDF.Literal.Generic do
|> fun.() |> fun.()
|> new(datatype: literal.datatype) |> new(datatype: literal.datatype)
end end
defimpl String.Chars do
def to_string(literal) do
literal.value
end
end
end end

View file

@ -5,7 +5,9 @@ defmodule RDF.LangString do
defstruct [:value, :language] defstruct [:value, :language]
@behaviour RDF.Literal.Datatype use RDF.Literal.Datatype,
name: "langString",
id: RDF.Utils.Bootstrapping.rdf_iri("langString")
alias RDF.Literal.Datatype alias RDF.Literal.Datatype
alias RDF.Literal alias RDF.Literal
@ -15,20 +17,9 @@ defmodule RDF.LangString do
language: String.t language: String.t
} }
@iri RDF.Utils.Bootstrapping.rdf_iri("langString") @impl RDF.Literal.Datatype
@id to_string(@iri)
@impl Datatype
def literal_type, do: __MODULE__
@impl Datatype
def name, do: "langString"
@impl Datatype
def id, do: @id
@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_or_opts \\ [])
def new(value, language) when is_binary(language), do: new(value, language: language) def new(value, language) when is_binary(language), do: new(value, language: language)
def new(value, language) when is_atom(language), do: new(value, language: language) def new(value, language) when is_atom(language), do: new(value, language: language)
def new(value, opts) do def new(value, opts) do
@ -45,8 +36,9 @@ defmodule RDF.LangString do
defp normalize_language(language) when is_atom(language), do: language |> to_string() |> normalize_language() defp normalize_language(language) when is_atom(language), do: language |> to_string() |> normalize_language()
defp normalize_language(language), do: String.downcase(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 def new!(value, language_or_opts \\ []) do
literal = new(value, language_or_opts) literal = new(value, language_or_opts)
if valid?(literal) do if valid?(literal) do
@ -56,10 +48,6 @@ defmodule RDF.LangString do
end end
end end
@impl Datatype
def datatype(%Literal{literal: literal}), do: datatype(literal)
def datatype(%__MODULE__{}), do: @iri
@impl Datatype @impl Datatype
def language(%Literal{literal: literal}), do: language(literal) def language(%Literal{literal: literal}), do: language(literal)
def language(%__MODULE__{} = literal), do: literal.language def language(%__MODULE__{} = literal), do: literal.language
@ -74,7 +62,7 @@ defmodule RDF.LangString do
@impl Datatype @impl Datatype
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
def canonical(%__MODULE__{} = literal), do: %Literal{literal: literal} def canonical(%__MODULE__{} = literal), do: literal(literal)
@impl Datatype @impl Datatype
def canonical?(%Literal{literal: literal}), do: canonical?(literal) def canonical?(%Literal{literal: literal}), do: canonical?(literal)
@ -86,14 +74,7 @@ defmodule RDF.LangString do
def valid?(_), do: false def valid?(_), do: false
@impl Datatype @impl Datatype
def cast(%Literal{literal: %__MODULE__{}} = literal), do: if valid?(literal), do: literal def do_cast(_), do: nil
def cast(_), do: nil
@impl Datatype
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(%__MODULE__{} = left, %__MODULE__{} = right), do: left == right
def equal_value?(_, _), do: nil
@impl Datatype @impl Datatype
def compare(left, %Literal{literal: right}), do: compare(left, right) def compare(left, %Literal{literal: right}), do: compare(left, right)
@ -157,10 +138,4 @@ defmodule RDF.LangString do
end end
def match_language?(_, _), do: false def match_language?(_, _), do: false
defimpl String.Chars do
def to_string(literal) do
literal.value
end
end
end end

View file

@ -1,191 +0,0 @@
defmodule RDF.Numeric do
@moduledoc """
Collection of functions for numeric literals.
"""
alias RDF.Literal
import Kernel, except: [abs: 1, floor: 1, ceil: 1, round: 1]
@datatypes XSD.Numeric.datatypes()
@doc """
The set of all numeric datatypes.
"""
def datatypes(), do: @datatypes
@doc """
Returns if a given datatype is a numeric datatype.
"""
def datatype?(datatype), do: datatype in @datatypes
@doc """
Returns if a given literal has a numeric datatype.
"""
@spec literal?(Literal.t | any) :: boolean
def literal?(%Literal{literal: %datatype{}}), do: datatype?(datatype)
def literal?(_), do: false
def zero?(%Literal{literal: literal}), do: zero?(literal)
def zero?(literal), do: XSD.Numeric.zero?(literal)
def negative_zero?(%Literal{literal: literal}), do: negative_zero?(literal)
def negative_zero?(literal), do: XSD.Numeric.negative_zero?(literal)
@doc """
Adds two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero or a
finite number and the other is INF or -INF, INF or -INF is returned. If both
operands are INF, INF is returned. If both operands are -INF, -INF is returned.
If one of the operands is INF and the other is -INF, NaN is returned.
If one of the given arguments is not a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-add>
"""
def add(left, right)
def add(left, %Literal{literal: right}), do: add(left, right)
def add(%Literal{literal: left}, right), do: add(left, right)
def add(left, right) do
if result = XSD.Numeric.add(left, right) do
Literal.new(result)
end
end
@doc """
Subtracts two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero or a
finite number and the other is INF or -INF, an infinity of the appropriate sign
is returned. If both operands are INF or -INF, NaN is returned. If one of the
operands is INF and the other is -INF, an infinity of the appropriate sign is
returned.
If one of the given arguments is not a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-subtract>
"""
def subtract(left, right)
def subtract(left, %Literal{literal: right}), do: subtract(left, right)
def subtract(%Literal{literal: left}, right), do: subtract(left, right)
def subtract(left, right) do
if result = XSD.Numeric.subtract(left, right) do
Literal.new(result)
end
end
@doc """
Multiplies two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero and
the other is an infinity, NaN is returned. If one of the operands is a non-zero
number and the other is an infinity, an infinity with the appropriate sign is
returned.
If one of the given arguments is not a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-multiply>
"""
def multiply(left, right)
def multiply(left, %Literal{literal: right}), do: multiply(left, right)
def multiply(%Literal{literal: left}, right), do: multiply(left, right)
def multiply(left, right) do
if result = XSD.Numeric.multiply(left, right) do
Literal.new(result)
end
end
@doc """
Divides two numeric literals.
For `xsd:float` and `xsd:double` operands, floating point division is performed
as specified in [IEEE 754-2008]. A positive number divided by positive zero
returns INF. A negative number divided by positive zero returns -INF. Division
by negative zero returns -INF and INF, respectively. Positive or negative zero
divided by positive or negative zero returns NaN. Also, INF or -INF divided by
INF or -INF returns NaN.
If one of the given arguments is not a numeric literal, `nil` is returned.
`nil` is also returned for `xsd:decimal` and `xsd:integer` operands, if the
divisor is (positive or negative) zero.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-divide>
"""
def divide(left, right)
def divide(left, %Literal{literal: right}), do: divide(left, right)
def divide(%Literal{literal: left}, right), do: divide(left, right)
def divide(left, right) do
if result = XSD.Numeric.divide(left, right) do
Literal.new(result)
end
end
@doc """
Returns the absolute value of a numeric literal.
If the argument is not a valid numeric literal `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-abs>
"""
def abs(numeric)
def abs(%Literal{literal: numeric}), do: abs(numeric)
def abs(numeric) do
if result = XSD.Numeric.abs(numeric) do
Literal.new(result)
end
end
@doc """
Rounds a value to a specified number of decimal places, rounding upwards if two such values are equally near.
The function returns the nearest (that is, numerically closest) value to the
given literal value that is a multiple of ten to the power of minus `precision`.
If two such values are equally near (for example, if the fractional part in the
literal value is exactly .5), the function returns the one that is closest to
positive infinity.
If the argument is not a valid numeric literal `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-round>
"""
def round(numeric, precision \\ 0)
def round(%Literal{literal: numeric}, precision), do: round(numeric, precision)
def round(numeric, precision) do
if result = XSD.Numeric.round(numeric, precision) do
Literal.new(result)
end
end
@doc """
Rounds a numeric literal upwards to a whole number literal.
If the argument is not a valid numeric literal `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-ceil>
"""
def ceil(numeric)
def ceil(%Literal{literal: numeric}), do: ceil(numeric)
def ceil(numeric) do
if result = XSD.Numeric.ceil(numeric) do
Literal.new(result)
end
end
@doc """
Rounds a numeric literal downwards to a whole number literal.
If the argument is not a valid numeric literal `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-floor>
"""
def floor(numeric)
def floor(%Literal{literal: numeric}), do: floor(numeric)
def floor(numeric) do
if result = XSD.Numeric.floor(numeric) do
Literal.new(result)
end
end
end

View file

@ -1,157 +0,0 @@
defmodule RDF.Literal.XSD do
@moduledoc false
alias RDF.Literal
@after_compile __MODULE__
def __after_compile__(_env, _bytecode) do
Enum.each XSD.datatypes(), &def_xsd_datatype/1
end
defp def_xsd_datatype(xsd_datatype) do
xsd_datatype
|> datatype_module_name()
|> Module.create(datatype_module_body(xsd_datatype), Macro.Env.location(__ENV__))
end
def datatype_module_name(xsd_datatype) do
Module.concat(RDF, xsd_datatype)
end
defp datatype_module_body(xsd_datatype) do
[
quote do
@behaviour RDF.Literal.Datatype
def new(value, opts \\ []) do
%Literal{literal: unquote(xsd_datatype).new(value, opts)}
end
def new!(value, opts \\ []) do
%Literal{literal: unquote(xsd_datatype).new!(value, opts)}
end
@impl RDF.Literal.Datatype
def literal_type, do: unquote(xsd_datatype)
@impl RDF.Literal.Datatype
defdelegate name, to: unquote(xsd_datatype)
@impl RDF.Literal.Datatype
defdelegate id, to: unquote(xsd_datatype)
@iri RDF.IRI.new(unquote(xsd_datatype).id())
@impl RDF.Literal.Datatype
def datatype(%Literal{literal: literal}), do: datatype(literal)
def datatype(%unquote(xsd_datatype){}), do: @iri
@impl RDF.Literal.Datatype
def language(%Literal{literal: literal}), do: language(literal)
def language(%unquote(xsd_datatype){}), do: nil
@impl RDF.Literal.Datatype
def value(%Literal{literal: literal}), do: value(literal)
def value(%unquote(xsd_datatype){} = literal), do: unquote(xsd_datatype).value(literal)
@impl RDF.Literal.Datatype
def lexical(%Literal{literal: literal}), do: lexical(literal)
def lexical(%unquote(xsd_datatype){} = literal), do: unquote(xsd_datatype).lexical(literal)
@impl RDF.Literal.Datatype
def canonical(%Literal{literal: %unquote(xsd_datatype){} = typed_literal} = literal),
do: %Literal{literal | literal: unquote(xsd_datatype).canonical(typed_literal)}
def canonical(%unquote(xsd_datatype){} = literal), do: canonical(%Literal{literal: literal})
@impl RDF.Literal.Datatype
def canonical?(%Literal{literal: literal}), do: canonical?(literal)
def canonical?(%unquote(xsd_datatype){} = literal), do: unquote(xsd_datatype).canonical?(literal)
@impl RDF.Literal.Datatype
def valid?(%Literal{literal: literal}), do: valid?(literal)
def valid?(%unquote(xsd_datatype){} = literal), do: unquote(xsd_datatype).valid?(literal)
def valid?(_), do: false
@impl RDF.Literal.Datatype
def cast(%Literal{literal: %unquote(xsd_datatype){}} = literal) do
if valid?(literal), do: literal
end
def cast(%Literal{literal: literal}) do
if casted_literal = unquote(xsd_datatype).cast(literal) do
%Literal{literal: casted_literal}
end
end
def cast(nil), do: nil
def cast(value), do: value |> Literal.new() |> cast()
@impl RDF.Literal.Datatype
def equal_value?(left, %Literal{literal: right}), do: equal_value?(left, right)
def equal_value?(%Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(%unquote(xsd_datatype){} = left, right) do
unquote(xsd_datatype).equal_value?(left, right)
end
def equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
@dialyzer {:nowarn_function, compare: 2} # TODO: Why is this warning raised
def compare(left, %Literal{literal: right}), do: compare(left, right)
def compare(%Literal{literal: left}, right), do: compare(left, right)
def compare(%unquote(xsd_datatype){} = left, right) do
unquote(xsd_datatype).compare(left, right)
end
@impl RDF.Literal.Datatype
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%unquote(xsd_datatype){} = literal, fun, opts) do
case Keyword.get(opts, :as) do
:lexical -> lexical(literal)
nil -> value(literal)
end
|> fun.()
|> new()
end
end
| datatype_specific_module_body(xsd_datatype)]
end
defp datatype_specific_module_body(XSD.Boolean) do
[
quote do
def ebv(%Literal{literal: literal}), do: ebv(literal)
def ebv(literal) do
if ebv = XSD.Boolean.ebv(literal), do: %Literal{literal: ebv}
end
def effective(value), do: ebv(value)
def fn_not(%Literal{literal: literal}), do: fn_not(literal)
def fn_not(literal) do
if result = XSD.Boolean.fn_not(literal), do: %Literal{literal: result}
end
def logical_and(%Literal{literal: left}, right), do: logical_and(left, right)
def logical_and(left, %Literal{literal: right}), do: logical_and(left, right)
def logical_and(left, right) do
if result = XSD.Boolean.logical_and(left, right), do: %Literal{literal: result}
end
def logical_or(%Literal{literal: left}, right), do: logical_or(left, right)
def logical_or(left, %Literal{literal: right}), do: logical_or(left, right)
def logical_or(left, right) do
if result = XSD.Boolean.logical_or(left, right), do: %Literal{literal: result}
end
end
]
end
defp datatype_specific_module_body(XSD.DateTime) do
[
quote do
def now(), do: XSD.DateTime.now() |> new()
end
]
end
defp datatype_specific_module_body(_), do: []
end

View file

@ -1,11 +0,0 @@
defmodule RDF.Literal.Helper.Macros do
@moduledoc false
defmacro defdelegate_to_rdf_datatype(fun_name) do
quote do
def unquote(fun_name)(%__MODULE__{literal: %datatype{}} = literal) do
apply(RDF.Literal.Datatype.Registry.rdf_datatype(datatype), unquote(fun_name), [literal])
end
end
end
end

View file

@ -13,14 +13,9 @@ defmodule RDF.NS do
use RDF.Vocabulary.Namespace use RDF.Vocabulary.Namespace
@vocabdoc """ # This is needed to ensure that the Turtle compiler is compiled and ready to be used to parse vocabularies.
The XML Schema datatypes vocabulary. # Without this we randomly get "unable to detect serialization format" errors depending on the parallel compilation order.
require RDF.Turtle
See <https://www.w3.org/TR/xmlschema11-2/>
"""
defvocab XSD,
base_iri: "http://www.w3.org/2001/XMLSchema#",
terms: RDF.Literal.Datatype.NS.XSD.__terms__
@vocabdoc """ @vocabdoc """
The RDF vocabulary. The RDF vocabulary.
@ -62,4 +57,59 @@ defmodule RDF.NS do
base_iri: "http://www.w3.org/2004/02/skos/core#", base_iri: "http://www.w3.org/2004/02/skos/core#",
file: "skos.ttl" file: "skos.ttl"
@vocabdoc """
The XML Schema datatypes vocabulary.
See <https://www.w3.org/TR/xmlschema11-2/>
"""
defvocab XSD,
base_iri: "http://www.w3.org/2001/XMLSchema#",
terms: ~w[
string
normalizedString
token
language
Name
NCName
ID
IDREF
IDREFS
ENTITY
ENTITIES
NMTOKEN
NMTOKENS
boolean
float
double
decimal
integer
long
int
short
byte
nonPositiveInteger
negativeInteger
nonNegativeInteger
positiveInteger
unsignedLong
unsignedInt
unsignedShort
unsignedByte
duration
dayTimeDuration
yearMonthDuration
dateTime
time
date
gYearMonth
gYear
gMonthDay
gDay
gMonth
base64Binary
hexBinary
anyURI
QName
NOTATION
]
end end

View file

@ -3,7 +3,7 @@ defmodule RDF.NTriples.Encoder do
use RDF.Serialization.Encoder use RDF.Serialization.Encoder
alias RDF.{BlankNode, Dataset, Graph, IRI, Literal, Statement, Triple, LangString} alias RDF.{BlankNode, Dataset, Graph, IRI, XSD, Literal, Statement, Triple, LangString}
@impl RDF.Serialization.Encoder @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}

View file

@ -4,7 +4,7 @@ defmodule RDF.Turtle.Encoder do
use RDF.Serialization.Encoder use RDF.Serialization.Encoder
alias RDF.Turtle.Encoder.State alias RDF.Turtle.Encoder.State
alias RDF.{BlankNode, Dataset, Description, Graph, IRI, Literal, LangString} alias RDF.{BlankNode, Dataset, Description, Graph, IRI, XSD, Literal, LangString}
@indentation_char " " @indentation_char " "
@indentation 4 @indentation 4

View file

@ -28,7 +28,7 @@ defprotocol RDF.Term do
false false
iex> RDF.Term.term?(RDF.bnode) iex> RDF.Term.term?(RDF.bnode)
true true
iex> RDF.Term.term?(RDF.integer(42)) iex> RDF.Term.term?(RDF.XSD.integer(42))
true true
iex> RDF.Term.term?(42) iex> RDF.Term.term?(42)
false false
@ -68,7 +68,7 @@ defprotocol RDF.Term do
iex> RDF.Term.coerce("foo") iex> RDF.Term.coerce("foo")
~L"foo" ~L"foo"
iex> RDF.Term.coerce(42) iex> RDF.Term.coerce(42)
RDF.integer(42) RDF.XSD.integer(42)
""" """
def coerce(value) def coerce(value)
@ -84,7 +84,7 @@ defprotocol RDF.Term do
"http://example.com/" "http://example.com/"
iex> RDF.Term.value(~L"foo") iex> RDF.Term.value(~L"foo")
"foo" "foo"
iex> RDF.integer(42) |> RDF.Term.value() iex> RDF.XSD.integer(42) |> RDF.Term.value()
42 42
""" """
@ -119,7 +119,7 @@ defimpl RDF.Term, for: Reference do
end end
defimpl RDF.Term, for: RDF.Literal do defimpl RDF.Term, for: RDF.Literal do
def equal?(term1, term2), do: 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 equal_value?(term1, term2), do: RDF.Literal.equal_value?(term1, term2)
def coerce(term), do: term def coerce(term), do: term
def value(term), do: RDF.Literal.value(term) || RDF.Literal.lexical(term) def value(term), do: RDF.Literal.value(term) || RDF.Literal.lexical(term)
@ -132,8 +132,8 @@ defimpl RDF.Term, for: Atom do
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 equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
def coerce(true), do: RDF.true def coerce(true), do: RDF.XSD.true
def coerce(false), do: RDF.false def coerce(false), do: RDF.XSD.false
def coerce(_), do: nil def coerce(_), do: nil
def value(true), do: true def value(true), do: true

View file

@ -3,14 +3,17 @@ defmodule RDF.Utils.Bootstrapping do
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#" @rdf_base_iri "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
@rdfs_base_iri "http://www.w3.org/2000/01/rdf-schema#" @rdfs_base_iri "http://www.w3.org/2000/01/rdf-schema#"
@owl_base_iri "http://www.w3.org/2002/07/owl#" @owl_base_iri "http://www.w3.org/2002/07/owl#"
def xsd_iri_base(), do: RDF.IRI.new(@xsd_base_iri)
def rdf_iri_base(), do: RDF.IRI.new(@rdf_base_iri) def rdf_iri_base(), do: RDF.IRI.new(@rdf_base_iri)
def rdfs_iri_base(), do: RDF.IRI.new(@rdfs_base_iri) def rdfs_iri_base(), do: RDF.IRI.new(@rdfs_base_iri)
def owl_iri_base(), do: RDF.IRI.new(@owl_base_iri) def owl_iri_base(), do: RDF.IRI.new(@owl_base_iri)
def xsd_iri(term), do: RDF.IRI.new(@xsd_base_iri <> term)
def rdf_iri(term), do: RDF.IRI.new(@rdf_base_iri <> term) def rdf_iri(term), do: RDF.IRI.new(@rdf_base_iri <> term)
def rdfs_iri(term), do: RDF.IRI.new(@rdfs_base_iri <> term) def rdfs_iri(term), do: RDF.IRI.new(@rdfs_base_iri <> term)
def owl_iri(term), do: RDF.IRI.new(@owl_base_iri <> term) def owl_iri(term), do: RDF.IRI.new(@owl_base_iri <> term)

83
lib/rdf/xsd.ex Normal file
View file

@ -0,0 +1,83 @@
defmodule RDF.XSD do
@moduledoc """
An implementation of the XML Schema (XSD) datatype system for use within `RDF.Literal.Datatype` system.
see <https://www.w3.org/TR/xmlschema-2/>
"""
alias __MODULE__
alias RDF.{IRI, Literal}
@datatypes [
XSD.Boolean,
XSD.String,
XSD.Date,
XSD.Time,
XSD.DateTime,
XSD.AnyURI
]
|> MapSet.new()
|> MapSet.union(XSD.Numeric.datatypes())
@facets [
XSD.Facets.MinInclusive,
XSD.Facets.MaxInclusive
]
@doc """
The list of all XSD facets.
"""
@spec facets() :: Enum.t()
def facets(), do: @facets
@facets_by_name Map.new(@facets, fn facet -> {facet.name(), facet} end)
def facet(name) when is_atom(name), do: @facets_by_name[to_string(name)]
def facet(name), do: @facets_by_name[name]
@doc """
The list of all XSD datatypes.
"""
@spec datatypes() :: Enum.t()
def datatypes(), do: @datatypes
@datatypes_by_name Map.new(@datatypes, fn datatype -> {datatype.name(), datatype} end)
@datatypes_by_iri Map.new(@datatypes, fn datatype -> {datatype.id(), datatype} end)
def datatype_by_name(name) when is_atom(name), do: @datatypes_by_name[to_string(name)]
def datatype_by_name(name), do: @datatypes_by_name[name]
def datatype_by_iri(iri) when is_binary(iri), do: @datatypes_by_iri[IRI.new(iri)]
def datatype_by_iri(%IRI{} = iri), do: @datatypes_by_iri[iri]
@doc """
Returns if a given datatype is a XSD datatype.
"""
def datatype?(datatype), do: datatype in @datatypes
@doc """
Returns if a given argument is a `RDF.XSD.datatype` literal.
"""
def literal?(%Literal{literal: %datatype{}}), do: datatype?(datatype)
def literal?(%datatype{}), do: datatype?(datatype)
def literal?(_), do: false
@doc false
def valid?(%datatype{} = datatype_literal), do: datatype.valid?(datatype_literal)
for datatype <- @datatypes do
defdelegate unquote(String.to_atom(datatype.name))(value), to: datatype, as: :new
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
end
end
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(false)(), to: XSD.Boolean.Value
end

310
lib/rdf/xsd/datatype.ex Normal file
View file

@ -0,0 +1,310 @@
defmodule RDF.XSD.Datatype do
@moduledoc """
The behaviour of all XSD datatypes.
"""
@type t :: module
@type uncanonical_lexical :: String.t() | nil
@type literal :: %{
:__struct__ => t(),
:value => any(),
:uncanonical_lexical => uncanonical_lexical()
}
@type comparison_result :: :lt | :gt | :eq
@doc """
Returns if the `RDF.XSD.Datatype` is a primitive datatype.
"""
@callback primitive?() :: boolean
@doc """
The base datatype from which a `RDF.XSD.Datatype` is derived.
"""
@callback base :: t() | nil
@doc """
The primitive `RDF.XSD.Datatype` from which a `RDF.XSD.Datatype` is derived.
In case of a primitive `RDF.XSD.Datatype` this function returns this `RDF.XSD.Datatype` itself.
"""
@callback base_primitive :: t()
@doc """
Checks if a `RDF.XSD.Datatype` is directly or indirectly derived from another `RDF.XSD.Datatype`.
"""
@callback derived_from?(t()) :: boolean
@doc """
Checks if the datatype of a given literal is derived from a `RDF.XSD.Datatype`.
"""
@callback derived?(RDF.XSD.Literal.t()) :: boolean
@doc """
The set of applicable facets of a `RDF.XSD.Datatype`.
"""
@callback applicable_facets :: [RDF.XSD.Facet.t()]
@doc """
A mapping from the lexical space of a `RDF.XSD.Datatype` into its value space.
"""
@callback lexical_mapping(String.t(), Keyword.t()) :: any
@doc """
A mapping from Elixir values into the value space of a `RDF.XSD.Datatype`.
"""
@callback elixir_mapping(any, Keyword.t()) :: any | {any, uncanonical_lexical}
@doc """
Returns the standard lexical representation for a value of the value space of a `RDF.XSD.Datatype`.
"""
@callback canonical_mapping(any) :: String.t()
@doc """
Produces the lexical representation to be used as for a `RDF.XSD.Datatype` literal.
"""
@callback init_valid_lexical(any, uncanonical_lexical, Keyword.t()) :: uncanonical_lexical
@doc """
Produces the lexical representation of an invalid value.
The default implementation of the `_using__` macro just returns `to_string/1`
representation of the value.
"""
@callback init_invalid_lexical(any, Keyword.t()) :: String.t()
@doc """
Matches the lexical form of the given `RDF.XSD.Datatype` literal against a XPath and XQuery regular expression pattern.
The regular expression language is defined in _XQuery 1.0 and XPath 2.0 Functions and Operators_.
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@callback matches?(RDF.XSD.Literal.t(), pattern :: String.t()) :: boolean
@doc """
Matches the lexical form of the given `RDF.XSD.Datatype` literal against a XPath and XQuery regular expression pattern with flags.
The regular expression language is defined in _XQuery 1.0 and XPath 2.0 Functions and Operators_.
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@callback matches?(RDF.XSD.Literal.t(), pattern :: String.t(), flags :: String.t()) :: boolean
defmacro __using__(opts) do
quote do
defstruct [:value, :uncanonical_lexical]
@behaviour unquote(__MODULE__)
use RDF.Literal.Datatype, unquote(opts)
@invalid_value nil
@type invalid_value :: nil
@type value :: valid_value | invalid_value
@type t :: %__MODULE__{
value: value,
uncanonical_lexical: RDF.XSD.Datatype.uncanonical_lexical()
}
@impl unquote(__MODULE__)
def derived_from?(datatype)
def derived_from?(__MODULE__), do: true
def derived_from?(datatype) do
base = base()
not is_nil(base) and base.derived_from?(datatype)
end
@impl unquote(__MODULE__)
def derived?(literal), do: RDF.XSD.Datatype.derived_from?(literal, __MODULE__)
# Dialyzer causes a warning on all primitives since the facet_conform?/2 call
# always returns true there, so the other branch is unnecessary. This could
# be fixed by generating a special version for primitives, but it's not worth
# maintaining different versions of this function which must be kept in-sync.
@dialyzer {:nowarn_function, new: 2}
@impl RDF.Literal.Datatype
def new(value, opts \\ [])
def new(lexical, opts) when is_binary(lexical) do
case lexical_mapping(lexical, opts) do
@invalid_value ->
build_invalid(lexical, opts)
value ->
if facet_conform?(value, lexical) do
build_valid(value, lexical, opts)
else
build_invalid(lexical, opts)
end
end
end
def new(value, opts) do
case elixir_mapping(value, opts) do
@invalid_value ->
build_invalid(value, opts)
value ->
{value, lexical} =
case value do
{value, lexical} -> {value, lexical}
value -> {value, nil}
end
if facet_conform?(value, lexical) do
build_valid(value, lexical, opts)
else
build_invalid(value, opts)
end
end
end
@impl RDF.Literal.Datatype
def new!(value, opts \\ []) do
literal = new(value, opts)
if valid?(literal) do
literal
else
raise ArgumentError, "#{inspect(value)} is not a valid #{inspect(__MODULE__)}"
end
end
@doc false
@spec build_valid(any, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) :: t()
def build_valid(value, lexical, opts) do
if Keyword.get(opts, :canonicalize) do
literal(%__MODULE__{value: value})
else
initial_lexical = init_valid_lexical(value, lexical, opts)
literal(%__MODULE__{
value: value,
uncanonical_lexical:
if(initial_lexical && initial_lexical != canonical_mapping(value),
do: initial_lexical
)
})
end
end
defp build_invalid(lexical, opts) do
literal(%__MODULE__{uncanonical_lexical: init_invalid_lexical(lexical, opts)})
end
def cast(literal_or_value)
def cast(%RDF.Literal{literal: literal}), do: cast(literal)
# Invalid values can not be casted in general
def cast(%{value: @invalid_value}), do: nil
def cast(%__MODULE__{} = datatype_literal), do: literal(datatype_literal)
def cast(nil), do: nil
def cast(value) do
case do_cast(value) do
%__MODULE__{} = literal -> if valid?(literal), do: literal(literal)
%RDF.Literal{literal: %__MODULE__{}} = literal -> if valid?(literal), do: literal
_ -> nil
end
end
@impl RDF.Literal.Datatype
def value(%RDF.Literal{literal: literal}), do: value(literal)
def value(%__MODULE__{} = literal), do: literal.value
@impl RDF.Literal.Datatype
def lexical(lexical)
def lexical(%RDF.Literal{literal: literal}), do: lexical(literal)
def lexical(%__MODULE__{value: value, uncanonical_lexical: nil}),
do: canonical_mapping(value)
def lexical(%__MODULE__{uncanonical_lexical: lexical}), do: lexical
@impl RDF.Literal.Datatype
@spec canonical(t()) :: t()
def canonical(literal)
def canonical(%RDF.Literal{literal: %__MODULE__{uncanonical_lexical: nil}} = literal),
do: literal
def canonical(%RDF.Literal{literal: %__MODULE__{value: @invalid_value}} = literal),
do: literal
def canonical(%RDF.Literal{literal: %__MODULE__{} = literal}),
do: canonical(literal)
def canonical(%__MODULE__{} = literal),
do: literal(%__MODULE__{literal | uncanonical_lexical: nil})
@impl RDF.Literal.Datatype
def canonical?(literal)
def canonical?(%RDF.Literal{literal: literal}), do: canonical?(literal)
def canonical?(%__MODULE__{uncanonical_lexical: nil}), do: true
def canonical?(%__MODULE__{}), do: false
@impl RDF.Literal.Datatype
def valid?(literal)
def valid?(%RDF.Literal{literal: literal}), do: valid?(literal)
def valid?(%__MODULE__{value: @invalid_value}), do: false
def valid?(%__MODULE__{}), do: true
def valid?(_), do: false
def canonical_lexical(literal)
def canonical_lexical(%RDF.Literal{literal: literal}), do: canonical_lexical(literal)
def canonical_lexical(%__MODULE__{value: nil}), do: nil
def canonical_lexical(%__MODULE__{value: value, uncanonical_lexical: nil}),
do: canonical_mapping(value)
def canonical_lexical(%__MODULE__{} = literal),
do: literal |> canonical() |> lexical()
def canonical_lexical(_), do: nil
@doc """
Matches the string representation of the given value against a XPath and XQuery regular expression pattern.
The regular expression language is defined in _XQuery 1.0 and XPath 2.0 Functions and Operators_.
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@impl RDF.XSD.Datatype
def matches?(literal, pattern, flags \\ "")
def matches?(%RDF.Literal{literal: literal}, pattern, flags), do: matches?(literal, pattern, flags)
def matches?(%__MODULE__{} = literal, pattern, flags) do
literal
|> lexical()
|> RDF.XSD.Utils.Regex.matches?(pattern, flags)
end
defimpl Inspect do
"Elixir.Inspect." <> datatype_name = to_string(__MODULE__)
@datatype_name datatype_name
def inspect(literal, _opts) do
"%#{@datatype_name}{value: #{inspect(literal.value)}, lexical: #{
literal |> literal.__struct__.lexical() |> inspect()
}}"
end
end
end
end
@spec base_primitive(t()) :: t()
def base_primitive(%RDF.Literal{literal: literal}), do: base_primitive(literal)
def base_primitive(%datatype{}), do: base_primitive(datatype)
def base_primitive(datatype), do: datatype.base_primitive()
@spec derived_from?(t() | literal() | RDF.Literal.t(), t()) :: boolean
def derived_from?(%RDF.Literal{literal: literal}, super_datatype), do: derived_from?(literal, super_datatype)
def derived_from?(%datatype{}, super_datatype), do: derived_from?(datatype, super_datatype)
def derived_from?(datatype, super_datatype) when is_atom(datatype), do: datatype.derived_from?(super_datatype)
end

View file

@ -0,0 +1,105 @@
defmodule RDF.XSD.Datatype.Primitive do
defmacro def_applicable_facet(facet) do
quote do
@applicable_facets unquote(facet)
use unquote(facet)
end
end
defmacro __using__(opts) do
quote do
use RDF.XSD.Datatype, unquote(opts)
import unquote(__MODULE__)
Module.register_attribute(__MODULE__, :applicable_facets, accumulate: true)
@impl RDF.XSD.Datatype
def primitive?, do: true
@impl RDF.XSD.Datatype
def base, do: nil
@impl RDF.XSD.Datatype
def base_primitive, do: __MODULE__
@impl RDF.XSD.Datatype
def init_valid_lexical(value, lexical, opts)
def init_valid_lexical(_value, nil, _opts), do: nil
def init_valid_lexical(_value, lexical, _opts), do: lexical
@impl RDF.XSD.Datatype
def init_invalid_lexical(value, _opts), do: to_string(value)
@doc false
# Optimization: facets are generally unconstrained on primitives
def facet_conform?(_, _), do: true
@impl RDF.XSD.Datatype
def canonical_mapping(value), do: to_string(value)
@impl RDF.Literal.Datatype
def do_cast(value) do
if RDF.XSD.literal?(value) do
if derived?(value) do
build_valid(value.value, value.uncanonical_lexical, [])
end
else
value |> RDF.Literal.coerce() |> cast()
end
end
@impl RDF.Literal.Datatype
def do_equal_value?(left, right)
def do_equal_value?(
%datatype{uncanonical_lexical: lexical1, value: nil},
%datatype{uncanonical_lexical: lexical2, value: nil}
) do
lexical1 == lexical2
end
def do_equal_value?(%datatype{} = literal1, %datatype{} = literal2) do
literal1 |> datatype.canonical() |> datatype.value() ==
literal2 |> datatype.canonical() |> datatype.value()
end
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
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(
%__MODULE__{value: left_value} = left,
%__MODULE__{value: right_value} = right
)
when not (is_nil(left_value) or is_nil(right_value)) do
case {left |> canonical() |> value(), right |> canonical() |> value()} do
{value1, value2} when value1 < value2 -> :lt
{value1, value2} when value1 > value2 -> :gt
_ -> if equal_value?(left, right), do: :eq
end
end
def compare(_, _), do: nil
defoverridable canonical_mapping: 1,
do_cast: 1,
init_valid_lexical: 3,
init_invalid_lexical: 2,
do_equal_value?: 2,
compare: 2
@before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(_env) do
quote do
@impl RDF.XSD.Datatype
def applicable_facets, do: @applicable_facets
end
end
end

View file

@ -0,0 +1,99 @@
defmodule RDF.XSD.Datatype.Restriction do
defmacro __using__(opts) do
base = Keyword.fetch!(opts, :base)
quote do
use RDF.XSD.Datatype, unquote(opts)
import RDF.XSD.Facet, only: [def_facet_constraint: 2]
@type valid_value :: unquote(base).valid_value()
@base unquote(base)
@impl RDF.XSD.Datatype
@spec base :: RDF.XSD.Datatype.t()
def base, do: @base
@impl RDF.XSD.Datatype
def primitive?, do: false
@impl RDF.XSD.Datatype
def base_primitive, do: @base.base_primitive()
@impl RDF.XSD.Datatype
def applicable_facets, do: @base.applicable_facets()
@impl RDF.XSD.Datatype
def init_valid_lexical(value, lexical, opts),
do: @base.init_valid_lexical(value, lexical, opts)
@impl RDF.XSD.Datatype
def init_invalid_lexical(value, opts),
do: @base.init_invalid_lexical(value, opts)
@doc false
def facet_conform?(value, lexical) do
Enum.all?(applicable_facets(), fn facet ->
facet.conform?(__MODULE__, value, lexical)
end)
end
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts),
do: @base.lexical_mapping(lexical, opts)
@impl RDF.XSD.Datatype
def elixir_mapping(value, opts),
do: @base.elixir_mapping(value, opts)
@impl RDF.XSD.Datatype
def canonical_mapping(value),
do: @base.canonical_mapping(value)
@impl RDF.Literal.Datatype
def do_cast(literal_or_value) do
# Note: This direct call of the cast/1 implementation of the base_primitive
# is an optimization to not have go through the whole derivation chain and
# doing potentially a lot of redundant validations, but this relies on
# cast/1 not being overridden on restrictions.
case base_primitive().cast(literal_or_value) do
nil ->
nil
%RDF.Literal{literal: %{value: value, uncanonical_lexical: lexical}} ->
if facet_conform?(value, lexical) do
build_valid(value, lexical, [])
end
end
end
# TODO: This makes it impossible to define do_equal_value definitions on derivations,
# but we need to overwrite this to reach for example the XSD.Numeric delegation.
def equal_value?(literal1, literal2), do: @base.equal_value?(literal1, literal2)
@impl RDF.Literal.Datatype
def do_equal_value?(left, right), do: nil # unused; see comment on equal_value?/2
@impl RDF.Literal.Datatype
def compare(left, right), do: @base.compare(left, right)
defoverridable canonical_mapping: 1,
do_cast: 1,
equal_value?: 2,
compare: 2
Module.register_attribute(__MODULE__, :facets, accumulate: true)
@before_compile unquote(__MODULE__)
end
end
defmacro __before_compile__(env) do
import RDF.XSD.Facet
restriction_impl(
Module.get_attribute(env.module, :facets),
Module.get_attribute(env.module, :base).applicable_facets
)
end
end

View file

@ -0,0 +1,22 @@
defmodule RDF.XSD.AnyURI do
@moduledoc """
`RDF.XSD.Datatype` for XSD anyURIs.
See: <http://www.w3.org/TR/xmlschema11-2/#anyURI>
"""
@type valid_value :: URI.t()
use RDF.XSD.Datatype.Primitive,
name: "anyURI",
id: RDF.Utils.Bootstrapping.xsd_iri("anyURI")
@impl RDF.XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value
def lexical_mapping(lexical, _), do: URI.parse(lexical)
@impl RDF.XSD.Datatype
@spec elixir_mapping(any, Keyword.t()) :: value
def elixir_mapping(%URI{} = uri, _), do: uri
def elixir_mapping(_, _), do: @invalid_value
end

View file

@ -0,0 +1,232 @@
defmodule RDF.XSD.Boolean do
@moduledoc """
`RDF.XSD.Datatype` for XSD booleans.
"""
@type valid_value :: boolean
@type input_value :: RDF.XSD.Literal.t() | valid_value | number | String.t() | any
use RDF.XSD.Datatype.Primitive,
name: "boolean",
id: RDF.Utils.Bootstrapping.xsd_iri("boolean")
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, _) do
with lexical do
cond do
lexical in ~W[true 1] -> true
lexical in ~W[false 0] -> false
true -> @invalid_value
end
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | integer | any, Keyword.t()) :: value
def elixir_mapping(value, _)
def elixir_mapping(value, _) when is_boolean(value), do: value
def elixir_mapping(1, _), do: true
def elixir_mapping(0, _), do: false
def elixir_mapping(_, _), do: @invalid_value
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.String{} = xsd_string) do
xsd_string.value |> new() |> canonical()
end
def do_cast(%RDF.XSD.Decimal{} = xsd_decimal) do
!Decimal.equal?(xsd_decimal.value, 0) |> new()
end
def do_cast(literal_or_value) do
if RDF.XSD.Numeric.literal?(literal_or_value) do
new(literal_or_value.value not in [0, 0.0, :nan])
else
super(literal_or_value)
end
end
@doc """
Returns an Effective Boolean Value (EBV).
The Effective Boolean Value is an algorithm to coerce values to a `RDF.XSD.Boolean`.
It is specified and used in the SPARQL query language and is based upon XPath's
`fn:boolean`. Other than specified in these specs any value which can not be
converted into a boolean results in `nil`.
see
- <https://www.w3.org/TR/xpath-31/#id-ebv>
- <https://www.w3.org/TR/sparql11-query/#ebv>
"""
@spec ebv(input_value) :: t() | nil
def ebv(value)
def ebv(%RDF.Literal{literal: literal}), do: ebv(literal)
def ebv(true), do: RDF.XSD.Boolean.Value.true()
def ebv(false), do: RDF.XSD.Boolean.Value.false()
def ebv(%__MODULE__{value: nil}), do: RDF.XSD.Boolean.Value.false()
def ebv(%__MODULE__{} = value), do: literal(value)
def ebv(%RDF.XSD.String{} = string) do
if String.length(string.value) == 0,
do: RDF.XSD.Boolean.Value.false(),
else: RDF.XSD.Boolean.Value.true()
end
def ebv(%datatype{} = literal) do
if RDF.XSD.Numeric.datatype?(datatype) do
if datatype.valid?(literal) and
not (literal.value == 0 or literal.value == :nan),
do: RDF.XSD.Boolean.Value.true(),
else: RDF.XSD.Boolean.Value.false()
end
end
def ebv(value) when is_binary(value) or is_number(value) do
value |> RDF.Literal.coerce() |> ebv()
end
def ebv(_), do: nil
@doc """
Alias for `ebv/1`.
"""
@spec effective(input_value) :: t() | nil
def effective(value), do: ebv(value)
@doc """
Returns `RDF.XSD.true` if the effective boolean value of the given argument is `RDF.XSD.false`, or `RDF.XSD.false` if it is `RDF.XSD.true`.
Otherwise it returns `nil`.
## Examples
iex> RDF.XSD.Boolean.fn_not(RDF.XSD.true)
RDF.XSD.false
iex> RDF.XSD.Boolean.fn_not(RDF.XSD.false)
RDF.XSD.true
iex> RDF.XSD.Boolean.fn_not(true)
RDF.XSD.false
iex> RDF.XSD.Boolean.fn_not(false)
RDF.XSD.true
iex> RDF.XSD.Boolean.fn_not(42)
RDF.XSD.false
iex> RDF.XSD.Boolean.fn_not("")
RDF.XSD.true
iex> RDF.XSD.Boolean.fn_not(nil)
nil
see <https://www.w3.org/TR/xpath-functions/#func-not>
"""
@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}} -> RDF.XSD.Boolean.Value.false()
%RDF.Literal{literal: %__MODULE__{value: false}} -> RDF.XSD.Boolean.Value.true()
nil -> nil
end
end
@doc """
Returns the logical `AND` of the effective boolean value of the given arguments.
It returns `nil` if only one argument is `nil` and the other argument is
`RDF.XSD.true` and `RDF.XSD.false` if the other argument is `RDF.XSD.false`.
## Examples
iex> RDF.XSD.Boolean.logical_and(RDF.XSD.true, RDF.XSD.true)
RDF.XSD.true
iex> RDF.XSD.Boolean.logical_and(RDF.XSD.true, RDF.XSD.false)
RDF.XSD.false
iex> RDF.XSD.Boolean.logical_and(RDF.XSD.true, nil)
nil
iex> RDF.XSD.Boolean.logical_and(nil, RDF.XSD.false)
RDF.XSD.false
iex> RDF.XSD.Boolean.logical_and(nil, nil)
nil
see <https://www.w3.org/TR/sparql11-query/#func-logical-and>
"""
@spec logical_and(input_value, input_value) :: t() | nil
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}} ->
RDF.XSD.Boolean.Value.false()
%RDF.Literal{literal: %__MODULE__{value: true}} ->
case ebv(right) do
%RDF.Literal{literal: %__MODULE__{value: true}} -> RDF.XSD.Boolean.Value.true()
%RDF.Literal{literal: %__MODULE__{value: false}} -> RDF.XSD.Boolean.Value.false()
nil -> nil
end
nil ->
if match?(%RDF.Literal{literal: %__MODULE__{value: false}}, ebv(right)) do
RDF.XSD.Boolean.Value.false()
end
end
end
@doc """
Returns the logical `OR` of the effective boolean value of the given arguments.
It returns `nil` if only one argument is `nil` and the other argument is
`RDF.XSD.false` and `RDF.XSD.true` if the other argument is `RDF.XSD.true`.
## Examples
iex> RDF.XSD.Boolean.logical_or(RDF.XSD.true, RDF.XSD.false)
RDF.XSD.true
iex> RDF.XSD.Boolean.logical_or(RDF.XSD.false, RDF.XSD.false)
RDF.XSD.false
iex> RDF.XSD.Boolean.logical_or(RDF.XSD.true, nil)
RDF.XSD.true
iex> RDF.XSD.Boolean.logical_or(nil, RDF.XSD.false)
nil
iex> RDF.XSD.Boolean.logical_or(nil, nil)
nil
see <https://www.w3.org/TR/sparql11-query/#func-logical-or>
"""
@spec logical_or(input_value, input_value) :: t() | nil
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}} ->
RDF.XSD.Boolean.Value.true()
%RDF.Literal{literal: %__MODULE__{value: false}} ->
case ebv(right) do
%RDF.Literal{literal: %__MODULE__{value: true}} -> RDF.XSD.Boolean.Value.true()
%RDF.Literal{literal: %__MODULE__{value: false}} -> RDF.XSD.Boolean.Value.false()
nil -> nil
end
nil ->
if match?(%RDF.Literal{literal: %__MODULE__{value: true}}, ebv(right)) do
RDF.XSD.Boolean.Value.true()
end
end
end
end

View file

@ -2,6 +2,8 @@ defmodule RDF.XSD.Boolean.Value do
@moduledoc !""" @moduledoc !"""
This module holds the two boolean value literals, so they can be accessed This module holds the two boolean value literals, so they can be accessed
directly without needing to construct them every time. 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)

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.Byte do
use RDF.XSD.Datatype.Restriction,
name: "byte",
id: RDF.Utils.Bootstrapping.xsd_iri("byte"),
base: RDF.XSD.Short
def_facet_constraint RDF.XSD.Facets.MinInclusive, -128
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 127
end

View file

@ -0,0 +1,231 @@
defmodule RDF.XSD.Date do
@moduledoc """
`RDF.XSD.Datatype` for XSD date.
Options:
- `tz` ... it will also overwrite an eventually already present timezone in an input lexical ...
"""
@type valid_value :: Date.t() | {Date.t(), String.t()}
use RDF.XSD.Datatype.Primitive,
name: "date",
id: RDF.Utils.Bootstrapping.xsd_iri("date")
# 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/
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do
case Regex.run(@grammar, lexical) do
[_, date] -> do_lexical_mapping(date, opts)
[_, date, tz] -> do_lexical_mapping(date, Keyword.put_new(opts, :tz, tz))
_ -> @invalid_value
end
end
defp do_lexical_mapping(value, opts) do
case Date.from_iso8601(value) do
{:ok, date} -> elixir_mapping(date, opts)
_ -> @invalid_value
end
|> case do
{{_, _} = value, _} -> value
value -> value
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(Date.t() | any, Keyword.t()) ::
value | {value, RDF.XSD.Datatype.uncanonical_lexical()}
def elixir_mapping(value, opts)
# Special case for date and dateTime, for which 0 is not a valid year
def elixir_mapping(%Date{year: 0}, _), do: @invalid_value
def elixir_mapping(%Date{} = value, opts) do
if tz = Keyword.get(opts, :tz) do
if valid_timezone?(tz) do
{{value, timezone_mapping(tz)}, nil}
else
@invalid_value
end
else
value
end
end
def elixir_mapping(_, _), do: @invalid_value
defp valid_timezone?(string), do: Regex.match?(@tz_grammar, string)
defp timezone_mapping("+00:00"), do: "Z"
defp timezone_mapping(tz), do: tz
@impl RDF.XSD.Datatype
@spec canonical_mapping(valid_value) :: String.t()
def canonical_mapping(value)
def canonical_mapping(%Date{} = value), do: Date.to_iso8601(value)
def canonical_mapping({%Date{} = value, "+00:00"}), do: canonical_mapping(value) <> "Z"
def canonical_mapping({%Date{} = value, zone}), do: canonical_mapping(value) <> zone
@impl RDF.XSD.Datatype
@spec init_valid_lexical(valid_value, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) ::
RDF.XSD.Datatype.uncanonical_lexical()
def init_valid_lexical(value, lexical, opts)
def init_valid_lexical({value, _}, nil, opts) do
if tz = Keyword.get(opts, :tz) do
canonical_mapping(value) <> tz
else
nil
end
end
def init_valid_lexical(_, nil, _), do: nil
def init_valid_lexical(_, lexical, opts) do
if tz = Keyword.get(opts, :tz) do
# When using the :tz option, we'll have to strip off the original timezone
case Regex.run(@grammar, lexical) do
[_, date] -> date
[_, date, _] -> date
end <> tz
else
lexical
end
end
@impl RDF.XSD.Datatype
@spec init_invalid_lexical(any, Keyword.t()) :: String.t()
def init_invalid_lexical(value, opts)
def init_invalid_lexical({date, tz}, opts) do
if tz_opt = Keyword.get(opts, :tz) do
to_string(date) <> tz_opt
else
to_string(date) <> to_string(tz)
end
end
def init_invalid_lexical(value, _) when is_binary(value), do: value
def init_invalid_lexical(value, opts) do
if tz = Keyword.get(opts, :tz) do
to_string(value) <> tz
else
to_string(value)
end
end
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.DateTime{} = xsd_datetime) do
case xsd_datetime.value do
%NaiveDateTime{} = datetime ->
datetime
|> NaiveDateTime.to_date()
|> new()
%DateTime{} = datetime ->
datetime
|> DateTime.to_date()
|> new(tz: RDF.XSD.DateTime.tz(xsd_datetime))
end
end
def do_cast(%RDF.XSD.String{} = xsd_string), do: new(xsd_string.value)
def do_cast(literal_or_value), do: super(literal_or_value)
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(
%__MODULE__{value: nil, uncanonical_lexical: lexical1},
%__MODULE__{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
end
def do_equal_value?(%__MODULE__{value: value1}, %__MODULE__{value: value2})
when is_nil(value1) or is_nil(value2),
do: false
def do_equal_value?(%__MODULE__{value: value1}, %__MODULE__{value: value2}) do
RDF.XSD.DateTime.equal_value?(
comparison_normalization(value1),
comparison_normalization(value2)
)
end
def do_equal_value?(%__MODULE__{}, %RDF.XSD.DateTime{}), do: false
def do_equal_value?(%RDF.XSD.DateTime{}, %__MODULE__{}), do: false
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
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(
%__MODULE__{value: value1},
%__MODULE__{value: value2}
)
when is_nil(value1) or is_nil(value2),
do: nil
def compare(
%__MODULE__{value: value1},
%__MODULE__{value: value2}
) do
RDF.XSD.DateTime.compare(
comparison_normalization(value1),
comparison_normalization(value2)
)
end
# It seems quite strange that open-world test date-2 from the SPARQL 1.0 test suite
# allows for equality comparisons between dates and datetimes, but disallows
# ordering comparisons in the date-3 test. The following implementation would allow
# an ordering comparisons between date and datetimes.
#
# def compare(
# %__MODULE__{value: date_value},
# %RDF.XSD.DateTime{} = datetime_literal
# ) do
# RDF.XSD.DateTime.compare(
# comparison_normalization(date_value),
# datetime_literal
# )
# end
#
# def compare(
# %RDF.XSD.DateTime{} = datetime_literal,
# %__MODULE__{value: date_value}
# ) do
# RDF.XSD.DateTime.compare(
# datetime_literal,
# comparison_normalization(date_value)
# )
# end
def compare(_, _), do: nil
defp comparison_normalization({date, tz}) do
(Date.to_iso8601(date) <> "T00:00:00" <> tz)
|> RDF.XSD.DateTime.new()
end
defp comparison_normalization(%Date{} = date) do
(Date.to_iso8601(date) <> "T00:00:00")
|> RDF.XSD.DateTime.new()
end
defp comparison_normalization(_), do: nil
end

View file

@ -0,0 +1,223 @@
defmodule RDF.XSD.DateTime do
@moduledoc """
`RDF.XSD.Datatype` for XSD dateTimes.
"""
@type valid_value :: DateTime.t() | NaiveDateTime.t()
use RDF.XSD.Datatype.Primitive,
name: "dateTime",
id: RDF.Utils.Bootstrapping.xsd_iri("dateTime")
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do
case DateTime.from_iso8601(lexical) do
{:ok, datetime, _} ->
elixir_mapping(datetime, opts)
{:error, :missing_offset} ->
case NaiveDateTime.from_iso8601(lexical) do
{:ok, datetime} -> elixir_mapping(datetime, opts)
_ -> @invalid_value
end
{:error, :invalid_format} ->
if String.ends_with?(lexical, "-00:00") do
lexical
|> String.replace_trailing("-00:00", "Z")
|> lexical_mapping(opts)
else
@invalid_value
end
{:error, :invalid_time} ->
if String.contains?(lexical, "T24:00:00") do
with [day, tz] <- String.split(lexical, "T24:00:00", parts: 2),
{:ok, day} <- Date.from_iso8601(day) do
lexical_mapping("#{day |> Date.add(1) |> Date.to_string()}T00:00:00#{tz}", opts)
else
_ -> @invalid_value
end
else
@invalid_value
end
_ ->
@invalid_value
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | any, Keyword.t()) :: value
def elixir_mapping(value, _)
# Special case for date and dateTime, for which 0 is not a valid year
def elixir_mapping(%DateTime{year: 0}, _), do: @invalid_value
def elixir_mapping(%DateTime{} = value, _), do: value
# Special case for date and dateTime, for which 0 is not a valid year
def elixir_mapping(%NaiveDateTime{year: 0}, _), do: @invalid_value
def elixir_mapping(%NaiveDateTime{} = value, _), do: value
def elixir_mapping(_, _), do: @invalid_value
@impl RDF.XSD.Datatype
@spec canonical_mapping(valid_value) :: String.t()
def canonical_mapping(value)
def canonical_mapping(%DateTime{} = value), do: DateTime.to_iso8601(value)
def canonical_mapping(%NaiveDateTime{} = value), do: NaiveDateTime.to_iso8601(value)
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.Date{} = xsd_date) do
case xsd_date.value do
{value, zone} ->
(value |> RDF.XSD.Date.new() |> RDF.XSD.Date.canonical_lexical()) <> "T00:00:00" <> zone
value ->
(value |> RDF.XSD.Date.new() |> RDF.XSD.Date.canonical_lexical()) <> "T00:00:00"
end
|> new()
end
def do_cast(%RDF.XSD.String{} = xsd_string), do: new(xsd_string.value)
def do_cast(literal_or_value), do: super(literal_or_value)
@doc """
Builds a `RDF.XSD.DateTime` literal for current moment in time.
"""
@spec now() :: t()
def now() do
new(DateTime.utc_now())
end
@doc """
Extracts the timezone string from a `RDF.XSD.DateTime` value.
"""
@spec tz(RDF.Literal.t() | t()) :: String.t() | nil
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
|> lexical()
|> RDF.XSD.Utils.DateTime.tz()
end
end
@doc """
Converts a datetime literal to a canonical string, preserving the zone information.
"""
@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 ->
nil
zone when zone in ["Z", "", "+00:00", "-00:00"] ->
canonical_lexical(xsd_datetime)
zone ->
xsd_datetime
|> lexical()
|> String.replace_trailing(zone, "Z")
|> DateTime.from_iso8601()
|> elem(1)
|> new()
|> canonical_lexical()
|> String.replace_trailing("Z", zone)
end
end
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(
%__MODULE__{value: %type{} = value1},
%__MODULE__{value: %type{} = value2}
) do
type.compare(value1, value2) == :eq
end
def do_equal_value?(
%__MODULE__{value: nil, uncanonical_lexical: lexical1},
%__MODULE__{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
end
# This is another quirk for the open-world test date-2 from the SPARQL 1.0 test suite:
# comparisons between one date with tz and another one without a tz are incomparable
# when the unequal, but comparable and returning false when equal.
# What's the reasoning behind this madness?
def do_equal_value?(%__MODULE__{} = literal1, %__MODULE__{} = literal2) do
case compare(literal1, literal2) do
:lt -> false
:gt -> false
# This actually can't/shouldn't happen.
:eq -> true
_ -> nil
end
end
def do_equal_value?(%__MODULE__{}, %RDF.XSD.Date{}), do: false
def do_equal_value?(%RDF.XSD.Date{}, %__MODULE__{}), do: false
def do_equal_value?(_, _), do: nil
@impl RDF.Literal.Datatype
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(
%__MODULE__{value: %type{} = value1},
%__MODULE__{value: %type{} = value2}
) do
type.compare(value1, value2)
end
# It seems quite strange that open-world test date-2 from the SPARQL 1.0 test suite
# allows for equality comparisons between dates and datetimes, but disallows
# ordering comparisons in the date-3 test. The following implementation would allow
# an ordering comparisons between date and datetimes.
#
# def compare(%__MODULE__{} = literal1, %RDF.XSD.Date{} = literal2) do
# RDF.XSD.Date.compare(literal1, literal2)
# end
#
# def compare(%RDF.XSD.Date{} = literal1, %__MODULE__{} = literal2) do
# RDF.XSD.Date.compare(literal1, literal2)
# end
def compare(
%__MODULE__{value: %DateTime{}} = left,
%__MODULE__{value: %NaiveDateTime{} = right_value}
) do
cond do
compare(left, new(to_datetime(right_value, "+"))) == :lt -> :lt
compare(left, new(to_datetime(right_value, "-"))) == :gt -> :gt
true -> :indeterminate
end
end
def compare(
%__MODULE__{value: %NaiveDateTime{} = left},
%__MODULE__{value: %DateTime{}} = right_literal
) do
cond do
compare(new(to_datetime(left, "-")), right_literal) == :lt -> :lt
compare(new(to_datetime(left, "+")), right_literal) == :gt -> :gt
true -> :indeterminate
end
end
def compare(_, _), do: nil
defp to_datetime(naive_datetime, offset) do
(NaiveDateTime.to_iso8601(naive_datetime) <> offset <> "14:00")
|> DateTime.from_iso8601()
|> elem(1)
end
end

View file

@ -0,0 +1,152 @@
defmodule RDF.XSD.Decimal do
@moduledoc """
`RDF.XSD.Datatype` for XSD decimals.
"""
@type valid_value :: Decimal.t()
use RDF.XSD.Datatype.Primitive,
name: "decimal",
id: RDF.Utils.Bootstrapping.xsd_iri("decimal")
alias Elixir.Decimal, as: D
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do
if String.contains?(lexical, ~w[e E]) do
@invalid_value
else
case D.parse(lexical) do
{:ok, decimal} -> elixir_mapping(decimal, opts)
:error -> @invalid_value
end
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | integer | float | any, Keyword.t()) :: value
def elixir_mapping(value, _)
def elixir_mapping(%D{coef: coef}, _) when coef in ~w[qNaN sNaN inf]a,
do: @invalid_value
def elixir_mapping(%D{} = decimal, _),
do: canonical_decimal(decimal)
def elixir_mapping(value, opts) when is_integer(value),
do: value |> D.new() |> elixir_mapping(opts)
def elixir_mapping(value, opts) when is_float(value),
do: value |> D.from_float() |> elixir_mapping(opts)
def elixir_mapping(_, _), do: @invalid_value
@doc false
@spec canonical_decimal(valid_value) :: valid_value
def canonical_decimal(decimal)
def canonical_decimal(%D{coef: 0} = decimal),
do: %{decimal | exp: -1}
def canonical_decimal(%D{coef: coef, exp: 0} = decimal),
do: %{decimal | coef: coef * 10, exp: -1}
def canonical_decimal(%D{coef: coef, exp: exp} = decimal)
when exp > 0,
do: canonical_decimal(%{decimal | coef: coef * 10, exp: exp - 1})
def canonical_decimal(%D{coef: coef} = decimal)
when Kernel.rem(coef, 10) != 0,
do: decimal
def canonical_decimal(%D{coef: coef, exp: exp} = decimal),
do: canonical_decimal(%{decimal | coef: Kernel.div(coef, 10), exp: exp + 1})
@impl RDF.XSD.Datatype
@spec canonical_mapping(valid_value) :: String.t()
def canonical_mapping(value)
def canonical_mapping(%D{sign: sign, coef: :qNaN}),
do: if(sign == 1, do: "NaN", else: "-NaN")
def canonical_mapping(%D{sign: sign, coef: :sNaN}),
do: if(sign == 1, do: "sNaN", else: "-sNaN")
def canonical_mapping(%D{sign: sign, coef: :inf}),
do: if(sign == 1, do: "Infinity", else: "-Infinity")
def canonical_mapping(%D{} = decimal),
do: D.to_string(decimal, :normal)
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.Boolean{value: false}), do: new(0.0)
def do_cast(%RDF.XSD.Boolean{value: true}), do: new(1.0)
def do_cast(%RDF.XSD.String{} = xsd_string) do
xsd_string.value |> new() |> canonical()
end
def do_cast(%RDF.XSD.Integer{} = xsd_integer), do: new(xsd_integer.value)
def do_cast(%RDF.XSD.Double{value: value}) when is_float(value), do: new(value)
def do_cast(%RDF.XSD.Float{value: value}) when is_float(value), do: new(value)
def do_cast(literal_or_value), do: super(literal_or_value)
def equal_value?(left, right), do: RDF.XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: RDF.XSD.Numeric.compare(left, right)
@doc """
The number of digits in the XML Schema canonical form of the literal value.
"""
@spec digit_count(RDF.XSD.Literal.t()) :: non_neg_integer | nil
def digit_count(%__MODULE__{} = literal), do: do_digit_count(literal)
def digit_count(literal) do
cond do
RDF.XSD.Integer.derived?(literal) -> RDF.XSD.Integer.digit_count(literal)
derived?(literal) -> do_digit_count(literal)
true -> nil
end
end
defp do_digit_count(%datatype{} = literal) do
if datatype.valid?(literal) do
literal
|> datatype.canonical()
|> datatype.lexical()
|> String.replace(".", "")
|> String.replace("-", "")
|> String.length()
end
end
@doc """
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.XSD.Literal.t()) :: non_neg_integer | nil
def fraction_digit_count(%__MODULE__{} = literal), do: do_fraction_digit_count(literal)
def fraction_digit_count(literal) do
cond do
RDF.XSD.Integer.derived?(literal) -> 0
derived?(literal) -> do_fraction_digit_count(literal)
true -> nil
end
end
defp do_fraction_digit_count(%datatype{} = literal) do
if datatype.valid?(literal) do
[_, fraction] =
literal
|> datatype.canonical()
|> datatype.lexical()
|> String.split(".")
String.length(fraction)
end
end
end

View file

@ -0,0 +1,125 @@
defmodule RDF.XSD.Double do
@moduledoc """
`RDF.XSD.Datatype` for XSD doubles.
"""
@type special_values :: :positive_infinity | :negative_infinity | :nan
@type valid_value :: float | special_values
use RDF.XSD.Datatype.Primitive,
name: "double",
id: RDF.Utils.Bootstrapping.xsd_iri("double")
@special_values ~W[positive_infinity negative_infinity nan]a
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do
case Float.parse(lexical) do
{float, ""} ->
float
{float, remainder} ->
# 1.E-8 is not a valid Elixir float literal and consequently not fully parsed with Float.parse
if Regex.match?(~r/^\.e?[\+\-]?\d+$/i, remainder) do
lexical_mapping(to_string(float) <> String.trim_leading(remainder, "."), opts)
else
@invalid_value
end
:error ->
case String.upcase(lexical) do
"INF" -> :positive_infinity
"-INF" -> :negative_infinity
"NAN" -> :nan
_ -> @invalid_value
end
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | integer | any, Keyword.t()) :: value
def elixir_mapping(value, _)
def elixir_mapping(value, _) when is_float(value), do: value
def elixir_mapping(value, _) when is_integer(value), do: value / 1
def elixir_mapping(value, _) when value in @special_values, do: value
def elixir_mapping(_, _), do: @invalid_value
@impl RDF.XSD.Datatype
@spec init_valid_lexical(valid_value, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) ::
RDF.XSD.Datatype.uncanonical_lexical()
def init_valid_lexical(value, lexical, opts)
def init_valid_lexical(value, nil, _) when is_atom(value), do: nil
def init_valid_lexical(value, nil, _), do: decimal_form(value)
def init_valid_lexical(_, lexical, _), do: lexical
defp decimal_form(float), do: to_string(float)
@impl RDF.XSD.Datatype
@spec canonical_mapping(valid_value) :: String.t()
def canonical_mapping(value)
# Produces the exponential form of a float
def canonical_mapping(float) when is_float(float) do
# We can't use simple %f transformation due to special requirements from N3 tests in representation
[i, f, e] =
float
|> float_to_string()
|> String.split(~r/[\.e]/)
# remove any trailing zeroes
f =
case String.replace(f, ~r/0*$/, "", global: false) do
# ...but there must be a digit to the right of the decimal point
"" -> "0"
f -> f
end
e = String.trim_leading(e, "+")
"#{i}.#{f}E#{e}"
end
def canonical_mapping(:nan), do: "NaN"
def canonical_mapping(:positive_infinity), do: "INF"
def canonical_mapping(:negative_infinity), do: "-INF"
if List.to_integer(:erlang.system_info(:otp_release)) >= 21 do
defp float_to_string(float) do
:io_lib.format("~.15e", [float])
|> to_string()
end
else
defp float_to_string(float) do
:io_lib.format("~.15e", [float])
|> List.first()
|> to_string()
end
end
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.Boolean{value: false}), do: new(0.0)
def do_cast(%RDF.XSD.Boolean{value: true}), do: new(1.0)
def do_cast(%RDF.XSD.String{} = xsd_string) do
xsd_string.value |> new() |> canonical()
end
def do_cast(%RDF.XSD.Integer{} = xsd_integer) do
new(xsd_integer.value)
end
def do_cast(%RDF.XSD.Decimal{} = xsd_decimal) do
xsd_decimal.value
|> Decimal.to_float()
|> new()
end
def do_cast(literal_or_value), do: super(literal_or_value)
def equal_value?(left, right), do: RDF.XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: RDF.XSD.Numeric.compare(left, right)
end

View file

@ -0,0 +1,13 @@
defmodule RDF.XSD.Float do
@moduledoc """
`RDF.XSD.Datatype` for XSD floats.
Although the XSD spec defines floats as a primitive we derive it here from `XSD.Double`
with any further constraints, since Erlang doesn't support 32-bit floats.
"""
use RDF.XSD.Datatype.Restriction,
name: "float",
id: RDF.Utils.Bootstrapping.xsd_iri("float"),
base: RDF.XSD.Double
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.Int do
use RDF.XSD.Datatype.Restriction,
name: "int",
id: RDF.Utils.Bootstrapping.xsd_iri("int"),
base: RDF.XSD.Long
def_facet_constraint RDF.XSD.Facets.MinInclusive, -2_147_483_648
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 2_147_483_647
end

View file

@ -0,0 +1,85 @@
defmodule RDF.XSD.Integer do
@moduledoc """
`RDF.XSD.Datatype` for XSD integers.
Although the XSD spec defines integers as derived from `xsd:decimal` we implement
it here as a primitive datatype for simplicity and performance reasons.
"""
@type valid_value :: integer
use RDF.XSD.Datatype.Primitive,
name: "integer",
id: RDF.Utils.Bootstrapping.xsd_iri("integer")
def_applicable_facet RDF.XSD.Facets.MinInclusive
def_applicable_facet RDF.XSD.Facets.MaxInclusive
def min_inclusive_conform?(min_inclusive, value, _lexical) do
value >= min_inclusive
end
def max_inclusive_conform?(max_inclusive, value, _lexical) do
value <= max_inclusive
end
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, _) do
case Integer.parse(lexical) do
{integer, ""} -> integer
{_, _} -> @invalid_value
:error -> @invalid_value
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | any, Keyword.t()) :: value
def elixir_mapping(value, _)
def elixir_mapping(value, _) when is_integer(value), do: value
def elixir_mapping(_, _), do: @invalid_value
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.Boolean{value: false}), do: new(0)
def do_cast(%RDF.XSD.Boolean{value: true}), do: new(1)
def do_cast(%RDF.XSD.String{} = xsd_string) do
xsd_string.value |> new() |> canonical()
end
def do_cast(%RDF.XSD.Decimal{} = xsd_decimal) do
xsd_decimal.value
|> Decimal.round(0, :down)
|> Decimal.to_integer()
|> new()
end
def do_cast(%datatype{value: value})
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and is_float(value) do
value
|> trunc()
|> new()
end
def do_cast(literal_or_value), do: super(literal_or_value)
def equal_value?(left, right), do: RDF.XSD.Numeric.equal_value?(left, right)
@impl RDF.Literal.Datatype
def compare(left, right), do: RDF.XSD.Numeric.compare(left, right)
@doc """
The number of digits in the XML Schema canonical form of the literal value.
"""
@spec digit_count(RDF.XSD.Literal.t()) :: non_neg_integer | nil
def digit_count(%datatype{} = literal) do
if derived?(literal) and datatype.valid?(literal) do
literal
|> datatype.canonical()
|> datatype.lexical()
|> String.replace("-", "")
|> String.length()
end
end
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.Long do
use RDF.XSD.Datatype.Restriction,
name: "long",
id: RDF.Utils.Bootstrapping.xsd_iri("long"),
base: RDF.XSD.Integer
def_facet_constraint RDF.XSD.Facets.MinInclusive, -9_223_372_036_854_775_808
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 9_223_372_036_854_775_807
end

View file

@ -0,0 +1,8 @@
defmodule RDF.XSD.NegativeInteger do
use RDF.XSD.Datatype.Restriction,
name: "negativeInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("negativeInteger"),
base: RDF.XSD.NonPositiveInteger
def_facet_constraint RDF.XSD.Facets.MaxInclusive, -1
end

View file

@ -0,0 +1,8 @@
defmodule RDF.XSD.NonNegativeInteger do
use RDF.XSD.Datatype.Restriction,
name: "nonNegativeInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("nonNegativeInteger"),
base: RDF.XSD.Integer
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
end

View file

@ -0,0 +1,8 @@
defmodule RDF.XSD.NonPositiveInteger do
use RDF.XSD.Datatype.Restriction,
name: "nonPositiveInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("nonPositiveInteger"),
base: RDF.XSD.Integer
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 0
end

View file

@ -0,0 +1,613 @@
defmodule RDF.XSD.Numeric do
@moduledoc """
Collection of functions for numeric literals.
"""
alias Elixir.Decimal, as: D
import Kernel, except: [abs: 1, floor: 1, ceil: 1]
@datatypes MapSet.new([
RDF.XSD.Decimal,
RDF.XSD.Integer,
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.Double,
RDF.XSD.Float
])
@type t ::
RDF.XSD.Decimal.t()
| RDF.XSD.Integer.t()
| RDF.XSD.Long.t()
| RDF.XSD.Int.t()
| RDF.XSD.Short.t()
| RDF.XSD.Byte.t()
| RDF.XSD.NonNegativeInteger.t()
| RDF.XSD.PositiveInteger.t()
| RDF.XSD.UnsignedLong.t()
| RDF.XSD.UnsignedInt.t()
| RDF.XSD.UnsignedShort.t()
| RDF.XSD.UnsignedByte.t()
| RDF.XSD.NonPositiveInteger.t()
| RDF.XSD.NegativeInteger.t()
| RDF.XSD.Double.t()
| RDF.XSD.Float.t()
@doc """
The set of all numeric datatypes.
"""
@spec datatypes() :: Enum.t
def datatypes(), do: @datatypes
@doc """
Returns if a given datatype is a numeric datatype.
"""
@spec datatype?(RDF.XSD.Datatype.t() | any) :: boolean
def datatype?(datatype), do: datatype in @datatypes
@doc """
Returns if a given XSD literal has a numeric datatype.
"""
@spec literal?(RDF.Literal.t() | any) :: boolean
def literal?(literal)
def literal?(%RDF.Literal{literal: literal}), do: literal?(literal)
def literal?(%datatype{}), do: datatype?(datatype)
def literal?(_), do: false
@doc """
Tests for numeric value equality of two numeric XSD datatyped literals.
see:
- <https://www.w3.org/TR/sparql11-query/#OperatorMapping>
- <https://www.w3.org/TR/xpath-functions/#func-numeric-equal>
"""
@spec equal_value?(t() | any, t() | any) :: boolean
def equal_value?(left, right)
def equal_value?(left, %RDF.Literal{literal: right}), do: equal_value?(left, right)
def equal_value?(%RDF.Literal{literal: left}, right), do: equal_value?(left, right)
def equal_value?(nil, _), do: nil
def equal_value?(_, nil), do: nil
def equal_value?(
%datatype{value: nil, uncanonical_lexical: lexical1},
%datatype{value: nil, uncanonical_lexical: lexical2}
) do
lexical1 == lexical2
end
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right})
when left_datatype == RDF.XSD.Decimal or right_datatype == RDF.XSD.Decimal,
do: not is_nil(left) and not is_nil(right) and equal_decimal_value?(left, right)
def equal_value?(%left_datatype{value: left}, %right_datatype{value: right}) do
if datatype?(left_datatype) and datatype?(right_datatype) do
left != :nan and right != :nan and left == right
end
end
def equal_value?(left, right),
do: equal_value?(RDF.Literal.coerce(left), RDF.Literal.coerce(right))
defp equal_decimal_value?(%D{} = left, %D{} = right), do: D.equal?(left, right)
defp equal_decimal_value?(%D{} = left, right),
do: equal_decimal_value?(left, new_decimal(right))
defp equal_decimal_value?(left, %D{} = right),
do: equal_decimal_value?(new_decimal(left), right)
defp equal_decimal_value?(_, _), do: false
defp new_decimal(value) when is_float(value), do: D.from_float(value)
defp new_decimal(value), do: D.new(value)
@doc """
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 `nil` when the given arguments are not comparable datatypes.
"""
@spec compare(t, t) :: RDF.XSD.Datatype.comparison_result() | 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(
%RDF.XSD.Decimal{value: left},
%right_datatype{value: right}
) do
if datatype?(right_datatype) do
compare_decimal_value(left, right)
end
end
def compare(
%left_datatype{value: left},
%RDF.XSD.Decimal{value: right}
) do
if datatype?(left_datatype) do
compare_decimal_value(left, right)
end
end
def compare(
%left_datatype{value: left},
%right_datatype{value: right}
)
when not (is_nil(left) or is_nil(right)) do
if datatype?(left_datatype) and datatype?(right_datatype) do
cond do
left < right -> :lt
left > right -> :gt
true -> :eq
end
end
end
def compare(_, _), do: nil
defp compare_decimal_value(%D{} = left, %D{} = right), do: D.cmp(left, right)
defp compare_decimal_value(%D{} = left, right),
do: compare_decimal_value(left, new_decimal(right))
defp compare_decimal_value(left, %D{} = right),
do: compare_decimal_value(new_decimal(left), right)
defp compare_decimal_value(_, _), do: nil
@spec zero?(any) :: boolean
def zero?(%RDF.Literal{literal: literal}), do: zero?(literal)
def zero?(%{value: value}), do: zero_value?(value)
defp zero_value?(zero) when zero == 0, do: true
defp zero_value?(%D{coef: 0}), do: true
defp zero_value?(_), do: false
@spec negative_zero?(any) :: boolean
def negative_zero?(%RDF.Literal{literal: literal}), do: negative_zero?(literal)
def negative_zero?(%{value: zero, uncanonical_lexical: "-" <> _}) when zero == 0, do: true
def negative_zero?(%{value: %D{sign: -1, coef: 0}}), do: true
def negative_zero?(_), do: false
@doc """
Adds two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero or a
finite number and the other is INF or -INF, INF or -INF is returned. If both
operands are INF, INF is returned. If both operands are -INF, -INF is returned.
If one of the operands is INF and the other is -INF, NaN is returned.
If one of the given arguments is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-add>
"""
def add(arg1, arg2) do
arithmetic_operation(:+, arg1, arg2, fn
:positive_infinity, :negative_infinity, _ -> :nan
:negative_infinity, :positive_infinity, _ -> :nan
:positive_infinity, _, _ -> :positive_infinity
_, :positive_infinity, _ -> :positive_infinity
:negative_infinity, _, _ -> :negative_infinity
_, :negative_infinity, _ -> :negative_infinity
%D{} = arg1, %D{} = arg2, _ -> D.add(arg1, arg2)
arg1, arg2, _ -> arg1 + arg2
end)
end
@doc """
Subtracts two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero or a
finite number and the other is INF or -INF, an infinity of the appropriate sign
is returned. If both operands are INF or -INF, NaN is returned. If one of the
operands is INF and the other is -INF, an infinity of the appropriate sign is
returned.
If one of the given arguments is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-subtract>
"""
def subtract(arg1, arg2) do
arithmetic_operation(:-, arg1, arg2, fn
:positive_infinity, :positive_infinity, _ -> :nan
:negative_infinity, :negative_infinity, _ -> :nan
:positive_infinity, :negative_infinity, _ -> :positive_infinity
:negative_infinity, :positive_infinity, _ -> :negative_infinity
:positive_infinity, _, _ -> :positive_infinity
_, :positive_infinity, _ -> :negative_infinity
:negative_infinity, _, _ -> :negative_infinity
_, :negative_infinity, _ -> :positive_infinity
%D{} = arg1, %D{} = arg2, _ -> D.sub(arg1, arg2)
arg1, arg2, _ -> arg1 - arg2
end)
end
@doc """
Multiplies two numeric literals.
For `xsd:float` or `xsd:double` values, if one of the operands is a zero and
the other is an infinity, NaN is returned. If one of the operands is a non-zero
number and the other is an infinity, an infinity with the appropriate sign is
returned.
If one of the given arguments is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-multiply>
"""
def multiply(arg1, arg2) do
arithmetic_operation(:*, arg1, arg2, fn
:positive_infinity, :negative_infinity, _ -> :nan
:negative_infinity, :positive_infinity, _ -> :nan
inf, zero, _ when inf in [:positive_infinity, :negative_infinity] and zero == 0 -> :nan
zero, inf, _ when inf in [:positive_infinity, :negative_infinity] and zero == 0 -> :nan
:positive_infinity, number, _ when number < 0 -> :negative_infinity
number, :positive_infinity, _ when number < 0 -> :negative_infinity
:positive_infinity, _, _ -> :positive_infinity
_, :positive_infinity, _ -> :positive_infinity
:negative_infinity, number, _ when number < 0 -> :positive_infinity
number, :negative_infinity, _ when number < 0 -> :positive_infinity
:negative_infinity, _, _ -> :negative_infinity
_, :negative_infinity, _ -> :negative_infinity
%D{} = arg1, %D{} = arg2, _ -> D.mult(arg1, arg2)
arg1, arg2, _ -> arg1 * arg2
end)
end
@doc """
Divides two numeric literals.
For `xsd:float` and `xsd:double` operands, floating point division is performed
as specified in [IEEE 754-2008]. A positive number divided by positive zero
returns INF. A negative number divided by positive zero returns -INF. Division
by negative zero returns -INF and INF, respectively. Positive or negative zero
divided by positive or negative zero returns NaN. Also, INF or -INF divided by
INF or -INF returns NaN.
If one of the given arguments is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
`nil` is also returned for `xsd:decimal` and `xsd:integer` operands, if the
divisor is (positive or negative) zero.
see <http://www.w3.org/TR/xpath-functions/#func-numeric-divide>
"""
def divide(arg1, arg2) do
negative_zero = negative_zero?(arg2)
arithmetic_operation(:/, arg1, arg2, fn
inf1, inf2, _
when inf1 in [:positive_infinity, :negative_infinity] and
inf2 in [:positive_infinity, :negative_infinity] ->
:nan
%D{} = arg1, %D{coef: coef} = arg2, _ ->
unless coef == 0, do: D.div(arg1, arg2)
arg1, arg2, result_type ->
if zero_value?(arg2) do
cond do
result_type not in [RDF.XSD.Double, RDF.XSD.Float] -> nil
zero_value?(arg1) -> :nan
negative_zero and arg1 < 0 -> :positive_infinity
negative_zero -> :negative_infinity
arg1 < 0 -> :negative_infinity
true -> :positive_infinity
end
else
arg1 / arg2
end
end)
end
@doc """
Returns the absolute value of a numeric literal.
If the given argument is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-abs>
"""
def abs(literal)
def abs(%RDF.Literal{literal: literal}), do: abs(literal)
def abs(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.abs()
|> RDF.XSD.Decimal.new()
end
end
def abs(nil), do: nil
def abs(value) do
cond do
literal?(value) ->
if RDF.XSD.valid?(value) do
%datatype{} = value
case value.value do
:nan ->
literal(value)
:positive_infinity ->
literal(value)
:negative_infinity ->
datatype.new(:positive_infinity)
value ->
value
|> Kernel.abs()
|> datatype.new()
end
end
RDF.XSD.literal?(value) ->
nil
true ->
value
|> RDF.Literal.coerce()
|> abs()
end
end
@doc """
Rounds a value to a specified number of decimal places, rounding upwards if two such values are equally near.
The function returns the nearest (that is, numerically closest) value to the
given literal value that is a multiple of ten to the power of minus `precision`.
If two such values are equally near (for example, if the fractional part in the
literal value is exactly .5), the function returns the one that is closest to
positive infinity.
If the given argument is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-round>
"""
def round(literal, precision \\ 0)
def round(%RDF.Literal{literal: literal}, precision), do: round(literal, precision)
def round(%RDF.XSD.Decimal{} = literal, precision) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> xpath_round(precision)
|> to_string()
|> RDF.XSD.Decimal.new()
end
end
def round(%datatype{value: value} = datatype_literal, _)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def round(%datatype{} = literal, precision) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> new_decimal()
|> xpath_round(precision)
|> D.to_float()
|> datatype.new()
end
end
def round(nil, _), do: nil
def round(value, precision) do
cond do
literal?(value) ->
if RDF.XSD.valid?(value) do
if precision < 0 do
value.value
|> new_decimal()
|> xpath_round(precision)
|> D.to_integer()
|> RDF.XSD.Integer.new()
else
literal(value)
end
end
RDF.XSD.literal?(value) ->
nil
true ->
value
|> RDF.Literal.coerce()
|> round(precision)
end
end
defp xpath_round(%D{sign: -1} = decimal, precision),
do: D.round(decimal, precision, :half_down)
defp xpath_round(decimal, precision),
do: D.round(decimal, precision)
@doc """
Rounds a numeric literal upwards to a whole number literal.
If the given argument is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-ceil>
"""
def ceil(literal)
def ceil(%RDF.Literal{literal: literal}), do: ceil(literal)
def ceil(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.round(0, if(literal.value.sign == -1, do: :down, else: :up))
|> D.to_string()
|> RDF.XSD.Decimal.new()
end
end
def ceil(%datatype{value: value} = datatype_literal)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def ceil(%datatype{} = literal) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> Float.ceil()
|> trunc()
|> to_string()
|> datatype.new()
end
end
def ceil(nil), do: nil
def ceil(value) do
cond do
literal?(value) ->
if RDF.XSD.valid?(value) do
literal(value)
end
RDF.XSD.literal?(value) ->
nil
true ->
value
|> RDF.Literal.coerce()
|> ceil()
end
end
@doc """
Rounds a numeric literal downwards to a whole number literal.
If the given argument is not a numeric literal or a value which
can be coerced into a numeric literal, `nil` is returned.
see <http://www.w3.org/TR/xpath-functions/#func-floor>
"""
def floor(literal)
def floor(%RDF.Literal{literal: literal}), do: floor(literal)
def floor(%RDF.XSD.Decimal{} = literal) do
if RDF.XSD.Decimal.valid?(literal) do
literal.value
|> D.round(0, if(literal.value.sign == -1, do: :up, else: :down))
|> D.to_string()
|> RDF.XSD.Decimal.new()
end
end
def floor(%datatype{value: value} = datatype_literal)
when datatype in [RDF.XSD.Double, RDF.XSD.Float] and
value in ~w[nan positive_infinity negative_infinity]a,
do: literal(datatype_literal)
def floor(%datatype{} = literal) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
if datatype.valid?(literal) do
literal.value
|> Float.floor()
|> trunc()
|> to_string()
|> datatype.new()
end
end
def floor(nil), do: nil
def floor(value) do
cond do
literal?(value) ->
if RDF.XSD.valid?(value), do: literal(value)
RDF.XSD.literal?(value) ->
nil
true ->
value
|> RDF.Literal.coerce()
|> floor()
end
end
defp arithmetic_operation(op, %RDF.Literal{literal: literal1}, literal2, fun), do: arithmetic_operation(op, literal1, literal2, fun)
defp arithmetic_operation(op, literal1, %RDF.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) do
result_type = result_type(op, datatype1, datatype2)
{arg1, arg2} = type_conversion(literal1, literal2, result_type)
result = fun.(arg1.value, arg2.value, result_type)
unless is_nil(result), do: result_type.new(result)
end
end
defp arithmetic_operation(op, left, right, fun) do
cond do
is_nil(left) -> nil
is_nil(right) -> nil
not RDF.XSD.literal?(left) -> arithmetic_operation(op, RDF.Literal.coerce(left), right, fun)
not RDF.XSD.literal?(right) -> arithmetic_operation(op, left, RDF.Literal.coerce(right), fun)
true -> false
end
end
defp type_conversion(%RDF.XSD.Decimal{} = left_decimal, %{value: right_value}, RDF.XSD.Decimal),
do: {left_decimal, RDF.XSD.Decimal.new(right_value).literal}
defp type_conversion(%{value: left_value}, %RDF.XSD.Decimal{} = right_decimal, RDF.XSD.Decimal),
do: {RDF.XSD.Decimal.new(left_value).literal, right_decimal}
defp type_conversion(%RDF.XSD.Decimal{value: left_decimal}, right, datatype)
when datatype in [RDF.XSD.Double, RDF.XSD.Float],
do: {(left_decimal |> D.to_float() |> RDF.XSD.Double.new()).literal, right}
defp type_conversion(left, %RDF.XSD.Decimal{value: right_decimal}, datatype)
when datatype in [RDF.XSD.Double, RDF.XSD.Float],
do: {left, (right_decimal |> D.to_float() |> RDF.XSD.Double.new()).literal}
defp type_conversion(left, right, _), do: {left, right}
defp result_type(_, RDF.XSD.Double, _), do: RDF.XSD.Double
defp result_type(_, _, RDF.XSD.Double), do: RDF.XSD.Double
defp result_type(_, RDF.XSD.Float, _), do: RDF.XSD.Float
defp result_type(_, _, RDF.XSD.Float), do: RDF.XSD.Float
defp result_type(_, RDF.XSD.Decimal, _), do: RDF.XSD.Decimal
defp result_type(_, _, RDF.XSD.Decimal), do: RDF.XSD.Decimal
defp result_type(:/, _, _), do: RDF.XSD.Decimal
defp result_type(_, _, _), do: RDF.XSD.Integer
defp literal(value), do: %RDF.Literal{literal: value}
end

View file

@ -0,0 +1,8 @@
defmodule RDF.XSD.PositiveInteger do
use RDF.XSD.Datatype.Restriction,
name: "positiveInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("positiveInteger"),
base: RDF.XSD.NonNegativeInteger
def_facet_constraint RDF.XSD.Facets.MinInclusive, 1
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.Short do
use RDF.XSD.Datatype.Restriction,
name: "short",
id: RDF.Utils.Bootstrapping.xsd_iri("short"),
base: RDF.XSD.Int
def_facet_constraint RDF.XSD.Facets.MinInclusive, -32768
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 32767
end

View file

@ -0,0 +1,78 @@
defmodule RDF.XSD.String do
@moduledoc """
`RDF.XSD.Datatype` for XSD strings.
"""
@type valid_value :: String.t()
use RDF.XSD.Datatype.Primitive,
name: "string",
id: RDF.Utils.Bootstrapping.xsd_iri("string")
@impl RDF.XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value
def lexical_mapping(lexical, _), do: to_string(lexical)
@impl RDF.XSD.Datatype
@spec elixir_mapping(any, Keyword.t()) :: value
def elixir_mapping(value, _), do: to_string(value)
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.Decimal{} = xsd_decimal) do
try do
xsd_decimal.value
|> Decimal.to_integer()
|> RDF.XSD.Integer.new()
|> cast()
rescue
_ ->
default_canonical_cast(xsd_decimal, RDF.XSD.Decimal)
end
end
def do_cast(%datatype{} = xsd_double) when datatype in [RDF.XSD.Double, RDF.XSD.Float] do
cond do
RDF.XSD.Numeric.negative_zero?(xsd_double) ->
new("-0")
RDF.XSD.Numeric.zero?(xsd_double) ->
new("0")
xsd_double.value >= 0.000_001 and xsd_double.value < 1_000_000 ->
xsd_double.value
|> RDF.XSD.Decimal.new()
|> cast()
true ->
default_canonical_cast(xsd_double, datatype)
end
end
def do_cast(%RDF.XSD.DateTime{} = xsd_datetime) do
xsd_datetime
|> RDF.XSD.DateTime.canonical_lexical_with_zone()
|> new()
end
def do_cast(%RDF.XSD.Time{} = xsd_time) do
xsd_time
|> RDF.XSD.Time.canonical_lexical_with_zone()
|> new()
end
def do_cast(%datatype{} = literal) do
if RDF.XSD.datatype?(datatype) do
default_canonical_cast(literal, datatype)
end
end
def do_cast(literal_or_value), do: super(literal_or_value)
defp default_canonical_cast(literal, datatype) do
literal
|> datatype.canonical_lexical()
|> new()
end
end

View file

@ -0,0 +1,217 @@
defmodule RDF.XSD.Time do
@moduledoc """
`RDF.XSD.Datatype` for XSD times.
"""
@type valid_value :: Time.t() | {Time.t(), true}
use RDF.XSD.Datatype.Primitive,
name: "time",
id: RDF.Utils.Bootstrapping.xsd_iri("time")
# TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ...
@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/
@impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do
case Regex.run(@grammar, lexical) do
[_, time] ->
do_lexical_mapping(time, opts)
[_, time, tz] ->
do_lexical_mapping(
time,
opts |> Keyword.put_new(:tz, tz) |> Keyword.put_new(:lexical_present, true)
)
_ ->
@invalid_value
end
end
defp do_lexical_mapping(value, opts) do
case Time.from_iso8601(value) do
{:ok, time} -> elixir_mapping(time, opts)
_ -> @invalid_value
end
|> case do
{{_, true} = value, _} -> value
value -> value
end
end
@impl RDF.XSD.Datatype
@spec elixir_mapping(valid_value | any, Keyword.t()) ::
value | {value, RDF.XSD.Datatype.uncanonical_lexical()}
def elixir_mapping(value, opts)
def elixir_mapping(%Time{} = value, opts) do
if tz = Keyword.get(opts, :tz) do
case with_offset(value, tz) do
@invalid_value ->
@invalid_value
time ->
{{time, true}, unless(Keyword.get(opts, :lexical_present), do: Time.to_iso8601(value))}
end
else
value
end
end
def elixir_mapping(_, _), do: @invalid_value
defp with_offset(time, zone) when zone in ~W[Z UTC GMT], do: time
defp with_offset(time, offset) do
case Regex.run(@tz_number_grammar, offset) do
[_, "-", hour, minute] ->
{hour, minute} = {String.to_integer(hour), String.to_integer(minute)}
minute = time.minute + minute
{rem(time.hour + hour + div(minute, 60), 24), rem(minute, 60)}
[_, "+", hour, minute] ->
{hour, minute} = {String.to_integer(hour), String.to_integer(minute)}
if (minute = time.minute - minute) < 0 do
{rem(24 + time.hour - hour - 1, 24), minute + 60}
else
{rem(24 + time.hour - hour - div(minute, 60), 24), rem(minute, 60)}
end
nil ->
@invalid_value
end
|> case do
{hour, minute} -> %Time{time | hour: hour, minute: minute}
@invalid_value -> @invalid_value
end
end
@impl RDF.XSD.Datatype
@spec canonical_mapping(valid_value) :: String.t()
def canonical_mapping(value)
def canonical_mapping(%Time{} = value), do: Time.to_iso8601(value)
def canonical_mapping({%Time{} = value, true}), do: canonical_mapping(value) <> "Z"
@impl RDF.XSD.Datatype
@spec init_valid_lexical(valid_value, RDF.XSD.Datatype.uncanonical_lexical(), Keyword.t()) ::
RDF.XSD.Datatype.uncanonical_lexical()
def init_valid_lexical(value, lexical, opts)
def init_valid_lexical({value, _}, nil, opts) do
if tz = Keyword.get(opts, :tz) do
canonical_mapping(value) <> tz
end
end
def init_valid_lexical(_, nil, _), do: nil
def init_valid_lexical(_, lexical, opts) do
if tz = Keyword.get(opts, :tz) do
# When using the :tz option, we'll have to strip off the original timezone
case Regex.run(@grammar, lexical) do
[_, time] -> time
[_, time, _] -> time
end <> tz
else
lexical
end
end
@impl RDF.XSD.Datatype
@spec init_invalid_lexical(any, Keyword.t()) :: String.t()
def init_invalid_lexical(value, opts)
def init_invalid_lexical({time, tz}, opts) do
if tz_opt = Keyword.get(opts, :tz) do
to_string(time) <> tz_opt
else
to_string(time) <> to_string(tz)
end
end
def init_invalid_lexical(value, _) when is_binary(value), do: value
def init_invalid_lexical(value, opts) do
if tz = Keyword.get(opts, :tz) do
to_string(value) <> tz
else
to_string(value)
end
end
@impl RDF.Literal.Datatype
def do_cast(value)
def do_cast(%RDF.XSD.DateTime{} = xsd_datetime) do
case xsd_datetime.value do
%NaiveDateTime{} = datetime ->
datetime
|> NaiveDateTime.to_time()
|> new()
%DateTime{} ->
[_date, time_with_zone] =
xsd_datetime
|> RDF.XSD.DateTime.canonical_lexical_with_zone()
|> String.split("T", parts: 2)
new(time_with_zone)
end
end
def do_cast(%RDF.XSD.String{} = xsd_string), do: new(xsd_string.value)
def do_cast(literal_or_value), do: super(literal_or_value)
@impl RDF.Literal.Datatype
def do_equal_value?(literal1, literal2)
def do_equal_value?(%__MODULE__{value: %_{}}, %__MODULE__{value: tz_tuple})
when is_tuple(tz_tuple),
do: nil
def do_equal_value?(%__MODULE__{value: tz_tuple}, %__MODULE__{value: %_{}})
when is_tuple(tz_tuple),
do: nil
def do_equal_value?(left, right), do: super(left, right)
@doc """
Extracts the timezone string from a `RDF.XSD.Time` value.
"""
def tz(time_literal) do
if valid?(time_literal) do
time_literal
|> lexical()
|> RDF.XSD.Utils.DateTime.tz()
end
end
@doc """
Converts a time literal to a canonical string, preserving the zone information.
"""
@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)
def canonical_lexical_with_zone(%__MODULE__{} = xsd_time) do
case tz(xsd_time) do
nil ->
nil
zone when zone in ["Z", "", "+00:00"] ->
canonical_lexical(xsd_time)
zone ->
xsd_time
|> lexical()
|> String.replace_trailing(zone, "")
|> Time.from_iso8601!()
|> new()
|> canonical_lexical()
|> Kernel.<>(zone)
end
end
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.UnsignedByte do
use RDF.XSD.Datatype.Restriction,
name: "unsignedByte",
id: RDF.Utils.Bootstrapping.xsd_iri("unsignedByte"),
base: RDF.XSD.UnsignedShort
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 255
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.UnsignedInt do
use RDF.XSD.Datatype.Restriction,
name: "unsignedInt",
id: RDF.Utils.Bootstrapping.xsd_iri("unsignedInt"),
base: RDF.XSD.UnsignedLong
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 4_294_967_295
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.UnsignedLong do
use RDF.XSD.Datatype.Restriction,
name: "unsignedLong",
id: RDF.Utils.Bootstrapping.xsd_iri("unsignedLong"),
base: RDF.XSD.NonNegativeInteger
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 18_446_744_073_709_551_615
end

View file

@ -0,0 +1,9 @@
defmodule RDF.XSD.UnsignedShort do
use RDF.XSD.Datatype.Restriction,
name: "unsignedShort",
id: RDF.Utils.Bootstrapping.xsd_iri("unsignedShort"),
base: RDF.XSD.UnsignedInt
def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 65535
end

113
lib/rdf/xsd/facet.ex Normal file
View file

@ -0,0 +1,113 @@
defmodule RDF.XSD.Facet do
@type t :: module
@doc """
The name of a `RDF.XSD.Facet`.
"""
@callback name :: String.t()
defmacro __using__(opts) do
name = Keyword.fetch!(opts, :name)
type_ast = Keyword.fetch!(opts, :type)
quote bind_quoted: [], unquote: true do
@behaviour RDF.XSD.Facet
@doc """
Returns the value of this `RDF.XSD.Facet` on specific `RDF.XSD.Datatype`.
"""
@callback unquote(name)() :: unquote(type_ast) | nil
@doc """
Validates if a `value` and `lexical` conforms with a concrete `facet_constaint_value` for this `RDF.XSD.Facet`.
This function must be implemented on a `RDF.XSD.Datatype` using this `RDF.XSD.Facet`.
"""
@callback unquote(conform_fun_name(name))(
facet_constaint_value :: any,
value :: any,
RDF.XSD.Datatype.uncanonical_lexical()
) :: boolean
@name unquote(Atom.to_string(name))
@impl RDF.XSD.Facet
def name, do: @name
@doc """
Checks if a `value` and `lexical` conforms with the `c:#{unquote(conform_fun_name(name))}/3` implementation on the `datatype` `RDF.XSD.Datatype`.
"""
@spec conform?(RDF.XSD.Datatype.t(), any, RDF.XSD.Datatype.uncanonical_lexical()) :: boolean
def conform?(datatype, value, lexical) do
constrain_value = apply(datatype, unquote(name), [])
is_nil(constrain_value) or
apply(datatype, unquote(conform_fun_name(name)), [constrain_value, value, lexical])
end
defmacro __using__(_opts) do
import unquote(__MODULE__)
default_facet_impl(__MODULE__, unquote(name))
end
end
end
defp conform_fun_name(facet_name), do: :"#{facet_name}_conform?"
@doc """
Macro for the definition of concrete constraining `value` for a `RDF.XSD.Facet` on a `RDF.XSD.Datatype`.
"""
defmacro def_facet_constraint(facet, value) do
facet_mod = Macro.expand_once(facet, __CALLER__)
facet_name = String.to_atom(facet_mod.name)
quote do
unless unquote(facet) in @base.applicable_facets,
do: raise("#{unquote(facet_name)} is not an applicable facet of #{@base}")
@facets unquote(facet_name)
@impl unquote(facet)
def unquote(facet_name)(), do: unquote(value)
end
end
@doc false
def default_facet_impl(facet_mod, facet_name) do
quote do
@behaviour unquote(facet_mod)
Module.put_attribute(__MODULE__, unquote(facet_mod), nil)
@impl unquote(facet_mod)
def unquote(facet_name)(), do: nil
defoverridable [{unquote(facet_name), 0}]
end
end
@doc false
def restriction_impl(facets, applicable_facets) do
Enum.map(applicable_facets, fn applicable_facet ->
applicable_facet_name = String.to_atom(applicable_facet.name)
quote do
@behaviour unquote(applicable_facet)
unless unquote(applicable_facet_name in facets) do
@impl unquote(applicable_facet)
def unquote(applicable_facet_name)(),
do: apply(@base, unquote(applicable_facet_name), [])
end
@impl unquote(applicable_facet)
def unquote(conform_fun_name(applicable_facet_name))(constrain_value, value, lexical) do
apply(@base, unquote(conform_fun_name(applicable_facet_name)), [
constrain_value,
value,
lexical
])
end
end
end)
end
end

View file

@ -0,0 +1,3 @@
defmodule RDF.XSD.Facets.MaxInclusive do
use RDF.XSD.Facet, name: :max_inclusive, type: integer
end

View file

@ -0,0 +1,3 @@
defmodule RDF.XSD.Facets.MinInclusive do
use RDF.XSD.Facet, name: :min_inclusive, type: integer
end

View file

@ -0,0 +1,18 @@
defmodule RDF.XSD.Utils.DateTime do
@moduledoc false
@spec tz(String.t()) :: String.t()
def tz(string) do
case Regex.run(~r/([+-])(\d\d:\d\d)/, string) do
[_, sign, zone] ->
sign <> zone
_ ->
if String.ends_with?(string, "Z") do
"Z"
else
""
end
end
end
end

View file

@ -0,0 +1,86 @@
defmodule RDF.XSD.Utils.Regex do
@moduledoc !"""
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.XSD.Literal.matches?/3` instead.
"""
@doc """
Matches the string representation of the given value against a XPath and XQuery regular expression pattern.
The regular expression language is defined in _XQuery 1.0 and XPath 2.0 Functions and Operators_.
see <https://www.w3.org/TR/xpath-functions/#func-matches>
"""
@spec matches?(String.t(), String.t(), String.t()) :: boolean
def matches?(value, pattern, flags \\ "") do
string = to_string(value)
case xpath_pattern(pattern, flags) do
{:regex, regex} ->
Regex.match?(regex, string)
{:q, pattern} ->
String.contains?(string, pattern)
{:qi, pattern} ->
string
|> String.downcase()
|> String.contains?(String.downcase(pattern))
{:error, error} ->
raise "Invalid XQuery regex pattern or flags: #{inspect(error)}"
end
end
@spec xpath_pattern(String.t(), String.t()) ::
{:q | :qi, String.t()} | {:regex, Regex.t()} | {:error, any}
def xpath_pattern(pattern, flags)
def xpath_pattern(pattern, flags) when is_binary(pattern) and is_binary(flags) do
q_pattern(pattern, flags) || xpath_regex_pattern(pattern, flags)
end
defp q_pattern(pattern, flags) do
if String.contains?(flags, "q") and String.replace(flags, ~r/[qi]/, "") == "" do
{if(String.contains?(flags, "i"), do: :qi, else: :q), pattern}
end
end
defp xpath_regex_pattern(pattern, flags) do
with {:ok, regex} <-
pattern
|> convert_utf_escaping()
|> Regex.compile(xpath_regex_flags(flags)) do
{:regex, regex}
end
end
@spec convert_utf_escaping(String.t()) :: String.t()
def convert_utf_escaping(string) do
require Integer
xpath_unicode_regex = ~r/(\\*)\\U([0-9]|[A-F]|[a-f]){2}(([0-9]|[A-F]|[a-f]){6})/
[first | possible_matches] = Regex.split(xpath_unicode_regex, string, include_captures: true)
[
first
| Enum.map_every(possible_matches, 2, fn possible_xpath_unicode ->
[_, escapes, _, codepoint, _] = Regex.run(xpath_unicode_regex, possible_xpath_unicode)
if escapes |> String.length() |> Integer.is_odd() do
"#{escapes}\\u{#{codepoint}}"
else
"\\" <> possible_xpath_unicode
end
end)
]
|> Enum.join()
end
defp xpath_regex_flags(flags) do
String.replace(flags, "q", "") <> "u"
end
end

View file

@ -67,7 +67,6 @@ defmodule RDF.Mixfile do
defp deps do defp deps do
[ [
{:xsd, path: "../../../RDF.ex/src/xsd"},
{:decimal, "~> 1.5"}, {:decimal, "~> 1.5"},
{:credo, "~> 1.3", only: [:dev, :test], runtime: false}, {:credo, "~> 1.3", only: [:dev, :test], runtime: false},

View file

@ -11,7 +11,7 @@ defmodule RDF.Test.Case do
using do using do
quote do quote do
alias RDF.{Dataset, Graph, Description, IRI} alias RDF.{Dataset, Graph, Description, IRI, XSD}
alias unquote(__MODULE__).EX alias unquote(__MODULE__).EX
import RDF, only: [iri: 1, literal: 1, bnode: 1] import RDF, only: [iri: 1, literal: 1, bnode: 1]

View file

@ -0,0 +1,284 @@
defmodule RDF.XSD.Datatype.Test.Case do
use ExUnit.CaseTemplate
alias RDF.XSD
using(opts) 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)
valid = Keyword.get(opts, :valid)
invalid = Keyword.get(opts, :invalid)
primitive = Keyword.get(opts, :primitive)
base = unless primitive, do: Keyword.fetch!(opts, :base)
base_primitive = unless primitive, do: Keyword.fetch!(opts, :base_primitive)
applicable_facets = Keyword.get(opts, :applicable_facets, [])
facets = Keyword.get(opts, :facets)
quote do
alias RDF.XSD
alias RDF.XSD.Datatype
alias unquote(datatype)
import unquote(__MODULE__)
doctest unquote(datatype)
@moduletag datatype: unquote(datatype)
if unquote(valid) do
@valid unquote(valid)
@invalid unquote(invalid)
test "registration" do
assert unquote(datatype) in XSD.datatypes()
assert XSD.datatype_by_name(unquote(datatype_name)) == unquote(datatype)
assert XSD.datatype_by_iri(unquote(datatype_iri)) == unquote(datatype)
end
test "primitive/0" do
assert unquote(datatype).primitive?() == unquote(!!primitive)
end
test "base/0" do
if unquote(primitive) do
assert unquote(datatype).base == nil
else
assert unquote(datatype).base == unquote(base)
end
end
test "base_primitive/0" do
if unquote(primitive) do
assert unquote(datatype).base_primitive == unquote(datatype)
else
assert unquote(datatype).base_primitive == unquote(base_primitive)
end
end
test "derived_from?/1" do
assert unquote(datatype).derived_from?(unquote(datatype)) == true
unless unquote(primitive) do
assert unquote(datatype).derived_from?(unquote(base)) == true
assert unquote(datatype).derived_from?(unquote(base_primitive)) == true
end
end
test "applicable_facets/0" do
assert MapSet.new(unquote(datatype).applicable_facets()) ==
MapSet.new(unquote(applicable_facets))
end
if unquote(facets) do
test "facets" do
Enum.each(unquote(facets), fn {facet, value} ->
assert apply(unquote(datatype), facet, []) == value
end)
end
end
test "name/0" do
assert unquote(datatype).name() == unquote(datatype_name)
end
test "id/0" do
assert unquote(datatype).id() == RDF.iri(unquote(datatype_iri))
end
test "language/1" do
Enum.each(@valid, fn {input, _} ->
assert (unquote(datatype).new(input) |> unquote(datatype).language()) == nil
end)
end
test "datatype/1" do
Enum.each(@valid, fn {input, _} ->
assert (unquote(datatype).new(input) |> unquote(datatype).datatype()) == RDF.iri(unquote(datatype_iri))
end)
end
describe "general new" do
Enum.each(@valid, fn {input, {value, lexical, _}} ->
expected = %RDF.Literal{
literal: %unquote(datatype){value: value, uncanonical_lexical: lexical}
}
@tag example: %{input: input, output: expected}
test "valid: #{unquote(datatype)}.new(#{inspect(input)})", %{example: example} do
assert unquote(datatype).new(example.input) == example.output
end
end)
Enum.each(@invalid, fn value ->
expected = %RDF.Literal{
literal: %unquote(datatype){
uncanonical_lexical: unquote(datatype).init_invalid_lexical(value, [])
}
}
@tag example: %{input: value, output: expected}
test "invalid: #{unquote(datatype)}.new(#{inspect(value)})",
%{example: example} do
assert unquote(datatype).new(example.input) == example.output
end
end)
test "canonicalize option" do
Enum.each(@valid, fn {input, _} ->
assert unquote(datatype).new(input, canonicalize: true) ==
unquote(datatype).new(input) |> unquote(datatype).canonical()
end)
Enum.each(@invalid, fn input ->
assert unquote(datatype).new(input, canonicalize: true) ==
unquote(datatype).new(input) |> unquote(datatype).canonical()
end)
end
end
describe "general new!" do
test "with valid values, it behaves the same as new" do
Enum.each(@valid, fn {input, _} ->
assert unquote(datatype).new!(input) == unquote(datatype).new(input)
assert unquote(datatype).new!(input) ==
unquote(datatype).new(input)
assert unquote(datatype).new!(input, canonicalize: true) ==
unquote(datatype).new(input, canonicalize: true)
end)
end
test "with invalid values, it raises an error" do
Enum.each(@invalid, fn value ->
assert_raise ArgumentError, fn -> unquote(datatype).new!(value) end
assert_raise ArgumentError, fn ->
unquote(datatype).new!(value, canonicalize: true)
end
end)
end
end
describe "general value" do
Enum.each(@valid, fn {input, {value, _, canonicalized}} ->
@tag example: %{input: input, value: value}
test "of valid #{unquote(datatype)}.new(#{inspect(input)})",
%{example: example} do
assert unquote(datatype).new(example.input) |> unquote(datatype).value() ==
example.value
end
end)
Enum.each(@invalid, fn value ->
@tag example: %{input: value, value: value}
test "of invalid #{unquote(datatype)}.new(#{inspect(value)})", %{example: example} do
assert unquote(datatype).new(example.input) |> unquote(datatype).value() == nil
end
end)
end
describe "general lexical" do
Enum.each(@valid, fn {input, {_, lexical, canonicalized}} ->
lexical = lexical || canonicalized
@tag example: %{input: input, lexical: lexical}
test "of valid #{unquote(datatype)}.new(#{inspect(input)})",
%{example: example} do
assert unquote(datatype).new(example.input) |> unquote(datatype).lexical() ==
example.lexical
end
end)
Enum.each(@invalid, fn value ->
lexical = unquote(datatype).init_invalid_lexical(value, [])
@tag example: %{input: value, lexical: lexical}
test "of invalid #{unquote(datatype)}.new(#{inspect(value)}) == #{inspect(lexical)}",
%{example: example} do
assert unquote(datatype).new(example.input) |> unquote(datatype).lexical() ==
example.lexical
end
end)
end
describe "general canonicalization" do
Enum.each(@valid, fn {input, {value, _, _}} ->
expected = %RDF.Literal{literal: %unquote(datatype){value: value}}
@tag example: %{input: input, output: expected}
test "#{unquote(datatype)} #{inspect(input)}", %{example: example} do
assert unquote(datatype).new(example.input) |> unquote(datatype).canonical() ==
example.output
end
end)
Enum.each(@valid, fn {input, {_, _, canonicalized}} ->
@tag example: %{input: input, canonicalized: canonicalized}
test "lexical of canonicalized #{unquote(datatype)} #{inspect(input, limit: 4)} is #{
inspect(canonicalized, limit: 4)
}",
%{example: example} do
assert unquote(datatype).new(example.input)
|> unquote(datatype).canonical()
|> unquote(datatype).lexical() ==
example.canonicalized
end
end)
Enum.each(@valid, fn {input, {_, _, canonicalized}} ->
@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
)
end
end)
test "does not change the XSD datatype value when it is invalid" do
Enum.each(@invalid, fn value ->
assert unquote(datatype).new(value) |> unquote(datatype).canonical() ==
unquote(datatype).new(value)
end)
end
end
describe "general validation" do
Enum.each(Map.keys(@valid), fn value ->
@tag value: value
test "#{inspect(value)} as a #{unquote(datatype)} is valid", %{value: value} do
assert unquote(datatype).valid?(unquote(datatype).new(value))
end
end)
Enum.each(@invalid, fn value ->
@tag value: value
test "#{inspect(value)} as a #{unquote(datatype)} is invalid", %{value: value} do
refute unquote(datatype).valid?(unquote(datatype).new(value))
end
end)
end
end
test "XSD.Datatype.matches?/3" do
Enum.each(@valid, fn {input, {_, lexical, canonicalized}} ->
lexical = lexical || canonicalized
assert unquote(datatype).new(input) |> unquote(datatype).matches?(lexical, "q") == true
end)
end
test "String.Chars protocol implementation" do
Enum.each(@valid, fn {input, _} ->
assert unquote(datatype).new(input) |> to_string() ==
unquote(datatype).new(input) |> unquote(datatype).lexical()
end)
end
end
end
def dt(value) do
{:ok, date, _} = DateTime.from_iso8601(value)
date
end
end

View file

@ -0,0 +1,187 @@
defmodule RDF.XSD.TestData do
@zero %{
0 => {0, nil, "0"},
"0" => {0, nil, "0"},
"+0" => {0, "+0", "0"},
"-0" => {0, "-0", "0"},
"000" => {0, "000", "0"}
}
@basic_valid_positive_integers %{
# input => { value, lexical, canonicalized }
1 => {1, nil, "1"},
"1" => {1, nil, "1"},
"01" => {1, "01", "1"},
"0123" => {123, "0123", "123"},
"+1" => {1, "+1", "1"}
}
|> Map.merge(@zero)
@basic_valid_negative_integers %{
-1 => {-1, nil, "-1"},
"-1" => {-1, nil, "-1"},
"-01" => {-1, "-01", "-1"},
"-0123" => {-123, "-0123", "-123"}
}
|> Map.merge(@zero)
@valid_unsigned_bytes %{
255 => {255, nil, "255"},
"0255" => {255, "0255", "255"}
}
|> Map.merge(@basic_valid_positive_integers)
@valid_unsigned_shorts %{
65535 => {65535, nil, "65535"}
}
|> Map.merge(@valid_unsigned_bytes)
@valid_unsigned_ints %{
4_294_967_295 => {4_294_967_295, nil, "4294967295"}
}
|> Map.merge(@valid_unsigned_shorts)
@valid_unsigned_longs %{
18_446_744_073_709_551_615 =>
{18_446_744_073_709_551_615, nil, "18446744073709551615"}
}
|> Map.merge(@valid_unsigned_ints)
@valid_bytes Map.merge(@basic_valid_positive_integers, @basic_valid_negative_integers)
@valid_shorts %{
32767 => {32767, nil, "32767"},
-32768 => {-32768, nil, "-32768"}
}
|> Map.merge(@valid_bytes)
@valid_ints %{
2_147_483_647 => {2_147_483_647, nil, "2147483647"},
-2_147_483_648 => {-2_147_483_648, nil, "-2147483648"}
}
|> Map.merge(@valid_bytes)
@valid_longs %{
9_223_372_036_854_775_807 =>
{9_223_372_036_854_775_807, nil, "9223372036854775807"},
-9_223_372_036_854_775_808 =>
{-9_223_372_036_854_775_808, nil, "-9223372036854775808"}
}
|> Map.merge(@valid_bytes)
@valid_non_negative_integers %{
200_000_000_000_000_000_000_000 =>
{200_000_000_000_000_000_000_000, nil,
"200000000000000000000000"}
}
|> Map.merge(@valid_unsigned_longs)
@valid_non_positive_integers %{
-200_000_000_000_000_000_000_000 =>
{-200_000_000_000_000_000_000_000, nil,
"-200000000000000000000000"}
}
|> Map.merge(@basic_valid_negative_integers)
@valid_positive_integers Map.drop(@valid_non_negative_integers, Map.keys(@zero))
@valid_negative_integers Map.drop(@valid_non_positive_integers, Map.keys(@zero))
@valid_integers @zero
|> Map.merge(@valid_non_negative_integers)
|> Map.merge(@valid_non_positive_integers)
@basic_invalid_integers [
"foo",
"10.1",
"12xyz",
true,
false,
3.14,
"1 2",
"foo 1",
"1 foo"
]
@invalid_bytes [128, "128", -129, "-129"] ++ @basic_invalid_integers
@invalid_shorts [32768, "32768", -32769, "-32769"] ++ @basic_invalid_integers
@invalid_ints [2_147_483_648, "2147483648", -21_474_836_489, "-2147483649"] ++
@basic_invalid_integers
@invalid_longs [
9_223_372_036_854_775_808,
"9223372036854775808",
-9_223_372_036_854_775_809,
"-92233720368547758089"
] ++ @basic_invalid_integers
@invalid_non_negative_integers [-1, "-1"] ++ @basic_invalid_integers
@invalid_non_positive_integers [1, "1", "+1"] ++ @basic_invalid_integers
@invalid_positive_integers [0, "0"] ++ @invalid_non_negative_integers
@invalid_negative_integers [0, "0"] ++ @invalid_non_positive_integers
@invalid_unsigned_bytes [256, "256"] ++ @invalid_non_negative_integers
@invalid_unsigned_shorts [65536, "65536"] ++ @invalid_non_negative_integers
@invalid_unsigned_ints [4_294_967_296, "4294967296"] ++ @invalid_non_negative_integers
@invalid_unsigned_longs [18_446_744_073_709_551_616, "18446744073709551616"] ++
@invalid_non_negative_integers
@valid_floats %{
# input => { value, lexical, canonicalized }
0 => {0.0, "0.0", "0.0E0"},
42 => {42.0, "42.0", "4.2E1"},
0.0e0 => {0.0, "0.0", "0.0E0"},
1.0e0 => {1.0, "1.0", "1.0E0"},
:positive_infinity => {:positive_infinity, nil, "INF"},
:negative_infinity => {:negative_infinity, nil, "-INF"},
:nan => {:nan, nil, "NaN"},
"1.0E0" => {1.0e0, nil, "1.0E0"},
"0.0" => {0.0, "0.0", "0.0E0"},
"1" => {1.0e0, "1", "1.0E0"},
"01" => {1.0e0, "01", "1.0E0"},
"0123" => {1.23e2, "0123", "1.23E2"},
"-1" => {-1.0e0, "-1", "-1.0E0"},
"+01.000" => {1.0e0, "+01.000", "1.0E0"},
"1.0" => {1.0e0, "1.0", "1.0E0"},
"123.456" => {1.23456e2, "123.456", "1.23456E2"},
"1.0e+1" => {1.0e1, "1.0e+1", "1.0E1"},
"1.0e-10" => {1.0e-10, "1.0e-10", "1.0E-10"},
"123.456e4" => {1.23456e6, "123.456e4", "1.23456E6"},
"1.E-8" => {1.0e-8, "1.E-8", "1.0E-8"},
"3E1" => {3.0e1, "3E1", "3.0E1"},
"INF" => {:positive_infinity, nil, "INF"},
"Inf" => {:positive_infinity, "Inf", "INF"},
"-INF" => {:negative_infinity, nil, "-INF"},
"NaN" => {:nan, nil, "NaN"}
}
@invalid_floats ["foo", "12.xyz", "1.0ez", "+INF", true, false, "1.1e1 foo", "foo 1.1e1"]
def valid_integers, do: @valid_integers
def valid_non_negative_integers, do: @valid_non_negative_integers
def valid_non_positive_integers, do: @valid_non_positive_integers
def valid_positive_integers, do: @valid_positive_integers
def valid_negative_integers, do: @valid_negative_integers
def valid_bytes, do: @valid_bytes
def valid_shorts, do: @valid_shorts
def valid_ints, do: @valid_ints
def valid_longs, do: @valid_longs
def valid_unsigned_bytes, do: @valid_unsigned_bytes
def valid_unsigned_shorts, do: @valid_unsigned_shorts
def valid_unsigned_ints, do: @valid_unsigned_ints
def valid_unsigned_longs, do: @valid_unsigned_longs
def valid_floats, do: @valid_floats
def invalid_integers, do: @basic_invalid_integers
def invalid_non_negative_integers, do: @invalid_non_negative_integers
def invalid_non_positive_integers, do: @invalid_non_positive_integers
def invalid_positive_integers, do: @invalid_positive_integers
def invalid_negative_integers, do: @invalid_negative_integers
def invalid_bytes, do: @invalid_bytes
def invalid_shorts, do: @invalid_shorts
def invalid_ints, do: @invalid_ints
def invalid_longs, do: @invalid_longs
def invalid_unsigned_bytes, do: @invalid_unsigned_bytes
def invalid_unsigned_shorts, do: @invalid_unsigned_shorts
def invalid_unsigned_ints, do: @invalid_unsigned_ints
def invalid_unsigned_longs, do: @invalid_unsigned_longs
def invalid_floats, do: @invalid_floats
end

View file

@ -1,60 +0,0 @@
defmodule RDF.NumericTest do
use RDF.Test.Case
alias RDF.Numeric
test "zero?/1" do
assert Numeric.zero?(RDF.integer(0)) == true
assert Numeric.zero?(RDF.string("0")) == false
end
test "negative_zero?/1" do
assert Numeric.negative_zero?(RDF.double("-0")) == true
assert Numeric.negative_zero?(RDF.integer(0)) == false
end
test "add/2" do
assert Numeric.add(RDF.integer(1), RDF.integer(2)) == RDF.integer(3)
assert Numeric.add(RDF.float(1), 2) == RDF.float(3.0)
end
test "subtract/2" do
assert Numeric.subtract(RDF.integer(2), RDF.integer(1)) == RDF.integer(1)
assert Numeric.subtract(RDF.decimal(2), 1) == RDF.decimal(1.0)
end
test "multiply/2" do
assert Numeric.multiply(RDF.integer(2), RDF.integer(3)) == RDF.integer(6)
assert Numeric.multiply(RDF.double(2), 3) == RDF.double(6.0)
end
test "divide/2" do
assert Numeric.divide(RDF.integer(4), RDF.integer(2)) == RDF.decimal(2)
assert Numeric.divide(RDF.double(3), 2) == RDF.double(1.5)
end
test "abs/1" do
assert Numeric.abs(RDF.integer(-2)) == RDF.integer(2)
assert Numeric.abs(RDF.double(-3.14)) == RDF.double(3.14)
end
test "round/1" do
assert Numeric.round(RDF.integer(2)) == RDF.integer(2)
assert Numeric.round(RDF.double(3.14)) == RDF.double(3.0)
end
test "round/2" do
assert Numeric.round(RDF.integer(2), 3) == RDF.integer(2)
assert Numeric.round(RDF.double(3.1415), 2) == RDF.double(3.14)
end
test "ceil/1" do
assert Numeric.ceil(RDF.integer(2)) == RDF.integer(2)
assert Numeric.ceil(RDF.double(3.14)) == RDF.double("4")
end
test "floor/1" do
assert Numeric.floor(RDF.integer(2)) == RDF.integer(2)
assert Numeric.floor(RDF.double(3.14)) == RDF.double("3")
end
end

View file

@ -1,215 +0,0 @@
defmodule RDF.Literal.XSDTest do
use ExUnit.Case
import RDF.TestLiterals
alias RDF.Literal
@examples [
{RDF.XSD.Boolean, XSD.Boolean, true},
{RDF.XSD.Boolean, XSD.Boolean, false},
{RDF.XSD.String, XSD.String, :plain},
{RDF.XSD.Date, XSD.Date, :date},
{RDF.XSD.Time, XSD.Time, :time},
{RDF.XSD.DateTime, XSD.DateTime, :datetime},
{RDF.XSD.DateTime, XSD.DateTime, :naive_datetime},
{RDF.XSD.AnyURI, XSD.AnyURI, :uri},
{RDF.XSD.Decimal, XSD.Decimal, :decimal},
{RDF.XSD.Integer, XSD.Integer, :long},
{RDF.XSD.Long, XSD.Long, :long},
{RDF.XSD.Int, XSD.Int, :int},
{RDF.XSD.Short, XSD.Short, :int},
{RDF.XSD.Byte, XSD.Byte, :int},
{RDF.XSD.NonNegativeInteger, XSD.NonNegativeInteger, :long},
{RDF.XSD.PositiveInteger, XSD.PositiveInteger, :long},
{RDF.XSD.UnsignedLong, XSD.UnsignedLong, :long},
{RDF.XSD.UnsignedInt, XSD.UnsignedInt, :int},
{RDF.XSD.UnsignedShort, XSD.UnsignedShort, :int},
{RDF.XSD.UnsignedByte, XSD.UnsignedByte, :int},
{RDF.XSD.NonPositiveInteger, XSD.NonPositiveInteger, :neg_int},
{RDF.XSD.NegativeInteger, XSD.NegativeInteger, :neg_int},
{RDF.XSD.Double, XSD.Double, :double},
{RDF.XSD.Float, XSD.Float, :double},
]
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.new(#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value} do
assert %Literal{literal: %datatype{}} = literal = rdf_datatype.new(value)
assert datatype == xsd_datatype
assert rdf_datatype.valid?(literal) == true
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype
test "#{rdf_datatype}.name/0 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype} do
assert rdf_datatype.name() == xsd_datatype.name()
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype
test "#{rdf_datatype}.id/0 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype} do
assert rdf_datatype.id() == xsd_datatype.id()
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.datatype/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value} do
assert rdf_datatype.new(value) |> rdf_datatype.datatype() == RDF.iri(xsd_datatype.id())
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.language/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do
assert rdf_datatype.new(value) |> rdf_datatype.language() == nil
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.value/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.value(literal) == value
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.lexical/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.lexical(literal) ==
xsd_datatype.new(value) |> xsd_datatype.lexical()
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.canonical/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.canonical(literal) ==
%Literal{literal: xsd_datatype.new(value) |> xsd_datatype.canonical()}
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.canonical?/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.canonical?(literal) ==
xsd_datatype.new(value) |> xsd_datatype.canonical?()
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.valid?/1 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.valid?(literal) == true
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.equal_value?/2 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.equal_value?(literal, literal) == true
assert rdf_datatype.equal_value?(literal, value) == true
end
end)
Enum.each(@examples, fn {rdf_datatype, xsd_datatype, value_type} ->
value = value_type |> value() |> List.first()
@tag rdf_datatype: rdf_datatype, xsd_datatype: xsd_datatype, value: value
test "#{rdf_datatype}.compare/2 (#{inspect value})", %{rdf_datatype: rdf_datatype, value: value} do
literal = rdf_datatype.new(value)
assert rdf_datatype.compare(literal, literal) == :eq
end
end)
describe "cast/1" do
test "when given a literal with the same datatype" do
assert RDF.XSD.String.new("foo") |> RDF.XSD.String.cast() == RDF.XSD.String.new("foo")
assert RDF.XSD.Integer.new(42) |> RDF.XSD.Integer.cast() == RDF.XSD.Integer.new(42)
assert RDF.XSD.Byte.new(42) |> RDF.XSD.Byte.cast() == RDF.XSD.Byte.new(42)
end
test "when given a literal with a datatype which is castable" do
assert RDF.XSD.Integer.new(42) |> RDF.XSD.String.cast() == RDF.XSD.String.new("42")
assert RDF.XSD.String.new("42") |> RDF.XSD.Integer.cast() == RDF.XSD.Integer.new(42)
assert RDF.XSD.Decimal.new(42) |> RDF.XSD.Byte.cast() == RDF.XSD.Byte.new(42)
end
test "when given a literal with a datatype which is not castable" do
assert RDF.XSD.String.new("foo") |> RDF.XSD.Integer.cast() == nil
assert RDF.XSD.Integer.new(12345) |> RDF.XSD.Byte.cast() == nil
end
test "when given a coercible value" do
assert "foo" |> RDF.XSD.String.cast() == RDF.XSD.String.new("foo")
assert "42" |> RDF.XSD.Integer.cast() == RDF.XSD.Integer.new(42)
assert 42 |> RDF.XSD.Byte.cast() == RDF.XSD.Byte.new(42)
end
test "with invalid literals" do
assert RDF.XSD.Integer.new(3.14) |> RDF.XSD.Integer.cast() == nil
assert RDF.XSD.Decimal.new("NAN") |> RDF.XSD.Decimal.cast() == nil
assert RDF.XSD.Double.new(true) |> RDF.XSD.Double.cast() == nil
assert RDF.XSD.Boolean.new("42") |> RDF.XSD.Boolean.cast() == nil
end
test "with non-coercible value" do
assert_raise RDF.Literal.InvalidError, fn -> RDF.XSD.String.cast(:foo) end
assert_raise RDF.Literal.InvalidError, fn -> assert RDF.XSD.String.cast(make_ref()) end
end
end
describe "RDF.XSD.Boolean" do
test "ebv/1" do
assert RDF.true |> RDF.XSD.Boolean.ebv() == RDF.true
assert RDF.string("foo") |> RDF.XSD.Boolean.ebv() == RDF.true
assert false |> RDF.XSD.Boolean.ebv() == RDF.false
assert "" |> RDF.XSD.Boolean.ebv() == RDF.false
assert 1 |> RDF.XSD.Boolean.ebv() == RDF.true
assert self() |> RDF.XSD.Boolean.ebv() == nil
end
test "fn_not/1" do
assert RDF.true |> RDF.XSD.Boolean.fn_not() == RDF.false
assert false |> RDF.XSD.Boolean.fn_not() == RDF.true
end
test "logical_and/1" do
assert RDF.true |> RDF.XSD.Boolean.logical_and(false) == RDF.false
assert true |> RDF.XSD.Boolean.logical_and(RDF.true) == RDF.true
assert false |> RDF.XSD.Boolean.logical_and(false) == RDF.false
assert 42 |> RDF.XSD.Boolean.logical_and(self()) == nil
end
test "logical_or/1" do
assert RDF.true |> RDF.XSD.Boolean.logical_or(false) == RDF.true
assert true |> RDF.XSD.Boolean.logical_or(RDF.true) == RDF.true
assert false |> RDF.XSD.Boolean.logical_or(false) == RDF.false
assert self() |> RDF.XSD.Boolean.logical_or(self()) == nil
end
end
describe "RDF.XSD.DateTime" do
test "now/0" do
assert %Literal{literal: %XSD.DateTime{}} = RDF.XSD.DateTime.now()
end
end
end

View file

@ -1,361 +1,409 @@
defmodule RDF.EqualityTest do defmodule RDF.EqualityTest do
use RDF.Test.Case use RDF.Test.Case
alias Decimal, as: D alias RDF.XSD
describe "RDF.IRI" do describe "RDF.IRI" do
@term_equal_iris [ @term_equal_iris [
{RDF.iri("http://example.com/"), RDF.iri("http://example.com/")}, {RDF.iri("http://example.com/"), RDF.iri("http://example.com/")},
] ]
@term_unequal_iris [
{RDF.iri("http://example.com/foo"), RDF.iri("http://example.com/bar")},
]
@value_equal_iris [ @value_equal_iris [
{RDF.iri("http://example.com/"), {RDF.iri("http://example.com/"), XSD.anyURI("http://example.com/")},
RDF.anyURI("http://example.com/")}, {XSD.anyURI("http://example.com/"), XSD.anyURI("http://example.com/")},
{RDF.anyURI("http://example.com/"),
RDF.iri("http://example.com/")},
{RDF.anyURI("http://example.com/"),
RDF.anyURI("http://example.com/")},
] ]
@value_unequal_iris [ @unequal_iris [
{RDF.iri("http://example.com/foo"), {RDF.iri("http://example.com/foo"), RDF.iri("http://example.com/bar")},
RDF.anyURI("http://example.com/bar")}, {RDF.iri("http://example.com/foo"), XSD.anyURI("http://example.com/bar")},
] ]
@equal_iris_by_coercion []
@unequal_iris_by_coercion []
@incomparable_iris [ @incomparable_iris [
{RDF.iri("http://example.com/"), RDF.string("http://example.com/")}, {RDF.iri("http://example.com/"), XSD.string("http://example.com/")},
] ]
test "term equality", do: assert_term_equal @term_equal_iris test "term equality", do: assert_term_equal(@term_equal_iris)
test "term inequality", do: assert_term_unequal @term_unequal_iris @tag skip: "TODO: finish value equality of XSD.AnyURI"
@tag skip: "TODO: finish equality extension of XSD.AnyURI" test "value equality", do: assert_value_equal(@value_equal_iris)
test "value equality", do: assert_value_equal @value_equal_iris @tag skip: "TODO: finish value equality of XSD.AnyURI"
@tag skip: "TODO: finish equality extension of XSD.AnyURI" test "inequality", do: assert_unequal(@unequal_iris)
test "value inequality", do: assert_value_unequal @value_unequal_iris test "coerced value equality", do: assert_coerced_equal(@equal_iris_by_coercion)
test "incomparability", do: assert_incomparable @incomparable_iris test "coerced value inequality", do: assert_coerced_unequal(@unequal_iris_by_coercion)
test "incomparability", do: assert_incomparable(@incomparable_iris)
end end
describe "RDF.BlankNode" do describe "RDF.BlankNode" do
@term_equal_bnodes [ @term_equal_bnodes [
{RDF.bnode("foo"), RDF.bnode("foo")}, {RDF.bnode("foo"), RDF.bnode("foo")},
] ]
@term_unequal_bnodes [
{RDF.bnode("foo"), RDF.bnode("bar")},
]
@value_equal_bnodes [ @value_equal_bnodes [
] ]
@value_unequal_bnodes [ @unequal_bnodes [
{RDF.bnode("foo"), RDF.bnode("bar")},
] ]
@equal_bnodes_by_coercion []
@unequal_bnodes_by_coercion []
@incomparable_bnodes [ @incomparable_bnodes [
{RDF.bnode("foo"), RDF.string("foo")}, {RDF.bnode("foo"), XSD.string("foo")},
{RDF.string("foo"), RDF.bnode("foo")}, {XSD.string("foo"), RDF.bnode("foo")},
] ]
test "term equality", do: assert_term_equal @term_equal_bnodes test "term equality", do: assert_term_equal @term_equal_bnodes
test "term inequality", do: assert_term_unequal @term_unequal_bnodes test "value equality", do: assert_value_equal @value_equal_bnodes
test "value equality", do: assert_value_equal @value_equal_bnodes test "inequality", do: assert_unequal @unequal_bnodes
test "value inequality", do: assert_value_unequal @value_unequal_bnodes test "coerced value equality", do: assert_coerced_equal(@equal_bnodes_by_coercion)
test "incomparability", do: assert_incomparable @incomparable_bnodes test "coerced value inequality", do: assert_coerced_unequal(@unequal_bnodes_by_coercion)
test "incomparability", do: assert_incomparable @incomparable_bnodes
end end
describe "RDF.String and RDF.LangString" do describe "XSD.String" do
@term_equal_strings [ @term_equal_strings [
{RDF.string("foo"), RDF.string("foo")}, {XSD.string("foo"), XSD.string("foo")}
{RDF.lang_string("foo", language: "de"), RDF.lang_string("foo", language: "de")},
] ]
@term_unequal_strings [ @value_equal_strings []
{RDF.string("foo"), RDF.string("bar")}, @unequal_strings [
{RDF.lang_string("foo", language: "de"), RDF.lang_string("bar", language: "de")}, {XSD.string("foo"), XSD.string("bar")}
] ]
@value_equal_strings [ @equal_strings_by_coercion [
{XSD.string("foo"), "foo"}
] ]
@value_unequal_strings [ @unequal_strings_by_coercion [
] {XSD.string("foo"), "bar"}
@value_equal_strings_by_coercion [
{RDF.string("foo"), "foo"},
]
@value_unequal_strings_by_coercion [
{RDF.string("foo"), "bar"},
] ]
@incomparable_strings [ @incomparable_strings [
{RDF.string("42"), 42}, {XSD.string("42"), 42}
{RDF.lang_string("foo", language: "de"), "foo"},
{RDF.string("foo"), RDF.lang_string("foo", language: "de")},
{RDF.lang_string("foo", language: "de"), RDF.string("foo")},
{RDF.string("foo"), RDF.bnode("foo")},
] ]
test "term equality", do: assert_term_equal @term_equal_strings test "term equality", do: assert_term_equal(@term_equal_strings)
test "term inequality", do: assert_term_unequal @term_unequal_strings test "value equality", do: assert_value_equal(@value_equal_strings)
test "value equality", do: assert_value_equal @value_equal_strings test "inequality", do: assert_unequal(@unequal_strings)
test "value inequality", do: assert_value_unequal @value_unequal_strings test "coerced value equality", do: assert_coerced_equal(@equal_strings_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_strings_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_strings_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_strings_by_coercion test "incomparability", do: assert_incomparable(@incomparable_strings)
test "incomparability", do: assert_incomparable @incomparable_strings
end end
describe "RDF.Boolean" do describe "XSD.String and RDF.LangString" do
@term_equal_booleans [ @term_equal_strings [
{RDF.true, RDF.true}, {XSD.string("foo"), XSD.string("foo")},
{RDF.false, RDF.false}, {RDF.lang_string("foo", language: "de"), RDF.lang_string("foo", language: "de")},
# invalid literals
{RDF.boolean("foo"), RDF.boolean("foo")},
] ]
@term_unequal_booleans [ @value_equal_strings []
{RDF.true, RDF.false}, @unequal_strings [
{RDF.false, RDF.true}, {XSD.string("foo"), XSD.string("bar")},
# invalid literals {RDF.lang_string("foo", language: "de"), RDF.lang_string("bar", language: "de")},
{RDF.boolean("foo"), RDF.boolean("bar")}, ]
@equal_strings_by_coercion [
{XSD.string("foo"), "foo"}
]
@unequal_strings_by_coercion [
{XSD.string("foo"), "bar"}
]
@incomparable_strings [
{XSD.string("42"), 42},
{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")},
]
test "term equality", do: assert_term_equal(@term_equal_strings)
test "value equality", do: assert_value_equal(@value_equal_strings)
test "inequality", do: assert_unequal(@unequal_strings)
test "coerced value equality", do: assert_coerced_equal(@equal_strings_by_coercion)
test "coerced value inequality", do: assert_coerced_unequal(@unequal_strings_by_coercion)
test "incomparability", do: assert_incomparable(@incomparable_strings)
end
describe "XSD.Boolean" do
@term_equal_booleans [
{XSD.true(), XSD.true()},
{XSD.false(), XSD.false()}
] ]
@value_equal_booleans [ @value_equal_booleans [
{RDF.true, RDF.boolean("1")}, {XSD.true(), XSD.boolean("1")},
{RDF.boolean(0), RDF.false}, {XSD.false(), XSD.boolean("0")}
# invalid literals
{RDF.boolean("foo"), RDF.boolean("foo")},
] ]
@value_unequal_booleans [ @unequal_booleans [
{RDF.true, RDF.boolean("false")}, {XSD.true(), XSD.false()},
{RDF.boolean(0), RDF.true}, {XSD.true(), XSD.boolean("false")},
# invalid literals {XSD.true(), XSD.boolean(0)}
{RDF.boolean("foo"), RDF.boolean("bar")},
] ]
@value_equal_booleans_by_coercion [ @equal_booleans_by_coercion [
{RDF.true, true}, {XSD.true(), true},
{RDF.false, false}, {XSD.false(), false}
] ]
@value_unequal_booleans_by_coercion [ @unequal_booleans_by_coercion [
{RDF.true, false}, {XSD.true(), false},
{RDF.false, true}, {XSD.false(), true}
]
@equal_invalid_booleans [
{XSD.boolean("foo"), XSD.boolean("foo")}
]
@unequal_invalid_booleans [
{XSD.boolean("foo"), XSD.boolean("bar")},
{XSD.true(), XSD.boolean("True")},
{XSD.false(), XSD.boolean("FALSE")}
] ]
@incomparable_booleans [ @incomparable_booleans [
{RDF.false, nil}, {XSD.false(), nil},
{RDF.true, 42}, {XSD.true(), 42},
{RDF.true, RDF.string("FALSE")}, {XSD.true(), XSD.integer(0)},
{RDF.true, RDF.integer(0)}, {XSD.true(), XSD.non_negative_integer(0)}
] ]
test "term equality", do: assert_term_equal @term_equal_booleans test "term equality", do: assert_term_equal(@term_equal_booleans)
test "term inequality", do: assert_term_unequal @term_unequal_booleans test "value equality", do: assert_value_equal(@value_equal_booleans)
test "value equality", do: assert_value_equal @value_equal_booleans test "inequality", do: assert_unequal(@unequal_booleans)
test "value inequality", do: assert_value_unequal @value_unequal_booleans test "coerced value equality", do: assert_coerced_equal(@equal_booleans_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_booleans_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_booleans_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_booleans_by_coercion test "invalid equality", do: assert_equal_invalid(@equal_invalid_booleans)
test "incomparability", do: assert_incomparable @incomparable_booleans test "invalid inequality", do: assert_unequal_invalid(@unequal_invalid_booleans)
test "incomparability", do: assert_incomparable(@incomparable_booleans)
end end
describe "RDF.Numeric" do describe "XSD.Numeric" do
@term_equal_numerics [ @term_equal_numerics [
{RDF.integer(42), RDF.integer(42)}, {XSD.integer(42), XSD.integer(42)},
{RDF.integer("042"), RDF.integer("042")}, {XSD.integer(42), XSD.integer("42")},
# invalid literals {XSD.integer("042"), XSD.integer("042")},
{RDF.integer("foo"), RDF.integer("foo")}, {XSD.double("1.0"), XSD.double(1.0)},
{RDF.decimal("foo"), RDF.decimal("foo")}, {XSD.double("-42.0"), XSD.double(-42.0)},
{RDF.double("foo"), RDF.double("foo")}, {XSD.double("1.0"), XSD.double(1.0)},
] {XSD.float("1.0"), XSD.float(1.0)},
@term_unequal_numerics [ {XSD.decimal("1.0"), XSD.decimal(1.0)},
{RDF.integer(1), RDF.integer(2)}, {XSD.decimal("-42.0"), XSD.decimal(-42.0)},
# invalid literals {XSD.decimal("1.0"), XSD.decimal(1.0)}
{RDF.integer("foo"), RDF.integer("bar")},
{RDF.decimal("foo"), RDF.decimal("bar")},
{RDF.double("foo"), RDF.double("bar")},
] ]
@value_equal_numerics [ @value_equal_numerics [
{RDF.integer("42"), RDF.integer("042")}, {XSD.integer("42"), XSD.non_negative_integer("42")},
{RDF.integer("42"), RDF.double("42")}, {XSD.integer("42"), XSD.positive_integer("42")},
{RDF.integer(42), RDF.double(42.0)}, {XSD.integer("42"), XSD.double("42")},
{RDF.integer("42"), RDF.decimal("42")}, {XSD.integer("42"), XSD.decimal("42")},
{RDF.integer(42), RDF.decimal(42.0)}, {XSD.double(3.14), XSD.float(3.14)},
{RDF.double(3.14), RDF.decimal(3.14)}, {XSD.double(3.14), XSD.decimal(3.14)},
{RDF.double("+0"), RDF.double("-0")}, {XSD.float(3.14), XSD.decimal(3.14)},
{RDF.decimal("+0"), RDF.decimal("-0")}, {XSD.integer(42), XSD.integer("042")},
# invalid literals {XSD.integer("42"), XSD.integer("042")},
{RDF.integer("foo"), RDF.integer("foo")}, {XSD.integer(42), XSD.integer("+42")},
{RDF.decimal("foo"), RDF.decimal("foo")}, {XSD.integer("42"), XSD.integer("+42")},
{RDF.double("foo"), RDF.double("foo")}, {XSD.integer(42), XSD.decimal(42.0)},
{XSD.integer(42), XSD.double(42.0)},
{XSD.integer(42), XSD.float(42.0)},
{XSD.non_negative_integer(42), XSD.decimal(42.0)},
{XSD.non_negative_integer(42), XSD.double(42.0)},
{XSD.positive_integer(42), XSD.decimal(42.0)},
{XSD.positive_integer(42), XSD.double(42.0)},
{XSD.double("+0"), XSD.double("-0")},
{XSD.double("1"), XSD.double(1.0)},
{XSD.double("01"), XSD.double(1.0)},
{XSD.double("1.0E0"), XSD.double(1.0)},
{XSD.double("1.0E0"), XSD.double("1.0")},
{XSD.double("+42"), XSD.double(42.0)},
{XSD.decimal("+0"), XSD.decimal("-0")},
{XSD.decimal("1"), XSD.decimal(1.0)},
{XSD.decimal("01"), XSD.decimal(1.0)},
{XSD.decimal("+42"), XSD.decimal(42.0)}
] ]
@value_unequal_numerics [ @unequal_numerics [
{RDF.integer("1"), RDF.double("1.1")}, {XSD.integer(1), XSD.integer(2)},
{RDF.integer("1"), RDF.decimal("1.1")}, {XSD.integer("1"), XSD.double("1.1")},
# invalid literals {XSD.integer("1"), XSD.decimal("1.1")}
{RDF.integer("foo"), RDF.integer("bar")},
{RDF.decimal("foo"), RDF.decimal("bar")},
{RDF.double("foo"), RDF.double("bar")},
] ]
@value_equal_numerics_by_coercion [ @equal_numerics_by_coercion [
{RDF.integer(42), 42}, {XSD.integer(42), 42},
{RDF.integer(42), 42.0}, {XSD.integer(42), 42.0},
{RDF.integer(42), D.new(42)}, {XSD.integer(42), Elixir.Decimal.new(42)},
{RDF.decimal(42), 42}, {XSD.decimal(42), 42},
{RDF.decimal(3.14), 3.14}, {XSD.decimal(3.14), 3.14},
{RDF.decimal(3.14), D.from_float(3.14)}, {XSD.decimal(3.14), Elixir.Decimal.from_float(3.14)},
{RDF.double(42), 42}, {XSD.double(42), 42},
{RDF.double(3.14), 3.14}, {XSD.double(3.14), 3.14},
{RDF.double(3.14), D.from_float(3.14)}, {XSD.double(3.14), Elixir.Decimal.from_float(3.14)},
{XSD.float(3.14), 3.14}
] ]
@value_unequal_numerics_by_coercion [ @unequal_numerics_by_coercion [
{RDF.integer(3), 3.14}, {XSD.integer(3), 3.14},
{RDF.integer(3), D.from_float(3.14)}, {XSD.integer(3), Elixir.Decimal.from_float(3.14)},
{RDF.double(3.14), 3}, {XSD.double(3.14), 3},
{RDF.decimal(3.14), 3}, {XSD.float(3.14), 3},
{XSD.decimal(3.14), 3}
]
@equal_invalid_numerics [
{XSD.integer("foo"), XSD.integer("foo")},
{XSD.decimal("foo"), XSD.decimal("foo")},
{XSD.double("foo"), XSD.double("foo")},
{XSD.float("foo"), XSD.float("foo")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("foo")},
{XSD.positive_integer("foo"), XSD.positive_integer("foo")}
]
@unequal_invalid_numerics [
{XSD.integer("foo"), XSD.integer("bar")},
{XSD.decimal("foo"), XSD.decimal("bar")},
{XSD.decimal("1.0E0"), XSD.decimal(1.0)},
{XSD.decimal("1.0E0"), XSD.decimal("1.0")},
{XSD.double("foo"), XSD.double("bar")},
{XSD.float("foo"), XSD.float("bar")},
{XSD.non_negative_integer("foo"), XSD.non_negative_integer("bar")},
{XSD.positive_integer("foo"), XSD.positive_integer("bar")}
] ]
@incomparable_numerics [ @incomparable_numerics [
{RDF.integer("42"), nil}, {XSD.integer("42"), nil},
{RDF.integer("42"), true}, {XSD.integer("42"), true},
{RDF.integer("42"), "42"}, {XSD.integer("42"), "42"},
{RDF.integer("42"), RDF.string("42")}, {XSD.integer("42"), XSD.string("42")}
] ]
test "term equality", do: assert_term_equal @term_equal_numerics test "term equality", do: assert_term_equal(@term_equal_numerics)
test "term inequality", do: assert_term_unequal @term_unequal_numerics test "value equality", do: assert_value_equal(@value_equal_numerics)
test "value equality", do: assert_value_equal @value_equal_numerics test "inequality", do: assert_unequal(@unequal_numerics)
test "value inequality", do: assert_value_unequal @value_unequal_numerics test "coerced value equality", do: assert_coerced_equal(@equal_numerics_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_numerics_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_numerics_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_numerics_by_coercion test "invalid equality", do: assert_equal_invalid(@equal_invalid_numerics)
test "incomparability", do: assert_incomparable @incomparable_numerics test "invalid inequality", do: assert_unequal_invalid(@unequal_invalid_numerics)
test "incomparability", do: assert_incomparable(@incomparable_numerics)
test "NaN is not equal to itself" do
refute XSD.Double.equal_value?(XSD.double(:nan), XSD.double(:nan))
end
end end
describe "RDF.DateTime" do describe "XSD.DateTime" do
@term_equal_datetimes [ @term_equal_datetimes [
{RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T12:00:00-01:00")}, {XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")},
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T12:00:00")}, {XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")}
# invalid literals
{RDF.date_time("foo"), RDF.date_time("foo")},
]
@term_unequal_datetimes [
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T17:00:00")},
# invalid literals
{RDF.date_time("foo"), RDF.date_time("bar")},
] ]
@value_equal_datetimes [ @value_equal_datetimes [
{RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T17:00:00+04:00")}, {XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T17:00:00+04:00")},
{RDF.date_time("2002-04-02T23:00:00-04:00"), RDF.date_time("2002-04-03T02:00:00-01:00")}, {XSD.datetime("2002-04-02T23:00:00-04:00"), XSD.datetime("2002-04-03T02:00:00-01:00")},
{RDF.date_time("1999-12-31T24:00:00"), RDF.date_time("2000-01-01T00:00:00")}, {XSD.datetime("1999-12-31T24:00:00"), XSD.datetime("2000-01-01T00:00:00")},
{XSD.datetime("2002-04-02T23:00:00Z"), XSD.datetime("2002-04-02T23:00:00+00:00")},
{RDF.date_time("2002-04-02T23:00:00Z"), RDF.date_time("2002-04-02T23:00:00+00:00")}, {XSD.datetime("2002-04-02T23:00:00Z"), XSD.datetime("2002-04-02T23:00:00-00:00")},
{RDF.date_time("2002-04-02T23:00:00Z"), RDF.date_time("2002-04-02T23:00:00-00:00")}, {XSD.datetime("2010-01-01T00:00:00+00:00"), XSD.datetime("2010-01-01T00:00:00Z")},
{RDF.date_time("2002-04-02T23:00:00+00:00"), RDF.date_time("2002-04-02T23:00:00-00:00")}, {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")},
# invalid literals {XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-05T00:00:00")}
{RDF.date_time("foo"), RDF.date_time("foo")},
] ]
@value_unequal_datetimes [ @unequal_datetimes [
{RDF.date_time("2005-04-04T24:00:00"), RDF.date_time("2005-04-04T00:00:00")}, {XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T17:00:00")},
# invalid literals {XSD.datetime("2005-04-04T24:00:00"), XSD.datetime("2005-04-04T00:00:00")}
{RDF.date_time("foo"), RDF.date_time("bar")},
] ]
@value_equal_datetimes_by_coercion [ @equal_datetimes_by_coercion [
{RDF.date_time("2002-04-02T12:00:00-01:00"), elem(DateTime.from_iso8601("2002-04-02T12:00:00-01:00"), 1)}, {XSD.datetime("2002-04-02T12:00:00-01:00"),
{RDF.date_time("2002-04-02T12:00:00"), ~N"2002-04-02T12:00:00"}, elem(DateTime.from_iso8601("2002-04-02T12:00:00-01:00"), 1)},
{RDF.date_time("2002-04-02T23:00:00Z"), elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)}, {XSD.datetime("2002-04-02T12:00:00"), ~N"2002-04-02T12:00:00"},
{RDF.date_time("2002-04-02T23:00:00+00:00"), elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)}, {XSD.datetime("2002-04-02T23:00:00Z"),
{RDF.date_time("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:00+00:00"), 1)},
{RDF.date_time("2002-04-02T23:00:00-00:00"), 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)},
{XSD.datetime("2002-04-02T23:00:00-00:00"),
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)}
] ]
@value_unequal_datetimes_by_coercion [ @unequal_datetimes_by_coercion [
{RDF.date_time("2002-04-02T12:00:00-01:00"), elem(DateTime.from_iso8601("2002-04-02T12:00:00+00:00"), 1)}, {XSD.datetime("2002-04-02T12:00:00-01:00"),
elem(DateTime.from_iso8601("2002-04-02T12:00:00+00:00"), 1)}
]
@equal_invalid_datetimes [
{XSD.datetime("foo"), XSD.datetime("foo")}
]
@unequal_invalid_datetimes [
{XSD.datetime("foo"), XSD.datetime("bar")}
] ]
@incomparable_datetimes [ @incomparable_datetimes [
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T12:00:00Z")}, {XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00Z")},
{RDF.string("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T12:00:00-01:00")}, {XSD.datetime("2010-01-01T00:00:00Z"), XSD.datetime("2010-01-01T00:00:00")},
{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 # These are incomparable because of indeterminacy due to missing timezone
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T23:00:00+00:00")}, {XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T23:00:00+00:00")}
] ]
test "term equality", do: assert_term_equal @term_equal_datetimes test "term equality", do: assert_term_equal(@term_equal_datetimes)
test "term inequality", do: assert_term_unequal @term_unequal_datetimes test "value equality", do: assert_value_equal(@value_equal_datetimes)
test "value equality", do: assert_value_equal @value_equal_datetimes test "inequality", do: assert_unequal(@unequal_datetimes)
test "value inequality", do: assert_value_unequal @value_unequal_datetimes test "coerced value equality", do: assert_coerced_equal(@equal_datetimes_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_datetimes_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_datetimes_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_datetimes_by_coercion test "invalid equality", do: assert_equal_invalid(@equal_invalid_datetimes)
test "incomparability", do: assert_incomparable @incomparable_datetimes test "invalid inequality", do: assert_unequal_invalid(@unequal_invalid_datetimes)
test "incomparability", do: assert_incomparable(@incomparable_datetimes)
end end
describe "RDF.Date" do describe "XSD.Date" do
@term_equal_dates [ @term_equal_dates [
{RDF.date("2002-04-02-01:00"), RDF.date("2002-04-02-01:00")}, {XSD.date("2002-04-02-01:00"), XSD.date("2002-04-02-01:00")},
{RDF.date("2002-04-02"), RDF.date("2002-04-02")}, {XSD.date("2002-04-02"), XSD.date("2002-04-02")}
# invalid literals
{RDF.date("foo"), RDF.date("foo")},
]
@term_unequal_dates [
{RDF.date("2002-04-01"), RDF.date("2002-04-02")},
# invalid literals
{RDF.date("foo"), RDF.date("bar")},
] ]
@value_equal_dates [ @value_equal_dates [
{RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02+00:00")}, {XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02+00:00")},
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02+00:00")}, {XSD.date("2002-04-02Z"), XSD.date("2002-04-02+00:00")},
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02-00:00")}, {XSD.date("2002-04-02Z"), XSD.date("2002-04-02-00:00")}
] ]
@value_unequal_dates [ @unequal_dates [
{RDF.date("2002-04-03Z"), RDF.date("2002-04-02")}, {XSD.date("2002-04-01"), XSD.date("2002-04-02")}
{RDF.date("2002-04-03Z"), RDF.date("2002-04-02")},
{RDF.date("2002-04-03"), RDF.date("2002-04-02Z")},
{RDF.date("2002-04-03+00:00"), RDF.date("2002-04-02")},
{RDF.date("2002-04-03-00:00"), RDF.date("2002-04-02")},
# invalid literals
{RDF.date("2002.04.02"), RDF.date("2002-04-02")},
] ]
@value_equal_dates_by_coercion [ @equal_dates_by_coercion [
{RDF.date("2002-04-02"), Date.from_iso8601!("2002-04-02")}, {XSD.date("2002-04-02"), Date.from_iso8601!("2002-04-02")}
] ]
@value_unequal_dates_by_coercion [ @unequal_dates_by_coercion [
{RDF.date("2002-04-02"), Date.from_iso8601!("2002-04-03")}, {XSD.date("2002-04-02"), Date.from_iso8601!("2002-04-03")}
{RDF.date("2002-04-03+01:00"), Date.from_iso8601!("2002-04-02")}, ]
{RDF.date("2002-04-03Z"), Date.from_iso8601!("2002-04-02")}, @equal_invalid_dates [
{RDF.date("2002-04-03+00:00"), Date.from_iso8601!("2002-04-02")}, {XSD.date("foo"), XSD.date("foo")}
{RDF.date("2002-04-03-00:00"), Date.from_iso8601!("2002-04-02")}, ]
@unequal_invalid_dates [
{XSD.date("2002.04.02"), XSD.date("2002-04-02")},
{XSD.date("foo"), XSD.date("bar")}
] ]
@incomparable_dates [ @incomparable_dates [
{RDF.date("2002-04-02"), RDF.string("2002-04-02")}, {XSD.date("2002-04-02"), XSD.string("2002-04-02")},
# These are incomparable because of indeterminacy due to missing timezone # These are incomparable because of indeterminacy due to missing timezone
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02")}, {XSD.date("2002-04-02Z"), XSD.date("2002-04-02")},
{RDF.date("2002-04-02"), RDF.date("2002-04-02Z")}, {XSD.date("2002-04-02"), XSD.date("2002-04-02Z")},
{RDF.date("2002-04-02+00:00"), RDF.date("2002-04-02")}, {XSD.date("2010-01-01Z"), XSD.date(~D[2010-01-01])},
{RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02")}, {XSD.date("2010-01-01+00:00"), XSD.date(~D[2010-01-01])},
{RDF.date("2002-04-02+01:00"), Date.from_iso8601!("2002-04-02")}, {XSD.date("2002-04-02+00:00"), XSD.date("2002-04-02")},
{RDF.date("2002-04-02Z"), Date.from_iso8601!("2002-04-02")}, {XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02")},
{RDF.date("2002-04-02+00:00"), Date.from_iso8601!("2002-04-02")}, {XSD.date("2002-04-02+01:00"), Date.from_iso8601!("2002-04-02")},
{RDF.date("2002-04-02-00:00"), Date.from_iso8601!("2002-04-02")}, {XSD.date("2002-04-02Z"), Date.from_iso8601!("2002-04-02")},
{XSD.date("2002-04-02+00:00"), Date.from_iso8601!("2002-04-02")},
{XSD.date("2002-04-02-00:00"), Date.from_iso8601!("2002-04-02")}
] ]
test "term equality", do: assert_term_equal @term_equal_dates test "term equality", do: assert_term_equal(@term_equal_dates)
test "term inequality", do: assert_term_unequal @term_unequal_dates test "value equality", do: assert_value_equal(@value_equal_dates)
test "value equality", do: assert_value_equal @value_equal_dates test "inequality", do: assert_unequal(@unequal_dates)
test "value inequality", do: assert_value_unequal @value_unequal_dates test "coerced value equality", do: assert_coerced_equal(@equal_dates_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_dates_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_dates_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_dates_by_coercion test "invalid equality", do: assert_equal_invalid(@equal_invalid_dates)
test "incomparability", do: assert_incomparable @incomparable_dates test "invalid inequality", do: assert_unequal_invalid(@unequal_invalid_dates)
test "incomparability", do: assert_incomparable(@incomparable_dates)
end end
describe "equality between RDF.Date and RDF.DateTime" do describe "equality between XSD.Date and XSD.DateTime" do
# It seems quite strange that open-world test date-2 from the SPARQL 1.0 test suite # It seems quite strange that open-world test date-2 from the SPARQL 1.0 test suite
# allows for equality comparisons between dates and datetimes, but disallows # allows for equality comparisons between dates and datetimes, but disallows
# ordering comparisons in the date-3 test. # ordering comparisons in the date-3 test.
# #
# @value_equal_dates_and_datetimes [ # @value_equal_dates_and_datetimes [
# {RDF.date("2002-04-02"), RDF.datetime("2002-04-02T00:00:00")}, # {XSD.date("2002-04-02"), XSD.datetime("2002-04-02T00:00:00")},
# {RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02")}, # {XSD.datetime("2002-04-02T00:00:00"), XSD.date("2002-04-02")},
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00Z")}, # {XSD.date("2002-04-02Z"), XSD.datetime("2002-04-02T00:00:00Z")},
# {RDF.datetime("2002-04-02T00:00:00Z"), RDF.date("2002-04-02Z")}, # {XSD.datetime("2002-04-02T00:00:00Z"), XSD.date("2002-04-02Z")},
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00+00:00")}, # {XSD.date("2002-04-02Z"), XSD.datetime("2002-04-02T00:00:00+00:00")},
# {RDF.datetime("2002-04-02T00:00:00-00:00"), RDF.date("2002-04-02Z")}, # {XSD.datetime("2002-04-02T00:00:00-00:00"), XSD.date("2002-04-02Z")},
# ] # ]
# @value_unequal_dates_and_datetimes [ # @value_unequal_dates_and_datetimes [
# {RDF.date("2002-04-01"), RDF.datetime("2002-04-02T00:00:00")}, # {XSD.date("2002-04-01"), XSD.datetime("2002-04-02T00:00:00")},
# {RDF.datetime("2002-04-01T00:00:00"), RDF.date("2002-04-02")}, # {XSD.datetime("2002-04-01T00:00:00"), XSD.date("2002-04-02")},
# {RDF.date("2002-04-01Z"), RDF.datetime("2002-04-02T00:00:00Z")}, # {XSD.date("2002-04-01Z"), XSD.datetime("2002-04-02T00:00:00Z")},
# {RDF.datetime("2002-04-01T00:00:00Z"), RDF.date("2002-04-02Z")}, # {XSD.datetime("2002-04-01T00:00:00Z"), XSD.date("2002-04-02Z")},
# {RDF.date("2002-04-01Z"), RDF.datetime("2002-04-02T00:00:00+00:00")}, # {XSD.date("2002-04-01Z"), XSD.datetime("2002-04-02T00:00:00+00:00")},
# {RDF.datetime("2002-04-01T00:00:00-00:00"), RDF.date("2002-04-02Z")}, # {XSD.datetime("2002-04-01T00:00:00-00:00"), XSD.date("2002-04-02Z")},
# ] # ]
# @incomparable_dates_and_datetimes [ # @incomparable_dates_and_datetimes [
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00")}, # {XSD.date("2002-04-02Z"), XSD.datetime("2002-04-02T00:00:00")},
# {RDF.datetime("2002-04-02T00:00:00Z"), RDF.date("2002-04-02")}, # {XSD.datetime("2002-04-02T00:00:00Z"), XSD.date("2002-04-02")},
# {RDF.date("2002-04-02"), RDF.datetime("2002-04-02T00:00:00Z")}, # {XSD.date("2002-04-02"), XSD.datetime("2002-04-02T00:00:00Z")},
# {RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02Z")}, # {XSD.datetime("2002-04-02T00:00:00"), XSD.date("2002-04-02Z")},
# ] # ]
# #
# test "value equality", do: assert_value_equal @value_equal_dates_and_datetimes # test "value equality", do: assert_value_equal @value_equal_dates_and_datetimes
@ -363,49 +411,82 @@ defmodule RDF.EqualityTest do
# test "incomparability", do: assert_incomparable @incomparable_dates_and_datetimes # test "incomparability", do: assert_incomparable @incomparable_dates_and_datetimes
@value_unequal_dates_and_datetimes [ @value_unequal_dates_and_datetimes [
{RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02")}, {XSD.datetime("2002-04-02T00:00:00"), XSD.date("2002-04-02")},
{RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-01")}, {XSD.datetime("2002-04-02T00:00:00"), XSD.date("2002-04-01")}
] ]
test "value inequality", do: assert_value_unequal @value_unequal_dates_and_datetimes test "value inequality", do: assert_unequal(@value_unequal_dates_and_datetimes)
end end
describe "RDF.Time" do describe "XSD.Time" do
@term_equal_times [ @term_equal_times [
{RDF.time("12:00:00+01:00"), RDF.time("12:00:00+01:00")}, {XSD.time("12:00:00+01:00"), XSD.time("12:00:00+01:00")},
{RDF.time("12:00:00"), RDF.time("12:00:00")}, {XSD.time("12:00:00"), XSD.time("12:00:00")}
# invalid literals
{RDF.time("foo"), RDF.time("foo")},
]
@term_unequal_times [
{RDF.time("12:00:00"), RDF.time("13:00:00")},
# invalid literals
{RDF.time("foo"), RDF.time("bar")},
] ]
@value_equal_times [ @value_equal_times [
{XSD.time("00:00:00+00:00"), XSD.time("00:00:00Z")}
] ]
@value_unequal_times [ @unequal_times [
{XSD.time("12:00:00"), XSD.time("13:00:00")},
{XSD.time("00:00:00.0000Z"), XSD.time("00:00:00Z")}
] ]
@value_equal_times_by_coercion [ @equal_times_by_coercion [
{RDF.time("12:00:00"), Time.from_iso8601!("12:00:00")}, {XSD.time("12:00:00"), Time.from_iso8601!("12:00:00")}
] ]
@value_unequal_times_by_coercion [ @unequal_times_by_coercion [
{RDF.time("12:00:00"), Time.from_iso8601!("13:00:00")}, {XSD.time("12:00:00"), Time.from_iso8601!("13:00:00")}
]
@equal_invalid_times [
{XSD.time("foo"), XSD.time("foo")}
]
@unequal_invalid_times [
{XSD.time("foo"), XSD.time("bar")}
] ]
@incomparable_times [ @incomparable_times [
{RDF.time("12:00:00"), RDF.string("12:00:00")}, {XSD.time("12:00:00"), XSD.string("12:00:00")},
{XSD.time("00:00:00"), XSD.time("00:00:00Z")},
{XSD.time("00:00:00.0000"), XSD.time("00:00:00Z")}
] ]
test "term equality", do: assert_term_equal @term_equal_times test "term equality", do: assert_term_equal(@term_equal_times)
test "term inequality", do: assert_term_unequal @term_unequal_times test "value equality", do: assert_value_equal(@value_equal_times)
test "value equality", do: assert_value_equal @value_equal_times test "inequality", do: assert_unequal(@unequal_times)
test "value inequality", do: assert_value_unequal @value_unequal_times test "coerced value equality", do: assert_coerced_equal(@equal_times_by_coercion)
test "coerced value equality", do: assert_value_equal @value_equal_times_by_coercion test "coerced value inequality", do: assert_coerced_unequal(@unequal_times_by_coercion)
test "coerced value inequality", do: assert_value_unequal @value_unequal_times_by_coercion test "invalid equality", do: assert_equal_invalid(@equal_invalid_times)
test "incomparability", do: assert_incomparable @incomparable_times test "invalid inequality", do: assert_unequal_invalid(@unequal_invalid_times)
test "incomparability", do: assert_incomparable(@incomparable_times)
end end
describe "RDF.Literals with unsupported types" do describe "XSD.AnyURI" do
@term_equal_uris [
{XSD.any_uri("http://example.com"), XSD.any_uri("http://example.com")}
]
@value_equal_uris []
@unequal_uris [
{XSD.any_uri("http://example.com"), XSD.any_uri("http://example.com#foo")}
]
@equal_uris_by_coercion [
{XSD.any_uri("http://example.com"), URI.parse("http://example.com")}
]
@unequal_uris_by_coercion [
{XSD.any_uri("http://example.com"), URI.parse("http://example.com#foo")}
]
@incomparable_uris [
{XSD.any_uri("http://example.com"), 42},
{XSD.any_uri("http://example.com"), "http://example.com"},
{XSD.any_uri("http://example.com"), XSD.string("http://example.com")}
]
test "term equality", do: assert_term_equal(@term_equal_uris)
test "value equality", do: assert_value_equal(@value_equal_uris)
test "inequality", do: assert_unequal(@unequal_uris)
test "coerced value equality", do: assert_coerced_equal(@equal_uris_by_coercion)
test "coerced value inequality", do: assert_coerced_unequal(@unequal_uris_by_coercion)
test "incomparability", do: assert_incomparable(@incomparable_uris)
end
describe "RDF.Literal.Generics" do
@equal_literals [ @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")}, RDF.literal("foo", datatype: "http://example.com/datatype")},
@ -419,72 +500,110 @@ defmodule RDF.EqualityTest do
RDF.literal("foo", datatype: "http://example.com/datatype2")}, RDF.literal("foo", datatype: "http://example.com/datatype2")},
] ]
test "term equality", do: assert_term_equal @equal_literals test "equality", do: assert_term_equal @equal_literals
test "term inequality", do: assert_value_unequal @unequal_literals test "inequality", do: assert_unequal @unequal_literals
test "incomparability", do: assert_incomparable @incomparable_literals test "incomparability", do: assert_incomparable @incomparable_literals
end end
defp assert_term_equal(examples) do defp assert_term_equal(examples) do
Enum.each examples, fn example -> assert_term_equality(example, true) end Enum.each(examples, fn example -> assert_equality(example, true) end)
Enum.each examples, fn example -> assert_value_equality(example, true) end Enum.each(examples, fn example -> assert_term_equality(example, true) end)
end Enum.each(examples, fn example -> assert_value_equality(example, true) end)
defp assert_term_unequal(examples) do
Enum.each examples, fn example -> assert_term_equality(example, false) end
Enum.each examples, fn example -> assert_value_equality(example, false) end
end end
defp assert_value_equal(examples) do defp assert_value_equal(examples) do
Enum.each examples, fn example -> assert_value_equality(example, true) end Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, true) end)
end end
defp assert_value_unequal(examples) do defp assert_unequal(examples) do
Enum.each examples, fn example -> assert_value_equality(example, false) end Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, false) end)
end
defp assert_coerced_equal(examples) do
Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, true) end)
end
defp assert_coerced_unequal(examples) do
Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, false) end)
end
def assert_equal_invalid(examples) do
Enum.each(examples, fn example -> assert_equality(example, true) end)
Enum.each(examples, fn example -> assert_term_equality(example, true) end)
Enum.each(examples, fn example -> assert_value_equality(example, true) end)
end
def assert_unequal_invalid(examples) do
Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, false) end)
end end
defp assert_incomparable(examples) do defp assert_incomparable(examples) do
Enum.each examples, fn example -> assert_term_equality(example, false) end Enum.each(examples, fn example -> assert_equality(example, false) end)
Enum.each examples, fn example -> assert_value_equality(example, nil) end Enum.each(examples, fn example -> assert_term_equality(example, false) end)
Enum.each(examples, fn example -> assert_value_equality(example, nil) end)
end
defp assert_equality({left, right}, expected) do
result = left == right
assert result == expected, """
expected #{inspect(left)} == #{inspect(right)})
to be: #{inspect(expected)}
but got: #{inspect(result)}
"""
end end
defp assert_term_equality({left, right}, expected) do defp assert_term_equality({left, right}, expected) do
result = RDF.Term.equal?(left, right) result = RDF.Term.equal?(left, right)
assert result == expected, """ assert result == expected, """
expected RDF.Term.equal?( expected RDF.Term.equal?(
#{inspect left}, #{inspect(left)},
#{inspect right}) #{inspect(right)})
to be: #{inspect expected} to be: #{inspect(expected)}
but got: #{inspect result} but got: #{inspect(result)}
""" """
result = RDF.Term.equal?(right, left) result = RDF.Term.equal?(right, left)
assert result == expected, """ assert result == expected, """
expected RDF.Term.equal?( expected RDF.Term.equal?(
#{inspect right}, #{inspect(right)},
#{inspect left}) #{inspect(left)})
to be: #{inspect expected} to be: #{inspect(expected)}
but got: #{inspect result} but got: #{inspect(result)}
""" """
end end
defp assert_value_equality({left, right}, expected) do defp assert_value_equality({left, right}, expected) do
result = RDF.Term.equal_value?(left, right) result = RDF.Term.equal_value?(left, right)
assert result == expected, """ assert result == expected, """
expected RDF.Term.equal_value?( expected RDF.Term.equal_value?(
#{inspect left}, #{inspect(left)},
#{inspect right}) #{inspect(right)})
to be: #{inspect expected} to be: #{inspect(expected)}
but got: #{inspect result} but got: #{inspect(result)}
""" """
result = RDF.Term.equal_value?(right, left) result = RDF.Term.equal_value?(right, left)
assert result == expected, """ assert result == expected, """
expected RDF.Term.equal_value?( expected RDF.Term.equal_value?(
#{inspect right}, #{inspect(right)},
#{inspect left}) #{inspect(left)})
to be: #{inspect expected} to be: #{inspect(expected)}
but got: #{inspect result} but got: #{inspect(result)}
""" """
end end
end end

View file

@ -190,7 +190,7 @@ defmodule RDF.ListTest do
RDF.List.from([[1]]) RDF.List.from([[1]])
assert [nested] = get_in(graph_with_list, [bnode, RDF.first]) 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, [bnode, RDF.rest]) == [RDF.nil]
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.integer(1)] assert get_in(graph_with_list, [nested, RDF.first]) == [XSD.integer(1)]
assert get_in(graph_with_list, [nested, RDF.rest]) == [RDF.nil] assert get_in(graph_with_list, [nested, RDF.rest]) == [RDF.nil]
assert %RDF.List{head: bnode, graph: graph_with_list} = assert %RDF.List{head: bnode, graph: graph_with_list} =
@ -198,9 +198,9 @@ defmodule RDF.ListTest do
assert get_in(graph_with_list, [bnode, RDF.first]) == [~L"foo"] assert get_in(graph_with_list, [bnode, RDF.first]) == [~L"foo"]
assert [second] = get_in(graph_with_list, [bnode, RDF.rest]) assert [second] = get_in(graph_with_list, [bnode, RDF.rest])
assert [nested] = get_in(graph_with_list, [second, RDF.first]) assert [nested] = get_in(graph_with_list, [second, RDF.first])
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.integer(1)] assert get_in(graph_with_list, [nested, RDF.first]) == [XSD.integer(1)]
assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest]) assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest])
assert get_in(graph_with_list, [nested_second, RDF.first]) == [RDF.integer(2)] 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 get_in(graph_with_list, [nested_second, RDF.rest]) == [RDF.nil]
assert [third] = get_in(graph_with_list, [second, RDF.rest]) 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.first]) == [~L"bar"]
@ -273,17 +273,17 @@ defmodule RDF.ListTest do
test "nested list", %{nested: nested} do test "nested list", %{nested: nested} do
assert RDF.List.values(nested) == assert RDF.List.values(nested) ==
[~L"foo", [RDF.integer(1), RDF.integer(2)], ~L"bar"] [~L"foo", [XSD.integer(1), XSD.integer(2)], ~L"bar"]
assert RDF.list(["foo", [1, 2]]) |> RDF.List.values == assert RDF.list(["foo", [1, 2]]) |> RDF.List.values ==
[~L"foo", [RDF.integer(1), RDF.integer(2)]] [~L"foo", [XSD.integer(1), XSD.integer(2)]]
assert RDF.list([[1, 2], "foo"]) |> RDF.List.values == assert RDF.list([[1, 2], "foo"]) |> RDF.List.values ==
[[RDF.integer(1), RDF.integer(2)], ~L"foo"] [[XSD.integer(1), XSD.integer(2)], ~L"foo"]
inner_list = RDF.list([1, 2], head: ~B<inner>) inner_list = RDF.list([1, 2], head: ~B<inner>)
assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph) assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph)
|> RDF.List.values == [~L"foo", [RDF.integer(1), RDF.integer(2)]] |> RDF.List.values == [~L"foo", [XSD.integer(1), XSD.integer(2)]]
end end
end end

View file

@ -0,0 +1,5 @@
defmodule RDF.Literal.DatatypeTest do
use RDF.Test.Case
doctest RDF.Literal.Datatype
end

View file

@ -4,19 +4,20 @@ defmodule RDF.LiteralTest do
import RDF.Sigils import RDF.Sigils
import RDF.TestLiterals import RDF.TestLiterals
alias RDF.{Literal, LangString} alias RDF.{Literal, XSD, LangString}
alias RDF.Literal.{Generic, Datatype} alias RDF.Literal.{Generic, Datatype}
alias Decimal, as: D
doctest RDF.Literal doctest RDF.Literal
alias RDF.NS alias RDF.NS
@examples %{ @examples %{
RDF.XSD.String => ["foo"], XSD.String => ["foo"],
RDF.XSD.Integer => [42], XSD.Integer => [42],
RDF.XSD.Double => [3.14], XSD.Double => [3.14],
RDF.XSD.Decimal => [Decimal.from_float(3.14)], XSD.Decimal => [Decimal.from_float(3.14)],
RDF.XSD.Boolean => [true, false], XSD.Boolean => [true, false],
} }
describe "new/1" do describe "new/1" do
@ -32,52 +33,51 @@ defmodule RDF.LiteralTest do
test "with typed literals" do test "with typed literals" do
Enum.each Datatype.Registry.datatypes() -- [RDF.LangString], fn datatype -> Enum.each Datatype.Registry.datatypes() -- [RDF.LangString], fn datatype ->
literal_type = datatype.literal_type() assert %Literal{literal: typed_literal} = Literal.new(datatype.new("foo"))
assert %Literal{literal: typed_literal} = Literal.new(literal_type.new("foo")) assert typed_literal.__struct__ == datatype
assert typed_literal.__struct__ == literal_type
end end
end end
test "when options without datatype given" do test "when options without datatype given" do
assert Literal.new(true, []) == RDF.XSD.Boolean.new(true) assert Literal.new(true, []) == XSD.Boolean.new(true)
assert Literal.new(42, []) == RDF.XSD.Integer.new(42) assert Literal.new(42, []) == XSD.Integer.new(42)
assert Literal.new!(true, []) == RDF.XSD.Boolean.new!(true) assert Literal.new!(true, []) == XSD.Boolean.new!(true)
assert Literal.new!(42, []) == RDF.XSD.Integer.new!(42) assert Literal.new!(42, []) == XSD.Integer.new!(42)
end end
end end
describe "typed construction" do describe "typed construction" do
test "boolean" do test "boolean" do
assert Literal.new(true, datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new(true) assert Literal.new(true, datatype: NS.XSD.boolean) == XSD.Boolean.new(true)
assert Literal.new(false, datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new(false) assert Literal.new(false, datatype: NS.XSD.boolean) == XSD.Boolean.new(false)
assert Literal.new("true", datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new("true") assert Literal.new("true", datatype: NS.XSD.boolean) == XSD.Boolean.new("true")
assert Literal.new("false", datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new("false") assert Literal.new("false", datatype: NS.XSD.boolean) == XSD.Boolean.new("false")
end end
test "integer" do test "integer" do
assert Literal.new(42, datatype: NS.XSD.integer) == RDF.XSD.Integer.new(42) assert Literal.new(42, datatype: NS.XSD.integer) == XSD.Integer.new(42)
assert Literal.new("42", datatype: NS.XSD.integer) == RDF.XSD.Integer.new("42") assert Literal.new("42", datatype: NS.XSD.integer) == XSD.Integer.new("42")
end end
test "double" do test "double" do
assert Literal.new(3.14, datatype: NS.XSD.double) == RDF.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) == RDF.XSD.Double.new("3.14") assert Literal.new("3.14", datatype: NS.XSD.double) == XSD.Double.new("3.14")
end end
test "decimal" do test "decimal" do
assert Literal.new(3.14, datatype: NS.XSD.decimal) == RDF.XSD.Decimal.new(3.14) assert Literal.new(3.14, datatype: NS.XSD.decimal) == XSD.Decimal.new(3.14)
assert Literal.new("3.14", datatype: NS.XSD.decimal) == RDF.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(Decimal.from_float(3.14), datatype: NS.XSD.decimal) ==
RDF.XSD.Decimal.new(Decimal.from_float(3.14)) XSD.Decimal.new(Decimal.from_float(3.14))
end end
test "unsignedInt" do test "unsignedInt" do
assert Literal.new(42, datatype: NS.XSD.unsignedInt) == RDF.XSD.UnsignedInt.new(42) assert Literal.new(42, datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new(42)
assert Literal.new("42", datatype: NS.XSD.unsignedInt) == RDF.XSD.UnsignedInt.new("42") assert Literal.new("42", datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new("42")
end end
test "string" do test "string" do
assert Literal.new("foo", datatype: NS.XSD.string) == RDF.XSD.String.new("foo") assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo")
end end
test "unmapped/unknown datatype" do test "unmapped/unknown datatype" do
@ -113,6 +113,66 @@ defmodule RDF.LiteralTest do
end end
end end
describe "coerce/1" do
test "with boolean" do
assert Literal.coerce(true) == XSD.true()
assert Literal.coerce(false) == XSD.false()
end
test "with string" do
assert Literal.coerce("foo") == XSD.string("foo")
end
test "with integer" do
assert Literal.coerce(42) == XSD.integer(42)
end
test "with float" do
assert Literal.coerce(3.14) == XSD.double(3.14)
end
test "with decimal" do
assert D.from_float(3.14) |> Literal.coerce() == XSD.decimal(3.14)
end
test "with datetime" do
assert DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> Literal.coerce() ==
DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> XSD.datetime()
end
test "with naive datetime" do
assert ~N"2002-04-02T12:00:00" |> Literal.coerce() ==
~N"2002-04-02T12:00:00" |> XSD.datetime()
end
test "with date" do
assert ~D"2002-04-02" |> Literal.coerce() ==
~D"2002-04-02" |> XSD.date()
end
test "with time" do
assert ~T"12:00:00" |> Literal.coerce() ==
~T"12:00:00" |> XSD.time()
end
test "with URI" do
assert URI.parse("http://example.com") |> Literal.coerce() ==
XSD.any_uri("http://example.com")
end
test "with RDF.Literals" do
assert XSD.integer(42) |> Literal.coerce() == XSD.integer(42)
end
test "with RDF datatype Literals" do
assert %XSD.Integer{value: 42} |> Literal.coerce() == XSD.integer(42)
end
test "with inconvertible values" do
assert self() |> Literal.coerce() == nil
end
end
describe "has_datatype?" do 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 @tag literal: literal
@ -241,16 +301,16 @@ defmodule RDF.LiteralTest do
describe "canonical/1" do describe "canonical/1" do
test "with XSD.Datatype literal" do test "with XSD.Datatype literal" do
[ [
RDF.XSD.String.new("foo"), XSD.String.new("foo"),
RDF.XSD.Byte.new(42), XSD.Byte.new(42),
] ]
|> Enum.each(fn |> Enum.each(fn
canonical_literal -> canonical_literal ->
assert Literal.canonical(canonical_literal) == canonical_literal assert Literal.canonical(canonical_literal) == canonical_literal
end) end)
assert RDF.XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42) assert XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42)
assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> RDF.XSD.Double.canonical() assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> XSD.Double.canonical()
end end
test "with RDF.LangString literal" do test "with RDF.LangString literal" do
@ -284,7 +344,7 @@ defmodule RDF.LiteralTest do
test "with XSD.Datatype literal" do test "with XSD.Datatype literal" do
assert Literal.new("foo") |> Literal.valid?() == true assert Literal.new("foo") |> Literal.valid?() == true
assert Literal.new(42) |> Literal.valid?() == true assert Literal.new(42) |> Literal.valid?() == true
assert RDF.XSD.Integer.new("foo") |> Literal.valid?() == false assert XSD.Integer.new("foo") |> Literal.valid?() == false
end end
test "with RDF.LangString literal" do test "with RDF.LangString literal" do
@ -300,7 +360,7 @@ defmodule RDF.LiteralTest do
describe "equal_value?/2" do describe "equal_value?/2" do
test "with XSD.Datatype literal" do test "with XSD.Datatype literal" do
assert Literal.equal_value?(Literal.new("foo"), Literal.new("foo")) == true assert Literal.equal_value?(Literal.new("foo"), Literal.new("foo")) == true
assert Literal.equal_value?(Literal.new(42), RDF.XSD.Byte.new(42)) == true assert Literal.equal_value?(Literal.new(42), XSD.Byte.new(42)) == true
assert Literal.equal_value?(Literal.new("foo"), "foo") == true assert Literal.equal_value?(Literal.new("foo"), "foo") == true
assert Literal.equal_value?(Literal.new(42), 42) == true assert Literal.equal_value?(Literal.new(42), 42) == true
assert Literal.equal_value?(Literal.new(42), 42.0) == true assert Literal.equal_value?(Literal.new(42), 42.0) == true
@ -325,7 +385,7 @@ defmodule RDF.LiteralTest do
describe "compare/2" do describe "compare/2" do
test "with XSD.Datatype literal" do test "with XSD.Datatype literal" do
assert Literal.compare(Literal.new("foo"), Literal.new("bar")) == :gt assert Literal.compare(Literal.new("foo"), Literal.new("bar")) == :gt
assert Literal.compare(Literal.new(42), RDF.XSD.Byte.new(43)) == :lt assert Literal.compare(Literal.new(42), XSD.Byte.new(43)) == :lt
end end
test "with RDF.LangString literal" do test "with RDF.LangString literal" do
@ -339,7 +399,7 @@ defmodule RDF.LiteralTest do
end end
end end
@poem RDF.XSD.String.new """ @poem XSD.String.new """
<poem author="Wilhelm Busch"> <poem author="Wilhelm Busch">
Kaum hat dies der Hahn gesehen, Kaum hat dies der Hahn gesehen,
Fängt er auch schon an zu krähen: Fängt er auch schon an zu krähen:
@ -365,8 +425,8 @@ defmodule RDF.LiteralTest do
{~L"abracadabra"en, ~L"bra", true}, {~L"abracadabra"en, ~L"bra", true},
{"abracadabra", "bra", true}, {"abracadabra", "bra", true},
{RDF.XSD.Integer.new("42"), ~L"4", true}, {XSD.Integer.new("42"), ~L"4", true},
{RDF.XSD.Integer.new("42"), ~L"en", false}, {XSD.Integer.new("42"), ~L"en", false},
] ]
|> Enum.each(fn {literal, pattern, expected_result} -> |> Enum.each(fn {literal, pattern, expected_result} ->
result = Literal.matches?(literal, pattern) result = Literal.matches?(literal, pattern)
@ -404,16 +464,16 @@ defmodule RDF.LiteralTest do
describe "update/2" do describe "update/2" do
test "it updates value and lexical form" do test "it updates value and lexical form" do
assert RDF.string("foo") assert XSD.string("foo")
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) == |> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
RDF.string("foobar") XSD.string("foobar")
assert RDF.integer(1) |> Literal.update(fn i when is_integer(i) -> i + 1 end) == assert XSD.integer(1) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
RDF.integer(2) XSD.integer(2)
assert RDF.byte(42) |> Literal.update(fn i when is_integer(i) -> i + 1 end) == assert XSD.byte(42) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
RDF.byte(43) XSD.byte(43)
assert RDF.integer(1) assert XSD.integer(1)
|> Literal.update(fn i when is_integer(i) -> "0" <> to_string(i) end) == |> Literal.update(fn i when is_integer(i) -> "0" <> to_string(i) end) ==
RDF.integer("01") XSD.integer("01")
end end
test "it does not change the datatype of generic literals" do test "it does not change the datatype of generic literals" do
@ -429,9 +489,9 @@ defmodule RDF.LiteralTest do
end end
test "with as: :lexical opt it passes the lexical form" do test "with as: :lexical opt it passes the lexical form" do
assert RDF.integer(1) 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) ==
RDF.integer("01") XSD.integer("01")
end end
end end
@ -439,7 +499,7 @@ defmodule RDF.LiteralTest do
test "with XSD.Datatype literal" do test "with XSD.Datatype literal" do
assert Literal.new("foo") |> to_string() == "foo" assert Literal.new("foo") |> to_string() == "foo"
assert Literal.new(42) |> to_string() == "42" assert Literal.new(42) |> to_string() == "42"
assert RDF.XSD.Integer.new("foo") |> to_string() == "foo" assert XSD.Integer.new("foo") |> to_string() == "foo"
end end
test "with RDF.LangString literal" do test "with RDF.LangString literal" do

View file

@ -7,9 +7,9 @@ defmodule RDF.QuadTest do
describe "values/1" do describe "values/1" do
test "with a valid RDF.Quad" do test "with a valid RDF.Quad" do
assert Quad.values({~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), ~I<http://example.com/Graph>}) assert Quad.values({~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42), ~I<http://example.com/Graph>})
== {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"} == {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
assert Quad.values({~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), nil}) assert Quad.values({~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42), nil})
== {"http://example.com/S", "http://example.com/p", 42, nil} == {"http://example.com/S", "http://example.com/p", 42, nil}
end end
@ -20,7 +20,7 @@ defmodule RDF.QuadTest do
end end
test "values/2" do test "values/2" do
assert {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42), ~I<http://example.com/Graph>} assert {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42), ~I<http://example.com/Graph>}
|> Quad.values(fn |> Quad.values(fn
{:subject, subject} -> subject |> to_string() |> String.last() |> String.to_atom() {:subject, subject} -> subject |> to_string() |> String.last() |> String.to_atom()
{:predicate, _} -> :p {:predicate, _} -> :p

View file

@ -4,16 +4,7 @@ defmodule RDFTest do
doctest RDF doctest RDF
test "Datatype constructor alias functions" do test "Datatype constructor alias functions" do
RDF.Literal.Datatype.Registry.datatypes() -- [RDF.LangString] assert RDF.langString("foo", language: "en") == RDF.Literal.new("foo", language: "en")
|> Enum.each(fn datatype ->
assert apply(RDF, String.to_atom(datatype.name), [1]) == datatype.new(1)
assert apply(RDF, String.to_atom(Macro.underscore(datatype.name)), [1]) == datatype.new(1)
end)
end
test "true and false aliases" do
assert RDF.true == RDF.XSD.Boolean.new(true)
assert RDF.false == RDF.XSD.Boolean.new(false)
end end
describe "default_prefixes/0" do describe "default_prefixes/0" do

View file

@ -7,7 +7,7 @@ defmodule RDF.StatementTest do
@iri ~I<http://example.com/Foo> @iri ~I<http://example.com/Foo>
@bnode ~B<foo> @bnode ~B<foo>
@valid_literal ~L"foo" @valid_literal ~L"foo"
@invalid_literal RDF.integer("foo") @invalid_literal XSD.integer("foo")
@valid_triples [ @valid_triples [
{@iri, @iri, @iri}, {@iri, @iri, @iri},

View file

@ -19,8 +19,8 @@ defmodule RDF.TermTest do
end end
test "with boolean" do test "with boolean" do
assert RDF.Term.coerce(true) == RDF.true assert RDF.Term.coerce(true) == XSD.true
assert RDF.Term.coerce(false) == RDF.false assert RDF.Term.coerce(false) == XSD.false
end end
test "with string" do test "with string" do
@ -28,32 +28,32 @@ defmodule RDF.TermTest do
end end
test "with integer" do test "with integer" do
assert RDF.Term.coerce(42) == RDF.integer(42) assert RDF.Term.coerce(42) == XSD.integer(42)
end end
test "with float" do test "with float" do
assert RDF.Term.coerce(3.14) == RDF.double(3.14) assert RDF.Term.coerce(3.14) == XSD.double(3.14)
end end
test "with decimal" do test "with decimal" do
assert D.from_float(3.14) |> RDF.Term.coerce() == RDF.decimal(3.14) assert D.from_float(3.14) |> RDF.Term.coerce() == XSD.decimal(3.14)
end end
test "with datetime" do test "with datetime" do
assert DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> RDF.Term.coerce() == assert DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> RDF.Term.coerce() ==
DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> RDF.datetime() DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> XSD.datetime()
assert ~N"2002-04-02T12:00:00" |> RDF.Term.coerce() == assert ~N"2002-04-02T12:00:00" |> RDF.Term.coerce() ==
~N"2002-04-02T12:00:00" |> RDF.datetime() ~N"2002-04-02T12:00:00" |> XSD.datetime()
end end
test "with date" do test "with date" do
assert ~D"2002-04-02" |> RDF.Term.coerce() == assert ~D"2002-04-02" |> RDF.Term.coerce() ==
~D"2002-04-02" |> RDF.date() ~D"2002-04-02" |> XSD.date()
end end
test "with time" do test "with time" do
assert ~T"12:00:00" |> RDF.Term.coerce() == assert ~T"12:00:00" |> RDF.Term.coerce() ==
~T"12:00:00" |> RDF.time() ~T"12:00:00" |> XSD.time()
end end
test "with reference" do test "with reference" do
@ -80,7 +80,7 @@ defmodule RDF.TermTest do
end end
test "with an invalid RDF.Literal" do test "with an invalid RDF.Literal" do
assert RDF.integer("foo") |> RDF.Term.value() == "foo" assert XSD.integer("foo") |> RDF.Term.value() == "foo"
end end
test "with boolean" do test "with boolean" do

View file

@ -7,7 +7,7 @@ defmodule RDF.TripleTest do
describe "values/1" do describe "values/1" do
test "with a valid RDF.Triple" do test "with a valid RDF.Triple" do
assert Triple.values({~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42)}) assert Triple.values({~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)})
== {"http://example.com/S", "http://example.com/p", 42} == {"http://example.com/S", "http://example.com/p", 42}
end end
@ -18,7 +18,7 @@ defmodule RDF.TripleTest do
end end
test "values/2" do test "values/2" do
assert {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.integer(42)} assert {~I<http://example.com/S>, ~I<http://example.com/p>, XSD.integer(42)}
|> Triple.values(fn |> Triple.values(fn
{:object, object} -> object |> RDF.Term.value() |> Kernel.+(1) {:object, object} -> object |> RDF.Term.value() |> Kernel.+(1)
{_, term} -> term |> to_string() |> String.last() {_, term} -> term |> to_string() |> String.last()

View file

@ -5,7 +5,7 @@ defmodule RDF.Turtle.DecoderTest do
import RDF.Sigils import RDF.Sigils
alias RDF.{Turtle, Graph, NS} alias RDF.{Turtle, Graph, NS, XSD}
use RDF.Vocabulary.Namespace use RDF.Vocabulary.Namespace
@ -240,28 +240,28 @@ defmodule RDF.Turtle.DecoderTest do
test "boolean" do test "boolean" do
assert Turtle.Decoder.decode!(""" assert Turtle.Decoder.decode!("""
<http://example.org/#Foo> <http://example.org/#bar> true . <http://example.org/#Foo> <http://example.org/#bar> true .
""") == Graph.new({EX.Foo, EX.bar, RDF.true}) """) == Graph.new({EX.Foo, EX.bar, XSD.true})
assert Turtle.Decoder.decode!(""" assert Turtle.Decoder.decode!("""
<http://example.org/#Foo> <http://example.org/#bar> false . <http://example.org/#Foo> <http://example.org/#bar> false .
""") == Graph.new({EX.Foo, EX.bar, RDF.false}) """) == Graph.new({EX.Foo, EX.bar, XSD.false})
end end
test "integer" do test "integer" do
assert Turtle.Decoder.decode!(""" assert Turtle.Decoder.decode!("""
<http://example.org/#Foo> <http://example.org/#bar> 42 . <http://example.org/#Foo> <http://example.org/#bar> 42 .
""") == Graph.new({EX.Foo, EX.bar, RDF.integer(42)}) """) == Graph.new({EX.Foo, EX.bar, XSD.integer(42)})
end end
test "decimal" do test "decimal" do
assert Turtle.Decoder.decode!(""" assert Turtle.Decoder.decode!("""
<http://example.org/#Foo> <http://example.org/#bar> 3.14 . <http://example.org/#Foo> <http://example.org/#bar> 3.14 .
""") == Graph.new({EX.Foo, EX.bar, RDF.decimal("3.14")}) """) == Graph.new({EX.Foo, EX.bar, XSD.decimal("3.14")})
end end
test "double" do test "double" do
assert Turtle.Decoder.decode!(""" assert Turtle.Decoder.decode!("""
<http://example.org/#Foo> <http://example.org/#bar> 1.2e3 . <http://example.org/#Foo> <http://example.org/#bar> 1.2e3 .
""") == Graph.new({EX.Foo, EX.bar, RDF.double("1.2e3")}) """) == Graph.new({EX.Foo, EX.bar, XSD.double("1.2e3")})
end end
end end

View file

@ -6,7 +6,8 @@ defmodule RDF.Turtle.EncoderTest do
doctest Turtle.Encoder doctest Turtle.Encoder
alias RDF.Graph alias RDF.Graph
alias RDF.NS.{XSD, RDFS, OWL} alias RDF.NS
alias RDF.NS.{RDFS, OWL}
import RDF.Sigils import RDF.Sigils
@ -47,7 +48,7 @@ defmodule RDF.Turtle.EncoderTest do
{EX.S2, EX.p3, EX.O4}, {EX.S2, EX.p3, EX.O4},
]), prefixes: %{ ]), prefixes: %{
ex: EX.__base_iri__, ex: EX.__base_iri__,
xsd: XSD.__base_iri__ xsd: NS.XSD.__base_iri__
}) == }) ==
""" """
@prefix ex: <#{to_string(EX.__base_iri__)}> . @prefix ex: <#{to_string(EX.__base_iri__)}> .
@ -66,11 +67,11 @@ defmodule RDF.Turtle.EncoderTest do
assert Turtle.Encoder.encode!(Graph.new([ assert Turtle.Encoder.encode!(Graph.new([
{EX.S1, EX.p1, EX.O1}, {EX.S1, EX.p1, EX.O1},
{EX.S1, EX.p1, EX.O2}, {EX.S1, EX.p1, EX.O2},
{EX.S1, EX.p2, XSD.integer}, {EX.S1, EX.p2, NS.XSD.integer},
{EX.S2, EX.p3, EX.O4}, {EX.S2, EX.p3, EX.O4},
], prefixes: %{ ], prefixes: %{
"": EX.__base_iri__, "": EX.__base_iri__,
xsd: XSD.__base_iri__ xsd: NS.XSD.__base_iri__
})) == })) ==
""" """
@prefix : <#{to_string(EX.__base_iri__)}> . @prefix : <#{to_string(EX.__base_iri__)}> .
@ -106,11 +107,11 @@ defmodule RDF.Turtle.EncoderTest do
end end
test "when no prefixes are given and no prefixes are in the given graph the default_prefixes are used" do test "when no prefixes are given and no prefixes are in the given graph the default_prefixes are used" do
assert Turtle.Encoder.encode!(Graph.new({EX.S, EX.p, XSD.string})) == assert Turtle.Encoder.encode!(Graph.new({EX.S, EX.p, NS.XSD.string})) ==
""" """
@prefix rdf: <#{to_string(RDF.__base_iri__)}> . @prefix rdf: <#{to_string(RDF.__base_iri__)}> .
@prefix rdfs: <#{to_string(RDFS.__base_iri__)}> . @prefix rdfs: <#{to_string(RDFS.__base_iri__)}> .
@prefix xsd: <#{to_string(XSD.__base_iri__)}> . @prefix xsd: <#{to_string(NS.XSD.__base_iri__)}> .
<http://example.org/#S> <http://example.org/#S>
<http://example.org/#p> xsd:string . <http://example.org/#p> xsd:string .
@ -546,7 +547,7 @@ defmodule RDF.Turtle.EncoderTest do
~r[@prefix xsd: <http://www.w3.org/2001/XMLSchema#> \.], ~r[@prefix xsd: <http://www.w3.org/2001/XMLSchema#> \.],
~r["http://foo/"\^\^xsd:anyURI \.] ~r["http://foo/"\^\^xsd:anyURI \.]
], ],
prefixes: %{xsd: XSD.__base_iri__} prefixes: %{xsd: NS.XSD.__base_iri__}
) )
end end
@ -560,7 +561,7 @@ defmodule RDF.Turtle.EncoderTest do
{"0", "false ."}, {"0", "false ."},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.boolean(value)}) Graph.new({EX.S, EX.p, RDF.XSD.boolean(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -573,7 +574,7 @@ defmodule RDF.Turtle.EncoderTest do
{"FaLsE", ~s{"FaLsE"^^<http://www.w3.org/2001/XMLSchema#boolean>}}, {"FaLsE", ~s{"FaLsE"^^<http://www.w3.org/2001/XMLSchema#boolean>}},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.boolean(value)}) Graph.new({EX.S, EX.p, RDF.XSD.boolean(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -591,7 +592,7 @@ defmodule RDF.Turtle.EncoderTest do
{"0010", "10 ."}, {"0010", "10 ."},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.integer(value)}) Graph.new({EX.S, EX.p, RDF.XSD.integer(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -602,7 +603,7 @@ defmodule RDF.Turtle.EncoderTest do
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#integer>}}, {"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#integer>}},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.integer(value)}) Graph.new({EX.S, EX.p, RDF.XSD.integer(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -620,7 +621,7 @@ defmodule RDF.Turtle.EncoderTest do
{"010.020", "10.02 ."}, {"010.020", "10.02 ."},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.decimal(value)}) Graph.new({EX.S, EX.p, RDF.XSD.decimal(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -631,7 +632,7 @@ defmodule RDF.Turtle.EncoderTest do
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#decimal>}}, {"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#decimal>}},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.decimal(value)}) Graph.new({EX.S, EX.p, RDF.XSD.decimal(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -650,7 +651,7 @@ defmodule RDF.Turtle.EncoderTest do
{"-1", "-1.0E0 ."}, {"-1", "-1.0E0 ."},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.double(value)}) Graph.new({EX.S, EX.p, RDF.XSD.double(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end
@ -661,7 +662,7 @@ defmodule RDF.Turtle.EncoderTest do
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#double>}}, {"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#double>}},
] ]
|> Enum.each(fn {value, output} -> |> Enum.each(fn {value, output} ->
Graph.new({EX.S, EX.p, RDF.double(value)}) Graph.new({EX.S, EX.p, RDF.XSD.double(value)})
|> assert_serialization(matches: [output]) |> assert_serialization(matches: [output])
end) end)
end end

View file

@ -0,0 +1,318 @@
defmodule RDF.XSD.ComparisonTest do
use ExUnit.Case
alias RDF.XSD
describe "XSD.String" do
@ordered_strings [
{"a", "b"},
{"0", "1"}
]
test "valid comparisons between string literals" do
Enum.each(@ordered_strings, fn {left, right} ->
assert_order({XSD.string(left), XSD.string(right)})
end)
assert_equal({XSD.string("foo"), XSD.string("foo")})
end
end
describe "XSD.Boolean" do
test "when unequal" do
assert_order({XSD.false(), XSD.true()})
end
test "when equal" do
assert_equal({XSD.false(), XSD.false()})
assert_equal({XSD.true(), XSD.true()})
end
end
describe "XSD.Numeric" do
test "when unequal" do
Enum.each(
[
{XSD.integer(0), XSD.integer(1)},
{XSD.integer("3"), XSD.integer("007")},
{XSD.double(1.1), XSD.double(2.2)},
{XSD.float(1.1), XSD.float(2.2)},
{XSD.decimal(1.1), XSD.decimal(2.2)},
{XSD.decimal(1.1), XSD.double(2.2)},
{XSD.float(1.1), XSD.decimal(2.2)},
{XSD.double(3.14), XSD.integer(42)},
{XSD.float(3.14), XSD.integer(42)},
{XSD.decimal(3.14), XSD.integer(42)},
{XSD.non_negative_integer(0), XSD.integer(1)},
{XSD.integer(0), XSD.positive_integer(1)},
{XSD.non_negative_integer(0), XSD.positive_integer(1)},
{XSD.positive_integer(1), XSD.non_negative_integer(2)}
],
&assert_order/1
)
end
test "when equal" do
Enum.each(
[
{XSD.integer(42), XSD.integer(42)},
{XSD.integer("42"), XSD.integer("042")},
{XSD.integer("42"), XSD.double("42")},
{XSD.integer(42), XSD.double(42.0)},
{XSD.integer(42), XSD.float(42.0)},
{XSD.integer("42"), XSD.decimal("42")},
{XSD.integer(42), XSD.decimal(42.0)},
{XSD.double(3.14), XSD.decimal(3.14)},
{XSD.float(3.14), XSD.decimal(3.14)},
{XSD.double("+0"), XSD.double("-0")},
{XSD.decimal("+0"), XSD.decimal("-0")},
{XSD.non_negative_integer(0), XSD.integer(0)},
{XSD.integer(1), XSD.positive_integer(1)},
{XSD.non_negative_integer(1), XSD.positive_integer(1)},
{XSD.positive_integer(1), XSD.non_negative_integer(1)}
],
&assert_equal/1
)
end
end
describe "XSD.DateTime" do
test "when unequal" do
assert_order({XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T17:00:00")})
assert_order(
{XSD.datetime("2002-04-02T12:00:00+01:00"), XSD.datetime("2002-04-02T12:00:00+00:00")}
)
assert_order({XSD.datetime("2000-01-15T12:00:00"), XSD.datetime("2000-01-16T12:00:00Z")})
end
test "when unequal due to missing time zone" do
assert_order({XSD.datetime("2000-01-15T00:00:00"), XSD.datetime("2000-02-15T00:00:00")})
end
test "when equal" do
assert_equal(
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T12:00:00-01:00")}
)
assert_equal({XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")})
assert_equal(
{XSD.datetime("2002-04-02T12:00:00-01:00"), XSD.datetime("2002-04-02T17:00:00+04:00")}
)
assert_equal(
{XSD.datetime("2002-04-02T23:00:00-04:00"), XSD.datetime("2002-04-03T02:00:00-01:00")}
)
assert_equal({XSD.datetime("1999-12-31T24:00:00"), XSD.datetime("2000-01-01T00:00:00")})
# TODO: Assume that the dynamic context provides an implicit timezone value of -05:00
# assert_equal {XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T23:00:00+06:00")}
end
test "when indeterminate" do
assert_indeterminate(
{XSD.datetime("2000-01-01T12:00:00"), XSD.datetime("1999-12-31T23:00:00Z")}
)
assert_indeterminate(
{XSD.datetime("2000-01-16T12:00:00"), XSD.datetime("2000-01-16T12:00:00Z")}
)
assert_indeterminate(
{XSD.datetime("2000-01-16T00:00:00"), XSD.datetime("2000-01-16T12:00:00Z")}
)
end
end
describe "XSD.Date" do
test "when unequal" do
assert_order({XSD.date("2002-04-02"), XSD.date("2002-04-03")})
assert_order({XSD.date("2002-04-02+01:00"), XSD.date("2002-04-03+00:00")})
assert_order({XSD.date("2002-04-02"), XSD.date("2002-04-03Z")})
end
test "when equal" do
assert_equal({XSD.date("2002-04-02-01:00"), XSD.date("2002-04-02-01:00")})
assert_equal({XSD.date("2002-04-02"), XSD.date("2002-04-02")})
assert_equal({XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02+00:00")})
assert_equal({XSD.date("2002-04-02Z"), XSD.date("2002-04-02+00:00")})
assert_equal({XSD.date("2002-04-02Z"), XSD.date("2002-04-02-00:00")})
end
test "when indeterminate" do
assert_indeterminate({XSD.date("2002-04-02Z"), XSD.date("2002-04-02")})
assert_indeterminate({XSD.date("2002-04-02+00:00"), XSD.date("2002-04-02")})
assert_indeterminate({XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02")})
end
end
# It seems quite strange that open-world test date-2 from the SPARQL 1.0 test suite
# allows for equality comparisons between dates and datetimes, but disallows
# ordering comparisons in the date-3 test.
#
# describe "comparisons XSD.DateTime between XSD.Date and XSD.DateTime" do
# test "when unequal" do
# # without timezone
# assert_order({XSD.datetime("2000-01-14T00:00:00"), XSD.date("2000-02-15")})
# assert_order({XSD.date("2000-01-15"), XSD.datetime("2000-01-15T00:00:01")})
# # with timezone
# assert_order({XSD.datetime("2000-01-14T00:00:00"), XSD.date("2000-02-15")})
# assert_order({XSD.datetime("2000-01-14T00:00:00"), XSD.date("2000-02-15Z")})
# assert_order({XSD.datetime("2000-01-14T00:00:00"), XSD.date("2000-02-15+01:00")})
# assert_order({XSD.datetime("2000-01-14T00:00:00Z"), XSD.date("2000-02-15")})
# assert_order({XSD.datetime("2000-01-14T00:00:00Z"), XSD.date("2000-02-15Z")})
# assert_order({XSD.datetime("2000-01-14T00:00:00Z"), XSD.date("2000-02-15+01:00")})
# end
#
# test "when equal" do
# assert_equal({XSD.datetime("2000-01-15T00:00:00"), XSD.date("2000-01-15")})
# assert_equal({XSD.datetime("2000-01-15T00:00:00Z"), XSD.date("2000-01-15Z")})
# assert_equal({XSD.datetime("2000-01-15T00:00:00Z"), XSD.date("2000-01-15+00:00")})
# assert_equal({XSD.datetime("2000-01-15T00:00:00Z"), XSD.date("2000-01-15-00:00")})
# end
#
# test "when indeterminate" do
# assert_indeterminate({XSD.datetime("2000-01-15T00:00:00"), XSD.date("2000-01-15Z")})
# assert_indeterminate({XSD.datetime("2000-01-15T00:00:00Z"), XSD.date("2000-01-15")})
# end
# end
describe "XSD.Time" do
test "when unequal" do
assert_order({XSD.time("12:00:00+01:00"), XSD.time("13:00:00+01:00")})
assert_order({XSD.time("12:00:00"), XSD.time("13:00:00")})
end
test "when equal" do
assert_equal({XSD.time("12:00:00+01:00"), XSD.time("12:00:00+01:00")})
assert_equal({XSD.time("12:00:00"), XSD.time("12:00:00")})
end
test "when indeterminate" do
assert_indeterminate({XSD.date("2002-04-02Z"), XSD.date("2002-04-02")})
assert_indeterminate({XSD.date("2002-04-02+00:00"), XSD.date("2002-04-02")})
assert_indeterminate({XSD.date("2002-04-02-00:00"), XSD.date("2002-04-02")})
end
end
describe "incomparable" do
test "when comparing incomparable types" do
Enum.each(
[
{XSD.string("true"), XSD.true()},
{XSD.string("42"), XSD.integer(42)},
{XSD.string("3.14"), XSD.decimal(3.14)},
{XSD.string("2002-04-02T12:00:00"), XSD.datetime("2002-04-02T12:00:00")},
{XSD.string("2002-04-02"), XSD.date("2002-04-02")},
{XSD.string("12:00:00"), XSD.time("12:00:00")},
{XSD.true(), XSD.integer(42)},
{XSD.true(), XSD.decimal(3.14)},
{XSD.datetime("2002-04-02T12:00:00"), XSD.true()},
{XSD.datetime("2002-04-02T12:00:00"), XSD.integer(42)},
{XSD.datetime("2002-04-02T12:00:00"), XSD.decimal(3.14)},
{XSD.date("2002-04-02"), XSD.true()},
{XSD.date("2002-04-02"), XSD.integer(42)},
{XSD.date("2002-04-02"), XSD.decimal(3.14)},
{XSD.time("12:00:00"), XSD.true()},
{XSD.time("12:00:00"), XSD.integer(42)},
{XSD.time("12:00:00"), XSD.decimal(3.14)}
],
&assert_incomparable/1
)
end
test "when comparing invalid literals" do
Enum.each(
[
{XSD.true(), XSD.boolean(42)},
{XSD.datetime("2002-04-02T12:00:00"), XSD.datetime("2002.04.02 12:00")},
{XSD.date("2002-04-02"), XSD.date("2002.04.02")},
{XSD.time("12:00:00"), XSD.time("12-00-00")}
],
&assert_incomparable/1
)
end
end
defp assert_order({left, right}) do
assert_compare_result({left, right}, :lt)
assert_compare_result({right, left}, :gt)
assert_less_than({left, right}, true)
assert_less_than({right, left}, false)
assert_greater_than({left, right}, false)
assert_greater_than({right, left}, true)
end
defp assert_equal({left, right}) do
assert_compare_result({left, right}, :eq)
assert_compare_result({right, left}, :eq)
assert_less_than({left, right}, false)
assert_less_than({right, left}, false)
assert_greater_than({left, right}, false)
assert_greater_than({right, left}, false)
end
defp assert_incomparable({left, right}) do
assert_compare_result({left, right}, nil)
assert_compare_result({right, left}, nil)
assert_greater_than({left, right}, false)
assert_greater_than({right, left}, false)
assert_less_than({left, right}, false)
assert_less_than({right, left}, false)
end
defp assert_indeterminate({left, right}) do
assert_compare_result({left, right}, :indeterminate)
assert_compare_result({right, left}, :indeterminate)
assert_greater_than({left, right}, false)
assert_greater_than({right, left}, false)
assert_less_than({left, right}, false)
assert_less_than({right, left}, false)
end
defp assert_compare_result({left, right}, expected) do
result = RDF.Literal.compare(left, right)
assert result == expected, """
expected RDF.Literal.compare(
#{inspect(left)},
#{inspect(right)})
to be: #{inspect(expected)}
but got: #{inspect(result)}
"""
end
defp assert_less_than({left, right}, expected) do
result = RDF.Literal.less_than?(left, right)
assert result == expected, """
expected RDF.Literal.less_than?(
#{inspect(left)},
#{inspect(right)})
to be: #{inspect(expected)}
but got: #{inspect(result)}
"""
end
defp assert_greater_than({left, right}, expected) do
result = RDF.Literal.greater_than?(left, right)
assert result == expected, """
expected RDF.Literal.greater_than?(
#{inspect(left)},
#{inspect(right)})
to be: #{inspect(expected)}
but got: #{inspect(result)}
"""
end
end

View file

@ -0,0 +1,18 @@
defmodule RDF.XSD.DatatypeTest do
use ExUnit.Case
alias RDF.XSD
test "base_primitive/1" do
assert XSD.integer(42) |> XSD.Datatype.base_primitive() == XSD.Integer
assert XSD.non_negative_integer(42) |> XSD.Datatype.base_primitive() == XSD.Integer
assert XSD.positive_integer(42) |> XSD.Datatype.base_primitive() == XSD.Integer
end
test "derived_from?/2" do
assert XSD.integer(42) |> XSD.Datatype.derived_from?(XSD.Integer)
assert XSD.non_negative_integer(42) |> XSD.Datatype.derived_from?(XSD.Integer)
assert XSD.positive_integer(42) |> XSD.Datatype.derived_from?(XSD.Integer)
assert XSD.positive_integer(42) |> XSD.Datatype.derived_from?(XSD.NonNegativeInteger)
end
end

View file

@ -0,0 +1,14 @@
defmodule RDF.XSD.AnyURITest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.AnyURI,
name: "anyURI",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
"http://example.com/foo" =>
{URI.parse("http://example.com/foo"), nil, "http://example.com/foo"},
URI.parse("http://example.com/foo") =>
{URI.parse("http://example.com/foo"), nil, "http://example.com/foo"}
},
invalid: [42, 3.14, true, false]
end

View file

@ -0,0 +1,238 @@
defmodule RDF.XSD.BooleanTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Boolean,
name: "boolean",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
true => {true, nil, "true"},
false => {false, nil, "false"},
0 => {false, nil, "false"},
1 => {true, nil, "true"},
"true" => {true, nil, "true"},
"false" => {false, nil, "false"},
"0" => {false, "0", "false"},
"1" => {true, "1", "true"}
},
invalid: ["foo", "10", 42, 3.14, "tRuE", "FaLsE", "true false", "true foo"]
describe "cast/1" do
test "casting a boolean returns the input as it is" do
assert XSD.true() |> XSD.Boolean.cast() == XSD.true()
assert XSD.false() |> XSD.Boolean.cast() == XSD.false()
end
test "casting a string with a value from the lexical value space of xsd:boolean" do
assert XSD.string("true") |> XSD.Boolean.cast() == XSD.true()
assert XSD.string("1") |> XSD.Boolean.cast() == XSD.true()
assert XSD.string("false") |> XSD.Boolean.cast() == XSD.false()
assert XSD.string("0") |> XSD.Boolean.cast() == XSD.false()
end
test "casting a string with a value not in the lexical value space of xsd:boolean" do
assert XSD.string("foo") |> XSD.Boolean.cast() == nil
end
test "casting an integer" do
assert XSD.integer(0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.integer(1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.integer(42) |> XSD.Boolean.cast() == XSD.true()
end
test "casting a decimal" do
assert XSD.decimal(0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal(0.0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal("+0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal("-0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal("+0.0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal("-0.0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal(0.0e0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.decimal(1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.decimal(0.1) |> XSD.Boolean.cast() == XSD.true()
end
test "casting a double" do
assert XSD.double(0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.double(0.0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("+0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("-0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("+0.0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("-0.0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("0.0E0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double("NAN") |> XSD.Boolean.cast() == XSD.false()
assert XSD.double(1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.double(0.1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.double("-INF") |> XSD.Boolean.cast() == XSD.true()
end
test "casting a float" do
assert XSD.float(0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.float(0.0) |> XSD.Boolean.cast() == XSD.false()
assert XSD.float("+0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.float("-0.0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.float("0.0E0") |> XSD.Boolean.cast() == XSD.false()
assert XSD.float("NAN") |> XSD.Boolean.cast() == XSD.false()
assert XSD.float(1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.float(0.1) |> XSD.Boolean.cast() == XSD.true()
assert XSD.float("-INF") |> XSD.Boolean.cast() == XSD.true()
end
test "with invalid literals" do
assert XSD.boolean("42") |> XSD.Boolean.cast() == nil
assert XSD.integer(3.14) |> XSD.Boolean.cast() == nil
assert XSD.decimal("NAN") |> XSD.Boolean.cast() == nil
assert XSD.double(true) |> XSD.Boolean.cast() == nil
end
test "with values of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.Boolean.cast() == nil
end
test "with coercible value" do
assert XSD.Boolean.cast(0) == XSD.false()
assert XSD.Boolean.cast(0.0) == XSD.false()
assert XSD.Boolean.cast(42) == XSD.true()
assert XSD.Boolean.cast(3.14) == XSD.true()
assert XSD.Boolean.cast("true") == XSD.true()
assert XSD.Boolean.cast("false") == XSD.false()
end
test "with non-coercible value" do
assert XSD.Boolean.cast(:foo) == nil
assert XSD.Boolean.cast(make_ref()) == nil
end
end
describe "ebv/1" do
import XSD.Boolean, only: [ebv: 1]
test "if the argument is a xsd:boolean typed literal and it has a valid lexical form, the EBV is the value of that argument" do
[
XSD.true(),
XSD.false(),
XSD.boolean(1),
XSD.boolean("0")
]
|> Enum.each(fn literal ->
assert ebv(literal) == literal
end)
end
test "any literal whose type is xsd:boolean or numeric is false if the lexical form is not valid for that datatype" do
[
XSD.boolean(42),
XSD.integer(3.14),
XSD.double("Foo")
]
|> Enum.each(fn literal ->
assert ebv(literal) == XSD.false()
end)
end
test "if the argument is a xsd:string, the EBV is false if the operand value has zero length" do
assert ebv(XSD.string("")) == XSD.false()
end
test "if the argument is a xsd:string, the EBV is true if the operand value has length greater zero" do
assert ebv(XSD.string("bar")) == XSD.true()
end
test "if the argument is a numeric literal with a valid lexical form having the value NaN or being numerically equal to zero, the EBV is false" do
[
XSD.integer(0),
XSD.integer("0"),
XSD.double("0"),
XSD.double("0.0"),
XSD.double(:nan),
XSD.double("NaN")
]
|> Enum.each(fn literal ->
assert ebv(literal) == XSD.false()
end)
end
test "if the argument is a numeric type with a valid lexical form being numerically unequal to zero, the EBV is true" do
assert ebv(XSD.integer(42)) == XSD.true()
assert ebv(XSD.integer("42")) == XSD.true()
assert ebv(XSD.double("3.14")) == XSD.true()
end
test "Elixirs booleans are treated as XSD.Booleans" do
assert ebv(true) == XSD.true()
assert ebv(false) == XSD.false()
end
test "Elixirs strings are treated as XSD.strings" do
assert ebv("") == XSD.false()
assert ebv("foo") == XSD.true()
assert ebv("0") == XSD.true()
end
test "Elixirs numbers are treated as XSD.Numerics" do
assert ebv(0) == XSD.false()
assert ebv(0.0) == XSD.false()
assert ebv(42) == XSD.true()
assert ebv(3.14) == XSD.true()
end
test "all other arguments, produce nil" do
[
XSD.date("2010-01-01"),
XSD.time("00:00:00"),
nil,
self(),
[true],
{true},
%{foo: :bar}
]
|> Enum.each(fn value ->
assert ebv(value) == nil
end)
end
end
test "truth-table of logical_and" do
[
{XSD.true(), XSD.true(), XSD.true()},
{XSD.true(), XSD.false(), XSD.false()},
{XSD.false(), XSD.true(), XSD.false()},
{XSD.false(), XSD.false(), XSD.false()},
{XSD.true(), nil, nil},
{nil, XSD.true(), nil},
{XSD.false(), nil, XSD.false()},
{nil, XSD.false(), XSD.false()},
{nil, nil, nil}
]
|> Enum.each(fn {left, right, result} ->
assert XSD.Boolean.logical_and(left, right) == result,
"expected logical_and(#{inspect(left)}, #{inspect(right)}) to be #{inspect(result)}, but got #{
inspect(XSD.Boolean.logical_and(left, right))
}"
end)
end
test "truth-table of logical_or" do
[
{XSD.true(), XSD.true(), XSD.true()},
{XSD.true(), XSD.false(), XSD.true()},
{XSD.false(), XSD.true(), XSD.true()},
{XSD.false(), XSD.false(), XSD.false()},
{XSD.true(), nil, XSD.true()},
{nil, XSD.true(), XSD.true()},
{XSD.false(), nil, nil},
{nil, XSD.false(), nil},
{nil, nil, nil}
]
|> Enum.each(fn {left, right, result} ->
assert XSD.Boolean.logical_or(left, right) == result,
"expected logical_or(#{inspect(left)}, #{inspect(right)}) to be #{inspect(result)}, but got #{
inspect(XSD.Boolean.logical_and(left, right))
}"
end)
end
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.ByteTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Byte,
name: "byte",
base: RDF.XSD.Short,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: -128,
max_inclusive: 127
},
valid: RDF.XSD.TestData.valid_bytes(),
invalid: RDF.XSD.TestData.invalid_bytes()
end

View file

@ -0,0 +1,140 @@
defmodule RDF.XSD.DateTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Date,
name: "date",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
~D[2010-01-01] => {~D[2010-01-01], nil, "2010-01-01"},
"2010-01-01" => {~D[2010-01-01], nil, "2010-01-01"},
"2010-01-01Z" => {{~D[2010-01-01], "Z"}, nil, "2010-01-01Z"},
"2010-01-01+00:00" => {{~D[2010-01-01], "Z"}, "2010-01-01+00:00", "2010-01-01Z"},
"2010-01-01-00:00" => {{~D[2010-01-01], "-00:00"}, nil, "2010-01-01-00:00"},
"2010-01-01+01:00" => {{~D[2010-01-01], "+01:00"}, nil, "2010-01-01+01:00"},
"2009-12-31-01:00" => {{~D[2009-12-31], "-01:00"}, nil, "2009-12-31-01:00"},
"2014-09-01-08:00" => {{~D[2014-09-01], "-08:00"}, nil, "2014-09-01-08:00"},
# negative years
"-2010-01-01" => {~D[-2010-01-01], nil, "-2010-01-01"},
"-2010-01-01Z" => {{~D[-2010-01-01], "Z"}, nil, "-2010-01-01Z"},
"-2010-01-01+00:00" => {{~D[-2010-01-01], "Z"}, "-2010-01-01+00:00", "-2010-01-01Z"}
},
invalid: [
"foo",
"+2010-01-01Z",
"2010-01-01TFOO",
"02010-01-01",
"2010-1-1",
"0000-01-01",
"2011-07",
"2011",
true,
false,
2010,
3.14,
# this value representation is just internal and not accepted as
{~D[2010-01-01], "Z"}
]
describe "new/2" do
test "with date and tz opt" do
assert XSD.Date.new("2010-01-01", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Date{value: {~D[2010-01-01], "+01:00"}}}
assert XSD.Date.new(~D[2010-01-01], tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Date{value: {~D[2010-01-01], "+01:00"}}}
assert XSD.Date.new("2010-01-01", tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Date{
value: {~D[2010-01-01], "Z"},
uncanonical_lexical: "2010-01-01+00:00"
}}
assert XSD.Date.new(~D[2010-01-01], tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Date{
value: {~D[2010-01-01], "Z"},
uncanonical_lexical: "2010-01-01+00:00"
}}
end
test "with date string including a timezone and tz opt" do
assert XSD.Date.new("2010-01-01+00:00", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Date{value: {~D[2010-01-01], "+01:00"}}}
assert XSD.Date.new("2010-01-01+01:00", tz: "Z") ==
%RDF.Literal{literal: %XSD.Date{value: {~D[2010-01-01], "Z"}}}
assert XSD.Date.new("2010-01-01+01:00", tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Date{
value: {~D[2010-01-01], "Z"},
uncanonical_lexical: "2010-01-01+00:00"
}}
end
test "with invalid tz opt" do
assert XSD.Date.new(~D[2020-01-01], tz: "+01:00:42") ==
%RDF.Literal{literal: %XSD.Date{uncanonical_lexical: "2020-01-01+01:00:42"}}
assert XSD.Date.new("2020-01-01-01", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Date{uncanonical_lexical: "2020-01-01-01"}}
assert XSD.Date.new("2020-01-01", tz: "+01:00:42") ==
%RDF.Literal{literal: %XSD.Date{uncanonical_lexical: "2020-01-01"}}
assert XSD.Date.new("2020-01-01+00:00:", tz: "+01:00:") ==
%RDF.Literal{literal: %XSD.Date{uncanonical_lexical: "2020-01-01+00:00:"}}
end
end
describe "cast/1" do
test "casting a date returns the input as it is" do
assert XSD.date("2010-01-01") |> XSD.Date.cast() ==
XSD.date("2010-01-01")
end
test "casting a string" do
assert XSD.string("2010-01-01") |> XSD.Date.cast() ==
XSD.date("2010-01-01")
assert XSD.string("2010-01-01Z") |> XSD.Date.cast() ==
XSD.date("2010-01-01Z")
assert XSD.string("2010-01-01+01:00") |> XSD.Date.cast() ==
XSD.date("2010-01-01+01:00")
end
test "casting a datetime" do
assert XSD.datetime("2010-01-01T01:00:00") |> XSD.Date.cast() ==
XSD.date("2010-01-01")
assert XSD.datetime("2010-01-01T00:00:00Z") |> XSD.Date.cast() ==
XSD.date("2010-01-01Z")
assert XSD.datetime("2010-01-01T00:00:00+00:00") |> XSD.Date.cast() ==
XSD.date("2010-01-01+00:00")
assert XSD.datetime("2010-01-01T23:00:00+01:00") |> XSD.Date.cast() ==
XSD.date("2010-01-01+01:00")
end
test "with invalid literals" do
assert XSD.date("02010-01-00") |> XSD.Date.cast() == nil
assert XSD.datetime("02010-01-01T00:00:00") |> XSD.Date.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.false() |> XSD.Date.cast() == nil
assert XSD.integer(1) |> XSD.Date.cast() == nil
assert XSD.decimal(3.14) |> XSD.Date.cast() == nil
end
test "with coercible value" do
assert XSD.Date.cast("2010-01-01") == XSD.date("2010-01-01")
end
test "with non-coercible value" do
assert XSD.Date.cast(:foo) == nil
assert XSD.Date.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,194 @@
defmodule RDF.XSD.DateTimeTest do
import RDF.XSD.Datatype.Test.Case, only: [dt: 1]
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.DateTime,
name: "dateTime",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
dt("2010-01-01T00:00:00Z") => {dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},
~N[2010-01-01T00:00:00] => {~N[2010-01-01T00:00:00], nil, "2010-01-01T00:00:00"},
~N[2010-01-01T00:00:00.00] => {~N[2010-01-01T00:00:00.00], nil, "2010-01-01T00:00:00.00"},
~N[2010-01-01T00:00:00.1234] =>
{~N[2010-01-01T00:00:00.1234], nil, "2010-01-01T00:00:00.1234"},
dt("2010-01-01T00:00:00+00:00") =>
{dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},
dt("2010-01-01T01:00:00+01:00") =>
{dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},
dt("2009-12-31T23:00:00-01:00") =>
{dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},
dt("2009-12-31T23:00:00.00-01:00") =>
{dt("2010-01-01T00:00:00.00Z"), nil, "2010-01-01T00:00:00.00Z"},
"2010-01-01T00:00:00Z" => {dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},
"2010-01-01T00:00:00.0000Z" =>
{dt("2010-01-01T00:00:00.0000Z"), nil, "2010-01-01T00:00:00.0000Z"},
"2010-01-01T00:00:00.123456Z" =>
{dt("2010-01-01T00:00:00.123456Z"), nil, "2010-01-01T00:00:00.123456Z"},
"2010-01-01T00:00:00" => {~N[2010-01-01T00:00:00], nil, "2010-01-01T00:00:00"},
"2010-01-01T00:00:00+00:00" =>
{dt("2010-01-01T00:00:00Z"), "2010-01-01T00:00:00+00:00", "2010-01-01T00:00:00Z"},
"2010-01-01T00:00:00-00:00" =>
{dt("2010-01-01T00:00:00Z"), "2010-01-01T00:00:00-00:00", "2010-01-01T00:00:00Z"},
"2010-01-01T01:00:00+01:00" =>
{dt("2010-01-01T00:00:00Z"), "2010-01-01T01:00:00+01:00", "2010-01-01T00:00:00Z"},
"2009-12-31T23:00:00.42-01:00" =>
{dt("2010-01-01T00:00:00.42Z"), "2009-12-31T23:00:00.42-01:00", "2010-01-01T00:00:00.42Z"},
"2009-12-31T23:00:00-01:00" =>
{dt("2010-01-01T00:00:00Z"), "2009-12-31T23:00:00-01:00", "2010-01-01T00:00:00Z"},
# 24:00 is a valid XSD dateTime
"2009-12-31T24:00:00" =>
{~N[2010-01-01T00:00:00], "2009-12-31T24:00:00", "2010-01-01T00:00:00"},
"2009-12-31T24:00:00+00:00" =>
{dt("2010-01-01T00:00:00Z"), "2009-12-31T24:00:00+00:00", "2010-01-01T00:00:00Z"},
"2009-12-31T24:00:00-00:00" =>
{dt("2010-01-01T00:00:00Z"), "2009-12-31T24:00:00-00:00", "2010-01-01T00:00:00Z"},
# negative years
dt("-2010-01-01T00:00:00Z") => {dt("-2010-01-01T00:00:00Z"), nil, "-2010-01-01T00:00:00Z"},
"-2010-01-01T00:00:00+00:00" =>
{dt("-2010-01-01T00:00:00Z"), "-2010-01-01T00:00:00+00:00", "-2010-01-01T00:00:00Z"}
},
invalid: [
"foo",
"+2010-01-01T00:00:00Z",
"2010-01-01T00:00:00FOO",
"02010-01-01T00:00:00",
"2010-01-01",
"2010-1-1T00:00:00",
"0000-01-01T00:00:00",
"2010-07",
"2010_",
true,
false,
2010,
3.14,
"2010-01-01T00:00:00Z foo",
"foo 2010-01-01T00:00:00Z"
]
describe "cast/1" do
test "casting a datetime returns the input as it is" do
assert XSD.datetime("2010-01-01T12:34:56") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T12:34:56")
end
test "casting a string with a value from the lexical value space of xsd:dateTime" do
assert XSD.string("2010-01-01T12:34:56") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T12:34:56")
assert XSD.string("2010-01-01T12:34:56Z") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T12:34:56Z")
assert XSD.string("2010-01-01T12:34:56+01:00") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T12:34:56+01:00")
end
test "casting a string with a value not in the lexical value space of xsd:dateTime" do
assert XSD.string("string") |> XSD.DateTime.cast() == nil
assert XSD.string("02010-01-01T00:00:00") |> XSD.DateTime.cast() == nil
end
test "casting a date" do
assert XSD.date("2010-01-01") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T00:00:00")
assert XSD.date("2010-01-01Z") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T00:00:00Z")
assert XSD.date("2010-01-01+00:00") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T00:00:00Z")
assert XSD.date("2010-01-01+01:00") |> XSD.DateTime.cast() ==
XSD.datetime("2010-01-01T00:00:00+01:00")
end
test "with invalid literals" do
assert XSD.datetime("02010-01-01T00:00:00") |> XSD.DateTime.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.false() |> XSD.DateTime.cast() == nil
assert XSD.integer(1) |> XSD.DateTime.cast() == nil
assert XSD.decimal(3.14) |> XSD.DateTime.cast() == nil
end
test "with coercible value" do
assert XSD.DateTime.cast("2010-01-01T12:34:56") == XSD.datetime("2010-01-01T12:34:56")
end
test "with non-coercible value" do
assert XSD.DateTime.cast(:foo) == nil
assert XSD.DateTime.cast(make_ref()) == nil
end
end
test "now/0" do
assert %RDF.Literal{literal: %XSD.DateTime{}} = XSD.DateTime.now()
end
describe "tz/1" do
test "with timezone" do
[
{"2010-01-01T00:00:00-23:00", "-23:00"},
{"2010-01-01T00:00:00+23:00", "+23:00"},
{"2010-01-01T00:00:00+00:00", "+00:00"}
]
|> Enum.each(fn {dt, tz} ->
assert dt |> DateTime.new() |> DateTime.tz() == tz
assert dt |> XSD.DateTime.new() |> DateTime.tz() == tz
end)
end
test "without any specific timezone" do
[
"2010-01-01T00:00:00Z",
"2010-01-01T00:00:00.0000Z"
]
|> Enum.each(fn dt ->
assert dt |> DateTime.new() |> DateTime.tz() == "Z"
end)
end
test "without any timezone" do
[
"2010-01-01T00:00:00",
"2010-01-01T00:00:00.0000"
]
|> Enum.each(fn dt ->
assert dt |> DateTime.new() |> DateTime.tz() == ""
end)
end
test "with invalid timezone literals" do
[
DateTime.new("2010-01-01T00:0"),
"2010-01-01T00:00:00.0000"
]
|> Enum.each(fn dt ->
assert DateTime.tz(dt) == nil
end)
end
end
test "canonical_lexical_with_zone/1" do
assert XSD.dateTime(~N[2010-01-01T12:34:56]) |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T12:34:56"
assert XSD.dateTime("2010-01-01T12:34:56") |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T12:34:56"
assert XSD.dateTime("2010-01-01T00:00:00+00:00") |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T00:00:00Z"
assert XSD.dateTime("2010-01-01T00:00:00-00:00") |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T00:00:00Z"
assert XSD.dateTime("2010-01-01T01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T01:00:00+01:00"
assert XSD.dateTime("2010-01-01 01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() ==
"2010-01-01T01:00:00+01:00"
end
end

View file

@ -0,0 +1,184 @@
defmodule RDF.XSD.DecimalTest do
# TODO: Why can't we use the Decimal alias in the use options? Maybe it's the special ExUnit.CaseTemplate.using/2 macro in XSD.Datatype.Test.Case?
# alias Elixir.Decimal, as: D
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Decimal,
name: "decimal",
primitive: true,
comparable_datatypes: [RDF.XSD.Integer, RDF.XSD.Double],
valid: %{
# input => {value, lexical, canonicalized}
0 => {Elixir.Decimal.from_float(0.0), nil, "0.0"},
1 => {Elixir.Decimal.from_float(1.0), nil, "1.0"},
-1 => {Elixir.Decimal.from_float(-1.0), nil, "-1.0"},
1.0 => {Elixir.Decimal.from_float(1.0), nil, "1.0"},
-3.14 => {Elixir.Decimal.from_float(-3.14), nil, "-3.14"},
0.0e2 => {Elixir.Decimal.from_float(0.0), nil, "0.0"},
1.2e3 => {Elixir.Decimal.new("1200.0"), nil, "1200.0"},
Elixir.Decimal.from_float(1.0) => {Elixir.Decimal.from_float(1.0), nil, "1.0"},
Elixir.Decimal.new(1) => {Elixir.Decimal.from_float(1.0), nil, "1.0"},
Elixir.Decimal.from_float(1.2e3) => {Elixir.Decimal.new("1200.0"), nil, "1200.0"},
"1" => {Elixir.Decimal.from_float(1.0), "1", "1.0"},
"01" => {Elixir.Decimal.from_float(1.0), "01", "1.0"},
"0123" => {Elixir.Decimal.from_float(123.0), "0123", "123.0"},
"-1" => {Elixir.Decimal.from_float(-1.0), "-1", "-1.0"},
"1." => {Elixir.Decimal.from_float(1.0), "1.", "1.0"},
"1.0" => {Elixir.Decimal.from_float(1.0), nil, "1.0"},
"1.000000000" => {Elixir.Decimal.from_float(1.0), "1.000000000", "1.0"},
"+001.00" => {Elixir.Decimal.from_float(1.0), "+001.00", "1.0"},
"123.456" => {Elixir.Decimal.from_float(123.456), nil, "123.456"},
"0123.456" => {Elixir.Decimal.from_float(123.456), "0123.456", "123.456"},
"010.020" => {Elixir.Decimal.from_float(10.02), "010.020", "10.02"},
"2.3" => {Elixir.Decimal.from_float(2.3), nil, "2.3"},
"2.345" => {Elixir.Decimal.from_float(2.345), nil, "2.345"},
"2.234000005" => {Elixir.Decimal.from_float(2.234000005), nil, "2.234000005"},
"1.234567890123456789012345789" =>
{Elixir.Decimal.new("1.234567890123456789012345789"), nil,
"1.234567890123456789012345789"},
".3" => {Elixir.Decimal.from_float(0.3), ".3", "0.3"},
"-.3" => {Elixir.Decimal.from_float(-0.3), "-.3", "-0.3"}
},
invalid: [
"foo",
"10.1e1",
"1.0E0",
"12.xyz",
"3,5",
"NaN",
"Inf",
true,
false,
"1.0 foo",
"foo 1.0",
Elixir.Decimal.new("NaN"),
Elixir.Decimal.new("Inf")
]
describe "cast/1" do
test "casting a decimal returns the input as it is" do
assert XSD.decimal(0) |> XSD.Decimal.cast() == XSD.decimal(0)
assert XSD.decimal("-0.0") |> XSD.Decimal.cast() == XSD.decimal("-0.0")
assert XSD.decimal(1) |> XSD.Decimal.cast() == XSD.decimal(1)
assert XSD.decimal(0.1) |> XSD.Decimal.cast() == XSD.decimal(0.1)
end
test "casting a boolean" do
assert XSD.true() |> XSD.Decimal.cast() == XSD.decimal(1.0)
assert XSD.false() |> XSD.Decimal.cast() == XSD.decimal(0.0)
end
test "casting a string with a value from the lexical value space of xsd:decimal" do
assert XSD.string("0") |> XSD.Decimal.cast() == XSD.decimal(0)
assert XSD.string("3.14") |> XSD.Decimal.cast() == XSD.decimal(3.14)
end
test "casting a string with a value not in the lexical value space of xsd:decimal" do
assert XSD.string("foo") |> XSD.Decimal.cast() == nil
end
test "casting an integer" do
assert XSD.integer(0) |> XSD.Decimal.cast() == XSD.decimal(0.0)
assert XSD.integer(42) |> XSD.Decimal.cast() == XSD.decimal(42.0)
end
test "casting a double" do
assert XSD.double(0.0) |> XSD.Decimal.cast() == XSD.decimal(0.0)
assert XSD.double("-0.0") |> XSD.Decimal.cast() == XSD.decimal(0.0)
assert XSD.double(0.1) |> XSD.Decimal.cast() == XSD.decimal(0.1)
assert XSD.double(1) |> XSD.Decimal.cast() == XSD.decimal(1.0)
assert XSD.double(3.14) |> XSD.Decimal.cast() == XSD.decimal(3.14)
assert XSD.double(10.1e1) |> XSD.Decimal.cast() == XSD.decimal(101.0)
assert XSD.double("NAN") |> XSD.Decimal.cast() == nil
assert XSD.double("+INF") |> XSD.Decimal.cast() == nil
end
test "casting a float" do
assert XSD.float(0.0) |> XSD.Decimal.cast() == XSD.decimal(0.0)
assert XSD.float("-0.0") |> XSD.Decimal.cast() == XSD.decimal(0.0)
assert XSD.float(0.1) |> XSD.Decimal.cast() == XSD.decimal(0.1)
assert XSD.float(1) |> XSD.Decimal.cast() == XSD.decimal(1.0)
assert XSD.float(3.14) |> XSD.Decimal.cast() == XSD.decimal(3.14)
assert XSD.float(10.1e1) |> XSD.Decimal.cast() == XSD.decimal(101.0)
assert XSD.float("NAN") |> XSD.Decimal.cast() == nil
assert XSD.float("+INF") |> XSD.Decimal.cast() == nil
end
test "with invalid literals" do
assert XSD.boolean("42") |> XSD.Decimal.cast() == nil
assert XSD.integer(3.14) |> XSD.Decimal.cast() == nil
assert XSD.decimal("NAN") |> XSD.Decimal.cast() == nil
assert XSD.double(true) |> XSD.Decimal.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.Decimal.cast() == nil
end
test "with coercible value" do
assert XSD.Decimal.cast("3.14") == XSD.decimal(3.14)
assert XSD.Decimal.cast("42") == XSD.decimal(42.0)
end
test "with non-coercible value" do
assert XSD.Decimal.cast(:foo) == nil
assert XSD.Decimal.cast(make_ref()) == nil
end
end
test "digit_count/1" do
assert XSD.Decimal.digit_count(XSD.decimal("1.2345")) == 5
assert XSD.Decimal.digit_count(XSD.decimal("-1.2345")) == 5
assert XSD.Decimal.digit_count(XSD.decimal("+1.2345")) == 5
assert XSD.Decimal.digit_count(XSD.decimal("01.23450")) == 5
assert XSD.Decimal.digit_count(XSD.decimal("01.23450")) == 5
assert XSD.Decimal.digit_count(XSD.decimal("NAN")) == nil
assert XSD.Decimal.digit_count(XSD.integer("2")) == 1
assert XSD.Decimal.digit_count(XSD.integer("23")) == 2
assert XSD.Decimal.digit_count(XSD.integer("023")) == 2
end
test "fraction_digit_count/1" do
assert XSD.Decimal.fraction_digit_count(XSD.decimal("1.2345")) == 4
assert XSD.Decimal.fraction_digit_count(XSD.decimal("-1.2345")) == 4
assert XSD.Decimal.fraction_digit_count(XSD.decimal("+1.2345")) == 4
assert XSD.Decimal.fraction_digit_count(XSD.decimal("01.23450")) == 4
assert XSD.Decimal.fraction_digit_count(XSD.decimal("0.023450")) == 5
assert XSD.Decimal.fraction_digit_count(XSD.decimal("NAN")) == nil
assert XSD.Decimal.fraction_digit_count(XSD.integer("2")) == 0
assert XSD.Decimal.fraction_digit_count(XSD.integer("23")) == 0
assert XSD.Decimal.fraction_digit_count(XSD.integer("023")) == 0
end
defmacrop sigil_d(str, _opts) do
quote do
Elixir.Decimal.new(unquote(str))
end
end
test "Decimal.canonical_decimal/1" do
assert Decimal.canonical_decimal(~d"0") == ~d"0.0"
assert Decimal.canonical_decimal(~d"0.0") == ~d"0.0"
assert Decimal.canonical_decimal(~d"0.001") == ~d"0.001"
assert Decimal.canonical_decimal(~d"-0") == ~d"-0.0"
assert Decimal.canonical_decimal(~d"-1") == ~d"-1.0"
assert Decimal.canonical_decimal(~d"-0.00") == ~d"-0.0"
assert Decimal.canonical_decimal(~d"1.00") == ~d"1.0"
assert Decimal.canonical_decimal(~d"1000") == ~d"1000.0"
assert Decimal.canonical_decimal(~d"1000.000000") == ~d"1000.0"
assert Decimal.canonical_decimal(~d"12345.000") == ~d"12345.0"
assert Decimal.canonical_decimal(~d"42") == ~d"42.0"
assert Decimal.canonical_decimal(~d"42.42") == ~d"42.42"
assert Decimal.canonical_decimal(~d"0.42") == ~d"0.42"
assert Decimal.canonical_decimal(~d"0.0042") == ~d"0.0042"
assert Decimal.canonical_decimal(~d"010.020") == ~d"10.02"
assert Decimal.canonical_decimal(~d"-1.23") == ~d"-1.23"
assert Decimal.canonical_decimal(~d"-0.0123") == ~d"-0.0123"
assert Decimal.canonical_decimal(~d"1E+2") == ~d"100.0"
assert Decimal.canonical_decimal(~d"1.2E3") == ~d"1200.0"
assert Decimal.canonical_decimal(~d"-42E+3") == ~d"-42000.0"
end
end

View file

@ -0,0 +1,69 @@
defmodule RDF.XSD.DoubleTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Double,
name: "double",
primitive: true,
comparable_datatypes: [RDF.XSD.Integer, RDF.XSD.Decimal],
valid: RDF.XSD.TestData.valid_floats(),
invalid: RDF.XSD.TestData.invalid_floats()
describe "cast/1" do
test "casting a double returns the input as it is" do
assert XSD.double(3.14) |> XSD.Double.cast() == XSD.double(3.14)
assert XSD.double("NAN") |> XSD.Double.cast() == XSD.double("NAN")
assert XSD.double("-INF") |> XSD.Double.cast() == XSD.double("-INF")
end
test "casting a boolean" do
assert XSD.true() |> XSD.Double.cast() == XSD.double(1.0)
assert XSD.false() |> XSD.Double.cast() == XSD.double(0.0)
end
test "casting a string with a value from the lexical value space of xsd:double" do
assert XSD.string("1.0") |> XSD.Double.cast() == XSD.double("1.0E0")
assert XSD.string("3.14") |> XSD.Double.cast() == XSD.double("3.14E0")
assert XSD.string("3.14E0") |> XSD.Double.cast() == XSD.double("3.14E0")
end
test "casting a string with a value not in the lexical value space of xsd:double" do
assert XSD.string("foo") |> XSD.Double.cast() == nil
end
test "casting an integer" do
assert XSD.integer(0) |> XSD.Double.cast() == XSD.double(0.0)
assert XSD.integer(42) |> XSD.Double.cast() == XSD.double(42.0)
end
test "casting a decimal" do
assert XSD.decimal(0) |> XSD.Double.cast() == XSD.double(0)
assert XSD.decimal(1) |> XSD.Double.cast() == XSD.double(1)
assert XSD.decimal(3.14) |> XSD.Double.cast() == XSD.double(3.14)
end
test "casting a float" do
assert XSD.float(0) |> XSD.Double.cast() == XSD.double(0)
assert XSD.float(1) |> XSD.Double.cast() == XSD.double(1)
assert XSD.float(3.14) |> XSD.Double.cast() == XSD.double(3.14)
end
test "with invalid literals" do
assert XSD.boolean("42") |> XSD.Double.cast() == nil
assert XSD.integer(3.14) |> XSD.Double.cast() == nil
assert XSD.decimal("NAN") |> XSD.Double.cast() == nil
assert XSD.double(true) |> XSD.Double.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.Double.cast() == nil
end
test "with coercible value" do
assert XSD.Double.cast("3.14") == XSD.double(3.14) |> XSD.Double.canonical()
end
test "with non-coercible value" do
assert XSD.Double.cast(:foo) == nil
assert XSD.Double.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,12 @@
defmodule RDF.XSD.FloatTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Float,
name: "float",
base: RDF.XSD.Double,
base_primitive: RDF.XSD.Double,
comparable_datatypes: [RDF.XSD.Integer, RDF.XSD.Decimal],
applicable_facets: [],
facets: %{},
valid: RDF.XSD.TestData.valid_floats(),
invalid: RDF.XSD.TestData.invalid_floats()
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.IntTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Int,
name: "int",
base: RDF.XSD.Long,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: -2_147_483_648,
max_inclusive: 2_147_483_647
},
valid: RDF.XSD.TestData.valid_ints(),
invalid: RDF.XSD.TestData.invalid_ints()
end

View file

@ -0,0 +1,107 @@
defmodule RDF.XSD.IntegerTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Integer,
name: "integer",
primitive: true,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: nil,
max_inclusive: nil
},
valid: RDF.XSD.TestData.valid_integers(),
invalid: RDF.XSD.TestData.invalid_integers()
describe "cast/1" do
test "casting an integer returns the input as it is" do
assert XSD.integer(0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.integer(1) |> XSD.Integer.cast() == XSD.integer(1)
end
test "casting a boolean" do
assert XSD.false() |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.true() |> XSD.Integer.cast() == XSD.integer(1)
end
test "casting a string with a value from the lexical value space of xsd:integer" do
assert XSD.string("0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.string("042") |> XSD.Integer.cast() == XSD.integer(42)
end
test "casting a string with a value not in the lexical value space of xsd:integer" do
assert XSD.string("foo") |> XSD.Integer.cast() == nil
assert XSD.string("3.14") |> XSD.Integer.cast() == nil
end
test "casting an decimal" do
assert XSD.decimal(0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.decimal(1.0) |> XSD.Integer.cast() == XSD.integer(1)
assert XSD.decimal(3.14) |> XSD.Integer.cast() == XSD.integer(3)
end
test "casting a double" do
assert XSD.double(0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double(0.0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double(0.1) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double("+0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double("+0.0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double("-0.0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double("0.0E0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.double(1) |> XSD.Integer.cast() == XSD.integer(1)
assert XSD.double(3.14) |> XSD.Integer.cast() == XSD.integer(3)
assert XSD.double("NAN") |> XSD.Integer.cast() == nil
assert XSD.double("+INF") |> XSD.Integer.cast() == nil
end
test "casting a float" do
assert XSD.float(0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float(0.0) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float(0.1) |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float("+0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float("+0.0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float("-0.0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float("0.0E0") |> XSD.Integer.cast() == XSD.integer(0)
assert XSD.float(1) |> XSD.Integer.cast() == XSD.integer(1)
assert XSD.float(3.14) |> XSD.Integer.cast() == XSD.integer(3)
assert XSD.float("NAN") |> XSD.Integer.cast() == nil
assert XSD.float("+INF") |> XSD.Integer.cast() == nil
end
test "with invalid literals" do
assert XSD.integer(3.14) |> XSD.Integer.cast() == nil
assert XSD.decimal("NAN") |> XSD.Integer.cast() == nil
assert XSD.double(true) |> XSD.Integer.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.Integer.cast() == nil
end
test "with coercible value" do
assert XSD.Integer.cast("42") == XSD.integer(42)
assert XSD.Integer.cast(3.14) == XSD.integer(3)
assert XSD.Integer.cast(true) == XSD.integer(1)
assert XSD.Integer.cast(false) == XSD.integer(0)
end
test "with non-coercible value" do
assert XSD.Integer.cast(:foo) == nil
assert XSD.Integer.cast(make_ref()) == nil
end
end
test "digit_count/1" do
assert XSD.Integer.digit_count(XSD.integer("2")) == 1
assert XSD.Integer.digit_count(XSD.integer("23")) == 2
assert XSD.Integer.digit_count(XSD.integer("023")) == 2
assert XSD.Integer.digit_count(XSD.integer("+023")) == 2
assert XSD.Integer.digit_count(XSD.integer("-023")) == 2
assert XSD.Integer.digit_count(XSD.positive_integer("23")) == 2
assert XSD.Integer.digit_count(XSD.byte("00023")) == 2
assert XSD.Integer.digit_count(XSD.integer("NaN")) == nil
assert XSD.Integer.digit_count(XSD.positive_integer("-023")) == nil
assert XSD.Integer.digit_count(XSD.byte("12345")) == nil
end
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.LongTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Long,
name: "long",
base: RDF.XSD.Integer,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: -9_223_372_036_854_775_808,
max_inclusive: 9_223_372_036_854_775_807
},
valid: RDF.XSD.TestData.valid_longs(),
invalid: RDF.XSD.TestData.invalid_longs()
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.NegativeIntegerTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.NegativeInteger,
name: "negativeInteger",
base: RDF.XSD.NonPositiveInteger,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: nil,
max_inclusive: -1
},
valid: RDF.XSD.TestData.valid_negative_integers(),
invalid: RDF.XSD.TestData.invalid_negative_integers()
end

View file

@ -0,0 +1,116 @@
defmodule RDF.XSD.NonNegativeIntegerTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.NonNegativeInteger,
name: "nonNegativeInteger",
base: RDF.XSD.Integer,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 0,
max_inclusive: nil
},
valid: RDF.XSD.TestData.valid_non_negative_integers(),
invalid: RDF.XSD.TestData.invalid_non_negative_integers()
describe "cast/1" do
test "casting a non_negative_integer returns the input as it is" do
assert XSD.non_negative_integer(0) |> XSD.NonNegativeInteger.cast() ==
XSD.non_negative_integer(0)
assert XSD.non_negative_integer(1) |> XSD.NonNegativeInteger.cast() ==
XSD.non_negative_integer(1)
end
test "casting an integer with a value from the value space of non_negative_integer" do
assert XSD.integer(0) |> XSD.NonNegativeInteger.cast() ==
XSD.non_negative_integer(0)
assert XSD.integer(1) |> XSD.NonNegativeInteger.cast() ==
XSD.non_negative_integer(1)
end
test "casting an integer with a value not from the value space of non_negative_integer" do
assert XSD.integer(-1) |> XSD.NonNegativeInteger.cast() == nil
end
test "casting a positive_integer" do
assert XSD.positive_integer(1) |> XSD.NonNegativeInteger.cast() ==
XSD.non_negative_integer(1)
end
test "casting a boolean" do
assert XSD.false() |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.true() |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(1)
end
test "casting a string with a value from the lexical value space of xsd:integer" do
assert XSD.string("0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.string("042") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(42)
end
test "casting a string with a value not in the lexical value space of xsd:integer" do
assert XSD.string("foo") |> XSD.NonNegativeInteger.cast() == nil
assert XSD.string("3.14") |> XSD.NonNegativeInteger.cast() == nil
end
test "casting an decimal" do
assert XSD.decimal(0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.decimal(1.0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(1)
assert XSD.decimal(3.14) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(3)
end
test "casting a double" do
assert XSD.double(0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double(0.0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double(0.1) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double("+0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double("+0.0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double("-0.0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double("0.0E0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.double(1) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(1)
assert XSD.double(3.14) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(3)
assert XSD.double("NAN") |> XSD.NonNegativeInteger.cast() == nil
assert XSD.double("+INF") |> XSD.NonNegativeInteger.cast() == nil
end
test "casting a float" do
assert XSD.float(0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float(0.0) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float(0.1) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float("+0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float("+0.0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float("-0.0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float("0.0E0") |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(0)
assert XSD.float(1) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(1)
assert XSD.float(3.14) |> XSD.NonNegativeInteger.cast() == XSD.non_negative_integer(3)
assert XSD.float("NAN") |> XSD.NonNegativeInteger.cast() == nil
assert XSD.float("+INF") |> XSD.NonNegativeInteger.cast() == nil
end
test "with invalid literals" do
assert XSD.non_negative_integer(3.14) |> XSD.NonNegativeInteger.cast() == nil
assert XSD.positive_integer(0) |> XSD.NonNegativeInteger.cast() == nil
assert XSD.decimal("NAN") |> XSD.NonNegativeInteger.cast() == nil
assert XSD.double(true) |> XSD.NonNegativeInteger.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.NonNegativeInteger.cast() == nil
end
test "with coercible value" do
assert XSD.NonNegativeInteger.cast("42") == XSD.non_negative_integer(42)
assert XSD.NonNegativeInteger.cast(3.14) == XSD.non_negative_integer(3)
assert XSD.NonNegativeInteger.cast(true) == XSD.non_negative_integer(1)
assert XSD.NonNegativeInteger.cast(false) == XSD.non_negative_integer(0)
end
test "with non-coercible value" do
assert XSD.NonNegativeInteger.cast(:foo) == nil
assert XSD.NonNegativeInteger.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.NonPositiveIntegerTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.NonPositiveInteger,
name: "nonPositiveInteger",
base: RDF.XSD.Integer,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: nil,
max_inclusive: 0
},
valid: RDF.XSD.TestData.valid_non_positive_integers(),
invalid: RDF.XSD.TestData.invalid_non_positive_integers()
end

View file

@ -0,0 +1,571 @@
defmodule RDF.XSD.NumericTest do
use ExUnit.Case
alias RDF.XSD
alias XSD.Numeric
alias Decimal, as: D
@positive_infinity XSD.double(:positive_infinity)
@negative_infinity XSD.double(:negative_infinity)
@nan XSD.double(:nan)
@negative_zeros ~w[
-0
-000
-0.0
-0.00000
]
test "negative_zero?/1" do
Enum.each(@negative_zeros, fn negative_zero ->
assert Numeric.negative_zero?(XSD.double(negative_zero))
assert Numeric.negative_zero?(XSD.float(negative_zero))
assert Numeric.negative_zero?(XSD.decimal(negative_zero))
end)
refute Numeric.negative_zero?(XSD.double("-0.00001"))
refute Numeric.negative_zero?(XSD.float("-0.00001"))
refute Numeric.negative_zero?(XSD.decimal("-0.00001"))
end
test "zero?/1" do
assert Numeric.zero?(XSD.integer(0))
assert Numeric.zero?(XSD.integer("0"))
~w[
0
000
0.0
00.00
]
|> Enum.each(fn positive_zero ->
assert Numeric.zero?(XSD.double(positive_zero))
assert Numeric.zero?(XSD.float(positive_zero))
assert Numeric.zero?(XSD.decimal(positive_zero))
end)
Enum.each(@negative_zeros, fn negative_zero ->
assert Numeric.zero?(XSD.double(negative_zero))
assert Numeric.zero?(XSD.float(negative_zero))
assert Numeric.zero?(XSD.decimal(negative_zero))
end)
refute Numeric.zero?(XSD.double("-0.00001"))
refute Numeric.zero?(XSD.float("-0.00001"))
refute Numeric.zero?(XSD.decimal("-0.00001"))
end
describe "add/2" do
test "xsd:integer literal + xsd:integer literal" do
assert Numeric.add(XSD.integer(1), XSD.integer(2)) == XSD.integer(3)
end
test "xsd:decimal literal + xsd:integer literal" do
assert Numeric.add(XSD.decimal(1.1), XSD.integer(2)) == XSD.decimal(3.1)
end
test "xsd:double literal + xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.double(1.1), XSD.integer(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(3.1)), 0.000000000000001
end
test "xsd:decimal literal + xsd:double literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.add(XSD.decimal(1.1), XSD.double(2.2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(3.3)), 0.000000000000001
end
test "xsd:float literal + xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.float(1.1), XSD.integer(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(3.1)), 0.000000000000001
end
test "xsd:decimal literal + xsd:float literal" do
assert result = %RDF.Literal{literal: %XSD.Float{}} = Numeric.add(XSD.decimal(1.1), XSD.float(2.2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.float(3.3)), 0.000000000000001
end
test "if one of the operands is a zero or a finite number and the other is INF or -INF, INF or -INF is returned" do
assert Numeric.add(@positive_infinity, XSD.double(0)) == @positive_infinity
assert Numeric.add(@positive_infinity, XSD.double(3.14)) == @positive_infinity
assert Numeric.add(XSD.double(0), @positive_infinity) == @positive_infinity
assert Numeric.add(XSD.double(3.14), @positive_infinity) == @positive_infinity
assert Numeric.add(@negative_infinity, XSD.double(0)) == @negative_infinity
assert Numeric.add(@negative_infinity, XSD.double(3.14)) == @negative_infinity
assert Numeric.add(XSD.double(0), @negative_infinity) == @negative_infinity
assert Numeric.add(XSD.double(3.14), @negative_infinity) == @negative_infinity
end
test "if both operands are INF, INF is returned" do
assert Numeric.add(@positive_infinity, @positive_infinity) ==
@positive_infinity
end
test "if both operands are -INF, -INF is returned" do
assert Numeric.add(@negative_infinity, @negative_infinity) ==
@negative_infinity
end
test "if one of the operands is INF and the other is -INF, NaN is returned" do
assert Numeric.add(@positive_infinity, @negative_infinity) == @nan
assert Numeric.add(@negative_infinity, @positive_infinity) == @nan
end
test "coercion" do
assert Numeric.add(1, 2) == XSD.integer(3)
assert Numeric.add(3.14, 42) == XSD.double(45.14)
assert XSD.decimal(3.14) |> Numeric.add(42) == XSD.decimal(45.14)
assert Numeric.add(42, XSD.decimal(3.14)) == XSD.decimal(45.14)
assert Numeric.add(42, :foo) == nil
assert Numeric.add(:foo, 42) == nil
assert Numeric.add(:foo, :bar) == nil
end
end
describe "subtract/2" do
test "xsd:integer literal - xsd:integer literal" do
assert Numeric.subtract(XSD.integer(3), XSD.integer(2)) == XSD.integer(1)
end
test "xsd:decimal literal - xsd:integer literal" do
assert Numeric.subtract(XSD.decimal(3.3), XSD.integer(2)) == XSD.decimal(1.3)
end
test "xsd:double literal - xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.subtract(XSD.double(3.3), XSD.integer(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(1.3)), 0.000000000000001
end
test "xsd:decimal literal - xsd:double literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.subtract(XSD.decimal(3.3), XSD.double(2.2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(1.1)), 0.000000000000001
end
test "if one of the operands is a zero or a finite number and the other is INF or -INF, an infinity of the appropriate sign is returned" do
assert Numeric.subtract(@positive_infinity, XSD.double(0)) == @positive_infinity
assert Numeric.subtract(@positive_infinity, XSD.double(3.14)) == @positive_infinity
assert Numeric.subtract(XSD.double(0), @positive_infinity) == @negative_infinity
assert Numeric.subtract(XSD.double(3.14), @positive_infinity) == @negative_infinity
assert Numeric.subtract(@negative_infinity, XSD.double(0)) == @negative_infinity
assert Numeric.subtract(@negative_infinity, XSD.double(3.14)) == @negative_infinity
assert Numeric.subtract(XSD.double(0), @negative_infinity) == @positive_infinity
assert Numeric.subtract(XSD.double(3.14), @negative_infinity) == @positive_infinity
end
test "if both operands are INF or -INF, NaN is returned" do
assert Numeric.subtract(@positive_infinity, @positive_infinity) == XSD.double(:nan)
assert Numeric.subtract(@negative_infinity, @negative_infinity) == XSD.double(:nan)
end
test "if one of the operands is INF and the other is -INF, an infinity of the appropriate sign is returned" do
assert Numeric.subtract(@positive_infinity, @negative_infinity) == @positive_infinity
assert Numeric.subtract(@negative_infinity, @positive_infinity) == @negative_infinity
end
test "coercion" do
assert Numeric.subtract(2, 1) == XSD.integer(1)
assert Numeric.subtract(42, 3.14) == XSD.double(38.86)
assert XSD.decimal(3.14) |> Numeric.subtract(42) == XSD.decimal(-38.86)
assert Numeric.subtract(42, XSD.decimal(3.14)) == XSD.decimal(38.86)
end
end
describe "multiply/2" do
test "xsd:integer literal * xsd:integer literal" do
assert Numeric.multiply(XSD.integer(2), XSD.integer(3)) == XSD.integer(6)
end
test "xsd:decimal literal * xsd:integer literal" do
assert Numeric.multiply(XSD.decimal(1.5), XSD.integer(3)) == XSD.decimal(4.5)
end
test "xsd:double literal * xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.multiply(XSD.double(1.5), XSD.integer(3))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(4.5)), 0.000000000000001
end
test "xsd:decimal literal * xsd:double literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.multiply(XSD.decimal(0.5), XSD.double(2.5))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(1.25)), 0.000000000000001
end
test "if one of the operands is a zero and the other is an infinity, NaN is returned" do
assert Numeric.multiply(@positive_infinity, XSD.double(0.0)) == @nan
assert Numeric.multiply(XSD.integer(0), @positive_infinity) == @nan
assert Numeric.multiply(XSD.decimal(0), @positive_infinity) == @nan
assert Numeric.multiply(@negative_infinity, XSD.double(0)) == @nan
assert Numeric.multiply(XSD.integer(0), @negative_infinity) == @nan
assert Numeric.multiply(XSD.decimal(0.0), @negative_infinity) == @nan
end
test "if one of the operands is a non-zero number and the other is an infinity, an infinity with the appropriate sign is returned" do
assert Numeric.multiply(@positive_infinity, XSD.double(3.14)) == @positive_infinity
assert Numeric.multiply(XSD.double(3.14), @positive_infinity) == @positive_infinity
assert Numeric.multiply(@positive_infinity, XSD.double(-3.14)) == @negative_infinity
assert Numeric.multiply(XSD.double(-3.14), @positive_infinity) == @negative_infinity
assert Numeric.multiply(@negative_infinity, XSD.double(3.14)) == @negative_infinity
assert Numeric.multiply(XSD.double(3.14), @negative_infinity) == @negative_infinity
assert Numeric.multiply(@negative_infinity, XSD.double(-3.14)) == @positive_infinity
assert Numeric.multiply(XSD.double(-3.14), @negative_infinity) == @positive_infinity
end
# The following assertions are not part of the spec.
test "if both operands are INF, INF is returned" do
assert Numeric.multiply(@positive_infinity, @positive_infinity) == @positive_infinity
end
test "if both operands are -INF, -INF is returned" do
assert Numeric.multiply(@negative_infinity, @negative_infinity) == @negative_infinity
end
test "if one of the operands is INF and the other is -INF, NaN is returned" do
assert Numeric.multiply(@positive_infinity, @negative_infinity) == XSD.double(:nan)
assert Numeric.multiply(@negative_infinity, @positive_infinity) == XSD.double(:nan)
end
test "coercion" do
assert Numeric.multiply(1, 2) == XSD.integer(2)
assert Numeric.multiply(2, 1.5) == XSD.double(3.0)
assert XSD.decimal(1.5) |> Numeric.multiply(2) == XSD.decimal(3.0)
assert Numeric.multiply(2, XSD.decimal(1.5)) == XSD.decimal(3.0)
end
end
describe "divide/2" do
test "xsd:integer literal / xsd:integer literal" do
assert Numeric.divide(XSD.integer(4), XSD.integer(2)) == XSD.decimal(2.0)
end
test "xsd:decimal literal / xsd:integer literal" do
assert Numeric.divide(XSD.decimal(4), XSD.integer(2)) == XSD.decimal(2.0)
end
test "xsd:double literal / xsd:integer literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.divide(XSD.double(4), XSD.integer(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(2)), 0.000000000000001
end
test "xsd:decimal literal / xsd:double literal" do
assert result = %RDF.Literal{literal: %XSD.Double{}} = Numeric.divide(XSD.decimal(4), XSD.double(2))
assert_in_delta RDF.Literal.value(result),
RDF.Literal.value(XSD.double(2)), 0.000000000000001
end
test "a positive number divided by positive zero returns INF" do
assert Numeric.divide(XSD.double(1.0), XSD.double(0.0)) == @positive_infinity
assert Numeric.divide(XSD.double(1.0), XSD.decimal(0.0)) == @positive_infinity
assert Numeric.divide(XSD.double(1.0), XSD.integer(0)) == @positive_infinity
assert Numeric.divide(XSD.decimal(1.0), XSD.double(0.0)) == @positive_infinity
assert Numeric.divide(XSD.integer(1), XSD.double(0.0)) == @positive_infinity
end
test "a negative number divided by positive zero returns -INF" do
assert Numeric.divide(XSD.double(-1.0), XSD.double(0.0)) == @negative_infinity
assert Numeric.divide(XSD.double(-1.0), XSD.decimal(0.0)) == @negative_infinity
assert Numeric.divide(XSD.double(-1.0), XSD.integer(0)) == @negative_infinity
assert Numeric.divide(XSD.decimal(-1.0), XSD.double(0.0)) == @negative_infinity
assert Numeric.divide(XSD.integer(-1), XSD.double(0.0)) == @negative_infinity
end
test "a positive number divided by negative zero returns -INF" do
assert Numeric.divide(XSD.double(1.0), XSD.double("-0.0")) == @negative_infinity
assert Numeric.divide(XSD.double(1.0), XSD.decimal("-0.0")) == @negative_infinity
assert Numeric.divide(XSD.decimal(1.0), XSD.double("-0.0")) == @negative_infinity
assert Numeric.divide(XSD.integer(1), XSD.double("-0.0")) == @negative_infinity
end
test "a negative number divided by negative zero returns INF" do
assert Numeric.divide(XSD.double(-1.0), XSD.double("-0.0")) == @positive_infinity
assert Numeric.divide(XSD.double(-1.0), XSD.decimal("-0.0")) == @positive_infinity
assert Numeric.divide(XSD.decimal(-1.0), XSD.double("-0.0")) == @positive_infinity
assert Numeric.divide(XSD.integer(-1), XSD.double("-0.0")) == @positive_infinity
end
test "nil is returned for xs:decimal and xs:integer operands, if the divisor is (positive or negative) zero" do
assert Numeric.divide(XSD.decimal(1.0), XSD.decimal(0.0)) == nil
assert Numeric.divide(XSD.decimal(1.0), XSD.integer(0)) == nil
assert Numeric.divide(XSD.decimal(-1.0), XSD.decimal(0.0)) == nil
assert Numeric.divide(XSD.decimal(-1.0), XSD.integer(0)) == nil
assert Numeric.divide(XSD.integer(1), XSD.integer(0)) == nil
assert Numeric.divide(XSD.integer(1), XSD.decimal(0.0)) == nil
assert Numeric.divide(XSD.integer(-1), XSD.integer(0)) == nil
assert Numeric.divide(XSD.integer(-1), XSD.decimal(0.0)) == nil
end
test "positive or negative zero divided by positive or negative zero returns NaN" do
assert Numeric.divide(XSD.double("-0.0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.double("-0.0"), XSD.decimal(0.0)) == @nan
assert Numeric.divide(XSD.double("-0.0"), XSD.integer(0)) == @nan
assert Numeric.divide(XSD.decimal("-0.0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.integer("-0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.double("0.0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.double("0.0"), XSD.decimal(0.0)) == @nan
assert Numeric.divide(XSD.double("0.0"), XSD.integer(0)) == @nan
assert Numeric.divide(XSD.decimal("0.0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.integer("0"), XSD.double(0.0)) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.double("-0.0")) == @nan
assert Numeric.divide(XSD.decimal(0.0), XSD.double("-0.0")) == @nan
assert Numeric.divide(XSD.integer(0), XSD.double("-0.0")) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.decimal("-0.0")) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.integer("-0")) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.double("0.0")) == @nan
assert Numeric.divide(XSD.decimal(0.0), XSD.double("0.0")) == @nan
assert Numeric.divide(XSD.integer(0), XSD.double("0.0")) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.decimal("0.0")) == @nan
assert Numeric.divide(XSD.double(0.0), XSD.integer("0")) == @nan
end
test "INF or -INF divided by INF or -INF returns NaN" do
assert Numeric.divide(@positive_infinity, @positive_infinity) == @nan
assert Numeric.divide(@negative_infinity, @negative_infinity) == @nan
assert Numeric.divide(@positive_infinity, @negative_infinity) == @nan
assert Numeric.divide(@negative_infinity, @positive_infinity) == @nan
end
# TODO: What happens when using INF/-INF on division with numbers?
test "coercion" do
assert Numeric.divide(4, 2) == XSD.decimal(2.0)
assert Numeric.divide(4, 2.0) == XSD.double(2.0)
assert XSD.decimal(4) |> Numeric.divide(2) == XSD.decimal(2.0)
assert Numeric.divide(4, XSD.decimal(2.0)) == XSD.decimal(2.0)
assert Numeric.divide("foo", "bar") == nil
assert Numeric.divide(4, "bar") == nil
assert Numeric.divide("foo", 2) == nil
assert Numeric.divide(42, :bar) == nil
assert Numeric.divide(:foo, 42) == nil
assert Numeric.divide(:foo, :bar) == nil
end
end
describe "abs/1" do
test "with xsd:integer" do
assert XSD.integer(42) |> Numeric.abs() == XSD.integer(42)
assert XSD.integer(-42) |> Numeric.abs() == XSD.integer(42)
end
test "with xsd:double" do
assert XSD.double(3.14) |> Numeric.abs() == XSD.double(3.14)
assert XSD.double(-3.14) |> Numeric.abs() == XSD.double(3.14)
assert XSD.double("INF") |> Numeric.abs() == XSD.double("INF")
assert XSD.double("-INF") |> Numeric.abs() == XSD.double("INF")
assert XSD.double("NAN") |> Numeric.abs() == XSD.double("NAN")
end
test "with xsd:decimal" do
assert XSD.decimal(3.14) |> Numeric.abs() == XSD.decimal(3.14)
assert XSD.decimal(-3.14) |> Numeric.abs() == XSD.decimal(3.14)
end
@tag skip: "TODO: type-promotion"
test "with derived numerics" do
assert XSD.byte(-42) |> Numeric.abs() == XSD.byte(42)
assert XSD.byte("-42") |> Numeric.abs() == XSD.byte(42)
assert XSD.non_positive_integer(-42) |> Numeric.abs() == XSD.integer(42)
end
test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.abs() == nil
assert XSD.double("foo") |> Numeric.abs() == nil
assert XSD.decimal("foo") |> Numeric.abs() == nil
end
test "coercion" do
assert Numeric.abs(42) == XSD.integer(42)
assert Numeric.abs(-42) == XSD.integer(42)
assert Numeric.abs(-3.14) == XSD.double(3.14)
assert Numeric.abs(D.from_float(-3.14)) == XSD.decimal(3.14)
assert Numeric.abs("foo") == nil
assert Numeric.abs(:foo) == nil
end
end
describe "round/1" do
test "with xsd:integer" do
assert XSD.integer(42) |> Numeric.round() == XSD.integer(42)
assert XSD.integer(-42) |> Numeric.round() == XSD.integer(-42)
end
test "with xsd:double" do
assert XSD.double(3.14) |> Numeric.round() == XSD.double(3.0)
assert XSD.double(-3.14) |> Numeric.round() == XSD.double(-3.0)
assert XSD.double(-2.5) |> Numeric.round() == XSD.double(-2.0)
assert XSD.double("INF") |> Numeric.round() == XSD.double("INF")
assert XSD.double("-INF") |> Numeric.round() == XSD.double("-INF")
assert XSD.double("NAN") |> Numeric.round() == XSD.double("NAN")
end
test "with xsd:decimal" do
assert XSD.decimal(2.5) |> Numeric.round() == XSD.decimal("3")
assert XSD.decimal(2.4999) |> Numeric.round() == XSD.decimal("2")
assert XSD.decimal(-2.5) |> Numeric.round() == XSD.decimal("-2")
end
test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.round() == nil
assert XSD.double("foo") |> Numeric.round() == nil
assert XSD.decimal("foo") |> Numeric.round() == nil
end
test "coercion" do
assert Numeric.round(-42) == XSD.integer(-42)
assert Numeric.round(-3.14) == XSD.double(-3.0)
assert Numeric.round(D.from_float(3.14)) == XSD.decimal("3")
assert Numeric.round("foo") == nil
assert Numeric.round(:foo) == nil
end
end
describe "round/2" do
test "with xsd:integer" do
assert XSD.integer(42) |> Numeric.round(3) == XSD.integer(42)
assert XSD.integer(8452) |> Numeric.round(-2) == XSD.integer(8500)
assert XSD.integer(85) |> Numeric.round(-1) == XSD.integer(90)
assert XSD.integer(-85) |> Numeric.round(-1) == XSD.integer(-80)
end
test "with xsd:double" do
assert XSD.double(3.14) |> Numeric.round(1) == XSD.double(3.1)
assert XSD.double(3.1415e0) |> Numeric.round(2) == XSD.double(3.14e0)
assert XSD.double("INF") |> Numeric.round(1) == XSD.double("INF")
assert XSD.double("-INF") |> Numeric.round(2) == XSD.double("-INF")
assert XSD.double("NAN") |> Numeric.round(3) == XSD.double("NAN")
end
test "with xsd:float" do
assert XSD.float(3.14) |> Numeric.round(1) == XSD.float(3.1)
assert XSD.float(3.1415e0) |> Numeric.round(2) == XSD.float(3.14e0)
assert XSD.float("INF") |> Numeric.round(1) == XSD.float("INF")
assert XSD.float("-INF") |> Numeric.round(2) == XSD.float("-INF")
assert XSD.float("NAN") |> Numeric.round(3) == XSD.float("NAN")
end
test "with xsd:decimal" do
assert XSD.decimal(1.125) |> Numeric.round(2) == XSD.decimal("1.13")
assert XSD.decimal(2.4999) |> Numeric.round(2) == XSD.decimal("2.50")
assert XSD.decimal(-2.55) |> Numeric.round(1) == XSD.decimal("-2.5")
end
test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.round(1) == nil
assert XSD.double("foo") |> Numeric.round(2) == nil
assert XSD.decimal("foo") |> Numeric.round(3) == nil
end
test "coercion" do
assert Numeric.round(-42, 1) == XSD.integer(-42)
assert Numeric.round(-3.14, 1) == XSD.double(-3.1)
assert Numeric.round(D.from_float(3.14), 1) == XSD.decimal("3.1")
assert Numeric.round("foo", 1) == nil
assert Numeric.round(:foo, 1) == nil
end
end
describe "ceil/1" do
test "with xsd:integer" do
assert XSD.integer(42) |> Numeric.ceil() == XSD.integer(42)
assert XSD.integer(-42) |> Numeric.ceil() == XSD.integer(-42)
end
test "with xsd:double" do
assert XSD.double(10.5) |> Numeric.ceil() == XSD.double("11")
assert XSD.double(-10.5) |> Numeric.ceil() == XSD.double("-10")
assert XSD.double("INF") |> Numeric.ceil() == XSD.double("INF")
assert XSD.double("-INF") |> Numeric.ceil() == XSD.double("-INF")
assert XSD.double("NAN") |> Numeric.ceil() == XSD.double("NAN")
end
test "with xsd:float" do
assert XSD.float(10.5) |> Numeric.ceil() == XSD.float("11")
assert XSD.float(-10.5) |> Numeric.ceil() == XSD.float("-10")
assert XSD.float("INF") |> Numeric.ceil() == XSD.float("INF")
assert XSD.float("-INF") |> Numeric.ceil() == XSD.float("-INF")
assert XSD.float("NAN") |> Numeric.ceil() == XSD.float("NAN")
end
test "with xsd:decimal" do
assert XSD.decimal(10.5) |> Numeric.ceil() == XSD.decimal("11")
assert XSD.decimal(-10.5) |> Numeric.ceil() == XSD.decimal("-10")
end
test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.ceil() == nil
assert XSD.double("foo") |> Numeric.ceil() == nil
assert XSD.decimal("foo") |> Numeric.ceil() == nil
end
test "coercion" do
assert Numeric.ceil(-42) == XSD.integer(-42)
assert Numeric.ceil(-3.14) == XSD.double("-3")
assert Numeric.ceil(D.from_float(3.14)) == XSD.decimal("4")
assert Numeric.ceil("foo") == nil
assert Numeric.ceil(:foo) == nil
end
end
describe "floor/1" do
test "with xsd:integer" do
assert XSD.integer(42) |> Numeric.floor() == XSD.integer(42)
assert XSD.integer(-42) |> Numeric.floor() == XSD.integer(-42)
end
test "with xsd:double" do
assert XSD.double(10.5) |> Numeric.floor() == XSD.double("10")
assert XSD.double(-10.5) |> Numeric.floor() == XSD.double("-11")
assert XSD.double("INF") |> Numeric.floor() == XSD.double("INF")
assert XSD.double("-INF") |> Numeric.floor() == XSD.double("-INF")
assert XSD.double("NAN") |> Numeric.floor() == XSD.double("NAN")
end
test "with xsd:float" do
assert XSD.float(10.5) |> Numeric.floor() == XSD.float("10")
assert XSD.float(-10.5) |> Numeric.floor() == XSD.float("-11")
assert XSD.float("INF") |> Numeric.floor() == XSD.float("INF")
assert XSD.float("-INF") |> Numeric.floor() == XSD.float("-INF")
assert XSD.float("NAN") |> Numeric.floor() == XSD.float("NAN")
end
test "with xsd:decimal" do
assert XSD.decimal(10.5) |> Numeric.floor() == XSD.decimal("10")
assert XSD.decimal(-10.5) |> Numeric.floor() == XSD.decimal("-11")
end
test "with invalid numeric literals" do
assert XSD.integer("-3.14") |> Numeric.floor() == nil
assert XSD.double("foo") |> Numeric.floor() == nil
assert XSD.decimal("foo") |> Numeric.floor() == nil
end
test "coercion" do
assert Numeric.floor(-42) == XSD.integer(-42)
assert Numeric.floor(-3.14) == XSD.double("-4")
assert Numeric.floor(D.from_float(3.14)) == XSD.decimal("3")
assert Numeric.floor("foo") == nil
assert Numeric.floor(:foo) == nil
end
end
end

View file

@ -0,0 +1,126 @@
defmodule RDF.XSD.PositiveIntegerTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.PositiveInteger,
name: "positiveInteger",
base: RDF.XSD.NonNegativeInteger,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 1,
max_inclusive: nil
},
valid: RDF.XSD.TestData.valid_positive_integers(),
invalid: RDF.XSD.TestData.invalid_positive_integers()
describe "cast/1" do
test "casting a positive_integer returns the input as it is" do
assert XSD.positive_integer(1) |> XSD.PositiveInteger.cast() ==
XSD.positive_integer(1)
assert XSD.positive_integer(1) |> XSD.PositiveInteger.cast() ==
XSD.positive_integer(1)
end
test "casting an integer with a value from the value space of positive_integer" do
assert XSD.integer(1) |> XSD.PositiveInteger.cast() ==
XSD.positive_integer(1)
end
test "casting an integer with a value not from the value space of positive_integer" do
assert XSD.integer(-1) |> XSD.PositiveInteger.cast() == nil
assert XSD.non_negative_integer(0) |> XSD.PositiveInteger.cast() == nil
end
test "casting a positive_integer" do
assert XSD.positive_integer(1) |> XSD.PositiveInteger.cast() ==
XSD.positive_integer(1)
end
test "casting a boolean" do
assert XSD.true() |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
end
test "casting a string with a value from the lexical value space of xsd:integer" do
assert XSD.string("1") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.string("042") |> XSD.PositiveInteger.cast() == XSD.positive_integer(42)
assert XSD.string("0") |> XSD.PositiveInteger.cast() == nil
end
test "casting a string with a value not in the lexical value space of xsd:integer" do
assert XSD.string("foo") |> XSD.PositiveInteger.cast() == nil
assert XSD.string("3.14") |> XSD.PositiveInteger.cast() == nil
end
test "casting an decimal" do
assert XSD.decimal(1.0) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.decimal(3.14) |> XSD.PositiveInteger.cast() == XSD.positive_integer(3)
assert XSD.decimal(0) |> XSD.PositiveInteger.cast() == nil
end
test "casting a double" do
assert XSD.double(1) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double(1.0) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double(1.1) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double("+1") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double("+1.0") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double("1.0E0") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.double(3.14) |> XSD.PositiveInteger.cast() == XSD.positive_integer(3)
assert XSD.double("NAN") |> XSD.PositiveInteger.cast() == nil
assert XSD.double("+INF") |> XSD.PositiveInteger.cast() == nil
assert XSD.double(0) |> XSD.PositiveInteger.cast() == nil
assert XSD.double(0.0) |> XSD.PositiveInteger.cast() == nil
assert XSD.double(0.1) |> XSD.PositiveInteger.cast() == nil
assert XSD.double("+0") |> XSD.PositiveInteger.cast() == nil
assert XSD.double("+0.0") |> XSD.PositiveInteger.cast() == nil
assert XSD.double("-0.0") |> XSD.PositiveInteger.cast() == nil
assert XSD.double("0.0E0") |> XSD.PositiveInteger.cast() == nil
assert XSD.double("-1.0") |> XSD.PositiveInteger.cast() == nil
end
test "casting a float" do
assert XSD.float(1) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float(1.0) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float(1.1) |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float("+1") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float("+1.0") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float("1.0E0") |> XSD.PositiveInteger.cast() == XSD.positive_integer(1)
assert XSD.float(3.14) |> XSD.PositiveInteger.cast() == XSD.positive_integer(3)
assert XSD.float("NAN") |> XSD.PositiveInteger.cast() == nil
assert XSD.float("+INF") |> XSD.PositiveInteger.cast() == nil
assert XSD.float(0) |> XSD.PositiveInteger.cast() == nil
assert XSD.float(0.0) |> XSD.PositiveInteger.cast() == nil
assert XSD.float(0.1) |> XSD.PositiveInteger.cast() == nil
assert XSD.float("+0") |> XSD.PositiveInteger.cast() == nil
assert XSD.float("+0.0") |> XSD.PositiveInteger.cast() == nil
assert XSD.float("-0.0") |> XSD.PositiveInteger.cast() == nil
assert XSD.float("0.0E0") |> XSD.PositiveInteger.cast() == nil
assert XSD.float("-1.0") |> XSD.PositiveInteger.cast() == nil
end
test "with invalid literals" do
assert XSD.positive_integer(3.14) |> XSD.PositiveInteger.cast() == nil
assert XSD.positive_integer(0) |> XSD.PositiveInteger.cast() == nil
assert XSD.decimal("NAN") |> XSD.PositiveInteger.cast() == nil
assert XSD.double(true) |> XSD.PositiveInteger.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.date("2020-01-01") |> XSD.PositiveInteger.cast() == nil
end
test "with coercible value" do
assert XSD.PositiveInteger.cast("42") == XSD.positive_integer(42)
assert XSD.PositiveInteger.cast(3.14) == XSD.positive_integer(3)
assert XSD.PositiveInteger.cast(true) == XSD.positive_integer(1)
assert XSD.PositiveInteger.cast(false) == nil
end
test "with non-coercible value" do
assert XSD.PositiveInteger.cast(:foo) == nil
assert XSD.PositiveInteger.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.ShortTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Short,
name: "short",
base: RDF.XSD.Int,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: -32768,
max_inclusive: 32767
},
valid: RDF.XSD.TestData.valid_shorts(),
invalid: RDF.XSD.TestData.invalid_shorts()
end

View file

@ -0,0 +1,130 @@
defmodule RDF.XSD.StringTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.String,
name: "string",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
"foo" => {"foo", nil, "foo"},
0 => {"0", nil, "0"},
42 => {"42", nil, "42"},
3.14 => {"3.14", nil, "3.14"},
true => {"true", nil, "true"},
false => {"false", nil, "false"}
},
invalid: []
describe "cast/1" do
test "casting a string returns the input as it is" do
assert XSD.string("foo") |> XSD.String.cast() == XSD.string("foo")
end
test "casting an integer" do
assert XSD.integer(0) |> XSD.String.cast() == XSD.string("0")
assert XSD.integer(1) |> XSD.String.cast() == XSD.string("1")
end
test "casting a boolean" do
assert XSD.false() |> XSD.String.cast() == XSD.string("false")
assert XSD.true() |> XSD.String.cast() == XSD.string("true")
end
test "casting a decimal" do
assert XSD.decimal(0) |> XSD.String.cast() == XSD.string("0")
assert XSD.decimal(1.0) |> XSD.String.cast() == XSD.string("1")
assert XSD.decimal(3.14) |> XSD.String.cast() == XSD.string("3.14")
end
test "casting a double" do
assert XSD.double(0) |> XSD.String.cast() == XSD.string("0")
assert XSD.double(0.0) |> XSD.String.cast() == XSD.string("0")
assert XSD.double("+0") |> XSD.String.cast() == XSD.string("0")
assert XSD.double("-0") |> XSD.String.cast() == XSD.string("-0")
assert XSD.double(0.1) |> XSD.String.cast() == XSD.string("0.1")
assert XSD.double(3.14) |> XSD.String.cast() == XSD.string("3.14")
assert XSD.double(0.000_001) |> XSD.String.cast() == XSD.string("0.000001")
assert XSD.double(123_456) |> XSD.String.cast() == XSD.string("123456")
assert XSD.double(1_234_567) |> XSD.String.cast() == XSD.string("1.234567E6")
assert XSD.double(0.0000001) |> XSD.String.cast() == XSD.string("1.0E-7")
assert XSD.double(1.0e-10) |> XSD.String.cast() == XSD.string("1.0E-10")
assert XSD.double("1.0e-10") |> XSD.String.cast() == XSD.string("1.0E-10")
assert XSD.double(1.26743223e15) |> XSD.String.cast() == XSD.string("1.26743223E15")
assert XSD.double(:nan) |> XSD.String.cast() == XSD.string("NaN")
assert XSD.double(:positive_infinity) |> XSD.String.cast() == XSD.string("INF")
assert XSD.double(:negative_infinity) |> XSD.String.cast() == XSD.string("-INF")
end
test "casting a float" do
assert XSD.float(0) |> XSD.String.cast() == XSD.string("0")
assert XSD.float(0.0) |> XSD.String.cast() == XSD.string("0")
assert XSD.float("+0") |> XSD.String.cast() == XSD.string("0")
assert XSD.float("-0") |> XSD.String.cast() == XSD.string("-0")
assert XSD.float(0.1) |> XSD.String.cast() == XSD.string("0.1")
assert XSD.float(3.14) |> XSD.String.cast() == XSD.string("3.14")
assert XSD.float(0.000_001) |> XSD.String.cast() == XSD.string("0.000001")
assert XSD.float(123_456) |> XSD.String.cast() == XSD.string("123456")
assert XSD.float(1_234_567) |> XSD.String.cast() == XSD.string("1.234567E6")
assert XSD.float(0.0000001) |> XSD.String.cast() == XSD.string("1.0E-7")
assert XSD.float(1.0e-10) |> XSD.String.cast() == XSD.string("1.0E-10")
assert XSD.float("1.0e-10") |> XSD.String.cast() == XSD.string("1.0E-10")
assert XSD.float(1.26743223e15) |> XSD.String.cast() == XSD.string("1.26743223E15")
assert XSD.float(:nan) |> XSD.String.cast() == XSD.string("NaN")
assert XSD.float(:positive_infinity) |> XSD.String.cast() == XSD.string("INF")
assert XSD.float(:negative_infinity) |> XSD.String.cast() == XSD.string("-INF")
end
test "casting a datetime" do
assert XSD.datetime(~N[2010-01-01T12:34:56]) |> XSD.String.cast() ==
XSD.string("2010-01-01T12:34:56")
assert XSD.datetime("2010-01-01T00:00:00+00:00") |> XSD.String.cast() ==
XSD.string("2010-01-01T00:00:00Z")
assert XSD.datetime("2010-01-01T01:00:00+01:00") |> XSD.String.cast() ==
XSD.string("2010-01-01T01:00:00+01:00")
assert XSD.datetime("2010-01-01 01:00:00+01:00") |> XSD.String.cast() ==
XSD.string("2010-01-01T01:00:00+01:00")
end
test "casting a date" do
assert XSD.date(~D[2000-01-01]) |> XSD.String.cast() == XSD.string("2000-01-01")
assert XSD.date("2000-01-01") |> XSD.String.cast() == XSD.string("2000-01-01")
assert XSD.date("2000-01-01+00:00") |> XSD.String.cast() == XSD.string("2000-01-01Z")
assert XSD.date("2000-01-01+01:00") |> XSD.String.cast() == XSD.string("2000-01-01+01:00")
assert XSD.date("0001-01-01") |> XSD.String.cast() == XSD.string("0001-01-01")
unless Version.compare(System.version(), "1.7.2") == :lt do
assert XSD.date("-0001-01-01") |> XSD.String.cast() == XSD.string("-0001-01-01")
end
end
test "casting a time" do
assert XSD.time(~T[00:00:00]) |> XSD.String.cast() == XSD.string("00:00:00")
assert XSD.time("00:00:00") |> XSD.String.cast() == XSD.string("00:00:00")
assert XSD.time("00:00:00Z") |> XSD.String.cast() == XSD.string("00:00:00Z")
assert XSD.time("00:00:00+00:00") |> XSD.String.cast() == XSD.string("00:00:00Z")
assert XSD.time("00:00:00+01:00") |> XSD.String.cast() == XSD.string("00:00:00+01:00")
end
test "with invalid literals" do
assert XSD.integer(3.14) |> XSD.String.cast() == nil
assert XSD.decimal("NAN") |> XSD.String.cast() == nil
assert XSD.double(true) |> XSD.String.cast() == nil
end
test "with coercible value" do
assert XSD.String.cast(42) == XSD.string("42")
assert XSD.String.cast(3.14) == XSD.string("3.14")
assert XSD.String.cast(true) == XSD.string("true")
assert XSD.String.cast(false) == XSD.string("false")
end
test "with non-coercible value" do
assert XSD.String.cast(:foo) == nil
assert XSD.String.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,151 @@
defmodule RDF.XSD.TimeTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.Time,
name: "time",
primitive: true,
valid: %{
# input => { value, lexical, canonicalized }
~T[00:00:00] => {~T[00:00:00], nil, "00:00:00"},
~T[00:00:00.123] => {~T[00:00:00.123], nil, "00:00:00.123"},
"00:00:00" => {~T[00:00:00], nil, "00:00:00"},
"00:00:00.123" => {~T[00:00:00.123], nil, "00:00:00.123"},
"00:00:00Z" => {{~T[00:00:00], true}, nil, "00:00:00Z"},
"00:00:00.1234Z" => {{~T[00:00:00.1234], true}, nil, "00:00:00.1234Z"},
"00:00:00.0000Z" => {{~T[00:00:00.0000], true}, nil, "00:00:00.0000Z"},
"00:00:00+00:00" => {{~T[00:00:00], true}, "00:00:00+00:00", "00:00:00Z"},
"00:00:00-00:00" => {{~T[00:00:00], true}, "00:00:00-00:00", "00:00:00Z"},
"01:00:00+01:00" => {{~T[00:00:00], true}, "01:00:00+01:00", "00:00:00Z"},
"23:00:00-01:00" => {{~T[00:00:00], true}, "23:00:00-01:00", "00:00:00Z"},
"23:00:00.45-01:00" => {{~T[00:00:00.45], true}, "23:00:00.45-01:00", "00:00:00.45Z"}
},
invalid: [
"foo",
"+2010-01-01Z",
"2010-01-01TFOO",
"02010-01-01",
"2010-1-1",
"0000-01-01",
"2011-07",
"2011",
true,
false,
2010,
3.14,
"00:00:00Z foo",
"foo 00:00:00Z",
# this value representation is just internal and not accepted as
{~T[00:00:00], true},
{~T[00:00:00], "Z"}
]
describe "new/2" do
test "with date and tz opt" do
assert XSD.Time.new("12:00:00", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[11:00:00], true},
uncanonical_lexical: "12:00:00+01:00"
}}
assert XSD.Time.new(~T[12:00:00], tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[11:00:00], true},
uncanonical_lexical: "12:00:00+01:00"
}}
assert XSD.Time.new("12:00:00", tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[12:00:00], true},
uncanonical_lexical: "12:00:00+00:00"
}}
assert XSD.Time.new(~T[12:00:00], tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[12:00:00], true},
uncanonical_lexical: "12:00:00+00:00"
}}
end
test "with date string including a timezone and tz opt" do
assert XSD.Time.new("12:00:00+00:00", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[11:00:00], true},
uncanonical_lexical: "12:00:00+01:00"
}}
assert XSD.Time.new("12:00:00+01:00", tz: "Z") ==
%RDF.Literal{literal: %XSD.Time{value: {~T[12:00:00], true}}}
assert XSD.Time.new("12:00:00+01:00", tz: "+00:00") ==
%RDF.Literal{literal: %XSD.Time{
value: {~T[12:00:00], true},
uncanonical_lexical: "12:00:00+00:00"
}}
end
test "with invalid tz opt" do
assert XSD.Time.new(~T[12:00:00], tz: "+01:00:42") ==
%RDF.Literal{literal: %XSD.Time{uncanonical_lexical: "12:00:00+01:00:42"}}
assert XSD.Time.new("12:00:00:foo", tz: "+01:00") ==
%RDF.Literal{literal: %XSD.Time{uncanonical_lexical: "12:00:00:foo"}}
assert XSD.Time.new("12:00:00", tz: "+01:00:42") ==
%RDF.Literal{literal: %XSD.Time{uncanonical_lexical: "12:00:00"}}
assert XSD.Time.new("12:00:00+00:00:", tz: "+01:00:") ==
%RDF.Literal{literal: %XSD.Time{uncanonical_lexical: "12:00:00+00:00:"}}
end
end
describe "cast/1" do
test "casting a time returns the input as it is" do
assert XSD.time("01:00:00") |> XSD.Time.cast() ==
XSD.time("01:00:00")
end
test "casting a string" do
assert XSD.string("01:00:00") |> XSD.Time.cast() ==
XSD.time("01:00:00")
assert XSD.string("01:00:00Z") |> XSD.Time.cast() ==
XSD.time("01:00:00Z")
assert XSD.string("01:00:00+01:00") |> XSD.Time.cast() ==
XSD.time("01:00:00+01:00")
end
test "casting a datetime" do
assert XSD.datetime("2010-01-01T01:00:00") |> XSD.Time.cast() ==
XSD.time("01:00:00")
assert XSD.datetime("2010-01-01T00:00:00Z") |> XSD.Time.cast() ==
XSD.time("00:00:00Z")
assert XSD.datetime("2010-01-01T00:00:00+00:00") |> XSD.Time.cast() ==
XSD.time("00:00:00Z")
assert XSD.datetime("2010-01-01T23:00:00+01:00") |> XSD.Time.cast() ==
XSD.time("23:00:00+01:00")
end
test "with invalid literals" do
assert XSD.time("25:00:00") |> XSD.Time.cast() == nil
assert XSD.datetime("02010-01-01T00:00:00") |> XSD.Time.cast() == nil
end
test "with literals of unsupported datatypes" do
assert XSD.false() |> XSD.Time.cast() == nil
assert XSD.integer(1) |> XSD.Time.cast() == nil
assert XSD.decimal(3.14) |> XSD.Time.cast() == nil
end
test "with coercible value" do
assert XSD.Time.cast("01:00:00") == XSD.time("01:00:00")
end
test "with non-coercible value" do
assert XSD.Time.cast(:foo) == nil
assert XSD.Time.cast(make_ref()) == nil
end
end
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.UnsignedByteTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.UnsignedByte,
name: "unsignedByte",
base: RDF.XSD.UnsignedShort,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 0,
max_inclusive: 255
},
valid: RDF.XSD.TestData.valid_unsigned_bytes(),
invalid: RDF.XSD.TestData.invalid_unsigned_bytes()
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.UnsignedIntTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.UnsignedInt,
name: "unsignedInt",
base: RDF.XSD.UnsignedLong,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 0,
max_inclusive: 4_294_967_295
},
valid: RDF.XSD.TestData.valid_unsigned_ints(),
invalid: RDF.XSD.TestData.invalid_unsigned_ints()
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.UnsignedLongTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.UnsignedLong,
name: "unsignedLong",
base: RDF.XSD.NonNegativeInteger,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 0,
max_inclusive: 18_446_744_073_709_551_615
},
valid: RDF.XSD.TestData.valid_unsigned_longs(),
invalid: RDF.XSD.TestData.invalid_unsigned_longs()
end

View file

@ -0,0 +1,15 @@
defmodule RDF.XSD.UnsignedShortTest do
use RDF.XSD.Datatype.Test.Case,
datatype: RDF.XSD.UnsignedShort,
name: "unsignedShort",
base: RDF.XSD.UnsignedInt,
base_primitive: RDF.XSD.Integer,
comparable_datatypes: [RDF.XSD.Decimal, RDF.XSD.Double],
applicable_facets: [RDF.XSD.Facets.MinInclusive, RDF.XSD.Facets.MaxInclusive],
facets: %{
min_inclusive: 0,
max_inclusive: 65535
},
valid: RDF.XSD.TestData.valid_unsigned_shorts(),
invalid: RDF.XSD.TestData.invalid_unsigned_shorts()
end

View file

@ -0,0 +1,67 @@
defmodule RDF.XSD.Utils.RegexTest do
use ExUnit.Case
alias RDF.XSD.Utils.Regex
@poem """
<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
[
{"abracadabra", "bra", true},
{"abracadabra", "^a.*a$", true},
{"abracadabra", "^bra", false},
{@poem, "Kaum.*krähen", false},
{@poem, "^Kaum.*gesehen,$", false},
{"foobar", "foo$", false},
{~S"noe\u0308l", ~S"noe\\u0308l", true},
{~S"noe\\u0308l", ~S"noe\\\\u0308l", true},
{~S"\u{01D4B8}", ~S"\\U0001D4B8", true},
{~S"\\U0001D4B8", ~S"\\\U0001D4B8", true},
{42, "4", true},
{42, "en", false}
]
|> Enum.each(fn {literal, pattern, expected_result} ->
result = Regex.matches?(literal, pattern)
assert result == expected_result,
"expected XSD.Regex.matches?(#{inspect(literal)}, #{inspect(pattern)}) to return #{
inspect(expected_result)
}, but got #{result}"
end)
end
test "with flags" do
[
{@poem, "Kaum.*krähen", "s", true},
{@poem, "^Kaum.*gesehen,$", "m", true},
{@poem, "kiki", "i", true}
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Regex.matches?(literal, pattern, flags) == result
end)
end
test "with q flag" do
[
{"abcd", ".*", "q", false},
{"Mr. B. Obama", "B. OBAMA", "iq", true},
# If the q flag is used together with the m, s, or x flag, that flag has no effect.
{"abcd", ".*", "mq", true},
{"abcd", ".*", "qim", true},
{"abcd", ".*", "xqm", true}
]
|> Enum.each(fn {literal, pattern, flags, result} ->
assert Regex.matches?(literal, pattern, flags) == result
end)
end
end
end

View file

@ -0,0 +1,17 @@
defmodule RDF.XSDTest do
use RDF.Test.Case
doctest RDF.XSD
test "Datatype constructor alias functions" do
Enum.each(XSD.datatypes(), fn datatype ->
assert apply(XSD, String.to_atom(datatype.name), [1]) == datatype.new(1)
assert apply(XSD, String.to_atom(Macro.underscore(datatype.name)), [1]) == datatype.new(1)
end)
end
test "true and false aliases" do
assert XSD.true == XSD.Boolean.new(true)
assert XSD.false == XSD.Boolean.new(false)
end
end