@@ -21,7 +21,7 @@ module Frame
2121 def frame ( state , subjects , frame , **options )
2222 parent , property = options [ :parent ] , options [ :property ]
2323 # Validate the frame
24- validate_frame ( state , frame )
24+ validate_frame ( frame )
2525 frame = frame . first if frame . is_a? ( Array )
2626
2727 # Get values for embedOn and explicitOn
@@ -200,7 +200,7 @@ def cleanup_preserve(input)
200200 def filter_subjects ( state , subjects , frame , flags )
201201 subjects . inject ( { } ) do |memo , id |
202202 subject = state [ :subjects ] [ id ]
203- memo [ id ] = subject if filter_subject ( subject , frame , flags )
203+ memo [ id ] = subject if filter_subject ( subject , frame , state , flags )
204204 memo
205205 end
206206 end
@@ -214,55 +214,84 @@ def filter_subjects(state, subjects, frame, flags)
214214 #
215215 # @param [Hash{String => Object}] subject the subject to check.
216216 # @param [Hash{String => Object}] frame the frame to check.
217+ # @param [Hash{Symbol => Object}] state Current framing state
217218 # @param [Hash{Symbol => Object}] flags the frame flags.
218219 #
219220 # @return [Boolean] true if the node matches, false if not.
220- def filter_subject ( subject , frame , flags )
221+ def filter_subject ( subject , frame , state , flags )
221222 types = frame . fetch ( '@type' , [ ] )
222223 subject_types = subject . fetch ( '@type' , [ ] )
224+ ids = frame . fetch ( '@id' , [ ] )
225+ subject_ids = subject . fetch ( '@id' , [ ] )
226+ subject_ids = [ subject_ids ] unless subject_ids . is_a? ( Array )
223227
224- # check @type (object value means 'any' type, fall through to ducktyping)
225- if !types . empty? && types != [ { } ]
226- # A subject must match if node has a @type property including any IRI from the corresponding @type property in frame.
227- return types . any? { |t | subject_types . include? ( t ) }
228- elsif types == [ { } ]
229- # Otherwise, a subject must match if node has a @type property and frame has a @type property containing only an empty dictionary.
230- return !subject_types . empty?
231- else
232- # Duck typing, for nodes not having a type, but having @id
233- wildcard , matches_some = true , false
234-
235- frame . each do |k , v |
236- case k
237- when '@id'
238- return false if v . is_a? ( String ) && subject [ '@id' ] != v
239- wildcard , matches_some = false , true
240- when '@type'
241- wildcard , matches_some = false , false
242- when /^@/
243- else
244- wildcard = false
245-
246- if subject . has_key? ( k )
247- matches_some = true
248- next
249- elsif v == [ ]
250- # v == [] means do not match if property is present
251- return false
252- end
228+ # Match on specific @id.
229+ return !( ids & subject_ids ) . empty? if !ids . empty? && ids != [ { } ]
230+
231+ # Match on specific @type
232+ return !( types & subject_types ) . empty? if !types . empty? && types != [ { } ]
233+
234+ # Match on wildcard @type
235+ return true if types == [ { } ] && !subject_types . empty?
236+
237+ # Don't Match on no @type
238+ return false if frame [ '@type' ] == [ ] && !subject_types . empty?
239+
240+ # Duck typing, for nodes not having a type, but having @id
241+ wildcard , matches_some = true , false
242+
243+ frame . reject { |k | k . start_with? ( '@' ) } . each do |k , v |
244+ is_empty = v . empty?
245+ if v = v . first
246+ validate_frame ( v )
247+ has_default = v . has_key? ( '@default' )
248+ # Exclude framing keywords
249+ v = v . dup . delete_if { |kk , vv | %w( @default @embed @explicit @omitDefault @requireAll ) . include? ( kk ) }
250+ end
253251
254- # all properties must match to be a duck unless a @default is specified
255- has_default = v . is_a? ( Array ) && v . length == 1 && v . first . is_a? ( Hash ) && v . first . has_key? ( '@default' )
256- return false if flags [ :requireAll ] && !has_default
252+ node_values = subject . fetch ( k , [ ] )
253+
254+ # No longer a wildcard pattern if frame has any non-keyword properties
255+ wildcard = false
256+
257+ # Skip, but allow match if node has no value for property, and frame has a default value
258+ next if node_values . empty? && has_default
259+
260+ # If frame value is empty, don't match if subject has any value
261+ return false if !node_values . empty? && is_empty
262+
263+ match_this = case v
264+ when nil
265+ # node does not match if values is not empty and the value of property in frame is match none.
266+ return false unless node_values . empty?
267+ true
268+ when { }
269+ # node matches if values is not empty and the value of property in frame is wildcard
270+ !node_values . empty?
271+ else
272+ if value? ( v )
273+ # Match on any matching value
274+ node_values . any? { |nv | value_match? ( v , nv ) }
275+ elsif node? ( v ) || node_reference? ( v )
276+ node_values . any? do |nv |
277+ node_match? ( v , nv , state , flags )
278+ end
279+ else
280+ false # No matching on non-value or node values
257281 end
258282 end
259283
260- # return true if wildcard or subject matches some properties
261- wildcard || matches_some
284+ # All non-defaulted values must match if @requireAll is set
285+ return false if !match_this && flags [ :requireAll ]
286+
287+ matches_some ||= match_this
262288 end
289+
290+ # return true if wildcard or subject matches some properties
291+ wildcard || matches_some
263292 end
264293
265- def validate_frame ( state , frame )
294+ def validate_frame ( frame )
266295 raise InvalidFrame ::Syntax ,
267296 "Invalid JSON-LD syntax; a JSON-LD frame must be an object: #{ frame . inspect } " unless
268297 frame . is_a? ( Hash ) || ( frame . is_a? ( Array ) && frame . first . is_a? ( Hash ) && frame . length == 1 )
@@ -371,5 +400,27 @@ def add_frame_output(parent, property, output)
371400 def create_implicit_frame ( flags )
372401 [ flags . keys . inject ( { } ) { |memo , key | memo [ "@#{ key } " ] = [ flags [ key ] ] ; memo } ]
373402 end
403+
404+ private
405+ # Node matches if it is a node, and matches the pattern as a frame
406+ def node_match? ( pattern , value , state , flags )
407+ return false unless value [ '@id' ]
408+ node_object = state [ :subjects ] [ value [ '@id' ] ]
409+ node_object && filter_subject ( node_object , pattern , state , flags )
410+ end
411+
412+ # Value matches if it is a value, and matches the value pattern.
413+ #
414+ # * @values are the same, or `pattern[@value]` is a wildcard, and
415+ # * @types are the same or `value[@type]` is not null and `pattern[@type]` is `{}`, or `value[@type]` is null and `pattern[@type]` is null or `[]`, and
416+ # * @languages are the same or `value[@language]` is not null and `pattern[@language]` is `{}`, or `value[@language]` is null and `pattern[@language]` is null or `[]`.
417+ def value_match? ( pattern , value )
418+ v1 , t1 , l1 = value [ '@value' ] , value [ '@type' ] , value [ '@language' ]
419+ v2 , t2 , l2 = pattern [ '@value' ] , pattern [ '@type' ] , pattern [ '@language' ]
420+ return false unless v1 == v2 || v1 && v2 == { }
421+ return false unless t1 == t2 || t1 && t2 == { } || t1 . nil? && ( t2 || [ ] ) == [ ]
422+ return false unless l1 == l2 || l1 && l2 == { } || l1 . nil? && ( l2 || [ ] ) == [ ]
423+ true
424+ end
374425 end
375426end
0 commit comments