Skip to content

Commit ba20d10

Browse files
committed
Add createAnnotations option to flatten, and use in fromRdf.
1 parent 6093834 commit ba20d10

File tree

5 files changed

+72
-7
lines changed

5 files changed

+72
-7
lines changed

lib/json/ld/api.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ def self.compact(input, context, expanded: false, **options)
253253
# @param [Boolean] expanded (false) Input is already expanded
254254
# @param [Hash{Symbol => Object}] options
255255
# @option options (see #initialize)
256+
# @option options [Boolean] :createAnnotations
257+
# Unfold embedded nodes which can be represented using `@annotation`.
256258
# @yield jsonld
257259
# @yieldparam [Hash] jsonld
258260
# The flattened JSON-LD document
@@ -284,6 +286,13 @@ def self.flatten(input, context, expanded: false, **options)
284286
graph_maps = {'@default' => {}}
285287
create_node_map(value, graph_maps)
286288

289+
# If create annotations flag is set, then update each node map in graph maps with the result of calling the create annotations algorithm.
290+
if options[:createAnnotations]
291+
graph_maps.values.each do |node_map|
292+
create_annotations(node_map)
293+
end
294+
end
295+
287296
default_graph = graph_maps['@default']
288297
graph_maps.keys.opt_sort(ordered: @options[:ordered]).each do |graph_name|
289298
next if graph_name == '@default'

lib/json/ld/flatten.rb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,50 @@ def create_node_map(element, graph_map,
191191
end
192192
end
193193

194+
##
195+
# Create annotations
196+
#
197+
# Updates a node map from which annotations have been folded into embedded triples to re-extract the annotations.
198+
#
199+
# Map entries where the key is of the form of a canonicalized JSON object are used to find keys with the `@id` and property components. If found, the original map entry is removed and entries added to an `@annotation` property of the associated value.
200+
#
201+
# * Keys which are of the form of a canonicalized JSON object are examined in inverse order of length.
202+
# * Deserialize the key into a map, and re-serialize the value of `@id`.
203+
# * If the map contains an entry with that value (after re-canonicalizing, as appropriate), and the associated antry has a item which matches the non-`@id` item from the map, the node is used to create an `@annotation` entry within that value.
204+
#
205+
# @param [Hash{String => Hash}] input
206+
# @return [Hash{String => Hash}]
207+
def create_annotations(node_map)
208+
node_map.keys.
209+
select {|k| k.start_with?('{')}.
210+
sort_by(&:length).
211+
reverse.each do |key|
212+
213+
# Deserialize key, and re-serialize the `@id` value.
214+
emb = JSON.parse(key)
215+
id = emb.delete('@id')
216+
property, value = emb.to_a.first
217+
218+
# If id is a map, set it to the result of canonicalizing that value, otherwise to itself.
219+
id = id.to_json_c14n if id.is_a?(Hash)
220+
221+
next unless node_map.key?(id)
222+
# If node map has an entry for id and that entry contains the same property and value from entry:
223+
node = node_map[id]
224+
225+
next unless node.key?(property)
226+
227+
node[property].each do |emb_value|
228+
next unless emb_value == value.first
229+
230+
annotation = node_map.delete(key)
231+
annotation.delete('@id')
232+
add_value(emb_value, '@annotation', annotation, property_is_array: true) unless
233+
annotation.empty?
234+
end
235+
end
236+
end
237+
194238
##
195239
# Rename blank nodes recursively within an embedded object
196240
#

lib/json/ld/format.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,13 @@ def self.cli_commands
174174
use: :required,
175175
on: ["--context CONTEXT"],
176176
description: "Context to use when compacting.") {|arg| RDF::URI(arg)},
177+
RDF::CLI::Option.new(
178+
symbol: :createAnnotations,
179+
datatype: TrueClass,
180+
default: false,
181+
control: :checkbox,
182+
on: ["--[no-]create-annotations"],
183+
description: "Unfold embedded nodes which can be represented using `@annotation`."),
177184
]
178185
},
179186
frame: {

lib/json/ld/from_rdf.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
4040

4141
default_graph[name] ||= {'@id' => name} unless name == '@default'
4242

43-
subject = statement.subject.to_s
44-
node = node_map[subject] ||= resource_representation(statement.subject,useNativeTypes)
43+
subject = statement.subject.statement? ?
44+
resource_representation(statement.subject, useNativeTypes)['@id'].to_json_c14n :
45+
statement.subject.to_s
46+
node = node_map[subject] ||= resource_representation(statement.subject, useNativeTypes)
4547

4648
# If predicate is rdf:datatype, note subject in compound literal subjects map
4749
if @options[:rdfDirection] == 'compound-literal' && statement.predicate == RDF.to_uri + 'direction'
@@ -50,12 +52,14 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
5052

5153
# If object is an IRI, blank node identifier, or statement, and node map does not have an object member, create one and initialize its value to a new JSON object consisting of a single member @id whose value is set to object.
5254
unless statement.object.literal?
53-
node_map[statement.object.to_s] ||=
55+
object = statement.object.statement? ?
56+
resource_representation(statement.object, useNativeTypes)['@id'].to_json_c14n :
57+
statement.object.to_s
58+
node_map[object] ||=
5459
resource_representation(statement.object, useNativeTypes)
5560
end
5661

5762
# If predicate equals rdf:type, and object is an IRI or blank node identifier, append object to the value of the @type member of node. If no such member exists, create one and initialize it to an array whose only item is object. Finally, continue to the next RDF triple.
58-
# XXX JSON-LD* does not support embedded value of @type
5963
if statement.predicate == RDF.type && statement.object.resource? && !useRdfType
6064
merge_value(node, '@type', statement.object.to_s)
6165
next
@@ -112,8 +116,7 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
112116
end
113117
end
114118

115-
# Skip to next graph, unless this one has lists
116-
next unless nil_var = graph_object[RDF.nil.to_s]
119+
nil_var = graph_object.fetch(RDF.nil.to_s, {})
117120

118121
# For each item usage in the usages member of nil, perform the following steps:
119122
nil_var.fetch(:usages, []).each do |usage|
@@ -141,6 +144,9 @@ def from_statements(dataset, useRdfType: false, useNativeTypes: false)
141144
head['@list'] = list.reverse
142145
list_nodes.each {|node_id| graph_object.delete(node_id)}
143146
end
147+
148+
# Create annotations on graph object
149+
create_annotations(graph_object)
144150
end
145151

146152
result = []

spec/spec_helper.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ def remap_bnodes(actual, expected)
7777
bijection = bijection.inject({}) {|memo, (k, v)| memo.merge(k.to_s => v.to_s)}
7878

7979
# Recursively replace blank nodes in actual with the bijection
80-
#require 'byebug'; byebug
8180
replace_nodes(actual, bijection)
8281
else
8382
actual

0 commit comments

Comments
 (0)