core: RDF.Double datatype
This commit is contained in:
parent
2b9aa62d69
commit
53e590dc14
8 changed files with 160 additions and 16 deletions
|
@ -8,6 +8,8 @@ defmodule RDF.Datatype do
|
|||
|
||||
@callback canonical_lexical(any) :: binary
|
||||
|
||||
@callback invalid_lexical(any) :: binary
|
||||
|
||||
@callback canonical(RDF.Literal.t) :: RDF.Literal.t
|
||||
|
||||
@doc """
|
||||
|
@ -85,7 +87,7 @@ defmodule RDF.Datatype do
|
|||
def build_literal_by_value(value, opts) do
|
||||
case convert(value, opts) do
|
||||
nil ->
|
||||
build_literal(nil, canonical_lexical(value), opts)
|
||||
build_literal(nil, invalid_lexical(value), opts)
|
||||
converted_value ->
|
||||
build_literal(converted_value, nil, opts)
|
||||
end
|
||||
|
@ -128,6 +130,7 @@ defmodule RDF.Datatype do
|
|||
|
||||
def canonical_lexical(value), do: to_string(value)
|
||||
|
||||
def invalid_lexical(value), do: to_string(value)
|
||||
|
||||
def canonical(%Literal{value: nil} = literal), do: literal
|
||||
def canonical(%Literal{uncanonical_lexical: nil} = literal), do: literal
|
||||
|
@ -146,6 +149,7 @@ defmodule RDF.Datatype do
|
|||
build_literal: 3,
|
||||
lexical: 1,
|
||||
canonical_lexical: 1,
|
||||
invalid_lexical: 1,
|
||||
convert: 2,
|
||||
valid?: 1,
|
||||
]
|
||||
|
|
|
@ -1,10 +1,82 @@
|
|||
defmodule RDF.Double do
|
||||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.double
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def convert(value, _) when is_float(value), do: value
|
||||
|
||||
def convert(value, _) when is_integer(value), do: value / 1
|
||||
def convert(value, _) when is_binary(value), do: String.to_float(value)
|
||||
# def convert(false, _), do: 0.0
|
||||
# def convert(true, _), do: 1.0
|
||||
|
||||
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" -> :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)
|
||||
|
||||
|
||||
def canonical_lexical(value) do
|
||||
case value do
|
||||
:nan -> "NaN"
|
||||
:positive_infinity -> "INF"
|
||||
:negative_infinity -> "-INF"
|
||||
float when is_float(float) -> exponential_form(float)
|
||||
_ -> to_string(value)
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
def decimal_form(float) when is_float(float) do
|
||||
to_string(float)
|
||||
end
|
||||
|
||||
def 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] =
|
||||
:io_lib.format("~.15e", [float])
|
||||
|> List.first
|
||||
|> 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
|
||||
|
||||
end
|
||||
|
|
|
@ -7,11 +7,12 @@ defmodule RDF.Integer do
|
|||
def convert(value, opts) when is_binary(value) do
|
||||
case Integer.parse(value) do
|
||||
{integer, ""} -> integer
|
||||
{integer, _} -> super(value, opts)
|
||||
{_, _} -> super(value, opts)
|
||||
:error -> super(value, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def convert(value, opts), do: super(value, opts)
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -2,13 +2,6 @@ defmodule RDF.LangString do
|
|||
use RDF.Datatype, id: RDF.langString
|
||||
|
||||
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
def valid?(%Literal{language: nil}), do: false
|
||||
def valid?(literal), do: super(literal)
|
||||
|
||||
|
||||
def build_literal(value, lexical, %{language: language} = opts) do
|
||||
%Literal{super(value, lexical, opts) | language: String.downcase(language)}
|
||||
end
|
||||
|
@ -17,4 +10,12 @@ defmodule RDF.LangString do
|
|||
raise ArgumentError, "datatype of rdf:langString requires a language"
|
||||
end
|
||||
|
||||
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
def valid?(%Literal{language: nil}), do: false
|
||||
def valid?(literal), do: super(literal)
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -2,11 +2,12 @@ defmodule RDF.String do
|
|||
use RDF.Datatype, id: RDF.Datatype.NS.XSD.string
|
||||
|
||||
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
def build_literal_by_lexical(lexical, opts) do
|
||||
build_literal(lexical, nil, opts)
|
||||
end
|
||||
|
||||
|
||||
def convert(value, _), do: to_string(value)
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -128,7 +128,7 @@ defmodule RDF.Literal do
|
|||
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
|
||||
"""
|
||||
def simple?(%RDF.Literal{datatype: @xsd_string}), do: true
|
||||
def simple?(foo), do: false
|
||||
def simple?(_), do: false
|
||||
|
||||
|
||||
@doc """
|
||||
|
|
60
test/unit/datatypes/double_test.exs
Normal file
60
test/unit/datatypes/double_test.exs
Normal file
|
@ -0,0 +1,60 @@
|
|||
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" },
|
||||
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" => { :positive_infinity , "+INF" , "INF" },
|
||||
"-INF" => { :negative_infinity , nil , "-INF" },
|
||||
"NaN" => { :nan , nil , "NaN" },
|
||||
},
|
||||
invalid: ~w(foo 12.xyz 1.0ez) ++ [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
|
||||
|
||||
end
|
|
@ -47,6 +47,11 @@ defmodule RDF.LiteralTest do
|
|||
assert Literal.new("42", datatype: XSD.integer) == RDF.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")
|
||||
end
|
||||
|
||||
test "string" do
|
||||
assert Literal.new("foo", datatype: XSD.string) == RDF.String.new("foo")
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue