Add auto-resolution of relative IRIs in Graph build blocks

This commit is contained in:
Marcel Otto 2022-04-06 22:43:02 +02:00
parent 3ff1186336
commit 178e8315ab
2 changed files with 73 additions and 24 deletions

View file

@ -1,5 +1,5 @@
defmodule RDF.Graph.Builder do defmodule RDF.Graph.Builder do
alias RDF.{Description, Graph, Dataset, PrefixMap} alias RDF.{Description, Graph, Dataset, PrefixMap, IRI}
defmodule Error do defmodule Error do
defexception [:message] defexception [:message]
@ -16,8 +16,11 @@ defmodule RDF.Graph.Builder do
def build({:__block__, _, block}, opts) do def build({:__block__, _, block}, opts) do
{declarations, data} = Enum.split_with(block, &declaration?/1) {declarations, data} = Enum.split_with(block, &declaration?/1)
{prefixes, declarations} = extract_prefixes(declarations)
{base, declarations} = extract_base(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 quote do
alias RDF.XSD alias RDF.XSD
@ -28,7 +31,12 @@ defmodule RDF.Graph.Builder do
unquote(declarations) 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
end end
@ -60,6 +68,31 @@ defmodule RDF.Graph.Builder do
end) end)
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 defp extract_base(declarations) do
{base, declarations} = {base, declarations} =
Enum.reduce(declarations, {nil, []}, fn Enum.reduce(declarations, {nil, []}, fn

View file

@ -12,7 +12,7 @@ defmodule RDF.Graph.BuilderTest do
defmodule TestNS do defmodule TestNS do
use RDF.Vocabulary.Namespace use RDF.Vocabulary.Namespace
defvocab EX, base_iri: "http://example.com/", terms: [], strict: false 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] defvocab ImportTest, base_iri: "http://import.com/bar#", terms: [:foo, :Bar]
end end
@ -410,6 +410,8 @@ defmodule RDF.Graph.BuilderTest do
describe "@base" do describe "@base" do
test "with vocabulary namespace" do test "with vocabulary namespace" do
import RDF.Sigils
# we're wrapping this in a function to isolate the alias # we're wrapping this in a function to isolate the alias
graph = graph =
(fn -> (fn ->
@ -418,12 +420,17 @@ defmodule RDF.Graph.BuilderTest do
# @base TestNS.Custom # @base TestNS.Custom
@base RDF.Graph.BuilderTest.TestNS.Custom @base RDF.Graph.BuilderTest.TestNS.Custom
Custom.S |> Custom.p(Custom.O) ~I<S> |> Custom.p(~I<O>)
{~I<foo>, ~I<bar>, ~I<baz>}
end end
end).() end).()
assert graph == 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 base_iri: TestNS.Custom
) )
end end
@ -433,10 +440,19 @@ defmodule RDF.Graph.BuilderTest do
RDF.Graph.build do RDF.Graph.build do
@base ~I<http://example.com/base> @base ~I<http://example.com/base>
EX.S |> EX.p(EX.O) ~I<#S> |> EX.p(~I<#O>)
{~I<#foo>, ~I<#bar>, ~I<#baz>}
end end
assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") assert graph ==
RDF.graph(
[
~I<http://example.com/base#S> |> EX.p(~I<http://example.com/base#O>),
{~I<http://example.com/base#foo>, ~I<http://example.com/base#bar>,
~I<http://example.com/base#baz>}
],
base_iri: "http://example.com/base"
)
end end
test "with URI as string" do test "with URI as string" do
@ -444,22 +460,19 @@ defmodule RDF.Graph.BuilderTest do
RDF.Graph.build do RDF.Graph.build do
@base "http://example.com/base" @base "http://example.com/base"
EX.S |> EX.p(EX.O) ~I<#S> |> EX.p(~I<#O>)
{~I<#foo>, ~I<#bar>, ~I<#baz>}
end end
assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") assert graph ==
end RDF.graph(
[
test "with URI from variable" do ~I<http://example.com/base#S> |> EX.p(~I<http://example.com/base#O>),
graph = {~I<http://example.com/base#foo>, ~I<http://example.com/base#bar>,
RDF.Graph.build do ~I<http://example.com/base#baz>}
foo = "http://example.com/base" ],
@base foo base_iri: "http://example.com/base"
)
EX.S |> EX.p(EX.O)
end
assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base")
end end
test "conflict with base_iri opt" do 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 RDF.Graph.build base_iri: "http://example.com/old" do
@base "http://example.com/base" @base "http://example.com/base"
EX.S |> EX.p(EX.O) ~I<#S> |> EX.p(~I<#O>)
end end
assert graph == RDF.graph(EX.S |> EX.p(EX.O), base_iri: "http://example.com/base") assert graph ==
RDF.graph(~I<http://example.com/base#S> |> EX.p(~I<http://example.com/base#O>),
base_iri: "http://example.com/base"
)
end end
end end