@@ -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
360384end
0 commit comments