Creation of ad-hoc namespaces in RDF.Graph.build/2
This commit is contained in:
parent
e9fd42430b
commit
eac696114f
4 changed files with 126 additions and 11 deletions
|
@ -17,6 +17,8 @@ The generated namespaces are much more flexible now and compile faster.
|
||||||
- macro `RDF.Namespace.IRI.iri/1` which allows to resolve `RDF.Namespace` terms
|
- macro `RDF.Namespace.IRI.iri/1` which allows to resolve `RDF.Namespace` terms
|
||||||
inside of pattern matches
|
inside of pattern matches
|
||||||
- `RDF.IRI.starts_with?/2` and `RDF.IRI.ends_with?/2`
|
- `RDF.IRI.starts_with?/2` and `RDF.IRI.ends_with?/2`
|
||||||
|
- `RDF.Graph.build/2` now supports the creation of ad-hoc vocabulary namespaces
|
||||||
|
with a `@prefix` declaration providing the URI of the namespace as a string
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,12 @@ defmodule RDF.Graph do
|
||||||
For a description of the DSL see [this guide](https://rdf-elixir.dev/rdf-ex/description-and-graph-dsl.html).
|
For a description of the DSL see [this guide](https://rdf-elixir.dev/rdf-ex/description-and-graph-dsl.html).
|
||||||
"""
|
"""
|
||||||
defmacro build(opts \\ [], do: block) do
|
defmacro build(opts \\ [], do: block) do
|
||||||
Builder.build(block, __CALLER__, opts)
|
Builder.build(
|
||||||
|
block,
|
||||||
|
__CALLER__,
|
||||||
|
Builder.namespace_context_mod(__CALLER__),
|
||||||
|
opts
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule RDF.Graph.Builder do
|
defmodule RDF.Graph.Builder do
|
||||||
alias RDF.{Description, Graph, Dataset, PrefixMap, IRI}
|
@moduledoc false
|
||||||
|
alias RDF.{Description, Graph, Dataset, PrefixMap, IRI, Vocabulary}
|
||||||
|
|
||||||
defmodule Error do
|
defmodule Error do
|
||||||
defexception [:message]
|
defexception [:message]
|
||||||
|
@ -16,14 +17,20 @@ defmodule RDF.Graph.Builder do
|
||||||
def exclude(_), do: nil
|
def exclude(_), do: nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def build({:__block__, _, block}, env, opts) do
|
def build(do_block, env, opts) do
|
||||||
|
build(do_block, env, namespace_context_mod(env), opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build({:__block__, _, block}, env, namespace_context_mod, opts) do
|
||||||
env_aliases = env_aliases(env)
|
env_aliases = env_aliases(env)
|
||||||
{declarations, data} = Enum.split_with(block, &declaration?/1)
|
{declarations, data} = Enum.split_with(block, &declaration?/1)
|
||||||
{base, declarations} = extract_base(declarations, env_aliases)
|
{base, declarations} = extract_base(declarations, env_aliases)
|
||||||
base_string = base_string(base)
|
base_string = base_string(base)
|
||||||
data = resolve_relative_iris(data, base_string)
|
data = resolve_relative_iris(data, base_string)
|
||||||
declarations = resolve_relative_iris(declarations, base_string)
|
declarations = resolve_relative_iris(declarations, base_string)
|
||||||
{prefixes, declarations} = extract_prefixes(declarations, env_aliases)
|
|
||||||
|
{prefixes, declarations} =
|
||||||
|
extract_prefixes(declarations, env_aliases, namespace_context_mod, env)
|
||||||
|
|
||||||
quote do
|
quote do
|
||||||
alias RDF.XSD
|
alias RDF.XSD
|
||||||
|
@ -43,8 +50,8 @@ defmodule RDF.Graph.Builder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build(single, env, opts) do
|
def build(single, env, namespace_context_mod, opts) do
|
||||||
build({:__block__, [], List.wrap(single)}, env, opts)
|
build({:__block__, [], List.wrap(single)}, env, namespace_context_mod, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -53,6 +60,10 @@ defmodule RDF.Graph.Builder do
|
||||||
|> Graph.add(Enum.filter(data, &rdf?/1))
|
|> Graph.add(Enum.filter(data, &rdf?/1))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def namespace_context_mod(env) do
|
||||||
|
Module.concat(env.module, "GraphBuilderNS#{random_number()}")
|
||||||
|
end
|
||||||
|
|
||||||
defp graph_opts(opts, prefixes, base) do
|
defp graph_opts(opts, prefixes, base) do
|
||||||
opts
|
opts
|
||||||
|> set_base_opt(base)
|
|> set_base_opt(base)
|
||||||
|
@ -115,16 +126,33 @@ defmodule RDF.Graph.Builder do
|
||||||
{base, Enum.reverse(declarations)}
|
{base, Enum.reverse(declarations)}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extract_prefixes(declarations, env_aliases) do
|
defp extract_prefixes(declarations, env_aliases, namespace_context_mod, env) do
|
||||||
{prefixes, declarations} =
|
{prefixes, declarations} =
|
||||||
Enum.reduce(declarations, {[], []}, fn
|
Enum.reduce(declarations, {[], []}, fn
|
||||||
|
{:@, line, [{:prefix, _, [{:__aliases__, _, ns}] = aliases}]}, {prefixes, declarations} ->
|
||||||
|
{
|
||||||
|
[prefix(ns, env_aliases) | prefixes],
|
||||||
|
[{:alias, line, aliases} | declarations]
|
||||||
|
}
|
||||||
|
|
||||||
{:@, line, [{:prefix, _, [[{prefix, {:__aliases__, _, ns} = aliases}]]}]},
|
{:@, line, [{:prefix, _, [[{prefix, {:__aliases__, _, ns} = aliases}]]}]},
|
||||||
{prefixes, declarations} ->
|
{prefixes, declarations} ->
|
||||||
{[prefix(prefix, ns, env_aliases) | prefixes],
|
{
|
||||||
[{:alias, line, [aliases]} | declarations]}
|
[prefix(prefix, ns, env_aliases) | prefixes],
|
||||||
|
[{:alias, line, [aliases]} | declarations]
|
||||||
|
}
|
||||||
|
|
||||||
{:@, line, [{:prefix, _, [{:__aliases__, _, ns}] = aliases}]}, {prefixes, declarations} ->
|
{:@, line, [{:prefix, _, [[{prefix, uri}]]}]}, {prefixes, declarations}
|
||||||
{[prefix(ns, env_aliases) | prefixes], [{:alias, line, aliases} | declarations]}
|
when is_binary(uri) ->
|
||||||
|
ns = ad_hoc_namespace(prefix, uri, namespace_context_mod, env)
|
||||||
|
|
||||||
|
{
|
||||||
|
[prefix(prefix, ns, env_aliases) | prefixes],
|
||||||
|
[{:alias, line, [{:__aliases__, line, ns}]} | declarations]
|
||||||
|
}
|
||||||
|
|
||||||
|
{:@, _, [{:prefix, _, _}]} = expr, _ ->
|
||||||
|
raise Error, "invalid @prefix expression:\n\t#{Macro.to_string(expr)}"
|
||||||
|
|
||||||
declaration, {prefixes, declarations} ->
|
declaration, {prefixes, declarations} ->
|
||||||
{prefixes, [declaration | declarations]}
|
{prefixes, [declaration | declarations]}
|
||||||
|
@ -152,6 +180,15 @@ defmodule RDF.Graph.Builder do
|
||||||
|> String.to_atom()
|
|> String.to_atom()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp ad_hoc_namespace(prefix, uri, namespace_context_mod, env) do
|
||||||
|
{:module, module, _, _} =
|
||||||
|
namespace_context_mod
|
||||||
|
|> Module.concat(prefix |> Atom.to_string() |> Macro.camelize())
|
||||||
|
|> Vocabulary.Namespace.create!(uri, [], env, strict: false)
|
||||||
|
|
||||||
|
module |> Module.split() |> Enum.map(&String.to_atom/1)
|
||||||
|
end
|
||||||
|
|
||||||
defp declaration?({:=, _, _}), do: true
|
defp declaration?({:=, _, _}), do: true
|
||||||
defp declaration?({:@, _, [{:prefix, _, _}]}), do: true
|
defp declaration?({:@, _, [{:prefix, _, _}]}), do: true
|
||||||
defp declaration?({:@, _, [{:base, _, _}]}), do: true
|
defp declaration?({:@, _, [{:base, _, _}]}), do: true
|
||||||
|
@ -195,4 +232,8 @@ defmodule RDF.Graph.Builder do
|
||||||
[short] = Module.split(module)
|
[short] = Module.split(module)
|
||||||
String.to_atom(short)
|
String.to_atom(short)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp random_number do
|
||||||
|
:erlang.unique_integer([:positive])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -427,6 +427,73 @@ defmodule RDF.Graph.BuilderTest do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ad-hoc vocabulary namespace for URIs given as string" do
|
||||||
|
# we're wrapping this in a function to isolate the alias
|
||||||
|
graph =
|
||||||
|
(fn ->
|
||||||
|
RDF.Graph.build do
|
||||||
|
@prefix ad: "http://example.com/ad-hoc/"
|
||||||
|
|
||||||
|
Ad.S |> Ad.p(Ad.O)
|
||||||
|
end
|
||||||
|
end).()
|
||||||
|
|
||||||
|
assert graph ==
|
||||||
|
RDF.graph(
|
||||||
|
{
|
||||||
|
RDF.iri("http://example.com/ad-hoc/S"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/p"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/O")
|
||||||
|
},
|
||||||
|
prefixes: RDF.default_prefixes(ad: "http://example.com/ad-hoc/")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "two ad-hoc vocabulary namespaces for the same URI in the same context" do
|
||||||
|
# we're wrapping this in a function to isolate the alias
|
||||||
|
graph =
|
||||||
|
(fn ->
|
||||||
|
graph1 =
|
||||||
|
RDF.Graph.build do
|
||||||
|
@prefix ad: "http://example.com/ad-hoc/"
|
||||||
|
@prefix ex1: "http://example.com/ad-hoc/ex1#"
|
||||||
|
|
||||||
|
Ad.S |> Ad.p(Ex1.O)
|
||||||
|
end
|
||||||
|
|
||||||
|
RDF.Graph.build do
|
||||||
|
@prefix ad: "http://example.com/ad-hoc/"
|
||||||
|
@prefix ex2: "http://example.com/ad-hoc/ex2#"
|
||||||
|
|
||||||
|
graph1
|
||||||
|
|
||||||
|
Ad.S |> Ad.p(Ex2.O)
|
||||||
|
end
|
||||||
|
end).()
|
||||||
|
|
||||||
|
assert graph ==
|
||||||
|
RDF.graph(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
RDF.iri("http://example.com/ad-hoc/S"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/p"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/ex1#O")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RDF.iri("http://example.com/ad-hoc/S"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/p"),
|
||||||
|
RDF.iri("http://example.com/ad-hoc/ex2#O")
|
||||||
|
}
|
||||||
|
],
|
||||||
|
prefixes:
|
||||||
|
RDF.default_prefixes(
|
||||||
|
ad: "http://example.com/ad-hoc/",
|
||||||
|
ex1: "http://example.com/ad-hoc/ex1#",
|
||||||
|
ex2: "http://example.com/ad-hoc/ex2#"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
test "merge with prefixes opt" do
|
test "merge with prefixes opt" do
|
||||||
# we're wrapping this in a function to isolate the alias
|
# we're wrapping this in a function to isolate the alias
|
||||||
graph =
|
graph =
|
||||||
|
|
Loading…
Reference in a new issue