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:
parent
adb1c12634
commit
5819eec0cf
94 changed files with 6990 additions and 1337 deletions
34
lib/rdf.ex
34
lib/rdf.ex
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
83
lib/rdf/xsd.ex
Normal 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
310
lib/rdf/xsd/datatype.ex
Normal 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
|
105
lib/rdf/xsd/datatype/primitive.ex
Normal file
105
lib/rdf/xsd/datatype/primitive.ex
Normal 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
|
99
lib/rdf/xsd/datatype/restriction.ex
Normal file
99
lib/rdf/xsd/datatype/restriction.ex
Normal 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
|
22
lib/rdf/xsd/datatypes/any_uri.ex
Normal file
22
lib/rdf/xsd/datatypes/any_uri.ex
Normal 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
|
232
lib/rdf/xsd/datatypes/boolean.ex
Normal file
232
lib/rdf/xsd/datatypes/boolean.ex
Normal 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
|
|
@ -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)
|
9
lib/rdf/xsd/datatypes/byte.ex
Normal file
9
lib/rdf/xsd/datatypes/byte.ex
Normal 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
|
231
lib/rdf/xsd/datatypes/date.ex
Normal file
231
lib/rdf/xsd/datatypes/date.ex
Normal 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
|
223
lib/rdf/xsd/datatypes/date_time.ex
Normal file
223
lib/rdf/xsd/datatypes/date_time.ex
Normal 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
|
152
lib/rdf/xsd/datatypes/decimal.ex
Normal file
152
lib/rdf/xsd/datatypes/decimal.ex
Normal 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
|
125
lib/rdf/xsd/datatypes/double.ex
Normal file
125
lib/rdf/xsd/datatypes/double.ex
Normal 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
|
13
lib/rdf/xsd/datatypes/float.ex
Normal file
13
lib/rdf/xsd/datatypes/float.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/int.ex
Normal file
9
lib/rdf/xsd/datatypes/int.ex
Normal 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
|
85
lib/rdf/xsd/datatypes/integer.ex
Normal file
85
lib/rdf/xsd/datatypes/integer.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/long.ex
Normal file
9
lib/rdf/xsd/datatypes/long.ex
Normal 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
|
8
lib/rdf/xsd/datatypes/negative_integer.ex
Normal file
8
lib/rdf/xsd/datatypes/negative_integer.ex
Normal 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
|
8
lib/rdf/xsd/datatypes/non_negative_integer.ex
Normal file
8
lib/rdf/xsd/datatypes/non_negative_integer.ex
Normal 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
|
8
lib/rdf/xsd/datatypes/non_positive_integer.ex
Normal file
8
lib/rdf/xsd/datatypes/non_positive_integer.ex
Normal 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
|
613
lib/rdf/xsd/datatypes/numeric.ex
Normal file
613
lib/rdf/xsd/datatypes/numeric.ex
Normal 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
|
8
lib/rdf/xsd/datatypes/positive_integer.ex
Normal file
8
lib/rdf/xsd/datatypes/positive_integer.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/short.ex
Normal file
9
lib/rdf/xsd/datatypes/short.ex
Normal 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
|
78
lib/rdf/xsd/datatypes/string.ex
Normal file
78
lib/rdf/xsd/datatypes/string.ex
Normal 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
|
217
lib/rdf/xsd/datatypes/time.ex
Normal file
217
lib/rdf/xsd/datatypes/time.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/unsigned_byte.ex
Normal file
9
lib/rdf/xsd/datatypes/unsigned_byte.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/unsigned_int.ex
Normal file
9
lib/rdf/xsd/datatypes/unsigned_int.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/unsigned_long.ex
Normal file
9
lib/rdf/xsd/datatypes/unsigned_long.ex
Normal 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
|
9
lib/rdf/xsd/datatypes/unsigned_short.ex
Normal file
9
lib/rdf/xsd/datatypes/unsigned_short.ex
Normal 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
113
lib/rdf/xsd/facet.ex
Normal 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
|
3
lib/rdf/xsd/facets/max_inclusive.ex
Normal file
3
lib/rdf/xsd/facets/max_inclusive.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule RDF.XSD.Facets.MaxInclusive do
|
||||||
|
use RDF.XSD.Facet, name: :max_inclusive, type: integer
|
||||||
|
end
|
3
lib/rdf/xsd/facets/min_inclusive.ex
Normal file
3
lib/rdf/xsd/facets/min_inclusive.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule RDF.XSD.Facets.MinInclusive do
|
||||||
|
use RDF.XSD.Facet, name: :min_inclusive, type: integer
|
||||||
|
end
|
18
lib/rdf/xsd/utils/date_time.ex
Normal file
18
lib/rdf/xsd/utils/date_time.ex
Normal 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
|
86
lib/rdf/xsd/utils/regex.ex
Normal file
86
lib/rdf/xsd/utils/regex.ex
Normal 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
|
1
mix.exs
1
mix.exs
|
@ -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},
|
||||||
|
|
|
@ -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]
|
||||||
|
|
284
test/support/xsd_datatype_case.ex
Normal file
284
test/support/xsd_datatype_case.ex
Normal 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
|
187
test/support/xsd_test_data.ex
Normal file
187
test/support/xsd_test_data.ex
Normal 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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
5
test/unit/literal/datatype_test.exs
Normal file
5
test/unit/literal/datatype_test.exs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule RDF.Literal.DatatypeTest do
|
||||||
|
use RDF.Test.Case
|
||||||
|
|
||||||
|
doctest RDF.Literal.Datatype
|
||||||
|
end
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
318
test/unit/xsd/comparison_test.exs
Normal file
318
test/unit/xsd/comparison_test.exs
Normal 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
|
18
test/unit/xsd/datatype_test.exs
Normal file
18
test/unit/xsd/datatype_test.exs
Normal 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
|
14
test/unit/xsd/datatypes/any_uri_test.exs
Normal file
14
test/unit/xsd/datatypes/any_uri_test.exs
Normal 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
|
238
test/unit/xsd/datatypes/boolean_test.exs
Normal file
238
test/unit/xsd/datatypes/boolean_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/byte_test.exs
Normal file
15
test/unit/xsd/datatypes/byte_test.exs
Normal 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
|
140
test/unit/xsd/datatypes/date_test.exs
Normal file
140
test/unit/xsd/datatypes/date_test.exs
Normal 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
|
194
test/unit/xsd/datatypes/date_time_test.exs
Normal file
194
test/unit/xsd/datatypes/date_time_test.exs
Normal 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
|
184
test/unit/xsd/datatypes/decimal_test.exs
Normal file
184
test/unit/xsd/datatypes/decimal_test.exs
Normal 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
|
69
test/unit/xsd/datatypes/double_test.exs
Normal file
69
test/unit/xsd/datatypes/double_test.exs
Normal 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
|
12
test/unit/xsd/datatypes/float_test.exs
Normal file
12
test/unit/xsd/datatypes/float_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/int_test.exs
Normal file
15
test/unit/xsd/datatypes/int_test.exs
Normal 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
|
107
test/unit/xsd/datatypes/integer_test.exs
Normal file
107
test/unit/xsd/datatypes/integer_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/long_test.exs
Normal file
15
test/unit/xsd/datatypes/long_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/negative_integer_test.exs
Normal file
15
test/unit/xsd/datatypes/negative_integer_test.exs
Normal 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
|
116
test/unit/xsd/datatypes/non_negative_integer_test.exs
Normal file
116
test/unit/xsd/datatypes/non_negative_integer_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/non_positive_integer_test.exs
Normal file
15
test/unit/xsd/datatypes/non_positive_integer_test.exs
Normal 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
|
571
test/unit/xsd/datatypes/numeric_test.exs
Normal file
571
test/unit/xsd/datatypes/numeric_test.exs
Normal 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
|
126
test/unit/xsd/datatypes/positive_integer_test.exs
Normal file
126
test/unit/xsd/datatypes/positive_integer_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/short_test.exs
Normal file
15
test/unit/xsd/datatypes/short_test.exs
Normal 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
|
130
test/unit/xsd/datatypes/string_test.exs
Normal file
130
test/unit/xsd/datatypes/string_test.exs
Normal 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
|
151
test/unit/xsd/datatypes/time_test.exs
Normal file
151
test/unit/xsd/datatypes/time_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/unsigned_byte_test.exs
Normal file
15
test/unit/xsd/datatypes/unsigned_byte_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/unsigned_int_test.exs
Normal file
15
test/unit/xsd/datatypes/unsigned_int_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/unsigned_long_test.exs
Normal file
15
test/unit/xsd/datatypes/unsigned_long_test.exs
Normal 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
|
15
test/unit/xsd/datatypes/unsigned_short_test.exs
Normal file
15
test/unit/xsd/datatypes/unsigned_short_test.exs
Normal 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
|
67
test/unit/xsd/utils/regex_test.exs
Normal file
67
test/unit/xsd/utils/regex_test.exs
Normal 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
|
17
test/unit/xsd/xsd_test.exs
Normal file
17
test/unit/xsd/xsd_test.exs
Normal 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
|
Loading…
Reference in a new issue