Add support for Turtle-star encoding
This commit is contained in:
parent
695a54159c
commit
e9102252ae
9 changed files with 485 additions and 170 deletions
|
@ -11,6 +11,12 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
|
|||
|
||||
- support for `RDF.PropertyMap` on `RDF.Statement.new/2` and `RDF.Statement.coerce/2`
|
||||
|
||||
### Changed
|
||||
|
||||
- the `RDF.Turtle.Encoder` no longer supports the encoding of `RDF.Dataset`s; you'll have to
|
||||
aggregate a `RDF.Dataset` to a `RDF.Graph` on your own now
|
||||
|
||||
|
||||
|
||||
[Compare v0.9.4...HEAD](https://github.com/rdf-elixir/rdf-ex/compare/v0.9.4...HEAD)
|
||||
|
||||
|
|
|
@ -154,12 +154,7 @@ defmodule RDF.List do
|
|||
"""
|
||||
@spec valid?(t) :: boolean
|
||||
def valid?(%__MODULE__{head: @rdf_nil}), do: true
|
||||
|
||||
def valid?(%__MODULE__{} = list) do
|
||||
Enum.all?(list, fn node_description ->
|
||||
RDF.bnode?(node_description.subject)
|
||||
end)
|
||||
end
|
||||
def valid?(%__MODULE__{} = list), do: Enum.all?(list, &RDF.bnode?(&1.subject))
|
||||
|
||||
@doc """
|
||||
Checks if a given resource is a RDF list node in a given `RDF.Graph`.
|
||||
|
@ -172,23 +167,16 @@ defmodule RDF.List do
|
|||
"""
|
||||
@spec node?(any, Graph.t()) :: boolean
|
||||
def node?(list_node, graph)
|
||||
|
||||
def node?(@rdf_nil, _),
|
||||
do: true
|
||||
|
||||
def node?(%BlankNode{} = list_node, graph),
|
||||
do: do_node?(list_node, graph)
|
||||
|
||||
def node?(%IRI{} = list_node, graph),
|
||||
do: do_node?(list_node, graph)
|
||||
def node?(@rdf_nil, _), do: true
|
||||
def node?(%BlankNode{} = list_node, graph), do: do_node?(list_node, graph)
|
||||
def node?(%IRI{} = list_node, graph), do: do_node?(list_node, graph)
|
||||
|
||||
def node?(list_node, graph) when maybe_ns_term(list_node),
|
||||
do: do_node?(RDF.iri(list_node), graph)
|
||||
|
||||
def node?(_, _), do: false
|
||||
|
||||
defp do_node?(list_node, graph),
|
||||
do: graph |> Graph.description(list_node) |> node?
|
||||
defp do_node?(list_node, graph), do: graph |> Graph.description(list_node) |> node?()
|
||||
|
||||
@doc """
|
||||
Checks if the given `RDF.Description` describes a RDF list node.
|
||||
|
|
77
lib/rdf/serializations/turtle/star_compact_graph.ex
Normal file
77
lib/rdf/serializations/turtle/star_compact_graph.ex
Normal file
|
@ -0,0 +1,77 @@
|
|||
defmodule RDF.Turtle.Star.CompactGraph do
|
||||
@moduledoc !"""
|
||||
A compact graph representation in which annotations are directly stored under
|
||||
the objects of the annotated triples.
|
||||
|
||||
This representation is not meant for direct use, but just for the `RDF.Turtle.Encoder`.
|
||||
"""
|
||||
|
||||
alias RDF.{Graph, Description}
|
||||
|
||||
def compact(graph) do
|
||||
Enum.reduce(graph.descriptions, graph, fn
|
||||
{{_, _, _} = quoted_triple, _}, compact_graph ->
|
||||
# First check the original graph to see if the quoted triple is asserted.
|
||||
if Graph.include?(graph, quoted_triple) do
|
||||
annotation =
|
||||
compact_graph
|
||||
# We'll have to re-fetch the description, since the compact_graph might already contain
|
||||
# an updated description with an annotation.
|
||||
|> Graph.description(quoted_triple)
|
||||
|> as_annotation()
|
||||
|
||||
compact_graph
|
||||
|> add_annotation(quoted_triple, annotation)
|
||||
|> Graph.delete_descriptions(quoted_triple)
|
||||
else
|
||||
compact_graph
|
||||
end
|
||||
|
||||
_, compact_graph ->
|
||||
compact_graph
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_annotation(compact_graph, {{_, _, _} = quoted_triple, p, o} = triple, annotation) do
|
||||
# Check if the compact graph still contains the annoted triple, we want to put the annotation under.
|
||||
if Graph.describes?(compact_graph, quoted_triple) do
|
||||
do_add_annotation(compact_graph, triple, annotation)
|
||||
else
|
||||
# It's not there anymore, which means the description of the quoted triple was already moved as an annotation.
|
||||
# Next we have to search recursively for the annotation, we want to put the nested annotation under.
|
||||
path = find_annotation_path(compact_graph, quoted_triple, [p, o])
|
||||
do_add_annotation(compact_graph, path, annotation)
|
||||
end
|
||||
end
|
||||
|
||||
defp add_annotation(compact_graph, triple, annotation) do
|
||||
do_add_annotation(compact_graph, triple, annotation)
|
||||
end
|
||||
|
||||
defp do_add_annotation(compact_graph, {s, p, o}, annotation) do
|
||||
update_in(compact_graph, [s], &put_in(&1.predications[p][o], annotation))
|
||||
end
|
||||
|
||||
defp do_add_annotation(compact_graph, [s | path], annotation) do
|
||||
update_in(compact_graph, [s], &update_annotation_in(&1, path, annotation))
|
||||
end
|
||||
|
||||
defp update_annotation_in(_, [], annotation), do: annotation
|
||||
|
||||
defp update_annotation_in(description, [p, o | rest], annotation) do
|
||||
%Description{
|
||||
description
|
||||
| predications:
|
||||
update_in(description.predications, [p, o], &update_annotation_in(&1, rest, annotation))
|
||||
}
|
||||
end
|
||||
|
||||
defp find_annotation_path(compact_graph, {s, p, o}, path) do
|
||||
cond do
|
||||
Graph.describes?(compact_graph, s) -> [s, p, o | path]
|
||||
match?({_, _, _}, s) -> find_annotation_path(compact_graph, s, [p, o | path])
|
||||
end
|
||||
end
|
||||
|
||||
defp as_annotation(description), do: %{description | subject: nil}
|
||||
end
|
|
@ -26,7 +26,8 @@ defmodule RDF.Turtle.Encoder do
|
|||
use RDF.Serialization.Encoder
|
||||
|
||||
alias RDF.Turtle.Encoder.State
|
||||
alias RDF.{BlankNode, Dataset, Description, Graph, IRI, XSD, Literal, LangString, PrefixMap}
|
||||
alias RDF.Turtle.Star.CompactGraph
|
||||
alias RDF.{BlankNode, Description, Graph, IRI, XSD, Literal, LangString, PrefixMap}
|
||||
|
||||
import RDF.NTriples.Encoder, only: [escape_string: 1]
|
||||
|
||||
|
@ -60,18 +61,22 @@ defmodule RDF.Turtle.Encoder do
|
|||
@ordered_properties MapSet.new(@predicate_order)
|
||||
|
||||
@impl RDF.Serialization.Encoder
|
||||
@spec encode(RDF.Data.t(), keyword) :: {:ok, String.t()} | {:error, any}
|
||||
def encode(data, opts \\ []) do
|
||||
@spec encode(Graph.t() | Description.t(), keyword) :: {:ok, String.t()} | {:error, any}
|
||||
def encode(data, opts \\ [])
|
||||
|
||||
def encode(%Description{} = description, opts), do: description |> Graph.new() |> encode(opts)
|
||||
|
||||
def encode(%Graph{} = graph, opts) do
|
||||
base =
|
||||
Keyword.get(opts, :base, Keyword.get(opts, :base_iri))
|
||||
|> base_iri(data)
|
||||
|> base_iri(graph)
|
||||
|> init_base_iri()
|
||||
|
||||
prefixes =
|
||||
Keyword.get(opts, :prefixes)
|
||||
|> prefixes(data)
|
||||
|> prefixes(graph)
|
||||
|
||||
{:ok, state} = State.start_link(data, base, prefixes)
|
||||
{:ok, state} = State.start_link(graph, base, prefixes)
|
||||
|
||||
try do
|
||||
State.preprocess(state)
|
||||
|
@ -108,16 +113,6 @@ defmodule RDF.Turtle.Encoder do
|
|||
|
||||
defp prefixes(nil, %Graph{prefixes: prefixes}) when not is_nil(prefixes), do: prefixes
|
||||
|
||||
defp prefixes(nil, %Dataset{} = dataset) do
|
||||
prefixes = Dataset.prefixes(dataset)
|
||||
|
||||
if Enum.empty?(prefixes) do
|
||||
RDF.default_prefixes()
|
||||
else
|
||||
prefixes
|
||||
end
|
||||
end
|
||||
|
||||
defp prefixes(nil, _), do: RDF.default_prefixes()
|
||||
defp prefixes(prefixes, _), do: PrefixMap.new(prefixes)
|
||||
|
||||
|
@ -150,6 +145,7 @@ defmodule RDF.Turtle.Encoder do
|
|||
indent = indent(opts)
|
||||
|
||||
State.data(state)
|
||||
|> CompactGraph.compact()
|
||||
|> RDF.Data.descriptions()
|
||||
|> order_descriptions(state)
|
||||
|> Enum.map(&description_statements(&1, state, Keyword.get(opts, :indent, 0)))
|
||||
|
@ -159,30 +155,14 @@ defmodule RDF.Turtle.Encoder do
|
|||
|
||||
defp order_descriptions(descriptions, state) do
|
||||
base_iri = State.base_iri(state)
|
||||
|
||||
group =
|
||||
Enum.group_by(descriptions, fn
|
||||
%Description{subject: ^base_iri} ->
|
||||
:base
|
||||
|
||||
description ->
|
||||
with types when not is_nil(types) <- description.predications[@rdf_type] do
|
||||
Enum.find(@top_classes, :other, fn top_class ->
|
||||
Map.has_key?(types, top_class)
|
||||
end)
|
||||
else
|
||||
_ -> :other
|
||||
end
|
||||
end)
|
||||
group = Enum.group_by(descriptions, &description_group(&1, base_iri))
|
||||
|
||||
ordered_descriptions =
|
||||
(@top_classes
|
||||
|> Stream.map(fn top_class -> group[top_class] end)
|
||||
|> Stream.map(&group[&1])
|
||||
|> Stream.reject(&is_nil/1)
|
||||
|> Stream.map(&sort_description_group/1)
|
||||
|> Enum.reduce([], fn class_group, ordered_descriptions ->
|
||||
ordered_descriptions ++ class_group
|
||||
end)) ++ (group |> Map.get(:other, []) |> sort_description_group())
|
||||
|> Enum.flat_map(&sort_descriptions/1)) ++
|
||||
(group |> Map.get(:other, []) |> sort_descriptions())
|
||||
|
||||
case group[:base] do
|
||||
[base] -> [base | ordered_descriptions]
|
||||
|
@ -190,23 +170,37 @@ defmodule RDF.Turtle.Encoder do
|
|||
end
|
||||
end
|
||||
|
||||
defp sort_description_group(descriptions) do
|
||||
Enum.sort(descriptions, fn
|
||||
%Description{subject: %IRI{}}, %Description{subject: %BlankNode{}} ->
|
||||
true
|
||||
defp description_group(%{subject: base_iri}, base_iri), do: :base
|
||||
|
||||
%Description{subject: %BlankNode{}}, %Description{subject: %IRI{}} ->
|
||||
false
|
||||
|
||||
%Description{subject: s1}, %Description{subject: s2} ->
|
||||
to_string(s1) < to_string(s2)
|
||||
end)
|
||||
defp description_group(description, _) do
|
||||
if types = description.predications[@rdf_type] do
|
||||
Enum.find(@top_classes, :other, &Map.has_key?(types, &1))
|
||||
else
|
||||
:other
|
||||
end
|
||||
end
|
||||
|
||||
defp sort_descriptions(descriptions), do: Enum.sort(descriptions, &description_order/2)
|
||||
|
||||
defp description_order(%{subject: %IRI{}}, %{subject: %BlankNode{}}), do: true
|
||||
defp description_order(%{subject: %BlankNode{}}, %{subject: %IRI{}}), do: false
|
||||
|
||||
defp description_order(%{subject: {s, p, o1}}, %{subject: {s, p, o2}}),
|
||||
do: to_string(o1) < to_string(o2)
|
||||
|
||||
defp description_order(%{subject: {s, p1, _}}, %{subject: {s, p2, _}}),
|
||||
do: to_string(p1) < to_string(p2)
|
||||
|
||||
defp description_order(%{subject: {s1, _, _}}, %{subject: {s2, _, _}}),
|
||||
do: to_string(s1) < to_string(s2)
|
||||
|
||||
defp description_order(%{subject: {_, _, _}}, %{subject: _}), do: false
|
||||
defp description_order(%{subject: _}, %{subject: {_, _, _}}), do: true
|
||||
defp description_order(%{subject: s1}, %{subject: s2}), do: to_string(s1) < to_string(s2)
|
||||
|
||||
defp description_statements(description, state, nesting) do
|
||||
with %BlankNode{} <- description.subject,
|
||||
ref_count when ref_count < 2 <-
|
||||
State.bnode_ref_counter(state, description.subject) do
|
||||
ref_count when ref_count < 2 <- State.bnode_ref_counter(state, description.subject) do
|
||||
unrefed_bnode_subject_term(description, ref_count, state, nesting)
|
||||
else
|
||||
_ -> full_description_statements(description, state, nesting)
|
||||
|
@ -226,10 +220,14 @@ defmodule RDF.Turtle.Encoder do
|
|||
defp blank_node_property_list(description, state, nesting) do
|
||||
indented = nesting + @indentation
|
||||
|
||||
"[" <>
|
||||
newline_indent(indented) <>
|
||||
predications(description, state, indented) <>
|
||||
newline_indent(nesting) <> "]"
|
||||
if Enum.empty?(description) do
|
||||
"[]"
|
||||
else
|
||||
"[" <>
|
||||
newline_indent(indented) <>
|
||||
predications(description, state, indented) <>
|
||||
newline_indent(nesting) <> "]"
|
||||
end
|
||||
end
|
||||
|
||||
defp predications(description, state, nesting) do
|
||||
|
@ -255,12 +253,30 @@ defmodule RDF.Turtle.Encoder do
|
|||
end
|
||||
|
||||
defp predication({predicate, objects}, state, nesting) do
|
||||
term(predicate, state, :predicate, nesting) <>
|
||||
" " <>
|
||||
(objects
|
||||
|> Enum.map(fn {object, _} -> term(object, state, :object, nesting) end)
|
||||
# TODO: split if the line gets too long
|
||||
|> Enum.join(", "))
|
||||
term(predicate, state, :predicate, nesting) <> " " <> objects(objects, state, nesting)
|
||||
end
|
||||
|
||||
defp objects(objects, state, nesting) do
|
||||
{objects, with_annotations} =
|
||||
Enum.map_reduce(objects, false, fn {object, annotation}, with_annotations ->
|
||||
if annotation do
|
||||
{
|
||||
term(object, state, :object, nesting) <>
|
||||
" {| #{predications(annotation, state, nesting + 2 * @indentation)} |}",
|
||||
true
|
||||
}
|
||||
else
|
||||
{term(object, state, :object, nesting), with_annotations}
|
||||
end
|
||||
end)
|
||||
|
||||
# TODO: split if the line gets too long
|
||||
separator =
|
||||
if with_annotations,
|
||||
do: "," <> newline_indent(nesting + @indentation),
|
||||
else: ", "
|
||||
|
||||
Enum.join(objects, separator)
|
||||
end
|
||||
|
||||
defp unrefed_bnode_subject_term(bnode_description, ref_count, state, nesting) do
|
||||
|
@ -372,6 +388,10 @@ defmodule RDF.Turtle.Encoder do
|
|||
defp term(%Literal{} = literal, state, _, nesting),
|
||||
do: typed_literal_term(literal, state, nesting)
|
||||
|
||||
defp term({s, p, o}, state, _, nesting) do
|
||||
"<< #{term(s, state, :subject, nesting)} #{term(p, state, :predicate, nesting)} #{term(o, state, :object, nesting)} >>"
|
||||
end
|
||||
|
||||
defp term(list, state, _, nesting) when is_list(list) do
|
||||
"(" <>
|
||||
(list
|
||||
|
|
|
@ -22,9 +22,8 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
end
|
||||
|
||||
def base_iri(state) do
|
||||
with {:ok, base} <- base(state) do
|
||||
RDF.iri(base)
|
||||
else
|
||||
case base(state) do
|
||||
{:ok, base} -> RDF.iri(base)
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
@ -32,13 +31,13 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
def list_values(head, state), do: Agent.get(state, & &1.list_values[head])
|
||||
|
||||
def preprocess(state) do
|
||||
with data = data(state),
|
||||
{bnode_ref_counter, list_parents} = bnode_info(data),
|
||||
{list_nodes, list_values} = valid_lists(list_parents, bnode_ref_counter, data) do
|
||||
Agent.update(state, &Map.put(&1, :bnode_ref_counter, bnode_ref_counter))
|
||||
Agent.update(state, &Map.put(&1, :list_nodes, list_nodes))
|
||||
Agent.update(state, &Map.put(&1, :list_values, list_values))
|
||||
end
|
||||
data = data(state)
|
||||
{bnode_ref_counter, list_parents} = bnode_info(data)
|
||||
{list_nodes, list_values} = valid_lists(list_parents, bnode_ref_counter, data)
|
||||
|
||||
Agent.update(state, &Map.put(&1, :bnode_ref_counter, bnode_ref_counter))
|
||||
Agent.update(state, &Map.put(&1, :list_nodes, list_nodes))
|
||||
Agent.update(state, &Map.put(&1, :list_values, list_values))
|
||||
end
|
||||
|
||||
defp bnode_info(data) do
|
||||
|
@ -47,15 +46,23 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
|> Enum.reduce(
|
||||
{%{}, %{}},
|
||||
fn %Description{subject: subject} = description, {bnode_ref_counter, list_parents} ->
|
||||
# We don't count blank node subjects, because when a blank node only occurs as a subject in
|
||||
# multiple triples, we still can and want to use the square bracket syntax for its encoding.
|
||||
|
||||
list_parents =
|
||||
if match?(%BlankNode{}, subject) and
|
||||
to_list?(description, Map.get(bnode_ref_counter, subject, 0)),
|
||||
do: Map.put_new(list_parents, subject, nil),
|
||||
else: list_parents
|
||||
|
||||
bnode_ref_counter = handle_quoted_triples(subject, bnode_ref_counter)
|
||||
|
||||
Enum.reduce(description.predications, {bnode_ref_counter, list_parents}, fn
|
||||
{predicate, objects}, {bnode_ref_counter, list_parents} ->
|
||||
Enum.reduce(Map.keys(objects), {bnode_ref_counter, list_parents}, fn
|
||||
{_, _, _} = quoted_triple, {bnode_ref_counter, list_parents} ->
|
||||
{handle_quoted_triples(quoted_triple, bnode_ref_counter), list_parents}
|
||||
|
||||
%BlankNode{} = object, {bnode_ref_counter, list_parents} ->
|
||||
{
|
||||
# Note: The following conditional produces imprecise results
|
||||
|
@ -83,6 +90,21 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
)
|
||||
end
|
||||
|
||||
defp handle_quoted_triples({s, _, o}, bnode_ref_counter) do
|
||||
bnode_ref_counter =
|
||||
case s do
|
||||
%BlankNode{} -> Map.update(bnode_ref_counter, s, 1, &(&1 + 1))
|
||||
_ -> bnode_ref_counter
|
||||
end
|
||||
|
||||
case o do
|
||||
%BlankNode{} -> Map.update(bnode_ref_counter, o, 1, &(&1 + 1))
|
||||
_ -> bnode_ref_counter
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_quoted_triples(_, bnode_ref_counter), do: bnode_ref_counter
|
||||
|
||||
@list_properties MapSet.new([
|
||||
RDF.Utils.Bootstrapping.rdf_iri("first"),
|
||||
RDF.Utils.Bootstrapping.rdf_iri("rest")
|
||||
|
@ -94,21 +116,17 @@ defmodule RDF.Turtle.Encoder.State do
|
|||
Description.predicates(description) |> MapSet.equal?(@list_properties)
|
||||
end
|
||||
|
||||
defp to_list?(%Description{} = description, 0),
|
||||
do: RDF.list?(description)
|
||||
|
||||
defp to_list?(_, _),
|
||||
do: false
|
||||
defp to_list?(%Description{} = description, 0), do: RDF.list?(description)
|
||||
defp to_list?(_, _), do: false
|
||||
|
||||
defp valid_lists(list_parents, bnode_ref_counter, data) do
|
||||
head_nodes = for {list_node, nil} <- list_parents, do: list_node
|
||||
|
||||
all_list_nodes =
|
||||
MapSet.new(
|
||||
for {list_node, _} <- list_parents, Map.get(bnode_ref_counter, list_node, 0) < 2 do
|
||||
list_node
|
||||
end
|
||||
)
|
||||
for {list_node, _} <- list_parents, Map.get(bnode_ref_counter, list_node, 0) < 2 do
|
||||
list_node
|
||||
end
|
||||
|> MapSet.new()
|
||||
|
||||
Enum.reduce(head_nodes, {MapSet.new(), %{}}, fn head_node, {valid_list_nodes, list_values} ->
|
||||
with list when not is_nil(list) <-
|
||||
|
|
|
@ -12,7 +12,7 @@ defmodule RDF.Test.Case do
|
|||
|
||||
using do
|
||||
quote do
|
||||
alias RDF.{Dataset, Graph, Description, IRI, XSD, PrefixMap, PropertyMap}
|
||||
alias RDF.{Dataset, Graph, Description, IRI, XSD, PrefixMap, PropertyMap, NS}
|
||||
alias RDF.NS.{RDFS, OWL}
|
||||
alias unquote(__MODULE__).{EX, FOAF}
|
||||
|
||||
|
|
|
@ -34,6 +34,11 @@ defmodule RDF.InspectTest do
|
|||
|> String.trim()) <> "\n>"
|
||||
end
|
||||
|
||||
test "it encodes the RDF-star graphs ands descriptions in Turtle-star" do
|
||||
{_, triples} = inspect_parts(annotation(), limit: 2)
|
||||
assert triples =~ "<< <http://example.com/S> <http://example.com/P> \"Foo\" >>"
|
||||
end
|
||||
|
||||
test ":limit option" do
|
||||
{_, triples} = inspect_parts(@test_description, limit: 2)
|
||||
|
||||
|
|
|
@ -344,80 +344,6 @@ defmodule RDF.Turtle.EncoderTest do
|
|||
description |> Graph.new() |> Turtle.Encoder.encode!()
|
||||
end
|
||||
|
||||
describe "serializing a dataset" do
|
||||
test "prefixes of the graphs are merged properly" do
|
||||
dataset =
|
||||
RDF.Dataset.new()
|
||||
|> RDF.Dataset.add(
|
||||
Graph.new(
|
||||
[
|
||||
{EX.__base_iri__(), RDF.type(), OWL.Ontology},
|
||||
{EX.S1, RDF.type(), EX.O}
|
||||
],
|
||||
base_iri: EX.__base_iri__(),
|
||||
prefixes: %{
|
||||
rdf: RDF,
|
||||
owl: OWL
|
||||
}
|
||||
)
|
||||
)
|
||||
|> RDF.Dataset.add(
|
||||
Graph.new(
|
||||
{EX.S3, EX.p(), EX.O},
|
||||
name: EX.Graph1,
|
||||
prefixes: %{
|
||||
ex: EX,
|
||||
rdf: RDF
|
||||
}
|
||||
)
|
||||
)
|
||||
|> RDF.Dataset.add(
|
||||
Graph.new(
|
||||
{~I<http://other.example.com/S2>, RDF.type(), RDFS.Class},
|
||||
name: EX.Graph2,
|
||||
prefixes: %{
|
||||
ex: "http://other.example.com/",
|
||||
rdf: RDF,
|
||||
rdfs: RDFS
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
assert Turtle.Encoder.encode!(dataset) ==
|
||||
"""
|
||||
@prefix ex: <http://example.org/#> .
|
||||
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
|
||||
<http://other.example.com/S2>
|
||||
a rdfs:Class .
|
||||
|
||||
ex:
|
||||
a owl:Ontology .
|
||||
|
||||
ex:S1
|
||||
a ex:O .
|
||||
|
||||
ex:S3
|
||||
ex:p ex:O .
|
||||
"""
|
||||
end
|
||||
|
||||
test "when none of the graphs uses prefixes the default prefixes are used" do
|
||||
assert RDF.Dataset.new({EX.S, EX.p(), EX.O})
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
||||
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
|
||||
<http://example.org/#S>
|
||||
<http://example.org/#p> <http://example.org/#O> .
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
describe "prefixed_name/2" do
|
||||
setup do
|
||||
{:ok,
|
||||
|
|
275
test/unit/turtle_star_encoder_test.exs
Normal file
275
test/unit/turtle_star_encoder_test.exs
Normal file
|
@ -0,0 +1,275 @@
|
|||
defmodule RDF.Star.Turtle.EncoderTest do
|
||||
use RDF.Test.Case
|
||||
|
||||
alias RDF.Turtle
|
||||
|
||||
test "quoted triple on subject position" do
|
||||
assert RDF.graph({{EX.s(), EX.p(), EX.o()}, EX.q(), EX.z()}, prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
<< :s :p :o >>
|
||||
:q :z .
|
||||
"""
|
||||
|
||||
assert RDF.graph({{EX.s(), EX.p(), "foo"}, EX.q(), "foo"}, prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
<< :s :p "foo" >>
|
||||
:q "foo" .
|
||||
"""
|
||||
end
|
||||
|
||||
test "blank nodes in quoted triples" do
|
||||
assert RDF.graph({{EX.s(), EX.p(), ~B"foo"}, EX.q(), ~B"foo"}, prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
<< :s :p _:foo >>
|
||||
:q _:foo .
|
||||
"""
|
||||
|
||||
# TODO: _:foo could be encoded as []
|
||||
assert RDF.graph({{~B"foo", EX.p(), ~B"bar"}, EX.q(), ~B"baz"}, prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
<< _:foo :p [] >>
|
||||
:q [] .
|
||||
"""
|
||||
end
|
||||
|
||||
test "quoted triple on object position" do
|
||||
assert RDF.graph({EX.a(), EX.q(), {EX.s(), EX.p(), EX.o()}}, prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:a
|
||||
:q << :s :p :o >> .
|
||||
"""
|
||||
end
|
||||
|
||||
test "single annotation" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.q(), EX.z()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p :o {| :q :z |} .
|
||||
"""
|
||||
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), "foo"},
|
||||
{{EX.s(), EX.p(), "foo"}, EX.q(), "foo"}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p "foo" {| :q "foo" |} .
|
||||
"""
|
||||
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o1()},
|
||||
{EX.s(), EX.p(), EX.o2()},
|
||||
{{EX.s(), EX.p(), EX.o2()}, EX.a(), EX.b()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p :o1,
|
||||
:o2 {| :a :b |} .
|
||||
"""
|
||||
end
|
||||
|
||||
test "multiple annotations" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{EX.s(), EX.p2(), EX.o2()},
|
||||
{EX.s(), EX.p2(), EX.o3()},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.a(), EX.b()},
|
||||
{{EX.s(), EX.p2(), EX.o2()}, EX.a2(), EX.b2()},
|
||||
{{EX.s(), EX.p2(), EX.o3()}, EX.a3(), EX.b3()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p :o {| :a :b |} ;
|
||||
:p2 :o2 {| :a2 :b2 |},
|
||||
:o3 {| :a3 :b3 |} .
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotations with blank nodes" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{~B"foo", EX.graph(), ~I<http://host1/>},
|
||||
{~B"foo", EX.date(), XSD.date("2020-01-20")},
|
||||
{~B"bar", EX.graph(), ~I<http://host2/>},
|
||||
{~B"bar", EX.date(), XSD.date("2020-12-31")},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.source(), ~B"foo"},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.source(), ~B"bar"}
|
||||
],
|
||||
prefixes: [nil: EX, xsd: NS.XSD]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
@prefix xsd: <#{NS.XSD.__base_iri__()}> .
|
||||
|
||||
:s
|
||||
:p :o {| :source [
|
||||
:date "2020-12-31"^^xsd:date ;
|
||||
:graph <http://host2/>
|
||||
], [
|
||||
:date "2020-01-20"^^xsd:date ;
|
||||
:graph <http://host1/>
|
||||
] |} .
|
||||
"""
|
||||
end
|
||||
|
||||
test "nested annotations" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.a(), EX.b()},
|
||||
{{{EX.s(), EX.p(), EX.o()}, EX.a(), EX.b()}, EX.a2(), EX.b2()},
|
||||
{{{{EX.s(), EX.p(), EX.o()}, EX.a(), EX.b()}, EX.a2(), EX.b2()}, EX.a3(), EX.b3()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p :o {| :a :b {| :a2 :b2 {| :a3 :b3 |} |} |} .
|
||||
"""
|
||||
|
||||
# test for a nested annotation where an inner annotation is moved inside the CompactGraph before the outer
|
||||
# Since every map with less than Erlang's configured MAP_SMALL_MAP_LIMIT number of elements behaves
|
||||
# ordered in Erlang, this case won't happen for smaller graphs, so, we're creating a graph with a
|
||||
# sufficiently large number of triples that this case will happen very likely.
|
||||
series = 1..99
|
||||
|
||||
assert """
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
""" <> predications =
|
||||
Enum.flat_map(series, fn i ->
|
||||
[
|
||||
{EX.s(), apply(EX, String.to_atom("p#{i}"), []), i},
|
||||
{{EX.s(), apply(EX, String.to_atom("p#{i}"), []), i}, EX.a(), EX.b()},
|
||||
{{{EX.s(), apply(EX, String.to_atom("p#{i}"), []), i}, EX.a(), EX.b()}, EX.a2(),
|
||||
EX.b2()},
|
||||
{
|
||||
{
|
||||
{{EX.s(), apply(EX, String.to_atom("p#{i}"), []), i}, EX.a(), EX.b()},
|
||||
EX.a2(),
|
||||
EX.b2()
|
||||
},
|
||||
EX.a34(),
|
||||
EX.b3()
|
||||
},
|
||||
{
|
||||
{
|
||||
{
|
||||
{{EX.s(), apply(EX, String.to_atom("p#{i}"), []), i}, EX.a(), EX.b()},
|
||||
EX.a2(),
|
||||
EX.b2()
|
||||
},
|
||||
EX.a34(),
|
||||
EX.b3()
|
||||
},
|
||||
EX.a34(),
|
||||
EX.b4()
|
||||
}
|
||||
]
|
||||
end)
|
||||
|> RDF.graph(prefixes: [nil: EX])
|
||||
|> Turtle.Encoder.encode!()
|
||||
|
||||
Enum.each(series, fn i ->
|
||||
assert predications =~
|
||||
" :p#{i} #{i} {| :a :b {| :a2 :b2 {| :a34 :b3 {| :a34 :b4 |} |} |} |}"
|
||||
end)
|
||||
end
|
||||
|
||||
test "quoted triple in annotation" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), EX.o()},
|
||||
{{EX.s(), EX.p(), EX.o()}, EX.r(), {EX.s1(), EX.p1(), EX.o1()}}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p :o {| :r << :s1 :p1 :o1 >> |} .
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotation of a statement with a quoted triple" do
|
||||
assert RDF.graph(
|
||||
[
|
||||
{{EX.s1(), EX.p1(), EX.o1()}, EX.p(), EX.o()},
|
||||
{{{EX.s1(), EX.p1(), EX.o1()}, EX.p(), EX.o()}, EX.r(), EX.z()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
<< :s1 :p1 :o1 >>
|
||||
:p :o {| :r :z |} .
|
||||
"""
|
||||
|
||||
assert RDF.graph(
|
||||
[
|
||||
{EX.s(), EX.p(), {EX.s2(), EX.p2(), EX.o2()}},
|
||||
{{EX.s(), EX.p(), {EX.s2(), EX.p2(), EX.o2()}}, EX.r(), EX.z()}
|
||||
],
|
||||
prefixes: [nil: EX]
|
||||
)
|
||||
|> Turtle.Encoder.encode!() ==
|
||||
"""
|
||||
@prefix : <http://example.com/> .
|
||||
|
||||
:s
|
||||
:p << :s2 :p2 :o2 >> {| :r :z |} .
|
||||
"""
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue