From 47a19c0a67662680325a47d6810fdaa979eaae19 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 17 May 2020 00:53:36 +0200 Subject: [PATCH] Add proper handling of vocabulary terms at various places - in the coerce and value functions of the RDF.Term protocol for atoms - allow them as XSD.AnyURI values - RDF.Literal.coerce produces XSD.AnyURI literals from them - allow them in equal_value comparisons with RDF.IRIs and XSD.AnyURIs --- lib/rdf/iri.ex | 10 ++++++++++ lib/rdf/literal.ex | 9 +++++++++ lib/rdf/term.ex | 11 +++++++++-- lib/rdf/xsd/datatypes/any_uri.ex | 20 ++++++++++++++++++++ test/unit/equality_test.exs | 4 ++++ test/unit/literal_test.exs | 12 +++++++++++- test/unit/term_test.exs | 20 ++++++++++++++++++++ test/unit/xsd/datatypes/any_uri_test.exs | 6 ++++-- 8 files changed, 87 insertions(+), 5 deletions(-) diff --git a/lib/rdf/iri.ex b/lib/rdf/iri.ex index 2a4e138..8007860 100644 --- a/lib/rdf/iri.ex +++ b/lib/rdf/iri.ex @@ -238,6 +238,16 @@ defmodule RDF.IRI do def equal_value?(%__MODULE__{value: left}, %URI{} = right), do: left == URI.to_string(right) + def equal_value?(left, %__MODULE__{} = right) when maybe_ns_term(left), + do: equal_value?(right, left) + + def equal_value?(%__MODULE__{} = left, right) when maybe_ns_term(right) do + case Namespace.resolve_term(right) do + {:ok, iri} -> equal_value?(left, iri) + _ -> nil + end + end + def equal_value?(_, _), do: nil diff --git a/lib/rdf/literal.ex b/lib/rdf/literal.ex index d109388..8cc9280 100644 --- a/lib/rdf/literal.ex +++ b/lib/rdf/literal.ex @@ -8,6 +8,8 @@ defmodule RDF.Literal do alias RDF.{IRI, LangString} alias RDF.Literal.{Generic, Datatype} + import RDF.Guards + @type t :: %__MODULE__{:literal => Datatype.literal()} @rdf_lang_string RDF.Utils.Bootstrapping.rdf_iri("langString") @@ -103,6 +105,13 @@ defmodule RDF.Literal do def coerce(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value) def coerce(%URI{} = value), do: RDF.XSD.AnyURI.new(value) + def coerce(value) when maybe_ns_term(value) do + case RDF.Namespace.resolve_term(value) do + {:ok, iri} -> iri |> IRI.parse() |> coerce() + _ -> nil + end + end + # Although the following catch-all-clause for all structs could handle the builtin datatypes # we're generating dedicated clauses for them here, as they are approx. 15% faster Enum.each(Datatype.Registry.builtin_datatypes(), fn datatype -> diff --git a/lib/rdf/term.ex b/lib/rdf/term.ex index 9852bf1..01a27c4 100644 --- a/lib/rdf/term.ex +++ b/lib/rdf/term.ex @@ -134,11 +134,18 @@ defimpl RDF.Term, for: Atom do def coerce(true), do: RDF.XSD.true def coerce(false), do: RDF.XSD.false - def coerce(_), do: nil + def coerce(nil), do: nil + def coerce(term) do + case RDF.Namespace.resolve_term(term) do + {:ok, iri} -> iri + _ -> nil + end + end def value(true), do: true def value(false), do: false - def value(_), do: nil + def value(nil), do: nil + def value(term), do: RDF.Term.value(coerce(term)) def term?(_), do: false end diff --git a/lib/rdf/xsd/datatypes/any_uri.ex b/lib/rdf/xsd/datatypes/any_uri.ex index d1e15ff..1208d7f 100644 --- a/lib/rdf/xsd/datatypes/any_uri.ex +++ b/lib/rdf/xsd/datatypes/any_uri.ex @@ -9,6 +9,8 @@ defmodule RDF.XSD.AnyURI do alias RDF.IRI + import RDF.Guards + use RDF.XSD.Datatype.Primitive, name: "anyURI", id: RDF.Utils.Bootstrapping.xsd_iri("anyURI") @@ -20,6 +22,14 @@ defmodule RDF.XSD.AnyURI do @impl RDF.XSD.Datatype @spec elixir_mapping(any, Keyword.t()) :: value def elixir_mapping(%URI{} = uri, _), do: uri + + def elixir_mapping(value, _) when maybe_ns_term(value) do + case RDF.Namespace.resolve_term(value) do + {:ok, iri} -> IRI.parse(iri) + _ -> @invalid_value + end + end + def elixir_mapping(_, _), do: @invalid_value @impl RDF.Literal.Datatype @@ -35,5 +45,15 @@ defmodule RDF.XSD.AnyURI do def do_equal_value?(%__MODULE__{} = any_uri, %IRI{value: iri}), do: lexical(any_uri) == iri + def do_equal_value?(left, %__MODULE__{} = right) when maybe_ns_term(left), + do: equal_value?(right, left) + + def do_equal_value?(%__MODULE__{} = left, right) when maybe_ns_term(right) do + case RDF.Namespace.resolve_term(right) do + {:ok, iri} -> equal_value?(left, iri) + _ -> nil + end + end + def do_equal_value?(literal1, literal2), do: super(literal1, literal2) end diff --git a/test/unit/equality_test.exs b/test/unit/equality_test.exs index 8b7c1f0..0699a8e 100644 --- a/test/unit/equality_test.exs +++ b/test/unit/equality_test.exs @@ -18,10 +18,14 @@ defmodule RDF.EqualityTest do @equal_iris_by_coercion [ {RDF.iri("http://example.com/"), URI.parse("http://example.com/")}, {XSD.anyURI("http://example.com/"), URI.parse("http://example.com/")}, + {RDF.iri("http://example.com/Foo"), EX.Foo}, + {XSD.anyURI("http://example.com/Foo"), EX.Foo}, ] @unequal_iris_by_coercion [ {RDF.iri("http://example.com/foo"), URI.parse("http://example.com/bar")}, {XSD.anyURI("http://example.com/foo"), URI.parse("http://example.com/bar")}, + {RDF.iri("http://example.com/Bar"), EX.Foo}, + {XSD.anyURI("http://example.com/Bar"), EX.Foo}, ] @incomparable_iris [ {RDF.iri("http://example.com/"), XSD.string("http://example.com/")}, diff --git a/test/unit/literal_test.exs b/test/unit/literal_test.exs index 7beaf30..3c45119 100644 --- a/test/unit/literal_test.exs +++ b/test/unit/literal_test.exs @@ -168,6 +168,15 @@ defmodule RDF.LiteralTest do XSD.any_uri("http://example.com") end + test "with a resolvable vocabulary namespace term atom" do + assert Literal.coerce(EX.Foo) == EX.Foo |> RDF.iri() |> IRI.parse() |> XSD.any_uri() + end + + test "with a non-resolvable atom" do + refute Literal.coerce(Foo) + refute Literal.coerce(:foo) + end + test "with RDF.Literals" do assert XSD.integer(42) |> Literal.coerce() == XSD.integer(42) end @@ -177,7 +186,8 @@ defmodule RDF.LiteralTest do end test "with inconvertible values" do - assert self() |> Literal.coerce() == nil + refute Literal.coerce(nil) + refute Literal.coerce(self()) end end diff --git a/test/unit/term_test.exs b/test/unit/term_test.exs index fa7df1e..99fd074 100644 --- a/test/unit/term_test.exs +++ b/test/unit/term_test.exs @@ -18,6 +18,16 @@ defmodule RDF.TermTest do assert RDF.Term.coerce(~L"foo") == ~L"foo" end + test "with a resolvable vocabulary namespace term atom" do + assert RDF.Term.coerce(EX.Foo) == RDF.iri(EX.Foo) + end + + test "with a non-resolvable atom" do + refute RDF.Term.coerce(nil) + refute RDF.Term.coerce(Foo) + refute RDF.Term.coerce(:foo) + end + test "with boolean" do assert RDF.Term.coerce(true) == XSD.true assert RDF.Term.coerce(false) == XSD.false @@ -83,6 +93,16 @@ defmodule RDF.TermTest do assert XSD.integer("foo") |> RDF.Term.value() == "foo" end + test "with a resolvable vocabulary namespace term atom" do + assert RDF.Term.value(EX.Foo) == EX.Foo |> RDF.iri() |> to_string() + end + + test "with a non-resolvable atom" do + refute RDF.Term.value(nil) + refute RDF.Term.value(Foo) + refute RDF.Term.value(:foo) + end + test "with boolean" do assert RDF.Term.value(true) == true assert RDF.Term.value(false) == false diff --git a/test/unit/xsd/datatypes/any_uri_test.exs b/test/unit/xsd/datatypes/any_uri_test.exs index ad821c7..fad447f 100644 --- a/test/unit/xsd/datatypes/any_uri_test.exs +++ b/test/unit/xsd/datatypes/any_uri_test.exs @@ -8,9 +8,11 @@ defmodule RDF.XSD.AnyURITest do "http://example.com/foo" => {URI.parse("http://example.com/foo"), nil, "http://example.com/foo"}, URI.parse("http://example.com/foo") => - {URI.parse("http://example.com/foo"), nil, "http://example.com/foo"} + {URI.parse("http://example.com/foo"), nil, "http://example.com/foo"}, + RDF.List => + {URI.parse("http://www.w3.org/1999/02/22-rdf-syntax-ns#List"), nil, "http://www.w3.org/1999/02/22-rdf-syntax-ns#List"}, }, - invalid: [42, 3.14, true, false] + invalid: [42, 3.14, Foo, :foo, true, false] describe "cast/1" do