Add Effective Boolean Value (EBV) algorithm and RDF.Numeric

This commit is contained in:
Marcel Otto 2018-05-27 22:19:08 +02:00
parent 3e5db97ce4
commit f3cc9ccf7e
4 changed files with 189 additions and 6 deletions

View file

@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
- top-level alias functions for constructors of the basic datatypes
- top-level constant functions `RDF.true` and `RDF.false` for the two boolean
RDF.Literal values
- `RDF.Numeric` with a list of all numeric datatypes
[Compare v0.4.1...HEAD](https://github.com/marcelotto/rdf-ex/compare/v0.4.1...HEAD)

View file

@ -24,15 +24,68 @@ defmodule RDF.Boolean do
def convert(value, opts), do: super(value, opts)
@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>
"""
def ebv(value)
def ebv(true), do: RDF.Boolean.Value.true
def ebv(false), do: RDF.Boolean.Value.false
def ebv(%RDF.Literal{value: nil, datatype: @xsd_boolean}), do: RDF.Boolean.Value.false
def ebv(%RDF.Literal{datatype: @xsd_boolean} = literal), do: literal
def ebv(%RDF.Literal{datatype: datatype} = literal) do
cond do
RDF.Numeric.type?(datatype) ->
if RDF.Literal.valid?(literal) and
not (literal.value == 0 or literal.value == :nan),
do: RDF.Boolean.Value.true,
else: RDF.Boolean.Value.false
RDF.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 |> RDF.Literal.new() |> ebv()
end
def ebv(_), do: nil
@doc """
Alias for `ebv/1`.
"""
def effective(value), do: ebv(value)
end
defmodule RDF.Boolean.Value do
@moduledoc false
# 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.
@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)

View file

@ -0,0 +1,37 @@
defmodule RDF.Numeric do
@moduledoc """
The set of all numeric datatypes.
"""
alias RDF.Datatype.NS.XSD
@types [
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.
"""
def types(), do: @types
@doc """
Returns if a given datatype is numeric.
"""
def type?(type), do: type in @types
end

View file

@ -15,6 +15,7 @@ defmodule RDF.BooleanTest do
},
invalid: ~w(foo 10) ++ [42, 3.14, "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
@ -42,4 +43,95 @@ defmodule RDF.BooleanTest do
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
end