defmodule JSON.LD.Decoder do @moduledoc """ """ use RDF.Serialization.Decoder import JSON.LD.{NodeIdentifierMap, Utils} alias JSON.LD.NodeIdentifierMap alias RDF.{Dataset, Graph} alias RDF.NS.{XSD} def decode(content, opts \\ []) do with {:ok, json_ld_object} <- parse_json(content), dataset = to_rdf(json_ld_object, opts) do {:ok, dataset} end end def to_rdf(element, options \\ %JSON.LD.Options{}) do with options = JSON.LD.Options.new(options) do {:ok, node_id_map} = NodeIdentifierMap.start_link try do element |> JSON.LD.expand(options) |> JSON.LD.node_map(node_id_map) |> Enum.sort_by(fn {graph_name, _} -> graph_name end) |> Enum.reduce(Dataset.new, fn ({graph_name, graph}, dataset) -> unless relative_iri?(graph_name) do rdf_graph = graph |> Enum.sort_by(fn {subject, _} -> subject end) |> Enum.reduce(Graph.new, fn ({subject, node}, rdf_graph) -> unless relative_iri?(subject) do node |> Enum.sort_by(fn {property, _} -> property end) |> Enum.reduce(rdf_graph, fn ({property, values}, rdf_graph) -> cond do property == "@type" -> Graph.add rdf_graph, node_to_rdf(subject), RDF.NS.RDF.type, Enum.map(values, &node_to_rdf/1) JSON.LD.keyword?(property) -> rdf_graph not options.produce_generalized_rdf and blank_node_id?(property) -> rdf_graph relative_iri?(property) -> rdf_graph true -> Enum.reduce values, rdf_graph, fn (%{"@list" => list}, rdf_graph) -> with {list_triples, first} <- list_to_rdf(list, node_id_map) do rdf_graph |> Graph.add({node_to_rdf(subject), node_to_rdf(property), first}) |> Graph.add(list_triples) end (item, rdf_graph) -> case object_to_rdf(item) do nil -> rdf_graph object -> Graph.add rdf_graph, {node_to_rdf(subject), node_to_rdf(property), object} end end end end) else rdf_graph end end) if Enum.empty?(rdf_graph) do dataset else Dataset.add(dataset, rdf_graph, if(graph_name == "@default", do: nil, else: graph_name)) end else dataset end end) after NodeIdentifierMap.stop(node_id_map) end end end def parse_json(content, _opts \\ []) do Jason.decode(content) end def parse_json!(content, _opts \\ []) do Jason.decode!(content) end def node_to_rdf(nil), do: nil def node_to_rdf(node) do if blank_node_id?(node) do node |> String.trim_leading("_:") |> RDF.bnode else RDF.uri(node) end end defp object_to_rdf(%{"@id" => id}) do unless relative_iri?(id) do node_to_rdf(id) end end defp object_to_rdf(%{"@value" => value} = item) do datatype = item["@type"] {value, datatype} = cond do is_boolean(value) -> value = value |> RDF.Boolean.new |> RDF.Literal.canonical |> RDF.Literal.lexical datatype = if is_nil(datatype), do: XSD.boolean, else: datatype {value, datatype} is_float(value) or (is_number(value) and datatype == to_string(XSD.double)) -> value = value |> RDF.Double.new |> RDF.Literal.canonical |> RDF.Literal.lexical datatype = if is_nil(datatype), do: XSD.double, else: datatype {value, datatype} is_integer(value) or (is_number(value) and datatype == to_string(XSD.integer)) -> value = value |> RDF.Integer.new |> RDF.Literal.canonical |> RDF.Literal.lexical datatype = if is_nil(datatype), do: XSD.integer, else: datatype {value, datatype} is_nil(datatype) -> datatype = if Map.has_key?(item, "@language") do RDF.langString else XSD.string end {value, datatype} true -> {value, datatype} end RDF.Literal.new(value, %{datatype: datatype, language: item["@language"], canonicalize: true}) end defp list_to_rdf(list, node_id_map) do {list_triples, first, last} = list |> Enum.reduce({[], nil, nil}, fn (item, {list_triples, first, last}) -> case object_to_rdf(item) do nil -> {list_triples, first, last} object -> with bnode = node_to_rdf(generate_blank_node_id(node_id_map)) do if last do { list_triples ++ [{last, RDF.NS.RDF.rest, bnode}, {bnode, RDF.NS.RDF.first, object}], first, bnode } else { list_triples ++ [{bnode, RDF.NS.RDF.first, object}], bnode, bnode } end end end end) if last do {list_triples ++ [{last, RDF.NS.RDF.rest, RDF.NS.RDF.nil}], first} else {[], RDF.NS.RDF.nil} end end # This is a much nicer and faster version, but the blank node numbering is reversed. # Although this isn't relevant, I prefer to be more spec conform (for now). # defp list_to_rdf(list, node_id_map) do # list # |> Enum.reverse # |> Enum.reduce({[], RDF.NS.RDF.nil}, fn (item, {list_triples, last}) -> # case object_to_rdf(item) do # nil -> {list_triples, last} # object -> # with bnode = node_to_rdf(generate_blank_node_id(node_id_map)) do # { # [{bnode, RDF.NS.RDF.first, object}, # {bnode, RDF.NS.RDF.rest, last } | list_triples], # bnode # } # end # end # end) # end end