Revision of the literal system with extracted XSD datatypes
This commit is contained in:
parent
c4791af2ad
commit
f6146c51b0
64 changed files with 1790 additions and 5866 deletions
6
.iex.exs
6
.iex.exs
|
@ -1,7 +1,7 @@
|
|||
import RDF.Sigils
|
||||
import RDF.Literal.Guards
|
||||
|
||||
alias RDF.NS.{XSD, RDFS, OWL, SKOS}
|
||||
alias RDF.NS
|
||||
alias RDF.NS.{RDFS, OWL, SKOS}
|
||||
|
||||
alias RDF.{
|
||||
Term,
|
||||
|
@ -16,6 +16,8 @@ alias RDF.{
|
|||
Description,
|
||||
Graph,
|
||||
Dataset,
|
||||
|
||||
PrefixMap,
|
||||
}
|
||||
|
||||
alias RDF.BlankNode, as: BNode
|
||||
|
|
44
lib/rdf.ex
44
lib/rdf.ex
|
@ -41,13 +41,15 @@ defmodule RDF do
|
|||
alias RDF.{IRI, Namespace, Literal, BlankNode, Triple, Quad,
|
||||
Description, Graph, Dataset, PrefixMap}
|
||||
|
||||
import RDF.Utils.Bootstrapping
|
||||
|
||||
defdelegate default_base_iri(), to: RDF.IRI, as: :default_base
|
||||
|
||||
|
||||
@standard_prefixes PrefixMap.new(
|
||||
xsd: IRI.new("http://www.w3.org/2001/XMLSchema#"),
|
||||
rdf: IRI.new("http://www.w3.org/1999/02/22-rdf-syntax-ns#"),
|
||||
rdfs: IRI.new("http://www.w3.org/2000/01/rdf-schema#")
|
||||
xsd: IRI.new(XSD.namespace()),
|
||||
rdf: rdf_iri_base(),
|
||||
rdfs: rdfs_iri_base()
|
||||
)
|
||||
|
||||
@doc """
|
||||
|
@ -235,30 +237,18 @@ defmodule RDF do
|
|||
def list(head, %Graph{} = graph), do: RDF.List.new(head, graph)
|
||||
def list(native_list, opts), do: RDF.List.from(native_list, opts)
|
||||
|
||||
defdelegate string(value), to: RDF.String, as: :new
|
||||
defdelegate string(value, opts), to: RDF.String, as: :new
|
||||
for datatype <- RDF.Literal.Datatype.Registry.datatypes() do
|
||||
defdelegate unquote(String.to_atom(datatype.name))(value), to: datatype, as: :new
|
||||
end
|
||||
|
||||
defdelegate lang_string(value), to: RDF.LangString, as: :new
|
||||
defdelegate lang_string(value, opts), to: RDF.LangString, as: :new
|
||||
defdelegate langString(value), to: RDF.LangString, as: :new
|
||||
defdelegate langString(value, opts), to: RDF.LangString, as: :new
|
||||
defdelegate boolean(value), to: RDF.Boolean, as: :new
|
||||
defdelegate boolean(value, opts), to: RDF.Boolean, as: :new
|
||||
defdelegate integer(value), to: RDF.Integer, as: :new
|
||||
defdelegate integer(value, opts), to: RDF.Integer, as: :new
|
||||
defdelegate double(value), to: RDF.Double, as: :new
|
||||
defdelegate double(value, opts), to: RDF.Double, as: :new
|
||||
defdelegate decimal(value), to: RDF.Decimal, as: :new
|
||||
defdelegate decimal(value, opts), to: RDF.Decimal, as: :new
|
||||
defdelegate date(value), to: RDF.Date, as: :new
|
||||
defdelegate date(value, opts), to: RDF.Date, as: :new
|
||||
defdelegate time(value), to: RDF.Time, as: :new
|
||||
defdelegate time(value, opts), to: RDF.Time, as: :new
|
||||
defdelegate date_time(value), to: RDF.DateTime, as: :new
|
||||
defdelegate date_time(value, opts), to: RDF.DateTime, as: :new
|
||||
defdelegate dateTime(value), to: RDF.DateTime, as: :new
|
||||
defdelegate dateTime(value, opts), to: RDF.DateTime, as: :new
|
||||
defdelegate datetime(value), to: RDF.DateTime, as: :new
|
||||
defdelegate datetime(value, opts), to: RDF.DateTime, as: :new
|
||||
defdelegate date_time(value), to: RDF.XSD.DateTime, as: :new
|
||||
defdelegate date_time(value, opts), to: RDF.XSD.DateTime, as: :new
|
||||
defdelegate datetime(value), to: RDF.XSD.DateTime, as: :new
|
||||
defdelegate datetime(value, opts), to: RDF.XSD.DateTime, as: :new
|
||||
defdelegate any_uri(value), to: RDF.XSD.AnyURI, as: :new
|
||||
defdelegate any_uri(value, opts), to: RDF.XSD.AnyURI, as: :new
|
||||
|
||||
defdelegate prefix_map(prefixes), to: RDF.PrefixMap, as: :new
|
||||
|
||||
|
@ -271,8 +261,8 @@ defmodule RDF do
|
|||
defdelegate unquote(term)(s, o1, o2, o3, o4, o5), to: RDF.NS.RDF
|
||||
end
|
||||
|
||||
defdelegate unquote(:true)(), to: RDF.Boolean.Value
|
||||
defdelegate unquote(:false)(), to: RDF.Boolean.Value
|
||||
defdelegate unquote(:true)(), to: RDF.XSD.Boolean.Value
|
||||
defdelegate unquote(:false)(), to: RDF.XSD.Boolean.Value
|
||||
|
||||
defdelegate langString(), to: RDF.NS.RDF
|
||||
defdelegate unquote(nil)(), to: RDF.NS.RDF
|
||||
|
|
|
@ -1,314 +0,0 @@
|
|||
defmodule RDF.Datatype do
|
||||
@moduledoc """
|
||||
A behaviour for natively supported literal datatypes.
|
||||
|
||||
A `RDF.Datatype` implements the foundational functions for the lexical form,
|
||||
the validation, conversion and canonicalization of typed `RDF.Literal`s.
|
||||
"""
|
||||
|
||||
alias RDF.{IRI, Literal}
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@type t :: module
|
||||
|
||||
@doc """
|
||||
The IRI of the datatype.
|
||||
"""
|
||||
@callback id :: IRI.t
|
||||
|
||||
@doc """
|
||||
Produces the lexical form of a `RDF.Literal`.
|
||||
"""
|
||||
@callback lexical(Literal.t) :: any
|
||||
|
||||
@doc """
|
||||
Produces the lexical form of a value.
|
||||
"""
|
||||
@callback canonical_lexical(Literal.literal_value) :: String.t | nil
|
||||
|
||||
@doc """
|
||||
Produces the lexical form of an invalid value of a typed Literal.
|
||||
|
||||
The default implementation of the `_using__` macro just returns `to_string`
|
||||
representation of the value.
|
||||
"""
|
||||
@callback invalid_lexical(any) :: String.t | nil
|
||||
|
||||
@doc """
|
||||
Produces the canonical form of a `RDF.Literal`.
|
||||
"""
|
||||
@callback canonical(Literal.t) :: Literal.t | nil
|
||||
|
||||
@doc """
|
||||
Converts a value into a proper native value.
|
||||
|
||||
If an invalid value is given an implementation should call `super`, which
|
||||
by default currently just returns `nil`.
|
||||
|
||||
Note: If a value is valid is determined by the lexical space of the implemented
|
||||
datatype, not by the Elixir semantics. For example, although `42`
|
||||
is a falsy value according to the Elixir semantics, this is not an element
|
||||
of the lexical value space of an `xsd:boolean`, so the `RDF.Boolean`
|
||||
implementation of this datatype calls `super`.
|
||||
"""
|
||||
@callback convert(any, map) :: any
|
||||
|
||||
|
||||
@doc """
|
||||
Casts a literal of another datatype into a literal of the datatype the function is implemented on.
|
||||
|
||||
If the given literal is invalid or can not be converted into this datatype
|
||||
`nil` is returned.
|
||||
"""
|
||||
@callback cast(Literal.t | any) :: Literal.t | nil
|
||||
|
||||
|
||||
@doc """
|
||||
Determines if the value of a `RDF.Literal` is a member of lexical value space of its datatype.
|
||||
"""
|
||||
@callback valid?(Literal.t) :: boolean | nil
|
||||
|
||||
@doc """
|
||||
Checks if the value of two `RDF.Literal`s of this datatype are equal.
|
||||
|
||||
Non-RDF terms are tried to be coerced via `RDF.Term.coerce/1` before comparison.
|
||||
|
||||
Returns `nil` when the given arguments are not comparable as literals of this datatype.
|
||||
|
||||
The default implementation of the `_using__` macro compares the values of the
|
||||
`canonical/1` forms of the given literals of this datatype.
|
||||
"""
|
||||
@callback equal_value?(Literal.t, Literal.t) :: boolean | nil
|
||||
|
||||
@doc """
|
||||
Compares two `RDF.Literal`s.
|
||||
|
||||
Returns `:gt` if first literal is greater than the second in terms of their datatype
|
||||
and `:lt` for vice versa. If the two literals are equal `:eq` is returned.
|
||||
For datatypes with only partial ordering `:indeterminate` is returned when the
|
||||
order of the given literals is not defined.
|
||||
|
||||
Returns `nil` when the given arguments are not comparable datatypes or if one
|
||||
them is invalid.
|
||||
|
||||
The default implementation of the `_using__` macro compares the values of the
|
||||
`canonical/1` forms of the given literals of this datatype.
|
||||
"""
|
||||
@callback compare(Literal.t, Literal.t) :: :lt | :gt | :eq | :indeterminate | nil
|
||||
|
||||
|
||||
@lang_string RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
|
||||
|
||||
# TODO: This mapping should be created dynamically and be extendable, to allow user-defined datatypes ...
|
||||
@mapping %{
|
||||
@lang_string => RDF.LangString,
|
||||
XSD.string => RDF.String,
|
||||
XSD.integer => RDF.Integer,
|
||||
XSD.double => RDF.Double,
|
||||
XSD.decimal => RDF.Decimal,
|
||||
XSD.boolean => RDF.Boolean,
|
||||
XSD.date => RDF.Date,
|
||||
XSD.time => RDF.Time,
|
||||
XSD.dateTime => RDF.DateTime,
|
||||
}
|
||||
|
||||
@doc """
|
||||
The mapping of IRIs of datatypes to their `RDF.Datatype`.
|
||||
"""
|
||||
@spec mapping :: %{IRI.t => t}
|
||||
def mapping, do: @mapping
|
||||
|
||||
@doc """
|
||||
The IRIs of all datatypes with a `RDF.Datatype` defined.
|
||||
"""
|
||||
@spec ids :: [IRI.t]
|
||||
def ids, do: Map.keys(@mapping)
|
||||
|
||||
@doc """
|
||||
All defined `RDF.Datatype` modules.
|
||||
"""
|
||||
@spec modules :: [t]
|
||||
def modules, do: Map.values(@mapping)
|
||||
|
||||
@doc """
|
||||
Returns the `RDF.Datatype` for a directly datatype IRI or the datatype IRI of a `RDF.Literal`.
|
||||
"""
|
||||
@spec get(Literal.t | IRI.t) :: t
|
||||
def get(%Literal{datatype: id}), do: get(id)
|
||||
def get(id), do: @mapping[id]
|
||||
|
||||
|
||||
defmacro __using__(opts) do
|
||||
id = Keyword.fetch!(opts, :id)
|
||||
quote bind_quoted: [], unquote: true do
|
||||
@behaviour unquote(__MODULE__)
|
||||
|
||||
alias RDF.Literal
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@id unquote(id)
|
||||
@impl unquote(__MODULE__)
|
||||
def id, do: @id
|
||||
|
||||
|
||||
@spec new(any, map | keyword) :: Literal.t
|
||||
def new(value, opts \\ %{})
|
||||
|
||||
def new(value, opts) when is_list(opts),
|
||||
do: new(value, Map.new(opts))
|
||||
def new(value, opts) when is_binary(value),
|
||||
do: build_literal_by_lexical(value, opts)
|
||||
def new(value, opts),
|
||||
do: build_literal_by_value(value, opts)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@dialyzer {:nowarn_function, build_literal_by_value: 2}
|
||||
@spec build_literal_by_value(any, map) :: Literal.t
|
||||
def build_literal_by_value(value, opts) do
|
||||
case convert(value, opts) do
|
||||
nil ->
|
||||
build_literal(nil, invalid_lexical(value), opts)
|
||||
converted_value ->
|
||||
build_literal(converted_value, nil, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@dialyzer {:nowarn_function, build_literal_by_lexical: 2}
|
||||
def build_literal_by_lexical(lexical, opts) do
|
||||
case convert(lexical, opts) do
|
||||
nil ->
|
||||
build_literal(nil, lexical, opts)
|
||||
value ->
|
||||
if opts[:canonicalize] || lexical == canonical_lexical(value) do
|
||||
build_literal(value, nil, opts)
|
||||
else
|
||||
build_literal(value, lexical, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec build_literal(Literal.literal_value | nil, String.t | nil, map) :: Literal.t
|
||||
def build_literal(value, lexical, %{canonicalize: true} = opts) do
|
||||
build_literal(value, lexical, Map.delete(opts, :canonicalize))
|
||||
|> canonical
|
||||
end
|
||||
|
||||
def build_literal(value, lexical, opts) do
|
||||
%Literal{value: value, uncanonical_lexical: lexical, datatype: @id}
|
||||
end
|
||||
|
||||
|
||||
def convert(value, _), do: nil
|
||||
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def lexical(literal)
|
||||
|
||||
def lexical(%Literal{value: value, uncanonical_lexical: nil}) do
|
||||
canonical_lexical(value)
|
||||
end
|
||||
|
||||
def lexical(%Literal{uncanonical_lexical: lexical}) do
|
||||
lexical
|
||||
end
|
||||
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def canonical_lexical(value), do: to_string(value)
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def invalid_lexical(value), do: to_string(value)
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def canonical(literal)
|
||||
def canonical(%Literal{value: nil} = literal), do: literal
|
||||
def canonical(%Literal{uncanonical_lexical: nil} = literal), do: literal
|
||||
def canonical(%Literal{} = literal) do
|
||||
%Literal{literal | uncanonical_lexical: nil}
|
||||
end
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def valid?(literal)
|
||||
def valid?(%Literal{value: nil}), do: false
|
||||
def valid?(%Literal{datatype: @id}), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def equal_value?(literal1, literal2)
|
||||
|
||||
def equal_value?(%Literal{uncanonical_lexical: lexical1, datatype: @id, value: nil},
|
||||
%Literal{uncanonical_lexical: lexical2, datatype: @id}) do
|
||||
lexical1 == lexical2
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id} = literal1, %Literal{datatype: @id} = literal2) do
|
||||
canonical(literal1).value == canonical(literal2).value
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{} = left, right) when not is_nil(right) do
|
||||
unless RDF.Term.term?(right) do
|
||||
equal_value?(left, RDF.Term.coerce(right))
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(_, _), do: nil
|
||||
|
||||
|
||||
def less_than?(literal1, literal2), do: Literal.less_than?(literal1, literal2)
|
||||
|
||||
def greater_than?(literal1, literal2), do: Literal.greater_than?(literal1, literal2)
|
||||
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%Literal{datatype: @id, value: value1} = literal1,
|
||||
%Literal{datatype: @id, value: value2} = literal2)
|
||||
when not (is_nil(value1) or is_nil(value2))
|
||||
do
|
||||
case {canonical(literal1).value, canonical(literal2).value} do
|
||||
{value1, value2} when value1 < value2 -> :lt
|
||||
{value1, value2} when value1 > value2 -> :gt
|
||||
_ ->
|
||||
if equal_value?(literal1, literal2), do: :eq
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
|
||||
|
||||
def validate_cast(%Literal{} = literal) do
|
||||
if valid?(literal), do: literal
|
||||
end
|
||||
|
||||
def validate_cast(_), do: nil
|
||||
|
||||
|
||||
defoverridable [
|
||||
build_literal_by_value: 2,
|
||||
build_literal_by_lexical: 2,
|
||||
build_literal: 3,
|
||||
lexical: 1,
|
||||
canonical_lexical: 1,
|
||||
invalid_lexical: 1,
|
||||
convert: 2,
|
||||
valid?: 1,
|
||||
equal_value?: 2,
|
||||
compare: 2,
|
||||
new: 2,
|
||||
new!: 2,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,263 +0,0 @@
|
|||
defmodule RDF.Boolean do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD boolean.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.boolean
|
||||
|
||||
import Literal.Guards
|
||||
|
||||
@type value :: boolean
|
||||
@type input :: value | String.t | number | Literal.t
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(value, _) when is_boolean(value), do: value
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
with value do
|
||||
cond do
|
||||
value in ~W[true 1] -> true
|
||||
value in ~W[false 0] -> false
|
||||
true ->
|
||||
super(value, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def convert(1, _), do: true
|
||||
def convert(0, _), do: false
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_boolean(datatype) ->
|
||||
literal
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|> canonical()
|
||||
|> validate_cast()
|
||||
|
||||
is_xsd_decimal(datatype) ->
|
||||
!Decimal.equal?(literal.value, 0)
|
||||
|> new()
|
||||
|
||||
RDF.Numeric.type?(datatype) ->
|
||||
literal.value not in [0, 0.0, :nan]
|
||||
|> new()
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Returns `RDF.true` if the effective boolean value of the given argument is `RDF.false`, or `RDF.false` if it is `RDF.true`.
|
||||
|
||||
Otherwise it returns `nil`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Boolean.fn_not(RDF.true)
|
||||
RDF.false
|
||||
iex> RDF.Boolean.fn_not(RDF.false)
|
||||
RDF.true
|
||||
|
||||
iex> RDF.Boolean.fn_not(true)
|
||||
RDF.false
|
||||
iex> RDF.Boolean.fn_not(false)
|
||||
RDF.true
|
||||
|
||||
iex> RDF.Boolean.fn_not(42)
|
||||
RDF.false
|
||||
iex> RDF.Boolean.fn_not("")
|
||||
RDF.true
|
||||
|
||||
iex> RDF.Boolean.fn_not(nil)
|
||||
nil
|
||||
|
||||
see <https://www.w3.org/TR/xpath-functions/#func-not>
|
||||
"""
|
||||
@spec fn_not(input | any) :: Literal.t | nil
|
||||
def fn_not(value) do
|
||||
case ebv(value) do
|
||||
%Literal{value: true} -> RDF.Boolean.Value.false
|
||||
%Literal{value: false} -> RDF.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.true` and `RDF.false` if the other argument is `RDF.false`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Boolean.logical_and(RDF.true, RDF.true)
|
||||
RDF.true
|
||||
iex> RDF.Boolean.logical_and(RDF.true, RDF.false)
|
||||
RDF.false
|
||||
|
||||
iex> RDF.Boolean.logical_and(RDF.true, nil)
|
||||
nil
|
||||
iex> RDF.Boolean.logical_and(nil, RDF.false)
|
||||
RDF.false
|
||||
iex> RDF.Boolean.logical_and(nil, nil)
|
||||
nil
|
||||
|
||||
see <https://www.w3.org/TR/sparql11-query/#func-logical-and>
|
||||
|
||||
"""
|
||||
@spec logical_and(input | any, input | any) :: Literal.t | nil
|
||||
def logical_and(left, right) do
|
||||
case ebv(left) do
|
||||
%Literal{value: false} ->
|
||||
RDF.false
|
||||
|
||||
%Literal{value: true} ->
|
||||
case ebv(right) do
|
||||
%Literal{value: true} -> RDF.true
|
||||
%Literal{value: false} -> RDF.false
|
||||
nil -> nil
|
||||
end
|
||||
|
||||
nil ->
|
||||
if match?(%Literal{value: false}, ebv(right)) do
|
||||
RDF.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.false` and `RDF.true` if the other argument is `RDF.true`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Boolean.logical_or(RDF.true, RDF.false)
|
||||
RDF.true
|
||||
iex> RDF.Boolean.logical_or(RDF.false, RDF.false)
|
||||
RDF.false
|
||||
|
||||
iex> RDF.Boolean.logical_or(RDF.true, nil)
|
||||
RDF.true
|
||||
iex> RDF.Boolean.logical_or(nil, RDF.false)
|
||||
nil
|
||||
iex> RDF.Boolean.logical_or(nil, nil)
|
||||
nil
|
||||
|
||||
see <https://www.w3.org/TR/sparql11-query/#func-logical-or>
|
||||
|
||||
"""
|
||||
@spec logical_or(input | any, input | any) :: Literal.t | nil
|
||||
def logical_or(left, right) do
|
||||
case ebv(left) do
|
||||
%Literal{value: true} ->
|
||||
RDF.true
|
||||
|
||||
%Literal{value: false} ->
|
||||
case ebv(right) do
|
||||
%Literal{value: true} -> RDF.true
|
||||
%Literal{value: false} -> RDF.false
|
||||
nil -> nil
|
||||
end
|
||||
|
||||
nil ->
|
||||
if match?(%Literal{value: true}, ebv(right)) do
|
||||
RDF.true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@xsd_boolean RDF.Datatype.NS.XSD.boolean
|
||||
|
||||
@doc """
|
||||
Returns an Effective Boolean Value (EBV).
|
||||
|
||||
The Effective Boolean Value is an algorithm to coerce values to a `RDF.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 | any) :: Literal.t | nil
|
||||
def ebv(value)
|
||||
|
||||
def ebv(true), do: RDF.Boolean.Value.true
|
||||
def ebv(false), do: RDF.Boolean.Value.false
|
||||
|
||||
def ebv(%Literal{value: nil, datatype: @xsd_boolean}), do: RDF.Boolean.Value.false
|
||||
def ebv(%Literal{datatype: @xsd_boolean} = literal), do: literal
|
||||
|
||||
def ebv(%Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
RDF.Numeric.type?(datatype) ->
|
||||
if Literal.valid?(literal) and
|
||||
not (literal.value == 0 or literal.value == :nan),
|
||||
do: RDF.Boolean.Value.true,
|
||||
else: RDF.Boolean.Value.false
|
||||
|
||||
Literal.plain?(literal) ->
|
||||
if String.length(literal.value) == 0,
|
||||
do: RDF.Boolean.Value.false,
|
||||
else: RDF.Boolean.Value.true
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def ebv(value) when is_binary(value) or is_number(value) do
|
||||
value |> Literal.new() |> ebv()
|
||||
end
|
||||
|
||||
def ebv(_), do: nil
|
||||
|
||||
@doc """
|
||||
Alias for `ebv/1`.
|
||||
"""
|
||||
@spec effective(input | any) :: Literal.t | nil
|
||||
def effective(value), do: ebv(value)
|
||||
|
||||
end
|
||||
|
||||
defmodule RDF.Boolean.Value do
|
||||
@moduledoc !"""
|
||||
This module holds the two boolean value literals, so they can be accessed
|
||||
directly without needing to construct them every time. They can't
|
||||
be defined in the RDF.Boolean module, because we can not use the
|
||||
`RDF.Boolean.new` function without having it compiled first.
|
||||
"""
|
||||
|
||||
@xsd_true RDF.Boolean.new(true)
|
||||
@xsd_false RDF.Boolean.new(false)
|
||||
|
||||
def unquote(:true)(), do: @xsd_true
|
||||
def unquote(:false)(), do: @xsd_false
|
||||
end
|
|
@ -1,184 +0,0 @@
|
|||
defmodule RDF.Date do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD date.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.date
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@type value :: Date.t | {Date.t, String.t}
|
||||
@type input :: value | String.t
|
||||
|
||||
@grammar ~r/\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
|
||||
@xsd_datetime RDF.Datatype.NS.XSD.dateTime
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(%Date{} = value, %{tz: "+00:00"} = opts) do
|
||||
{convert(value, Map.delete(opts, :tz)), "Z"}
|
||||
end
|
||||
|
||||
def convert(%Date{} = value, %{tz: tz} = opts) do
|
||||
{convert(value, Map.delete(opts, :tz)), tz}
|
||||
end
|
||||
|
||||
# Special case for date and dateTime, for which 0 is not a valid year
|
||||
def convert(%Date{year: 0} = value, opts), do: super(value, opts)
|
||||
def convert(%Date{} = value, _), do: value
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
case Regex.run(@grammar, value) do
|
||||
[_, date] ->
|
||||
date
|
||||
|> do_convert
|
||||
|> convert(opts)
|
||||
[_, date, zone] ->
|
||||
date
|
||||
|> do_convert
|
||||
|> convert(Map.put(opts, :tz, zone))
|
||||
_ ->
|
||||
super(value, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
defp do_convert(value) do
|
||||
case Date.from_iso8601(value) do
|
||||
{:ok, date} -> date
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec canonical_lexical(value) :: String.t
|
||||
def canonical_lexical(value)
|
||||
|
||||
def canonical_lexical(%Date{} = value) do
|
||||
Date.to_iso8601(value)
|
||||
end
|
||||
|
||||
def canonical_lexical({%Date{} = value, zone}) do
|
||||
canonical_lexical(value) <> zone
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_date(datatype) ->
|
||||
literal
|
||||
|
||||
is_xsd_datetime(datatype) ->
|
||||
case literal.value do
|
||||
%NaiveDateTime{} = datetime ->
|
||||
datetime
|
||||
|> NaiveDateTime.to_date()
|
||||
|> new()
|
||||
|
||||
%DateTime{} = datetime ->
|
||||
datetime
|
||||
|> DateTime.to_date()
|
||||
|> new(%{tz: RDF.DateTime.tz(literal)})
|
||||
end
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def equal_value?(literal1, literal2)
|
||||
|
||||
def equal_value?(%Literal{datatype: @id, value: nil, uncanonical_lexical: lexical1},
|
||||
%Literal{datatype: @id, value: nil, uncanonical_lexical: lexical2}) do
|
||||
lexical1 == lexical2
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id, value: value1},
|
||||
%Literal{datatype: @id, value: value2})
|
||||
when is_nil(value1) or is_nil(value2), do: false
|
||||
|
||||
def equal_value?(%Literal{datatype: @id, value: value1},
|
||||
%Literal{datatype: @id, value: value2}) do
|
||||
RDF.DateTime.equal_value?(
|
||||
comparison_normalization(value1),
|
||||
comparison_normalization(value2)
|
||||
)
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id}, %Literal{datatype: @xsd_datetime}), do: false
|
||||
def equal_value?(%Literal{datatype: @xsd_datetime}, %Literal{datatype: @id}), do: false
|
||||
|
||||
def equal_value?(_, _), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%Literal{datatype: @id, value: value1},
|
||||
%Literal{datatype: @id, value: value2})
|
||||
when is_nil(value1) or is_nil(value2), do: nil
|
||||
|
||||
def compare(%Literal{datatype: @id, value: value1},
|
||||
%Literal{datatype: @id, value: value2}) do
|
||||
RDF.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(%Literal{datatype: @id, value: date_value},
|
||||
# %Literal{datatype: @xsd_datetime} = datetime_literal) do
|
||||
# RDF.DateTime.compare(
|
||||
# comparison_normalization(date_value),
|
||||
# datetime_literal
|
||||
# )
|
||||
# end
|
||||
#
|
||||
# def compare(%Literal{datatype: @xsd_datetime} = datetime_literal,
|
||||
# %Literal{datatype: @id, value: date_value}) do
|
||||
# RDF.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.DateTime.new()
|
||||
end
|
||||
|
||||
defp comparison_normalization(%Date{} = date) do
|
||||
(Date.to_iso8601(date) <> "T00:00:00")
|
||||
|> RDF.DateTime.new()
|
||||
end
|
||||
|
||||
defp comparison_normalization(_), do: nil
|
||||
|
||||
end
|
|
@ -1,251 +0,0 @@
|
|||
defmodule RDF.DateTime do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD dateTime.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.dateTime
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@type value :: DateTime.t | NaiveDateTime.t
|
||||
@type input :: value | String.t
|
||||
|
||||
@xsd_date RDF.Datatype.NS.XSD.date
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
# Special case for date and dateTime, for which 0 is not a valid year
|
||||
def convert(%DateTime{year: 0} = value, opts), do: super(value, opts)
|
||||
def convert(%DateTime{} = value, _), do: value
|
||||
|
||||
# Special case for date and dateTime, for which 0 is not a valid year
|
||||
def convert(%NaiveDateTime{year: 0} = value, opts), do: super(value, opts)
|
||||
def convert(%NaiveDateTime{} = value, _), do: value
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
case DateTime.from_iso8601(value) do
|
||||
{:ok, datetime, _} -> convert(datetime, opts)
|
||||
|
||||
{:error, :missing_offset} ->
|
||||
case NaiveDateTime.from_iso8601(value) do
|
||||
{:ok, datetime} -> convert(datetime, opts)
|
||||
_ -> super(value, opts)
|
||||
end
|
||||
|
||||
{:error, :invalid_format} ->
|
||||
if String.ends_with?(value, "-00:00") do
|
||||
String.replace_trailing(value, "-00:00", "Z")
|
||||
|> convert(opts)
|
||||
else
|
||||
super(value, opts)
|
||||
end
|
||||
|
||||
{:error, :invalid_time} ->
|
||||
if String.contains?(value, "T24:00:00") do
|
||||
with [day, tz] <- String.split(value, "T24:00:00", parts: 2),
|
||||
{:ok, day} <- Date.from_iso8601(day)
|
||||
do
|
||||
"#{day |> Date.add(1) |> Date.to_string()}T00:00:00#{tz}"
|
||||
|> convert(opts)
|
||||
else
|
||||
_ -> super(value, opts)
|
||||
end
|
||||
|
||||
else
|
||||
super(value, opts)
|
||||
end
|
||||
|
||||
_ ->
|
||||
super(value, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec canonical_lexical(value) :: String.t
|
||||
def canonical_lexical(value)
|
||||
|
||||
def canonical_lexical(%DateTime{} = value) do
|
||||
DateTime.to_iso8601(value)
|
||||
end
|
||||
|
||||
def canonical_lexical(%NaiveDateTime{} = value) do
|
||||
NaiveDateTime.to_iso8601(value)
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_datetime(datatype) ->
|
||||
literal
|
||||
|
||||
is_xsd_date(datatype) ->
|
||||
case literal.value do
|
||||
{value, zone} ->
|
||||
RDF.Date.canonical_lexical(value) <> "T00:00:00" <> zone
|
||||
value ->
|
||||
RDF.Date.canonical_lexical(value) <> "T00:00:00"
|
||||
end
|
||||
|> new()
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|> validate_cast()
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Builds a `RDF.DateTime` literal for current moment in time.
|
||||
"""
|
||||
@spec now :: Literal.t
|
||||
def now() do
|
||||
new(DateTime.utc_now())
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Extracts the timezone string from a `RDF.DateTime` literal.
|
||||
"""
|
||||
@spec tz(Literal.t) :: String.t | nil
|
||||
def tz(literal)
|
||||
|
||||
def tz(%Literal{value: %NaiveDateTime{}}), do: ""
|
||||
|
||||
def tz(date_time_literal) do
|
||||
if valid?(date_time_literal) do
|
||||
date_time_literal
|
||||
|> lexical()
|
||||
|> RDF.DateTimeUtils.tz()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Converts a datetime literal to a canonical string, preserving the zone information.
|
||||
"""
|
||||
@spec canonical_lexical_with_zone(Literal.t) :: String.t | nil
|
||||
def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal)
|
||||
when is_xsd_datetime(datatype) do
|
||||
case tz(literal) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zone when zone in ["Z", "", "+00:00", "-00:00"] ->
|
||||
canonical_lexical(literal.value)
|
||||
|
||||
zone ->
|
||||
literal
|
||||
|> lexical()
|
||||
|> String.replace_trailing(zone, "Z")
|
||||
|> DateTime.from_iso8601()
|
||||
|> elem(1)
|
||||
|> canonical_lexical()
|
||||
|> String.replace_trailing("Z", zone)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def equal_value?(literal1, literal2)
|
||||
|
||||
def equal_value?(%Literal{datatype: @id, value: %type{} = value1},
|
||||
%Literal{datatype: @id, value: %type{} = value2})
|
||||
do
|
||||
type.compare(value1, value2) == :eq
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id, value: nil, uncanonical_lexical: lexical1},
|
||||
%Literal{datatype: @id, value: nil, uncanonical_lexical: lexical2}) do
|
||||
lexical1 == lexical2
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id} = literal1, %Literal{datatype: @id} = literal2) do
|
||||
case compare(literal1, literal2) do
|
||||
:lt -> false
|
||||
:gt -> false
|
||||
:eq -> true # This actually can't/shouldn't happen.
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: @id}, %Literal{datatype: @xsd_date}), do: false
|
||||
def equal_value?(%Literal{datatype: @xsd_date}, %Literal{datatype: @id}), do: false
|
||||
|
||||
def equal_value?(%RDF.Literal{} = left, right) when not is_nil(right) do
|
||||
unless RDF.Term.term?(right) do
|
||||
equal_value?(left, RDF.Term.coerce(right))
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(_, _), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%Literal{datatype: @id, value: %type{} = value1},
|
||||
%Literal{datatype: @id, 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(%Literal{datatype: @id} = literal1,
|
||||
# %Literal{datatype: @xsd_date} = literal2) do
|
||||
# RDF.Date.compare(literal1, literal2)
|
||||
# end
|
||||
#
|
||||
# def compare(%Literal{datatype: @xsd_date} = literal1,
|
||||
# %Literal{datatype: @id} = literal2) do
|
||||
# RDF.Date.compare(literal1, literal2)
|
||||
# end
|
||||
|
||||
def compare(%Literal{datatype: @id, value: %DateTime{}} = literal1,
|
||||
%Literal{datatype: @id, value: %NaiveDateTime{} = value2}) do
|
||||
cond do
|
||||
compare(literal1, new(to_datetime(value2, "+"))) == :lt -> :lt
|
||||
compare(literal1, new(to_datetime(value2, "-"))) == :gt -> :gt
|
||||
true -> :indeterminate
|
||||
end
|
||||
end
|
||||
|
||||
def compare(%Literal{datatype: @id, value: %NaiveDateTime{} = value1},
|
||||
%Literal{datatype: @id, value: %DateTime{}} = literal2) do
|
||||
cond do
|
||||
compare(new(to_datetime(value1, "-")), literal2) == :lt -> :lt
|
||||
compare(new(to_datetime(value1, "+")), literal2) == :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
|
|
@ -1,18 +0,0 @@
|
|||
defmodule RDF.DateTimeUtils 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
|
|
@ -1,157 +0,0 @@
|
|||
defmodule RDF.Decimal do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD decimal.
|
||||
"""
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.decimal
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
alias Elixir.Decimal, as: D
|
||||
|
||||
@type value :: Decimal.t
|
||||
@type input :: value | number | String.t
|
||||
|
||||
@xsd_integer RDF.Datatype.NS.XSD.integer
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(%D{coef: coef} = value, opts) when coef in ~w[qNaN sNaN inf]a,
|
||||
do: super(value, opts)
|
||||
|
||||
def convert(%D{} = decimal, _),
|
||||
do: canonical_decimal(decimal)
|
||||
|
||||
def convert(value, opts) when is_integer(value),
|
||||
do: value |> D.new() |> convert(opts)
|
||||
|
||||
def convert(value, opts) when is_float(value),
|
||||
do: value |> D.from_float() |> convert(opts)
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
if String.contains?(value, ~w[e E]) do
|
||||
super(value, opts)
|
||||
else
|
||||
case D.parse(value) do
|
||||
{:ok, decimal} -> convert(decimal, opts)
|
||||
:error -> super(value, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def canonical_lexical(value)
|
||||
|
||||
def canonical_lexical(%D{sign: sign, coef: :qNaN}),
|
||||
do: if sign == 1, do: "NaN", else: "-NaN"
|
||||
|
||||
def canonical_lexical(%D{sign: sign, coef: :sNaN}),
|
||||
do: if sign == 1, do: "sNaN", else: "-sNaN"
|
||||
|
||||
def canonical_lexical(%D{sign: sign, coef: :inf}),
|
||||
do: if sign == 1, do: "Infinity", else: "-Infinity"
|
||||
|
||||
def canonical_lexical(%D{} = decimal),
|
||||
do: D.to_string(decimal, :normal)
|
||||
|
||||
|
||||
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.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_decimal(datatype) ->
|
||||
literal
|
||||
|
||||
literal == RDF.false ->
|
||||
new(0.0)
|
||||
|
||||
literal == RDF.true ->
|
||||
new(1.0)
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|> canonical()
|
||||
|> validate_cast()
|
||||
|
||||
is_number(literal.value) and (is_xsd_integer(datatype) or
|
||||
is_xsd_double(datatype) or is_xsd_float(datatype)) ->
|
||||
new(literal.value)
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right), do: RDF.Numeric.compare(left, right)
|
||||
|
||||
@doc """
|
||||
The number of digits in the XML Schema canonical form of the literal value.
|
||||
"""
|
||||
@spec digit_count(Literal.t) :: non_neg_integer
|
||||
def digit_count(%RDF.Literal{datatype: @id} = literal) do
|
||||
if valid?(literal) do
|
||||
literal
|
||||
|> canonical()
|
||||
|> lexical()
|
||||
|> String.replace(".", "")
|
||||
|> String.replace("-", "")
|
||||
|> String.length()
|
||||
end
|
||||
end
|
||||
|
||||
def digit_count(%RDF.Literal{datatype: @xsd_integer} = literal),
|
||||
do: RDF.Integer.digit_count(literal)
|
||||
|
||||
@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(Literal.t) :: non_neg_integer
|
||||
def fraction_digit_count(%RDF.Literal{datatype: @id} = literal) do
|
||||
if valid?(literal) do
|
||||
[_, fraction] =
|
||||
literal
|
||||
|> canonical()
|
||||
|> lexical()
|
||||
|> String.split(".")
|
||||
String.length(fraction)
|
||||
end
|
||||
end
|
||||
|
||||
def fraction_digit_count(%RDF.Literal{datatype: @xsd_integer}), do: 0
|
||||
|
||||
end
|
|
@ -1,151 +0,0 @@
|
|||
defmodule RDF.Double do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD double.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.double
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@type value :: float | :positive_infinity | :negative_infinity | :nan
|
||||
@type input :: value | number | String.t
|
||||
|
||||
|
||||
def build_literal_by_value(value, opts) do
|
||||
case convert(value, opts) do
|
||||
float when is_float(float) ->
|
||||
build_literal(float, decimal_form(float), opts)
|
||||
nil ->
|
||||
build_literal(nil, invalid_lexical(value), opts)
|
||||
special_value when is_atom(special_value) ->
|
||||
build_literal(special_value, nil, opts)
|
||||
end
|
||||
end
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(value, _) when is_float(value), do: value
|
||||
|
||||
def convert(value, _) when is_integer(value), do: value / 1
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
case Float.parse(value) 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
|
||||
convert(to_string(float) <> String.trim_leading(remainder, "."), opts)
|
||||
else
|
||||
super(value, opts)
|
||||
end
|
||||
:error ->
|
||||
case String.upcase(value) do
|
||||
"INF" -> :positive_infinity
|
||||
"-INF" -> :negative_infinity
|
||||
"NAN" -> :nan
|
||||
_ -> super(value, opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, _)
|
||||
when value in ~W[positive_infinity negative_infinity nan]a,
|
||||
do: value
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec canonical_lexical(value) :: String.t
|
||||
def canonical_lexical(value)
|
||||
|
||||
def canonical_lexical(:nan), do: "NaN"
|
||||
def canonical_lexical(:positive_infinity), do: "INF"
|
||||
def canonical_lexical(:negative_infinity), do: "-INF"
|
||||
def canonical_lexical(float) when is_float(float), do: exponential_form(float)
|
||||
def canonical_lexical(value), do: to_string(value)
|
||||
|
||||
|
||||
defp decimal_form(float) when is_float(float) do
|
||||
to_string(float)
|
||||
end
|
||||
|
||||
defp exponential_form(float) when is_float(float) do
|
||||
# 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]/)
|
||||
f =
|
||||
case String.replace(f, ~r/0*$/, "", global: false) do # remove any trailing zeroes
|
||||
"" -> "0" # ...but there must be a digit to the right of the decimal point
|
||||
f -> f
|
||||
end
|
||||
e = String.trim_leading(e, "+")
|
||||
"#{i}.#{f}E#{e}"
|
||||
end
|
||||
|
||||
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.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_double(datatype) ->
|
||||
literal
|
||||
|
||||
literal == RDF.false ->
|
||||
new(0.0)
|
||||
|
||||
literal == RDF.true ->
|
||||
new(1.0)
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|> canonical()
|
||||
|> validate_cast()
|
||||
|
||||
is_xsd_decimal(datatype) ->
|
||||
literal.value
|
||||
|> Decimal.to_float()
|
||||
|> new()
|
||||
|
||||
is_xsd_integer(datatype) or is_xsd_float(datatype) ->
|
||||
new(literal.value)
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right), do: RDF.Numeric.compare(left, right)
|
||||
|
||||
end
|
|
@ -1,95 +0,0 @@
|
|||
defmodule RDF.Integer do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD integer.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.integer
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@type value :: integer
|
||||
@type input :: value | String.t
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(value, _) when is_integer(value), do: value
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
case Integer.parse(value) do
|
||||
{integer, ""} -> integer
|
||||
{_, _} -> super(value, opts)
|
||||
:error -> super(value, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_integer(datatype) ->
|
||||
literal
|
||||
|
||||
literal == RDF.false ->
|
||||
new(0)
|
||||
|
||||
literal == RDF.true ->
|
||||
new(1)
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|> canonical()
|
||||
|> validate_cast()
|
||||
|
||||
is_xsd_decimal(datatype) ->
|
||||
literal.value
|
||||
|> Decimal.round(0, :down)
|
||||
|> Decimal.to_integer()
|
||||
|> new()
|
||||
|
||||
is_float(literal.value) and
|
||||
(is_xsd_double(datatype) or is_xsd_float(datatype)) ->
|
||||
literal.value
|
||||
|> trunc()
|
||||
|> new()
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right)
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right), do: RDF.Numeric.compare(left, right)
|
||||
|
||||
|
||||
@doc """
|
||||
The number of digits in the XML Schema canonical form of the literal value.
|
||||
"""
|
||||
@spec digit_count(Literal.t) :: non_neg_integer
|
||||
def digit_count(%RDF.Literal{datatype: @id} = literal) do
|
||||
if valid?(literal) do
|
||||
literal
|
||||
|> canonical()
|
||||
|> lexical()
|
||||
|> String.replace("-", "")
|
||||
|> String.length()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,85 +0,0 @@
|
|||
defmodule RDF.LangString do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for RDF langString.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
|
||||
|
||||
@type value :: String.t
|
||||
|
||||
|
||||
def build_literal(value, lexical, %{language: language} = opts)
|
||||
when is_binary(language) and language != "" do
|
||||
%Literal{super(value, lexical, opts) | language: String.downcase(language)}
|
||||
end
|
||||
|
||||
def build_literal(value, lexical, opts) do
|
||||
super(value, lexical, opts)
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(any, map) :: value
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def valid?(literal)
|
||||
def valid?(%Literal{language: nil}), do: false
|
||||
def valid?(literal), do: super(literal)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(_) do
|
||||
nil
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if a language tagged string literal or language tag matches a language range.
|
||||
|
||||
The check is performed per the basic filtering scheme defined in
|
||||
[RFC4647](http://www.ietf.org/rfc/rfc4647.txt) section 3.3.1.
|
||||
A language range is a basic language range per _Matching of Language Tags_ in
|
||||
RFC4647 section 2.1.
|
||||
A language range of `"*"` matches any non-empty language-tag string.
|
||||
|
||||
see <https://www.w3.org/TR/sparql11-query/#func-langMatches>
|
||||
"""
|
||||
@spec match_language?(Literal.t | String.t, String.t) :: boolean
|
||||
def match_language?(language_tag, language_range)
|
||||
|
||||
def match_language?(%Literal{language: nil}, _), do: false
|
||||
def match_language?(%Literal{language: language_tag}, language_range),
|
||||
do: match_language?(language_tag, language_range)
|
||||
|
||||
def match_language?("", "*"), do: false
|
||||
def match_language?(_, "*"), do: true
|
||||
|
||||
def match_language?(language_tag, language_range) do
|
||||
language_tag = String.downcase(language_tag)
|
||||
language_range = String.downcase(language_range)
|
||||
|
||||
case String.split(language_tag, language_range, parts: 2) do
|
||||
[_, rest] -> rest == "" or String.starts_with?(rest, "-")
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
@impl RDF.Datatype
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%Literal{datatype: @id, value: value1, language: language_tag} = literal1,
|
||||
%Literal{datatype: @id, value: value2, language: language_tag} = literal2)
|
||||
when not (is_nil(value1) or is_nil(value2))
|
||||
do
|
||||
case {canonical(literal1).value, canonical(literal2).value} do
|
||||
{value1, value2} when value1 < value2 -> :lt
|
||||
{value1, value2} when value1 > value2 -> :gt
|
||||
_ ->
|
||||
if equal_value?(literal1, literal2), do: :eq
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
end
|
|
@ -1,570 +0,0 @@
|
|||
defmodule RDF.Numeric do
|
||||
@moduledoc """
|
||||
The set of all numeric datatypes.
|
||||
"""
|
||||
|
||||
alias RDF.Literal
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
alias Elixir.Decimal, as: D
|
||||
|
||||
import RDF.Literal.Guards
|
||||
import Kernel, except: [abs: 1, floor: 1, ceil: 1]
|
||||
|
||||
@type value :: RDF.Decimal.value | RDF.Integer.value | RDF.Double.value
|
||||
|
||||
@types MapSet.new [
|
||||
XSD.integer,
|
||||
XSD.decimal,
|
||||
XSD.float,
|
||||
XSD.double,
|
||||
XSD.nonPositiveInteger,
|
||||
XSD.negativeInteger,
|
||||
XSD.long,
|
||||
XSD.int,
|
||||
XSD.short,
|
||||
XSD.byte,
|
||||
XSD.nonNegativeInteger,
|
||||
XSD.unsignedLong,
|
||||
XSD.unsignedInt,
|
||||
XSD.unsignedShort,
|
||||
XSD.unsignedByte,
|
||||
XSD.positiveInteger,
|
||||
]
|
||||
|
||||
|
||||
@doc """
|
||||
The list of all numeric datatypes.
|
||||
"""
|
||||
@dialyzer {:nowarn_function, types: 0}
|
||||
def types(), do: MapSet.to_list(@types)
|
||||
|
||||
@doc """
|
||||
Returns if a given datatype is a numeric datatype.
|
||||
"""
|
||||
@dialyzer {:nowarn_function, type?: 1}
|
||||
def type?(type), do: MapSet.member?(@types, type)
|
||||
|
||||
@doc """
|
||||
Returns if a given literal has a numeric datatype.
|
||||
"""
|
||||
@spec literal?(Literal.t | any) :: boolean
|
||||
def literal?(%Literal{datatype: datatype}), do: type?(datatype)
|
||||
def literal?(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Tests for numeric value equality of two numeric literals.
|
||||
|
||||
Returns `nil` when the given arguments are not comparable as numeric literals.
|
||||
|
||||
see:
|
||||
|
||||
- <https://www.w3.org/TR/sparql11-query/#OperatorMapping>
|
||||
- <https://www.w3.org/TR/xpath-functions/#func-numeric-equal>
|
||||
"""
|
||||
def equal_value?(left, right)
|
||||
|
||||
def equal_value?(%Literal{uncanonical_lexical: lexical1, datatype: dt, value: nil},
|
||||
%Literal{uncanonical_lexical: lexical2, datatype: dt}) do
|
||||
lexical1 == lexical2
|
||||
end
|
||||
|
||||
def equal_value?(%Literal{datatype: left_datatype, value: left},
|
||||
%Literal{datatype: right_datatype, value: right})
|
||||
when is_xsd_decimal(left_datatype) or is_xsd_decimal(right_datatype),
|
||||
do: equal_decimal_value?(left, right)
|
||||
|
||||
def equal_value?(%Literal{datatype: left_datatype, value: left},
|
||||
%Literal{datatype: right_datatype, value: right}) do
|
||||
if type?(left_datatype) and type?(right_datatype) do
|
||||
left == right
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(%RDF.Literal{} = left, right) when not is_nil(right) do
|
||||
unless RDF.Term.term?(right) do
|
||||
equal_value?(left, RDF.Term.coerce(right))
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(_, _), do: nil
|
||||
|
||||
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: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Compares two numeric `RDF.Literal`s.
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%Literal{datatype: left_datatype, value: left},
|
||||
%Literal{datatype: right_datatype, value: right})
|
||||
when is_xsd_decimal(left_datatype)
|
||||
do
|
||||
if type?(right_datatype) do
|
||||
compare_decimal_value(left, right)
|
||||
end
|
||||
end
|
||||
|
||||
def compare(%Literal{datatype: left_datatype, value: left},
|
||||
%Literal{datatype: right_datatype, value: right})
|
||||
when is_xsd_decimal(right_datatype)
|
||||
do
|
||||
if type?(left_datatype) do
|
||||
compare_decimal_value(left, right)
|
||||
end
|
||||
end
|
||||
|
||||
def compare(%Literal{datatype: left_datatype, value: left},
|
||||
%Literal{datatype: right_datatype, value: right})
|
||||
when not (is_nil(left) or is_nil(right))
|
||||
do
|
||||
if type?(left_datatype) and type?(right_datatype) do
|
||||
cond do
|
||||
left < right -> :lt
|
||||
left > right -> :gt
|
||||
true -> :eq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
|
||||
@dialyzer {:nowarn_function, compare_decimal_value: 2}
|
||||
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
|
||||
|
||||
|
||||
def zero?(%Literal{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
|
||||
|
||||
|
||||
def negative_zero?(%Literal{value: zero, uncanonical_lexical: "-" <> _, datatype: datatype})
|
||||
when zero == 0 and is_xsd_double(datatype), do: true
|
||||
|
||||
def negative_zero?(%Literal{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, `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, `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, `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, `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 [XSD.double] -> nil # TODO: or XSD.float
|
||||
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 argument is not a valid numeric literal `nil` is returned.
|
||||
|
||||
see <http://www.w3.org/TR/xpath-functions/#func-abs>
|
||||
|
||||
"""
|
||||
def abs(literal)
|
||||
|
||||
def abs(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype) do
|
||||
if RDF.Decimal.valid?(literal) do
|
||||
literal.value
|
||||
|> D.abs()
|
||||
|> RDF.Decimal.new()
|
||||
end
|
||||
end
|
||||
|
||||
def abs(%Literal{datatype: datatype} = literal) do
|
||||
if type?(datatype) and Literal.valid?(literal) do
|
||||
case literal.value do
|
||||
:nan -> literal
|
||||
:positive_infinity -> literal
|
||||
:negative_infinity -> Literal.new(:positive_infinity, datatype: datatype)
|
||||
value ->
|
||||
value
|
||||
|> Kernel.abs()
|
||||
|> Literal.new(datatype: datatype)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def abs(value) do
|
||||
if not is_nil(value) and not RDF.Term.term?(value) do
|
||||
value
|
||||
|> RDF.Term.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 argument is not a valid numeric literal `nil` is returned.
|
||||
|
||||
see <http://www.w3.org/TR/xpath-functions/#func-round>
|
||||
|
||||
"""
|
||||
def round(literal, precision \\ 0)
|
||||
|
||||
def round(%Literal{datatype: datatype} = literal, precision) when is_xsd_decimal(datatype) do
|
||||
if RDF.Decimal.valid?(literal) do
|
||||
literal.value
|
||||
|> xpath_round(precision)
|
||||
|> to_string()
|
||||
|> RDF.Decimal.new()
|
||||
end
|
||||
end
|
||||
|
||||
def round(%Literal{datatype: datatype, value: value} = literal, _)
|
||||
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
|
||||
do: literal
|
||||
|
||||
def round(%Literal{datatype: datatype} = literal, precision) when is_xsd_double(datatype) do
|
||||
if RDF.Double.valid?(literal) do
|
||||
literal.value
|
||||
|> new_decimal()
|
||||
|> xpath_round(precision)
|
||||
|> D.to_float()
|
||||
|> RDF.Double.new()
|
||||
end
|
||||
end
|
||||
|
||||
def round(%Literal{datatype: datatype} = literal, precision) do
|
||||
if type?(datatype) and Literal.valid?(literal) do
|
||||
if precision < 0 do
|
||||
literal.value
|
||||
|> new_decimal()
|
||||
|> xpath_round(precision)
|
||||
|> D.to_integer()
|
||||
|> RDF.Integer.new()
|
||||
else
|
||||
literal
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def round(value, precision) do
|
||||
if not is_nil(value) and not RDF.Term.term?(value) do
|
||||
value
|
||||
|> RDF.Term.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 argument is not a valid numeric literal `nil` is returned.
|
||||
|
||||
see <http://www.w3.org/TR/xpath-functions/#func-ceil>
|
||||
|
||||
"""
|
||||
def ceil(literal)
|
||||
|
||||
def ceil(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype)do
|
||||
if RDF.Decimal.valid?(literal) do
|
||||
literal.value
|
||||
|> D.round(0, (if literal.value.sign == -1, do: :down, else: :up))
|
||||
|> D.to_string()
|
||||
|> RDF.Decimal.new()
|
||||
end
|
||||
end
|
||||
|
||||
def ceil(%Literal{datatype: datatype, value: value} = literal)
|
||||
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
|
||||
do: literal
|
||||
|
||||
def ceil(%Literal{datatype: datatype} = literal) when is_xsd_double(datatype) do
|
||||
if RDF.Double.valid?(literal) do
|
||||
literal.value
|
||||
|> Float.ceil()
|
||||
|> trunc()
|
||||
|> to_string()
|
||||
|> RDF.Double.new()
|
||||
end
|
||||
end
|
||||
|
||||
def ceil(%Literal{datatype: datatype} = literal) do
|
||||
if type?(datatype) and Literal.valid?(literal) do
|
||||
literal
|
||||
end
|
||||
end
|
||||
|
||||
def ceil(value) do
|
||||
if not is_nil(value) and not RDF.Term.term?(value) do
|
||||
value
|
||||
|> RDF.Term.coerce()
|
||||
|> ceil()
|
||||
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(literal)
|
||||
|
||||
def floor(%Literal{datatype: datatype} = literal) when is_xsd_decimal(datatype)do
|
||||
if RDF.Decimal.valid?(literal) do
|
||||
literal.value
|
||||
|> D.round(0, (if literal.value.sign == -1, do: :up, else: :down))
|
||||
|> D.to_string()
|
||||
|> RDF.Decimal.new()
|
||||
end
|
||||
end
|
||||
|
||||
def floor(%Literal{datatype: datatype, value: value} = literal)
|
||||
when is_xsd_double(datatype) and value in ~w[nan positive_infinity negative_infinity]a,
|
||||
do: literal
|
||||
|
||||
def floor(%Literal{datatype: datatype} = literal) when is_xsd_double(datatype)do
|
||||
if RDF.Double.valid?(literal) do
|
||||
literal.value
|
||||
|> Float.floor()
|
||||
|> trunc()
|
||||
|> to_string()
|
||||
|> RDF.Double.new()
|
||||
end
|
||||
end
|
||||
|
||||
def floor(%Literal{datatype: datatype} = literal) do
|
||||
if type?(datatype) and Literal.valid?(literal) do
|
||||
literal
|
||||
end
|
||||
end
|
||||
|
||||
def floor(value) do
|
||||
if not is_nil(value) and not RDF.Term.term?(value) do
|
||||
value
|
||||
|> RDF.Term.coerce()
|
||||
|> floor()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp arithmetic_operation(op, %Literal{} = arg1, %Literal{} = arg2, fun) do
|
||||
if literal?(arg1) && literal?(arg2) do
|
||||
with result_type = result_type(op, arg1.datatype, arg2.datatype),
|
||||
{arg1, arg2} = type_conversion(arg1, arg2, result_type),
|
||||
result = fun.(arg1.value, arg2.value, result_type)
|
||||
do
|
||||
unless is_nil(result),
|
||||
do: Literal.new(result, datatype: result_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp arithmetic_operation(op, %Literal{} = arg1, arg2, fun) do
|
||||
if not is_nil(arg2) and not RDF.Term.term?(arg2) do
|
||||
arithmetic_operation(op, arg1, RDF.Term.coerce(arg2), fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp arithmetic_operation(op, arg1, %Literal{} = arg2, fun) do
|
||||
if not is_nil(arg1) and not RDF.Term.term?(arg1) do
|
||||
arithmetic_operation(op, RDF.Term.coerce(arg1), arg2, fun)
|
||||
end
|
||||
end
|
||||
|
||||
defp arithmetic_operation(op, arg1, arg2, fun) do
|
||||
if not is_nil(arg1) and not RDF.Term.term?(arg1) and
|
||||
not is_nil(arg2) and not RDF.Term.term?(arg2) do
|
||||
arithmetic_operation(op, RDF.Term.coerce(arg1), RDF.Term.coerce(arg2), fun)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp type_conversion(%Literal{datatype: datatype} = arg1,
|
||||
%Literal{value: arg2}, datatype) when is_xsd_decimal(datatype),
|
||||
do: {arg1, RDF.decimal(arg2)}
|
||||
|
||||
defp type_conversion(%Literal{value: arg1},
|
||||
%Literal{datatype: datatype} = arg2, datatype)
|
||||
when is_xsd_decimal(datatype),
|
||||
do: {RDF.decimal(arg1), arg2}
|
||||
|
||||
defp type_conversion(%Literal{datatype: input_datatype, value: arg1}, arg2, output_datatype)
|
||||
when is_xsd_decimal(input_datatype) and is_xsd_double(output_datatype),
|
||||
do: {arg1 |> D.to_float() |> RDF.double(), arg2}
|
||||
|
||||
defp type_conversion(arg1, %Literal{datatype: input_datatype, value: arg2}, output_datatype)
|
||||
when is_xsd_decimal(input_datatype) and is_xsd_double(output_datatype),
|
||||
do: {arg1, arg2 |> D.to_float() |> RDF.double()}
|
||||
|
||||
defp type_conversion(arg1, arg2, _), do: {arg1, arg2}
|
||||
|
||||
|
||||
defp result_type(:/, type1, type2) do
|
||||
types = [type1, type2]
|
||||
cond do
|
||||
XSD.double in types -> XSD.double
|
||||
XSD.float in types -> XSD.float
|
||||
true -> XSD.decimal
|
||||
end
|
||||
end
|
||||
|
||||
defp result_type(_, type1, type2) do
|
||||
types = [type1, type2]
|
||||
cond do
|
||||
XSD.double in types -> XSD.double
|
||||
XSD.float in types -> XSD.float
|
||||
XSD.decimal in types -> XSD.decimal
|
||||
true -> XSD.integer
|
||||
end
|
||||
end
|
||||
|
||||
defp new_decimal(value) when is_float(value), do: D.from_float(value)
|
||||
defp new_decimal(value), do: D.new(value)
|
||||
|
||||
end
|
|
@ -1,103 +0,0 @@
|
|||
defmodule RDF.String do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD string.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.string
|
||||
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@type value :: String.t
|
||||
|
||||
|
||||
def new(value, opts) when is_list(opts),
|
||||
do: new(value, Map.new(opts))
|
||||
def new(value, %{language: language} = opts) when not is_nil(language),
|
||||
do: RDF.LangString.new!(value, opts)
|
||||
def new(value, opts),
|
||||
do: super(value, opts)
|
||||
|
||||
def new!(value, opts) when is_list(opts),
|
||||
do: new!(value, Map.new(opts))
|
||||
def new!(value, %{language: language} = opts) when not is_nil(language),
|
||||
do: RDF.LangString.new!(value, opts)
|
||||
def new!(value, opts),
|
||||
do: super(value, opts)
|
||||
|
||||
|
||||
def build_literal_by_lexical(lexical, opts) do
|
||||
build_literal(lexical, nil, opts)
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(any, map) :: value
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.IRI{value: value}), do: new(value)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
datatype == XSD.string ->
|
||||
literal
|
||||
|
||||
datatype == XSD.decimal ->
|
||||
try do
|
||||
literal.value
|
||||
|> Decimal.to_integer()
|
||||
|> RDF.Integer.new()
|
||||
|> cast()
|
||||
rescue
|
||||
_ ->
|
||||
literal.value
|
||||
|> RDF.Decimal.canonical_lexical()
|
||||
|> new()
|
||||
end
|
||||
|
||||
datatype in [XSD.double, XSD.float] ->
|
||||
cond do
|
||||
RDF.Numeric.negative_zero?(literal) ->
|
||||
new("-0")
|
||||
|
||||
RDF.Numeric.zero?(literal) ->
|
||||
new("0")
|
||||
|
||||
literal.value >= 0.000_001 and literal.value < 1000000 ->
|
||||
literal.value
|
||||
|> RDF.Decimal.new()
|
||||
|> cast()
|
||||
|
||||
true ->
|
||||
literal.value
|
||||
|> RDF.Double.canonical_lexical()
|
||||
|> new()
|
||||
end
|
||||
|
||||
datatype == XSD.dateTime ->
|
||||
literal
|
||||
|> RDF.DateTime.canonical_lexical_with_zone()
|
||||
|> new()
|
||||
|
||||
datatype == XSD.time ->
|
||||
literal
|
||||
|> RDF.Time.canonical_lexical_with_zone()
|
||||
|> new()
|
||||
|
||||
true ->
|
||||
literal
|
||||
|> RDF.Literal.canonical()
|
||||
|> RDF.Literal.lexical()
|
||||
|> new()
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
end
|
|
@ -1,158 +0,0 @@
|
|||
defmodule RDF.Time do
|
||||
@moduledoc """
|
||||
`RDF.Datatype` for XSD time.
|
||||
"""
|
||||
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.time
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@type value :: Time.t
|
||||
@type input :: value | String.t
|
||||
|
||||
@grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
|
||||
@tz_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
@spec convert(input | any, map) :: value | nil
|
||||
def convert(value, opts)
|
||||
|
||||
def convert(%Time{} = value, %{tz: tz} = opts) do
|
||||
{convert(value, Map.delete(opts, :tz)), tz}
|
||||
end
|
||||
|
||||
def convert(%Time{} = value, _opts), do: value
|
||||
|
||||
def convert(value, opts) when is_binary(value) do
|
||||
case Regex.run(@grammar, value) do
|
||||
[_, time] ->
|
||||
time
|
||||
|> do_convert
|
||||
|> convert(opts)
|
||||
[_, time, zone] ->
|
||||
time
|
||||
|> do_convert
|
||||
|> with_offset(zone)
|
||||
|> convert(Map.put(opts, :tz, true))
|
||||
_ ->
|
||||
super(value, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
defp do_convert(value) do
|
||||
case Time.from_iso8601(value) do
|
||||
{:ok, time} -> time
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp with_offset(time, zone) when zone in ~W[Z UTC GMT], do: time
|
||||
defp with_offset(time, offset) do
|
||||
{hour, minute} =
|
||||
case Regex.run(@tz_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
|
||||
end
|
||||
%Time{time | hour: hour, minute: minute}
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def canonical_lexical(value)
|
||||
|
||||
def canonical_lexical(%Time{} = value) do
|
||||
Time.to_iso8601(value)
|
||||
end
|
||||
|
||||
def canonical_lexical({%Time{} = value, true}) do
|
||||
canonical_lexical(value) <> "Z"
|
||||
end
|
||||
|
||||
|
||||
@impl RDF.Datatype
|
||||
def cast(literal)
|
||||
|
||||
def cast(%RDF.Literal{datatype: datatype} = literal) do
|
||||
cond do
|
||||
not RDF.Literal.valid?(literal) ->
|
||||
nil
|
||||
|
||||
is_xsd_time(datatype) ->
|
||||
literal
|
||||
|
||||
is_xsd_datetime(datatype) ->
|
||||
case literal.value do
|
||||
%NaiveDateTime{} = datetime ->
|
||||
datetime
|
||||
|> NaiveDateTime.to_time()
|
||||
|> new()
|
||||
|
||||
%DateTime{} ->
|
||||
[_date, time_with_zone] =
|
||||
literal
|
||||
|> RDF.DateTime.canonical_lexical_with_zone()
|
||||
|> String.split("T", parts: 2)
|
||||
new(time_with_zone)
|
||||
end
|
||||
|
||||
is_xsd_string(datatype) ->
|
||||
literal.value
|
||||
|> new()
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Extracts the timezone string from a `RDF.Time` literal.
|
||||
"""
|
||||
@spec tz(Literal.t) :: String.t | nil
|
||||
def tz(time_literal) do
|
||||
if valid?(time_literal) do
|
||||
time_literal
|
||||
|> lexical()
|
||||
|> RDF.DateTimeUtils.tz()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Converts a time literal to a canonical string, preserving the zone information.
|
||||
"""
|
||||
@spec canonical_lexical_with_zone(Literal.t) :: String.t | nil
|
||||
def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal)
|
||||
when is_xsd_time(datatype) do
|
||||
case tz(literal) do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
zone when zone in ["Z", "", "+00:00"] ->
|
||||
canonical_lexical(literal.value)
|
||||
|
||||
zone ->
|
||||
literal
|
||||
|> lexical()
|
||||
|> String.replace_trailing(zone, "")
|
||||
|> Time.from_iso8601!()
|
||||
|> canonical_lexical()
|
||||
|> Kernel.<>(zone)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -52,27 +52,8 @@ defimpl Inspect, for: RDF.BlankNode do
|
|||
end
|
||||
|
||||
defimpl Inspect, for: RDF.Literal do
|
||||
@xsd_string RDF.Datatype.NS.XSD.string
|
||||
@rdf_lang_string RDF.langString
|
||||
|
||||
def inspect(%RDF.Literal{value: value, language: language}, _opts) when not is_nil(language) do
|
||||
~s[~L"#{value}"#{language}]
|
||||
end
|
||||
|
||||
def inspect(%RDF.Literal{value: value, uncanonical_lexical: lexical, datatype: datatype}, _opts)
|
||||
when not is_nil(lexical) do
|
||||
"%RDF.Literal{value: #{inspect value}, lexical: #{inspect lexical}, datatype: ~I<#{datatype}>}"
|
||||
end
|
||||
|
||||
def inspect(%RDF.Literal{value: value, datatype: datatype, language: language}, _opts) do
|
||||
case datatype do
|
||||
@xsd_string ->
|
||||
~s[~L"#{value}"]
|
||||
@rdf_lang_string ->
|
||||
"%RDF.Literal{value: #{inspect value}, datatype: ~I<#{datatype}>, language: #{inspect language}}"
|
||||
_ ->
|
||||
"%RDF.Literal{value: #{inspect value}, datatype: ~I<#{datatype}>}"
|
||||
end
|
||||
def inspect(literal, _opts) do
|
||||
"%RDF.Literal{literal: #{inspect literal.literal}, valid: #{RDF.Literal.valid?(literal)}}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -233,13 +233,6 @@ defmodule RDF.IRI do
|
|||
def equal_value?(%RDF.IRI{value: left}, %RDF.IRI{value: right}),
|
||||
do: left == right
|
||||
|
||||
@xsd_any_uri "http://www.w3.org/2001/XMLSchema#anyURI"
|
||||
|
||||
def equal_value?(%RDF.Literal{datatype: %RDF.IRI{value: @xsd_any_uri}, value: left}, right),
|
||||
do: equal_value?(new(left), right)
|
||||
|
||||
def equal_value?(left, %RDF.Literal{datatype: %RDF.IRI{value: @xsd_any_uri}, value: right}),
|
||||
do: equal_value?(left, new(right))
|
||||
|
||||
def equal_value?(_, _),
|
||||
do: nil
|
||||
|
|
|
@ -17,7 +17,7 @@ defmodule RDF.List do
|
|||
@enforce_keys [:head]
|
||||
defstruct [:head, :graph]
|
||||
|
||||
@rdf_nil RDF.nil
|
||||
@rdf_nil RDF.Utils.Bootstrapping.rdf_iri("nil")
|
||||
|
||||
|
||||
@doc """
|
||||
|
@ -209,7 +209,7 @@ defmodule RDF.List do
|
|||
|
||||
|
||||
defimpl Enumerable do
|
||||
@rdf_nil RDF.nil
|
||||
@rdf_nil RDF.Utils.Bootstrapping.rdf_iri("nil")
|
||||
|
||||
def reduce(_, {:halt, acc}, _fun), do: {:halted, acc}
|
||||
def reduce(list, {:suspend, acc}, fun), do: {:suspended, acc, &reduce(list, &1, fun)}
|
||||
|
|
|
@ -3,33 +3,16 @@ defmodule RDF.Literal do
|
|||
RDF literals are leaf nodes of a RDF graph containing raw data, like strings and numbers.
|
||||
"""
|
||||
|
||||
alias RDF.Datatype.NS.XSD
|
||||
alias RDF.IRI
|
||||
defstruct [:literal]
|
||||
|
||||
@type literal_value ::
|
||||
RDF.Boolean.value
|
||||
| RDF.Integer.value
|
||||
| RDF.Double.value
|
||||
| RDF.String.value
|
||||
| RDF.Decimal.value
|
||||
| RDF.Date.value
|
||||
| RDF.Time.value
|
||||
| RDF.DateTime.value
|
||||
alias RDF.{IRI, LangString}
|
||||
alias RDF.Literal.{Generic, Datatype}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
value: literal_value,
|
||||
datatype: IRI.t,
|
||||
uncanonical_lexical: String.t | nil,
|
||||
language: String.t | nil
|
||||
}
|
||||
import RDF.Literal.Helper.Macros
|
||||
|
||||
defstruct [:value, :datatype, :uncanonical_lexical, :language]
|
||||
|
||||
# to be able to pattern-match on plain types; we can't use RDF.Literal.Guards here since these aren't compiled here yet
|
||||
@xsd_string XSD.string
|
||||
@lang_string RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
|
||||
@plain_types [@xsd_string, @lang_string]
|
||||
@type t :: %__MODULE__{:literal => Datatype.literal()}
|
||||
|
||||
@rdf_lang_string RDF.Utils.Bootstrapping.rdf_iri("langString")
|
||||
|
||||
@doc """
|
||||
Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype.
|
||||
|
@ -44,34 +27,39 @@ defmodule RDF.Literal do
|
|||
| `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
|
||||
|
||||
iex> RDF.Literal.new(42)
|
||||
%RDF.Literal{value: 42, datatype: XSD.integer}
|
||||
%RDF.Literal{literal: %XSD.Integer{value: 42}}
|
||||
|
||||
"""
|
||||
@spec new(literal_value | t) :: t
|
||||
@spec new(t | any) :: t | nil
|
||||
def new(value)
|
||||
|
||||
def new(%RDF.Literal{} = literal), do: literal
|
||||
|
||||
def new(value) when is_binary(value), do: RDF.String.new(value)
|
||||
def new(value) when is_boolean(value), do: RDF.Boolean.new(value)
|
||||
def new(value) when is_integer(value), do: RDF.Integer.new(value)
|
||||
def new(value) when is_float(value), do: RDF.Double.new(value)
|
||||
def new(%Decimal{} = value), do: RDF.Decimal.new(value)
|
||||
|
||||
def new(%Date{} = value), do: RDF.Date.new(value)
|
||||
def new(%Time{} = value), do: RDF.Time.new(value)
|
||||
def new(%DateTime{} = value), do: RDF.DateTime.new(value)
|
||||
def new(%NaiveDateTime{} = value), do: RDF.DateTime.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
|
||||
raise RDF.Literal.InvalidError, "#{inspect value} not convertible to a RDF.Literal"
|
||||
|
@ -80,40 +68,29 @@ defmodule RDF.Literal do
|
|||
@doc """
|
||||
Creates a new `RDF.Literal` with the given datatype or language tag.
|
||||
"""
|
||||
@spec new(literal_value | t, map | keyword) :: t
|
||||
def new(value, opts)
|
||||
@spec new(t | any, keyword) :: t | nil
|
||||
def new(value, opts) do
|
||||
cond do
|
||||
length(opts) == 0 ->
|
||||
new(value)
|
||||
|
||||
def new(value, opts) when is_list(opts),
|
||||
do: new(value, Map.new(opts))
|
||||
|
||||
def new(value, %{language: nil} = opts),
|
||||
do: new(value, Map.delete(opts, :language))
|
||||
|
||||
def new(value, %{language: _} = opts) do
|
||||
if is_binary(value) do
|
||||
if opts[:datatype] in [nil, @lang_string] do
|
||||
RDF.LangString.new(value, opts)
|
||||
Keyword.has_key?(opts, :language) ->
|
||||
if Keyword.get(opts, :datatype, @rdf_lang_string) |> IRI.new() == @rdf_lang_string do
|
||||
LangString.new(value, opts)
|
||||
else
|
||||
raise ArgumentError, "datatype with language must be rdf:langString"
|
||||
end
|
||||
else
|
||||
new(value, Map.delete(opts, :language)) # Should we raise a warning?
|
||||
end
|
||||
end
|
||||
|
||||
def new(value, %{datatype: %RDF.IRI{} = id} = opts) do
|
||||
case RDF.Datatype.get(id) do
|
||||
nil -> %RDF.Literal{value: value, datatype: id}
|
||||
datatype = Keyword.get(opts, :datatype) ->
|
||||
case Datatype.Registry.get(datatype) do
|
||||
nil -> Generic.new(value, opts)
|
||||
datatype -> datatype.new(value, opts)
|
||||
end
|
||||
|
||||
true ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def new(value, %{datatype: datatype} = opts),
|
||||
do: new(value, %{opts | datatype: RDF.iri(datatype)})
|
||||
|
||||
def new(value, opts) when is_map(opts) and map_size(opts) == 0,
|
||||
do: new(value)
|
||||
|
||||
|
||||
@doc """
|
||||
Creates a new `RDF.Literal`, but fails if it's not valid.
|
||||
|
@ -123,90 +100,25 @@ defmodule RDF.Literal do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> RDF.Literal.new!("3.14", datatype: XSD.double) == RDF.Literal.new("3.14", datatype: XSD.double)
|
||||
true
|
||||
iex> RDF.Literal.new("foo")
|
||||
%RDF.Literal{literal: %XSD.String{value: "foo"}}
|
||||
|
||||
iex> RDF.Literal.new!("invalid", datatype: "http://example/unkown_datatype") == RDF.Literal.new("invalid", datatype: "http://example/unkown_datatype")
|
||||
true
|
||||
|
||||
iex> RDF.Literal.new!("foo", datatype: XSD.integer)
|
||||
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{value: nil, lexical: "foo", datatype: ~I<http://www.w3.org/2001/XMLSchema#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}
|
||||
|
||||
iex> RDF.Literal.new!("foo", datatype: RDF.langString)
|
||||
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{value: "foo", datatype: ~I<http://www.w3.org/1999/02/22-rdf-syntax-ns#langString>, language: nil}
|
||||
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.LangString{language: nil, value: "foo"}, valid: false}
|
||||
|
||||
"""
|
||||
@spec new!(literal_value | t, map | keyword) :: t
|
||||
def new!(value, opts \\ %{}) do
|
||||
with %RDF.Literal{} = literal <- new(value, opts) do
|
||||
@spec new!(t | any, keyword) :: t
|
||||
def new!(value, opts \\ []) do
|
||||
literal = new(value, opts)
|
||||
if valid?(literal) do
|
||||
literal
|
||||
else
|
||||
raise RDF.Literal.InvalidError, "invalid RDF.Literal: #{inspect literal}"
|
||||
end
|
||||
else
|
||||
invalid ->
|
||||
raise RDF.Literal.InvalidError, "invalid result of RDF.Literal.new: #{inspect invalid}"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the lexical representation of the given literal according to its datatype.
|
||||
"""
|
||||
@spec lexical(t) :: String.t
|
||||
def lexical(%RDF.Literal{value: value, uncanonical_lexical: nil, datatype: id} = literal) do
|
||||
case RDF.Datatype.get(id) do
|
||||
nil -> to_string(value)
|
||||
datatype -> datatype.lexical(literal)
|
||||
end
|
||||
end
|
||||
|
||||
def lexical(%RDF.Literal{uncanonical_lexical: lexical}), do: lexical
|
||||
|
||||
@doc """
|
||||
Returns the given literal in its canonical lexical representation.
|
||||
"""
|
||||
@spec canonical(t) :: t
|
||||
def canonical(%RDF.Literal{uncanonical_lexical: nil} = literal), do: literal
|
||||
def canonical(%RDF.Literal{datatype: id} = literal) do
|
||||
case RDF.Datatype.get(id) do
|
||||
nil -> literal
|
||||
datatype -> datatype.canonical(literal)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns if the given literal is in its canonical lexical representation.
|
||||
"""
|
||||
@spec canonical?(t) :: boolean
|
||||
def canonical?(%RDF.Literal{uncanonical_lexical: nil}), do: true
|
||||
def canonical?(%RDF.Literal{} = _), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Returns if the value of the given literal is a valid according to its datatype.
|
||||
"""
|
||||
@spec valid?(t) :: boolean
|
||||
def valid?(%RDF.Literal{datatype: id} = literal) do
|
||||
case RDF.Datatype.get(id) do
|
||||
nil -> true
|
||||
datatype -> datatype.valid?(literal)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Returns if a literal is a simple literal.
|
||||
|
||||
A simple literal has no datatype or language.
|
||||
|
||||
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
|
||||
"""
|
||||
@spec simple?(t) :: boolean
|
||||
def simple?(%RDF.Literal{datatype: @xsd_string}), do: true
|
||||
def simple?(%RDF.Literal{} = _), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Returns if a literal is a language-tagged literal.
|
||||
|
@ -214,9 +126,8 @@ defmodule RDF.Literal do
|
|||
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
||||
"""
|
||||
@spec has_language?(t) :: boolean
|
||||
def has_language?(%RDF.Literal{datatype: @lang_string}), do: true
|
||||
def has_language?(%RDF.Literal{} = _), do: false
|
||||
|
||||
def has_language?(%__MODULE__{literal: %LangString{} = literal}), do: LangString.valid?(literal)
|
||||
def has_language?(%__MODULE__{} = _), do: false
|
||||
|
||||
@doc """
|
||||
Returns if a literal is a datatyped literal.
|
||||
|
@ -230,6 +141,17 @@ defmodule RDF.Literal do
|
|||
not plain?(literal) and not has_language?(literal)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns if a literal is a simple literal.
|
||||
|
||||
A simple literal has no datatype or language.
|
||||
|
||||
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
|
||||
"""
|
||||
@spec simple?(t) :: boolean
|
||||
def simple?(%__MODULE__{literal: %XSD.String{}}), do: true
|
||||
def simple?(%__MODULE__{} = _), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
Returns if a literal is a plain literal.
|
||||
|
@ -240,60 +162,46 @@ defmodule RDF.Literal do
|
|||
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
||||
"""
|
||||
@spec plain?(t) :: boolean
|
||||
def plain?(%RDF.Literal{datatype: datatype})
|
||||
when datatype in @plain_types, do: true
|
||||
def plain?(%RDF.Literal{} = _), do: false
|
||||
def plain?(%__MODULE__{literal: %XSD.String{}}), do: true
|
||||
def plain?(%__MODULE__{literal: %LangString{}}), do: true
|
||||
def plain?(%__MODULE__{} = _), do: false
|
||||
|
||||
@spec typed?(t) :: boolean
|
||||
def typed?(literal), do: not plain?(literal)
|
||||
|
||||
|
||||
@doc """
|
||||
Checks if two `RDF.Literal`s are equal.
|
||||
############################################################################
|
||||
# functions delegating to the RDF.Datatype of a RDF.Literal
|
||||
|
||||
Non-RDF terms are tried to be coerced via `RDF.Term.coerce/1` before comparison.
|
||||
defdelegate_to_rdf_datatype :datatype
|
||||
|
||||
Returns `nil` when the given arguments are not comparable as Literals.
|
||||
defdelegate_to_rdf_datatype :language
|
||||
|
||||
see <https://www.w3.org/TR/rdf-concepts/#section-Literal-Equality>
|
||||
"""
|
||||
@spec equal_value?(t | IRI.t | any, t | IRI.t | any) :: boolean | nil
|
||||
def equal_value?(left, right)
|
||||
def value(%__MODULE__{literal: %datatype{} = literal}), do: datatype.value(literal)
|
||||
|
||||
def equal_value?(%RDF.Literal{datatype: id1} = literal1, %RDF.Literal{datatype: id2} = literal2) do
|
||||
case RDF.Datatype.get(id1) do
|
||||
nil ->
|
||||
if id1 == id2 do
|
||||
literal1.value == literal2.value
|
||||
end
|
||||
datatype ->
|
||||
datatype.equal_value?(literal1, literal2)
|
||||
end
|
||||
def lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.lexical(literal)
|
||||
|
||||
defdelegate_to_rdf_datatype :canonical
|
||||
|
||||
def canonical?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical?(literal)
|
||||
|
||||
def valid?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.valid?(literal)
|
||||
def valid?(_), do: false
|
||||
|
||||
def equal_value?(%__MODULE__{literal: %datatype{} = left}, right) do
|
||||
Datatype.Registry.rdf_datatype(datatype).equal_value?(left, right)
|
||||
end
|
||||
|
||||
# TODO: Handle AnyURI in its own RDF.Datatype implementation
|
||||
@xsd_any_uri "http://www.w3.org/2001/XMLSchema#anyURI"
|
||||
def equal_value?(_, _), do: false
|
||||
|
||||
def equal_value?(%RDF.Literal{datatype: %RDF.IRI{value: @xsd_any_uri}} = left, right),
|
||||
do: RDF.IRI.equal_value?(left, right)
|
||||
|
||||
def equal_value?(left, %RDF.Literal{datatype: %RDF.IRI{value: @xsd_any_uri}} = right),
|
||||
do: RDF.IRI.equal_value?(left, right)
|
||||
|
||||
def equal_value?(%RDF.Literal{} = left, right) when not is_nil(right) do
|
||||
unless RDF.Term.term?(right) do
|
||||
equal_value?(left, RDF.Term.coerce(right))
|
||||
def compare(%__MODULE__{literal: %datatype{} = left}, right) do
|
||||
Datatype.Registry.rdf_datatype(datatype).compare(left, right)
|
||||
end
|
||||
end
|
||||
|
||||
def equal_value?(_, _), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
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 | any, t | any) :: boolean | nil
|
||||
def less_than?(literal1, literal2) do
|
||||
|
@ -308,7 +216,6 @@ defmodule RDF.Literal do
|
|||
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 | any, t | any) :: boolean | nil
|
||||
def greater_than?(literal1, literal2) do
|
||||
|
@ -321,127 +228,26 @@ defmodule RDF.Literal do
|
|||
|
||||
|
||||
@doc """
|
||||
Compares two `RDF.Literal`s.
|
||||
|
||||
Returns `:gt` if first literal is greater than the second in terms of their datatype
|
||||
and `:lt` for vice versa. If the two literals are equal `:eq` is returned.
|
||||
For datatypes with only partial ordering `:indeterminate` is returned when the
|
||||
order of the given literals is not defined.
|
||||
|
||||
Returns `nil` when the given arguments are not comparable datatypes.
|
||||
|
||||
"""
|
||||
@spec compare(t | any, t | any) :: :eq | :lt | :gt | nil
|
||||
def compare(left, right)
|
||||
|
||||
def compare(%RDF.Literal{datatype: id1} = literal1, %RDF.Literal{datatype: id2} = literal2) do
|
||||
case RDF.Datatype.get(id1) do
|
||||
nil ->
|
||||
if id1 == id2 do
|
||||
cond do
|
||||
literal1.value == literal2.value -> :eq
|
||||
literal1.value < literal2.value -> :lt
|
||||
true -> :gt
|
||||
end
|
||||
end
|
||||
|
||||
datatype ->
|
||||
datatype.compare(literal1, literal2)
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
|
||||
|
||||
@doc """
|
||||
Matches the string representation of the given value against a XPath and XQuery regular expression pattern.
|
||||
Matches the lexical form of the given `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_.
|
||||
|
||||
The `pattern` and the optional `flags` can be given as an Elixir string or as
|
||||
`xsd:string` `RDF.Literal`s.
|
||||
|
||||
see <https://www.w3.org/TR/xpath-functions/#func-matches>
|
||||
"""
|
||||
@spec matches?(t | String.t, t | String.t, 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)
|
||||
@spec matches?(t | String.t, pattern :: t | String.t, flags :: t | String.t) :: boolean
|
||||
def matches?(value, pattern, flags \\ "")
|
||||
def matches?(%__MODULE__{} = literal, pattern, flags),
|
||||
do: matches?(lexical(literal), pattern, flags)
|
||||
def matches?(value, %__MODULE__{literal: %XSD.String{}} = pattern, flags),
|
||||
do: matches?(value, lexical(pattern), flags)
|
||||
def matches?(value, pattern, %__MODULE__{literal: %XSD.String{}} = flags),
|
||||
do: matches?(value, pattern, lexical(flags))
|
||||
def matches?(value, pattern, flags) when is_binary(value) and is_binary(pattern) and is_binary(flags),
|
||||
do: XSD.Utils.Regex.matches?(value, pattern, flags)
|
||||
|
||||
{:q, pattern} ->
|
||||
String.contains?(string, pattern)
|
||||
|
||||
{:qi, pattern} ->
|
||||
string
|
||||
|> String.downcase()
|
||||
|> String.contains?(String.downcase(pattern))
|
||||
|
||||
_ ->
|
||||
raise "Invalid XQuery regex pattern or flags"
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec xpath_pattern(t | String.t, t | String.t) ::
|
||||
{:q | :qi, String.t} | {:regex, Regex.t} | {:error, any}
|
||||
def xpath_pattern(pattern, flags)
|
||||
|
||||
def xpath_pattern(%RDF.Literal{datatype: @xsd_string} = pattern, flags),
|
||||
do: xpath_pattern(pattern.value, flags)
|
||||
|
||||
def xpath_pattern(pattern, %RDF.Literal{datatype: @xsd_string} = flags),
|
||||
do: xpath_pattern(pattern, flags.value)
|
||||
|
||||
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
|
||||
|
||||
@doc false
|
||||
@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
|
||||
|
||||
defimpl String.Chars, for: RDF.Literal do
|
||||
defimpl String.Chars do
|
||||
def to_string(literal) do
|
||||
RDF.Literal.lexical(literal)
|
||||
String.Chars.to_string(literal.literal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
88
lib/rdf/literal/datatype.ex
Normal file
88
lib/rdf/literal/datatype.ex
Normal file
|
@ -0,0 +1,88 @@
|
|||
defmodule RDF.Literal.Datatype do
|
||||
alias RDF.{Literal, IRI}
|
||||
|
||||
@type t :: module
|
||||
|
||||
@type literal :: %{:__struct__ => t(), optional(atom()) => any()}
|
||||
|
||||
@type comparison_result :: :lt | :gt | :eq
|
||||
|
||||
@doc false
|
||||
@callback literal_type :: module
|
||||
|
||||
@doc """
|
||||
The name of the datatype.
|
||||
"""
|
||||
@callback name :: String.t()
|
||||
|
||||
@doc """
|
||||
The IRI of the datatype.
|
||||
"""
|
||||
@callback id :: String.t() | nil
|
||||
|
||||
@doc """
|
||||
The datatype IRI of the given datatype literal.
|
||||
"""
|
||||
@callback datatype(Literal.t | literal) :: IRI.t()
|
||||
|
||||
@doc """
|
||||
The language of the given datatype literal if present.
|
||||
"""
|
||||
@callback language(Literal.t | literal) :: String.t() | nil
|
||||
|
||||
@doc """
|
||||
Returns the value of a datatype literal.
|
||||
"""
|
||||
@callback value(Literal.t | literal) :: any
|
||||
|
||||
@doc """
|
||||
Returns the lexical form of a datatype literal.
|
||||
"""
|
||||
@callback lexical(Literal.t() | literal) :: String.t()
|
||||
|
||||
@doc """
|
||||
Produces the canonical representation of a datatype literal.
|
||||
"""
|
||||
@callback canonical(Literal.t() | literal) :: Literal.t()
|
||||
|
||||
@doc """
|
||||
Determines if the lexical form of a datatype literal is the canonical form.
|
||||
|
||||
Note: For `RDF.Literal.Generic` literals with the canonical form not defined,
|
||||
this always return `true`.
|
||||
"""
|
||||
@callback canonical?(Literal.t() | literal | any) :: boolean
|
||||
|
||||
@doc """
|
||||
Determines if the lexical form of a datatype literal is a member of its lexical value space.
|
||||
"""
|
||||
@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 """
|
||||
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.
|
||||
"""
|
||||
@callback equal_value?(Literal.t() | literal, Literal.t() | literal | any) :: boolean
|
||||
|
||||
@doc """
|
||||
Compares two datatype literals.
|
||||
|
||||
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.
|
||||
For datatypes with only partial ordering `:indeterminate` is returned when the
|
||||
order of the given literals is not defined.
|
||||
|
||||
Returns `nil` when the given arguments are not comparable datatypes or if one
|
||||
them is invalid.
|
||||
"""
|
||||
@callback compare(Literal.t() | literal, Literal.t() | literal) :: comparison_result | :indeterminate | nil
|
||||
end
|
|
@ -1,8 +1,10 @@
|
|||
defmodule RDF.Datatype.NS do
|
||||
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
|
||||
|
@ -58,5 +60,4 @@ defmodule RDF.Datatype.NS do
|
|||
QName
|
||||
NOTATION
|
||||
]
|
||||
|
||||
end
|
52
lib/rdf/literal/datatype/registry.ex
Normal file
52
lib/rdf/literal/datatype/registry.ex
Normal file
|
@ -0,0 +1,52 @@
|
|||
# TODO: This registry should be managed automatically/dynamically and be extendable, to allow user-defined datatypes ...
|
||||
defmodule RDF.Literal.Datatype.Registry do
|
||||
@moduledoc false
|
||||
|
||||
alias RDF.Literal
|
||||
alias RDF.IRI
|
||||
|
||||
@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)
|
||||
|
||||
@doc """
|
||||
The mapping of IRIs of datatypes to their `RDF.Literal.Datatype`.
|
||||
"""
|
||||
@spec mapping :: %{IRI.t => Literal.Datatype.t}
|
||||
def mapping, do: @mapping
|
||||
|
||||
@doc """
|
||||
The IRIs of all datatypes with a `RDF.Literal.Datatype` defined.
|
||||
"""
|
||||
@spec ids :: [IRI.t]
|
||||
def ids, do: Map.keys(@mapping)
|
||||
|
||||
@doc """
|
||||
All defined `RDF.Literal.Datatype` modules.
|
||||
"""
|
||||
@spec datatypes :: Enum.t
|
||||
def datatypes, do: @datatypes
|
||||
|
||||
@spec datatype?(module) :: boolean
|
||||
def datatype?(module), do: module in @datatypes
|
||||
|
||||
@doc """
|
||||
Returns the `RDF.Literal.Datatype` for a directly datatype IRI or the datatype IRI of a `RDF.Literal`.
|
||||
"""
|
||||
@spec get(Literal.t | IRI.t | String.t) :: Literal.Datatype.t
|
||||
def get(%Literal{} = literal), do: Literal.datatype(literal)
|
||||
def get(id) when is_binary(id), do: id |> IRI.new() |> get()
|
||||
def get(id), do: @mapping[id]
|
||||
|
||||
@doc false
|
||||
def rdf_datatype(type) do
|
||||
if type in XSD.datatypes() do
|
||||
RDF.Literal.XSD.datatype_module_name(type)
|
||||
else
|
||||
type
|
||||
end
|
||||
end
|
||||
end
|
110
lib/rdf/literal/datatypes/generic.ex
Normal file
110
lib/rdf/literal/datatypes/generic.ex
Normal file
|
@ -0,0 +1,110 @@
|
|||
defmodule RDF.Literal.Generic do
|
||||
@moduledoc """
|
||||
A generic `RDF.Literal.Datatype` for literals of an unknown datatype.
|
||||
"""
|
||||
|
||||
defstruct [:value, :datatype]
|
||||
|
||||
@behaviour RDF.Literal.Datatype
|
||||
|
||||
alias RDF.Literal.Datatype
|
||||
alias RDF.{Literal, IRI}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
value: String.t,
|
||||
datatype: String.t
|
||||
}
|
||||
|
||||
@impl Datatype
|
||||
def literal_type, do: __MODULE__
|
||||
|
||||
@impl Datatype
|
||||
def name, do: "generic"
|
||||
|
||||
@impl Datatype
|
||||
def id, do: nil
|
||||
|
||||
def new(value, opts \\ []) do
|
||||
%Literal{
|
||||
literal: %__MODULE__{
|
||||
value: value,
|
||||
datatype: Keyword.get(opts, :datatype) |> normalize_datatype()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp normalize_datatype(nil), do: nil
|
||||
defp normalize_datatype(""), do: nil
|
||||
defp normalize_datatype(datatype), do: IRI.new(datatype)
|
||||
|
||||
def new!(value, opts \\ []) do
|
||||
literal = new(value, opts)
|
||||
|
||||
if valid?(literal) do
|
||||
literal
|
||||
else
|
||||
raise ArgumentError, "#{inspect(value)} with datatype #{inspect literal.literal.datatype} is not a valid #{inspect(__MODULE__)}"
|
||||
end
|
||||
end
|
||||
|
||||
@impl Datatype
|
||||
def datatype(%Literal{literal: literal}), do: datatype(literal)
|
||||
def datatype(%__MODULE__{} = literal), do: literal.datatype
|
||||
|
||||
@impl Datatype
|
||||
def language(%Literal{literal: literal}), do: language(literal)
|
||||
def language(%__MODULE__{}), do: nil
|
||||
|
||||
@impl Datatype
|
||||
def value(%Literal{literal: literal}), do: value(literal)
|
||||
def value(%__MODULE__{} = literal), do: literal.value
|
||||
|
||||
@impl Datatype
|
||||
def lexical(%Literal{literal: literal}), do: lexical(literal)
|
||||
def lexical(%__MODULE__{} = literal), do: literal.value
|
||||
|
||||
@impl Datatype
|
||||
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
|
||||
def canonical(%__MODULE__{} = literal), do: %Literal{literal: literal}
|
||||
|
||||
@impl Datatype
|
||||
def canonical?(%Literal{literal: literal}), do: canonical?(literal)
|
||||
def canonical?(%__MODULE__{}), do: true
|
||||
|
||||
@impl Datatype
|
||||
def valid?(%Literal{literal: %__MODULE__{} = literal}), do: valid?(literal)
|
||||
def valid?(%__MODULE__{datatype: %IRI{}}), do: true
|
||||
def valid?(_), do: false
|
||||
|
||||
@impl Datatype
|
||||
def cast(%Literal{literal: %__MODULE__{}} = literal), do: literal
|
||||
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, right), do: left == right
|
||||
|
||||
@impl Datatype
|
||||
def compare(left, %Literal{literal: right}), do: compare(left, right)
|
||||
def compare(%Literal{literal: left}, right), do: compare(left, right)
|
||||
def compare(%__MODULE__{datatype: datatype} = literal1,
|
||||
%__MODULE__{datatype: datatype} = literal2) do
|
||||
if valid?(literal1) and valid?(literal2) do
|
||||
case {canonical(literal1).literal.value, canonical(literal2).literal.value} do
|
||||
{value1, value2} when value1 < value2 -> :lt
|
||||
{value1, value2} when value1 > value2 -> :gt
|
||||
_ ->
|
||||
if equal_value?(literal1, literal2), do: :eq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(literal) do
|
||||
literal.value
|
||||
end
|
||||
end
|
||||
end
|
150
lib/rdf/literal/datatypes/lang_string.ex
Normal file
150
lib/rdf/literal/datatypes/lang_string.ex
Normal file
|
@ -0,0 +1,150 @@
|
|||
defmodule RDF.LangString do
|
||||
@moduledoc """
|
||||
`RDF.Literal.Datatype` for `rdf:langString`s.
|
||||
"""
|
||||
|
||||
defstruct [:value, :language]
|
||||
|
||||
@behaviour RDF.Literal.Datatype
|
||||
|
||||
alias RDF.Literal.Datatype
|
||||
alias RDF.Literal
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
value: String.t,
|
||||
language: String.t
|
||||
}
|
||||
|
||||
@iri RDF.Utils.Bootstrapping.rdf_iri("langString")
|
||||
@id to_string(@iri)
|
||||
|
||||
@impl Datatype
|
||||
def literal_type, do: __MODULE__
|
||||
|
||||
@impl Datatype
|
||||
def name, do: "langString"
|
||||
|
||||
@impl Datatype
|
||||
def id, do: @id
|
||||
|
||||
def new(value, opts \\ []) do
|
||||
%Literal{
|
||||
literal: %__MODULE__{
|
||||
value: to_string(value),
|
||||
language: Keyword.get(opts, :language) |> normalize_language()
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
defp normalize_language(nil), do: nil
|
||||
defp normalize_language(""), do: nil
|
||||
defp normalize_language(language), do: String.downcase(language)
|
||||
|
||||
def new!(value, opts \\ []) do
|
||||
literal = new(value, opts)
|
||||
|
||||
if valid?(literal) do
|
||||
literal
|
||||
else
|
||||
raise ArgumentError, "#{inspect(value)} with language #{inspect literal.literal.language} is not a valid #{inspect(__MODULE__)}"
|
||||
end
|
||||
end
|
||||
|
||||
@impl Datatype
|
||||
def datatype(%Literal{literal: literal}), do: datatype(literal)
|
||||
def datatype(%__MODULE__{}), do: @iri
|
||||
|
||||
@impl Datatype
|
||||
def language(%Literal{literal: literal}), do: language(literal)
|
||||
def language(%__MODULE__{} = literal), do: literal.language
|
||||
|
||||
@impl Datatype
|
||||
def value(%Literal{literal: literal}), do: value(literal)
|
||||
def value(%__MODULE__{} = literal), do: literal.value
|
||||
|
||||
@impl Datatype
|
||||
def lexical(%Literal{literal: literal}), do: lexical(literal)
|
||||
def lexical(%__MODULE__{} = literal), do: literal.value
|
||||
|
||||
@impl Datatype
|
||||
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
|
||||
def canonical(%__MODULE__{} = literal), do: %Literal{literal: literal}
|
||||
|
||||
@impl Datatype
|
||||
def canonical?(%Literal{literal: literal}), do: canonical?(literal)
|
||||
def canonical?(%__MODULE__{}), do: true
|
||||
|
||||
@impl Datatype
|
||||
def valid?(%Literal{literal: %__MODULE__{} = literal}), do: valid?(literal)
|
||||
def valid?(%__MODULE__{language: language}) when is_binary(language), do: language != ""
|
||||
def valid?(_), do: false
|
||||
|
||||
@impl Datatype
|
||||
def cast(%Literal{literal: %__MODULE__{}} = literal), do: literal
|
||||
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, right), do: left == right
|
||||
def equal_value?(_, _), do: false
|
||||
|
||||
@impl Datatype
|
||||
def compare(left, %Literal{literal: right}), do: compare(left, right)
|
||||
def compare(%Literal{literal: left}, right), do: compare(left, right)
|
||||
def compare(%__MODULE__{language: language_tag} = literal1,
|
||||
%__MODULE__{language: language_tag} = literal2) do
|
||||
if valid?(literal1) and valid?(literal2) do
|
||||
case {canonical(literal1).literal.value, canonical(literal2).literal.value} do
|
||||
{value1, value2} when value1 < value2 -> :lt
|
||||
{value1, value2} when value1 > value2 -> :gt
|
||||
_ ->
|
||||
if equal_value?(literal1, literal2), do: :eq
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def compare(_, _), do: nil
|
||||
|
||||
@doc """
|
||||
Checks if a language tagged string literal or language tag matches a language range.
|
||||
|
||||
The check is performed per the basic filtering scheme defined in
|
||||
[RFC4647](http://www.ietf.org/rfc/rfc4647.txt) section 3.3.1.
|
||||
A language range is a basic language range per _Matching of Language Tags_ in
|
||||
RFC4647 section 2.1.
|
||||
A language range of `"*"` matches any non-empty language-tag string.
|
||||
|
||||
see <https://www.w3.org/TR/sparql11-query/#func-langMatches>
|
||||
"""
|
||||
@spec match_language?(Literal.t | t() | String.t, String.t) :: boolean
|
||||
def match_language?(language_tag, language_range)
|
||||
|
||||
def match_language?(%Literal{literal: literal}, language_range),
|
||||
do: match_language?(literal, language_range)
|
||||
def match_language?(%__MODULE__{language: nil}, _), do: false
|
||||
def match_language?(%__MODULE__{language: language_tag}, language_range),
|
||||
do: match_language?(language_tag, language_range)
|
||||
|
||||
def match_language?("", "*"), do: false
|
||||
def match_language?(_, "*"), do: true
|
||||
|
||||
def match_language?(language_tag, language_range)
|
||||
when is_binary(language_tag) and is_binary(language_range) do
|
||||
language_tag = String.downcase(language_tag)
|
||||
language_range = String.downcase(language_range)
|
||||
|
||||
case String.split(language_tag, language_range, parts: 2) do
|
||||
[_, rest] -> rest == "" or String.starts_with?(rest, "-")
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def match_language?(_, _), do: false
|
||||
|
||||
defimpl String.Chars do
|
||||
def to_string(literal) do
|
||||
literal.value
|
||||
end
|
||||
end
|
||||
end
|
191
lib/rdf/literal/datatypes/numeric.ex
Normal file
191
lib/rdf/literal/datatypes/numeric.ex
Normal file
|
@ -0,0 +1,191 @@
|
|||
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 MapSet.new(XSD.Numeric.datatypes(), &RDF.Literal.Datatype.Registry.rdf_datatype/1)
|
||||
|
||||
@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
|
100
lib/rdf/literal/datatypes/xsd.ex
Normal file
100
lib/rdf/literal/datatypes/xsd.ex
Normal file
|
@ -0,0 +1,100 @@
|
|||
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: literal
|
||||
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: false
|
||||
|
||||
@impl RDF.Literal.Datatype
|
||||
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
|
||||
end
|
||||
end
|
||||
end
|
12
lib/rdf/literal/datatypes/xsd_boolean_values.ex
Normal file
12
lib/rdf/literal/datatypes/xsd_boolean_values.ex
Normal file
|
@ -0,0 +1,12 @@
|
|||
defmodule RDF.XSD.Boolean.Value do
|
||||
@moduledoc !"""
|
||||
This module holds the two boolean value literals, so they can be accessed
|
||||
directly without needing to construct them every time.
|
||||
"""
|
||||
|
||||
@xsd_true RDF.XSD.Boolean.new(true)
|
||||
@xsd_false RDF.XSD.Boolean.new(false)
|
||||
|
||||
def unquote(:true)(), do: @xsd_true
|
||||
def unquote(:false)(), do: @xsd_false
|
||||
end
|
11
lib/rdf/literal/helper_macros.ex
Normal file
11
lib/rdf/literal/helper_macros.ex
Normal file
|
@ -0,0 +1,11 @@
|
|||
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
|
|
@ -1,89 +0,0 @@
|
|||
defmodule RDF.Literal.Guards do
|
||||
@moduledoc """
|
||||
Guards for working with `RDF.Literal`s.
|
||||
|
||||
These are useful for pattern matching on datatypes of literals, since
|
||||
Elixir doesn't allow function calls in pattern matching clauses, which means
|
||||
the qualified terms of a `RDF.Vocabulary.Namespace` can't be used.
|
||||
|
||||
## Examples
|
||||
|
||||
defmodule M do
|
||||
import RDF.Literal.Guards
|
||||
|
||||
def f(%RDF.Literal{datatype: datatype} = literal) when is_xsd_integer(datatype) do
|
||||
# ...
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@xsd_integer XSD.integer()
|
||||
@xsd_decimal XSD.decimal()
|
||||
@xsd_float XSD.float()
|
||||
@xsd_double XSD.double()
|
||||
@xsd_string XSD.string()
|
||||
@xsd_boolean XSD.boolean()
|
||||
@xsd_dateTime XSD.dateTime()
|
||||
@xsd_date XSD.date()
|
||||
@xsd_time XSD.time()
|
||||
@xsd_any_uri XSD.anyURI()
|
||||
@rdf_lang_string RDF.langString()
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:integer`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_integer(datatype) when datatype == @xsd_integer
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:decimal`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_decimal(datatype) when datatype == @xsd_decimal
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:float`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_float(datatype) when datatype == @xsd_float
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:double`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_double(datatype) when datatype == @xsd_double
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:string`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_string(datatype) when datatype == @xsd_string
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:boolean`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_boolean(datatype) when datatype == @xsd_boolean
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:dateTime`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_datetime(datatype) when datatype == @xsd_dateTime
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:date`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_date(datatype) when datatype == @xsd_date
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:time`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_time(datatype) when datatype == @xsd_time
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `xsd:anyURI`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_xsd_any_uri(datatype) when datatype == @xsd_any_uri
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given datatype is `rdf:langString`; otherwise returns `false`.
|
||||
"""
|
||||
defguard is_rdf_lang_string(datatype) when datatype == @rdf_lang_string
|
||||
|
||||
end
|
|
@ -20,7 +20,7 @@ defmodule RDF.NS do
|
|||
"""
|
||||
defvocab XSD,
|
||||
base_iri: "http://www.w3.org/2001/XMLSchema#",
|
||||
terms: RDF.Datatype.NS.XSD.__terms__
|
||||
terms: RDF.Literal.Datatype.NS.XSD.__terms__
|
||||
|
||||
@vocabdoc """
|
||||
The RDF vocabulary.
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule RDF.Quad do
|
|||
RDF values for subject, predicate, object and a graph context.
|
||||
"""
|
||||
|
||||
alias RDF.{Literal, Statement}
|
||||
alias RDF.Statement
|
||||
|
||||
@type t :: {Statement.subject, Statement.predicate, Statement.object, Statement.graph_name}
|
||||
|
||||
|
@ -14,7 +14,7 @@ defmodule RDF.Quad do
|
|||
{Statement.coercible_subject, Statement.coercible_predicate,
|
||||
Statement.coercible_object, Statement.coercible_graph_name}
|
||||
|
||||
@type t_values :: {String.t, String.t, Literal.literal_value, String.t}
|
||||
@type t_values :: {String.t, String.t, any, String.t}
|
||||
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -29,8 +29,6 @@ defmodule RDF.Serialization.Encoder do
|
|||
quote bind_quoted: [], unquote: true do
|
||||
@behaviour unquote(__MODULE__)
|
||||
|
||||
import RDF.Literal.Guards
|
||||
|
||||
@impl unquote(__MODULE__)
|
||||
@dialyzer {:nowarn_function, encode!: 2}
|
||||
@spec encode!(Graph.t | Dataset.t, keyword) :: String.t
|
||||
|
|
|
@ -2,9 +2,8 @@ defmodule RDF.Serialization.ParseHelper do
|
|||
@moduledoc false
|
||||
|
||||
alias RDF.IRI
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@rdf_type RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
|
||||
@rdf_type RDF.Utils.Bootstrapping.rdf_iri("type")
|
||||
def rdf_type, do: @rdf_type
|
||||
|
||||
|
||||
|
@ -47,9 +46,9 @@ defmodule RDF.Serialization.ParseHelper do
|
|||
def to_literal(string_literal_quote_ast, type),
|
||||
do: {string_literal_quote_ast, type}
|
||||
|
||||
def integer(value), do: RDF.Integer.new(List.to_string(value))
|
||||
def decimal(value), do: RDF.Literal.new(List.to_string(value), datatype: XSD.decimal)
|
||||
def double(value), do: RDF.Double.new(List.to_string(value))
|
||||
def integer(value), do: RDF.XSD.Integer.new(List.to_string(value))
|
||||
def decimal(value), do: RDF.XSD.Decimal.new(List.to_string(value))
|
||||
def double(value), do: RDF.XSD.Double.new(List.to_string(value))
|
||||
def boolean('true'), do: true
|
||||
def boolean('false'), do: false
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule RDF.NTriples.Encoder do
|
|||
|
||||
use RDF.Serialization.Encoder
|
||||
|
||||
alias RDF.{BlankNode, Dataset, Graph, IRI, Literal, Statement, Triple}
|
||||
alias RDF.{BlankNode, Dataset, Graph, IRI, Literal, Statement, Triple, LangString}
|
||||
|
||||
@impl RDF.Serialization.Encoder
|
||||
@callback encode(Graph.t | Dataset.t, keyword | map) :: {:ok, String.t} | {:error, any}
|
||||
|
@ -28,20 +28,16 @@ defmodule RDF.NTriples.Encoder do
|
|||
"<#{to_string(iri)}>"
|
||||
end
|
||||
|
||||
def term(%Literal{value: value, language: language}) when not is_nil(language) do
|
||||
~s["#{value}"@#{language}]
|
||||
def term(%Literal{literal: %LangString{} = lang_string}) do
|
||||
~s["#{lang_string.value}"@#{lang_string.language}]
|
||||
end
|
||||
|
||||
def term(%Literal{value: value, language: language}) when not is_nil(language) do
|
||||
~s["#{value}"@#{language}]
|
||||
def term(%Literal{literal: %XSD.String{} = xsd_string}) do
|
||||
~s["#{xsd_string.value}"]
|
||||
end
|
||||
|
||||
def term(%Literal{datatype: datatype} = literal) when is_xsd_string(datatype) do
|
||||
~s["#{Literal.lexical(literal)}"]
|
||||
end
|
||||
|
||||
def term(%Literal{datatype: datatype} = literal) do
|
||||
~s["#{Literal.lexical(literal)}"^^<#{to_string(datatype)}>]
|
||||
def term(%Literal{} = literal) do
|
||||
~s["#{Literal.lexical(literal)}"^^<#{to_string(Literal.datatype(literal))}>]
|
||||
end
|
||||
|
||||
def term(%BlankNode{} = bnode) do
|
||||
|
|
|
@ -4,25 +4,29 @@ defmodule RDF.Turtle.Encoder do
|
|||
use RDF.Serialization.Encoder
|
||||
|
||||
alias RDF.Turtle.Encoder.State
|
||||
alias RDF.{BlankNode, Dataset, Description, Graph, IRI, Literal}
|
||||
alias RDF.{BlankNode, Dataset, Description, Graph, IRI, Literal, LangString}
|
||||
|
||||
@indentation_char " "
|
||||
@indentation 4
|
||||
|
||||
@native_supported_datatypes [
|
||||
RDF.Datatype.NS.XSD.boolean,
|
||||
RDF.Datatype.NS.XSD.integer,
|
||||
RDF.Datatype.NS.XSD.double,
|
||||
RDF.Datatype.NS.XSD.decimal
|
||||
XSD.Boolean,
|
||||
XSD.Integer,
|
||||
XSD.Double,
|
||||
XSD.Decimal
|
||||
]
|
||||
@rdf_type RDF.type
|
||||
@rdf_nil RDF.nil
|
||||
@rdf_type RDF.Utils.Bootstrapping.rdf_iri("type")
|
||||
@rdf_nil RDF.Utils.Bootstrapping.rdf_iri("nil")
|
||||
|
||||
# Defines rdf:type of subjects to be serialized at the beginning of the encoded graph
|
||||
@top_classes [RDF.NS.RDFS.Class] |> Enum.map(&RDF.iri/1)
|
||||
@top_classes [RDF.Utils.Bootstrapping.rdfs_iri("Class")]
|
||||
|
||||
# Defines order of predicates at the beginning of a resource description
|
||||
@predicate_order [RDF.type, RDF.NS.RDFS.label, RDF.iri("http://purl.org/dc/terms/title")]
|
||||
@predicate_order [
|
||||
@rdf_type,
|
||||
RDF.Utils.Bootstrapping.rdfs_iri("label"),
|
||||
RDF.iri("http://purl.org/dc/terms/title")
|
||||
]
|
||||
@ordered_properties MapSet.new(@predicate_order)
|
||||
|
||||
|
||||
|
@ -283,19 +287,18 @@ defmodule RDF.Turtle.Encoder do
|
|||
defp term(%BlankNode{} = bnode, _, _, _),
|
||||
do: to_string(bnode)
|
||||
|
||||
defp term(%Literal{value: value, language: language}, _,_ , _) when not is_nil(language),
|
||||
do: ~s["#{value}"@#{language}]
|
||||
defp term(%Literal{literal: %LangString{} = lang_string}, _, _, _) do
|
||||
~s["#{lang_string.value}"@#{lang_string.language}]
|
||||
end
|
||||
|
||||
defp term(%Literal{value: value, language: language}, _,_ , _) when not is_nil(language),
|
||||
do: ~s["#{value}"@#{language}]
|
||||
defp term(%Literal{literal: %XSD.String{}} = literal, _, _, _) do
|
||||
literal |> Literal.lexical() |> quoted()
|
||||
end
|
||||
|
||||
defp term(%Literal{datatype: datatype} = literal, _, _,_) when is_xsd_string(datatype),
|
||||
do: literal |> Literal.lexical |> quoted()
|
||||
|
||||
defp term(%Literal{datatype: datatype} = literal, state, _, nesting)
|
||||
defp term(%Literal{literal: %datatype{}} = literal, state, _, nesting)
|
||||
when datatype in @native_supported_datatypes do
|
||||
if Literal.valid?(literal) do
|
||||
literal |> Literal.canonical |> Literal.lexical
|
||||
literal |> Literal.canonical() |> Literal.lexical()
|
||||
else
|
||||
typed_literal_term(literal, state, nesting)
|
||||
end
|
||||
|
@ -324,8 +327,8 @@ defmodule RDF.Turtle.Encoder do
|
|||
defp based_name(_, _), do: nil
|
||||
|
||||
|
||||
defp typed_literal_term(%Literal{datatype: datatype} = literal, state, nesting),
|
||||
do: ~s["#{Literal.lexical(literal)}"^^#{term(datatype, state, :datatype, nesting)}]
|
||||
defp typed_literal_term(%Literal{} = literal, state, nesting),
|
||||
do: ~s["#{Literal.lexical(literal)}"^^#{literal |> Literal.datatype() |> term(state, :datatype, nesting)}]
|
||||
|
||||
|
||||
def prefixed_name(iri, prefixes) do
|
||||
|
|
|
@ -80,7 +80,10 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
end)
|
||||
end
|
||||
|
||||
@list_properties MapSet.new([RDF.first, RDF.rest])
|
||||
@list_properties MapSet.new([
|
||||
RDF.Utils.Bootstrapping.rdf_iri("first"),
|
||||
RDF.Utils.Bootstrapping.rdf_iri("rest")
|
||||
])
|
||||
|
||||
@dialyzer {:nowarn_function, to_list?: 2}
|
||||
defp to_list?(%Description{} = description, 1) do
|
||||
|
|
|
@ -54,10 +54,10 @@ defmodule RDF.Sigils do
|
|||
defmacro sigil_L(value, language)
|
||||
|
||||
defmacro sigil_L({:<<>>, _, [value]}, []) when is_binary(value) do
|
||||
Macro.escape(RDF.String.new(value))
|
||||
Macro.escape(RDF.XSD.String.new(value))
|
||||
end
|
||||
|
||||
defmacro sigil_L({:<<>>, _, [value]}, language) when is_binary(value) do
|
||||
Macro.escape(RDF.LangString.new(value, %{language: to_string(language)}))
|
||||
Macro.escape(RDF.LangString.new(value, language: to_string(language)))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,11 +14,11 @@ defmodule RDF.Statement do
|
|||
|
||||
@type coercible_subject :: subject | atom | String.t
|
||||
@type coercible_predicate :: predicate | atom | String.t
|
||||
@type coercible_object :: object | Literal.literal_value
|
||||
@type coercible_object :: object | any
|
||||
@type coercible_graph_name :: graph_name | atom | String.t
|
||||
|
||||
@type qualified_term :: {atom, Term.t | nil}
|
||||
@type term_mapping :: (qualified_term -> Literal.literal_value | nil)
|
||||
@type term_mapping :: (qualified_term -> any | nil)
|
||||
|
||||
@type t :: Triple.t | Quad.t
|
||||
|
||||
|
@ -123,7 +123,7 @@ defmodule RDF.Statement do
|
|||
def values(_, _), do: nil
|
||||
|
||||
@doc false
|
||||
@spec default_term_mapping(qualified_term) :: Literal.literal_value | nil
|
||||
@spec default_term_mapping(qualified_term) :: any | nil
|
||||
def default_term_mapping(qualified_term)
|
||||
def default_term_mapping({:graph_name, nil}), do: nil
|
||||
def default_term_mapping({_, term}), do: RDF.Term.value(term)
|
||||
|
|
|
@ -122,7 +122,7 @@ defimpl RDF.Term, for: RDF.Literal do
|
|||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Literal.equal_value?(term1, term2)
|
||||
def coerce(term), do: term
|
||||
def value(term), do: term.value || RDF.Literal.lexical(term)
|
||||
def value(term), do: RDF.Literal.value(term) || RDF.Literal.lexical(term)
|
||||
def term?(_), do: true
|
||||
end
|
||||
|
||||
|
@ -146,7 +146,7 @@ end
|
|||
defimpl RDF.Term, for: BitString do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.String.new(term)
|
||||
def coerce(term), do: RDF.XSD.String.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -154,7 +154,7 @@ end
|
|||
defimpl RDF.Term, for: Integer do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.Integer.new(term)
|
||||
def coerce(term), do: RDF.XSD.Integer.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -162,7 +162,7 @@ end
|
|||
defimpl RDF.Term, for: Float do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.Double.new(term)
|
||||
def coerce(term), do: RDF.XSD.Double.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -170,7 +170,7 @@ end
|
|||
defimpl RDF.Term, for: Decimal do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.Decimal.new(term)
|
||||
def coerce(term), do: RDF.XSD.Decimal.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -178,7 +178,7 @@ end
|
|||
defimpl RDF.Term, for: DateTime do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.DateTime.new(term)
|
||||
def coerce(term), do: RDF.XSD.DateTime.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -186,7 +186,7 @@ end
|
|||
defimpl RDF.Term, for: NaiveDateTime do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.DateTime.new(term)
|
||||
def coerce(term), do: RDF.XSD.DateTime.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -194,7 +194,7 @@ end
|
|||
defimpl RDF.Term, for: Date do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.Date.new(term)
|
||||
def coerce(term), do: RDF.XSD.Date.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
@ -202,7 +202,15 @@ end
|
|||
defimpl RDF.Term, for: Time do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.Time.new(term)
|
||||
def coerce(term), do: RDF.XSD.Time.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
||||
defimpl RDF.Term, for: URI do
|
||||
def equal?(term1, term2), do: term1 == term2
|
||||
def equal_value?(term1, term2), do: RDF.Term.equal_value?(coerce(term1), term2)
|
||||
def coerce(term), do: RDF.XSD.AnyURI.new(term)
|
||||
def value(term), do: term
|
||||
def term?(_), do: false
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule RDF.Triple do
|
|||
RDF values for subject, predicate and object.
|
||||
"""
|
||||
|
||||
alias RDF.{Literal, Statement}
|
||||
alias RDF.Statement
|
||||
|
||||
@type t :: {Statement.subject, Statement.predicate, Statement.object}
|
||||
|
||||
|
@ -14,7 +14,7 @@ defmodule RDF.Triple do
|
|||
{Statement.coercible_subject, Statement.coercible_predicate,
|
||||
Statement.coercible_object}
|
||||
|
||||
@type t_values :: {String.t, String.t, Literal.literal_value}
|
||||
@type t_values :: {String.t, String.t, any}
|
||||
|
||||
|
||||
@doc """
|
||||
|
|
17
lib/rdf/utils/bootstrapping.ex
Normal file
17
lib/rdf/utils/bootstrapping.ex
Normal file
|
@ -0,0 +1,17 @@
|
|||
defmodule RDF.Utils.Bootstrapping do
|
||||
@moduledoc !"""
|
||||
This module holds functions to circumvent circular dependency problems.
|
||||
"""
|
||||
|
||||
@rdf_base_iri "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
@rdfs_base_iri "http://www.w3.org/2000/01/rdf-schema#"
|
||||
@owl_base_iri "http://www.w3.org/2002/07/owl#"
|
||||
|
||||
def rdf_iri_base(), do: RDF.IRI.new(@rdf_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 rdf_iri(term), do: RDF.IRI.new(@rdf_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)
|
||||
end
|
|
@ -3,7 +3,9 @@ defmodule RDF.Utils.ResourceClassifier do
|
|||
|
||||
alias RDF.Description
|
||||
|
||||
@rdf_type RDF.iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
|
||||
import RDF.Utils.Bootstrapping
|
||||
|
||||
@rdf_type rdf_iri("type")
|
||||
|
||||
@doc """
|
||||
Determines if the given resource is RDF property by
|
||||
|
@ -17,15 +19,16 @@ defmodule RDF.Utils.ResourceClassifier do
|
|||
end
|
||||
|
||||
|
||||
@property_properties ~w[
|
||||
http://www.w3.org/2000/01/rdf-schema#domain
|
||||
http://www.w3.org/2000/01/rdf-schema#range
|
||||
http://www.w3.org/2000/01/rdf-schema#subPropertyOf
|
||||
http://www.w3.org/2002/07/owl#equivalentProperty
|
||||
http://www.w3.org/2002/07/owl#inverseOf
|
||||
http://www.w3.org/2002/07/owl#propertyDisjointWith
|
||||
]
|
||||
|> Enum.map(&RDF.iri/1)
|
||||
@property_properties Enum.map(~w[
|
||||
domain
|
||||
range
|
||||
subPropertyOf
|
||||
], &rdfs_iri/1) ++
|
||||
Enum.map(~w[
|
||||
equivalentProperty
|
||||
inverseOf
|
||||
propertyDisjointWith
|
||||
], &owl_iri/1)
|
||||
|> MapSet.new
|
||||
|
||||
defp property_by_domain?(description) do
|
||||
|
@ -34,23 +37,20 @@ defmodule RDF.Utils.ResourceClassifier do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
@property_classes ~w[
|
||||
http://www.w3.org/1999/02/22-rdf-syntax-ns#Property
|
||||
http://www.w3.org/2000/01/rdf-schema#ContainerMembershipProperty
|
||||
http://www.w3.org/2002/07/owl#ObjectProperty
|
||||
http://www.w3.org/2002/07/owl#DatatypeProperty
|
||||
http://www.w3.org/2002/07/owl#AnnotationProperty
|
||||
http://www.w3.org/2002/07/owl#FunctionalProperty
|
||||
http://www.w3.org/2002/07/owl#InverseFunctionalProperty
|
||||
http://www.w3.org/2002/07/owl#SymmetricProperty
|
||||
http://www.w3.org/2002/07/owl#AsymmetricProperty
|
||||
http://www.w3.org/2002/07/owl#ReflexiveProperty
|
||||
http://www.w3.org/2002/07/owl#IrreflexiveProperty
|
||||
http://www.w3.org/2002/07/owl#TransitiveProperty
|
||||
http://www.w3.org/2002/07/owl#DeprecatedProperty
|
||||
]
|
||||
|> Enum.map(&RDF.iri/1)
|
||||
@property_classes [rdf_iri("Property"), rdfs_iri("ContainerMembershipProperty")] ++
|
||||
Enum.map(~w[
|
||||
ObjectProperty
|
||||
DatatypeProperty
|
||||
AnnotationProperty
|
||||
FunctionalProperty
|
||||
InverseFunctionalProperty
|
||||
SymmetricProperty
|
||||
AsymmetricProperty
|
||||
ReflexiveProperty
|
||||
IrreflexiveProperty
|
||||
TransitiveProperty
|
||||
DeprecatedProperty
|
||||
], &owl_iri/1)
|
||||
|> MapSet.new
|
||||
|
||||
@dialyzer {:nowarn_function, property_by_rdf_type?: 1}
|
||||
|
|
1
mix.exs
1
mix.exs
|
@ -67,6 +67,7 @@ defmodule RDF.Mixfile do
|
|||
|
||||
defp deps do
|
||||
[
|
||||
{:xsd, path: "../../../RDF.ex/src/xsd"},
|
||||
{:decimal, "~> 1.5"},
|
||||
|
||||
{:credo, "~> 1.3", only: [:dev, :test], runtime: false},
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
defmodule RDF.Datatype.Test.Case do
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
use RDF.Vocabulary.Namespace
|
||||
defvocab EX,
|
||||
base_iri: "http://example.com/",
|
||||
terms: [], strict: false
|
||||
|
||||
alias RDF.{Literal, Datatype}
|
||||
|
||||
|
||||
def dt(value) do
|
||||
RDF.DateTime.convert(value, %{})
|
||||
end
|
||||
|
||||
|
||||
using(opts) do
|
||||
datatype = Keyword.fetch!(opts, :datatype)
|
||||
datatype_id = Keyword.fetch!(opts, :id)
|
||||
valid = Keyword.get(opts, :valid)
|
||||
invalid = Keyword.get(opts, :invalid)
|
||||
|
||||
allow_language = Keyword.get(opts, :allow_language, false)
|
||||
|
||||
quote do
|
||||
alias RDF.{Literal, Datatype}
|
||||
alias RDF.NS.XSD
|
||||
|
||||
alias unquote(datatype)
|
||||
alias unquote(__MODULE__).EX
|
||||
|
||||
import unquote(__MODULE__)
|
||||
|
||||
doctest unquote(datatype)
|
||||
|
||||
@moduletag datatype: unquote(datatype)
|
||||
|
||||
if unquote(valid) do
|
||||
@valid unquote(valid)
|
||||
@invalid unquote(invalid)
|
||||
|
||||
test "RDF.Datatype mapping" do
|
||||
assert RDF.Datatype.mapping[unquote(datatype_id)] == unquote(datatype)
|
||||
end
|
||||
|
||||
describe "general new" do
|
||||
Enum.each @valid, fn {input, {value, lexical, _}} ->
|
||||
expected_literal =
|
||||
%Literal{value: value, uncanonical_lexical: lexical, datatype: unquote(datatype_id), language: nil}
|
||||
@tag example: %{input: input, output: expected_literal}
|
||||
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_literal =
|
||||
%Literal{uncanonical_lexical: to_string(value), datatype: unquote(datatype_id), language: nil}
|
||||
@tag example: %{input: value, output: expected_literal}
|
||||
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) |> Literal.canonical)
|
||||
end
|
||||
Enum.each @invalid, fn input ->
|
||||
assert unquote(datatype).new(input, canonicalize: true) ==
|
||||
(unquote(datatype).new(input) |> Literal.canonical)
|
||||
end
|
||||
end
|
||||
|
||||
test "datatype option is ignored" do
|
||||
Enum.each Datatype.ids, fn id ->
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert unquote(datatype).new(input, datatype: id) == unquote(datatype).new(input)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless unquote(allow_language) do
|
||||
test "language option is ignored" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert unquote(datatype).new(input, language: "en") == unquote(datatype).new(input)
|
||||
end
|
||||
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, datatype: unquote(datatype_id)) == 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 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) |> Literal.lexical) == example.lexical
|
||||
end
|
||||
end
|
||||
|
||||
Enum.each @invalid, fn value ->
|
||||
lexical = to_string(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) |> Literal.lexical) == example.lexical
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "general canonicalization" do
|
||||
Enum.each @valid, fn {input, {value, _, _}} ->
|
||||
expected_literal = %Literal{value: value, datatype: unquote(datatype_id)}
|
||||
@tag example: %{input: input, output: expected_literal}
|
||||
test "#{unquote(datatype)} #{inspect input}",
|
||||
%{example: example} do
|
||||
assert (unquote(datatype).new(example.input) |> Literal.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: 9} is #{inspect canonicalized}",
|
||||
%{example: example} do
|
||||
assert (unquote(datatype).new(example.input) |> Literal.canonical |> Literal.lexical) ==
|
||||
example.canonicalized
|
||||
end
|
||||
end
|
||||
|
||||
test "does not change the Literal when it is invalid" do
|
||||
Enum.each @invalid, fn value ->
|
||||
assert (unquote(datatype).new(value) |> Literal.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 Literal.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 Literal.valid? unquote(datatype).new(value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,16 +1,19 @@
|
|||
defmodule RDF.TestLiterals do
|
||||
|
||||
alias RDF.{Literal}
|
||||
alias RDF.Literal
|
||||
alias RDF.NS.XSD
|
||||
|
||||
def value(:empty), do: [""]
|
||||
def value(:plain), do: ["Hello"]
|
||||
def value(:empty_lang), do: ["", [language: "en"]]
|
||||
def value(:plain_lang), do: ["Hello", [language: "en"]]
|
||||
def value(:string), do: ["String", [datatype: XSD.string]]
|
||||
def value(:typed_string), do: ["String", [datatype: XSD.string]]
|
||||
def value(:uri), do: [URI.parse("http://example.com")]
|
||||
def value(:true), do: [true]
|
||||
def value(:false), do: [false]
|
||||
def value(:int), do: [123]
|
||||
def value(:neg_int), do: [-123]
|
||||
def value(:decimal), do: [Decimal.from_float(3.14)]
|
||||
def value(:long), do: [9223372036854775807]
|
||||
def value(:double), do: [3.1415]
|
||||
def value(:date), do: [~D[2017-04-13]]
|
||||
|
@ -22,7 +25,7 @@ defmodule RDF.TestLiterals do
|
|||
end
|
||||
|
||||
def values(:all_simple),
|
||||
do: Enum.map(~W(empty plain string)a, &value/1)
|
||||
do: Enum.map(~W(empty plain typed_string)a, &value/1)
|
||||
def values(:all_plain_lang),
|
||||
do: Enum.map(~W[empty_lang plain_lang]a, &value/1)
|
||||
def values(:all_native),
|
||||
|
|
|
@ -473,7 +473,7 @@ defmodule RDF.DataTest do
|
|||
RDF.Term.value(RDF.iri(EX.O2))
|
||||
],
|
||||
RDF.Term.value(EX.p2) => [RDF.Term.value(RDF.iri(EX.O3))],
|
||||
RDF.Term.value(EX.p3) => ["_:foo", RDF.Term.value(RDF.iri(EX.O5)), "bar"],
|
||||
RDF.Term.value(EX.p3) => ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))],
|
||||
},
|
||||
RDF.Term.value(RDF.iri(EX.S3)) => %{
|
||||
RDF.Term.value(EX.p3) => [
|
||||
|
@ -519,7 +519,7 @@ defmodule RDF.DataTest do
|
|||
RDF.Term.value(RDF.iri(EX.O2))
|
||||
],
|
||||
p2: [RDF.Term.value(RDF.iri(EX.O3))],
|
||||
p3: ["_:foo", RDF.Term.value(RDF.iri(EX.O5)), "bar"],
|
||||
p3: ["_:foo", "bar", RDF.Term.value(RDF.iri(EX.O5))],
|
||||
},
|
||||
RDF.Term.value(RDF.iri(EX.S3)) => %{
|
||||
p3: [
|
||||
|
|
|
@ -1,245 +0,0 @@
|
|||
defmodule RDF.BooleanTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Boolean, id: RDF.NS.XSD.boolean,
|
||||
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: ~w(foo 10) ++ [42, 3.14, "tRuE", "FaLsE", "true false", "true foo"]
|
||||
|
||||
import RDF.Sigils
|
||||
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{true , "true" },
|
||||
{false , "false"},
|
||||
{1 , "true" },
|
||||
{0 , "false"},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Boolean.new(l) == Boolean.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{"True" , "true" },
|
||||
{"FALSE" , "false"},
|
||||
{"1" , "true" },
|
||||
{"0" , "false"},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Boolean.new(l) != Boolean.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a boolean returns the input as it is" do
|
||||
assert RDF.true |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.false |> RDF.Boolean.cast() == RDF.false
|
||||
end
|
||||
|
||||
test "casting a string with a value from the lexical value space of xsd:boolean" do
|
||||
assert RDF.string("true") |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.string("1") |> RDF.Boolean.cast() == RDF.true
|
||||
|
||||
assert RDF.string("false") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.string("0") |> RDF.Boolean.cast() == RDF.false
|
||||
end
|
||||
|
||||
test "casting a string with a value not in the lexical value space of xsd:boolean" do
|
||||
assert RDF.string("foo") |> RDF.Boolean.cast() == nil
|
||||
end
|
||||
|
||||
test "casting an integer" do
|
||||
assert RDF.integer(0) |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.integer(1) |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.integer(42) |> RDF.Boolean.cast() == RDF.true
|
||||
end
|
||||
|
||||
test "casting a decimal" do
|
||||
assert RDF.decimal(0) |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal(0.0) |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal("+0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal("-0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal("+0.0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal("-0.0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.decimal(0.0e0) |> RDF.Boolean.cast() == RDF.false
|
||||
|
||||
assert RDF.decimal(1) |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.decimal(0.1) |> RDF.Boolean.cast() == RDF.true
|
||||
end
|
||||
|
||||
test "casting a double" do
|
||||
assert RDF.double(0) |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double(0.0) |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("+0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("-0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("+0.0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("-0.0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("0.0E0") |> RDF.Boolean.cast() == RDF.false
|
||||
assert RDF.double("NAN") |> RDF.Boolean.cast() == RDF.false
|
||||
|
||||
assert RDF.double(1) |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.double(0.1) |> RDF.Boolean.cast() == RDF.true
|
||||
assert RDF.double("-INF") |> RDF.Boolean.cast() == RDF.true
|
||||
end
|
||||
|
||||
@tag skip: "TODO: RDF.Float datatype"
|
||||
test "casting a float"
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.boolean("42") |> RDF.Boolean.cast() == nil
|
||||
assert RDF.integer(3.14) |> RDF.Boolean.cast() == nil
|
||||
assert RDF.decimal("NAN") |> RDF.Boolean.cast() == nil
|
||||
assert RDF.double(true) |> RDF.Boolean.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.DateTime.now() |> RDF.Boolean.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Boolean.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "ebv/1" do
|
||||
import RDF.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
|
||||
[
|
||||
RDF.true,
|
||||
RDF.false,
|
||||
RDF.boolean(1),
|
||||
RDF.boolean("0"),
|
||||
]
|
||||
|> Enum.each(fn value ->
|
||||
assert ebv(value) == value
|
||||
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
|
||||
[
|
||||
RDF.boolean(42),
|
||||
RDF.integer(3.14),
|
||||
RDF.double("Foo"),
|
||||
]
|
||||
|> Enum.each(fn value ->
|
||||
assert ebv(value) == RDF.false
|
||||
end)
|
||||
end
|
||||
|
||||
test "if the argument is a plain or xsd:string typed literal, the EBV is false if the operand value has zero length" do
|
||||
assert ebv(~L"") == RDF.false
|
||||
assert ebv(~L""de) == RDF.false
|
||||
end
|
||||
|
||||
test "if the argument is a plain or xsd:string typed literal, the EBV is true if the operand value has length greater zero" do
|
||||
assert ebv(~L"bar") == RDF.true
|
||||
assert ebv(~L"baz"de) == RDF.true
|
||||
end
|
||||
|
||||
test "if the argument is a numeric type with a valid lexical form having the value NaN or being numerically equal to zero, the EBV is false" do
|
||||
[
|
||||
RDF.integer(0),
|
||||
RDF.integer("0"),
|
||||
RDF.double("0"),
|
||||
RDF.double("0.0"),
|
||||
RDF.double(:nan),
|
||||
RDF.double("NaN"),
|
||||
]
|
||||
|> Enum.each(fn value ->
|
||||
assert ebv(value) == RDF.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(RDF.integer(42)) == RDF.true
|
||||
assert ebv(RDF.integer("42")) == RDF.true
|
||||
assert ebv(RDF.double("3.14")) == RDF.true
|
||||
end
|
||||
|
||||
test "Elixirs booleans are treated as RDF.Booleans" do
|
||||
assert ebv(true) == RDF.true
|
||||
assert ebv(false) == RDF.false
|
||||
end
|
||||
|
||||
test "Elixirs strings are treated as RDF.Strings" do
|
||||
assert ebv("") == RDF.false
|
||||
assert ebv("foo") == RDF.true
|
||||
assert ebv("0") == RDF.true
|
||||
end
|
||||
|
||||
test "Elixirs numbers are treated as RDF.Numerics" do
|
||||
assert ebv(0) == RDF.false
|
||||
assert ebv(0.0) == RDF.false
|
||||
|
||||
assert ebv(42) == RDF.true
|
||||
assert ebv(3.14) == RDF.true
|
||||
end
|
||||
|
||||
test "all other arguments, produce nil" do
|
||||
[
|
||||
RDF.date("2010-01-01"),
|
||||
RDF.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
|
||||
[
|
||||
{RDF.true, RDF.true, RDF.true},
|
||||
{RDF.true, RDF.false, RDF.false},
|
||||
{RDF.false, RDF.true, RDF.false},
|
||||
{RDF.false, RDF.false, RDF.false},
|
||||
{RDF.true, nil, nil},
|
||||
{nil, RDF.true, nil},
|
||||
{RDF.false, nil, RDF.false},
|
||||
{nil, RDF.false, RDF.false},
|
||||
{nil, nil, nil},
|
||||
]
|
||||
|> Enum.each(fn {left, right, result} ->
|
||||
assert RDF.Boolean.logical_and(left, right) == result,
|
||||
"expected logical_and(#{inspect left}, #{inspect right}) to be #{inspect result}, but got #{inspect RDF.Boolean.logical_and(left, right)}"
|
||||
end)
|
||||
end
|
||||
|
||||
test "truth-table of logical_or" do
|
||||
[
|
||||
{RDF.true, RDF.true, RDF.true},
|
||||
{RDF.true, RDF.false, RDF.true},
|
||||
{RDF.false, RDF.true, RDF.true},
|
||||
{RDF.false, RDF.false, RDF.false},
|
||||
{RDF.true, nil, RDF.true},
|
||||
{nil, RDF.true, RDF.true},
|
||||
{RDF.false, nil, nil},
|
||||
{nil, RDF.false, nil},
|
||||
{nil, nil, nil},
|
||||
]
|
||||
|> Enum.each(fn {left, right, result} ->
|
||||
assert RDF.Boolean.logical_or(left, right) == result,
|
||||
"expected logical_or(#{inspect left}, #{inspect right}) to be #{inspect result}, but got #{inspect RDF.Boolean.logical_and(left, right)}"
|
||||
end)
|
||||
end
|
||||
|
||||
end
|
|
@ -1,118 +0,0 @@
|
|||
defmodule RDF.DateTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Date, id: RDF.NS.XSD.date,
|
||||
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" },
|
||||
# TODO: Dates on Elixir versions < 1.7.2 don't handle negative years correctly, so we test this conditionally below
|
||||
# "-2010-01-01Z" => { {~D[-2010-01-01], "Z"} , nil , "-2010-01-01Z" },
|
||||
},
|
||||
invalid: ~w(
|
||||
foo
|
||||
+2010-01-01Z
|
||||
2010-01-01TFOO
|
||||
02010-01-01
|
||||
2010-1-1
|
||||
0000-01-01
|
||||
2011-07
|
||||
2011
|
||||
) ++ [true, false, 2010, 3.14, "2010-01-01Z foo", "foo 2010-01-01Z"]
|
||||
|
||||
|
||||
unless Version.compare(System.version(), "1.7.2") == :lt do
|
||||
test "negative years" do
|
||||
assert Date.new("-2010-01-01Z") ==
|
||||
%Literal{value: {~D[-2010-01-01], "Z"}, uncanonical_lexical: nil, datatype: RDF.NS.XSD.date, language: nil}
|
||||
assert (Date.new("-2010-01-01Z") |> Literal.lexical) == "-2010-01-01Z"
|
||||
assert (Date.new("-2010-01-01Z") |> Literal.canonical) == Date.new("-2010-01-01Z")
|
||||
assert Literal.valid? Date.new("-2010-01-01Z")
|
||||
|
||||
assert Date.new("-2010-01-01") ==
|
||||
%Literal{value: ~D[-2010-01-01], uncanonical_lexical: nil, datatype: RDF.NS.XSD.date, language: nil}
|
||||
assert (Date.new("-2010-01-01") |> Literal.lexical) == "-2010-01-01"
|
||||
assert (Date.new("-2010-01-01") |> Literal.canonical) == Date.new("-2010-01-01")
|
||||
assert Literal.valid? Date.new("-2010-01-01")
|
||||
|
||||
assert Date.new("-2010-01-01+00:00") ==
|
||||
%Literal{value: {~D[-2010-01-01], "Z"}, uncanonical_lexical: "-2010-01-01+00:00", datatype: RDF.NS.XSD.date, language: nil}
|
||||
assert (Date.new("-2010-01-01+00:00") |> Literal.lexical) == "-2010-01-01+00:00"
|
||||
assert (Date.new("-2010-01-01+00:00") |> Literal.canonical) == Date.new("-2010-01-01Z")
|
||||
assert Literal.valid? Date.new("-2010-01-01+00:00")
|
||||
end
|
||||
end
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{ ~D[2010-01-01] , "2010-01-01" },
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Date.new(l) == Date.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{ ~D[2010-01-01] , "2010-01-01Z" },
|
||||
{ ~D[2010-01-01] , "2010-01-01+00:00" },
|
||||
{ "2010-01-01" , "00:00:00Z" },
|
||||
{ "2010-01-01+00:00" , "00:00:00Z" },
|
||||
{ "2010-01-01-00:00" , "00:00:00Z" },
|
||||
{ "2010-01-01+00:00" , "00:00:00" },
|
||||
{ "2010-01-01-00:00" , "00:00:00" },
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Date.new(l) != Date.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a date returns the input as it is" do
|
||||
assert RDF.date("2010-01-01") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01")
|
||||
end
|
||||
|
||||
test "casting a string" do
|
||||
assert RDF.string("2010-01-01") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01")
|
||||
assert RDF.string("2010-01-01Z") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01Z")
|
||||
assert RDF.string("2010-01-01+01:00") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01+01:00")
|
||||
end
|
||||
|
||||
test "casting a datetime" do
|
||||
assert RDF.date_time("2010-01-01T01:00:00") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01")
|
||||
assert RDF.date_time("2010-01-01T00:00:00Z") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01Z")
|
||||
assert RDF.date_time("2010-01-01T00:00:00+00:00") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01Z")
|
||||
assert RDF.date_time("2010-01-01T23:00:00+01:00") |> RDF.Date.cast() ==
|
||||
RDF.date("2010-01-01+01:00")
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.date("02010-01-00") |> RDF.Date.cast() == nil
|
||||
assert RDF.date_time("02010-01-01T00:00:00") |> RDF.Date.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.false |> RDF.Date.cast() == nil
|
||||
assert RDF.integer(1) |> RDF.Date.cast() == nil
|
||||
assert RDF.decimal(3.14) |> RDF.Date.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Date.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,184 +0,0 @@
|
|||
defmodule RDF.DateTimeTest do
|
||||
import RDF.Datatype.Test.Case, only: [dt: 1]
|
||||
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.DateTime, id: RDF.NS.XSD.dateTime,
|
||||
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] => { dt( "2010-01-01T00:00:00") , nil , "2010-01-01T00:00:00" },
|
||||
~N[2010-01-01T00:00:00.00] => { dt( "2010-01-01T00:00:00.00") , nil , "2010-01-01T00:00:00.00" },
|
||||
~N[2010-01-01T00:00:00.1234] => { dt( "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" => { dt( "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" },
|
||||
"2009-12-31T24:00:00" => { dt( "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" },
|
||||
# DateTimes on Elixir versions < 1.7.2 don't handle negative years correctly, so we test this conditionally below
|
||||
# "-2010-01-01T00:00:00Z" => { dt("-2010-01-01T00:00:00Z") , nil, "-2010-01-01T00:00:00Z" },
|
||||
},
|
||||
invalid: ~w(
|
||||
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"]
|
||||
|
||||
unless Version.compare(System.version(), "1.7.2") == :lt do
|
||||
test "negative years" do
|
||||
assert DateTime.new("-2010-01-01T00:00:00Z") ==
|
||||
%Literal{value: dt("-2010-01-01T00:00:00Z"), uncanonical_lexical: nil, datatype: RDF.NS.XSD.dateTime, language: nil}
|
||||
assert (DateTime.new("-2010-01-01T00:00:00Z") |> Literal.lexical) == "-2010-01-01T00:00:00Z"
|
||||
assert (DateTime.new("-2010-01-01T00:00:00Z") |> Literal.canonical) ==
|
||||
DateTime.new("-2010-01-01T00:00:00Z")
|
||||
assert Literal.valid? DateTime.new("-2010-01-01T00:00:00Z")
|
||||
|
||||
assert DateTime.new("-2010-01-01T00:00:00+00:00") ==
|
||||
%Literal{value: dt("-2010-01-01T00:00:00Z"), uncanonical_lexical: "-2010-01-01T00:00:00+00:00", datatype: RDF.NS.XSD.dateTime, language: nil}
|
||||
assert (DateTime.new("-2010-01-01T00:00:00+00:00") |> Literal.lexical) == "-2010-01-01T00:00:00+00:00"
|
||||
assert (DateTime.new("-2010-01-01T00:00:00+00:00") |> Literal.canonical) ==
|
||||
DateTime.new("-2010-01-01T00:00:00Z")
|
||||
assert Literal.valid? DateTime.new("-2010-01-01T00:00:00+00:00")
|
||||
end
|
||||
end
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{"2010-01-01T00:00:00Z" , dt("2010-01-01T00:00:00Z")},
|
||||
{"2010-01-01T00:00:00" , ~N[2010-01-01T00:00:00]},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert DateTime.new(l) == DateTime.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{"2010-01-01T00:00:00Z" , "2010-01-01T00:00:00" },
|
||||
{"2010-01-01T00:00:00+00:00", "2010-01-01T00:00:00Z"},
|
||||
{"2010-01-01T00:00:00.0000Z", "2010-01-01T00:00:00Z"},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert DateTime.new(l) != DateTime.new(r)
|
||||
end)
|
||||
end
|
||||
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
|
||||
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
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a datetime returns the input as it is" do
|
||||
assert RDF.date_time("2010-01-01T12:34:56") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T12:34:56")
|
||||
end
|
||||
|
||||
test "casting a string with a value from the lexical value space of xsd:dateTime" do
|
||||
assert RDF.string("2010-01-01T12:34:56") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T12:34:56")
|
||||
assert RDF.string("2010-01-01T12:34:56Z") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T12:34:56Z")
|
||||
assert RDF.string("2010-01-01T12:34:56+01:00") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("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 RDF.string("string") |> RDF.DateTime.cast() == nil
|
||||
assert RDF.string("02010-01-01T00:00:00") |> RDF.DateTime.cast() == nil
|
||||
end
|
||||
|
||||
test "casting a date" do
|
||||
assert RDF.date("2010-01-01") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T00:00:00")
|
||||
assert RDF.date("2010-01-01Z") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T00:00:00Z")
|
||||
assert RDF.date("2010-01-01+00:00") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T00:00:00Z")
|
||||
assert RDF.date("2010-01-01+01:00") |> RDF.DateTime.cast() ==
|
||||
RDF.date_time("2010-01-01T00:00:00+01:00")
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.date_time("02010-01-01T00:00:00") |> RDF.DateTime.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.false |> RDF.DateTime.cast() == nil
|
||||
assert RDF.integer(1) |> RDF.DateTime.cast() == nil
|
||||
assert RDF.decimal(3.14) |> RDF.DateTime.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.DateTime.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
test "canonical_lexical_with_zone/1" do
|
||||
assert RDF.date_time(~N[2010-01-01T12:34:56]) |> DateTime.canonical_lexical_with_zone() == "2010-01-01T12:34:56"
|
||||
assert RDF.date_time("2010-01-01T12:34:56") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T12:34:56"
|
||||
assert RDF.date_time("2010-01-01T00:00:00+00:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T00:00:00Z"
|
||||
assert RDF.date_time("2010-01-01T00:00:00-00:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T00:00:00Z"
|
||||
assert RDF.date_time("2010-01-01T01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T01:00:00+01:00"
|
||||
assert RDF.date_time("2010-01-01 01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T01:00:00+01:00"
|
||||
end
|
||||
|
||||
end
|
|
@ -1,180 +0,0 @@
|
|||
defmodule RDF.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 RDF.Datatype.Test.Case?
|
||||
# alias Elixir.Decimal, as: D
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Decimal, id: RDF.NS.XSD.decimal,
|
||||
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: ~w(foo 10.1e1 12.xyz 3,5 NaN Inf) ++ [true, false, "1.0 foo", "foo 1.0",
|
||||
Elixir.Decimal.new("NaN"), Elixir.Decimal.new("Inf")]
|
||||
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{"1.0" , 1.0},
|
||||
{"-42.0" , -42.0},
|
||||
{"1.0" , 1.0},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Decimal.new(l) == Decimal.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{"1" , 1.0},
|
||||
{"01" , 1.0},
|
||||
{"1.0E0" , 1.0},
|
||||
{"1.0E0" , "1.0"},
|
||||
{"+42" , 42.0},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Decimal.new(l) != Decimal.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a decimal returns the input as it is" do
|
||||
assert RDF.decimal(0) |> RDF.Decimal.cast() == RDF.decimal(0)
|
||||
assert RDF.decimal("-0.0") |> RDF.Decimal.cast() == RDF.decimal("-0.0")
|
||||
assert RDF.decimal(1) |> RDF.Decimal.cast() == RDF.decimal(1)
|
||||
assert RDF.decimal(0.1) |> RDF.Decimal.cast() == RDF.decimal(0.1)
|
||||
end
|
||||
|
||||
test "casting a boolean" do
|
||||
assert RDF.true |> RDF.Decimal.cast() == RDF.decimal(1.0)
|
||||
assert RDF.false |> RDF.Decimal.cast() == RDF.decimal(0.0)
|
||||
end
|
||||
|
||||
test "casting a string with a value from the lexical value space of xsd:decimal" do
|
||||
assert RDF.string("0") |> RDF.Decimal.cast() == RDF.decimal(0)
|
||||
assert RDF.string("3.14") |> RDF.Decimal.cast() == RDF.decimal(3.14)
|
||||
end
|
||||
|
||||
test "casting a string with a value not in the lexical value space of xsd:decimal" do
|
||||
assert RDF.string("foo") |> RDF.Decimal.cast() == nil
|
||||
end
|
||||
|
||||
test "casting an integer" do
|
||||
assert RDF.integer(0) |> RDF.Decimal.cast() == RDF.decimal(0.0)
|
||||
assert RDF.integer(42) |> RDF.Decimal.cast() == RDF.decimal(42.0)
|
||||
end
|
||||
|
||||
test "casting a double" do
|
||||
assert RDF.double(0.0) |> RDF.Decimal.cast() == RDF.decimal(0.0)
|
||||
assert RDF.double("-0.0") |> RDF.Decimal.cast() == RDF.decimal(0.0)
|
||||
assert RDF.double(0.1) |> RDF.Decimal.cast() == RDF.decimal(0.1)
|
||||
assert RDF.double(1) |> RDF.Decimal.cast() == RDF.decimal(1.0)
|
||||
assert RDF.double(3.14) |> RDF.Decimal.cast() == RDF.decimal(3.14)
|
||||
assert RDF.double(10.1e1) |> RDF.Decimal.cast() == RDF.decimal(101.0)
|
||||
|
||||
assert RDF.double("NAN") |> RDF.Decimal.cast() == nil
|
||||
assert RDF.double("+INF") |> RDF.Decimal.cast() == nil
|
||||
end
|
||||
|
||||
@tag skip: "TODO: RDF.Float datatype"
|
||||
test "casting a float"
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.boolean("42") |> RDF.Decimal.cast() == nil
|
||||
assert RDF.integer(3.14) |> RDF.Decimal.cast() == nil
|
||||
assert RDF.decimal("NAN") |> RDF.Decimal.cast() == nil
|
||||
assert RDF.double(true) |> RDF.Decimal.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.DateTime.now() |> RDF.Decimal.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Decimal.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "digit_count/1" do
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("1.2345")) == 5
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("-1.2345")) == 5
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("+1.2345")) == 5
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("01.23450")) == 5
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("01.23450")) == 5
|
||||
assert RDF.Decimal.digit_count(RDF.decimal("NAN")) == nil
|
||||
|
||||
assert RDF.Decimal.digit_count(RDF.integer("2")) == 1
|
||||
assert RDF.Decimal.digit_count(RDF.integer("23")) == 2
|
||||
assert RDF.Decimal.digit_count(RDF.integer("023")) == 2
|
||||
end
|
||||
|
||||
test "fraction_digit_count/1" do
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("1.2345")) == 4
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("-1.2345")) == 4
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("+1.2345")) == 4
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("01.23450")) == 4
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("0.023450")) == 5
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.decimal("NAN")) == nil
|
||||
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.integer("2")) == 0
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.integer("23")) == 0
|
||||
assert RDF.Decimal.fraction_digit_count(RDF.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
|
|
@ -1,113 +0,0 @@
|
|||
defmodule RDF.DoubleTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Double, id: RDF.NS.XSD.double,
|
||||
valid: %{
|
||||
# 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: ~w(foo 12.xyz 1.0ez +INF) ++ [true, false, "1.1e1 foo", "foo 1.1e1"]
|
||||
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{"1.0" , 1.0},
|
||||
{"-42.0" , -42.0},
|
||||
{"1.0" , 1.0},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Double.new(l) == Double.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{"1" , 1.0},
|
||||
{"01" , 1.0},
|
||||
{"1.0E0" , 1.0},
|
||||
{"1.0E0" , "1.0"},
|
||||
{"+42" , 42.0},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Double.new(l) != Double.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a double returns the input as it is" do
|
||||
assert RDF.double(3.14) |> RDF.Double.cast() == RDF.double(3.14)
|
||||
assert RDF.double("NAN") |> RDF.Double.cast() == RDF.double("NAN")
|
||||
assert RDF.double("-INF") |> RDF.Double.cast() == RDF.double("-INF")
|
||||
end
|
||||
|
||||
test "casting a boolean" do
|
||||
assert RDF.true |> RDF.Double.cast() == RDF.double(1.0)
|
||||
assert RDF.false |> RDF.Double.cast() == RDF.double(0.0)
|
||||
end
|
||||
|
||||
test "casting a string with a value from the lexical value space of xsd:double" do
|
||||
assert RDF.string("1.0") |> RDF.Double.cast() == RDF.double("1.0E0")
|
||||
assert RDF.string("3.14") |> RDF.Double.cast() == RDF.double("3.14E0")
|
||||
assert RDF.string("3.14E0") |> RDF.Double.cast() == RDF.double("3.14E0")
|
||||
end
|
||||
|
||||
test "casting a string with a value not in the lexical value space of xsd:double" do
|
||||
assert RDF.string("foo") |> RDF.Double.cast() == nil
|
||||
end
|
||||
|
||||
test "casting an integer" do
|
||||
assert RDF.integer(0) |> RDF.Double.cast() == RDF.double(0.0)
|
||||
assert RDF.integer(42) |> RDF.Double.cast() == RDF.double(42.0)
|
||||
end
|
||||
|
||||
test "casting a decimal" do
|
||||
assert RDF.decimal(0) |> RDF.Double.cast() == RDF.double(0)
|
||||
assert RDF.decimal(1) |> RDF.Double.cast() == RDF.double(1)
|
||||
assert RDF.decimal(3.14) |> RDF.Double.cast() == RDF.double(3.14)
|
||||
end
|
||||
|
||||
@tag skip: "TODO: RDF.Float datatype"
|
||||
test "casting a float"
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.boolean("42") |> RDF.Double.cast() == nil
|
||||
assert RDF.integer(3.14) |> RDF.Double.cast() == nil
|
||||
assert RDF.decimal("NAN") |> RDF.Double.cast() == nil
|
||||
assert RDF.double(true) |> RDF.Double.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.DateTime.now() |> RDF.Double.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Double.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
193
test/unit/datatypes/generic_test.exs
Normal file
193
test/unit/datatypes/generic_test.exs
Normal file
|
@ -0,0 +1,193 @@
|
|||
defmodule RDF.Literal.GenericTest do
|
||||
use ExUnit.Case
|
||||
|
||||
alias RDF.Literal
|
||||
alias RDF.Literal.Generic
|
||||
|
||||
@valid %{
|
||||
# input => { value , datatype }
|
||||
"foo" => { "foo" , "http://example.com/datatype" },
|
||||
}
|
||||
|
||||
describe "new" do
|
||||
test "with value and datatype" do
|
||||
Enum.each @valid, fn {input, {value, datatype}} ->
|
||||
datatype_iri = RDF.iri(datatype)
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype_iri}} =
|
||||
Generic.new(input, datatype: datatype_iri)
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype_iri}} =
|
||||
Generic.new(input, datatype: datatype)
|
||||
end
|
||||
end
|
||||
|
||||
test "with canonicalize opts" do
|
||||
Enum.each @valid, fn {input, {value, datatype}} ->
|
||||
datatype_iri = RDF.iri(datatype)
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: ^datatype_iri}} =
|
||||
Generic.new(input, datatype: datatype, canonicalize: true)
|
||||
end
|
||||
end
|
||||
|
||||
test "without a datatype it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
|
||||
literal = Generic.new(input)
|
||||
assert Generic.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
|
||||
test "with nil as a datatype it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
|
||||
literal = Generic.new(input, datatype: nil)
|
||||
assert Generic.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
|
||||
test "with the empty string as a datatype it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %Generic{value: ^value, datatype: nil}} =
|
||||
literal = Generic.new(input, datatype: "")
|
||||
assert Generic.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "new!" do
|
||||
test "with valid values, it behaves the same as new" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert Generic.new!(input, datatype: datatype) ==
|
||||
Generic.new(input, datatype: datatype)
|
||||
assert Generic.new!(input, datatype: datatype, canonicalize: true) ==
|
||||
Generic.new(input, datatype: datatype, canonicalize: true)
|
||||
end
|
||||
end
|
||||
|
||||
test "without a datatype it raises an error" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert_raise ArgumentError, fn -> Generic.new!(input) end
|
||||
end
|
||||
end
|
||||
|
||||
test "with nil as a datatype it raises an error" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert_raise ArgumentError, fn -> Generic.new!(input, datatype: nil) end
|
||||
end
|
||||
end
|
||||
|
||||
test "with the empty string as a datatype it raises an error" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert_raise ArgumentError, fn -> Generic.new!(input, datatype: "") end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "datatype/1" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.datatype()) == RDF.iri(datatype)
|
||||
end
|
||||
end
|
||||
|
||||
test "language/1" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.language()) == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "value/1" do
|
||||
Enum.each @valid, fn {input, {value, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.value()) == value
|
||||
end
|
||||
end
|
||||
|
||||
test "lexical/1" do
|
||||
Enum.each @valid, fn {input, {value, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.lexical()) == value
|
||||
end
|
||||
end
|
||||
|
||||
test "canonical/1" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.canonical()) ==
|
||||
Generic.new(input, datatype: datatype)
|
||||
end
|
||||
end
|
||||
|
||||
test "canonical?/1" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.canonical?()) == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "valid?/1" do
|
||||
test "with a datatype" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.valid?()) == true
|
||||
end
|
||||
end
|
||||
|
||||
test "without a datatype" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert (Generic.new(input, datatype: nil) |> Generic.valid?()) == false
|
||||
assert (Generic.new(input, datatype: "") |> Generic.valid?()) == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "cast/1" do
|
||||
test "when given a RDF.Literal.Generic literal" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert (Generic.new(input, datatype: datatype) |> Generic.cast()) ==
|
||||
Generic.new(input, datatype: datatype)
|
||||
end
|
||||
end
|
||||
|
||||
test "when given a literal with a datatype which is not castable" do
|
||||
assert RDF.XSD.String.new("foo") |> Generic.cast() == nil
|
||||
assert RDF.XSD.Integer.new(12345) |> Generic.cast() == nil
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.XSD.Integer.new(3.14) |> Generic.cast() == nil
|
||||
assert RDF.XSD.Decimal.new("NAN") |> Generic.cast() == nil
|
||||
assert RDF.XSD.Double.new(true) |> Generic.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-coercible value" do
|
||||
assert Generic.cast(:foo) == nil
|
||||
assert Generic.cast(make_ref()) == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "equal_value?/2" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert Generic.equal_value?(
|
||||
Generic.new(input, datatype: datatype),
|
||||
Generic.new(input, datatype: datatype)) == true
|
||||
end
|
||||
|
||||
assert Generic.equal_value?(
|
||||
Generic.new("foo", datatype: "http://example.com/foo"),
|
||||
Generic.new("foo", datatype: "http://example.com/bar")) == false
|
||||
assert Generic.equal_value?(Generic.new("foo"), Generic.new("foo")) == true
|
||||
assert Generic.equal_value?(Generic.new("foo"), Generic.new("bar")) == false
|
||||
assert Generic.equal_value?(Generic.new("foo", datatype: "foo"), RDF.XSD.String.new("foo")) == false
|
||||
end
|
||||
|
||||
test "compare/2" do
|
||||
Enum.each @valid, fn {input, {_, datatype}} ->
|
||||
assert Generic.compare(
|
||||
Generic.new(input, datatype: datatype),
|
||||
Generic.new(input, datatype: datatype)) == :eq
|
||||
end
|
||||
|
||||
assert Generic.compare(Generic.new("foo", datatype: "en"), Generic.new("bar", datatype: "en")) == :gt
|
||||
assert Generic.compare(Generic.new("bar", datatype: "en"), Generic.new("baz", datatype: "en")) == :lt
|
||||
|
||||
assert Generic.compare(
|
||||
Generic.new("foo", datatype: "en"),
|
||||
Generic.new("foo", datatype: "de")) == nil
|
||||
assert Generic.compare(Generic.new("foo"), Generic.new("foo")) == nil
|
||||
assert Generic.compare(Generic.new("foo"), RDF.XSD.String.new("foo")) == nil
|
||||
end
|
||||
end
|
|
@ -1,110 +0,0 @@
|
|||
defmodule RDF.IntegerTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Integer, id: RDF.NS.XSD.integer,
|
||||
valid: %{
|
||||
# input => { value , lexical , canonicalized }
|
||||
0 => { 0 , nil , "0" },
|
||||
1 => { 1 , nil , "1" },
|
||||
"0" => { 0 , nil , "0" },
|
||||
"1" => { 1 , nil , "1" },
|
||||
"01" => { 1 , "01" , "1" },
|
||||
"0123" => { 123 , "0123" , "123" },
|
||||
+1 => { 1 , nil , "1" },
|
||||
-1 => { -1 , nil , "-1" },
|
||||
"+1" => { 1 , "+1" , "1" },
|
||||
"-1" => { -1 , nil , "-1" },
|
||||
},
|
||||
invalid: ~w(foo 10.1 12xyz) ++ [true, false, 3.14, "1 2", "foo 1", "1 foo"]
|
||||
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting an integer returns the input as it is" do
|
||||
assert RDF.integer(0) |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.integer(1) |> RDF.Integer.cast() == RDF.integer(1)
|
||||
end
|
||||
|
||||
test "casting a boolean" do
|
||||
assert RDF.false |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.true |> RDF.Integer.cast() == RDF.integer(1)
|
||||
end
|
||||
|
||||
test "casting a string with a value from the lexical value space of xsd:integer" do
|
||||
assert RDF.string("0") |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.string("042") |> RDF.Integer.cast() == RDF.integer(42)
|
||||
end
|
||||
|
||||
test "casting a string with a value not in the lexical value space of xsd:integer" do
|
||||
assert RDF.string("foo") |> RDF.Integer.cast() == nil
|
||||
assert RDF.string("3.14") |> RDF.Integer.cast() == nil
|
||||
end
|
||||
|
||||
test "casting an decimal" do
|
||||
assert RDF.decimal(0) |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.decimal(1.0) |> RDF.Integer.cast() == RDF.integer(1)
|
||||
assert RDF.decimal(3.14) |> RDF.Integer.cast() == RDF.integer(3)
|
||||
end
|
||||
|
||||
test "casting a double" do
|
||||
assert RDF.double(0) |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double(0.0) |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double(0.1) |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double("+0") |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double("+0.0") |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double("-0.0") |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double("0.0E0") |> RDF.Integer.cast() == RDF.integer(0)
|
||||
assert RDF.double(1) |> RDF.Integer.cast() == RDF.integer(1)
|
||||
assert RDF.double(3.14) |> RDF.Integer.cast() == RDF.integer(3)
|
||||
|
||||
assert RDF.double("NAN") |> RDF.Integer.cast() == nil
|
||||
assert RDF.double("+INF") |> RDF.Integer.cast() == nil
|
||||
end
|
||||
|
||||
@tag skip: "TODO: RDF.Float datatype"
|
||||
test "casting a float"
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.integer(3.14) |> RDF.Integer.cast() == nil
|
||||
assert RDF.decimal("NAN") |> RDF.Integer.cast() == nil
|
||||
assert RDF.double(true) |> RDF.Integer.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.DateTime.now() |> RDF.Integer.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Integer.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
test "digit_count/1" do
|
||||
assert RDF.Integer.digit_count(RDF.integer("2")) == 1
|
||||
assert RDF.Integer.digit_count(RDF.integer("23")) == 2
|
||||
assert RDF.Integer.digit_count(RDF.integer("023")) == 2
|
||||
assert RDF.Integer.digit_count(RDF.integer("+023")) == 2
|
||||
assert RDF.Integer.digit_count(RDF.integer("-023")) == 2
|
||||
assert RDF.Integer.digit_count(RDF.integer("NaN")) == nil
|
||||
end
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{"1" , 1},
|
||||
{"-42" , -42},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Integer.new(l) == Integer.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{"01" , 1},
|
||||
{"+42" , 42},
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Integer.new(l) != Integer.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,79 +1,73 @@
|
|||
defmodule RDF.LangStringTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.LangString, id: RDF.langString
|
||||
use ExUnit.Case
|
||||
|
||||
alias RDF.{Literal, LangString}
|
||||
alias RDF.XSD
|
||||
|
||||
@valid %{
|
||||
# input => { language , value , lexical , canonicalized }
|
||||
"foo" => { "en" , "foo" , nil , "foo" },
|
||||
0 => { "en" , "0" , nil , "0" },
|
||||
42 => { "en" , "42" , nil , "42" },
|
||||
3.14 => { "en" , "3.14" , nil , "3.14" },
|
||||
true => { "en" , "true" , nil , "true" },
|
||||
false => { "en" , "false" , nil , "false" },
|
||||
# input => { value , language }
|
||||
"foo" => { "foo" , "en" },
|
||||
0 => { "0" , "en" },
|
||||
42 => { "42" , "en" },
|
||||
3.14 => { "3.14" , "en" },
|
||||
true => { "true" , "en" },
|
||||
false => { "false" , "en" },
|
||||
}
|
||||
|
||||
|
||||
describe "new" do
|
||||
Enum.each @valid, fn {input, {language, value, lexical, _}} ->
|
||||
expected_literal =
|
||||
%Literal{value: value, uncanonical_lexical: lexical, datatype: RDF.langString, language: language}
|
||||
@tag example: %{input: input, language: language, output: expected_literal}
|
||||
test "valid: LangString.new(#{inspect input}) == #{inspect expected_literal}",
|
||||
%{example: example} do
|
||||
assert LangString.new(example.input, language: example.language) == example.output
|
||||
test "with value and language" do
|
||||
Enum.each @valid, fn {input, {value, language}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: ^language}} =
|
||||
LangString.new(input, language: language)
|
||||
end
|
||||
end
|
||||
|
||||
# valid value with canonical option
|
||||
Enum.each @valid, fn {input, {language, value, _, _}} ->
|
||||
expected_literal =
|
||||
%Literal{value: value, datatype: RDF.langString, language: language}
|
||||
@tag example: %{input: input, language: language, output: expected_literal}
|
||||
test "valid: LangString.new(#{inspect input}, canonicalize: true) == #{inspect expected_literal}",
|
||||
%{example: example} do
|
||||
assert LangString.new(example.input, language: example.language, canonicalize: true) == example.output
|
||||
test "language get normalized to downcase" do
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: "de"}} =
|
||||
LangString.new(input, language: "DE")
|
||||
end
|
||||
end
|
||||
|
||||
test "datatype option is ignored" do
|
||||
Enum.each Datatype.ids, fn id ->
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert LangString.new(input, language: "en", datatype: id) == LangString.new(input, language: "en")
|
||||
end
|
||||
test "with canonicalize opts" do
|
||||
Enum.each @valid, fn {input, {value, language}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: ^language}} =
|
||||
LangString.new(input, language: language, canonicalize: true)
|
||||
end
|
||||
end
|
||||
|
||||
test "without a language it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert %Literal{} = literal = LangString.new(input)
|
||||
refute Literal.valid?(literal)
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
|
||||
literal = LangString.new(input)
|
||||
assert LangString.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
|
||||
test "with nil as a language it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert %Literal{} = literal = LangString.new(input, language: nil)
|
||||
refute Literal.valid?(literal)
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
|
||||
literal = LangString.new(input, language: nil)
|
||||
assert LangString.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
|
||||
test "with the empty string as a language it produces an invalid literal" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert %Literal{} = literal = LangString.new(input, language: "")
|
||||
refute Literal.valid?(literal)
|
||||
Enum.each @valid, fn {input, {value, _}} ->
|
||||
assert %Literal{literal: %LangString{value: ^value, language: nil}} =
|
||||
literal = LangString.new(input, language: "")
|
||||
assert LangString.valid?(literal) == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "new!" do
|
||||
test "with valid values, it behaves the same as new" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert LangString.new!(input, language: "de") ==
|
||||
LangString.new(input, language: "de")
|
||||
assert LangString.new!(input, language: "de", datatype: RDF.langString) ==
|
||||
LangString.new(input, language: "de")
|
||||
assert LangString.new!(input, language: "de", canonicalize: true) ==
|
||||
LangString.new(input, language: "de", canonicalize: true)
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert LangString.new!(input, language: language) ==
|
||||
LangString.new(input, language: language)
|
||||
assert LangString.new!(input, language: language, canonicalize: true) ==
|
||||
LangString.new(input, language: language, canonicalize: true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -96,57 +90,118 @@ defmodule RDF.LangStringTest do
|
|||
end
|
||||
end
|
||||
|
||||
test "datatype/1" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.datatype()) == RDF.iri(LangString.id())
|
||||
end
|
||||
end
|
||||
|
||||
describe "lexical" do
|
||||
Enum.each @valid, fn {input, {language, _, lexical, canonicalized}} ->
|
||||
lexical = lexical || canonicalized
|
||||
@tag example: %{input: input, language: language, lexical: lexical}
|
||||
test "of valid LangString.new(#{inspect input}) == #{inspect lexical}",
|
||||
%{example: example} do
|
||||
assert (LangString.new(example.input, language: example.language) |> Literal.lexical) == example.lexical
|
||||
test "language/1" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.language()) == language
|
||||
end
|
||||
|
||||
assert (LangString.new("foo", language: nil) |> LangString.language()) == nil
|
||||
assert (LangString.new("foo", language: "") |> LangString.language()) == nil
|
||||
end
|
||||
|
||||
test "value/1" do
|
||||
Enum.each @valid, fn {input, {value, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.value()) == value
|
||||
end
|
||||
end
|
||||
|
||||
test "lexical/1" do
|
||||
Enum.each @valid, fn {input, {value, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.lexical()) == value
|
||||
end
|
||||
end
|
||||
|
||||
test "canonical/1" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.canonical()) ==
|
||||
LangString.new(input, language: language)
|
||||
end
|
||||
end
|
||||
|
||||
test "canonical?/1" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.canonical?()) == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "valid?/1" do
|
||||
test "with a language" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert (LangString.new(input, language: language) |> LangString.valid?()) == true
|
||||
end
|
||||
end
|
||||
|
||||
test "without a language" do
|
||||
Enum.each @valid, fn {input, _} ->
|
||||
assert (LangString.new(input, language: nil) |> LangString.valid?()) == false
|
||||
assert (LangString.new(input, language: "") |> LangString.valid?()) == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "canonicalization" do
|
||||
Enum.each @valid, fn {input, {language, value, _, _}} ->
|
||||
expected_literal =
|
||||
%Literal{value: value, datatype: RDF.langString, language: language}
|
||||
@tag example: %{input: input, language: language, output: expected_literal}
|
||||
test "LangString #{inspect input} is canonicalized #{inspect expected_literal}",
|
||||
%{example: example} do
|
||||
assert (LangString.new(example.input, language: example.language) |> Literal.canonical) == example.output
|
||||
describe "cast/1" do
|
||||
test "when given a RDF.LangString literal" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert LangString.new(input, language: language) |> LangString.cast() ==
|
||||
LangString.new(input, language: language)
|
||||
end
|
||||
end
|
||||
|
||||
Enum.each @valid, fn {input, {language, _, _, canonicalized}} ->
|
||||
@tag example: %{input: input, language: language, canonicalized: canonicalized}
|
||||
test "lexical of canonicalized LangString #{inspect input} is #{inspect canonicalized}",
|
||||
%{example: example} do
|
||||
assert (LangString.new(example.input, language: example.language) |> Literal.canonical |> Literal.lexical) ==
|
||||
example.canonicalized
|
||||
test "when given a literal with a datatype which is not castable" do
|
||||
assert RDF.XSD.String.new("foo") |> LangString.cast() == nil
|
||||
assert RDF.XSD.Integer.new(12345) |> LangString.cast() == nil
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.XSD.Integer.new(3.14) |> LangString.cast() == nil
|
||||
assert RDF.XSD.Decimal.new("NAN") |> LangString.cast() == nil
|
||||
assert RDF.XSD.Double.new(true) |> LangString.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-coercible value" do
|
||||
assert LangString.cast(:foo) == nil
|
||||
assert LangString.cast(make_ref()) == nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "validation" do
|
||||
Enum.each Map.keys(@valid), fn value ->
|
||||
@tag value: value
|
||||
test "#{inspect value} as a RDF.LangString is valid", %{value: value} do
|
||||
assert Literal.valid? LangString.new(value, language: "es")
|
||||
end
|
||||
test "equal_value?/2" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert LangString.equal_value?(
|
||||
LangString.new(input, language: language),
|
||||
LangString.new(input, language: language)) == true
|
||||
end
|
||||
|
||||
test "a RDF.LangString without a language is invalid" do
|
||||
Enum.each @valid, fn {_, {_, value, lexical, _}} ->
|
||||
refute Literal.valid?(
|
||||
%Literal{value: value, uncanonical_lexical: lexical, datatype: RDF.langString})
|
||||
end
|
||||
end
|
||||
assert LangString.equal_value?(
|
||||
LangString.new("foo", language: "en"),
|
||||
LangString.new("foo", language: "de")) == false
|
||||
assert LangString.equal_value?(LangString.new("foo"), LangString.new("foo")) == true
|
||||
assert LangString.equal_value?(LangString.new("foo"), LangString.new("bar")) == false
|
||||
assert LangString.equal_value?(LangString.new("foo"), RDF.XSD.String.new("foo")) == false
|
||||
assert LangString.equal_value?(RDF.XSD.String.new("foo"), LangString.new("foo")) == false
|
||||
end
|
||||
|
||||
test "compare/2" do
|
||||
Enum.each @valid, fn {input, {_, language}} ->
|
||||
assert LangString.compare(
|
||||
LangString.new(input, language: language),
|
||||
LangString.new(input, language: language)) == :eq
|
||||
end
|
||||
|
||||
assert LangString.compare(LangString.new("foo", language: "en"), LangString.new("bar", language: "en")) == :gt
|
||||
assert LangString.compare(LangString.new("bar", language: "en"), LangString.new("baz", language: "en")) == :lt
|
||||
|
||||
assert LangString.compare(
|
||||
LangString.new("foo", language: "en"),
|
||||
LangString.new("foo", language: "de")) == nil
|
||||
assert LangString.compare(LangString.new("foo"), LangString.new("foo")) == nil
|
||||
assert LangString.compare(LangString.new("foo"), RDF.XSD.String.new("foo")) == nil
|
||||
end
|
||||
|
||||
describe "match_language?/2" do
|
||||
@positive_examples [
|
||||
|
@ -195,28 +250,27 @@ defmodule RDF.LangStringTest do
|
|||
refute LangString.match_language?("de", "")
|
||||
end
|
||||
|
||||
test "with a language-tagged literal and a language range" do
|
||||
test "with a RDF.LangString literal and a language range" do
|
||||
Enum.each @positive_examples, fn {language_tag, language_range} ->
|
||||
literal = RDF.lang_string("foo", language: language_tag)
|
||||
literal = LangString.new("foo", language: language_tag)
|
||||
assert LangString.match_language?(literal, language_range),
|
||||
"expected language range #{inspect language_range} to match #{inspect literal}, but it didn't"
|
||||
end
|
||||
Enum.each @negative_examples, fn {language_tag, language_range} ->
|
||||
literal = RDF.lang_string("foo", language: language_tag)
|
||||
literal = LangString.new("foo", language: language_tag)
|
||||
refute LangString.match_language?(literal, language_range),
|
||||
"expected language range #{inspect language_range} to not match #{inspect literal}, but it did"
|
||||
end
|
||||
refute LangString.match_language?(RDF.lang_string("foo", language: ""), "de")
|
||||
refute LangString.match_language?(RDF.lang_string("foo", language: ""), "*")
|
||||
refute LangString.match_language?(RDF.lang_string("foo", language: nil), "de")
|
||||
refute LangString.match_language?(RDF.lang_string("foo", language: nil), "*")
|
||||
refute LangString.match_language?(LangString.new("foo", language: ""), "de")
|
||||
refute LangString.match_language?(LangString.new("foo", language: ""), "*")
|
||||
refute LangString.match_language?(LangString.new("foo", language: nil), "de")
|
||||
refute LangString.match_language?(LangString.new("foo", language: nil), "*")
|
||||
end
|
||||
|
||||
test "with a non-language-tagged literal" do
|
||||
refute RDF.string("42") |> LangString.match_language?("de")
|
||||
refute RDF.string("42") |> LangString.match_language?("")
|
||||
refute RDF.integer("42") |> LangString.match_language?("de")
|
||||
refute XSD.String.new("42") |> LangString.match_language?("de")
|
||||
refute XSD.String.new("42") |> LangString.match_language?("")
|
||||
refute XSD.Integer.new("42") |> LangString.match_language?("de")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -2,544 +2,59 @@ defmodule RDF.NumericTest do
|
|||
use RDF.Test.Case
|
||||
|
||||
alias RDF.Numeric
|
||||
alias RDF.NS.XSD
|
||||
alias Decimal, as: D
|
||||
|
||||
@positive_infinity RDF.double(:positive_infinity)
|
||||
@negative_infinity RDF.double(:negative_infinity)
|
||||
@nan RDF.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?(RDF.double(negative_zero))
|
||||
assert Numeric.negative_zero?(RDF.decimal(negative_zero))
|
||||
end
|
||||
|
||||
refute Numeric.negative_zero?(RDF.double("-0.00001"))
|
||||
refute Numeric.negative_zero?(RDF.decimal("-0.00001"))
|
||||
end
|
||||
|
||||
test "zero?/1" do
|
||||
assert Numeric.zero?(RDF.integer(0))
|
||||
assert Numeric.zero?(RDF.integer("0"))
|
||||
|
||||
~w[
|
||||
0
|
||||
000
|
||||
0.0
|
||||
00.00
|
||||
]
|
||||
|> Enum.each(fn positive_zero ->
|
||||
assert Numeric.zero?(RDF.double(positive_zero))
|
||||
assert Numeric.zero?(RDF.decimal(positive_zero))
|
||||
end)
|
||||
|
||||
Enum.each @negative_zeros, fn negative_zero ->
|
||||
assert Numeric.zero?(RDF.double(negative_zero))
|
||||
assert Numeric.zero?(RDF.decimal(negative_zero))
|
||||
assert Numeric.zero?(RDF.integer(0)) == true
|
||||
assert Numeric.zero?(RDF.string("0")) == false
|
||||
end
|
||||
|
||||
refute Numeric.zero?(RDF.double("-0.00001"))
|
||||
refute Numeric.zero?(RDF.decimal("-0.00001"))
|
||||
test "negative_zero?/1" do
|
||||
assert Numeric.negative_zero?(RDF.double("-0")) == true
|
||||
assert Numeric.negative_zero?(RDF.integer(0)) == false
|
||||
end
|
||||
|
||||
describe "add/2" do
|
||||
test "xsd:integer literal + xsd:integer literal" do
|
||||
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 "xsd:decimal literal + xsd:integer literal" do
|
||||
assert Numeric.add(RDF.decimal(1.1), RDF.integer(2)) == RDF.decimal(3.1)
|
||||
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 "xsd:double literal + xsd:integer literal" do
|
||||
result = Numeric.add(RDF.double(1.1), RDF.integer(2))
|
||||
expected = RDF.double(3.1)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
end
|
||||
|
||||
test "xsd:decimal literal + xsd:double literal" do
|
||||
result = Numeric.add(RDF.decimal(1.1), RDF.double(2.2))
|
||||
expected = RDF.double(3.3)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 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, RDF.double(0)) == @positive_infinity
|
||||
assert Numeric.add(@positive_infinity, RDF.double(3.14)) == @positive_infinity
|
||||
assert Numeric.add(RDF.double(0), @positive_infinity) == @positive_infinity
|
||||
assert Numeric.add(RDF.double(3.14), @positive_infinity) == @positive_infinity
|
||||
|
||||
assert Numeric.add(@negative_infinity, RDF.double(0)) == @negative_infinity
|
||||
assert Numeric.add(@negative_infinity, RDF.double(3.14)) == @negative_infinity
|
||||
assert Numeric.add(RDF.double(0), @negative_infinity) == @negative_infinity
|
||||
assert Numeric.add(RDF.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) == RDF.double(:nan)
|
||||
assert Numeric.add(@negative_infinity, @positive_infinity) == RDF.double(:nan)
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.add(1, 2) == RDF.integer(3)
|
||||
assert Numeric.add(3.14, 42) == RDF.double(45.14)
|
||||
assert RDF.decimal(3.14) |> Numeric.add(42) == RDF.decimal(45.14)
|
||||
assert Numeric.add(42, RDF.decimal(3.14)) == RDF.decimal(45.14)
|
||||
assert Numeric.add(42, ~B"foo") == nil
|
||||
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(RDF.integer(3), RDF.integer(2)) == RDF.integer(1)
|
||||
end
|
||||
|
||||
test "xsd:decimal literal - xsd:integer literal" do
|
||||
assert Numeric.subtract(RDF.decimal(3.3), RDF.integer(2)) == RDF.decimal(1.3)
|
||||
end
|
||||
|
||||
test "xsd:double literal - xsd:integer literal" do
|
||||
result = Numeric.subtract(RDF.double(3.3), RDF.integer(2))
|
||||
expected = RDF.double(1.3)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
end
|
||||
|
||||
test "xsd:decimal literal - xsd:double literal" do
|
||||
result = Numeric.subtract(RDF.decimal(3.3), RDF.double(2.2))
|
||||
expected = RDF.double(1.1)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 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, RDF.double(0)) == @positive_infinity
|
||||
assert Numeric.subtract(@positive_infinity, RDF.double(3.14)) == @positive_infinity
|
||||
assert Numeric.subtract(RDF.double(0), @positive_infinity) == @negative_infinity
|
||||
assert Numeric.subtract(RDF.double(3.14), @positive_infinity) == @negative_infinity
|
||||
|
||||
assert Numeric.subtract(@negative_infinity, RDF.double(0)) == @negative_infinity
|
||||
assert Numeric.subtract(@negative_infinity, RDF.double(3.14)) == @negative_infinity
|
||||
assert Numeric.subtract(RDF.double(0), @negative_infinity) == @positive_infinity
|
||||
assert Numeric.subtract(RDF.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) == RDF.double(:nan)
|
||||
assert Numeric.subtract(@negative_infinity, @negative_infinity) == RDF.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) == RDF.integer(1)
|
||||
assert Numeric.subtract(42, 3.14) == RDF.double(38.86)
|
||||
assert RDF.decimal(3.14) |> Numeric.subtract(42) == RDF.decimal(-38.86)
|
||||
assert Numeric.subtract(42, RDF.decimal(3.14)) == RDF.decimal(38.86)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "multiply/2" do
|
||||
test "xsd:integer literal * xsd:integer literal" do
|
||||
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 "xsd:decimal literal * xsd:integer literal" do
|
||||
assert Numeric.multiply(RDF.decimal(1.5), RDF.integer(3)) == RDF.decimal(4.5)
|
||||
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 "xsd:double literal * xsd:integer literal" do
|
||||
result = Numeric.multiply(RDF.double(1.5), RDF.integer(3))
|
||||
expected = RDF.double(4.5)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
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 "xsd:decimal literal * xsd:double literal" do
|
||||
result = Numeric.multiply(RDF.decimal(0.5), RDF.double(2.5))
|
||||
expected = RDF.double(1.25)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
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 "if one of the operands is a zero and the other is an infinity, NaN is returned" do
|
||||
assert Numeric.multiply(@positive_infinity, RDF.double(0.0)) == @nan
|
||||
assert Numeric.multiply(RDF.integer(0), @positive_infinity) == @nan
|
||||
assert Numeric.multiply(RDF.decimal(0), @positive_infinity) == @nan
|
||||
|
||||
assert Numeric.multiply(@negative_infinity, RDF.double(0)) == @nan
|
||||
assert Numeric.multiply(RDF.integer(0), @negative_infinity) == @nan
|
||||
assert Numeric.multiply(RDF.decimal(0.0), @negative_infinity) == @nan
|
||||
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 "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, RDF.double(3.14)) == @positive_infinity
|
||||
assert Numeric.multiply(RDF.double(3.14), @positive_infinity) == @positive_infinity
|
||||
assert Numeric.multiply(@positive_infinity, RDF.double(-3.14)) == @negative_infinity
|
||||
assert Numeric.multiply(RDF.double(-3.14), @positive_infinity) == @negative_infinity
|
||||
|
||||
assert Numeric.multiply(@negative_infinity, RDF.double(3.14)) == @negative_infinity
|
||||
assert Numeric.multiply(RDF.double(3.14), @negative_infinity) == @negative_infinity
|
||||
assert Numeric.multiply(@negative_infinity, RDF.double(-3.14)) == @positive_infinity
|
||||
assert Numeric.multiply(RDF.double(-3.14), @negative_infinity) == @positive_infinity
|
||||
test "ceil/1" do
|
||||
assert Numeric.ceil(RDF.integer(2)) == RDF.integer(2)
|
||||
assert Numeric.ceil(RDF.double(3.14)) == RDF.double("4")
|
||||
end
|
||||
|
||||
# The following are 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
|
||||
test "floor/1" do
|
||||
assert Numeric.floor(RDF.integer(2)) == RDF.integer(2)
|
||||
assert Numeric.floor(RDF.double(3.14)) == RDF.double("3")
|
||||
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) == RDF.double(:nan)
|
||||
assert Numeric.multiply(@negative_infinity, @positive_infinity) == RDF.double(:nan)
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.multiply(1, 2) == RDF.integer(2)
|
||||
assert Numeric.multiply(2, 1.5) == RDF.double(3.0)
|
||||
assert RDF.decimal(1.5) |> Numeric.multiply(2) == RDF.decimal(3.0)
|
||||
assert Numeric.multiply(2, RDF.decimal(1.5)) == RDF.decimal(3.0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "divide/2" do
|
||||
test "xsd:integer literal / xsd:integer literal" do
|
||||
assert Numeric.divide(RDF.integer(4), RDF.integer(2)) == RDF.decimal(2.0)
|
||||
end
|
||||
|
||||
test "xsd:decimal literal / xsd:integer literal" do
|
||||
assert Numeric.divide(RDF.decimal(4), RDF.integer(2)) == RDF.decimal(2.0)
|
||||
end
|
||||
|
||||
test "xsd:double literal / xsd:integer literal" do
|
||||
result = Numeric.divide(RDF.double(4), RDF.integer(2))
|
||||
expected = RDF.double(2)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
end
|
||||
|
||||
test "xsd:decimal literal / xsd:double literal" do
|
||||
result = Numeric.divide(RDF.decimal(4), RDF.double(2))
|
||||
expected = RDF.double(2)
|
||||
assert result.datatype == expected.datatype
|
||||
assert_in_delta result.value, expected.value, 0.000000000000001
|
||||
end
|
||||
|
||||
test "a positive number divided by positive zero returns INF" do
|
||||
assert Numeric.divide(RDF.double(1.0), RDF.double(0.0)) == @positive_infinity
|
||||
assert Numeric.divide(RDF.double(1.0), RDF.decimal(0.0)) == @positive_infinity
|
||||
assert Numeric.divide(RDF.double(1.0), RDF.integer(0)) == @positive_infinity
|
||||
assert Numeric.divide(RDF.decimal(1.0), RDF.double(0.0)) == @positive_infinity
|
||||
assert Numeric.divide(RDF.integer(1), RDF.double(0.0)) == @positive_infinity
|
||||
end
|
||||
|
||||
test "a negative number divided by positive zero returns -INF" do
|
||||
assert Numeric.divide(RDF.double(-1.0), RDF.double(0.0)) == @negative_infinity
|
||||
assert Numeric.divide(RDF.double(-1.0), RDF.decimal(0.0)) == @negative_infinity
|
||||
assert Numeric.divide(RDF.double(-1.0), RDF.integer(0)) == @negative_infinity
|
||||
assert Numeric.divide(RDF.decimal(-1.0), RDF.double(0.0)) == @negative_infinity
|
||||
assert Numeric.divide(RDF.integer(-1), RDF.double(0.0)) == @negative_infinity
|
||||
end
|
||||
|
||||
test "a positive number divided by negative zero returns -INF" do
|
||||
assert Numeric.divide(RDF.double(1.0), RDF.double("-0.0")) == @negative_infinity
|
||||
assert Numeric.divide(RDF.double(1.0), RDF.decimal("-0.0")) == @negative_infinity
|
||||
assert Numeric.divide(RDF.decimal(1.0), RDF.double("-0.0")) == @negative_infinity
|
||||
assert Numeric.divide(RDF.integer(1), RDF.double("-0.0")) == @negative_infinity
|
||||
end
|
||||
|
||||
test "a negative number divided by negative zero returns INF" do
|
||||
assert Numeric.divide(RDF.double(-1.0), RDF.double("-0.0")) == @positive_infinity
|
||||
assert Numeric.divide(RDF.double(-1.0), RDF.decimal("-0.0")) == @positive_infinity
|
||||
assert Numeric.divide(RDF.decimal(-1.0), RDF.double("-0.0")) == @positive_infinity
|
||||
assert Numeric.divide(RDF.integer(-1), RDF.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(RDF.decimal(1.0), RDF.decimal(0.0)) == nil
|
||||
assert Numeric.divide(RDF.decimal(1.0), RDF.integer(0)) == nil
|
||||
assert Numeric.divide(RDF.decimal(-1.0), RDF.decimal(0.0)) == nil
|
||||
assert Numeric.divide(RDF.decimal(-1.0), RDF.integer(0)) == nil
|
||||
assert Numeric.divide(RDF.integer(1), RDF.integer(0)) == nil
|
||||
assert Numeric.divide(RDF.integer(1), RDF.decimal(0.0)) == nil
|
||||
assert Numeric.divide(RDF.integer(-1), RDF.integer(0)) == nil
|
||||
assert Numeric.divide(RDF.integer(-1), RDF.decimal(0.0)) == nil
|
||||
end
|
||||
|
||||
test "positive or negative zero divided by positive or negative zero returns NaN" do
|
||||
assert Numeric.divide(RDF.double( "-0.0"), RDF.double(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.double( "-0.0"), RDF.decimal(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.double( "-0.0"), RDF.integer(0)) == @nan
|
||||
assert Numeric.divide(RDF.decimal("-0.0"), RDF.double(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.integer("-0"), RDF.double(0.0)) == @nan
|
||||
|
||||
assert Numeric.divide(RDF.double( "0.0"), RDF.double(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.double( "0.0"), RDF.decimal(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.double( "0.0"), RDF.integer(0)) == @nan
|
||||
assert Numeric.divide(RDF.decimal("0.0"), RDF.double(0.0)) == @nan
|
||||
assert Numeric.divide(RDF.integer("0"), RDF.double(0.0)) == @nan
|
||||
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.double( "-0.0")) == @nan
|
||||
assert Numeric.divide(RDF.decimal(0.0), RDF.double( "-0.0")) == @nan
|
||||
assert Numeric.divide(RDF.integer(0) , RDF.double( "-0.0")) == @nan
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.decimal("-0.0")) == @nan
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.integer("-0")) == @nan
|
||||
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.double( "0.0")) == @nan
|
||||
assert Numeric.divide(RDF.decimal(0.0), RDF.double( "0.0")) == @nan
|
||||
assert Numeric.divide(RDF.integer(0) , RDF.double( "0.0")) == @nan
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.decimal("0.0")) == @nan
|
||||
assert Numeric.divide(RDF.double(0.0) , RDF.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) == RDF.decimal(2.0)
|
||||
assert Numeric.divide(4, 2.0) == RDF.double(2.0)
|
||||
assert RDF.decimal(4) |> Numeric.divide(2) == RDF.decimal(2.0)
|
||||
assert Numeric.divide(4, RDF.decimal(2.0)) == RDF.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 RDF.integer(42) |> Numeric.abs() == RDF.integer(42)
|
||||
assert RDF.integer(-42) |> Numeric.abs() == RDF.integer(42)
|
||||
end
|
||||
|
||||
test "with xsd:double" do
|
||||
assert RDF.double(3.14) |> Numeric.abs() == RDF.double(3.14)
|
||||
assert RDF.double(-3.14) |> Numeric.abs() == RDF.double(3.14)
|
||||
assert RDF.double("INF") |> Numeric.abs() == RDF.double("INF")
|
||||
assert RDF.double("-INF") |> Numeric.abs() == RDF.double("INF")
|
||||
assert RDF.double("NAN") |> Numeric.abs() == RDF.double("NAN")
|
||||
end
|
||||
|
||||
test "with xsd:decimal" do
|
||||
assert RDF.decimal(3.14) |> Numeric.abs() == RDF.decimal(3.14)
|
||||
assert RDF.decimal(-3.14) |> Numeric.abs() == RDF.decimal(3.14)
|
||||
end
|
||||
|
||||
@tag skip: "TODO: derived datatypes"
|
||||
test "with derived numerics" do
|
||||
assert RDF.literal(-42, datatype: XSD.byte) |> Numeric.abs() ==
|
||||
RDF.literal(42, datatype: XSD.byte)
|
||||
assert RDF.literal("-42", datatype: XSD.byte) |> Numeric.abs() ==
|
||||
RDF.literal(42, datatype: XSD.byte)
|
||||
assert RDF.literal(-42, datatype: XSD.nonPositiveInteger)
|
||||
|> Numeric.abs() == RDF.integer(42)
|
||||
end
|
||||
|
||||
test "with invalid numeric literals" do
|
||||
assert RDF.integer("-3.14") |> Numeric.abs() == nil
|
||||
assert RDF.double("foo") |> Numeric.abs() == nil
|
||||
assert RDF.decimal("foo") |> Numeric.abs() == nil
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.abs(42) == RDF.integer(42)
|
||||
assert Numeric.abs(-42) == RDF.integer(42)
|
||||
assert Numeric.abs(-3.14) == RDF.double(3.14)
|
||||
assert Numeric.abs(D.from_float(-3.14)) == RDF.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 RDF.integer(42) |> Numeric.round() == RDF.integer(42)
|
||||
assert RDF.integer(-42) |> Numeric.round() == RDF.integer(-42)
|
||||
end
|
||||
|
||||
test "with xsd:double" do
|
||||
assert RDF.double(3.14) |> Numeric.round() == RDF.double(3.0)
|
||||
assert RDF.double(-3.14) |> Numeric.round() == RDF.double(-3.0)
|
||||
assert RDF.double(-2.5) |> Numeric.round() == RDF.double(-2.0)
|
||||
|
||||
assert RDF.double("INF") |> Numeric.round() == RDF.double("INF")
|
||||
assert RDF.double("-INF") |> Numeric.round() == RDF.double("-INF")
|
||||
assert RDF.double("NAN") |> Numeric.round() == RDF.double("NAN")
|
||||
end
|
||||
|
||||
test "with xsd:decimal" do
|
||||
assert RDF.decimal(2.5) |> Numeric.round() == RDF.decimal("3")
|
||||
assert RDF.decimal(2.4999) |> Numeric.round() == RDF.decimal("2")
|
||||
assert RDF.decimal(-2.5) |> Numeric.round() == RDF.decimal("-2")
|
||||
end
|
||||
|
||||
test "with invalid numeric literals" do
|
||||
assert RDF.integer("-3.14") |> Numeric.round() == nil
|
||||
assert RDF.double("foo") |> Numeric.round() == nil
|
||||
assert RDF.decimal("foo") |> Numeric.round() == nil
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.round(-42) == RDF.integer(-42)
|
||||
assert Numeric.round(-3.14) == RDF.double(-3.0)
|
||||
assert Numeric.round(D.from_float(3.14)) == RDF.decimal("3")
|
||||
assert Numeric.round("foo") == nil
|
||||
assert Numeric.round(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "round/2" do
|
||||
test "with xsd:integer" do
|
||||
assert RDF.integer(42) |> Numeric.round(3) == RDF.integer(42)
|
||||
assert RDF.integer(8452) |> Numeric.round(-2) == RDF.integer(8500)
|
||||
assert RDF.integer(85) |> Numeric.round(-1) == RDF.integer(90)
|
||||
assert RDF.integer(-85) |> Numeric.round(-1) == RDF.integer(-80)
|
||||
end
|
||||
|
||||
@tag skip: "TODO: xsd:float"
|
||||
test "with xsd:float"
|
||||
|
||||
test "with xsd:double" do
|
||||
assert RDF.double(3.14) |> Numeric.round(1) == RDF.double(3.1)
|
||||
assert RDF.double(3.1415e0) |> Numeric.round(2) == RDF.double(3.14e0)
|
||||
|
||||
assert RDF.double("INF") |> Numeric.round(1) == RDF.double("INF")
|
||||
assert RDF.double("-INF") |> Numeric.round(2) == RDF.double("-INF")
|
||||
assert RDF.double("NAN") |> Numeric.round(3) == RDF.double("NAN")
|
||||
end
|
||||
|
||||
test "with xsd:decimal" do
|
||||
assert RDF.decimal(1.125) |> Numeric.round(2) == RDF.decimal("1.13")
|
||||
assert RDF.decimal(2.4999) |> Numeric.round(2) == RDF.decimal("2.50")
|
||||
assert RDF.decimal(-2.55) |> Numeric.round(1) == RDF.decimal("-2.5")
|
||||
end
|
||||
|
||||
test "with invalid numeric literals" do
|
||||
assert RDF.integer("-3.14") |> Numeric.round(1) == nil
|
||||
assert RDF.double("foo") |> Numeric.round(2) == nil
|
||||
assert RDF.decimal("foo") |> Numeric.round(3) == nil
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.round(-42, 1) == RDF.integer(-42)
|
||||
assert Numeric.round(-3.14, 1) == RDF.double(-3.1)
|
||||
assert Numeric.round(D.from_float(3.14), 1) == RDF.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 RDF.integer(42) |> Numeric.ceil() == RDF.integer(42)
|
||||
assert RDF.integer(-42) |> Numeric.ceil() == RDF.integer(-42)
|
||||
end
|
||||
|
||||
test "with xsd:double" do
|
||||
assert RDF.double(10.5) |> Numeric.ceil() == RDF.double("11")
|
||||
assert RDF.double(-10.5) |> Numeric.ceil() == RDF.double("-10")
|
||||
|
||||
assert RDF.double("INF") |> Numeric.ceil() == RDF.double("INF")
|
||||
assert RDF.double("-INF") |> Numeric.ceil() == RDF.double("-INF")
|
||||
assert RDF.double("NAN") |> Numeric.ceil() == RDF.double("NAN")
|
||||
end
|
||||
|
||||
test "with xsd:decimal" do
|
||||
assert RDF.decimal(10.5) |> Numeric.ceil() == RDF.decimal("11")
|
||||
assert RDF.decimal(-10.5) |> Numeric.ceil() == RDF.decimal("-10")
|
||||
end
|
||||
|
||||
test "with invalid numeric literals" do
|
||||
assert RDF.integer("-3.14") |> Numeric.ceil() == nil
|
||||
assert RDF.double("foo") |> Numeric.ceil() == nil
|
||||
assert RDF.decimal("foo") |> Numeric.ceil() == nil
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.ceil(-42) == RDF.integer(-42)
|
||||
assert Numeric.ceil(-3.14) == RDF.double("-3")
|
||||
assert Numeric.ceil(D.from_float(3.14)) == RDF.decimal("4")
|
||||
assert Numeric.ceil("foo") == nil
|
||||
assert Numeric.ceil(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "floor/1" do
|
||||
test "with xsd:integer" do
|
||||
assert RDF.integer(42) |> Numeric.floor() == RDF.integer(42)
|
||||
assert RDF.integer(-42) |> Numeric.floor() == RDF.integer(-42)
|
||||
end
|
||||
|
||||
test "with xsd:double" do
|
||||
assert RDF.double(10.5) |> Numeric.floor() == RDF.double("10")
|
||||
assert RDF.double(-10.5) |> Numeric.floor() == RDF.double("-11")
|
||||
|
||||
assert RDF.double("INF") |> Numeric.floor() == RDF.double("INF")
|
||||
assert RDF.double("-INF") |> Numeric.floor() == RDF.double("-INF")
|
||||
assert RDF.double("NAN") |> Numeric.floor() == RDF.double("NAN")
|
||||
end
|
||||
|
||||
test "with xsd:decimal" do
|
||||
assert RDF.decimal(10.5) |> Numeric.floor() == RDF.decimal("10")
|
||||
assert RDF.decimal(-10.5) |> Numeric.floor() == RDF.decimal("-11")
|
||||
end
|
||||
|
||||
test "with invalid numeric literals" do
|
||||
assert RDF.integer("-3.14") |> Numeric.floor() == nil
|
||||
assert RDF.double("foo") |> Numeric.floor() == nil
|
||||
assert RDF.decimal("foo") |> Numeric.floor() == nil
|
||||
end
|
||||
|
||||
test "coercion" do
|
||||
assert Numeric.floor(-42) == RDF.integer(-42)
|
||||
assert Numeric.floor(-3.14) == RDF.double("-4")
|
||||
assert Numeric.floor(D.from_float(3.14)) == RDF.decimal("3")
|
||||
assert Numeric.floor("foo") == nil
|
||||
assert Numeric.floor(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
defmodule RDF.StringTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.String, id: RDF.NS.XSD.string,
|
||||
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: [],
|
||||
allow_language: true
|
||||
|
||||
describe "new" do
|
||||
test "when given a language tag it produces a rdf:langString" do
|
||||
assert RDF.String.new("foo", language: "en") ==
|
||||
RDF.LangString.new("foo", language: "en")
|
||||
end
|
||||
|
||||
test "nil as language is ignored" do
|
||||
assert RDF.String.new("Eule", datatype: XSD.string, language: nil) ==
|
||||
RDF.String.new("Eule", datatype: XSD.string)
|
||||
assert RDF.String.new("Eule", language: nil) ==
|
||||
RDF.String.new("Eule")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "new!" do
|
||||
test "when given a language tag it produces a rdf:langString" do
|
||||
assert RDF.String.new!("foo", language: "en") ==
|
||||
RDF.LangString.new!("foo", language: "en")
|
||||
end
|
||||
|
||||
test "nil as language is ignored" do
|
||||
assert RDF.String.new!("Eule", datatype: XSD.string, language: nil) ==
|
||||
RDF.String.new!("Eule", datatype: XSD.string)
|
||||
assert RDF.String.new!("Eule", language: nil) ==
|
||||
RDF.String.new!("Eule")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a string returns the input as it is" do
|
||||
assert RDF.string("foo") |> RDF.String.cast() == RDF.string("foo")
|
||||
end
|
||||
|
||||
test "casting an integer" do
|
||||
assert RDF.integer(0) |> RDF.String.cast() == RDF.string("0")
|
||||
assert RDF.integer(1) |> RDF.String.cast() == RDF.string("1")
|
||||
end
|
||||
|
||||
test "casting a boolean" do
|
||||
assert RDF.false |> RDF.String.cast() == RDF.string("false")
|
||||
assert RDF.true |> RDF.String.cast() == RDF.string("true")
|
||||
end
|
||||
|
||||
test "casting a decimal" do
|
||||
assert RDF.decimal(0) |> RDF.String.cast() == RDF.string("0")
|
||||
assert RDF.decimal(1.0) |> RDF.String.cast() == RDF.string("1")
|
||||
assert RDF.decimal(3.14) |> RDF.String.cast() == RDF.string("3.14")
|
||||
end
|
||||
|
||||
test "casting a double" do
|
||||
assert RDF.double(0) |> RDF.String.cast() == RDF.string("0")
|
||||
assert RDF.double(0.0) |> RDF.String.cast() == RDF.string("0")
|
||||
assert RDF.double("+0") |> RDF.String.cast() == RDF.string("0")
|
||||
assert RDF.double("-0") |> RDF.String.cast() == RDF.string("-0")
|
||||
assert RDF.double(0.1) |> RDF.String.cast() == RDF.string("0.1")
|
||||
assert RDF.double(3.14) |> RDF.String.cast() == RDF.string("3.14")
|
||||
assert RDF.double(0.000_001) |> RDF.String.cast() == RDF.string("0.000001")
|
||||
assert RDF.double(123_456) |> RDF.String.cast() == RDF.string("123456")
|
||||
assert RDF.double(1_234_567) |> RDF.String.cast() == RDF.string("1.234567E6")
|
||||
assert RDF.double(0.0000001) |> RDF.String.cast() == RDF.string("1.0E-7")
|
||||
assert RDF.double(1.0e-10) |> RDF.String.cast() == RDF.string("1.0E-10")
|
||||
assert RDF.double("1.0e-10") |> RDF.String.cast() == RDF.string("1.0E-10")
|
||||
assert RDF.double(1.26743223e15) |> RDF.String.cast() == RDF.string("1.26743223E15")
|
||||
|
||||
assert RDF.double(:nan) |> RDF.String.cast() == RDF.string("NaN")
|
||||
assert RDF.double(:positive_infinity) |> RDF.String.cast() == RDF.string("INF")
|
||||
assert RDF.double(:negative_infinity) |> RDF.String.cast() == RDF.string("-INF")
|
||||
end
|
||||
|
||||
@tag skip: "TODO: RDF.Float datatype"
|
||||
test "casting a float"
|
||||
|
||||
test "casting a datetime" do
|
||||
assert RDF.date_time(~N[2010-01-01T12:34:56]) |> RDF.String.cast() == RDF.string("2010-01-01T12:34:56")
|
||||
assert RDF.date_time("2010-01-01T00:00:00+00:00") |> RDF.String.cast() == RDF.string("2010-01-01T00:00:00Z")
|
||||
assert RDF.date_time("2010-01-01T01:00:00+01:00") |> RDF.String.cast() == RDF.string("2010-01-01T01:00:00+01:00")
|
||||
assert RDF.date_time("2010-01-01 01:00:00+01:00") |> RDF.String.cast() == RDF.string("2010-01-01T01:00:00+01:00")
|
||||
end
|
||||
|
||||
test "casting a date" do
|
||||
assert RDF.date(~D[2000-01-01]) |> RDF.String.cast() == RDF.string("2000-01-01")
|
||||
assert RDF.date("2000-01-01") |> RDF.String.cast() == RDF.string("2000-01-01")
|
||||
assert RDF.date("2000-01-01+00:00") |> RDF.String.cast() == RDF.string("2000-01-01Z")
|
||||
assert RDF.date("2000-01-01+01:00") |> RDF.String.cast() == RDF.string("2000-01-01+01:00")
|
||||
assert RDF.date("0001-01-01") |> RDF.String.cast() == RDF.string("0001-01-01")
|
||||
unless Version.compare(System.version(), "1.7.2") == :lt do
|
||||
assert RDF.date("-0001-01-01") |> RDF.String.cast() == RDF.string("-0001-01-01")
|
||||
end
|
||||
end
|
||||
|
||||
test "casting a time" do
|
||||
assert RDF.time(~T[00:00:00]) |> RDF.String.cast() == RDF.string("00:00:00")
|
||||
assert RDF.time("00:00:00") |> RDF.String.cast() == RDF.string("00:00:00")
|
||||
assert RDF.time("00:00:00Z") |> RDF.String.cast() == RDF.string("00:00:00Z")
|
||||
assert RDF.time("00:00:00+00:00") |> RDF.String.cast() == RDF.string("00:00:00Z")
|
||||
assert RDF.time("00:00:00+01:00") |> RDF.String.cast() == RDF.string("00:00:00+01:00")
|
||||
end
|
||||
|
||||
test "casting an IRI" do
|
||||
assert RDF.iri("http://example.com") |> RDF.String.cast() == RDF.string("http://example.com")
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.integer(3.14) |> RDF.String.cast() == nil
|
||||
assert RDF.decimal("NAN") |> RDF.String.cast() == nil
|
||||
assert RDF.double(true) |> RDF.String.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.String.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,110 +0,0 @@
|
|||
defmodule RDF.TimeTest do
|
||||
use RDF.Datatype.Test.Case, datatype: RDF.Time, id: RDF.NS.XSD.time,
|
||||
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: ~w(
|
||||
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"]
|
||||
|
||||
|
||||
test "conversion with time zones" do
|
||||
[
|
||||
{ "01:00:00+01:00", ~T[00:00:00] },
|
||||
{ "01:00:00-01:00", ~T[02:00:00] },
|
||||
{ "01:00:00-00:01", ~T[01:01:00] },
|
||||
{ "01:00:00+00:01", ~T[00:59:00] },
|
||||
{ "00:00:00+01:30", ~T[22:30:00] },
|
||||
{ "23:00:00-02:30", ~T[01:30:00] },
|
||||
]
|
||||
|> Enum.each(fn {input, output} ->
|
||||
assert RDF.Time.convert(input, %{}) == {output, true}
|
||||
end)
|
||||
end
|
||||
|
||||
describe "equality" do
|
||||
test "two literals are equal when they have the same datatype and lexical form" do
|
||||
[
|
||||
{ ~T[00:00:00] , "00:00:00" },
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Time.new(l) == Time.new(r)
|
||||
end)
|
||||
end
|
||||
|
||||
test "two literals with same value but different lexical form are not equal" do
|
||||
[
|
||||
{ ~T[00:00:00] , "00:00:00Z" },
|
||||
{ "00:00:00" , "00:00:00Z" },
|
||||
{ "00:00:00.0000" , "00:00:00Z" },
|
||||
{ "00:00:00.0000Z" , "00:00:00Z" },
|
||||
{ "00:00:00+00:00" , "00:00:00Z" },
|
||||
]
|
||||
|> Enum.each(fn {l, r} ->
|
||||
assert Time.new(l) != Time.new(r)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "cast/1" do
|
||||
test "casting a time returns the input as it is" do
|
||||
assert RDF.time("01:00:00") |> RDF.Time.cast() ==
|
||||
RDF.time("01:00:00")
|
||||
end
|
||||
|
||||
test "casting a string" do
|
||||
assert RDF.string("01:00:00") |> RDF.Time.cast() ==
|
||||
RDF.time("01:00:00")
|
||||
assert RDF.string("01:00:00Z") |> RDF.Time.cast() ==
|
||||
RDF.time("01:00:00Z")
|
||||
assert RDF.string("01:00:00+01:00") |> RDF.Time.cast() ==
|
||||
RDF.time("01:00:00+01:00")
|
||||
end
|
||||
|
||||
test "casting a datetime" do
|
||||
assert RDF.date_time("2010-01-01T01:00:00") |> RDF.Time.cast() ==
|
||||
RDF.time("01:00:00")
|
||||
assert RDF.date_time("2010-01-01T00:00:00Z") |> RDF.Time.cast() ==
|
||||
RDF.time("00:00:00Z")
|
||||
assert RDF.date_time("2010-01-01T00:00:00+00:00") |> RDF.Time.cast() ==
|
||||
RDF.time("00:00:00Z")
|
||||
assert RDF.date_time("2010-01-01T23:00:00+01:00") |> RDF.Time.cast() ==
|
||||
RDF.time("23:00:00+01:00")
|
||||
end
|
||||
|
||||
test "with invalid literals" do
|
||||
assert RDF.time("25:00:00") |> RDF.Time.cast() == nil
|
||||
assert RDF.date_time("02010-01-01T00:00:00") |> RDF.Time.cast() == nil
|
||||
end
|
||||
|
||||
test "with literals of unsupported datatypes" do
|
||||
assert RDF.false |> RDF.Time.cast() == nil
|
||||
assert RDF.integer(1) |> RDF.Time.cast() == nil
|
||||
assert RDF.decimal(3.14) |> RDF.Time.cast() == nil
|
||||
end
|
||||
|
||||
test "with non-RDF terms" do
|
||||
assert RDF.Time.cast(:foo) == nil
|
||||
end
|
||||
end
|
||||
|
||||
end
|
178
test/unit/datatypes/xsd_test.exs
Normal file
178
test/unit/datatypes/xsd_test.exs
Normal file
|
@ -0,0 +1,178 @@
|
|||
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.String.cast() == nil
|
||||
assert RDF.XSD.Decimal.new("NAN") |> RDF.XSD.String.cast() == nil
|
||||
assert RDF.XSD.Double.new(true) |> RDF.XSD.String.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
|
||||
end
|
|
@ -1,491 +0,0 @@
|
|||
defmodule RDF.EqualityTest do
|
||||
use RDF.Test.Case
|
||||
|
||||
alias RDF.NS.XSD
|
||||
alias Decimal, as: D
|
||||
|
||||
describe "RDF.IRI" do
|
||||
@term_equal_iris [
|
||||
{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 [
|
||||
{RDF.iri("http://example.com/"),
|
||||
RDF.literal("http://example.com/", datatype: XSD.anyURI)},
|
||||
|
||||
{RDF.literal("http://example.com/", datatype: XSD.anyURI),
|
||||
RDF.iri("http://example.com/")},
|
||||
|
||||
{RDF.literal("http://example.com/", datatype: XSD.anyURI),
|
||||
RDF.literal("http://example.com/", datatype: XSD.anyURI)},
|
||||
]
|
||||
@value_unequal_iris [
|
||||
{RDF.iri("http://example.com/foo"),
|
||||
RDF.literal("http://example.com/bar", datatype: XSD.anyURI)},
|
||||
]
|
||||
@incomparable_iris [
|
||||
{RDF.iri("http://example.com/"), RDF.string("http://example.com/")},
|
||||
]
|
||||
|
||||
test "term equality", do: assert_term_equal @term_equal_iris
|
||||
test "term inequality", do: assert_term_unequal @term_unequal_iris
|
||||
test "value equality", do: assert_value_equal @value_equal_iris
|
||||
test "value inequality", do: assert_value_unequal @value_unequal_iris
|
||||
test "incomparability", do: assert_incomparable @incomparable_iris
|
||||
end
|
||||
|
||||
describe "RDF.BlankNode" do
|
||||
@term_equal_bnodes [
|
||||
{RDF.bnode("foo"), RDF.bnode("foo")},
|
||||
]
|
||||
@term_unequal_bnodes [
|
||||
{RDF.bnode("foo"), RDF.bnode("bar")},
|
||||
]
|
||||
@value_equal_bnodes [
|
||||
]
|
||||
@value_unequal_bnodes [
|
||||
]
|
||||
@incomparable_bnodes [
|
||||
{RDF.bnode("foo"), RDF.string("foo")},
|
||||
{RDF.string("foo"), RDF.bnode("foo")},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_bnodes
|
||||
test "incomparability", do: assert_incomparable @incomparable_bnodes
|
||||
end
|
||||
|
||||
describe "RDF.String and RDF.LangString" do
|
||||
@term_equal_strings [
|
||||
{RDF.string("foo"), RDF.string("foo")},
|
||||
{RDF.lang_string("foo", language: "de"), RDF.lang_string("foo", language: "de")},
|
||||
]
|
||||
@term_unequal_strings [
|
||||
{RDF.string("foo"), RDF.string("bar")},
|
||||
{RDF.lang_string("foo", language: "de"), RDF.lang_string("bar", language: "de")},
|
||||
]
|
||||
@value_equal_strings [
|
||||
]
|
||||
@value_unequal_strings [
|
||||
]
|
||||
@value_equal_strings_by_coercion [
|
||||
{RDF.string("foo"), "foo"},
|
||||
]
|
||||
@value_unequal_strings_by_coercion [
|
||||
{RDF.string("foo"), "bar"},
|
||||
]
|
||||
@incomparable_strings [
|
||||
{RDF.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 inequality", do: assert_term_unequal @term_unequal_strings
|
||||
test "value equality", do: assert_value_equal @value_equal_strings
|
||||
test "value inequality", do: assert_value_unequal @value_unequal_strings
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_strings_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_strings_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_strings
|
||||
end
|
||||
|
||||
describe "RDF.Boolean" do
|
||||
@term_equal_booleans [
|
||||
{RDF.true, RDF.true},
|
||||
{RDF.false, RDF.false},
|
||||
# invalid literals
|
||||
{RDF.boolean("foo"), RDF.boolean("foo")},
|
||||
]
|
||||
@term_unequal_booleans [
|
||||
{RDF.true, RDF.false},
|
||||
{RDF.false, RDF.true},
|
||||
# invalid literals
|
||||
{RDF.boolean("foo"), RDF.boolean("bar")},
|
||||
]
|
||||
@value_equal_booleans [
|
||||
{RDF.true, RDF.boolean("1")},
|
||||
{RDF.boolean(0), RDF.false},
|
||||
# invalid literals
|
||||
{RDF.boolean("foo"), RDF.boolean("foo")},
|
||||
]
|
||||
@value_unequal_booleans [
|
||||
{RDF.true, RDF.boolean("false")},
|
||||
{RDF.boolean(0), RDF.true},
|
||||
# invalid literals
|
||||
{RDF.boolean("foo"), RDF.boolean("bar")},
|
||||
]
|
||||
@value_equal_booleans_by_coercion [
|
||||
{RDF.true, true},
|
||||
{RDF.false, false},
|
||||
]
|
||||
@value_unequal_booleans_by_coercion [
|
||||
{RDF.true, false},
|
||||
{RDF.false, true},
|
||||
]
|
||||
@incomparable_booleans [
|
||||
{RDF.false, nil},
|
||||
{RDF.true, 42},
|
||||
{RDF.true, RDF.string("FALSE")},
|
||||
{RDF.true, RDF.integer(0)},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_booleans
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_booleans_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_booleans_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_booleans
|
||||
end
|
||||
|
||||
describe "RDF.Numeric" do
|
||||
@term_equal_numerics [
|
||||
{RDF.integer(42), RDF.integer(42)},
|
||||
{RDF.integer("042"), RDF.integer("042")},
|
||||
# invalid literals
|
||||
{RDF.integer("foo"), RDF.integer("foo")},
|
||||
{RDF.decimal("foo"), RDF.decimal("foo")},
|
||||
{RDF.double("foo"), RDF.double("foo")},
|
||||
]
|
||||
@term_unequal_numerics [
|
||||
{RDF.integer(1), RDF.integer(2)},
|
||||
# invalid literals
|
||||
{RDF.integer("foo"), RDF.integer("bar")},
|
||||
{RDF.decimal("foo"), RDF.decimal("bar")},
|
||||
{RDF.double("foo"), RDF.double("bar")},
|
||||
]
|
||||
@value_equal_numerics [
|
||||
{RDF.integer("42"), RDF.integer("042")},
|
||||
{RDF.integer("42"), RDF.double("42")},
|
||||
{RDF.integer(42), RDF.double(42.0)},
|
||||
{RDF.integer("42"), RDF.decimal("42")},
|
||||
{RDF.integer(42), RDF.decimal(42.0)},
|
||||
{RDF.double(3.14), RDF.decimal(3.14)},
|
||||
{RDF.double("+0"), RDF.double("-0")},
|
||||
{RDF.decimal("+0"), RDF.decimal("-0")},
|
||||
# invalid literals
|
||||
{RDF.integer("foo"), RDF.integer("foo")},
|
||||
{RDF.decimal("foo"), RDF.decimal("foo")},
|
||||
{RDF.double("foo"), RDF.double("foo")},
|
||||
]
|
||||
@value_unequal_numerics [
|
||||
{RDF.integer("1"), RDF.double("1.1")},
|
||||
{RDF.integer("1"), RDF.decimal("1.1")},
|
||||
# invalid literals
|
||||
{RDF.integer("foo"), RDF.integer("bar")},
|
||||
{RDF.decimal("foo"), RDF.decimal("bar")},
|
||||
{RDF.double("foo"), RDF.double("bar")},
|
||||
]
|
||||
@value_equal_numerics_by_coercion [
|
||||
{RDF.integer(42), 42},
|
||||
{RDF.integer(42), 42.0},
|
||||
{RDF.integer(42), D.new(42)},
|
||||
{RDF.decimal(42), 42},
|
||||
{RDF.decimal(3.14), 3.14},
|
||||
{RDF.decimal(3.14), D.from_float(3.14)},
|
||||
{RDF.double(42), 42},
|
||||
{RDF.double(3.14), 3.14},
|
||||
{RDF.double(3.14), D.from_float(3.14)},
|
||||
]
|
||||
@value_unequal_numerics_by_coercion [
|
||||
{RDF.integer(3), 3.14},
|
||||
{RDF.integer(3), D.from_float(3.14)},
|
||||
{RDF.double(3.14), 3},
|
||||
{RDF.decimal(3.14), 3},
|
||||
]
|
||||
@incomparable_numerics [
|
||||
{RDF.integer("42"), nil},
|
||||
{RDF.integer("42"), true},
|
||||
{RDF.integer("42"), "42"},
|
||||
{RDF.integer("42"), RDF.string("42")},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_numerics
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_numerics_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_numerics_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_numerics
|
||||
end
|
||||
|
||||
describe "RDF.DateTime" do
|
||||
@term_equal_datetimes [
|
||||
{RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T12:00:00-01:00")},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("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 [
|
||||
{RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("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")},
|
||||
{RDF.date_time("1999-12-31T24:00:00"), RDF.date_time("2000-01-01T00:00:00")},
|
||||
|
||||
{RDF.date_time("2002-04-02T23:00:00Z"), RDF.date_time("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")},
|
||||
{RDF.date_time("2002-04-02T23:00:00+00:00"), RDF.date_time("2002-04-02T23:00:00-00:00")},
|
||||
|
||||
# invalid literals
|
||||
{RDF.date_time("foo"), RDF.date_time("foo")},
|
||||
]
|
||||
@value_unequal_datetimes [
|
||||
{RDF.date_time("2005-04-04T24:00:00"), RDF.date_time("2005-04-04T00:00:00")},
|
||||
# invalid literals
|
||||
{RDF.date_time("foo"), RDF.date_time("bar")},
|
||||
]
|
||||
@value_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)},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), ~N"2002-04-02T12:00:00"},
|
||||
{RDF.date_time("2002-04-02T23:00:00Z"), 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:00Z"), 1)},
|
||||
{RDF.date_time("2002-04-02T23:00:00-00:00"), elem(DateTime.from_iso8601("2002-04-02T23:00:00Z"), 1)},
|
||||
{RDF.date_time("2002-04-02T23:00:00-00:00"), elem(DateTime.from_iso8601("2002-04-02T23:00:00+00:00"), 1)},
|
||||
]
|
||||
@value_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)},
|
||||
]
|
||||
@incomparable_datetimes [
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T12:00:00Z")},
|
||||
{RDF.string("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T12:00:00-01:00")},
|
||||
# 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")},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_datetimes
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_datetimes_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_datetimes_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_datetimes
|
||||
end
|
||||
|
||||
describe "RDF.Date" do
|
||||
@term_equal_dates [
|
||||
{RDF.date("2002-04-02-01:00"), RDF.date("2002-04-02-01:00")},
|
||||
{RDF.date("2002-04-02"), RDF.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 [
|
||||
{RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02+00:00")},
|
||||
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02+00:00")},
|
||||
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02-00:00")},
|
||||
]
|
||||
@value_unequal_dates [
|
||||
{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 [
|
||||
{RDF.date("2002-04-02"), Date.from_iso8601!("2002-04-02")},
|
||||
]
|
||||
@value_unequal_dates_by_coercion [
|
||||
{RDF.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")},
|
||||
{RDF.date("2002-04-03+00:00"), Date.from_iso8601!("2002-04-02")},
|
||||
{RDF.date("2002-04-03-00:00"), Date.from_iso8601!("2002-04-02")},
|
||||
]
|
||||
@incomparable_dates [
|
||||
{RDF.date("2002-04-02"), RDF.string("2002-04-02")},
|
||||
# These are incomparable because of indeterminacy due to missing timezone
|
||||
{RDF.date("2002-04-02Z"), RDF.date("2002-04-02")},
|
||||
{RDF.date("2002-04-02"), RDF.date("2002-04-02Z")},
|
||||
{RDF.date("2002-04-02+00:00"), RDF.date("2002-04-02")},
|
||||
{RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02")},
|
||||
{RDF.date("2002-04-02+01:00"), Date.from_iso8601!("2002-04-02")},
|
||||
{RDF.date("2002-04-02Z"), Date.from_iso8601!("2002-04-02")},
|
||||
{RDF.date("2002-04-02+00:00"), Date.from_iso8601!("2002-04-02")},
|
||||
{RDF.date("2002-04-02-00:00"), Date.from_iso8601!("2002-04-02")},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_dates
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_dates_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_dates_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_dates
|
||||
end
|
||||
|
||||
describe "equality between RDF.Date and RDF.DateTime" do
|
||||
# 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.
|
||||
#
|
||||
# @value_equal_dates_and_datetimes [
|
||||
# {RDF.date("2002-04-02"), RDF.datetime("2002-04-02T00:00:00")},
|
||||
# {RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02")},
|
||||
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00Z")},
|
||||
# {RDF.datetime("2002-04-02T00:00:00Z"), RDF.date("2002-04-02Z")},
|
||||
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00+00:00")},
|
||||
# {RDF.datetime("2002-04-02T00:00:00-00:00"), RDF.date("2002-04-02Z")},
|
||||
# ]
|
||||
# @value_unequal_dates_and_datetimes [
|
||||
# {RDF.date("2002-04-01"), RDF.datetime("2002-04-02T00:00:00")},
|
||||
# {RDF.datetime("2002-04-01T00:00:00"), RDF.date("2002-04-02")},
|
||||
# {RDF.date("2002-04-01Z"), RDF.datetime("2002-04-02T00:00:00Z")},
|
||||
# {RDF.datetime("2002-04-01T00:00:00Z"), RDF.date("2002-04-02Z")},
|
||||
# {RDF.date("2002-04-01Z"), RDF.datetime("2002-04-02T00:00:00+00:00")},
|
||||
# {RDF.datetime("2002-04-01T00:00:00-00:00"), RDF.date("2002-04-02Z")},
|
||||
# ]
|
||||
# @incomparable_dates_and_datetimes [
|
||||
# {RDF.date("2002-04-02Z"), RDF.datetime("2002-04-02T00:00:00")},
|
||||
# {RDF.datetime("2002-04-02T00:00:00Z"), RDF.date("2002-04-02")},
|
||||
# {RDF.date("2002-04-02"), RDF.datetime("2002-04-02T00:00:00Z")},
|
||||
# {RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02Z")},
|
||||
# ]
|
||||
#
|
||||
# test "value equality", do: assert_value_equal @value_equal_dates_and_datetimes
|
||||
# test "value inequality", do: assert_value_unequal @value_unequal_dates_and_datetimes
|
||||
# test "incomparability", do: assert_incomparable @incomparable_dates_and_datetimes
|
||||
|
||||
@value_unequal_dates_and_datetimes [
|
||||
{RDF.date("2002-04-02"), RDF.datetime("2002-04-02T00:00:00")},
|
||||
{RDF.datetime("2002-04-02T00:00:00"), RDF.date("2002-04-02")},
|
||||
{RDF.date("2002-04-01"), RDF.datetime("2002-04-02T00:00:00")},
|
||||
{RDF.datetime("2002-04-01T00:00:00"), RDF.date("2002-04-02")},
|
||||
]
|
||||
|
||||
test "value inequality", do: assert_value_unequal @value_unequal_dates_and_datetimes
|
||||
end
|
||||
|
||||
describe "RDF.Time" do
|
||||
@term_equal_times [
|
||||
{RDF.time("12:00:00+01:00"), RDF.time("12:00:00+01:00")},
|
||||
{RDF.time("12:00:00"), RDF.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_unequal_times [
|
||||
]
|
||||
@value_equal_times_by_coercion [
|
||||
{RDF.time("12:00:00"), Time.from_iso8601!("12:00:00")},
|
||||
]
|
||||
@value_unequal_times_by_coercion [
|
||||
{RDF.time("12:00:00"), Time.from_iso8601!("13:00:00")},
|
||||
]
|
||||
@incomparable_times [
|
||||
{RDF.time("12:00:00"), RDF.string("12:00:00")},
|
||||
]
|
||||
|
||||
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 inequality", do: assert_value_unequal @value_unequal_times
|
||||
test "coerced value equality", do: assert_value_equal @value_equal_times_by_coercion
|
||||
test "coerced value inequality", do: assert_value_unequal @value_unequal_times_by_coercion
|
||||
test "incomparability", do: assert_incomparable @incomparable_times
|
||||
end
|
||||
|
||||
describe "RDF.Literals with unsupported types" do
|
||||
@equal_literals [
|
||||
{RDF.literal("foo", datatype: "http://example.com/datatype"),
|
||||
RDF.literal("foo", datatype: "http://example.com/datatype")},
|
||||
]
|
||||
@unequal_literals [
|
||||
{RDF.literal("foo", datatype: "http://example.com/datatype"),
|
||||
RDF.literal("bar", datatype: "http://example.com/datatype")},
|
||||
]
|
||||
@incomparable_literals [
|
||||
{RDF.literal("foo", datatype: "http://example.com/datatype1"),
|
||||
RDF.literal("foo", datatype: "http://example.com/datatype2")},
|
||||
]
|
||||
|
||||
test "term equality", do: assert_term_equal @equal_literals
|
||||
test "term inequality", do: assert_value_unequal @unequal_literals
|
||||
test "incomparability", do: assert_incomparable @incomparable_literals
|
||||
end
|
||||
|
||||
|
||||
defp assert_term_equal(examples) do
|
||||
Enum.each examples, fn example -> assert_term_equality(example, true) end
|
||||
Enum.each examples, fn example -> assert_value_equality(example, true) end
|
||||
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
|
||||
|
||||
defp assert_value_equal(examples) do
|
||||
Enum.each examples, fn example -> assert_value_equality(example, true) end
|
||||
end
|
||||
|
||||
defp assert_value_unequal(examples) do
|
||||
Enum.each examples, fn example -> assert_value_equality(example, false) end
|
||||
end
|
||||
|
||||
defp assert_incomparable(examples) do
|
||||
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_term_equality({left, right}, expected) do
|
||||
result = RDF.Term.equal?(left, right)
|
||||
assert result == expected, """
|
||||
expected RDF.Term.equal?(
|
||||
#{inspect left},
|
||||
#{inspect right})
|
||||
to be: #{inspect expected}
|
||||
but got: #{inspect result}
|
||||
"""
|
||||
|
||||
result = RDF.Term.equal?(right, left)
|
||||
assert result == expected, """
|
||||
expected RDF.Term.equal?(
|
||||
#{inspect right},
|
||||
#{inspect left})
|
||||
to be: #{inspect expected}
|
||||
but got: #{inspect result}
|
||||
"""
|
||||
end
|
||||
|
||||
defp assert_value_equality({left, right}, expected) do
|
||||
result = RDF.Term.equal_value?(left, right)
|
||||
assert result == expected, """
|
||||
expected RDF.Term.equal_value?(
|
||||
#{inspect left},
|
||||
#{inspect right})
|
||||
to be: #{inspect expected}
|
||||
but got: #{inspect result}
|
||||
"""
|
||||
|
||||
result = RDF.Term.equal_value?(right, left)
|
||||
assert result == expected, """
|
||||
expected RDF.Term.equal_value?(
|
||||
#{inspect right},
|
||||
#{inspect left})
|
||||
to be: #{inspect expected}
|
||||
but got: #{inspect result}
|
||||
"""
|
||||
end
|
||||
|
||||
end
|
|
@ -190,7 +190,7 @@ defmodule RDF.ListTest do
|
|||
RDF.List.from([[1]])
|
||||
assert [nested] = get_in(graph_with_list, [bnode, RDF.first])
|
||||
assert get_in(graph_with_list, [bnode, RDF.rest]) == [RDF.nil]
|
||||
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.Integer.new(1)]
|
||||
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.integer(1)]
|
||||
assert get_in(graph_with_list, [nested, RDF.rest]) == [RDF.nil]
|
||||
|
||||
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 [second] = get_in(graph_with_list, [bnode, RDF.rest])
|
||||
assert [nested] = get_in(graph_with_list, [second, RDF.first])
|
||||
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.Integer.new(1)]
|
||||
assert get_in(graph_with_list, [nested, RDF.first]) == [RDF.integer(1)]
|
||||
assert [nested_second] = get_in(graph_with_list, [nested, RDF.rest])
|
||||
assert get_in(graph_with_list, [nested_second, RDF.first]) == [RDF.Integer.new(2)]
|
||||
assert get_in(graph_with_list, [nested_second, RDF.first]) == [RDF.integer(2)]
|
||||
assert get_in(graph_with_list, [nested_second, RDF.rest]) == [RDF.nil]
|
||||
assert [third] = get_in(graph_with_list, [second, RDF.rest])
|
||||
assert get_in(graph_with_list, [third, RDF.first]) == [~L"bar"]
|
||||
|
@ -273,17 +273,17 @@ defmodule RDF.ListTest do
|
|||
|
||||
test "nested list", %{nested: nested} do
|
||||
assert RDF.List.values(nested) ==
|
||||
[~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)], ~L"bar"]
|
||||
[~L"foo", [RDF.integer(1), RDF.integer(2)], ~L"bar"]
|
||||
|
||||
assert RDF.list(["foo", [1, 2]]) |> RDF.List.values ==
|
||||
[~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
[~L"foo", [RDF.integer(1), RDF.integer(2)]]
|
||||
|
||||
assert RDF.list([[1, 2], "foo"]) |> RDF.List.values ==
|
||||
[[RDF.Integer.new(1), RDF.Integer.new(2)], ~L"foo"]
|
||||
[[RDF.integer(1), RDF.integer(2)], ~L"foo"]
|
||||
|
||||
inner_list = RDF.list([1, 2], head: ~B<inner>)
|
||||
assert RDF.list(["foo", ~B<inner>], graph: inner_list.graph)
|
||||
|> RDF.List.values == [~L"foo", [RDF.Integer.new(1), RDF.Integer.new(2)]]
|
||||
|> RDF.List.values == [~L"foo", [RDF.integer(1), RDF.integer(2)]]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,310 +0,0 @@
|
|||
defmodule RDF.LiteralComparisonTest do
|
||||
use RDF.Test.Case
|
||||
|
||||
describe "RDF.String and RDF.LangString" do
|
||||
@ordered_strings [
|
||||
{"a", "b"},
|
||||
{"0", "1"},
|
||||
]
|
||||
|
||||
test "valid comparisons between string literals" do
|
||||
Enum.each @ordered_strings, fn {left, right} ->
|
||||
assert_order({RDF.string(left), RDF.string(right)})
|
||||
end
|
||||
|
||||
assert_equal {RDF.string("foo"), RDF.string("foo")}
|
||||
end
|
||||
|
||||
test "valid comparisons between language tagged literals" do
|
||||
Enum.each @ordered_strings, fn {left, right} ->
|
||||
assert_order({RDF.lang_string(left, language: "en"), RDF.lang_string(right, language: "en")})
|
||||
end
|
||||
|
||||
assert_equal {RDF.lang_string("foo", language: "en"), RDF.lang_string("foo", language: "en")}
|
||||
end
|
||||
|
||||
test "invalid comparisons between string and language tagged literals" do
|
||||
Enum.each @ordered_strings, fn {left, right} ->
|
||||
assert_incomparable({RDF.string(left), RDF.lang_string(right, language: "en")})
|
||||
end
|
||||
|
||||
assert_incomparable {RDF.string("foo"), RDF.lang_string("foo", language: "en")}
|
||||
end
|
||||
|
||||
test "invalid comparisons between language tagged literals of different languages" do
|
||||
Enum.each @ordered_strings, fn {left, right} ->
|
||||
assert_incomparable({RDF.lang_string(left, language: "en"), RDF.lang_string(right, language: "de")})
|
||||
end
|
||||
|
||||
assert_incomparable {RDF.lang_string("foo", language: "en"), RDF.lang_string("foo", language: "de")}
|
||||
end
|
||||
end
|
||||
|
||||
describe "RDF.Boolean comparisons" do
|
||||
test "when unequal" do
|
||||
assert_order {RDF.false, RDF.true}
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
assert_equal {RDF.false, RDF.false}
|
||||
assert_equal {RDF.true, RDF.true}
|
||||
end
|
||||
end
|
||||
|
||||
describe "RDF.Numeric comparisons" do
|
||||
test "when unequal" do
|
||||
Enum.each [
|
||||
{RDF.integer(0), RDF.integer(1)},
|
||||
{RDF.integer("3"), RDF.integer("007")},
|
||||
{RDF.double(1.1), RDF.double(2.2)},
|
||||
{RDF.decimal(1.1), RDF.decimal(2.2)},
|
||||
{RDF.decimal(1.1), RDF.double(2.2)},
|
||||
{RDF.double(3.14), RDF.integer(42)},
|
||||
{RDF.decimal(3.14), RDF.integer(42)},
|
||||
# TODO: We need support for other derived numeric datatypes
|
||||
# {RDF.literal(0, datatype: XSD.byte), RDF.integer(1)},
|
||||
], &assert_order/1
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
Enum.each [
|
||||
{RDF.integer(42), RDF.integer(42)},
|
||||
{RDF.integer("42"), RDF.integer("042")},
|
||||
{RDF.integer("42"), RDF.double("42")},
|
||||
{RDF.integer(42), RDF.double(42.0)},
|
||||
{RDF.integer("42"), RDF.decimal("42")},
|
||||
{RDF.integer(42), RDF.decimal(42.0)},
|
||||
{RDF.double(3.14), RDF.decimal(3.14)},
|
||||
{RDF.double("+0"), RDF.double("-0")},
|
||||
{RDF.decimal("+0"), RDF.decimal("-0")},
|
||||
], &assert_equal/1
|
||||
end
|
||||
end
|
||||
|
||||
describe "RDF.DateTime comparisons" do
|
||||
test "when unequal" do
|
||||
assert_order {RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T17:00:00")}
|
||||
assert_order {RDF.date_time("2002-04-02T12:00:00+01:00"), RDF.date_time("2002-04-02T12:00:00+00:00")}
|
||||
assert_order {RDF.date_time("2000-01-15T12:00:00"), RDF.date_time("2000-01-16T12:00:00Z")}
|
||||
end
|
||||
|
||||
test "when unequal due to missing time zone" do
|
||||
assert_order {RDF.date_time("2000-01-15T00:00:00"), RDF.date_time("2000-02-15T00:00:00")}
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
assert_equal {RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T12:00:00-01:00")}
|
||||
assert_equal {RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T12:00:00")}
|
||||
assert_equal {RDF.date_time("2002-04-02T12:00:00-01:00"), RDF.date_time("2002-04-02T17:00:00+04:00")}
|
||||
assert_equal {RDF.date_time("2002-04-02T23:00:00-04:00"), RDF.date_time("2002-04-03T02:00:00-01:00")}
|
||||
assert_equal {RDF.date_time("1999-12-31T24:00:00"), RDF.date_time("2000-01-01T00:00:00")}
|
||||
# TODO: Assume that the dynamic context provides an implicit timezone value of -05:00
|
||||
# assert_equal {RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T23:00:00+06:00")}
|
||||
end
|
||||
|
||||
test "when indeterminate" do
|
||||
assert_indeterminate {RDF.date_time("2000-01-01T12:00:00"), RDF.date_time("1999-12-31T23:00:00Z")}
|
||||
assert_indeterminate {RDF.date_time("2000-01-16T12:00:00"), RDF.date_time("2000-01-16T12:00:00Z")}
|
||||
assert_indeterminate {RDF.date_time("2000-01-16T00:00:00"), RDF.date_time("2000-01-16T12:00:00Z")}
|
||||
end
|
||||
end
|
||||
|
||||
describe "RDF.Date comparisons" do
|
||||
test "when unequal" do
|
||||
assert_order {RDF.date("2002-04-02"), RDF.date("2002-04-03")}
|
||||
assert_order {RDF.date("2002-04-02+01:00"), RDF.date("2002-04-03+00:00")}
|
||||
assert_order {RDF.date("2002-04-02"), RDF.date("2002-04-03Z")}
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
assert_equal {RDF.date("2002-04-02-01:00"), RDF.date("2002-04-02-01:00")}
|
||||
assert_equal {RDF.date("2002-04-02"), RDF.date("2002-04-02")}
|
||||
# TODO:
|
||||
assert_equal {RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02+00:00")}
|
||||
assert_equal {RDF.date("2002-04-02Z"), RDF.date("2002-04-02+00:00")}
|
||||
assert_equal {RDF.date("2002-04-02Z"), RDF.date("2002-04-02-00:00")}
|
||||
end
|
||||
|
||||
test "when indeterminate" do
|
||||
assert_indeterminate {RDF.date("2002-04-02Z"), RDF.date("2002-04-02")}
|
||||
assert_indeterminate {RDF.date("2002-04-02+00:00"), RDF.date("2002-04-02")}
|
||||
assert_indeterminate {RDF.date("2002-04-02-00:00"), RDF.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 RDF.DateTime between RDF.Date and RDF.DateTime" do
|
||||
# test "when unequal" do
|
||||
# # without timezone
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00"), RDF.date("2000-02-15")}
|
||||
# assert_order {RDF.date("2000-01-15"), RDF.date_time("2000-01-15T00:00:01")}
|
||||
# # with timezone
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00"), RDF.date("2000-02-15")}
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00"), RDF.date("2000-02-15Z")}
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00"), RDF.date("2000-02-15+01:00")}
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00Z"), RDF.date("2000-02-15")}
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00Z"), RDF.date("2000-02-15Z")}
|
||||
# assert_order {RDF.date_time("2000-01-14T00:00:00Z"), RDF.date("2000-02-15+01:00")}
|
||||
# end
|
||||
#
|
||||
# test "when equal" do
|
||||
# assert_equal {RDF.date_time("2000-01-15T00:00:00"), RDF.date("2000-01-15")}
|
||||
# assert_equal {RDF.date_time("2000-01-15T00:00:00Z"), RDF.date("2000-01-15Z")}
|
||||
# assert_equal {RDF.date_time("2000-01-15T00:00:00Z"), RDF.date("2000-01-15+00:00")}
|
||||
# assert_equal {RDF.date_time("2000-01-15T00:00:00Z"), RDF.date("2000-01-15-00:00")}
|
||||
# end
|
||||
#
|
||||
# test "when indeterminate" do
|
||||
# assert_indeterminate {RDF.date_time("2000-01-15T00:00:00"), RDF.date("2000-01-15Z")}
|
||||
# assert_indeterminate {RDF.date_time("2000-01-15T00:00:00Z"), RDF.date("2000-01-15")}
|
||||
# end
|
||||
# end
|
||||
|
||||
describe "RDF.Time comparisons" do
|
||||
test "when unequal" do
|
||||
assert_order {RDF.time("12:00:00+01:00"), RDF.time("13:00:00+01:00")}
|
||||
assert_order {RDF.time("12:00:00"), RDF.time("13:00:00")}
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
assert_equal {RDF.time("12:00:00+01:00"), RDF.time("12:00:00+01:00")}
|
||||
assert_equal {RDF.time("12:00:00"), RDF.time("12:00:00")}
|
||||
end
|
||||
|
||||
test "when indeterminate" do
|
||||
assert_indeterminate {RDF.date("2002-04-02Z"), RDF.date("2002-04-02")}
|
||||
assert_indeterminate {RDF.date("2002-04-02+00:00"), RDF.date("2002-04-02")}
|
||||
assert_indeterminate {RDF.date("2002-04-02-00:00"), RDF.date("2002-04-02")}
|
||||
end
|
||||
end
|
||||
|
||||
describe "comparisons on RDF.Literals with unsupported types" do
|
||||
test "when unequal" do
|
||||
assert_order {RDF.literal("a", datatype: "http://example.com/datatype"),
|
||||
RDF.literal("b", datatype: "http://example.com/datatype")}
|
||||
end
|
||||
|
||||
test "when equal" do
|
||||
assert_equal {RDF.literal("a", datatype: "http://example.com/datatype"),
|
||||
RDF.literal("a", datatype: "http://example.com/datatype")}
|
||||
end
|
||||
end
|
||||
|
||||
describe "incomparable " do
|
||||
test "when comparing incomparable types" do
|
||||
Enum.each [
|
||||
{RDF.string("http://example.com/"), RDF.iri("http://example.com/")},
|
||||
{RDF.string("foo"), RDF.bnode("foo")},
|
||||
{RDF.string("true"), RDF.true},
|
||||
{RDF.string("42"), RDF.integer(42)},
|
||||
{RDF.string("3.14"), RDF.decimal(3.14)},
|
||||
{RDF.string("2002-04-02T12:00:00"), RDF.date_time("2002-04-02T12:00:00")},
|
||||
{RDF.string("2002-04-02"), RDF.date("2002-04-02")},
|
||||
{RDF.string("12:00:00"), RDF.time("12:00:00")},
|
||||
{RDF.false, nil},
|
||||
{RDF.true, RDF.integer(42)},
|
||||
{RDF.true, RDF.decimal(3.14)},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.true},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.integer(42)},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.decimal(3.14)},
|
||||
{RDF.date("2002-04-02"), RDF.true},
|
||||
{RDF.date("2002-04-02"), RDF.integer(42)},
|
||||
{RDF.date("2002-04-02"), RDF.decimal(3.14)},
|
||||
{RDF.time("12:00:00"), RDF.true},
|
||||
{RDF.time("12:00:00"), RDF.integer(42)},
|
||||
{RDF.time("12:00:00"), RDF.decimal(3.14)},
|
||||
], &assert_incomparable/1
|
||||
end
|
||||
|
||||
test "when comparing invalid literals" do
|
||||
Enum.each [
|
||||
{RDF.true, RDF.boolean(42)},
|
||||
{RDF.date_time("2002-04-02T12:00:00"), RDF.date_time("2002.04.02 12:00")},
|
||||
{RDF.date("2002-04-02"), RDF.date("2002.04.02")},
|
||||
{RDF.time("12:00:00"), RDF.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}, nil)
|
||||
assert_greater_than({right, left}, nil)
|
||||
|
||||
assert_less_than({left, right}, nil)
|
||||
assert_less_than({right, left}, nil)
|
||||
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
|
|
@ -4,141 +4,161 @@ defmodule RDF.LiteralTest do
|
|||
import RDF.Sigils
|
||||
import RDF.TestLiterals
|
||||
|
||||
alias RDF.Literal
|
||||
alias RDF.NS.XSD
|
||||
alias RDF.{Literal, LangString}
|
||||
alias RDF.Literal.{Generic, Datatype}
|
||||
|
||||
doctest RDF.Literal
|
||||
|
||||
alias RDF.NS
|
||||
|
||||
@examples %{
|
||||
RDF.String => ["foo"],
|
||||
RDF.Integer => [42],
|
||||
RDF.Double => [3.14],
|
||||
RDF.Decimal => [Decimal.from_float(3.14)],
|
||||
RDF.Boolean => [true, false],
|
||||
RDF.XSD.String => ["foo"],
|
||||
RDF.XSD.Integer => [42],
|
||||
RDF.XSD.Double => [3.14],
|
||||
RDF.XSD.Decimal => [Decimal.from_float(3.14)],
|
||||
RDF.XSD.Boolean => [true, false],
|
||||
}
|
||||
|
||||
|
||||
describe "construction by type inference" do
|
||||
describe "new/1" do
|
||||
Enum.each @examples, fn {datatype, example_values} ->
|
||||
@tag example: %{datatype: datatype, values: example_values}
|
||||
test (datatype |> Module.split |> List.last |> to_string), %{example: example} do
|
||||
test "coercion from #{datatype |> Module.split |> List.last |> to_string}", %{example: example} do
|
||||
Enum.each example.values, fn example_value ->
|
||||
assert Literal.new(example_value) == example.datatype.new(example_value)
|
||||
assert Literal.new!(example_value) == example.datatype.new!(example_value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "with typed literals" do
|
||||
Enum.each Datatype.Registry.datatypes(), fn datatype ->
|
||||
literal_type = datatype.literal_type()
|
||||
assert %Literal{literal: typed_literal} = Literal.new(literal_type.new("foo"))
|
||||
assert typed_literal.__struct__ == literal_type
|
||||
end
|
||||
end
|
||||
|
||||
test "when options without datatype given" do
|
||||
assert Literal.new(true, %{}) == RDF.Boolean.new(true)
|
||||
assert Literal.new(42, %{}) == RDF.Integer.new(42)
|
||||
assert Literal.new(true, []) == RDF.XSD.Boolean.new(true)
|
||||
assert Literal.new(42, []) == RDF.XSD.Integer.new(42)
|
||||
assert Literal.new!(true, []) == RDF.XSD.Boolean.new!(true)
|
||||
assert Literal.new!(42, []) == RDF.XSD.Integer.new!(42)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "typed construction" do
|
||||
test "boolean" do
|
||||
assert Literal.new(true, datatype: XSD.boolean) == RDF.Boolean.new(true)
|
||||
assert Literal.new(false, datatype: XSD.boolean) == RDF.Boolean.new(false)
|
||||
assert Literal.new("true", datatype: XSD.boolean) == RDF.Boolean.new("true")
|
||||
assert Literal.new("false", datatype: XSD.boolean) == RDF.Boolean.new("false")
|
||||
assert Literal.new(true, datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new(true)
|
||||
assert Literal.new(false, datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new(false)
|
||||
assert Literal.new("true", datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new("true")
|
||||
assert Literal.new("false", datatype: NS.XSD.boolean) == RDF.XSD.Boolean.new("false")
|
||||
end
|
||||
|
||||
test "integer" do
|
||||
assert Literal.new(42, datatype: XSD.integer) == RDF.Integer.new(42)
|
||||
assert Literal.new("42", datatype: XSD.integer) == RDF.Integer.new("42")
|
||||
assert Literal.new(42, datatype: NS.XSD.integer) == RDF.XSD.Integer.new(42)
|
||||
assert Literal.new("42", datatype: NS.XSD.integer) == RDF.XSD.Integer.new("42")
|
||||
end
|
||||
|
||||
test "double" do
|
||||
assert Literal.new(3.14, datatype: XSD.double) == RDF.Double.new(3.14)
|
||||
assert Literal.new("3.14", datatype: XSD.double) == RDF.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) == RDF.XSD.Double.new("3.14")
|
||||
end
|
||||
|
||||
test "decimal" do
|
||||
assert Literal.new(3.14, datatype: XSD.decimal) == RDF.Decimal.new(3.14)
|
||||
assert Literal.new("3.14", datatype: XSD.decimal) == RDF.Decimal.new("3.14")
|
||||
assert Literal.new(Decimal.from_float(3.14), datatype: XSD.decimal) ==
|
||||
RDF.Decimal.new(Decimal.from_float(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) == RDF.XSD.Decimal.new("3.14")
|
||||
assert Literal.new(Decimal.from_float(3.14), datatype: NS.XSD.decimal) ==
|
||||
RDF.XSD.Decimal.new(Decimal.from_float(3.14))
|
||||
end
|
||||
|
||||
test "string" do
|
||||
assert Literal.new("foo", datatype: XSD.string) == RDF.String.new("foo")
|
||||
assert Literal.new("foo", datatype: NS.XSD.string) == RDF.XSD.String.new("foo")
|
||||
end
|
||||
|
||||
test "unmapped/unknown datatype" do
|
||||
literal = Literal.new("custom typed value", datatype: "http://example/dt")
|
||||
assert literal.value == "custom typed value"
|
||||
assert literal.datatype == ~I<http://example/dt>
|
||||
assert Literal.new("custom typed value", datatype: "http://example/dt") ==
|
||||
Generic.new("custom typed value", datatype: "http://example/dt")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
describe "language tagged construction" do
|
||||
test "string literal with a language tag" do
|
||||
literal = Literal.new("Eule", language: "de")
|
||||
assert literal.value == "Eule"
|
||||
assert literal.datatype == RDF.langString
|
||||
assert literal.language == "de"
|
||||
assert Literal.new("Eule", language: "de") == LangString.new("Eule", language: "de")
|
||||
assert Literal.new!("Eule", language: "de") == LangString.new!("Eule", language: "de")
|
||||
end
|
||||
|
||||
test "language is ignored on non-string literals" do
|
||||
literal = Literal.new(1, language: "de")
|
||||
assert literal.value == 1
|
||||
assert literal.datatype == XSD.integer
|
||||
refute literal.language
|
||||
test "non-string literals with a language tag" do
|
||||
assert Literal.new(1, language: "de") == LangString.new(1, language: "de")
|
||||
assert Literal.new!(1, language: "de") == LangString.new!(1, language: "de")
|
||||
end
|
||||
|
||||
test "construction of an other than rdf:langString typed and language-tagged literal fails" do
|
||||
assert Literal.new("Eule", datatype: RDF.langString, language: "de") ==
|
||||
LangString.new("Eule", language: "de")
|
||||
assert_raise ArgumentError, fn ->
|
||||
Literal.new("Eule", datatype: XSD.string, language: "de")
|
||||
Literal.new("Eule", datatype: NS.XSD.string, language: "de")
|
||||
end
|
||||
end
|
||||
|
||||
test "nil as language is ignored" do
|
||||
assert Literal.new("Eule", datatype: XSD.string, language: nil) ==
|
||||
Literal.new("Eule", datatype: XSD.string)
|
||||
assert Literal.new("Eule", language: nil) ==
|
||||
Literal.new("Eule")
|
||||
|
||||
assert Literal.new!("Eule", datatype: XSD.string, language: nil) ==
|
||||
Literal.new!("Eule", datatype: XSD.string)
|
||||
assert Literal.new!("Eule", language: nil) ==
|
||||
Literal.new!("Eule")
|
||||
end
|
||||
|
||||
test "construction of a rdf:langString works, but results in an invalid literal" do
|
||||
assert %Literal{value: "Eule"} = literal = Literal.new("Eule", datatype: RDF.langString)
|
||||
refute Literal.valid?(literal)
|
||||
assert Literal.new("Eule", datatype: RDF.langString) == LangString.new("Eule")
|
||||
assert_raise RDF.Literal.InvalidError, fn ->
|
||||
Literal.new!("Eule", datatype: RDF.langString)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "language" do
|
||||
Enum.each literals(:all_plain_lang), fn literal ->
|
||||
describe "has_datatype?" do
|
||||
Enum.each literals(~W[all_simple all_plain_lang]a), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} has correct language", %{literal: literal} do
|
||||
assert literal.language == "en"
|
||||
test "#{inspect literal} has no datatype", %{literal: literal} do
|
||||
refute Literal.has_datatype?(literal)
|
||||
end
|
||||
end
|
||||
Enum.each literals(:all) -- literals(:all_plain_lang), fn literal ->
|
||||
|
||||
Enum.each literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{literal} has no language", %{literal: literal} do
|
||||
assert is_nil(literal.language)
|
||||
test "Literal for #{inspect literal} has a datatype", %{literal: literal} do
|
||||
assert Literal.has_datatype?(literal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "language get lower-cased" do
|
||||
assert Literal.new("Upper", language: "EN").language == "en"
|
||||
assert Literal.new("Upper", %{language: "EN"}).language == "en"
|
||||
describe "plain?" do
|
||||
Enum.each literals(:all_plain), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} is plain", %{literal: literal} do
|
||||
assert Literal.plain?(literal)
|
||||
end
|
||||
end
|
||||
Enum.each literals(:all) -- literals(:all_plain), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{inspect literal} is not plain", %{literal: literal} do
|
||||
refute Literal.plain?(literal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "simple?" do
|
||||
Enum.each literals(:all_simple), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} is simple", %{literal: literal} do
|
||||
assert Literal.simple?(literal)
|
||||
end
|
||||
end
|
||||
Enum.each literals(:all) -- literals(:all_simple), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{inspect literal} is not simple", %{literal: literal} do
|
||||
refute Literal.simple?(literal)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "datatype" do
|
||||
describe "datatype/1" do
|
||||
Enum.each literals(:all_simple), fn literal ->
|
||||
@tag literal: literal
|
||||
test "simple literal #{inspect literal} has datatype xsd:string", %{literal: literal} do
|
||||
assert literal.datatype == XSD.string
|
||||
assert Literal.datatype(literal) == NS.XSD.string
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -154,88 +174,167 @@ defmodule RDF.LiteralTest do
|
|||
}
|
||||
|> Enum.each(fn {value, type} ->
|
||||
@tag data: %{literal: literal = Literal.new(value), type: type}
|
||||
test "Literal for #{literal} has datatype xsd:#{type}",
|
||||
test "Literal for #{inspect literal} has datatype xsd:#{type}",
|
||||
%{data: %{literal: literal, type: type}} do
|
||||
assert literal.datatype == apply(XSD, String.to_atom(type), [])
|
||||
assert Literal.datatype(literal) == apply(NS.XSD, String.to_atom(type), [])
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
describe "has_datatype?" do
|
||||
Enum.each literals(~W[all_simple all_plain_lang]a), fn literal ->
|
||||
describe "language" do
|
||||
Enum.each literals(:all_plain_lang), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} has no datatype", %{literal: literal} do
|
||||
refute Literal.has_datatype?(literal)
|
||||
test "#{inspect literal} has correct language", %{literal: literal} do
|
||||
assert Literal.language(literal) == "en"
|
||||
end
|
||||
end
|
||||
|
||||
Enum.each literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
|
||||
Enum.each literals(:all) -- literals(:all_plain_lang), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{literal} has a datatype", %{literal: literal} do
|
||||
assert Literal.has_datatype?(literal)
|
||||
end
|
||||
test "Literal for #{inspect literal} has no language", %{literal: literal} do
|
||||
assert is_nil(Literal.language(literal))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "plain?" do
|
||||
Enum.each literals(:all_plain), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} is plain", %{literal: literal} do
|
||||
assert Literal.plain?(literal)
|
||||
end
|
||||
end
|
||||
Enum.each literals(:all) -- literals(:all_plain), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{literal} is not plain", %{literal: literal} do
|
||||
refute Literal.plain?(literal)
|
||||
end
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("Upper", language: "en") |> Literal.language() == "en"
|
||||
assert Literal.new("Upper", language: "EN") |> Literal.language() == "en"
|
||||
assert Literal.new("Upper", language: "") |> Literal.language() == nil
|
||||
assert Literal.new("Upper", language: nil) |> Literal.language() == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "value/1" do
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.new("foo") |> Literal.value() == "foo"
|
||||
assert Literal.new(42) |> Literal.value() == 42
|
||||
end
|
||||
|
||||
describe "simple?" do
|
||||
Enum.each literals(:all_simple), fn literal ->
|
||||
@tag literal: literal
|
||||
test "#{inspect literal} is simple", %{literal: literal} do
|
||||
assert Literal.simple?(literal)
|
||||
end
|
||||
end
|
||||
Enum.each literals(:all) -- literals(:all_simple), fn literal ->
|
||||
@tag literal: literal
|
||||
test "Literal for #{literal} is not simple", %{literal: literal} do
|
||||
refute Literal.simple?(literal)
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> Literal.value() == "foo"
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.value() == "foo"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "canonicalization" do
|
||||
|
||||
# for mapped/known datatypes the RDF.Datatype.Test.Case uses the general RDF.Literal.canonical function
|
||||
|
||||
test "an unmapped/unknown datatypes is always canonical" do
|
||||
assert Literal.canonical? Literal.new("custom typed value", datatype: "http://example/dt")
|
||||
describe "lexical/1" do
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.new("foo") |> Literal.lexical() == "foo"
|
||||
assert Literal.new(42) |> Literal.lexical() == "42"
|
||||
end
|
||||
|
||||
test "for unmapped/unknown datatypes, canonicalize is a no-op" do
|
||||
assert Literal.new("custom typed value", datatype: "http://example/dt") ==
|
||||
Literal.canonical(Literal.new("custom typed value", datatype: "http://example/dt"))
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> Literal.lexical() == "foo"
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.lexical() == "foo"
|
||||
end
|
||||
end
|
||||
|
||||
describe "canonical/1" do
|
||||
test "with XSD.Datatype literal" do
|
||||
[
|
||||
RDF.XSD.String.new("foo"),
|
||||
RDF.XSD.Byte.new(42),
|
||||
|
||||
]
|
||||
|> Enum.each(fn
|
||||
canonical_literal ->
|
||||
assert Literal.canonical(canonical_literal) == canonical_literal
|
||||
end)
|
||||
assert RDF.XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42)
|
||||
assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> RDF.XSD.Double.canonical()
|
||||
end
|
||||
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> Literal.canonical() ==
|
||||
Literal.new("foo", language: "en")
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.canonical() ==
|
||||
Literal.new("foo", datatype: "http://example.com/dt")
|
||||
end
|
||||
end
|
||||
|
||||
describe "canonical?/1" do
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.new("foo") |> Literal.canonical?() == true
|
||||
assert Literal.new(42) |> Literal.canonical?() == true
|
||||
assert Literal.new(3.14) |> Literal.canonical?() == false
|
||||
end
|
||||
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> Literal.canonical?() == true
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.canonical?() == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "validation" do
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.new("foo") |> Literal.valid?() == true
|
||||
assert Literal.new(42) |> Literal.valid?() == true
|
||||
assert RDF.XSD.Integer.new("foo") |> Literal.valid?() == false
|
||||
end
|
||||
|
||||
# for mapped/known datatypes the RDF.Datatype.Test.Case uses the general RDF.Literal.valid? function
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> Literal.valid?() == true
|
||||
end
|
||||
|
||||
test "a literal with an unmapped/unknown datatype is always valid" do
|
||||
assert Literal.valid? Literal.new("custom typed value", datatype: "http://example/dt")
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> Literal.valid?() == true
|
||||
assert Literal.new("foo", datatype: "") |> Literal.valid?() == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "equal_value?/2" do
|
||||
test "with XSD.Datatype literal" do
|
||||
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("foo"), "foo") == 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(false), false) == true
|
||||
assert Literal.equal_value?(Literal.new(false), true) == false
|
||||
end
|
||||
|
||||
@poem RDF.string """
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.equal_value?(Literal.new("foo", language: "en"),
|
||||
Literal.new("foo", language: "en")) == true
|
||||
assert Literal.equal_value?(Literal.new("foo", language: "en"), Literal.new("foo")) == false
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
|
||||
Literal.new("foo", datatype: "http://example.com/dt")) == true
|
||||
assert Literal.equal_value?(Literal.new("foo", datatype: "http://example.com/dt"),
|
||||
Literal.new("foo")) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "compare/2" do
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.compare(Literal.new("foo"), Literal.new("bar")) == :gt
|
||||
assert Literal.compare(Literal.new(42), RDF.XSD.Byte.new(43)) == :lt
|
||||
end
|
||||
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.compare(Literal.new("foo", language: "en"),
|
||||
Literal.new("bar", language: "en")) == :gt
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.compare(Literal.new("foo", datatype: "http://example.com/dt"),
|
||||
Literal.new("bar", datatype: "http://example.com/dt")) == :gt
|
||||
end
|
||||
end
|
||||
|
||||
@poem RDF.XSD.String.new """
|
||||
<poem author="Wilhelm Busch">
|
||||
Kaum hat dies der Hahn gesehen,
|
||||
Fängt er auch schon an zu krähen:
|
||||
|
@ -261,8 +360,8 @@ defmodule RDF.LiteralTest do
|
|||
|
||||
{~L"abracadabra"en, ~L"bra", true},
|
||||
{"abracadabra", "bra", true},
|
||||
{RDF.integer("42"), ~L"4", true},
|
||||
{RDF.integer("42"), ~L"en", false},
|
||||
{RDF.XSD.Integer.new("42"), ~L"4", true},
|
||||
{RDF.XSD.Integer.new("42"), ~L"en", false},
|
||||
]
|
||||
|> Enum.each(fn {literal, pattern, expected_result} ->
|
||||
result = Literal.matches?(literal, pattern)
|
||||
|
@ -298,33 +397,20 @@ defmodule RDF.LiteralTest do
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
describe "String.Chars protocol implementation" do
|
||||
Enum.each values(:all_plain), fn value ->
|
||||
@tag value: value
|
||||
test "returns String representation of #{inspect value}", %{value: value} do
|
||||
assert to_string(apply(Literal, :new, value)) == List.first(value)
|
||||
end
|
||||
test "with XSD.Datatype literal" do
|
||||
assert Literal.new("foo") |> to_string() == "foo"
|
||||
assert Literal.new(42) |> to_string() == "42"
|
||||
assert RDF.XSD.Integer.new("foo") |> to_string() == "foo"
|
||||
end
|
||||
|
||||
%{
|
||||
literal(:int) => "123",
|
||||
literal(:true) => "true",
|
||||
literal(:false) => "false",
|
||||
literal(:long) => "9223372036854775807",
|
||||
literal(:double) => "3.1415",
|
||||
literal(:date) => "2017-04-13",
|
||||
literal(:datetime) => "2017-04-14T15:32:07Z",
|
||||
literal(:naive_datetime) => "2017-04-14T15:32:07",
|
||||
literal(:time) => "01:02:03"
|
||||
}
|
||||
|> Enum.each(fn {literal, rep} ->
|
||||
@tag data: %{literal: literal, rep: rep}
|
||||
test "returns String representation of Literal value #{literal}",
|
||||
%{data: %{literal: literal, rep: rep}} do
|
||||
assert to_string(literal) == rep
|
||||
end
|
||||
end)
|
||||
test "with RDF.LangString literal" do
|
||||
assert Literal.new("foo", language: "en") |> to_string() == "foo"
|
||||
end
|
||||
|
||||
test "with generic literal" do
|
||||
assert Literal.new("foo", datatype: "http://example.com/dt") |> to_string() == "foo"
|
||||
assert Literal.new("foo", datatype: "") |> to_string() == "foo"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,16 +4,16 @@ defmodule RDFTest do
|
|||
doctest RDF
|
||||
|
||||
test "Datatype constructor alias functions" do
|
||||
RDF.Datatype.modules
|
||||
RDF.Literal.Datatype.Registry.datatypes()
|
||||
|> Enum.each(fn datatype ->
|
||||
"rdf/" <> alias_name = datatype |> Macro.underscore
|
||||
assert apply(RDF, String.to_atom(alias_name), [1]) == datatype.new(1)
|
||||
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.Boolean.new(true)
|
||||
assert RDF.false == RDF.Boolean.new(false)
|
||||
assert RDF.true == RDF.XSD.Boolean.new(true)
|
||||
assert RDF.false == RDF.XSD.Boolean.new(false)
|
||||
end
|
||||
|
||||
describe "default_prefixes/0" do
|
||||
|
@ -21,5 +21,4 @@ defmodule RDFTest do
|
|||
assert RDF.default_prefixes() == RDF.standard_prefixes()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,9 +5,7 @@ defmodule RDF.Turtle.DecoderTest do
|
|||
|
||||
import RDF.Sigils
|
||||
|
||||
alias RDF.{Turtle, Graph}
|
||||
alias RDF.NS.{XSD}
|
||||
|
||||
alias RDF.{Turtle, Graph, NS}
|
||||
|
||||
use RDF.Vocabulary.Namespace
|
||||
|
||||
|
@ -218,7 +216,7 @@ defmodule RDF.Turtle.DecoderTest do
|
|||
assert Turtle.Decoder.decode!("""
|
||||
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
||||
<http://example.org/#spiderman> <http://example.org/#p> "42"^^xsd:integer .
|
||||
""") == Graph.new({EX.spiderman, EX.p, RDF.literal(42)}, prefixes: %{xsd: XSD})
|
||||
""") == Graph.new({EX.spiderman, EX.p, RDF.literal(42)}, prefixes: %{xsd: NS.XSD})
|
||||
end
|
||||
|
||||
test "a language tagged literal" do
|
||||
|
@ -242,28 +240,28 @@ defmodule RDF.Turtle.DecoderTest do
|
|||
test "boolean" do
|
||||
assert Turtle.Decoder.decode!("""
|
||||
<http://example.org/#Foo> <http://example.org/#bar> true .
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.Boolean.new(true)})
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.true})
|
||||
assert Turtle.Decoder.decode!("""
|
||||
<http://example.org/#Foo> <http://example.org/#bar> false .
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.Boolean.new(false)})
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.false})
|
||||
end
|
||||
|
||||
test "integer" do
|
||||
assert Turtle.Decoder.decode!("""
|
||||
<http://example.org/#Foo> <http://example.org/#bar> 42 .
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.Integer.new(42)})
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.integer(42)})
|
||||
end
|
||||
|
||||
test "decimal" do
|
||||
assert Turtle.Decoder.decode!("""
|
||||
<http://example.org/#Foo> <http://example.org/#bar> 3.14 .
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.Literal.new("3.14", datatype: XSD.decimal)})
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.decimal("3.14")})
|
||||
end
|
||||
|
||||
test "double" do
|
||||
assert Turtle.Decoder.decode!("""
|
||||
<http://example.org/#Foo> <http://example.org/#bar> 1.2e3 .
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.Double.new("1.2e3")})
|
||||
""") == Graph.new({EX.Foo, EX.bar, RDF.double("1.2e3")})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -560,7 +560,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"0", "false ."},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Boolean.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.boolean(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -573,7 +573,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"FaLsE", ~s{"FaLsE"^^<http://www.w3.org/2001/XMLSchema#boolean>}},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Boolean.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.boolean(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -591,7 +591,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"0010", "10 ."},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Integer.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.integer(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -602,7 +602,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#integer>}},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Integer.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.integer(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -620,7 +620,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"010.020", "10.02 ."},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Literal.new(value, datatype: XSD.decimal)})
|
||||
Graph.new({EX.S, EX.p, RDF.decimal(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -631,7 +631,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#decimal>}},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Literal.new(value, datatype: XSD.decimal)})
|
||||
Graph.new({EX.S, EX.p, RDF.decimal(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -650,7 +650,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"-1", "-1.0E0 ."},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Double.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.double(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
@ -661,7 +661,7 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
{"true", ~s{"true"^^<http://www.w3.org/2001/XMLSchema#double>}},
|
||||
]
|
||||
|> Enum.each(fn {value, output} ->
|
||||
Graph.new({EX.S, EX.p, RDF.Double.new(value)})
|
||||
Graph.new({EX.S, EX.p, RDF.double(value)})
|
||||
|> assert_serialization(matches: [output])
|
||||
end)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue