@@ -19,6 +19,11 @@ module Frame
1919 # The parent property.
2020 # @raise [JSON::LD::InvalidFrame]
2121 def frame ( state , subjects , frame , **options )
22+ log_depth do
23+ log_debug ( "frame" ) { "subjects: #{ subjects . inspect } " }
24+ log_debug ( "frame" ) { "frame: #{ frame . to_json ( JSON_STATE ) } " }
25+ log_debug ( "frame" ) { "property: #{ options [ :property ] . inspect } " }
26+
2227 parent , property = options [ :parent ] , options [ :property ]
2328 # Validate the frame
2429 validate_frame ( frame )
@@ -31,6 +36,9 @@ def frame(state, subjects, frame, **options)
3136 requireAll : get_frame_flag ( frame , options , :requireAll ) ,
3237 }
3338
39+ # Get link for current graph
40+ link = state [ :link ] [ state [ :graph ] ] ||= { }
41+
3442 # Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
3543 # This gives us a hash of objects indexed by @id
3644 matches = filter_subjects ( state , subjects , frame , flags )
@@ -40,35 +48,64 @@ def frame(state, subjects, frame, **options)
4048 subject = matches [ id ]
4149
4250 # Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is None, which only occurs at the top-level.
43- state = state . merge ( uniqueEmbeds : { } ) if property . nil?
51+ if property . nil?
52+ state [ :uniqueEmbeds ] = { state [ :graph ] => { } }
53+ else
54+ state [ :uniqueEmbeds ] [ state [ :graph ] ] ||= { }
55+ end
4456
45- if flags [ :embed ] == '@link' && state [ : link] . has_key? ( id )
57+ if flags [ :embed ] == '@link' && link . has_key? ( id )
4658 # add existing linked subject
47- add_frame_output ( parent , property , state [ : link] [ id ] )
59+ add_frame_output ( parent , property , link [ id ] )
4860 next
4961 end
5062
5163 output = { '@id' => id }
52- state [ : link] [ id ] = output
64+ link [ id ] = output
5365
5466 # if embed is @never or if a circular reference would be created by an embed, the subject cannot be embedded, just add the reference; note that a circular reference won't occur when the embed flag is `@link` as the above check will short-circuit before reaching this point
55- if flags [ :embed ] == '@never' || creates_circular_reference ( subject , state [ :subjectStack ] )
67+ if flags [ :embed ] == '@never' || creates_circular_reference ( subject , state [ :graph ] , state [ : subjectStack] )
5668 add_frame_output ( parent , property , output )
5769 next
5870 end
5971
6072 # if only the last match should be embedded
6173 if flags [ :embed ] == '@last'
6274 # remove any existing embed
63- remove_embed ( state , id ) if state [ :uniqueEmbeds ] . include? ( id )
64- state [ :uniqueEmbeds ] [ id ] = {
75+ remove_embed ( state , id ) if state [ :uniqueEmbeds ] [ state [ :graph ] ] . include? ( id )
76+ state [ :uniqueEmbeds ] [ state [ :graph ] ] [ id ] = {
6577 parent : parent ,
6678 property : property
6779 }
6880 end
6981
7082 # push matching subject onto stack to enable circular embed checks
71- state [ :subjectStack ] << subject
83+ state [ :subjectStack ] << { subject : subject , graph : state [ :graph ] }
84+
85+ # Subject is also the name of a graph
86+ if state [ :graphMap ] . has_key? ( id )
87+ # check frame's "@graph" to see what to do next
88+ # 1. if it doesn't exist and state.graph === "@merged", don't recurse
89+ # 2. if it doesn't exist and state.graph !== "@merged", recurse
90+ # 3. if "@merged" then don't recurse
91+ # 4. if "@default" then don't recurse
92+ # 5. recurse
93+ recurse , subframe = false , nil
94+ if !frame . has_key? ( '@graph' )
95+ recurse , subframe = ( state [ :graph ] != '@merged' ) , { }
96+ else
97+ subframe = frame [ '@graph' ] . first
98+ recurse = !%w( @merged @default ) . include? ( subframe )
99+ subframe = { } unless subframe . is_a? ( Hash )
100+ end
101+
102+ if recurse
103+ state [ :graphStack ] . push ( state [ :graph ] )
104+ state [ :graph ] = id
105+ frame ( state , state [ :graphMap ] [ id ] . keys , [ subframe ] , options . merge ( parent : output , property : '@graph' ) )
106+ state [ :graph ] = state [ :graphStack ] . pop
107+ end
108+ end
72109
73110 # iterate over subject properties in order
74111 subject . keys . kw_sort . each do |prop |
@@ -141,6 +178,7 @@ def frame(state, subjects, frame, **options)
141178 # pop matching subject from circular ref-checking stack
142179 state [ :subjectStack ] . pop ( )
143180 end
181+ end
144182 end
145183
146184 ##
@@ -193,7 +231,7 @@ def cleanup_preserve(input)
193231 # @return all of the matched subjects.
194232 def filter_subjects ( state , subjects , frame , flags )
195233 subjects . inject ( { } ) do |memo , id |
196- subject = state [ :subjects ] [ id ]
234+ subject = state [ :graphMap ] [ state [ :graph ] ] [ id ]
197235 memo [ id ] = subject if filter_subject ( subject , frame , state , flags )
198236 memo
199237 end
@@ -306,12 +344,13 @@ def validate_frame(frame)
306344 # Checks the current subject stack to see if embedding the given subject would cause a circular reference.
307345 #
308346 # @param subject_to_embed the subject to embed.
347+ # @param graph the graph the subject to embed is in.
309348 # @param subject_stack the current stack of subjects.
310349 #
311350 # @return true if a circular reference would be created, false if not.
312- def creates_circular_reference ( subject_to_embed , subject_stack )
351+ def creates_circular_reference ( subject_to_embed , graph , subject_stack )
313352 subject_stack [ 0 ..-2 ] . any? do |subject |
314- subject [ '@id' ] == subject_to_embed [ '@id' ]
353+ subject [ :graph ] == graph && subject [ :subject ] [ '@id' ] == subject_to_embed [ '@id' ]
315354 end
316355 end
317356
@@ -344,7 +383,7 @@ def get_frame_flag(frame, options, name)
344383 # @param id the @id of the embed to remove.
345384 def remove_embed ( state , id )
346385 # get existing embed
347- embeds = state [ :uniqueEmbeds ] ;
386+ embeds = state [ :uniqueEmbeds ] [ state [ :graph ] ] ;
348387 embed = embeds [ id ] ;
349388 property = embed [ :property ] ;
350389
0 commit comments