Add RDF.NTriples.Decoder.decode_from_stream/2

This commit is contained in:
Marcel Otto 2020-11-04 13:10:07 +01:00
parent 6ad8d0da72
commit b4f0ae074c
3 changed files with 81 additions and 26 deletions

View file

@ -9,18 +9,39 @@ defmodule RDF.NTriples.Decoder do
@impl RDF.Serialization.Decoder
@spec decode(String.t(), keyword) :: {:ok, Graph.t()} | {:error, any}
def decode(content, _opts \\ []) do
with {:ok, tokens, _} <- tokenize(content),
{:ok, ast} <- parse(tokens) do
def decode(string, _opts \\ []) do
with {:ok, ast} <- do_decode(string, true) do
{:ok, build_graph(ast)}
end
end
@impl RDF.Serialization.Decoder
@spec decode_from_stream(Enumerable.t(), keyword) :: Graph.t()
def decode_from_stream(stream, _opts \\ []) do
Enum.reduce(stream, Graph.new(), fn line, graph ->
case do_decode(line, false) do
{:ok, []} -> graph
{:ok, [[triple]]} -> Graph.add(graph, triple)
{:error, error} -> raise error
end
end)
end
defp do_decode(string, error_with_line_number) do
with {:ok, tokens, _} <- tokenize(string) do
parse(tokens)
else
{:error, {error_line, :ntriples_lexer, error_descriptor}, _error_line_again} ->
{:error,
"N-Triple scanner error on line #{error_line}: #{error_description(error_descriptor)}"}
"N-Triple scanner error#{if error_with_line_number, do: " on line #{error_line}"}: #{
error_description(error_descriptor)
}"}
{:error, {error_line, :ntriples_parser, error_descriptor}} ->
{:error,
"N-Triple parser error on line #{error_line}: #{error_description(error_descriptor)}"}
"N-Triple parser error#{if error_with_line_number, do: " on line #{error_line}"}: #{
error_description(error_descriptor)
}"}
end
end
@ -28,7 +49,9 @@ defmodule RDF.NTriples.Decoder do
defp parse(tokens), do: tokens |> :ntriples_parser.parse()
defp build_graph(ast) do
Enum.reduce(ast, Graph.new(), &Graph.add(&2, &1))
defp build_graph([]), do: Graph.new()
defp build_graph([triples]) do
Enum.reduce(triples, Graph.new(), &Graph.add(&2, &1))
end
end

View file

@ -34,6 +34,11 @@ defmodule RDF.Test.Case do
end
end
def string_to_stream(string) do
{:ok, pid} = StringIO.open(string)
IO.binstream(pid, :line)
end
###############################
# RDF.Description

View file

@ -3,6 +3,7 @@ defmodule RDF.NTriples.DecoderTest do
doctest RDF.NTriples.Decoder
alias RDF.NTriples.Decoder
alias RDF.Graph
use RDF.Vocabulary.Namespace
@ -11,29 +12,36 @@ defmodule RDF.NTriples.DecoderTest do
defvocab P, base_iri: "http://www.perceive.net/schemas/relationship/", terms: [], strict: false
import RDF.Sigils
import RDF.Test.Case, only: [string_to_stream: 1]
test "stream_support?/0" do
assert Decoder.stream_support?()
end
test "an empty string is deserialized to an empty graph" do
assert RDF.NTriples.Decoder.decode!("") == Graph.new()
assert RDF.NTriples.Decoder.decode!(" \n\r\r\n ") == Graph.new()
assert Decoder.decode!("") == Graph.new()
assert Decoder.decode!(" \n\r\r\n ") == Graph.new()
end
test "decoding comments" do
assert RDF.NTriples.Decoder.decode!("# just a comment") == Graph.new()
assert Decoder.decode!("# just a comment") == Graph.new()
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> _:1 . # a comment
""") == Graph.new({EX.S, EX.p(), RDF.bnode("1")})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
# a comment
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> .
""") == Graph.new({EX.S, EX.p(), EX.O})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> <http://example.org/#O> .
# a comment
""") == Graph.new({EX.S, EX.p(), EX.O})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
# Header line 1
# Header line 2
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
@ -48,17 +56,17 @@ defmodule RDF.NTriples.DecoderTest do
end
test "empty lines" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> .
""") == Graph.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> .
""") == Graph.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
@ -73,45 +81,45 @@ defmodule RDF.NTriples.DecoderTest do
end
test "decoding a single triple with iris" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/enemyOf> <http://example.org/#green_goblin> .
""") == Graph.new({EX.spiderman(), P.enemyOf(), EX.green_goblin()})
end
test "decoding a single triple with a blank node" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
_:foo <http://example.org/#p> <http://example.org/#O> .
""") == Graph.new({RDF.bnode("foo"), EX.p(), EX.O})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> _:1 .
""") == Graph.new({EX.S, EX.p(), RDF.bnode("1")})
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
_:foo <http://example.org/#p> _:bar .
""") == Graph.new({RDF.bnode("foo"), EX.p(), RDF.bnode("bar")})
end
test "decoding a single triple with an untyped string literal" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#spiderman> <http://www.perceive.net/schemas/relationship/realname> "Peter Parker" .
""") == Graph.new({EX.spiderman(), P.realname(), RDF.literal("Peter Parker")})
end
test "decoding a single triple with a typed literal" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#spiderman> <http://example.org/#p> "42"^^<http://www.w3.org/2001/XMLSchema#integer> .
""") == Graph.new({EX.spiderman(), EX.p(), RDF.literal(42)})
end
test "decoding a single triple with a language tagged literal" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S> <http://example.org/#p> "foo"@en .
""") == Graph.new({EX.S, EX.p(), RDF.literal("foo", language: "en")})
end
test "decoding multiple triples" do
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> .
""") ==
@ -120,9 +128,10 @@ defmodule RDF.NTriples.DecoderTest do
{EX.S1, EX.p2(), EX.O2}
])
assert RDF.NTriples.Decoder.decode!("""
assert Decoder.decode!("""
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S1> <http://example.org/#p2> <http://example.org/#O2> .
<http://example.org/#S2> <http://example.org/#p3> <http://example.org/#O3> .
""") ==
Graph.new([
@ -131,4 +140,22 @@ defmodule RDF.NTriples.DecoderTest do
{EX.S2, EX.p3(), EX.O3}
])
end
test "decode_from_stream/2" do
assert """
<http://example.org/#S1> <http://example.org/#p1> <http://example.org/#O1> .
<http://example.org/#S1> <http://example.org/#p2> _:foo .
<http://example.org/#S2> <http://example.org/#p3> "foo"@en .
"""
|> string_to_stream()
|> Decoder.decode_from_stream() ==
Graph.new([
{EX.S1, EX.p1(), EX.O1},
{EX.S1, EX.p2(), ~B"foo"},
{EX.S2, EX.p3(), ~L"foo"en}
])
end
end