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
|
||||
inside of pattern matches
|
||||
- `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
|
||||
|
||||
|
|
|
@ -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).
|
||||
"""
|
||||
defmacro build(opts \\ [], do: block) do
|
||||
Builder.build(block, __CALLER__, opts)
|
||||
Builder.build(
|
||||
block,
|
||||
__CALLER__,
|
||||
Builder.namespace_context_mod(__CALLER__),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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
|
||||
defexception [:message]
|
||||
|
@ -16,14 +17,20 @@ defmodule RDF.Graph.Builder do
|
|||
def exclude(_), do: nil
|
||||
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)
|
||||
{declarations, data} = Enum.split_with(block, &declaration?/1)
|
||||
{base, declarations} = extract_base(declarations, env_aliases)
|
||||
base_string = base_string(base)
|
||||
data = resolve_relative_iris(data, 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
|
||||
alias RDF.XSD
|
||||
|
@ -43,8 +50,8 @@ defmodule RDF.Graph.Builder do
|
|||
end
|
||||
end
|
||||
|
||||
def build(single, env, opts) do
|
||||
build({:__block__, [], List.wrap(single)}, env, opts)
|
||||
def build(single, env, namespace_context_mod, opts) do
|
||||
build({:__block__, [], List.wrap(single)}, env, namespace_context_mod, opts)
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -53,6 +60,10 @@ defmodule RDF.Graph.Builder do
|
|||
|> Graph.add(Enum.filter(data, &rdf?/1))
|
||||
end
|
||||
|
||||
def namespace_context_mod(env) do
|
||||
Module.concat(env.module, "GraphBuilderNS#{random_number()}")
|
||||
end
|
||||
|
||||
defp graph_opts(opts, prefixes, base) do
|
||||
opts
|
||||
|> set_base_opt(base)
|
||||
|
@ -115,16 +126,33 @@ defmodule RDF.Graph.Builder do
|
|||
{base, Enum.reverse(declarations)}
|
||||
end
|
||||
|
||||
defp extract_prefixes(declarations, env_aliases) do
|
||||
defp extract_prefixes(declarations, env_aliases, namespace_context_mod, env) do
|
||||
{prefixes, declarations} =
|
||||
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}]]}]},
|
||||
{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} ->
|
||||
{[prefix(ns, env_aliases) | prefixes], [{:alias, line, aliases} | declarations]}
|
||||
{:@, line, [{:prefix, _, [[{prefix, uri}]]}]}, {prefixes, 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} ->
|
||||
{prefixes, [declaration | declarations]}
|
||||
|
@ -152,6 +180,15 @@ defmodule RDF.Graph.Builder do
|
|||
|> String.to_atom()
|
||||
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?({:@, _, [{:prefix, _, _}]}), do: true
|
||||
defp declaration?({:@, _, [{:base, _, _}]}), do: true
|
||||
|
@ -195,4 +232,8 @@ defmodule RDF.Graph.Builder do
|
|||
[short] = Module.split(module)
|
||||
String.to_atom(short)
|
||||
end
|
||||
|
||||
defp random_number do
|
||||
:erlang.unique_integer([:positive])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -427,6 +427,73 @@ defmodule RDF.Graph.BuilderTest do
|
|||
)
|
||||
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
|
||||
# we're wrapping this in a function to isolate the alias
|
||||
graph =
|
||||
|
|
Loading…
Reference in a new issue