Skip to content

Commit 04a3cb7

Browse files
committed
Update framing based on pyLd implementation.
1 parent 9ab5746 commit 04a3cb7

File tree

3 files changed

+72
-49
lines changed

3 files changed

+72
-49
lines changed

lib/json/ld/api.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def self.flatten(input, context, options = {})
252252

253253
# Initialize node map to a JSON object consisting of a single member whose key is @default and whose value is an empty JSON object.
254254
graphs = {'@default' => {}}
255-
self.create_node_map(value, graphs)
255+
create_node_map(value, graphs, '@default', namer)
256256

257257
default_graph = graphs['@default']
258258
graphs.keys.kw_sort.reject {|k| k == '@default'}.each do |graph_name|
@@ -325,7 +325,6 @@ def self.frame(input, frame, options = {})
325325
}.merge(options)
326326

327327
framing_state = {
328-
options: options,
329328
graphs: {'@default' => {}, '@merged' => {}},
330329
subjectStack: [],
331330
link: {},
@@ -356,13 +355,13 @@ def self.frame(input, frame, options = {})
356355

357356
# Get framing nodes from expanded input, replacing Blank Node identifiers as necessary
358357
old_dbg, @options[:debug] = @options[:debug], nil
359-
create_node_map(value, framing_state[:graphs], '@merged')
358+
create_node_map(value, framing_state[:graphs], '@merged', namer)
360359
@options[:debug] = old_dbg
361360
framing_state[:subjects] = framing_state[:graphs]['@merged']
362361
debug(".frame") {"subjects: #{framing_state[:subjects].to_json(JSON_STATE) rescue 'malformed json'}"}
363362

364363
result = []
365-
frame(framing_state, framing_state[:subjects], (expanded_frame.first || {}), parent: result)
364+
frame(framing_state, framing_state[:subjects].keys.sort, (expanded_frame.first || {}), options.merge(parent: result))
366365
debug(".frame") {"after frame: #{result.to_json(JSON_STATE) rescue 'malformed json'}"}
367366

368367
# Initalize context from frame
@@ -416,7 +415,7 @@ def self.toRdf(input, options = {}, &block)
416415

417416
# Generate _nodeMap_
418417
graphs = {'@default' => {}}
419-
create_node_map(expanded_input, graphs)
418+
create_node_map(expanded_input, graphs, '@default', namer)
420419
debug(".toRdf") {"node map: #{graphs.to_json(JSON_STATE) rescue 'malformed json'}"}
421420

422421
# Start generating statements

lib/json/ld/frame.rb

Lines changed: 67 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module Frame
77
#
88
# @param [Hash{Symbol => Object}] state
99
# Current framing state
10-
# @param [Hash{String => Hash}] subjects
10+
# @param [Array<String>] subjects
1111
# The subjects to filter
1212
# @param [Hash{String => Object}] frame
1313
# @param [Hash{Symbol => Object}] options ({})
@@ -19,24 +19,20 @@ module Frame
1919
def frame(state, subjects, frame, options = {})
2020
depth do
2121
parent, property = options[:parent], options[:property]
22-
debug("frame") {"frame: #{frame.to_json(JSON_STATE) rescue 'malformed json'}"}
23-
debug("frame") {"parent: #{parent.to_json(JSON_STATE) rescue 'malformed json'}"}
24-
debug("frame") {"property: #{property.inspect}"}
2522
# Validate the frame
2623
validate_frame(state, frame)
2724
frame = frame.first if frame.is_a?(Array)
2825

2926
# Get values for embedOn and explicitOn
3027
flags = {
31-
embed: get_frame_flag(state, frame, 'embed'),
32-
explicit: get_frame_flag(state, frame, 'explicit'),
33-
require_all: get_frame_flag(state, frame, 'requireAll'),
28+
embed: get_frame_flag(frame, options, :embed),
29+
explicit: get_frame_flag(frame, options, :explicit),
30+
requireAll: get_frame_flag(frame, options, :requireAll),
3431
}
3532

3633
# Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
3734
# This gives us a hash of objects indexed by @id
3835
matches = filter_subjects(state, subjects, frame, flags)
39-
debug("frame") {"matches: #{matches.keys.inspect}"}
4036

4137
# For each id and node from the set of matched subjects ordered by id
4238
matches.keys.kw_sort.each do |id|
@@ -66,8 +62,7 @@ def frame(state, subjects, frame, options = {})
6662
# reference; note that a circular reference won't occur when the
6763
# embed flag is `@link` as the above check will short-circuit
6864
# before reaching this point
69-
if flags[:embed] == '@never' ||
70-
creates_circular_reference(subject, state[:subjectStack])
65+
if flags[:embed] == '@never' || creates_circular_reference(subject, state[:subjectStack])
7166
add_frame_output(parent, property, output)
7267
next
7368
end
@@ -109,16 +104,17 @@ def frame(state, subjects, frame, options = {})
109104
src = o['@list']
110105
src.each do |oo|
111106
if node_reference?(oo)
112-
subframe = frame[prop] ? frame[prop].first['@list'] : create_implicit_frame(flags)
113-
frame(state, [oo['@id']], subframe, list, '@list')
107+
subframe = frame[prop].first['@list'] if frame[prop].is_a?(Array) && frame[prop].first.is_a?(Hash)
108+
subframe ||= create_implicit_frame(flags)
109+
frame(state, [oo['@id']], subframe, options.merge(parent: list, property: '@list'))
114110
else
115111
add_frame_output(list, '@list', oo.dup)
116112
end
117113
end
118114
when node_reference?(o)
119115
# recurse into subject reference
120-
subframe = frame[prop] ? frame[prop]: create_implicit_frame(flags)
121-
frame(state, [o['@id']], subframe, output, prop)
116+
subframe = frame[prop] || create_implicit_frame(flags)
117+
frame(state, [o['@id']], subframe, options.merge(parent: output, property: prop))
122118
else
123119
# include other values automatically
124120
add_frame_output(output, prop, o.dup)
@@ -131,8 +127,8 @@ def frame(state, subjects, frame, options = {})
131127
# if omit default is off, then include default values for
132128
# properties that appear in the next frame but are not in
133129
# the matching subject
134-
n = frame[prop].first
135-
omit_default_on = get_frame_flag(n, options, 'omitDefault')
130+
n = frame[prop].first || {}
131+
omit_default_on = get_frame_flag(n, options, :omitDefault)
136132
if !omit_default_on && !output[prop]
137133
preserve = n.fetch('@default', '@null').dup
138134
preserve = [preserve] unless preserve.is_a?(Array)
@@ -156,7 +152,6 @@ def frame(state, subjects, frame, options = {})
156152
# @return [Array, Hash]
157153
def cleanup_preserve(input)
158154
depth do
159-
#debug("cleanup preserve") {input.inspect}
160155
result = case input
161156
when Array
162157
# If, after replacement, an array contains only the value null remove the value, leaving an empty array.
@@ -183,7 +178,6 @@ def cleanup_preserve(input)
183178
else
184179
input
185180
end
186-
#debug(" => ") {result.inspect}
187181
result
188182
end
189183
end
@@ -193,14 +187,16 @@ def cleanup_preserve(input)
193187
##
194188
# Returns a map of all of the subjects that match a parsed frame.
195189
#
196-
# @param state the current framing state.
197-
# @param subjects the set of subjects to filter.
198-
# @param frame the parsed frame.
199-
# @param flags the frame flags.
190+
# @param [Hash{Symbol => Object}] state
191+
# Current framing state
192+
# @param [Hash{String => Hash}] subjects
193+
# The subjects to filter
194+
# @param [Hash{String => Object}] frame
195+
# @param [Hash{Symbol => String}] flags the frame flags.
200196
#
201197
# @return all of the matched subjects.
202198
def filter_subjects(state, subjects, frame, flags)
203-
subjects.keys.inject({}) do |memo, id|
199+
subjects.inject({}) do |memo, id|
204200
subject = state[:subjects][id]
205201
memo[id] = subject if filter_subject(subject, frame, flags)
206202
memo
@@ -229,37 +225,38 @@ def filter_subject(subject, frame, flags)
229225
subject_types = subject.fetch('@type', [])
230226
raise InvalidFrame::Syntax, "node @type must be an array: #{node_types.inspect}" unless subject_types.is_a?(Array)
231227

232-
# check @type (object value means 'any' type, fall through to
233-
# ducktyping)
234-
if (types.length != 1 || !types.first.is_a?(Hash))
228+
# check @type (object value means 'any' type, fall through to ducktyping)
229+
if !types.empty? &&
230+
!(types.length == 1 && types.first.is_a?(Hash))
235231
# If frame has an @type, use it for selecting appropriate nodes.
236232
return types.any? {|t| subject_types.include?(t)}
237233
else
238234
# Duck typing, for nodes not having a type, but having @id
239-
wildcard = true
240-
matches_some = false
235+
wildcard, matches_some = true, false
241236

242237
frame.each do |k, v|
243238
case k
244239
when '@id'
245-
wildcard = true
246240
return false if v.is_a?(String) && subject['@id'] != v
247-
when '@type' then wildcard = true
248-
when /^@/ then next
241+
wildcard, matches_some = true, true
242+
when '@type'
243+
wildcard, matches_some = true, true
244+
when /^@/
249245
else
250246
wildcard = false
251247

252248
# v == [] means do not match if property is present
253249
if subject.has_key?(k)
254250
return false if v == []
255251
matches_some = true
252+
next
256253
end
257-
end
258254

259-
# all properties must match to be a duck unless a @default is
260-
# specified
261-
has_default = v.is_a?(Array) && v.length == 1 && v.first.is_a?(Hash) && v.has_key?('@default')
262-
return false if flags[:requireAll] && !has_default
255+
# all properties must match to be a duck unless a @default is
256+
# specified
257+
has_default = v.is_a?(Array) && v.length == 1 && v.first.is_a?(Hash) && v.first.has_key?('@default')
258+
return false if flags[:requireAll] && !has_default
259+
end
263260
end
264261

265262
# return true if wildcard or subject matches some properties
@@ -273,10 +270,38 @@ def validate_frame(state, frame)
273270
frame.is_a?(Hash) || (frame.is_a?(Array) && frame.first.is_a?(Hash) && frame.length == 1)
274271
end
275272

276-
# Return value of @name in frame, or default from state if it doesn't exist
277-
def get_frame_flag(state, frame, name)
278-
value = frame.fetch("@#{name}", [state[name.to_sym]]).first
279-
!!(value?(value) ? value['@value'] : value)
273+
# Checks the current subject stack to see if embedding the given subject
274+
# would cause a circular reference.
275+
#
276+
# @param subject_to_embed the subject to embed.
277+
# @param subject_stack the current stack of subjects.
278+
#
279+
# @return true if a circular reference would be created, false if not.
280+
def creates_circular_reference(subject_to_embed, subject_stack)
281+
subject_stack[0..-2].any? do |subject|
282+
subject['@id'] == subject_to_embed['@id']
283+
end
284+
end
285+
286+
# Gets the frame flag value for the given flag name.
287+
#
288+
# @param frame the frame.
289+
# @param options the framing options.
290+
# @param name the flag name.
291+
#
292+
# @return the flag value.
293+
def get_frame_flag(frame, options, name)
294+
rval = frame.fetch("@#{name}", [options[name]]).first
295+
rval = rval.values.first if value?(rval)
296+
if name == :embed
297+
rval = case rval
298+
when true then '@last'
299+
when false then '@never'
300+
when '@always', '@never', '@link' then rval
301+
else '@last'
302+
end
303+
end
304+
rval
280305
end
281306

282307
##
@@ -285,7 +310,6 @@ def get_frame_flag(state, frame, name)
285310
# @param state the current framing state.
286311
# @param id the @id of the embed to remove.
287312
def remove_embed(state, id)
288-
debug("frame") {"remove embed #{id.inspect}"}
289313
# get existing embed
290314
embeds = state[:uniqueEmbeds];
291315
embed = embeds[id];
@@ -337,7 +361,7 @@ def remove_dependents(id, embeds)
337361
# @param parent the parent to add to.
338362
# @param property the parent property, null for an array parent.
339363
# @param output the output to add.
340-
def add_frame_output(state, parent, property, output)
364+
def add_frame_output(parent, property, output)
341365
if parent.is_a?(Hash)
342366
parent[property] ||= []
343367
parent[property] << output
@@ -354,7 +378,7 @@ def add_frame_output(state, parent, property, output)
354378
# @param [Hash] flags: the current framing flags.
355379
# @return [Array<Hash>] the implicit frame.
356380
def create_implicit_frame(flags)
357-
[flags.inject({}) {|memo, key| memo['@' + key] = [flags[key]]; memo}]
381+
[flags.keys.inject({}) {|memo, key| memo["@#{key}"] = [flags[key]]; memo}]
358382
end
359383
end
360384
end

lib/json/ld/utils.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ def initialize(prefix)
294294
# @return [String]
295295
def get_sym(old = "")
296296
old = old.to_s.sub(/_:/, '')
297-
if old && self.has_key?(old)
297+
if !old.empty? && self.has_key?(old)
298298
self[old]
299299
elsif !old.empty?
300300
@num += 1

0 commit comments

Comments
 (0)