core: add lexical form to RDF.Literal

This commit is contained in:
Marcel Otto 2017-04-20 23:09:55 +02:00
parent d0b511c771
commit d812998fb0
8 changed files with 132 additions and 96 deletions

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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})

View file

@ -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

View file

@ -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}