diff --git a/lib/rdf/graph_builder.ex b/lib/rdf/graph_builder.ex index a21f1a6..fc3bd21 100644 --- a/lib/rdf/graph_builder.ex +++ b/lib/rdf/graph_builder.ex @@ -1,5 +1,5 @@ defmodule RDF.Graph.Builder do - alias RDF.{Description, Graph, Dataset, PrefixMap} + alias RDF.{Description, Graph, Dataset, PrefixMap, IRI} defmodule Error do defexception [:message] @@ -16,8 +16,11 @@ defmodule RDF.Graph.Builder do def build({:__block__, _, block}, opts) do {declarations, data} = Enum.split_with(block, &declaration?/1) - {prefixes, declarations} = extract_prefixes(declarations) {base, declarations} = extract_base(declarations) + base_string = base_string(base) + data = resolve_relative_iris(data, base_string) + declarations = resolve_relative_iris(declarations, base_string) + {prefixes, declarations} = extract_prefixes(declarations) quote do alias RDF.XSD @@ -28,7 +31,12 @@ defmodule RDF.Graph.Builder do unquote(declarations) - RDF.Graph.Builder.do_build(unquote(data), unquote(opts), unquote(prefixes), unquote(base)) + RDF.Graph.Builder.do_build( + unquote(data), + unquote(opts), + unquote(prefixes), + unquote(base_string) + ) end end @@ -60,6 +68,31 @@ defmodule RDF.Graph.Builder do end) end + defp base_string(nil), do: nil + defp base_string(base) when is_binary(base), do: base + defp base_string(base) when is_atom(base), do: apply(base, :__base_iri__, []) + defp base_string({:sigil_I, _, [{_, _, [base]}, _]}), do: base + + defp base_string(_) do + raise Error, + message: "invalid @base expression; only literal values are allowed as @base value" + end + + defp resolve_relative_iris(ast, base) do + Macro.prewalk(ast, fn + {:sigil_I, meta_outer, [{:<<>>, meta_inner, [iri]}, list]} = sigil -> + if IRI.absolute?(iri) do + sigil + else + absolute = iri |> IRI.absolute(base) |> IRI.to_string() + {:sigil_I, meta_outer, [{:<<>>, meta_inner, [absolute]}, list]} + end + + other -> + other + end) + end + defp extract_base(declarations) do {base, declarations} = Enum.reduce(declarations, {nil, []}, fn diff --git a/test/unit/graph_builder_test.exs b/test/unit/graph_builder_test.exs index aa75fc8..c299580 100644 --- a/test/unit/graph_builder_test.exs +++ b/test/unit/graph_builder_test.exs @@ -12,7 +12,7 @@ defmodule RDF.Graph.BuilderTest do defmodule TestNS do use RDF.Vocabulary.Namespace defvocab EX, base_iri: "http://example.com/", terms: [], strict: false - defvocab Custom, base_iri: "http://custom.com/foo#", terms: [], strict: false + defvocab Custom, base_iri: "http://custom.com/foo/", terms: [], strict: false defvocab ImportTest, base_iri: "http://import.com/bar#", terms: [:foo, :Bar] end @@ -410,6 +410,8 @@ defmodule RDF.Graph.BuilderTest do describe "@base" do test "with vocabulary namespace" do + import RDF.Sigils + # we're wrapping this in a function to isolate the alias graph = (fn -> @@ -418,12 +420,17 @@ defmodule RDF.Graph.BuilderTest do # @base TestNS.Custom @base RDF.Graph.BuilderTest.TestNS.Custom - Custom.S |> Custom.p(Custom.O) + ~I |> Custom.p(~I) + {~I, ~I, ~I} end end).() assert graph == - RDF.graph(TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O), + RDF.graph( + [ + TestNS.Custom.S |> TestNS.Custom.p(TestNS.Custom.O), + TestNS.Custom.foo() |> TestNS.Custom.bar(TestNS.Custom.baz()) + ], base_iri: TestNS.Custom ) end @@ -433,10 +440,19 @@ defmodule RDF.Graph.BuilderTest do RDF.Graph.build do @base ~I - EX.S |> EX.p(EX.O) + ~I<#S> |> EX.p(~I<#O>) + {~I<#foo>, ~I<#bar>, ~I<#baz>} end - assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") + assert graph == + RDF.graph( + [ + ~I |> EX.p(~I), + {~I, ~I, + ~I} + ], + base_iri: "http://example.com/base" + ) end test "with URI as string" do @@ -444,22 +460,19 @@ defmodule RDF.Graph.BuilderTest do RDF.Graph.build do @base "http://example.com/base" - EX.S |> EX.p(EX.O) + ~I<#S> |> EX.p(~I<#O>) + {~I<#foo>, ~I<#bar>, ~I<#baz>} end - assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") - end - - test "with URI from variable" do - graph = - RDF.Graph.build do - foo = "http://example.com/base" - @base foo - - EX.S |> EX.p(EX.O) - end - - assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") + assert graph == + RDF.graph( + [ + ~I |> EX.p(~I), + {~I, ~I, + ~I} + ], + base_iri: "http://example.com/base" + ) end test "conflict with base_iri opt" do @@ -467,10 +480,13 @@ defmodule RDF.Graph.BuilderTest do RDF.Graph.build base_iri: "http://example.com/old" do @base "http://example.com/base" - EX.S |> EX.p(EX.O) + ~I<#S> |> EX.p(~I<#O>) end - assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") + assert graph == + RDF.graph(~I |> EX.p(~I), + base_iri: "http://example.com/base" + ) end end