Revision of the literal system with extracted XSD datatypes

This commit is contained in:
Marcel Otto 2020-04-10 23:40:33 +02:00
parent c4791af2ad
commit f6146c51b0
64 changed files with 1790 additions and 5866 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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