2017-04-10 19:24:43 +00:00
|
|
|
defmodule JSON.LD.Decoder do
|
2017-04-09 20:55:54 +00:00
|
|
|
@moduledoc """
|
|
|
|
"""
|
|
|
|
|
2017-04-10 19:24:43 +00:00
|
|
|
use RDF.Serialization.Decoder
|
2017-04-09 20:55:54 +00:00
|
|
|
|
|
|
|
import JSON.LD.{NodeIdentifierMap, Utils}
|
|
|
|
alias JSON.LD.NodeIdentifierMap
|
|
|
|
alias RDF.{Dataset, Graph}
|
|
|
|
alias RDF.NS.{XSD}
|
|
|
|
|
|
|
|
|
2017-04-10 19:24:43 +00:00
|
|
|
def decode(content, opts \\ []) do
|
2017-04-09 20:55:54 +00:00
|
|
|
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" ->
|
2017-09-13 23:41:12 +00:00
|
|
|
Graph.add rdf_graph,
|
|
|
|
node_to_rdf(subject), RDF.NS.RDF.type,
|
2017-04-09 20:55:54 +00:00
|
|
|
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) ->
|
2017-09-13 23:41:12 +00:00
|
|
|
with {list_triples, first} <-
|
2017-04-09 20:55:54 +00:00
|
|
|
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)
|
2017-04-14 11:38:38 +00:00
|
|
|
if Enum.empty?(rdf_graph) do
|
|
|
|
dataset
|
|
|
|
else
|
|
|
|
Dataset.add(dataset, rdf_graph,
|
2017-04-09 20:55:54 +00:00
|
|
|
if(graph_name == "@default", do: nil, else: graph_name))
|
2017-04-14 11:38:38 +00:00
|
|
|
end
|
2017-04-09 20:55:54 +00:00
|
|
|
else
|
|
|
|
dataset
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
after
|
|
|
|
NodeIdentifierMap.stop(node_id_map)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-01-27 22:32:41 +00:00
|
|
|
def parse_json(content, _opts \\ []) do
|
2018-03-15 23:08:39 +00:00
|
|
|
Jason.decode(content)
|
2017-04-09 20:55:54 +00:00
|
|
|
end
|
|
|
|
|
2018-01-27 22:32:41 +00:00
|
|
|
def parse_json!(content, _opts \\ []) do
|
2018-03-15 23:08:39 +00:00
|
|
|
Jason.decode!(content)
|
2017-04-09 20:55:54 +00:00
|
|
|
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"]
|
2017-09-13 23:41:12 +00:00
|
|
|
{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
|
2017-04-27 20:21:29 +00:00
|
|
|
RDF.Literal.new(value,
|
|
|
|
%{datatype: datatype, language: item["@language"], canonicalize: true})
|
2017-04-09 20:55:54 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
defp list_to_rdf(list, node_id_map) do
|
2017-09-13 23:41:12 +00:00
|
|
|
{list_triples, first, last} =
|
2017-04-14 11:38:38 +00:00
|
|
|
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
|
|
|
|
{
|
2017-09-13 23:41:12 +00:00
|
|
|
list_triples ++
|
2017-04-14 11:38:38 +00:00
|
|
|
[{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
|
2017-04-09 20:55:54 +00:00
|
|
|
end
|
|
|
|
|
2017-04-14 11:38:38 +00:00
|
|
|
# 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
|
|
|
|
|
2017-04-09 20:55:54 +00:00
|
|
|
end
|