Add Effective Boolean Value (EBV) algorithm and RDF.Numeric
This commit is contained in:
parent
3e5db97ce4
commit
f3cc9ccf7e
4 changed files with 189 additions and 6 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
37
lib/rdf/datatypes/numeric.ex
Normal file
37
lib/rdf/datatypes/numeric.ex
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue