diff --git a/lib/rdf/serializations/turtle_encoder.ex b/lib/rdf/serializations/turtle_encoder.ex index dd994a4..b50b7af 100644 --- a/lib/rdf/serializations/turtle_encoder.ex +++ b/lib/rdf/serializations/turtle_encoder.ex @@ -19,7 +19,12 @@ defmodule RDF.Turtle.Encoder do - `:implicit_base`: This boolean flag allows to use a base URI to get relative IRIs without embedding it explicitly in the content with a `@base` directive, so that the URIs will be resolved according to the remaining strategy specified in - section 5.1 of [RFC3986](https://www.ietf.org/rfc/rfc3986.txt). + section 5.1 of [RFC3986](https://www.ietf.org/rfc/rfc3986.txt) (default: `false`). + - `:base_description`: Allows to provide a description of the resource denoted by + the base URI. This option is especially useful when the base URI is actually not + specified, eg. in the common use case of wanting to describe the Turtle document + itself, which should be denoted by the URL where it is hosted as the implicit base + URI. - `:only`: Allows to specify which parts of a Turtle document should be generated. Possible values: `:base`, `:prefixes`, `:directives` (means the same as `[:base, :prefixes]`), `:triples` or a list with any combination of these values. @@ -64,6 +69,8 @@ defmodule RDF.Turtle.Encoder do ] @ordered_properties MapSet.new(@predicate_order) + @implicit_default_base "http://this-implicit-default-base-iri-should-never-appear-in-a-document" + @impl RDF.Serialization.Encoder @spec encode(Graph.t() | Description.t(), keyword) :: {:ok, String.t()} | {:error, any} def encode(data, opts \\ []) @@ -80,6 +87,9 @@ defmodule RDF.Turtle.Encoder do Keyword.get(opts, :prefixes) |> prefixes(graph) + {graph, base, opts} = + add_base_description(graph, base, Keyword.get(opts, :base_description), opts) + {:ok, state} = State.start_link(graph, base, prefixes) try do @@ -149,6 +159,21 @@ defmodule RDF.Turtle.Encoder do end end + defp add_base_description(graph, base, nil, opts), do: {graph, base, opts} + + defp add_base_description(graph, nil, base_description, opts) do + add_base_description( + graph, + @implicit_default_base, + base_description, + Keyword.put(opts, :implicit_base, true) + ) + end + + defp add_base_description(graph, base, base_description, opts) do + {Graph.add(graph, Description.new(base, init: base_description)), base, opts} + end + defp graph_statements(state, opts) do indent = indent(opts) diff --git a/test/unit/turtle_encoder_test.exs b/test/unit/turtle_encoder_test.exs index 947b457..1ea97d2 100644 --- a/test/unit/turtle_encoder_test.exs +++ b/test/unit/turtle_encoder_test.exs @@ -174,6 +174,60 @@ defmodule RDF.Turtle.EncoderTest do """ end + test ":base_description with a base IRI" do + assert Turtle.Encoder.encode!( + Graph.new([{EX.S1, EX.p1(), EX.O1}], + prefixes: %{} + ), + base_iri: EX, + base_description: %{EX.P2 => [EX.O2, EX.O3]} + ) == + """ + @base <#{to_string(EX.__base_iri__())}> . + + <> + , . + + + . + """ + + assert Turtle.Encoder.encode!( + Graph.new([{EX.S1, EX.p1(), EX.O1}], + prefixes: %{}, + base_iri: EX + ), + base_description: %{EX.P2 => [EX.O2, EX.O3]} + ) == + """ + @base <#{to_string(EX.__base_iri__())}> . + + <> + , . + + + . + """ + end + + test ":base_description without a base IRI" do + assert Turtle.Encoder.encode!( + Graph.new([{EX.S1, EX.p1(), EX.O1}], + prefixes: %{ex: EX} + ), + base_description: %{EX.P2 => [EX.O2, EX.O3]} + ) == + """ + @prefix ex: <#{to_string(EX.__base_iri__())}> . + + <> + ex:P2 ex:O2, ex:O3 . + + ex:S1 + ex:p1 ex:O1 . + """ + end + test "when no prefixes are given and no prefixes are in the given graph the default_prefixes are used" do assert Turtle.Encoder.encode!(Graph.new({EX.S, EX.p(), NS.XSD.string()})) == """