2017-07-05 21:27:55 +00:00
|
|
|
defmodule RDF.Turtle.Decoder do
|
2020-11-12 15:34:21 +00:00
|
|
|
@moduledoc """
|
|
|
|
A decoder for N-Triples serializations to `RDF.Graph`s.
|
|
|
|
|
|
|
|
As for all decoders of `RDF.Serialization.Format`s, you normally won't use these
|
|
|
|
functions directly, but via one of the `read_` functions on the `RDF.Turtle` format
|
|
|
|
module or the generic `RDF.Serialization` module.
|
|
|
|
|
|
|
|
|
|
|
|
## Options
|
|
|
|
|
|
|
|
- `:base`: allows to specify the base URI to be used against relative URIs
|
|
|
|
when no base URI is defined with a `@base` directive within the document
|
|
|
|
|
|
|
|
"""
|
2017-07-05 21:27:55 +00:00
|
|
|
|
|
|
|
use RDF.Serialization.Decoder
|
|
|
|
|
2019-07-06 00:32:31 +00:00
|
|
|
import RDF.Serialization.ParseHelper, only: [error_description: 1]
|
|
|
|
|
2020-11-03 12:01:47 +00:00
|
|
|
alias RDF.{Graph, IRI}
|
2020-03-10 00:37:53 +00:00
|
|
|
|
2017-07-05 21:27:55 +00:00
|
|
|
defmodule State do
|
2017-08-20 20:35:14 +00:00
|
|
|
defstruct base_iri: nil, namespaces: %{}, bnode_counter: 0
|
2017-07-05 21:27:55 +00:00
|
|
|
|
|
|
|
def add_namespace(%State{namespaces: namespaces} = state, ns, iri) do
|
|
|
|
%State{state | namespaces: Map.put(namespaces, ns, iri)}
|
|
|
|
end
|
|
|
|
|
|
|
|
def ns(%State{namespaces: namespaces}, prefix) do
|
|
|
|
namespaces[prefix]
|
|
|
|
end
|
|
|
|
|
|
|
|
def next_bnode(%State{bnode_counter: bnode_counter} = state) do
|
2020-06-29 08:37:42 +00:00
|
|
|
{RDF.bnode("b#{bnode_counter}"), %State{state | bnode_counter: bnode_counter + 1}}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-09-17 00:08:16 +00:00
|
|
|
@impl RDF.Serialization.Decoder
|
2020-11-03 12:01:47 +00:00
|
|
|
@spec decode(String.t(), keyword) :: {:ok, Graph.t()} | {:error, any}
|
|
|
|
def decode(content, opts \\ []) do
|
2021-05-19 20:59:13 +00:00
|
|
|
base_iri =
|
|
|
|
Keyword.get_lazy(
|
|
|
|
opts,
|
|
|
|
:base_iri,
|
|
|
|
fn -> Keyword.get_lazy(opts, :base, fn -> RDF.default_base_iri() end) end
|
|
|
|
)
|
|
|
|
|
2017-07-05 21:27:55 +00:00
|
|
|
with {:ok, tokens, _} <- tokenize(content),
|
2021-05-19 20:59:13 +00:00
|
|
|
{:ok, ast} <- parse(tokens) do
|
2019-08-03 22:13:13 +00:00
|
|
|
build_graph(ast, base_iri && RDF.iri(base_iri))
|
2017-07-05 21:27:55 +00:00
|
|
|
else
|
|
|
|
{:error, {error_line, :turtle_lexer, error_descriptor}, _error_line_again} ->
|
2020-06-29 08:37:42 +00:00
|
|
|
{:error,
|
|
|
|
"Turtle scanner error on line #{error_line}: #{error_description(error_descriptor)}"}
|
|
|
|
|
2017-07-05 21:27:55 +00:00
|
|
|
{:error, {error_line, :turtle_parser, error_descriptor}} ->
|
2020-06-29 08:37:42 +00:00
|
|
|
{:error,
|
|
|
|
"Turtle parser error on line #{error_line}: #{error_description(error_descriptor)}"}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-06-29 08:37:42 +00:00
|
|
|
def tokenize(content), do: content |> to_charlist |> :turtle_lexer.string()
|
2017-07-05 21:27:55 +00:00
|
|
|
|
2020-06-29 08:37:42 +00:00
|
|
|
def parse([]), do: {:ok, []}
|
|
|
|
def parse(tokens), do: tokens |> :turtle_parser.parse()
|
2017-07-05 21:27:55 +00:00
|
|
|
|
2019-08-03 22:13:13 +00:00
|
|
|
defp build_graph(ast, base_iri) do
|
2019-08-02 22:41:06 +00:00
|
|
|
{graph, %State{namespaces: namespaces, base_iri: base_iri}} =
|
2020-06-29 08:37:42 +00:00
|
|
|
Enum.reduce(ast, {RDF.Graph.new(), %State{base_iri: base_iri}}, fn
|
2018-05-15 23:59:46 +00:00
|
|
|
{:triples, triples_ast}, {graph, state} ->
|
2021-10-01 21:12:52 +00:00
|
|
|
{statements, state} = triples(triples_ast, state)
|
|
|
|
{RDF.Graph.add(graph, statements), state}
|
2017-07-05 21:27:55 +00:00
|
|
|
|
2018-05-15 23:59:46 +00:00
|
|
|
{:directive, directive_ast}, {graph, state} ->
|
|
|
|
{graph, directive(directive_ast, state)}
|
2020-06-29 08:37:42 +00:00
|
|
|
end)
|
2017-07-05 21:27:55 +00:00
|
|
|
|
2019-03-31 03:07:34 +00:00
|
|
|
{:ok,
|
2020-06-29 08:37:42 +00:00
|
|
|
if Enum.empty?(namespaces) do
|
|
|
|
graph
|
|
|
|
else
|
|
|
|
RDF.Graph.add_prefixes(graph, namespaces)
|
|
|
|
end
|
|
|
|
|> RDF.Graph.set_base_iri(base_iri)}
|
2018-05-15 23:59:46 +00:00
|
|
|
rescue
|
|
|
|
error -> {:error, Exception.message(error)}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp directive({:prefix, {:prefix_ns, _, ns}, iri}, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
absolute_iri =
|
|
|
|
if IRI.absolute?(iri) do
|
|
|
|
iri
|
|
|
|
else
|
|
|
|
iri |> IRI.absolute(state.base_iri) |> to_string()
|
2017-07-09 23:41:36 +00:00
|
|
|
end
|
2021-10-01 21:12:52 +00:00
|
|
|
|
|
|
|
State.add_namespace(state, ns, absolute_iri)
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
2017-08-20 20:35:14 +00:00
|
|
|
defp directive({:base, iri}, %State{base_iri: base_iri} = state) do
|
2017-07-09 23:41:36 +00:00
|
|
|
cond do
|
2021-10-01 21:12:52 +00:00
|
|
|
IRI.absolute?(iri) -> %State{state | base_iri: RDF.iri(iri)}
|
|
|
|
not is_nil(base_iri) -> %State{state | base_iri: IRI.absolute(iri, base_iri)}
|
|
|
|
true -> raise "Could not resolve relative IRI '#{iri}', no base iri provided"
|
2017-07-09 23:41:36 +00:00
|
|
|
end
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp triples({:blankNodePropertyList, _} = ast, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{_, statements, state} = resolve_node(ast, [], state)
|
|
|
|
{statements, state}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp triples({subject, predications}, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{subject, statements, state} = resolve_node(subject, [], state)
|
|
|
|
|
|
|
|
predications(subject, predications, statements, state)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp predications(subject, predications, statements, state) do
|
|
|
|
Enum.reduce(predications, {statements, state}, fn
|
|
|
|
{predicate, objects}, {statements, state} ->
|
|
|
|
{predicate, statements, state} = resolve_node(predicate, statements, state)
|
|
|
|
|
|
|
|
Enum.reduce(objects, {statements, state}, fn
|
|
|
|
{:annotation, annotation}, {[last_statement | _] = statements, state} ->
|
|
|
|
predications(last_statement, annotation, statements, state)
|
|
|
|
|
|
|
|
object, {statements, state} ->
|
|
|
|
{object, statements, state} = resolve_node(object, statements, state)
|
|
|
|
{[{subject, predicate, object} | statements], state}
|
|
|
|
end)
|
|
|
|
end)
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
2017-07-09 17:27:21 +00:00
|
|
|
defp resolve_node({:prefix_ln, line_number, {prefix, name}}, statements, state) do
|
|
|
|
if ns = State.ns(state, prefix) do
|
2017-08-20 20:35:14 +00:00
|
|
|
{RDF.iri(ns <> local_name_unescape(name)), statements, state}
|
2017-07-09 17:27:21 +00:00
|
|
|
else
|
2020-06-29 08:37:42 +00:00
|
|
|
raise "line #{line_number}: undefined prefix #{inspect(prefix)}"
|
2017-07-09 17:27:21 +00:00
|
|
|
end
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
2017-07-09 17:27:21 +00:00
|
|
|
defp resolve_node({:prefix_ns, line_number, prefix}, statements, state) do
|
|
|
|
if ns = State.ns(state, prefix) do
|
2017-08-20 20:35:14 +00:00
|
|
|
{RDF.iri(ns), statements, state}
|
2017-07-09 17:27:21 +00:00
|
|
|
else
|
2020-06-29 08:37:42 +00:00
|
|
|
raise "line #{line_number}: undefined prefix #{inspect(prefix)}"
|
2017-07-09 17:27:21 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-20 20:35:14 +00:00
|
|
|
defp resolve_node({:relative_iri, relative_iri}, _, %State{base_iri: nil}) do
|
2018-07-17 22:08:37 +00:00
|
|
|
raise "Could not resolve relative IRI '#{relative_iri}', no base iri provided"
|
2017-07-07 16:00:09 +00:00
|
|
|
end
|
|
|
|
|
2017-08-20 20:35:14 +00:00
|
|
|
defp resolve_node({:relative_iri, relative_iri}, statements, state) do
|
|
|
|
{IRI.absolute(relative_iri, state.base_iri), statements, state}
|
2017-07-07 16:00:09 +00:00
|
|
|
end
|
|
|
|
|
2017-07-05 21:27:55 +00:00
|
|
|
defp resolve_node({:anon}, statements, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{node, state} = State.next_bnode(state)
|
|
|
|
{node, statements, state}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp resolve_node({:blankNodePropertyList, property_list}, statements, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{subject, state} = State.next_bnode(state)
|
|
|
|
{new_statements, state} = triples({subject, property_list}, state)
|
|
|
|
{subject, statements ++ new_statements, state}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
2020-06-29 08:37:42 +00:00
|
|
|
defp resolve_node(
|
|
|
|
{{:string_literal_quote, _line, value}, {:datatype, datatype}},
|
|
|
|
statements,
|
|
|
|
state
|
|
|
|
) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{datatype, statements, state} = resolve_node(datatype, statements, state)
|
|
|
|
{RDF.literal(value, datatype: datatype), statements, state}
|
2017-07-09 17:27:21 +00:00
|
|
|
end
|
|
|
|
|
2017-07-05 21:27:55 +00:00
|
|
|
defp resolve_node({:collection, []}, statements, state) do
|
2020-06-29 08:37:42 +00:00
|
|
|
{RDF.nil(), statements, state}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp resolve_node({:collection, elements}, statements, state) do
|
2021-10-01 21:12:52 +00:00
|
|
|
{first_list_node, state} = State.next_bnode(state)
|
|
|
|
[first_element | rest_elements] = elements
|
|
|
|
{first_element_node, statements, state} = resolve_node(first_element, statements, state)
|
|
|
|
first_statement = [{first_list_node, RDF.first(), first_element_node}]
|
|
|
|
|
|
|
|
{last_list_node, statements, state} =
|
|
|
|
Enum.reduce(
|
|
|
|
rest_elements,
|
|
|
|
{first_list_node, statements ++ first_statement, state},
|
|
|
|
fn element, {list_node, statements, state} ->
|
|
|
|
{element_node, statements, state} = resolve_node(element, statements, state)
|
|
|
|
{next_list_node, state} = State.next_bnode(state)
|
|
|
|
|
|
|
|
{next_list_node,
|
|
|
|
statements ++
|
|
|
|
[
|
|
|
|
{list_node, RDF.rest(), next_list_node},
|
|
|
|
{next_list_node, RDF.first(), element_node}
|
|
|
|
], state}
|
|
|
|
end
|
|
|
|
)
|
|
|
|
|
|
|
|
{first_list_node, statements ++ [{last_list_node, RDF.rest(), RDF.nil()}], state}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp resolve_node({:quoted_triple, s_node, p_node, o_node}, statements, state) do
|
|
|
|
{subject, statements, state} = resolve_node(s_node, statements, state)
|
|
|
|
{predicate, statements, state} = resolve_node(p_node, statements, state)
|
|
|
|
{object, statements, state} = resolve_node(o_node, statements, state)
|
|
|
|
{{subject, predicate, object}, statements, state}
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp resolve_node(node, statements, state), do: {node, statements, state}
|
|
|
|
|
2017-07-09 20:21:07 +00:00
|
|
|
defp local_name_unescape(string),
|
|
|
|
do: Macro.unescape_string(string, &local_name_unescape_map(&1))
|
|
|
|
|
|
|
|
@reserved_characters ~c[~.-!$&'()*+,;=/?#@%_]
|
|
|
|
|
|
|
|
defp local_name_unescape_map(e) when e in @reserved_characters, do: e
|
|
|
|
defp local_name_unescape_map(_), do: false
|
2017-07-05 21:27:55 +00:00
|
|
|
end
|