5819eec0cf
It turned out that the costs of separating the XSD datatypes are too high and probably not worth the effort, since with its limited scope probably nobody would want to use XSD.ex outside of the RDF.ex context anyway.
514 lines
18 KiB
Elixir
514 lines
18 KiB
Elixir
defmodule RDF.LiteralTest do
|
|
use ExUnit.Case
|
|
|
|
import RDF.Sigils
|
|
import RDF.TestLiterals
|
|
|
|
alias RDF.{Literal, XSD, LangString}
|
|
alias RDF.Literal.{Generic, Datatype}
|
|
alias Decimal, as: D
|
|
|
|
doctest RDF.Literal
|
|
|
|
alias RDF.NS
|
|
|
|
@examples %{
|
|
XSD.String => ["foo"],
|
|
XSD.Integer => [42],
|
|
XSD.Double => [3.14],
|
|
XSD.Decimal => [Decimal.from_float(3.14)],
|
|
XSD.Boolean => [true, false],
|
|
}
|
|
|
|
describe "new/1" do
|
|
Enum.each @examples, fn {datatype, example_values} ->
|
|
@tag example: %{datatype: datatype, values: example_values}
|
|
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() -- [RDF.LangString], fn datatype ->
|
|
assert %Literal{literal: typed_literal} = Literal.new(datatype.new("foo"))
|
|
assert typed_literal.__struct__ == datatype
|
|
end
|
|
end
|
|
|
|
test "when options without datatype given" do
|
|
assert Literal.new(true, []) == XSD.Boolean.new(true)
|
|
assert Literal.new(42, []) == XSD.Integer.new(42)
|
|
assert Literal.new!(true, []) == XSD.Boolean.new!(true)
|
|
assert Literal.new!(42, []) == XSD.Integer.new!(42)
|
|
end
|
|
end
|
|
|
|
describe "typed construction" do
|
|
test "boolean" do
|
|
assert Literal.new(true, datatype: NS.XSD.boolean) == XSD.Boolean.new(true)
|
|
assert Literal.new(false, datatype: NS.XSD.boolean) == XSD.Boolean.new(false)
|
|
assert Literal.new("true", datatype: NS.XSD.boolean) == XSD.Boolean.new("true")
|
|
assert Literal.new("false", datatype: NS.XSD.boolean) == XSD.Boolean.new("false")
|
|
end
|
|
|
|
test "integer" do
|
|
assert Literal.new(42, datatype: NS.XSD.integer) == XSD.Integer.new(42)
|
|
assert Literal.new("42", datatype: NS.XSD.integer) == XSD.Integer.new("42")
|
|
end
|
|
|
|
test "double" do
|
|
assert Literal.new(3.14, datatype: NS.XSD.double) == XSD.Double.new(3.14)
|
|
assert Literal.new("3.14", datatype: NS.XSD.double) == XSD.Double.new("3.14")
|
|
end
|
|
|
|
test "decimal" do
|
|
assert Literal.new(3.14, datatype: NS.XSD.decimal) == XSD.Decimal.new(3.14)
|
|
assert Literal.new("3.14", datatype: NS.XSD.decimal) == XSD.Decimal.new("3.14")
|
|
assert Literal.new(Decimal.from_float(3.14), datatype: NS.XSD.decimal) ==
|
|
XSD.Decimal.new(Decimal.from_float(3.14))
|
|
end
|
|
|
|
test "unsignedInt" do
|
|
assert Literal.new(42, datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new(42)
|
|
assert Literal.new("42", datatype: NS.XSD.unsignedInt) == XSD.UnsignedInt.new("42")
|
|
end
|
|
|
|
test "string" do
|
|
assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo")
|
|
end
|
|
|
|
test "unmapped/unknown datatype" do
|
|
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
|
|
assert Literal.new("Eule", language: "de") == LangString.new("Eule", language: "de")
|
|
assert Literal.new!("Eule", language: "de") == LangString.new!("Eule", language: "de")
|
|
end
|
|
|
|
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: NS.XSD.string, language: "de")
|
|
end
|
|
end
|
|
|
|
test "construction of a rdf:langString works, but results in an invalid literal" do
|
|
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 "coerce/1" do
|
|
test "with boolean" do
|
|
assert Literal.coerce(true) == XSD.true()
|
|
assert Literal.coerce(false) == XSD.false()
|
|
end
|
|
|
|
test "with string" do
|
|
assert Literal.coerce("foo") == XSD.string("foo")
|
|
end
|
|
|
|
test "with integer" do
|
|
assert Literal.coerce(42) == XSD.integer(42)
|
|
end
|
|
|
|
test "with float" do
|
|
assert Literal.coerce(3.14) == XSD.double(3.14)
|
|
end
|
|
|
|
test "with decimal" do
|
|
assert D.from_float(3.14) |> Literal.coerce() == XSD.decimal(3.14)
|
|
end
|
|
|
|
test "with datetime" do
|
|
assert DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> Literal.coerce() ==
|
|
DateTime.from_iso8601("2002-04-02T12:00:00+00:00") |> elem(1) |> XSD.datetime()
|
|
end
|
|
|
|
test "with naive datetime" do
|
|
assert ~N"2002-04-02T12:00:00" |> Literal.coerce() ==
|
|
~N"2002-04-02T12:00:00" |> XSD.datetime()
|
|
end
|
|
|
|
test "with date" do
|
|
assert ~D"2002-04-02" |> Literal.coerce() ==
|
|
~D"2002-04-02" |> XSD.date()
|
|
end
|
|
|
|
test "with time" do
|
|
assert ~T"12:00:00" |> Literal.coerce() ==
|
|
~T"12:00:00" |> XSD.time()
|
|
end
|
|
|
|
test "with URI" do
|
|
assert URI.parse("http://example.com") |> Literal.coerce() ==
|
|
XSD.any_uri("http://example.com")
|
|
end
|
|
|
|
test "with RDF.Literals" do
|
|
assert XSD.integer(42) |> Literal.coerce() == XSD.integer(42)
|
|
end
|
|
|
|
test "with RDF datatype Literals" do
|
|
assert %XSD.Integer{value: 42} |> Literal.coerce() == XSD.integer(42)
|
|
end
|
|
|
|
test "with inconvertible values" do
|
|
assert self() |> Literal.coerce() == nil
|
|
end
|
|
end
|
|
|
|
describe "has_datatype?" do
|
|
Enum.each literals(~W[all_simple all_plain_lang]a), fn literal ->
|
|
@tag literal: literal
|
|
test "#{inspect literal} has no datatype", %{literal: literal} do
|
|
refute Literal.has_datatype?(literal)
|
|
end
|
|
end
|
|
|
|
Enum.each literals(:all) -- literals(~W[all_simple all_plain_lang]a), fn literal ->
|
|
@tag literal: literal
|
|
test "Literal for #{inspect literal} has a datatype", %{literal: literal} do
|
|
assert Literal.has_datatype?(literal)
|
|
end
|
|
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 #{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/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(literal) == NS.XSD.string
|
|
end
|
|
end
|
|
|
|
%{
|
|
123 => "integer",
|
|
true => "boolean",
|
|
false => "boolean",
|
|
9223372036854775807 => "integer",
|
|
3.1415 => "double",
|
|
~D[2017-04-13] => "date",
|
|
~N[2017-04-14 15:32:07] => "dateTime",
|
|
~T[01:02:03] => "time"
|
|
}
|
|
|> Enum.each(fn {value, type} ->
|
|
@tag data: %{literal: literal = Literal.new(value), type: type}
|
|
test "Literal for #{inspect literal} has datatype xsd:#{type}",
|
|
%{data: %{literal: literal, type: type}} do
|
|
assert Literal.datatype(literal) == apply(NS.XSD, String.to_atom(type), [])
|
|
end
|
|
end)
|
|
end
|
|
|
|
describe "language" do
|
|
Enum.each literals(:all_plain_lang), fn literal ->
|
|
@tag literal: literal
|
|
test "#{inspect literal} has correct language", %{literal: literal} do
|
|
assert Literal.language(literal) == "en"
|
|
end
|
|
end
|
|
Enum.each literals(:all) -- literals(:all_plain_lang), fn literal ->
|
|
@tag literal: literal
|
|
test "Literal for #{inspect literal} has no language", %{literal: literal} do
|
|
assert is_nil(Literal.language(literal))
|
|
end
|
|
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
|
|
|
|
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 "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 "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
|
|
[
|
|
XSD.String.new("foo"),
|
|
XSD.Byte.new(42),
|
|
|
|
]
|
|
|> Enum.each(fn
|
|
canonical_literal ->
|
|
assert Literal.canonical(canonical_literal) == canonical_literal
|
|
end)
|
|
assert XSD.Integer.new("042") |> Literal.canonical() == Literal.new(42)
|
|
assert Literal.new(3.14) |> Literal.canonical() == Literal.new(3.14) |> 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 XSD.Integer.new("foo") |> Literal.valid?() == false
|
|
end
|
|
|
|
test "with RDF.LangString literal" do
|
|
assert Literal.new("foo", language: "en") |> Literal.valid?() == true
|
|
end
|
|
|
|
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), 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
|
|
|
|
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")) == nil
|
|
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")) == nil
|
|
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), 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 XSD.String.new """
|
|
<poem author="Wilhelm Busch">
|
|
Kaum hat dies der Hahn gesehen,
|
|
Fängt er auch schon an zu krähen:
|
|
Kikeriki! Kikikerikih!!
|
|
Tak, tak, tak! - da kommen sie.
|
|
</poem>
|
|
"""
|
|
|
|
describe "matches?" do
|
|
test "without flags" do
|
|
[
|
|
{~L"abracadabra", ~L"bra", true},
|
|
{~L"abracadabra", ~L"^a.*a$", true},
|
|
{~L"abracadabra", ~L"^bra", false},
|
|
{@poem, ~L"Kaum.*krähen", false},
|
|
{@poem, ~L"^Kaum.*gesehen,$", false},
|
|
{~L"foobar", ~L"foo$", false},
|
|
|
|
{~L"noe\u0308l", ~L"noe\\u0308l", true},
|
|
{~L"noe\\u0308l", ~L"noe\\\\u0308l", true},
|
|
{~L"\u{01D4B8}", ~L"\\U0001D4B8", true},
|
|
{~L"\\U0001D4B8", ~L"\\\U0001D4B8", true},
|
|
|
|
{~L"abracadabra"en, ~L"bra", true},
|
|
{"abracadabra", "bra", true},
|
|
{XSD.Integer.new("42"), ~L"4", true},
|
|
{XSD.Integer.new("42"), ~L"en", false},
|
|
]
|
|
|> Enum.each(fn {literal, pattern, expected_result} ->
|
|
result = Literal.matches?(literal, pattern)
|
|
assert result == expected_result,
|
|
"expected RDF.Literal.matches?(#{inspect literal}, #{inspect pattern}) to return #{inspect expected_result}, but got #{result}"
|
|
end)
|
|
end
|
|
|
|
test "with flags" do
|
|
[
|
|
{@poem, ~L"Kaum.*krähen", ~L"s", true},
|
|
{@poem, ~L"^Kaum.*gesehen,$", ~L"m", true},
|
|
{@poem, ~L"kiki", ~L"i", true},
|
|
]
|
|
|> Enum.each(fn {literal, pattern, flags, result} ->
|
|
assert Literal.matches?(literal, pattern, flags) == result
|
|
end)
|
|
end
|
|
|
|
test "with q flag" do
|
|
[
|
|
{~L"abcd", ~L".*", ~L"q", false},
|
|
{~L"Mr. B. Obama", ~L"B. OBAMA", ~L"iq", true},
|
|
|
|
# If the q flag is used together with the m, s, or x flag, that flag has no effect.
|
|
{~L"abcd", ~L".*", ~L"mq", true},
|
|
{~L"abcd", ~L".*", ~L"qim", true},
|
|
{~L"abcd", ~L".*", ~L"xqm", true},
|
|
]
|
|
|> Enum.each(fn {literal, pattern, flags, result} ->
|
|
assert Literal.matches?(literal, pattern, flags) == result
|
|
end)
|
|
end
|
|
end
|
|
|
|
describe "update/2" do
|
|
test "it updates value and lexical form" do
|
|
assert XSD.string("foo")
|
|
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
|
|
XSD.string("foobar")
|
|
assert XSD.integer(1) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
|
|
XSD.integer(2)
|
|
assert XSD.byte(42) |> Literal.update(fn i when is_integer(i) -> i + 1 end) ==
|
|
XSD.byte(43)
|
|
assert XSD.integer(1)
|
|
|> Literal.update(fn i when is_integer(i) -> "0" <> to_string(i) end) ==
|
|
XSD.integer("01")
|
|
end
|
|
|
|
test "it does not change the datatype of generic literals" do
|
|
assert RDF.literal("foo", datatype: "http://example.com/dt")
|
|
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
|
|
RDF.literal("foobar", datatype: "http://example.com/dt")
|
|
end
|
|
|
|
test "it does not change the language of language literals" do
|
|
assert RDF.langString("foo", language: "en")
|
|
|> Literal.update(fn s when is_binary(s) -> s <> "bar" end) ==
|
|
RDF.langString("foobar", language: "en")
|
|
end
|
|
|
|
test "with as: :lexical opt it passes the lexical form" do
|
|
assert XSD.integer(1)
|
|
|> Literal.update(fn i when is_binary(i) -> "0" <> i end, as: :lexical) ==
|
|
XSD.integer("01")
|
|
end
|
|
end
|
|
|
|
describe "String.Chars protocol implementation" do
|
|
test "with XSD.Datatype literal" do
|
|
assert Literal.new("foo") |> to_string() == "foo"
|
|
assert Literal.new(42) |> to_string() == "42"
|
|
assert XSD.Integer.new("foo") |> to_string() == "foo"
|
|
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
|