rdf-ex/lib/rdf/literal/datatypes/generic.ex
2020-06-29 10:37:42 +02:00

134 lines
3.6 KiB
Elixir

defmodule RDF.Literal.Generic do
@moduledoc """
A generic `RDF.Literal.Datatype` for literals of an unknown datatype.
"""
defstruct [:value, :datatype]
use RDF.Literal.Datatype,
name: "generic",
id: nil
alias RDF.Literal.Datatype
alias RDF.{Literal, IRI}
import RDF.Guards
@type t :: %__MODULE__{
value: String.t(),
datatype: String.t()
}
@impl Datatype
@spec new(any, String.t() | IRI.t() | keyword) :: Literal.t()
def new(value, datatype_or_opts \\ [])
def new(value, %IRI{} = datatype), do: new(value, datatype: datatype)
def new(value, datatype) when is_binary(datatype) or maybe_ns_term(datatype),
do: new(value, datatype: datatype)
def new(value, opts) do
%Literal{
literal: %__MODULE__{
value: value,
datatype: Keyword.get(opts, :datatype) |> normalize_datatype()
}
}
end
defp normalize_datatype(nil), do: nil
defp normalize_datatype(""), do: nil
defp normalize_datatype(%IRI{} = datatype), do: to_string(datatype)
defp normalize_datatype(datatype) when maybe_ns_term(datatype),
do: datatype |> RDF.iri() |> to_string()
defp normalize_datatype(datatype), do: datatype
@impl Datatype
@spec new!(any, String.t() | IRI.t() | keyword) :: Literal.t()
def new!(value, datatype_or_opts \\ []) do
literal = new(value, datatype_or_opts)
if valid?(literal) do
literal
else
raise ArgumentError,
"#{inspect(value)} with datatype #{inspect(literal.literal.datatype)} is not a valid #{
inspect(__MODULE__)
}"
end
end
@impl Datatype
def datatype_id(%Literal{literal: literal}), do: datatype_id(literal)
def datatype_id(%__MODULE__{} = literal), do: RDF.iri(literal.datatype)
@impl Datatype
def value(%Literal{literal: literal}), do: value(literal)
def value(%__MODULE__{} = literal), do: literal.value
@impl Datatype
def lexical(%Literal{literal: literal}), do: lexical(literal)
def lexical(%__MODULE__{} = literal), do: literal.value
@impl Datatype
def canonical(%Literal{literal: %__MODULE__{}} = literal), do: literal
def canonical(%__MODULE__{} = literal), do: literal(literal)
@impl Datatype
def canonical?(%Literal{literal: literal}), do: canonical?(literal)
def canonical?(%__MODULE__{}), do: true
@impl Datatype
def valid?(%Literal{literal: %__MODULE__{} = literal}), do: valid?(literal)
def valid?(%__MODULE__{datatype: datatype}) when is_binary(datatype), do: true
def valid?(_), do: false
@doc """
Since generic literals don't support casting, always returns `nil`.
"""
def cast(_), do: nil
@impl Datatype
def do_cast(_), do: nil
@impl Datatype
def do_equal_value_same_or_derived_datatypes?(
%{datatype: datatype} = left,
%{datatype: datatype} = right
),
do: left == right
def do_equal_value_same_or_derived_datatypes?(_, _), do: nil
@impl Datatype
def do_compare(
%__MODULE__{datatype: datatype} = left_literal,
%__MODULE__{datatype: datatype} = right_literal
) do
case {left_literal.value, right_literal.value} do
{left_value, right_value} when left_value < right_value ->
:lt
{left_value, right_value} when left_value > right_value ->
:gt
_ ->
if equal_value?(left_literal, right_literal), do: :eq
end
end
def do_compare(_, _), do: nil
@impl Datatype
def update(literal, fun, opts \\ [])
def update(%Literal{literal: literal}, fun, opts), do: update(literal, fun, opts)
def update(%__MODULE__{} = literal, fun, _opts) do
literal
|> value()
|> fun.()
|> new(datatype: literal.datatype)
end
end