Add general RDF.Serialization.read_* and write_* functions

This commit is contained in:
Marcel Otto 2018-03-09 21:15:43 +01:00
parent 54d9eff014
commit 24aabc389b
5 changed files with 439 additions and 3 deletions

View file

@ -18,7 +18,13 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
### Added
- `RDF.Serialization.Format`s define a `name` atom
- The following functions to access available `RDF.Serialization.Format`s:
- all `RDF.Serialization.Reader` and `RDF.Serialization.Writer` functions are now
available on the `RDF.Serialization` module (or aliased on the top-level `RDF`
module) and the format can be specified instead of a `RDF.Serialization.Format`
argument, via the `format` or `media_type` option or in case of `*_file`
functions, without explicit specification of the format, but inferred from file
name extension instead; see the updated README section about RDF serializations
- the following functions to access available `RDF.Serialization.Format`s:
- `RDF.Serialization.formats/0`
- `RDF.Serialization.available_formats/0`
- `RDF.Serialization.format/1`

View file

@ -20,8 +20,9 @@ defmodule RDF do
- `RDF.Dataset`
- `RDF.Data`
- `RDF.List`
- the foundations for the definition of RDF serialization formats
- `RDF.Serialization`
- functions for working with RDF serializations: `RDF.Serialization`
- behaviours for the definition of RDF serialization formats
- `RDF.Serialization.Format`
- `RDF.Serialization.Decoder`
- `RDF.Serialization.Encoder`
- and the implementation of various RDF serialization formats
@ -38,6 +39,16 @@ defmodule RDF do
alias RDF.{IRI, Namespace, Literal, BlankNode, Triple, Quad,
Description, Graph, Dataset}
defdelegate read_string(content, opts), to: RDF.Serialization
defdelegate read_string!(content, opts), to: RDF.Serialization
defdelegate read_file(filename, opts \\ []), to: RDF.Serialization
defdelegate read_file!(filename, opts \\ []), to: RDF.Serialization
defdelegate write_string(content, opts), to: RDF.Serialization
defdelegate write_string!(content, opts), to: RDF.Serialization
defdelegate write_file(filename, opts \\ []), to: RDF.Serialization
defdelegate write_file!(filename, opts \\ []), to: RDF.Serialization
@doc """
Checks if the given value is a RDF resource.

View file

@ -90,9 +90,15 @@ defmodule RDF.Serialization do
iex> RDF.Serialization.format_by_extension("ttl")
RDF.Turtle
iex> RDF.Serialization.format_by_extension(".ttl")
RDF.Turtle
iex> RDF.Serialization.format_by_extension("jsonld")
nil # unless json_ld is defined as a dependency of the application
"""
def format_by_extension(extension)
def format_by_extension("." <> extension), do: format_by_extension(extension)
def format_by_extension(extension) do
format_where(fn format -> format.extension == extension end)
end
@ -102,4 +108,169 @@ defmodule RDF.Serialization do
|> Stream.filter(&Code.ensure_loaded?/1)
|> Enum.find(fun)
end
@doc """
Reads and decodes a serialized graph or dataset from a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
def read_string(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string(content, opts)
end
end
@doc """
Reads and decodes a serialized graph or dataset from a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
As opposed to `read_string`, it raises an exception if an error occurs.
"""
def read_string!(content, opts) do
with {:ok, format} <- string_format(opts) do
format.read_string!(content, opts)
else
{:error, error} -> raise error
end
end
@doc """
Reads and decodes a serialized graph or dataset from a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
It returns an `{:ok, data}` tuple, with `data` being the deserialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
def read_file(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file(file, opts)
end
end
@doc """
Reads and decodes a serialized graph or dataset from a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
As opposed to `read_file`, it raises an exception if an error occurs.
"""
def read_file!(file, opts \\ []) do
with {:ok, format} <- file_format(file, opts) do
format.read_file!(file, opts)
else
{:error, error} -> raise error
end
end
@doc """
Encodes and writes a graph or dataset to a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
It returns an `{:ok, string}` tuple, with `string` being the serialized graph or
dataset, or `{:error, reason}` if an error occurs.
"""
def write_string(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string(data, opts)
end
end
@doc """
Encodes and writes a graph or dataset to a string.
The format must be specified with the `format` option and a format name or the
`media_type` option and the media type of the format.
As opposed to `write_string`, it raises an exception if an error occurs.
"""
def write_string!(data, opts) do
with {:ok, format} <- string_format(opts) do
format.write_string!(data, opts)
else
{:error, error} -> raise error
end
end
@doc """
Encodes and writes a graph or dataset to a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
Other available serialization-independent options:
- `:force` - If not set to `true`, an error is raised when the given file
already exists (default: `false`)
- `:file_mode` - A list with the Elixir `File.open` modes to be used fior writing
(default: `[:utf8, :write]`)
It returns `:ok` if successfull or `{:error, reason}` if an error occurs.
"""
def write_file(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file(data, path, opts)
end
end
@doc """
Encodes and writes a graph or dataset to a file.
The format can be specified with the `format` option and a format name or the
`media_type` option and the media type of the format. If none of these are
given, the format gets inferred from the extension of the given file name.
See `write_file` for a list of other available options.
As opposed to `write_file`, it raises an exception if an error occurs.
"""
def write_file!(data, path, opts \\ []) do
with {:ok, format} <- file_format(path, opts) do
format.write_file!(data, path, opts)
else
{:error, error} -> raise error
end
end
defp string_format(opts) do
if format =
(opts |> Keyword.get(:format) |> format()) ||
(opts |> Keyword.get(:media_type) |> format_by_media_type())
do
{:ok, format}
else
{:error, "unable to detect serialization format"}
end
end
defp file_format(filename, opts) do
case string_format(opts) do
{:ok, format} -> {:ok, format}
_ -> format_by_file_name(filename)
end
end
defp format_by_file_name(filename) do
if format = filename |> Path.extname() |> format_by_extension() do
{:ok, format}
else
{:error, "unable to detect serialization format"}
end
end
end

47
test/data/cbd.ttl Normal file
View file

@ -0,0 +1,47 @@
@prefix dc: <http://purl.org/dc/terms/> .
@prefix dc11: <http://purl.org/dc/elements/1.1/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@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#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
<http://example.com/aBookCritic> <http://example.com/dislikes> <http://example.com/anotherGreatBook>;
<http://example.com/likes> <http://example.com/aReallyGreatBook> .
<http://example.com/john.jpg> dc11:extent "1234";
dc11:format "image/jpeg";
a foaf:Image .
foaf:mbox a rdf:Property,
owl:InverseFunctionalProperty .
<http://example.com/anotherGreatBook> dc11:creator "June Doe (june@example.com)";
dc11:format "application/pdf";
dc11:language "en";
dc11:publisher "Examples-R-Us";
dc11:rights "Copyright (C) 2004 Examples-R-Us. All rights reserved.";
dc11:title "Another Great Book";
dc:issued "2004-05-03"^^xsd:date;
rdfs:seeAlso <http://example.com/aReallyGreatBook> .
<http://example.com/aReallyGreatBook> dc11:contributor [ a foaf:Person;
foaf:name "Jane Doe"];
dc11:creator [ a foaf:Person;
foaf:img <http://example.com/john.jpg>;
foaf:mbox "john@example.com";
foaf:name "John Doe";
foaf:phone <tel:+1-999-555-1234>];
dc11:format "application/pdf";
dc11:language "en";
dc11:publisher "Examples-R-Us";
dc11:rights "Copyright (C) 2004 Examples-R-Us. All rights reserved.";
dc11:title "A Really Great Book";
dc:issued "2004-01-19"^^xsd:date;
rdfs:seeAlso <http://example.com/anotherGreatBook> .
[ rdf:object "image/jpeg";
rdf:predicate dc11:format;
rdf:subject foaf:Image;
a rdf:Statement;
rdfs:isDefinedBy <http://example.com/image-formats.rdf>] .
[ rdf:object "application/pdf";
rdf:predicate dc11:format;
rdf:subject <http://example.com/aReallyGreatBook>;
a rdf:Statement;
rdfs:isDefinedBy <http://example.com/book-formats.rdf>] .

View file

@ -2,4 +2,205 @@ defmodule RDF.SerializationTest do
use ExUnit.Case
doctest RDF.Serialization
use RDF.Vocabulary.Namespace
defvocab EX,
base_iri: "http://example.org/",
terms: [], strict: false
@example_turtle_file "test/data/cbd.ttl"
@example_turtle_string """
@prefix ex: <http://example.org/#> .
ex:Aaron <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ex:Person .
"""
@example_graph RDF.Graph.new [{EX.S, EX.p, EX.O}]
@example_graph_turtle """
@prefix : <#{to_string(EX. __base_iri__)}> .
:S
:p :O .
"""
defp file(name), do: System.tmp_dir!() |> Path.join(name)
describe "read_string/2" do
test "with correct format name" do
assert {:ok, %RDF.Graph{}} =
RDF.Serialization.read_string(@example_turtle_string, format: :turtle)
end
test "with wrong format name" do
assert {:error, "N-Triple scanner error" <> _} =
RDF.Serialization.read_string(@example_turtle_string, format: :ntriples)
end
test "with invalid format name" do
assert {:error, "unable to detect serialization format"} ==
RDF.Serialization.read_string(@example_turtle_string, format: :foo)
end
test "with media_type" do
assert {:ok, %RDF.Graph{}} =
RDF.Serialization.read_string(@example_turtle_string, media_type: "text/turtle")
end
end
describe "read_string!/2" do
test "with correct format name" do
assert %RDF.Graph{} =
RDF.Serialization.read_string!(@example_turtle_string, format: :turtle)
end
test "with wrong format name" do
assert_raise RuntimeError, ~r/^N-Triple scanner error.*/, fn ->
RDF.Serialization.read_string!(@example_turtle_string, format: :ntriples)
end
end
test "with invalid format name" do
assert_raise RuntimeError, "unable to detect serialization format", fn ->
RDF.Serialization.read_string!(@example_turtle_string, format: :foo)
end
end
test "with media_type" do
assert %RDF.Graph{} =
RDF.Serialization.read_string!(@example_turtle_string, media_type: "text/turtle")
end
end
describe "read_file/2" do
test "without arguments, i.e. via correct file extension" do
assert {:ok, %RDF.Graph{}} = RDF.Serialization.read_file(@example_turtle_file)
end
test "with correct format name" do
assert {:ok, %RDF.Graph{}} =
RDF.Serialization.read_file(@example_turtle_file, format: :turtle)
end
test "with wrong format name" do
assert {:error, "N-Triple scanner error" <> _} =
RDF.Serialization.read_file(@example_turtle_file, format: :ntriples)
end
test "with invalid format name, but correct file extension" do
assert {:ok, %RDF.Graph{}} = RDF.Serialization.read_file(@example_turtle_file, format: :foo)
end
test "with media_type" do
assert {:ok, %RDF.Graph{}} =
RDF.Serialization.read_file(@example_turtle_file, media_type: "text/turtle")
end
end
describe "read_file!/2" do
test "without arguments, i.e. via correct file extension" do
assert %RDF.Graph{} = RDF.Serialization.read_file!(@example_turtle_file)
end
test "with correct format name" do
assert %RDF.Graph{} =
RDF.Serialization.read_file!(@example_turtle_file, format: :turtle)
end
test "with wrong format name" do
assert_raise RuntimeError, ~r/^N-Triple scanner error.*/, fn ->
RDF.Serialization.read_file!(@example_turtle_file, format: :ntriples)
end
end
test "with media_type name" do
assert %RDF.Graph{} =
RDF.Serialization.read_file!(@example_turtle_file, media_type: "text/turtle")
end
end
describe "write_string/2" do
test "with name of available format" do
assert RDF.Serialization.write_string(@example_graph, format: :turtle,
prefixes: %{"" => EX. __base_iri__}) ==
{:ok, @example_graph_turtle}
end
test "with invalid format name" do
assert RDF.Serialization.write_string(@example_graph, format: :foo,
prefixes: %{"" => EX. __base_iri__}) ==
{:error, "unable to detect serialization format"}
end
test "with media type" do
assert RDF.Serialization.write_string(@example_graph, media_type: "text/turtle",
prefixes: %{"" => EX. __base_iri__}) ==
{:ok, @example_graph_turtle}
end
end
describe "write_string!/2" do
test "with name of available format" do
assert RDF.Serialization.write_string!(@example_graph, format: :turtle,
prefixes: %{"" => EX. __base_iri__}) ==
@example_graph_turtle
end
test "with invalid format name" do
assert_raise RuntimeError, "unable to detect serialization format", fn ->
RDF.Serialization.write_string!(@example_graph, format: :foo,
prefixes: %{"" => EX. __base_iri__})
end
end
test "with media type" do
assert RDF.Serialization.write_string!(@example_graph, media_type: "text/turtle",
prefixes: %{"" => EX. __base_iri__}) ==
@example_graph_turtle
end
end
describe "write_file/2" do
test "without arguments, i.e. via file extension" do
file = file("write_file_test.ttl")
if File.exists?(file), do: File.rm(file)
assert RDF.Serialization.write_file(@example_graph, file,
prefixes: %{"" => EX. __base_iri__}) == :ok
assert File.exists?(file)
assert File.read!(file) == @example_graph_turtle
File.rm(file)
end
test "with format name" do
file = file("write_file_test.nt")
if File.exists?(file), do: File.rm(file)
assert RDF.Serialization.write_file(@example_graph, file, format: :turtle,
prefixes: %{"" => EX. __base_iri__}) == :ok
assert File.exists?(file)
assert File.read!(file) == @example_graph_turtle
File.rm(file)
end
end
describe "write_file!/2" do
test "without arguments, i.e. via file extension" do
file = file("write_file_test.ttl")
if File.exists?(file), do: File.rm(file)
assert RDF.Serialization.write_file!(@example_graph, file,
prefixes: %{"" => EX. __base_iri__}) == :ok
assert File.exists?(file)
assert File.read!(file) == @example_graph_turtle
File.rm(file)
end
test "with format name" do
file = file("write_file_test.nt")
if File.exists?(file), do: File.rm(file)
assert RDF.Serialization.write_file!(@example_graph, file, format: :turtle,
prefixes: %{"" => EX. __base_iri__}) == :ok
assert File.exists?(file)
assert File.read!(file) == @example_graph_turtle
File.rm(file)
end
end
end