2016-10-15 16:26:56 +00:00
|
|
|
defmodule RDF.Literal do
|
|
|
|
@moduledoc """
|
|
|
|
RDF literals are leaf nodes of a RDF graph containing raw data, like strings and numbers.
|
|
|
|
"""
|
2017-06-09 22:18:39 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
defstruct [:literal]
|
2017-04-20 21:09:55 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
alias RDF.{IRI, LangString}
|
|
|
|
alias RDF.Literal.{Generic, Datatype}
|
|
|
|
|
|
|
|
@type t :: %__MODULE__{:literal => Datatype.literal()}
|
|
|
|
|
|
|
|
@rdf_lang_string RDF.Utils.Bootstrapping.rdf_iri("langString")
|
2016-10-15 16:26:56 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype.
|
|
|
|
|
2020-05-05 21:58:44 +00:00
|
|
|
See `coerce/1` for applied mapping of Elixir types to XSD datatypes.
|
2016-10-15 16:26:56 +00:00
|
|
|
|
2020-05-05 21:58:44 +00:00
|
|
|
Note: The `RDF.literal` function is a shortcut to this function.
|
2016-10-15 16:26:56 +00:00
|
|
|
|
2017-06-16 22:27:05 +00:00
|
|
|
## Examples
|
2016-10-15 16:26:56 +00:00
|
|
|
|
|
|
|
iex> RDF.Literal.new(42)
|
2020-05-05 21:58:44 +00:00
|
|
|
%RDF.Literal{literal: %RDF.XSD.Integer{value: 42}}
|
2016-10-15 16:26:56 +00:00
|
|
|
|
|
|
|
"""
|
2020-04-10 21:40:33 +00:00
|
|
|
@spec new(t | any) :: t | nil
|
2016-10-15 16:26:56 +00:00
|
|
|
def new(value) do
|
2020-05-05 21:58:44 +00:00
|
|
|
case coerce(value) do
|
|
|
|
nil ->
|
|
|
|
raise RDF.Literal.InvalidError, "#{inspect value} not convertible to a RDF.Literal"
|
|
|
|
literal -> literal
|
|
|
|
end
|
2016-10-15 16:26:56 +00:00
|
|
|
end
|
|
|
|
|
2018-03-14 10:46:11 +00:00
|
|
|
@doc """
|
|
|
|
Creates a new `RDF.Literal` with the given datatype or language tag.
|
|
|
|
"""
|
2020-04-10 21:40:33 +00:00
|
|
|
@spec new(t | any, keyword) :: t | nil
|
|
|
|
def new(value, opts) do
|
|
|
|
cond do
|
|
|
|
length(opts) == 0 ->
|
|
|
|
new(value)
|
2016-10-30 18:36:46 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
Keyword.has_key?(opts, :language) ->
|
|
|
|
if Keyword.get(opts, :datatype, @rdf_lang_string) |> IRI.new() == @rdf_lang_string do
|
|
|
|
LangString.new(value, opts)
|
|
|
|
else
|
|
|
|
raise ArgumentError, "datatype with language must be rdf:langString"
|
|
|
|
end
|
2017-04-16 21:13:39 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
datatype = Keyword.get(opts, :datatype) ->
|
|
|
|
case Datatype.Registry.get(datatype) do
|
|
|
|
nil -> Generic.new(value, opts)
|
|
|
|
datatype -> datatype.new(value, opts)
|
|
|
|
end
|
2017-04-16 21:13:39 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
true ->
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
2017-04-16 21:13:39 +00:00
|
|
|
|
2020-05-05 21:58:44 +00:00
|
|
|
@doc """
|
|
|
|
Coerces a new `RDF.Literal` from another value.
|
|
|
|
|
|
|
|
The following mapping of Elixir types to XSD datatypes is applied:
|
|
|
|
|
|
|
|
| Elixir datatype | XSD datatype |
|
|
|
|
| :-------------- | :------------- |
|
|
|
|
| `string` | `xsd:string` |
|
|
|
|
| `boolean` | `xsd:boolean` |
|
|
|
|
| `integer` | `xsd:integer` |
|
|
|
|
| `float` | `xsd:double` |
|
|
|
|
| `Decimal` | `xsd:decimal` |
|
|
|
|
| `Time` | `xsd:time` |
|
|
|
|
| `Date` | `xsd:date` |
|
|
|
|
| `DateTime` | `xsd:dateTime` |
|
|
|
|
| `NaiveDateTime` | `xsd:dateTime` |
|
|
|
|
| `URI` | `xsd:AnyURI` |
|
|
|
|
|
|
|
|
When an `RDF.Literal` can not be coerced, `nil` is returned
|
|
|
|
(as opposed to `new/1` which fails in this case).
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> RDF.Literal.coerce(42)
|
|
|
|
%RDF.Literal{literal: %RDF.XSD.Integer{value: 42}}
|
|
|
|
|
|
|
|
"""
|
|
|
|
def coerce(value)
|
|
|
|
|
|
|
|
def coerce(%__MODULE__{} = literal), do: literal
|
|
|
|
|
|
|
|
def coerce(value) when is_binary(value), do: RDF.XSD.String.new(value)
|
|
|
|
def coerce(value) when is_boolean(value), do: RDF.XSD.Boolean.new(value)
|
|
|
|
def coerce(value) when is_integer(value), do: RDF.XSD.Integer.new(value)
|
|
|
|
def coerce(value) when is_float(value), do: RDF.XSD.Double.new(value)
|
|
|
|
def coerce(%Decimal{} = value), do: RDF.XSD.Decimal.new(value)
|
|
|
|
def coerce(%Date{} = value), do: RDF.XSD.Date.new(value)
|
|
|
|
def coerce(%Time{} = value), do: RDF.XSD.Time.new(value)
|
|
|
|
def coerce(%DateTime{} = value), do: RDF.XSD.DateTime.new(value)
|
|
|
|
def coerce(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value)
|
|
|
|
def coerce(%URI{} = value), do: RDF.XSD.AnyURI.new(value)
|
2020-05-07 13:37:21 +00:00
|
|
|
|
|
|
|
# Although the following catch-all-clause for all structs could handle the core datatypes
|
|
|
|
# we're generating dedicated clauses for them here, as they are approx. 15% faster
|
|
|
|
Enum.each(Datatype.Registry.core_datatypes(), fn datatype ->
|
|
|
|
def coerce(%unquote(datatype){} = datatype_literal) do
|
|
|
|
%__MODULE__{literal: datatype_literal}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
|
|
|
def coerce(%_datatype{} = datatype_literal) do
|
|
|
|
if Datatype.Registry.literal?(datatype_literal) do
|
|
|
|
%__MODULE__{literal: datatype_literal}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def coerce(_), do: nil
|
2020-05-05 21:58:44 +00:00
|
|
|
|
|
|
|
|
2018-03-14 10:46:11 +00:00
|
|
|
@doc """
|
|
|
|
Creates a new `RDF.Literal`, but fails if it's not valid.
|
|
|
|
|
|
|
|
Note: Validation is only possible if an `RDF.Datatype` with an implementation of
|
|
|
|
`RDF.Datatype.valid?/1` exists.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
iex> RDF.Literal.new("foo")
|
2020-05-05 21:58:44 +00:00
|
|
|
%RDF.Literal{literal: %RDF.XSD.String{value: "foo"}}
|
2018-03-14 10:46:11 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
iex> RDF.Literal.new!("foo", datatype: RDF.NS.XSD.integer)
|
2020-05-05 21:58:44 +00:00
|
|
|
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.XSD.Integer{value: nil, lexical: "foo"}, valid: false}
|
2018-03-14 10:46:11 +00:00
|
|
|
|
|
|
|
iex> RDF.Literal.new!("foo", datatype: RDF.langString)
|
2020-04-10 21:40:33 +00:00
|
|
|
** (RDF.Literal.InvalidError) invalid RDF.Literal: %RDF.Literal{literal: %RDF.LangString{language: nil, value: "foo"}, valid: false}
|
2018-03-14 10:46:11 +00:00
|
|
|
|
|
|
|
"""
|
2020-04-10 21:40:33 +00:00
|
|
|
@spec new!(t | any, keyword) :: t
|
|
|
|
def new!(value, opts \\ []) do
|
|
|
|
literal = new(value, opts)
|
|
|
|
if valid?(literal) do
|
|
|
|
literal
|
2018-03-14 10:46:11 +00:00
|
|
|
else
|
2020-04-10 21:40:33 +00:00
|
|
|
raise RDF.Literal.InvalidError, "invalid RDF.Literal: #{inspect literal}"
|
2017-04-23 21:41:29 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
@doc """
|
2017-06-09 22:18:39 +00:00
|
|
|
Returns if a literal is a language-tagged literal.
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
|
|
|
"""
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec has_language?(t) :: boolean
|
2020-04-10 21:40:33 +00:00
|
|
|
def has_language?(%__MODULE__{literal: %LangString{} = literal}), do: LangString.valid?(literal)
|
|
|
|
def has_language?(%__MODULE__{} = _), do: false
|
2017-04-23 21:41:29 +00:00
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
@doc """
|
2017-06-09 22:18:39 +00:00
|
|
|
Returns if a literal is a datatyped literal.
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
For historical reasons, this excludes `xsd:string` and `rdf:langString`.
|
|
|
|
|
|
|
|
see <http://www.w3.org/TR/rdf-concepts/#dfn-typed-literal>
|
|
|
|
"""
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec has_datatype?(t) :: boolean
|
2017-04-16 21:13:39 +00:00
|
|
|
def has_datatype?(literal) do
|
|
|
|
not plain?(literal) and not has_language?(literal)
|
2017-04-02 21:15:07 +00:00
|
|
|
end
|
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
@doc """
|
|
|
|
Returns if a literal is a simple literal.
|
|
|
|
|
|
|
|
A simple literal has no datatype or language.
|
|
|
|
|
|
|
|
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
|
|
|
|
"""
|
|
|
|
@spec simple?(t) :: boolean
|
2020-05-05 21:58:44 +00:00
|
|
|
def simple?(%__MODULE__{literal: %RDF.XSD.String{}}), do: true
|
2020-04-10 21:40:33 +00:00
|
|
|
def simple?(%__MODULE__{} = _), do: false
|
|
|
|
|
2017-04-23 21:41:29 +00:00
|
|
|
|
2017-04-16 21:13:39 +00:00
|
|
|
@doc """
|
2017-06-09 22:18:39 +00:00
|
|
|
Returns if a literal is a plain literal.
|
2017-04-16 21:13:39 +00:00
|
|
|
|
|
|
|
A plain literal may have a language, but may not have a datatype.
|
|
|
|
For all practical purposes, this includes `xsd:string` literals too.
|
|
|
|
|
|
|
|
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
|
|
|
"""
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec plain?(t) :: boolean
|
2020-05-05 21:58:44 +00:00
|
|
|
def plain?(%__MODULE__{literal: %RDF.XSD.String{}}), do: true
|
2020-04-10 21:40:33 +00:00
|
|
|
def plain?(%__MODULE__{literal: %LangString{}}), do: true
|
|
|
|
def plain?(%__MODULE__{} = _), do: false
|
2017-04-16 21:13:39 +00:00
|
|
|
|
2020-03-02 01:07:31 +00:00
|
|
|
@spec typed?(t) :: boolean
|
2017-04-20 21:09:55 +00:00
|
|
|
def typed?(literal), do: not plain?(literal)
|
|
|
|
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
############################################################################
|
2020-05-05 21:58:44 +00:00
|
|
|
# functions delegating to the RDF.Literal.Datatype of a RDF.Literal
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec datatype(t) :: IRI.t()
|
2020-05-05 21:58:44 +00:00
|
|
|
def datatype(%__MODULE__{literal: %datatype{} = literal}), do: datatype.datatype(literal)
|
2018-09-16 02:02:53 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec language(t) :: String.t() | nil
|
2020-05-05 21:58:44 +00:00
|
|
|
def language(%__MODULE__{literal: %datatype{} = literal}), do: datatype.language(literal)
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec value(t) :: any
|
2020-04-10 21:40:33 +00:00
|
|
|
def value(%__MODULE__{literal: %datatype{} = literal}), do: datatype.value(literal)
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec lexical(t) :: String.t
|
2020-04-10 21:40:33 +00:00
|
|
|
def lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.lexical(literal)
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec canonical(t) :: t
|
2020-05-05 21:58:44 +00:00
|
|
|
def canonical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical(literal)
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-05-06 13:19:32 +00:00
|
|
|
@spec canonical_lexical(t) :: String.t | nil
|
|
|
|
def canonical_lexical(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical_lexical(literal)
|
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec canonical?(t) :: boolean
|
2020-04-10 21:40:33 +00:00
|
|
|
def canonical?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.canonical?(literal)
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec valid?(t | any) :: boolean
|
2020-04-10 21:40:33 +00:00
|
|
|
def valid?(%__MODULE__{literal: %datatype{} = literal}), do: datatype.valid?(literal)
|
|
|
|
def valid?(_), do: false
|
|
|
|
|
2020-05-05 21:58:44 +00:00
|
|
|
@spec equal?(any, any) :: boolean
|
|
|
|
def equal?(left, right), do: left == right
|
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec equal_value?(t, t | any) :: boolean
|
2020-05-05 21:58:44 +00:00
|
|
|
def equal_value?(%__MODULE__{literal: %datatype{} = left}, right),
|
|
|
|
do: datatype.equal_value?(left, right)
|
|
|
|
|
|
|
|
def equal_value?(left, right) when not is_nil(left),
|
|
|
|
do: equal_value?(coerce(left), right)
|
2018-09-16 02:02:53 +00:00
|
|
|
|
2020-04-18 19:27:36 +00:00
|
|
|
def equal_value?(_, _), do: nil
|
2018-06-08 10:26:52 +00:00
|
|
|
|
2020-04-12 13:38:02 +00:00
|
|
|
@spec compare(t, t) :: Datatype.comparison_result | :indeterminate | nil
|
2020-04-10 21:40:33 +00:00
|
|
|
def compare(%__MODULE__{literal: %datatype{} = left}, right) do
|
2020-05-05 21:58:44 +00:00
|
|
|
datatype.compare(left, right)
|
2020-04-10 21:40:33 +00:00
|
|
|
end
|
2018-11-02 21:00:48 +00:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Checks if the first of two `RDF.Literal`s is smaller then the other.
|
|
|
|
"""
|
2020-05-05 21:58:44 +00:00
|
|
|
@spec less_than?(t, t) :: boolean
|
|
|
|
def less_than?(left, right) do
|
|
|
|
compare(left, right) == :lt
|
2018-11-02 21:00:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Checks if the first of two `RDF.Literal`s is greater then the other.
|
|
|
|
"""
|
2020-05-05 21:58:44 +00:00
|
|
|
@spec greater_than?(t, t) :: boolean
|
|
|
|
def greater_than?(left, right) do
|
|
|
|
compare(left, right) == :gt
|
2018-11-02 21:00:48 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
2020-05-06 13:19:32 +00:00
|
|
|
Matches the lexical form of the given `RDF.Literal` against a XPath and XQuery regular expression pattern with flags.
|
2019-04-20 21:33:09 +00:00
|
|
|
|
|
|
|
The regular expression language is defined in _XQuery 1.0 and XPath 2.0 Functions and Operators_.
|
|
|
|
|
|
|
|
see <https://www.w3.org/TR/xpath-functions/#func-matches>
|
|
|
|
"""
|
2020-04-10 21:40:33 +00:00
|
|
|
@spec matches?(t | String.t, pattern :: t | String.t, flags :: t | String.t) :: boolean
|
|
|
|
def matches?(value, pattern, flags \\ "")
|
|
|
|
def matches?(%__MODULE__{} = literal, pattern, flags),
|
|
|
|
do: matches?(lexical(literal), pattern, flags)
|
2020-05-05 21:58:44 +00:00
|
|
|
def matches?(value, %__MODULE__{literal: %RDF.XSD.String{}} = pattern, flags),
|
2020-04-10 21:40:33 +00:00
|
|
|
do: matches?(value, lexical(pattern), flags)
|
2020-05-05 21:58:44 +00:00
|
|
|
def matches?(value, pattern, %__MODULE__{literal: %RDF.XSD.String{}} = flags),
|
2020-04-10 21:40:33 +00:00
|
|
|
do: matches?(value, pattern, lexical(flags))
|
|
|
|
def matches?(value, pattern, flags) when is_binary(value) and is_binary(pattern) and is_binary(flags),
|
2020-05-05 21:58:44 +00:00
|
|
|
do: RDF.XSD.Utils.Regex.matches?(value, pattern, flags)
|
2020-04-10 21:40:33 +00:00
|
|
|
|
2020-04-16 18:57:10 +00:00
|
|
|
def update(%__MODULE__{literal: %datatype{} = literal}, fun, opts \\ []) do
|
2020-05-05 21:58:44 +00:00
|
|
|
datatype.update(literal, fun, opts)
|
2020-04-16 18:57:10 +00:00
|
|
|
end
|
|
|
|
|
2020-04-10 21:40:33 +00:00
|
|
|
defimpl String.Chars do
|
|
|
|
def to_string(literal) do
|
|
|
|
String.Chars.to_string(literal.literal)
|
2019-04-20 21:33:09 +00:00
|
|
|
end
|
|
|
|
end
|
2016-10-15 16:26:56 +00:00
|
|
|
end
|