Add :default_graph_name option to RDF.NQuads.Encoder

and use the graph name of an RDF.Graph as its default
This commit is contained in:
Marcel Otto 2021-11-11 00:24:28 +01:00
parent c753a7a30e
commit 7859c4e46d
3 changed files with 138 additions and 45 deletions

View file

@ -9,13 +9,21 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
### Added
- support for `RDF.PropertyMap` on `RDF.Statement.new/2` and `RDF.Statement.coerce/2`
- Support for `RDF.PropertyMap` on `RDF.Statement.new/2` and `RDF.Statement.coerce/2`.
- `RDF.Dataset.graph_count/1`
- The `RDF.NQuads.Encoder` now supports a `:default_graph_name` option, which
allow to specify the graph name to be used as the default for triples
from a `RDF.Graph` or `RDF.Description`.
### 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
- 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.
- The `RDF.NQuads.Encoder` now uses the `RDF.Graph.name/1` as the graph name for
the triples of a `RDF.Graph`.
Previously the triples of an `RDF.Graph` were always encoded as part of default
graph. You can use the new `:default_graph_name` option and set it to `nil` to get
the old behaviour.

View file

@ -5,57 +5,70 @@ defmodule RDF.NQuads.Encoder do
As for all encoders of `RDF.Serialization.Format`s, you normally won't use these
functions directly, but via one of the `write_` functions on the `RDF.NQuads`
format module or the generic `RDF.Serialization` module.
## Options
- `:default_graph_name`: The graph name to be used as the default for triples
from a `RDF.Graph` or `RDF.Description`. When the input to be encoded is a
`RDF.Description` the default is `nil` for the default graph. In case of a
`RDF.Graph` the default is the `RDF.Graph.name/1`. The option doesn't
have any effect at all when the input to be encoded is a `RDF.Dataset`.
"""
use RDF.Serialization.Encoder
alias RDF.Statement
alias RDF.{Statement, Graph}
@impl RDF.Serialization.Encoder
@callback encode(RDF.Data.t(), keyword) :: {:ok, String.t()} | {:error, any}
def encode(data, _opts \\ []) do
{:ok,
data
|> Enum.reduce([], &[statement(&1) | &2])
|> Enum.reverse()
|> Enum.join()}
def encode(data, opts \\ []) do
default_graph_name = default_graph_name(data, Keyword.get(opts, :default_graph_name, false))
{:ok, Enum.map_join(data, &statement(&1, default_graph_name))}
end
@impl RDF.Serialization.Encoder
@spec stream(RDF.Data.t(), keyword) :: Enumerable.t()
def stream(data, opts \\ []) do
default_graph_name = default_graph_name(data, Keyword.get(opts, :default_graph_name, false))
case Keyword.get(opts, :mode, :string) do
:string -> Stream.map(data, &statement(&1))
:iodata -> Stream.map(data, &iolist_statement(&1))
:string -> Stream.map(data, &statement(&1, default_graph_name))
:iodata -> Stream.map(data, &iolist_statement(&1, default_graph_name))
invalid -> raise "Invalid stream mode: #{invalid}"
end
end
@spec statement(Statement.t()) :: String.t()
def statement(statement)
defp default_graph_name(%Graph{} = graph, false), do: graph.name
defp default_graph_name(_, none) when none in [false, nil], do: nil
def statement({subject, predicate, object, nil}) do
statement({subject, predicate, object})
defp default_graph_name(_, default_graph_name),
do: Statement.coerce_graph_name(default_graph_name)
@spec statement(Statement.t(), Statement.graph_name()) :: String.t()
def statement(statement, default_graph_name)
def statement({subject, predicate, object, nil}, _) do
"#{term(subject)} #{term(predicate)} #{term(object)} .\n"
end
def statement({subject, predicate, object, graph}) do
def statement({subject, predicate, object, graph}, _) do
"#{term(subject)} #{term(predicate)} #{term(object)} #{term(graph)} .\n"
end
def statement({subject, predicate, object}) do
"#{term(subject)} #{term(predicate)} #{term(object)} .\n"
def statement({subject, predicate, object}, default_graph_name) do
statement({subject, predicate, object, default_graph_name}, default_graph_name)
end
defdelegate term(value), to: RDF.NTriples.Encoder
@spec iolist_statement(Statement.t()) :: iolist
def iolist_statement(statement)
@spec iolist_statement(Statement.t(), Statement.graph_name()) :: iolist
def iolist_statement(statement, default_graph_name)
def iolist_statement({subject, predicate, object, nil}) do
iolist_statement({subject, predicate, object})
def iolist_statement({subject, predicate, object, nil}, _) do
[iolist_term(subject), " ", iolist_term(predicate), " ", iolist_term(object), " .\n"]
end
def iolist_statement({subject, predicate, object, graph}) do
def iolist_statement({subject, predicate, object, graph}, _) do
[
iolist_term(subject),
" ",
@ -68,8 +81,8 @@ defmodule RDF.NQuads.Encoder do
]
end
def iolist_statement({subject, predicate, object}) do
[iolist_term(subject), " ", iolist_term(predicate), " ", iolist_term(object), " .\n"]
def iolist_statement({subject, predicate, object}, default_graph_name) do
iolist_statement({subject, predicate, object, default_graph_name}, default_graph_name)
end
defdelegate iolist_term(value), to: RDF.NTriples.Encoder

View file

@ -24,6 +24,32 @@ defmodule RDF.NQuads.EncoderTest do
assert NQuads.Encoder.encode!(Graph.new()) == ""
end
test "graph name" do
assert Graph.new({EX.S1, EX.p1(), EX.O1}) |> NQuads.Encoder.encode!() ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
"""
assert Graph.new({EX.S1, EX.p1(), EX.O1}, name: EX.Graph) |> NQuads.Encoder.encode!() ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#Graph> .
"""
end
test "default_graph_name opt" do
assert Graph.new({EX.S1, EX.p1(), EX.O1})
|> NQuads.Encoder.encode!(default_graph_name: EX.Graph) ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#Graph> .
"""
assert Graph.new({EX.S1, EX.p1(), EX.O1}, name: EX.Graph)
|> NQuads.Encoder.encode!(default_graph_name: nil) ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
"""
end
test "statements with IRIs only" do
assert NQuads.Encoder.encode!(
Graph.new([
@ -87,6 +113,20 @@ defmodule RDF.NQuads.EncoderTest do
end
end
test "serializing a description" do
description = EX.S1 |> EX.p1(EX.O1)
assert NQuads.Encoder.encode!(description) ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
"""
assert NQuads.Encoder.encode!(description, default_graph_name: EX.Graph) ==
"""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#Graph> .
"""
end
describe "serializing a dataset" do
test "an empty dataset is serialized to an empty string" do
assert NQuads.Encoder.encode!(Dataset.new()) == ""
@ -143,27 +183,59 @@ defmodule RDF.NQuads.EncoderTest do
end
describe "stream/2" do
dataset =
Dataset.new([
{EX.S1, EX.p1(), EX.O1},
{EX.S2, EX.p2(), RDF.bnode("foo"), EX.G},
{EX.S3, EX.p3(), ~L"foo"},
{EX.S3, EX.p3(), ~L"foo"en, EX.G}
])
test "a dataset" do
dataset =
Dataset.new([
{EX.S1, EX.p1(), EX.O1},
{EX.S2, EX.p2(), RDF.bnode("foo"), EX.G},
{EX.S3, EX.p3(), ~L"foo"},
{EX.S3, EX.p3(), ~L"foo"en, EX.G}
])
expected_result = """
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S3> <http://example.org/#p3> "foo" .
<http://example.org/#S2> <http://example.org/#p2> _:foo <http://example.org/#G> .
<http://example.org/#S3> <http://example.org/#p3> "foo"@en <http://example.org/#G> .
"""
expected_result = """
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S3> <http://example.org/#p3> "foo" .
<http://example.org/#S2> <http://example.org/#p2> _:foo <http://example.org/#G> .
<http://example.org/#S3> <http://example.org/#p3> "foo"@en <http://example.org/#G> .
"""
assert NQuads.Encoder.stream(dataset, mode: :string)
|> stream_to_string() ==
expected_result
assert NQuads.Encoder.stream(dataset, mode: :string)
|> stream_to_string() ==
expected_result
assert NQuads.Encoder.stream(dataset, mode: :iodata)
|> stream_to_string() ==
expected_result
assert NQuads.Encoder.stream(dataset, mode: :iodata)
|> stream_to_string() ==
expected_result
end
test "a graph" do
expected_unnamed_graph_result = """
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
"""
expected_named_graph_result = """
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> <http://example.org/#Graph> .
"""
assert Graph.new({EX.S1, EX.p1(), EX.O1})
|> NQuads.Encoder.stream(mode: :string)
|> stream_to_string() ==
expected_unnamed_graph_result
assert Graph.new({EX.S1, EX.p1(), EX.O1})
|> NQuads.Encoder.stream(mode: :iodata)
|> stream_to_string() ==
expected_unnamed_graph_result
assert Graph.new({EX.S1, EX.p1(), EX.O1}, name: EX.Graph)
|> NQuads.Encoder.stream(mode: :string)
|> stream_to_string() ==
expected_named_graph_result
assert Graph.new({EX.S1, EX.p1(), EX.O1}, name: EX.Graph)
|> NQuads.Encoder.stream(mode: :iodata)
|> stream_to_string() ==
expected_named_graph_result
end
end
end