Add ordering of descriptions to Turtle encoder

This commit is contained in:
Marcel Otto 2017-08-11 18:34:26 +02:00
parent eeffceb16d
commit 75b84254f4
3 changed files with 87 additions and 1 deletions

View file

@ -18,6 +18,10 @@ defmodule RDF.Turtle.Encoder do
@rdf_type RDF.type
@rdf_nil RDF.nil
# Defines rdf:type of subjects to be serialized at the beginning of the encoded graph
@top_classes [RDF.NS.RDFS.Class] |> Enum.map(&RDF.uri/1)
# Defines order of predicates at the beginning of a resource description
@predicate_order [RDF.type, RDF.NS.RDFS.label, RDF.uri("http://purl.org/dc/terms/title")]
@ordered_properties MapSet.new(@predicate_order)
@ -78,11 +82,52 @@ defmodule RDF.Turtle.Encoder do
defp graph_statements(state) do
State.data(state)
|> RDF.Data.descriptions
|> order_descriptions(state)
|> Enum.map(&description_statements(&1, state))
|> Enum.reject(&is_nil/1)
|> Enum.join("\n")
end
defp order_descriptions(descriptions, state) do
base_uri = State.base_uri(state)
group =
Enum.group_by descriptions, fn
%Description{subject: ^base_uri} ->
: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
ordered_descriptions = (
@top_classes
|> Stream.map(fn top_class -> group[top_class] end)
|> 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())
case group[:base] do
[base] -> [base | ordered_descriptions]
_ -> ordered_descriptions
end
end
defp sort_description_group(descriptions) do
Enum.sort descriptions, fn
%Description{subject: %URI{}}, %Description{subject: %BlankNode{}} -> true
%Description{subject: %BlankNode{}}, %Description{subject: %URI{}} -> false
%Description{subject: s1}, %Description{subject: s2} ->
to_string(s1) < to_string(s2)
end
end
defp description_statements(description, state, nesting \\ 0) do
with %BlankNode{} <- description.subject,
ref_count when ref_count < 2 <-

View file

@ -22,6 +22,14 @@ defmodule RDF.Turtle.Encoder.State do
bnode_ref_counter(state) |> Map.get(bnode, 0)
end
def base_uri(state) do
with {:ok, base} <- base(state) do
RDF.uri(base)
else
_ -> nil
end
end
def list_values(head, state), do: Agent.get(state, &(&1.list_values[head]))
def preprocess(state) do

View file

@ -6,7 +6,7 @@ defmodule RDF.Turtle.EncoderTest do
doctest Turtle.Encoder
alias RDF.Graph
alias RDF.NS.XSD
alias RDF.NS.{XSD, RDFS, OWL}
import RDF.Sigils
@ -101,6 +101,39 @@ defmodule RDF.Turtle.EncoderTest do
<http://example.org/#p1> _:1, _:bar, _:foo .
"""
end
test "ordering of descriptions" do
assert Turtle.Encoder.encode!(Graph.new([
{EX.__base_uri__, RDF.type, OWL.Ontology},
{EX.S1, RDF.type, EX.O},
{EX.S2, RDF.type, RDFS.Class},
{EX.S3, RDF.type, RDF.Property},
]),
base: EX.__base_uri__,
prefixes: %{
rdf: RDF.__base_uri__,
rdfs: RDFS.__base_uri__,
owl: OWL.__base_uri__,
}) ==
"""
@base <#{to_string(EX.__base_uri__)}> .
@prefix rdf: <#{to_string(RDF.__base_uri__)}> .
@prefix rdfs: <#{to_string(RDFS.__base_uri__)}> .
@prefix owl: <#{to_string(OWL.__base_uri__)}> .
<>
a owl:Ontology .
<S2>
a rdfs:Class .
<S1>
a <O> .
<S3>
a rdf:Property .
"""
end
end