diff --git a/lib/json/ld/compaction.ex b/lib/json/ld/compaction.ex index 8ed7a73..90df127 100644 --- a/lib/json/ld/compaction.ex +++ b/lib/json/ld/compaction.ex @@ -2,36 +2,24 @@ defmodule JSON.LD.Compaction do @moduledoc nil import JSON.LD.Utils - alias JSON.LD.Context - @doc """ - Compacts the given input according to the steps in the JSON-LD Compaction Algorithm. - > Compaction is the process of applying a developer-supplied context to shorten - > IRIs to terms or compact IRIs and JSON-LD values expressed in expanded form - > to simple values such as strings or numbers. Often this makes it simpler to - > work with document as the data is expressed in application-specific terms. - > Compacted documents are also typically easier to read for humans. - - -- - - Details at - """ - def compact(input, context, opts \\ []) do - with active_context = JSON.LD.context(context), + def compact(input, context, options \\ %JSON.LD.Options{}) do + with options = JSON.LD.Options.new(options), + active_context = JSON.LD.context(context), inverse_context = Context.inverse(active_context), - compact_arrays = Keyword.get(opts, :compact_arrays, true) + expanded = JSON.LD.expand(input, options) do - result = case do_compact(JSON.LD.expand(input), active_context, inverse_context, - nil, compact_arrays) do - [] -> - %{} - result when is_list(result) -> - %{compact_iri("@graph", active_context, inverse_context) => result} - result -> - result - end + result = + case do_compact(expanded, active_context, inverse_context, nil, options.compact_arrays) do + [] -> + %{} + result when is_list(result) -> + %{compact_iri("@graph", active_context, inverse_context) => result} + result -> + result + end if Context.empty?(active_context), do: result, else: Map.put(result, "@context", context["@context"] || context) diff --git a/lib/json/ld/context.ex b/lib/json/ld/context.ex index 41f496d..72966a2 100644 --- a/lib/json/ld/context.ex +++ b/lib/json/ld/context.ex @@ -10,14 +10,12 @@ defmodule JSON.LD.Context do alias JSON.LD.Context.TermDefinition - def new(opts \\ []) - - def new([base: base_iri]), do: %JSON.LD.Context{base_iri: base_iri} - def new(_), do: %JSON.LD.Context{} + def new(options \\ %JSON.LD.Options{}), + do: %JSON.LD.Context{base_iri: JSON.LD.Options.new(options).base} - def create(%{"@context" => json_ld_context}, opts), - do: new(opts) |> update(json_ld_context, Keyword.get(opts, :remote, [])) + def create(%{"@context" => json_ld_context}, options), + do: new(options) |> update(json_ld_context) def update(active, local, remote \\ []) diff --git a/lib/json/ld/expansion.ex b/lib/json/ld/expansion.ex index d950b60..d485e0a 100644 --- a/lib/json/ld/expansion.ex +++ b/lib/json/ld/expansion.ex @@ -1,40 +1,42 @@ defmodule JSON.LD.Expansion do @moduledoc nil - import JSON.LD.IRIExpansion - import JSON.LD.Utils + import JSON.LD.{IRIExpansion, Utils} - @doc """ - Expands the given input according to the steps in the JSON-LD Expansion Algorithm. + def expand(input, options \\ %JSON.LD.Options{}) do + with options = JSON.LD.Options.new(options), + active_context = JSON.LD.Context.new(options) + do + active_context = + case options.expand_context do + %{"@context" => context} -> + JSON.LD.Context.update(active_context, context) + %{} = context -> + JSON.LD.Context.update(active_context, context) + nil -> + active_context + end - > Expansion is the process of taking a JSON-LD document and applying a `@context` - > such that all IRIs, types, and values are expanded so that the `@context` is - > no longer necessary. - - -- - - Details at - """ - def expand(json_ld_object, opts \\ []) do - case do_expand(JSON.LD.Context.new(opts), nil, json_ld_object, Keyword.delete(opts, :base)) do - result = %{"@graph" => graph} when map_size(result) == 1 -> - graph - nil -> - [] - result when not is_list(result) -> - [result] - result -> result + case do_expand(active_context, nil, input, options) do + result = %{"@graph" => graph} when map_size(result) == 1 -> + graph + nil -> + [] + result when not is_list(result) -> + [result] + result -> result + end end end - defp do_expand(active_context, active_property, element, opts \\ []) + defp do_expand(active_context, active_property, element, options) # 1) If element is null, return null. defp do_expand(_, _, nil, _), do: nil # 2) If element is a scalar, ... - defp do_expand(active_context, active_property, element, opts) + defp do_expand(active_context, active_property, element, options) when is_binary(element) or is_number(element) or is_boolean(element) do if active_property in [nil, "@graph"] do nil @@ -44,13 +46,13 @@ defmodule JSON.LD.Expansion do end # 3) If element is an array, ... - defp do_expand(active_context, active_property, element, opts) + defp do_expand(active_context, active_property, element, options) when is_list(element) do term_def = active_context.term_defs[active_property] container_mapping = term_def && term_def.container_mapping element |> Enum.reduce([], fn (item, result) -> - expanded_item = do_expand(active_context, active_property, item) + expanded_item = do_expand(active_context, active_property, item, options) if (active_property == "@list" or container_mapping == "@list") and (is_list(expanded_item) or Map.has_key?(expanded_item, "@list")), do: raise JSON.LD.ListOfListsError, @@ -66,7 +68,7 @@ defmodule JSON.LD.Expansion do end # 4) - 13) - defp do_expand(active_context, active_property, element, opts) + defp do_expand(active_context, active_property, element, options) when is_map(element) do # 5) if Map.has_key?(element, "@context") do @@ -107,7 +109,7 @@ defmodule JSON.LD.Expansion do message: "#{inspect value} is not a valid @type value" end "@graph" -> # 7.4.5) - do_expand(active_context, "@graph", value, opts) + do_expand(active_context, "@graph", value, options) "@value" -> # 7.4.6) if scalar?(value) or is_nil(value) do if is_nil(value) do @@ -133,7 +135,7 @@ defmodule JSON.LD.Expansion do if active_property in [nil, "@graph"] do # 7.4.9.1) {:skip, result} else - value = do_expand(active_context, active_property, value, opts) + value = do_expand(active_context, active_property, value, options) # Spec FIXME: need to be sure that result is a list [from RDF.rb implementation] value = if is_list(value), @@ -148,12 +150,12 @@ defmodule JSON.LD.Expansion do value end "@set" -> # 7.4.10) - do_expand(active_context, active_property, value, opts) + do_expand(active_context, active_property, value, options) "@reverse" -> # 7.4.11) unless is_map(value), do: raise JSON.LD.InvalidReverseValueError, message: "#{inspect value} is not a valid @reverse value" - expanded_value = do_expand(active_context, "@reverse", value, opts) # 7.4.11.1) + expanded_value = do_expand(active_context, "@reverse", value, options) # 7.4.11.1) new_result = if Map.has_key?(expanded_value, "@reverse") do # 7.4.11.2) If expanded value contains an @reverse member, i.e., properties that are reversed twice, execute for each of its property and item the following steps: Enum.reduce expanded_value["@reverse"], result, @@ -234,7 +236,7 @@ defmodule JSON.LD.Expansion do index_value = if(is_list(index_value), do: index_value, else: [index_value]) - index_value = do_expand(active_context, key, index_value, opts) + index_value = do_expand(active_context, key, index_value, options) Enum.map(index_value, fn item -> Map.put_new(item, "@index", index) end) @@ -242,7 +244,7 @@ defmodule JSON.LD.Expansion do end) # 7.7) true -> - do_expand(active_context, key, value, opts) + do_expand(active_context, key, value, options) end # 7.8) if is_nil(expanded_value) do diff --git a/lib/json/ld/flattening.ex b/lib/json/ld/flattening.ex index f727ecd..6999b9d 100644 --- a/lib/json/ld/flattening.ex +++ b/lib/json/ld/flattening.ex @@ -1,24 +1,14 @@ defmodule JSON.LD.Flattening do @moduledoc nil - import JSON.LD.Utils - + import JSON.LD.{NodeIdentifierMap, Utils} alias JSON.LD.NodeIdentifierMap - @doc """ - Flattens the given input according to the steps in the JSON-LD Flattening Algorithm. - > Flattening collects all properties of a node in a single JSON object and labels - > all blank nodes with blank node identifiers. This ensures a shape of the data - > and consequently may drastically simplify the code required to process JSON-LD - > in certain applications. - - -- - - Details at - """ - def flatten(input, context \\ nil, opts \\ []) do - with expanded = JSON.LD.expand(input) do + def flatten(input, context \\ nil, options \\ %JSON.LD.Options{}) do + with options = JSON.LD.Options.new(options), + expanded = JSON.LD.expand(input, options) + do {:ok, node_id_map} = NodeIdentifierMap.start_link node_map = try do @@ -66,7 +56,7 @@ defmodule JSON.LD.Flattening do |> Enum.reverse if context && !Enum.empty?(flattened) do # TODO: Spec fixme: !Enum.empty?(flattened) is not in the spec, but in other implementations (Ruby, Java, Go, ...) - JSON.LD.compact(flattened, context, opts) + JSON.LD.compact(flattened, context, options) else flattened end @@ -106,7 +96,7 @@ defmodule JSON.LD.Flattening do types = Enum.reduce(types, [], fn (item, types) -> if blank_node_id?(item) do - identifier = NodeIdentifierMap.generate_blank_node_id(node_id_map, item) + identifier = generate_blank_node_id(node_id_map, item) types ++ [identifier] else types ++ [item] @@ -165,13 +155,13 @@ defmodule JSON.LD.Flattening do id = if id do if blank_node_id?(id) do - NodeIdentifierMap.generate_blank_node_id(node_id_map, id) + generate_blank_node_id(node_id_map, id) else id end # 6.2) else - NodeIdentifierMap.generate_blank_node_id(node_id_map) + generate_blank_node_id(node_id_map) end # 6.3) @@ -271,7 +261,7 @@ defmodule JSON.LD.Flattening do |> Enum.sort_by(fn {property, _} -> property end) |> Enum.reduce(node_map, fn ({property, value}, node_map) -> if blank_node_id?(property) do - property = NodeIdentifierMap.generate_blank_node_id(node_id_map, property) + property = generate_blank_node_id(node_id_map, property) end unless Map.has_key?(node_map[active_graph][id], property) do node_map = update_in node_map, [active_graph, id], fn node -> diff --git a/lib/json/ld/options.ex b/lib/json/ld/options.ex new file mode 100644 index 0000000..393b000 --- /dev/null +++ b/lib/json/ld/options.ex @@ -0,0 +1,18 @@ +defmodule JSON.LD.Options do + @moduledoc """ + Options accepted by the JSON-LD processing algorithms. + + as specified at + """ + + defstruct base: nil, + compact_arrays: true, + document_loader: nil, + expand_context: nil, + processing_mode: "json-ld-1.0" + + def new(), do: %JSON.LD.Options{} + def new(%JSON.LD.Options{} = options), do: options + def new(options), do: struct(JSON.LD.Options, options) + +end diff --git a/lib/json_ld.ex b/lib/json_ld.ex index 8ba2d91..3f518f1 100644 --- a/lib/json_ld.ex +++ b/lib/json_ld.ex @@ -30,27 +30,67 @@ defmodule JSON.LD do def keyword?(value) when is_binary(value) and value in @keywords, do: true def keyword?(value), do: false - defdelegate expand(input, options \\ []), + + @doc """ + Expands the given input according to the steps in the JSON-LD Expansion Algorithm. + + > Expansion is the process of taking a JSON-LD document and applying a `@context` + > such that all IRIs, types, and values are expanded so that the `@context` is + > no longer necessary. + + -- + + Details at + """ + defdelegate expand(input, options \\ %JSON.LD.Options{}), to: JSON.LD.Expansion - defdelegate compact(input, context, options \\ []), + + @doc """ + Compacts the given input according to the steps in the JSON-LD Compaction Algorithm. + + > Compaction is the process of applying a developer-supplied context to shorten + > IRIs to terms or compact IRIs and JSON-LD values expressed in expanded form + > to simple values such as strings or numbers. Often this makes it simpler to + > work with document as the data is expressed in application-specific terms. + > Compacted documents are also typically easier to read for humans. + + -- + + Details at + """ + defdelegate compact(input, context, options \\ %JSON.LD.Options{}), to: JSON.LD.Compaction - defdelegate flatten(input, context \\ nil, options \\ []), + + @doc """ + Flattens the given input according to the steps in the JSON-LD Flattening Algorithm. + + > Flattening collects all properties of a node in a single JSON object and labels + > all blank nodes with blank node identifiers. This ensures a shape of the data + > and consequently may drastically simplify the code required to process JSON-LD + > in certain applications. + + -- + + Details at + """ + defdelegate flatten(input, context \\ nil, options \\ %JSON.LD.Options{}), to: JSON.LD.Flattening + @doc """ Generator function for `JSON.LD.Context`s. You can either pass a map with a `"@context"` key having the JSON-LD context object its value, or the JSON-LD context object directly. """ - def context(args, opts \\ []) + def context(args, opts \\ %JSON.LD.Options{}) - def context(%{"@context" => _} = object, opts), - do: JSON.LD.Context.create(object, opts) + def context(%{"@context" => _} = object, options), + do: JSON.LD.Context.create(object, options) - def context(context, opts), - do: JSON.LD.Context.create(%{"@context" => context}, opts) + def context(context, options), + do: JSON.LD.Context.create(%{"@context" => context}, options) end