2017-04-16 21:13:39 +00:00
|
|
|
defmodule RDF.Datatype do
|
2017-06-09 22:18:39 +00:00
|
|
|
@moduledoc """
|
|
|
|
A behaviour for natively supported literal datatypes.
|
|
|
|
|
|
|
|
A `RDF.Datatype` implements the foundational functions for the lexical form,
|
|
|
|
the validation, conversion and canonicalization of typed `RDF.Literal`s.
|
|
|
|
"""
|
|
|
|
|
2017-04-20 21:09:55 +00:00
|
|
|
alias RDF.Literal
|
2017-04-16 21:13:39 +00:00
|
|
|
alias RDF.Datatype.NS.XSD
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
The URI of the datatype.
|
|
|
|
"""
|
2017-04-16 21:13:39 +00:00
|
|
|
@callback id :: URI.t
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Produces the lexical form of a `RDF.Literal`.
|
|
|
|
"""
|
|
|
|
@callback lexical(literal :: RDF.Literal.t) :: any
|
2017-04-23 21:41:29 +00:00
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Produces the lexical form of a value.
|
|
|
|
"""
|
2017-04-23 21:41:29 +00:00
|
|
|
@callback canonical_lexical(any) :: binary
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Produces the lexical form of an invalid value of a typed Literal.
|
|
|
|
|
|
|
|
The default implementation of the `_using__` macro just returns `to_string`
|
|
|
|
representation of the value.
|
|
|
|
"""
|
2017-04-26 00:48:49 +00:00
|
|
|
@callback invalid_lexical(any) :: binary
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Produces the canonical form of a `RDF.Literal`.
|
|
|
|
"""
|
|
|
|
@callback canonical(literal :: RDF.Literal.t) :: RDF.Literal.t
|
2017-04-23 21:41:29 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Converts a value into a proper native value.
|
|
|
|
|
|
|
|
If an invalid value is given an implementation should call `super`, which
|
|
|
|
by default currently just returns `nil`.
|
|
|
|
|
|
|
|
Note: If a value is valid is determined by the lexical space of the implemented
|
|
|
|
datatype, not by the Elixir semantics. For example, although `42`
|
|
|
|
is a falsy value according to the Elixir semantics, this is not an element
|
|
|
|
of the lexical value space of an `xsd:boolean`, so the `RDF.Boolean`
|
|
|
|
implementation of this datatype calls `super`.
|
|
|
|
"""
|
2017-04-16 21:13:39 +00:00
|
|
|
@callback convert(any, keyword) :: any
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Determines if the value of a `RDF.Literal` is a member of lexical value space of its datatype.
|
|
|
|
"""
|
|
|
|
@callback valid?(literal :: RDF.Literal.t) :: boolean
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
|
2017-06-10 21:08:49 +00:00
|
|
|
@lang_string RDF.uri("http://www.w3.org/1999/02/22-rdf-syntax-ns#langString")
|
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
# TODO: This mapping should be created dynamically and be extendable, to allow user-defined datatypes ...
|
|
|
|
@mapping %{
|
2017-06-10 21:08:49 +00:00
|
|
|
@lang_string => RDF.LangString,
|
|
|
|
XSD.string => RDF.String,
|
|
|
|
XSD.integer => RDF.Integer,
|
|
|
|
XSD.double => RDF.Double,
|
|
|
|
XSD.boolean => RDF.Boolean,
|
|
|
|
XSD.date => RDF.Date,
|
|
|
|
XSD.time => RDF.Time,
|
|
|
|
XSD.dateTime => RDF.DateTime,
|
2017-04-16 21:13:39 +00:00
|
|
|
}
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
The mapping of URIs of datatypes to their `RDF.Datatype`.
|
|
|
|
"""
|
2017-04-30 12:38:26 +00:00
|
|
|
def mapping, do: @mapping
|
2017-06-09 22:18:39 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
The URIs of all datatypes with a `RDF.Datatype` defined.
|
|
|
|
"""
|
2017-04-20 21:09:55 +00:00
|
|
|
def ids, do: Map.keys(@mapping)
|
2017-06-09 22:18:39 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
All defined `RDF.Datatype` modules.
|
|
|
|
"""
|
2017-04-20 21:09:55 +00:00
|
|
|
def modules, do: Map.values(@mapping)
|
|
|
|
|
2017-06-09 22:18:39 +00:00
|
|
|
@doc """
|
|
|
|
Returns the `RDF.Datatype` for a directly datatype URI or the datatype URI of a `RDF.Literal`.
|
|
|
|
"""
|
2017-04-20 21:09:55 +00:00
|
|
|
def get(%Literal{datatype: id}), do: get(id)
|
|
|
|
def get(id), do: @mapping[id]
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
defmacro __using__(opts) do
|
|
|
|
id = Keyword.fetch!(opts, :id)
|
|
|
|
quote bind_quoted: [], unquote: true do
|
|
|
|
@behaviour unquote(__MODULE__)
|
|
|
|
|
|
|
|
alias RDF.Literal
|
|
|
|
alias RDF.Datatype.NS.XSD
|
|
|
|
|
|
|
|
@id unquote(id)
|
|
|
|
def id, do: @id
|
|
|
|
|
2017-04-20 21:09:55 +00:00
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
def new(value, opts \\ %{})
|
|
|
|
|
|
|
|
def new(value, opts) when is_list(opts),
|
|
|
|
do: new(value, Map.new(opts))
|
2017-04-20 21:09:55 +00:00
|
|
|
def new(value, opts) when is_binary(value),
|
|
|
|
do: build_literal_by_lexical(value, opts)
|
2017-04-16 21:13:39 +00:00
|
|
|
def new(value, opts),
|
2017-04-23 21:41:29 +00:00
|
|
|
do: build_literal_by_value(value, opts)
|
|
|
|
|
|
|
|
def new!(value, opts \\ %{}) do
|
|
|
|
literal = new(value, opts)
|
|
|
|
if valid?(literal) do
|
|
|
|
literal
|
|
|
|
else
|
|
|
|
raise ArgumentError, "#{inspect value} is not a valid #{inspect __MODULE__}"
|
|
|
|
end
|
|
|
|
end
|
2017-04-20 21:09:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
def build_literal_by_value(value, opts) do
|
2017-04-23 21:41:29 +00:00
|
|
|
case convert(value, opts) do
|
|
|
|
nil ->
|
2017-04-26 00:48:49 +00:00
|
|
|
build_literal(nil, invalid_lexical(value), opts)
|
2017-04-23 21:41:29 +00:00
|
|
|
converted_value ->
|
|
|
|
build_literal(converted_value, nil, opts)
|
|
|
|
end
|
2017-04-20 21:09:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def build_literal_by_lexical(lexical, opts) do
|
2017-04-23 21:41:29 +00:00
|
|
|
case convert(lexical, opts) do
|
|
|
|
nil ->
|
|
|
|
build_literal(nil, lexical, opts)
|
|
|
|
value ->
|
|
|
|
if opts[:canonicalize] || lexical == canonical_lexical(value) do
|
|
|
|
build_literal(value, nil, opts)
|
|
|
|
else
|
|
|
|
build_literal(value, lexical, opts)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_literal(value, lexical, %{canonicalize: true} = opts) do
|
|
|
|
build_literal(value, lexical, Map.delete(opts, :canonicalize))
|
|
|
|
|> canonical
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_literal(value, lexical, opts) do
|
|
|
|
%Literal{value: value, uncanonical_lexical: lexical, datatype: @id}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def convert(value, _), do: nil
|
|
|
|
|
|
|
|
|
|
|
|
def lexical(%RDF.Literal{value: value, uncanonical_lexical: nil}) do
|
|
|
|
canonical_lexical(value)
|
2017-04-20 21:09:55 +00:00
|
|
|
end
|
|
|
|
|
2017-04-23 21:41:29 +00:00
|
|
|
def lexical(%RDF.Literal{uncanonical_lexical: lexical}) do
|
|
|
|
lexical
|
2017-04-20 21:09:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2017-04-23 21:41:29 +00:00
|
|
|
def canonical_lexical(value), do: to_string(value)
|
|
|
|
|
2017-04-26 00:48:49 +00:00
|
|
|
def invalid_lexical(value), do: to_string(value)
|
2017-04-20 21:09:55 +00:00
|
|
|
|
2017-04-23 21:41:29 +00:00
|
|
|
def canonical(%Literal{value: nil} = literal), do: literal
|
|
|
|
def canonical(%Literal{uncanonical_lexical: nil} = literal), do: literal
|
|
|
|
def canonical(%Literal{} = literal) do
|
|
|
|
%Literal{literal | uncanonical_lexical: nil}
|
|
|
|
end
|
2017-04-20 21:09:55 +00:00
|
|
|
|
2017-04-23 21:41:29 +00:00
|
|
|
def valid?(%Literal{value: nil}), do: false
|
|
|
|
def valid?(%Literal{datatype: @id}), do: true
|
|
|
|
def valid?(_), do: false
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
|
2017-04-20 21:09:55 +00:00
|
|
|
defoverridable [
|
|
|
|
build_literal_by_value: 2,
|
|
|
|
build_literal_by_lexical: 2,
|
|
|
|
build_literal: 3,
|
2017-04-23 21:41:29 +00:00
|
|
|
lexical: 1,
|
|
|
|
canonical_lexical: 1,
|
2017-04-26 00:48:49 +00:00
|
|
|
invalid_lexical: 1,
|
2017-04-23 21:41:29 +00:00
|
|
|
convert: 2,
|
|
|
|
valid?: 1,
|
2017-04-20 21:09:55 +00:00
|
|
|
]
|
2017-04-16 21:13:39 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|