core: add lexical form to RDF.Literal
This commit is contained in:
parent
d0b511c771
commit
d812998fb0
8 changed files with 132 additions and 96 deletions
|
@ -60,10 +60,7 @@ defmodule RDF do
|
|||
## Examples
|
||||
|
||||
iex> RDF.literal(42)
|
||||
%RDF.Literal{value: 42, language: nil,
|
||||
datatype: %URI{authority: "www.w3.org", fragment: "integer",
|
||||
host: "www.w3.org", path: "/2001/XMLSchema", port: 80,
|
||||
query: nil, scheme: "http", userinfo: nil}}
|
||||
%RDF.Literal{value: 42, lexical: "42", datatype: XSD.integer}
|
||||
"""
|
||||
def literal(value)
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
defmodule RDF.Datatype do
|
||||
alias RDF.Literal
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
@callback id :: URI.t
|
||||
|
||||
@callback convert(any, keyword) :: any
|
||||
|
||||
@callback build_literal(any, keyword) :: RDF.Literal.t
|
||||
@callback build_literal_by_value(binary, keyword) :: RDF.Literal.t
|
||||
@callback build_literal_by_lexical(binary, keyword) :: RDF.Literal.t
|
||||
@callback build_literal_(binary, any, keyword) :: RDF.Literal.t
|
||||
|
||||
@callback canonicalize(RDF.Literal.t | any) :: binary
|
||||
|
||||
|
||||
|
||||
# TODO: This mapping should be created dynamically and be extendable, to allow user-defined datatypes ...
|
||||
|
@ -21,7 +27,11 @@ defmodule RDF.Datatype do
|
|||
# XSD.dateTime => RDF.DateTime,
|
||||
}
|
||||
|
||||
def for(id), do: @mapping[id]
|
||||
def ids, do: Map.keys(@mapping)
|
||||
def modules, do: Map.values(@mapping)
|
||||
|
||||
def get(%Literal{datatype: id}), do: get(id)
|
||||
def get(id), do: @mapping[id]
|
||||
|
||||
|
||||
defmacro __using__(opts) do
|
||||
|
@ -35,18 +45,56 @@ defmodule RDF.Datatype do
|
|||
@id unquote(id)
|
||||
def id, do: @id
|
||||
|
||||
|
||||
def new(value, opts \\ %{})
|
||||
|
||||
def new(value, opts) when is_list(opts),
|
||||
do: new(value, Map.new(opts))
|
||||
|
||||
def new(value, %{lexical: lexical} = opts),
|
||||
do: build_literal(lexical, value, opts)
|
||||
|
||||
def new(nil, %{lexical: lexical} = opts),
|
||||
do: build_literal_by_lexical(lexical, opts)
|
||||
|
||||
def new(value, opts) when is_binary(value),
|
||||
do: build_literal_by_lexical(value, opts)
|
||||
|
||||
def new(value, opts),
|
||||
do: build_literal(convert(value, opts), opts)
|
||||
do: build_literal_by_value(convert(value, opts), opts)
|
||||
|
||||
def build_literal(value, _),
|
||||
do: %Literal{value: value, datatype: @id}
|
||||
# TODO: def new!(value, opts \\ %{})
|
||||
|
||||
defoverridable [build_literal: 2]
|
||||
|
||||
def build_literal_by_value(value, opts) do
|
||||
build_literal(canonicalize(value), value, opts)
|
||||
end
|
||||
|
||||
def build_literal_by_lexical(lexical, opts) do
|
||||
build_literal(lexical, convert(lexical, opts), opts)
|
||||
end
|
||||
|
||||
def build_literal(lexical, value, _) do
|
||||
%Literal{lexical: lexical, value: value, datatype: @id}
|
||||
end
|
||||
|
||||
|
||||
def canonicalize(%Literal{value: value, lexical: nil}),
|
||||
do: canonicalize(value)
|
||||
|
||||
def canonicalize(%Literal{lexical: lexical}),
|
||||
do: lexical
|
||||
|
||||
def canonicalize(value),
|
||||
do: to_string(value)
|
||||
|
||||
|
||||
defoverridable [
|
||||
build_literal_by_value: 2,
|
||||
build_literal_by_lexical: 2,
|
||||
build_literal: 3,
|
||||
canonicalize: 1
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@ defmodule RDF.LangString do
|
|||
|
||||
def convert(value, _) when is_binary(value), do: value
|
||||
|
||||
def build_literal(value, %{language: language} = opts) do
|
||||
%Literal{value: value, datatype: @id, language: String.downcase(language)}
|
||||
def build_literal_by_lexical(lexical, %{language: language} = opts) do
|
||||
%Literal{
|
||||
lexical: lexical, value: lexical, datatype: @id,
|
||||
language: String.downcase(language)}
|
||||
end
|
||||
|
||||
def build_literal(value, opts) do
|
||||
def build_literal_by_lexical(value, opts) do
|
||||
raise ArgumentError, "datatype of rdf:langString requires a language"
|
||||
end
|
||||
|
||||
|
|
|
@ -38,13 +38,13 @@ defmodule RDF.InspectHelper do
|
|||
end
|
||||
|
||||
defimpl Inspect, for: RDF.Literal do
|
||||
def inspect(%RDF.Literal{value: value, language: language}, _opts)
|
||||
def inspect(%RDF.Literal{lexical: lexical, language: language}, _opts)
|
||||
when not is_nil(language) do
|
||||
"%RDF.Literal{value: #{inspect value}, language: #{inspect language}}"
|
||||
"%RDF.Literal{lexical: #{inspect lexical}, language: #{inspect language}}"
|
||||
end
|
||||
|
||||
def inspect(%RDF.Literal{value: value, datatype: datatype}, _opts) do
|
||||
"%RDF.Literal{value: #{inspect value}, datatype: ~I<#{datatype}>}"
|
||||
def inspect(%RDF.Literal{lexical: lexical, datatype: datatype}, _opts) do
|
||||
"%RDF.Literal{lexical: #{inspect lexical}, datatype: ~I<#{datatype}>}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2,12 +2,17 @@ defmodule RDF.Literal do
|
|||
@moduledoc """
|
||||
RDF literals are leaf nodes of a RDF graph containing raw data, like strings and numbers.
|
||||
"""
|
||||
defstruct [:value, :datatype, :language]
|
||||
defstruct [:lexical, :value, :datatype, :language]
|
||||
|
||||
@type t :: module
|
||||
|
||||
alias RDF.Datatype.NS.XSD
|
||||
|
||||
# to be able to pattern-match on plain types
|
||||
@xsd_string XSD.string
|
||||
@lang_string RDF.langString
|
||||
@plain_types [@xsd_string, @lang_string]
|
||||
|
||||
|
||||
@doc """
|
||||
Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype.
|
||||
|
@ -31,7 +36,7 @@ defmodule RDF.Literal do
|
|||
# Examples
|
||||
|
||||
iex> RDF.Literal.new(42)
|
||||
%RDF.Literal{value: 42, datatype: XSD.integer}
|
||||
%RDF.Literal{value: 42, lexical: "42", datatype: XSD.integer}
|
||||
|
||||
"""
|
||||
def new(value)
|
||||
|
@ -70,7 +75,7 @@ defmodule RDF.Literal do
|
|||
do: new(value, Map.delete(opts, :language)) # Should we raise a warning?
|
||||
|
||||
def new(value, %{datatype: %URI{} = id} = opts) do
|
||||
case RDF.Datatype.for(id) do
|
||||
case RDF.Datatype.get(id) do
|
||||
nil -> %RDF.Literal{value: value, datatype: id}
|
||||
literal_type -> literal_type.new(value, opts)
|
||||
end
|
||||
|
@ -90,18 +95,16 @@ defmodule RDF.Literal do
|
|||
|
||||
see <http://www.w3.org/TR/sparql11-query/#simple_literal>
|
||||
"""
|
||||
def simple?(%RDF.Literal{datatype: datatype}) do
|
||||
datatype == XSD.string
|
||||
end
|
||||
def simple?(%RDF.Literal{datatype: @xsd_string}), do: true
|
||||
def simple?(foo), do: false
|
||||
|
||||
@doc """
|
||||
Checks if a literal is a language-tagged literal.
|
||||
|
||||
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
||||
"""
|
||||
def has_language?(%RDF.Literal{datatype: datatype}) do
|
||||
datatype == RDF.langString
|
||||
end
|
||||
def has_language?(%RDF.Literal{datatype: @lang_string}), do: true
|
||||
def has_language?(_), do: false
|
||||
|
||||
@doc """
|
||||
Checks if a literal is a datatyped literal.
|
||||
|
@ -122,14 +125,22 @@ defmodule RDF.Literal do
|
|||
|
||||
see <http://www.w3.org/TR/rdf-concepts/#dfn-plain-literal>
|
||||
"""
|
||||
def plain?(%RDF.Literal{datatype: datatype}) do
|
||||
datatype in [RDF.langString, XSD.string]
|
||||
end
|
||||
def plain?(%RDF.Literal{datatype: datatype})
|
||||
when datatype in @plain_types, do: true
|
||||
def plain?(_), do: false
|
||||
|
||||
def typed?(literal), do: not plain?(literal)
|
||||
|
||||
#
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: RDF.Literal do
|
||||
def to_string(%RDF.Literal{value: value}) do
|
||||
# TODO: remove this when time types were implemented?
|
||||
def to_string(%RDF.Literal{lexical: nil, value: value}) do
|
||||
Kernel.to_string(value)
|
||||
end
|
||||
|
||||
def to_string(%RDF.Literal{lexical: lexical}) do
|
||||
lexical
|
||||
end
|
||||
end
|
||||
|
|
|
@ -281,8 +281,7 @@ defmodule RDF.DatasetTest do
|
|||
end
|
||||
|
||||
@tag skip: "TODO"
|
||||
test "a list of Graphs" do
|
||||
end
|
||||
test "a list of Graphs"
|
||||
|
||||
test "duplicates are ignored" do
|
||||
ds = Dataset.add(dataset(), {EX.Subject, EX.predicate, EX.Object, EX.GraphName})
|
||||
|
|
|
@ -4,92 +4,69 @@ defmodule RDF.LiteralTest do
|
|||
import RDF.Sigils
|
||||
import RDF.TestLiterals
|
||||
|
||||
alias RDF.{Literal}
|
||||
alias RDF.Literal
|
||||
alias RDF.NS.XSD
|
||||
|
||||
doctest RDF.Literal
|
||||
|
||||
|
||||
describe "construction by type inference" do
|
||||
test "creating an string literal" do
|
||||
string_literal = Literal.new("foo")
|
||||
assert string_literal.value == "foo"
|
||||
assert string_literal.datatype == XSD.string
|
||||
test "string" do
|
||||
assert Literal.new("foo") == RDF.String.new("foo")
|
||||
end
|
||||
|
||||
test "creating an integer by type inference" do
|
||||
int_literal = Literal.new(42)
|
||||
assert int_literal.value == 42
|
||||
assert int_literal.datatype == XSD.integer
|
||||
test "integer" do
|
||||
assert Literal.new(42) == RDF.Integer.new(42)
|
||||
end
|
||||
|
||||
test "creating a boolean by type inference" do
|
||||
int_literal = Literal.new(true)
|
||||
assert int_literal.value == true
|
||||
assert int_literal.datatype == XSD.boolean
|
||||
test "double" do
|
||||
assert Literal.new(3.14) == RDF.Double.new(3.14)
|
||||
end
|
||||
|
||||
int_literal = Literal.new(false)
|
||||
assert int_literal.value == false
|
||||
assert int_literal.datatype == XSD.boolean
|
||||
test "boolean" do
|
||||
assert Literal.new(true) == RDF.Boolean.new(true)
|
||||
assert Literal.new(false) == RDF.Boolean.new(false)
|
||||
end
|
||||
|
||||
@tag skip: "TODO"
|
||||
test "when options without datatype given"
|
||||
end
|
||||
|
||||
describe "typed construction" do
|
||||
test "boolean" do
|
||||
assert Literal.new(true, datatype: XSD.boolean) == RDF.Boolean.new(true)
|
||||
assert Literal.new(false, datatype: XSD.boolean) == RDF.Boolean.new(false)
|
||||
assert Literal.new("true", datatype: XSD.boolean) == RDF.Boolean.new("true")
|
||||
assert Literal.new("false", datatype: XSD.boolean) == RDF.Boolean.new("false")
|
||||
end
|
||||
|
||||
test "integer" do
|
||||
assert Literal.new(42, datatype: XSD.integer) == RDF.Integer.new(42)
|
||||
assert Literal.new("42", datatype: XSD.integer) == RDF.Integer.new("42")
|
||||
end
|
||||
|
||||
|
||||
|
||||
test "unknown datatype" do
|
||||
literal = Literal.new("custom typed value", datatype: "http://example/dt")
|
||||
assert literal.value == "custom typed value"
|
||||
assert literal.datatype == ~I<http://example/dt>
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "construction with an explicit unknown datatype" do
|
||||
literal = Literal.new("custom typed value", datatype: "http://example/dt")
|
||||
assert literal.value == "custom typed value"
|
||||
assert literal.datatype == ~I<http://example/dt>
|
||||
end
|
||||
|
||||
describe "construction with an explicit known (XSD) datatype" do
|
||||
test "creating a boolean" do
|
||||
bool_literal = Literal.new("true", datatype: XSD.boolean)
|
||||
assert bool_literal.value == true
|
||||
assert bool_literal.datatype == XSD.boolean
|
||||
|
||||
bool_literal = Literal.new(true, datatype: XSD.boolean)
|
||||
assert bool_literal.value == true
|
||||
assert bool_literal.datatype == XSD.boolean
|
||||
|
||||
bool_literal = Literal.new("false", datatype: XSD.boolean)
|
||||
assert bool_literal.value == false
|
||||
assert bool_literal.datatype == XSD.boolean
|
||||
|
||||
bool_literal = Literal.new(false, datatype: XSD.boolean)
|
||||
assert bool_literal.value == false
|
||||
assert bool_literal.datatype == XSD.boolean
|
||||
end
|
||||
|
||||
test "creating an integer" do
|
||||
int_literal = Literal.new(42, datatype: XSD.integer)
|
||||
assert int_literal.value == 42
|
||||
assert int_literal.datatype == XSD.integer
|
||||
|
||||
int_literal = Literal.new("42", datatype: XSD.integer)
|
||||
assert int_literal.value == 42
|
||||
assert int_literal.datatype == XSD.integer
|
||||
|
||||
int_literal = Literal.new(true, datatype: XSD.integer)
|
||||
assert int_literal.value == 1
|
||||
assert int_literal.datatype == XSD.integer
|
||||
int_literal = Literal.new(false, datatype: XSD.integer)
|
||||
assert int_literal.value == 0
|
||||
assert int_literal.datatype == XSD.integer
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "language-tags" do
|
||||
test "creating a string literal with a language tag" do
|
||||
describe "language tagged construction" do
|
||||
test "string literal with a language tag" do
|
||||
literal = Literal.new("Eule", language: "de")
|
||||
assert literal.value == "Eule"
|
||||
assert literal.value == "Eule"
|
||||
assert literal.datatype == RDF.langString
|
||||
assert literal.language == "de"
|
||||
end
|
||||
|
||||
test "when given a language, but the value is not a string, language is ignored" do
|
||||
test "language is ignored on non-string literals" do
|
||||
literal = Literal.new(1, language: "de")
|
||||
assert literal.value == 1
|
||||
assert literal.value == 1
|
||||
assert literal.datatype == XSD.integer
|
||||
refute literal.language
|
||||
end
|
||||
|
|
|
@ -4,6 +4,8 @@ defmodule RDF.CoreTest do
|
|||
use RDF.Vocabulary.Namespace
|
||||
defvocab EX, base_uri: "http://example.com/", terms: [], strict: false
|
||||
|
||||
alias RDF.NS.XSD
|
||||
|
||||
doctest RDF
|
||||
|
||||
# alias RDF.{Triple, Literal, BlankNode}
|
||||
|
|
Loading…
Reference in a new issue