rdf-ex/lib/rdf/datatypes/boolean.ex
2018-09-17 02:08:16 +02:00

254 lines
6.1 KiB
Elixir

defmodule RDF.Boolean do
@moduledoc """
`RDF.Datatype` for XSD boolean.
"""
use RDF.Datatype, id: RDF.Datatype.NS.XSD.boolean
import RDF.Literal.Guards
@impl RDF.Datatype
def convert(value, opts)
def convert(value, _) when is_boolean(value), do: value
def convert(value, opts) when is_binary(value) do
with normalized_value = String.downcase(value) do
cond do
normalized_value in ~W[true 1] -> true
normalized_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(%RDF.Literal{datatype: datatype} = literal) do
cond do
not RDF.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>
"""
def fn_not(value) do
case ebv(value) do
%RDF.Literal{value: true} -> RDF.Boolean.Value.false
%RDF.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>
"""
def logical_and(left, right) do
case ebv(left) do
%RDF.Literal{value: false} ->
RDF.false
%RDF.Literal{value: true} ->
case ebv(right) do
%RDF.Literal{value: true} -> RDF.true
%RDF.Literal{value: false} -> RDF.false
nil -> nil
end
nil ->
if match?(%RDF.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>
"""
def logical_or(left, right) do
case ebv(left) do
%RDF.Literal{value: true} ->
RDF.true
%RDF.Literal{value: false} ->
case ebv(right) do
%RDF.Literal{value: true} -> RDF.true
%RDF.Literal{value: false} -> RDF.false
nil -> nil
end
nil ->
if match?(%RDF.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>
"""
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 !"""
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