json-ld 3.0.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/AUTHORS +1 -1
  3. data/README.md +90 -53
  4. data/UNLICENSE +1 -1
  5. data/VERSION +1 -1
  6. data/bin/jsonld +4 -4
  7. data/lib/json/ld.rb +27 -10
  8. data/lib/json/ld/api.rb +325 -96
  9. data/lib/json/ld/compact.rb +75 -27
  10. data/lib/json/ld/conneg.rb +188 -0
  11. data/lib/json/ld/context.rb +677 -292
  12. data/lib/json/ld/expand.rb +240 -75
  13. data/lib/json/ld/flatten.rb +5 -3
  14. data/lib/json/ld/format.rb +19 -19
  15. data/lib/json/ld/frame.rb +135 -85
  16. data/lib/json/ld/from_rdf.rb +44 -17
  17. data/lib/json/ld/html/nokogiri.rb +151 -0
  18. data/lib/json/ld/html/rexml.rb +186 -0
  19. data/lib/json/ld/reader.rb +25 -5
  20. data/lib/json/ld/resource.rb +2 -2
  21. data/lib/json/ld/streaming_writer.rb +3 -1
  22. data/lib/json/ld/to_rdf.rb +47 -17
  23. data/lib/json/ld/utils.rb +4 -2
  24. data/lib/json/ld/writer.rb +75 -14
  25. data/spec/api_spec.rb +13 -34
  26. data/spec/compact_spec.rb +968 -9
  27. data/spec/conneg_spec.rb +373 -0
  28. data/spec/context_spec.rb +447 -53
  29. data/spec/expand_spec.rb +1872 -416
  30. data/spec/flatten_spec.rb +434 -47
  31. data/spec/frame_spec.rb +979 -344
  32. data/spec/from_rdf_spec.rb +305 -5
  33. data/spec/spec_helper.rb +177 -0
  34. data/spec/streaming_writer_spec.rb +4 -4
  35. data/spec/suite_compact_spec.rb +2 -2
  36. data/spec/suite_expand_spec.rb +14 -2
  37. data/spec/suite_flatten_spec.rb +10 -2
  38. data/spec/suite_frame_spec.rb +3 -2
  39. data/spec/suite_from_rdf_spec.rb +2 -2
  40. data/spec/suite_helper.rb +55 -20
  41. data/spec/suite_html_spec.rb +22 -0
  42. data/spec/suite_http_spec.rb +35 -0
  43. data/spec/suite_remote_doc_spec.rb +2 -2
  44. data/spec/suite_to_rdf_spec.rb +14 -3
  45. data/spec/support/extensions.rb +5 -1
  46. data/spec/test-files/test-4-input.json +3 -3
  47. data/spec/test-files/test-5-input.json +2 -2
  48. data/spec/test-files/test-8-framed.json +14 -18
  49. data/spec/to_rdf_spec.rb +606 -16
  50. data/spec/writer_spec.rb +5 -5
  51. metadata +144 -88
@@ -18,14 +18,11 @@ module JSON::LD
18
18
  # Property within current node
19
19
  # @param [Array] list (nil)
20
20
  # Used when property value is a list
21
- # @param [Boolean] ordered (true)
22
- # Ensure output objects have keys ordered properly
23
21
  def create_node_map(element, graph_map,
24
22
  active_graph: '@default',
25
23
  active_subject: nil,
26
24
  active_property: nil,
27
25
  list: nil)
28
- log_debug("node_map") {"active_graph: #{active_graph}, element: #{element.inspect}, active_subject: #{active_subject}"}
29
26
  if element.is_a?(Array)
30
27
  # If element is an array, process each entry in element recursively by passing item for element, node map, active graph, active subject, active property, and list.
31
28
  element.map do |o|
@@ -112,6 +109,11 @@ module JSON::LD
112
109
  active_graph: id)
113
110
  end
114
111
 
112
+ if element['@included']
113
+ create_node_map(element.delete('@included'), graph_map,
114
+ active_graph: active_graph)
115
+ end
116
+
115
117
  element.keys.each do |property|
116
118
  value = element[property]
117
119
 
@@ -18,8 +18,8 @@ module JSON::LD
18
18
  # @example Obtaining serialization format file extension mappings
19
19
  # RDF::Format.file_extensions #=> {:jsonld => [JSON::LD::Format] }
20
20
  #
21
- # @see http://www.w3.org/TR/json-ld/
22
- # @see https://json-ld.org/test-suite/
21
+ # @see https://www.w3.org/TR/json-ld11/
22
+ # @see https://w3c.github.io/json-ld-api/tests/
23
23
  class Format < RDF::Format
24
24
  content_type 'application/ld+json',
25
25
  extension: :jsonld,
@@ -54,7 +54,7 @@ module JSON::LD
54
54
  parse: false,
55
55
  help: "expand [--context <context-file>] files ...",
56
56
  filter: {output_format: :jsonld}, # Only shows output format set
57
- lambda: ->(files, options) do
57
+ lambda: ->(files, **options) do
58
58
  out = options[:output] || $stdout
59
59
  out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
60
60
  options = options.merge(expandContext: options.delete(:context)) if options.has_key?(:context)
@@ -75,7 +75,7 @@ module JSON::LD
75
75
  end
76
76
  else
77
77
  # Turn RDF into JSON-LD first
78
- RDF::CLI.parse(files, options) do |reader|
78
+ RDF::CLI.parse(files, **options) do |reader|
79
79
  JSON::LD::API.fromRdf(reader) do |expanded|
80
80
  out.puts expanded.to_json(JSON::LD::JSON_STATE)
81
81
  end
@@ -89,7 +89,7 @@ module JSON::LD
89
89
  parse: false,
90
90
  filter: {output_format: :jsonld}, # Only shows output format set
91
91
  help: "compact --context <context-file> files ...",
92
- lambda: ->(files, options) do
92
+ lambda: ->(files, **options) do
93
93
  raise ArgumentError, "Compacting requires a context" unless options[:context]
94
94
  out = options[:output] || $stdout
95
95
  out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
@@ -98,21 +98,21 @@ module JSON::LD
98
98
  # If files are empty, either use options[:execute]
99
99
  input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
100
100
  input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
101
- JSON::LD::API.compact(input, options[:context], options) do |compacted|
101
+ JSON::LD::API.compact(input, options[:context], **options) do |compacted|
102
102
  out.puts compacted.to_json(JSON::LD::JSON_STATE)
103
103
  end
104
104
  else
105
105
  files.each do |file|
106
- JSON::LD::API.compact(file, options[:context], options) do |compacted|
106
+ JSON::LD::API.compact(file, options[:context], **options) do |compacted|
107
107
  out.puts compacted.to_json(JSON::LD::JSON_STATE)
108
108
  end
109
109
  end
110
110
  end
111
111
  else
112
112
  # Turn RDF into JSON-LD first
113
- RDF::CLI.parse(files, options) do |reader|
113
+ RDF::CLI.parse(files, **options) do |reader|
114
114
  JSON::LD::API.fromRdf(reader) do |expanded|
115
- JSON::LD::API.compact(expanded, options[:context], options) do |compacted|
115
+ JSON::LD::API.compact(expanded, options[:context], **options) do |compacted|
116
116
  out.puts compacted.to_json(JSON::LD::JSON_STATE)
117
117
  end
118
118
  end
@@ -134,7 +134,7 @@ module JSON::LD
134
134
  parse: false,
135
135
  help: "flatten [--context <context-file>] files ...",
136
136
  filter: {output_format: :jsonld}, # Only shows output format set
137
- lambda: ->(files, options) do
137
+ lambda: ->(files, **options) do
138
138
  out = options[:output] || $stdout
139
139
  out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
140
140
  if options[:format] == :jsonld
@@ -142,21 +142,21 @@ module JSON::LD
142
142
  # If files are empty, either use options[:execute]
143
143
  input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
144
144
  input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
145
- JSON::LD::API.flatten(input, options[:context], options) do |flattened|
145
+ JSON::LD::API.flatten(input, options[:context], **options) do |flattened|
146
146
  out.puts flattened.to_json(JSON::LD::JSON_STATE)
147
147
  end
148
148
  else
149
149
  files.each do |file|
150
- JSON::LD::API.flatten(file, options[:context], options) do |flattened|
150
+ JSON::LD::API.flatten(file, options[:context], **options) do |flattened|
151
151
  out.puts flattened.to_json(JSON::LD::JSON_STATE)
152
152
  end
153
153
  end
154
154
  end
155
155
  else
156
156
  # Turn RDF into JSON-LD first
157
- RDF::CLI.parse(files, options) do |reader|
157
+ RDF::CLI.parse(files, **options) do |reader|
158
158
  JSON::LD::API.fromRdf(reader) do |expanded|
159
- JSON::LD::API.flatten(expanded, options[:context], options) do |flattened|
159
+ JSON::LD::API.flatten(expanded, options[:context], **options) do |flattened|
160
160
  out.puts flattened.to_json(JSON::LD::JSON_STATE)
161
161
  end
162
162
  end
@@ -169,7 +169,7 @@ module JSON::LD
169
169
  parse: false,
170
170
  help: "frame --frame <frame-file> files ...",
171
171
  filter: {output_format: :jsonld}, # Only shows output format set
172
- lambda: ->(files, options) do
172
+ lambda: ->(files, **options) do
173
173
  raise ArgumentError, "Framing requires a frame" unless options[:frame]
174
174
  out = options[:output] || $stdout
175
175
  out.set_encoding(Encoding::UTF_8) if RUBY_PLATFORM == "java"
@@ -178,21 +178,21 @@ module JSON::LD
178
178
  # If files are empty, either use options[:execute]
179
179
  input = options[:evaluate] ? StringIO.new(options[:evaluate]) : STDIN
180
180
  input.set_encoding(options.fetch(:encoding, Encoding::UTF_8))
181
- JSON::LD::API.frame(input, options[:frame], options) do |framed|
181
+ JSON::LD::API.frame(input, options[:frame], **options) do |framed|
182
182
  out.puts framed.to_json(JSON::LD::JSON_STATE)
183
183
  end
184
184
  else
185
185
  files.each do |file|
186
- JSON::LD::API.frame(file, options[:frame], options) do |framed|
186
+ JSON::LD::API.frame(file, options[:frame], **options) do |framed|
187
187
  out.puts framed.to_json(JSON::LD::JSON_STATE)
188
188
  end
189
189
  end
190
190
  end
191
191
  else
192
192
  # Turn RDF into JSON-LD first
193
- RDF::CLI.parse(files, options) do |reader|
193
+ RDF::CLI.parse(files, **options) do |reader|
194
194
  JSON::LD::API.fromRdf(reader) do |expanded|
195
- JSON::LD::API.frame(expanded, options[:frame], options) do |framed|
195
+ JSON::LD::API.frame(expanded, options[:frame], **options) do |framed|
196
196
  out.puts framed.to_json(JSON::LD::JSON_STATE)
197
197
  end
198
198
  end
@@ -23,11 +23,6 @@ module JSON::LD
23
23
  # @param [Hash{Symbol => Object}] options ({})
24
24
  # @raise [JSON::LD::InvalidFrame]
25
25
  def frame(state, subjects, frame, parent: nil, property: nil, ordered: false, **options)
26
- #log_depth do
27
- #log_debug("frame") {"subjects: #{subjects.inspect}"}
28
- #log_debug("frame") {"frame: #{frame.to_json(JSON_STATE)}"}
29
- #log_debug("frame") {"property: #{property.inspect}"}
30
-
31
26
  # Validate the frame
32
27
  validate_frame(frame)
33
28
  frame = frame.first if frame.is_a?(Array)
@@ -66,28 +61,43 @@ module JSON::LD
66
61
  output = {'@id' => id}
67
62
  link[id] = output
68
63
 
69
- # 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
70
- if flags[:embed] == '@never' || creates_circular_reference(subject, state[:graph], state[:subjectStack])
64
+ if %w(@first @last).include?(flags[:embed]) && context.processingMode('json-ld-1.1')
65
+ raise JSON::LD::JsonLdError::InvalidEmbedValue, "#{flags[:embed]} is not a valid value of @embed in 1.1 mode" if @options[:validate]
66
+ warn "[DEPRECATION] #{flags[:embed]} is not a valid value of @embed in 1.1 mode.\n"
67
+ end
68
+
69
+ if !state[:embedded] && state[:uniqueEmbeds][state[:graph]].has_key?(id)
70
+ # Skip adding this node object to the top-level, as it was included in another node object
71
+ next
72
+ elsif state[:embedded] &&
73
+ (flags[:embed] == '@never' || creates_circular_reference(subject, state[:graph], state[:subjectStack]))
74
+ # 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
71
75
  add_frame_output(parent, property, output)
72
76
  next
73
- end
77
+ elsif state[:embedded] &&
78
+ %w(@first @once).include?(flags[:embed]) &&
79
+ state[:uniqueEmbeds][state[:graph]].has_key?(id)
74
80
 
75
- # if only the last match should be embedded
76
- if flags[:embed] == '@last'
81
+ # if only the first match should be embedded
82
+ # Embed unless already embedded
83
+ add_frame_output(parent, property, output)
84
+ next
85
+ elsif flags[:embed] == '@last'
86
+ # if only the last match should be embedded
77
87
  # remove any existing embed
78
88
  remove_embed(state, id) if state[:uniqueEmbeds][state[:graph]].include?(id)
79
- state[:uniqueEmbeds][state[:graph]][id] = {
80
- parent: parent,
81
- property: property
82
- }
83
89
  end
84
90
 
91
+ state[:uniqueEmbeds][state[:graph]][id] = {
92
+ parent: parent,
93
+ property: property
94
+ }
95
+
85
96
  # push matching subject onto stack to enable circular embed checks
86
97
  state[:subjectStack] << {subject: subject, graph: state[:graph]}
87
98
 
88
99
  # Subject is also the name of a graph
89
100
  if state[:graphMap].has_key?(id)
90
- log_debug("frame") {"#{id} in graphMap"}
91
101
  # check frame's "@graph" to see what to do next
92
102
  # 1. if it doesn't exist and state.graph === "@merged", don't recurse
93
103
  # 2. if it doesn't exist and state.graph !== "@merged", recurse
@@ -105,12 +115,15 @@ module JSON::LD
105
115
 
106
116
  if recurse
107
117
  state[:graphStack].push(state[:graph])
108
- state[:graph] = id
109
- frame(state, state[:graphMap][id].keys, [subframe], parent: output, property: '@graph', **options)
110
- state[:graph] = state[:graphStack].pop
118
+ frame(state.merge(graph: id, embedded: false), state[:graphMap][id].keys, [subframe], parent: output, property: '@graph', **options)
111
119
  end
112
120
  end
113
121
 
122
+ # If frame has `@included`, recurse over it's sub-frame
123
+ if frame['@included']
124
+ frame(state.merge(embedded: false), subjects, frame['@included'], parent: output, property: '@included', **options)
125
+ end
126
+
114
127
  # iterate over subject properties in order
115
128
  subject.keys.opt_sort(ordered: ordered).each do |prop|
116
129
  objects = subject[prop]
@@ -139,14 +152,14 @@ module JSON::LD
139
152
  src = o['@list']
140
153
  src.each do |oo|
141
154
  if node_reference?(oo)
142
- frame(state, [oo['@id']], subframe, parent: list, property: '@list', **options)
155
+ frame(state.merge(embedded: true), [oo['@id']], subframe, parent: list, property: '@list', **options)
143
156
  else
144
157
  add_frame_output(list, '@list', oo.dup)
145
158
  end
146
159
  end
147
160
  when node_reference?(o)
148
161
  # recurse into subject reference
149
- frame(state, [o['@id']], subframe, parent: output, property: prop, **options)
162
+ frame(state.merge(embedded: true), [o['@id']], subframe, parent: output, property: prop, **options)
150
163
  when value_match?(subframe, o)
151
164
  # Include values if they match
152
165
  add_frame_output(output, prop, o.dup)
@@ -156,7 +169,11 @@ module JSON::LD
156
169
 
157
170
  # handle defaults in order
158
171
  frame.keys.opt_sort(ordered: ordered).each do |prop|
159
- next if prop.start_with?('@')
172
+ if prop == '@type' && frame[prop].first.is_a?(Hash) && frame[prop].first.keys == %w(@default)
173
+ # Treat this as a default
174
+ elsif prop.start_with?('@')
175
+ next
176
+ end
160
177
 
161
178
  # if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject
162
179
  n = frame[prop].first || {}
@@ -174,7 +191,7 @@ module JSON::LD
174
191
  # Node has property referencing this subject
175
192
  # recurse into reference
176
193
  (output['@reverse'] ||= {})[reverse_prop] ||= []
177
- frame(state, [r_id], subframe, parent: output['@reverse'][reverse_prop], property: property, **options)
194
+ frame(state.merge(embedded: true), [r_id], subframe, parent: output['@reverse'][reverse_prop], property: property, **options)
178
195
  end
179
196
  end
180
197
  end
@@ -243,35 +260,43 @@ module JSON::LD
243
260
  ##
244
261
  # Replace @preserve keys with the values, also replace @null with null.
245
262
  #
246
- # Optionally, remove BNode identifiers only used once.
247
- #
248
263
  # @param [Array, Hash] input
249
264
  # @return [Array, Hash]
250
265
  def cleanup_preserve(input)
251
- result = case input
266
+ case input
252
267
  when Array
253
268
  # If, after replacement, an array contains only the value null remove the value, leaving an empty array.
254
- v = input.map {|o| cleanup_preserve(o)}.compact
255
-
256
- # If the array contains a single member, which is itself an array, use that value as the result
257
- (v.length == 1 && v.first.is_a?(Array)) ? v.first : v
269
+ input.map {|o| cleanup_preserve(o)}
258
270
  when Hash
259
- output = Hash.new
260
- input.each do |key, value|
261
- if key == '@preserve'
262
- # replace all key-value pairs where the key is @preserve with the value from the key-pair
263
- output = cleanup_preserve(value)
264
- else
265
- v = cleanup_preserve(value)
266
-
267
- # Because we may have added a null value to an array, we need to clean that up, if we possible
268
- v = v.first if v.is_a?(Array) && v.length == 1 && !context.as_array?(key)
269
- output[key] = v
271
+ if input.has_key?('@preserve')
272
+ # Replace with the content of `@preserve`
273
+ cleanup_preserve(input['@preserve'].first)
274
+ else
275
+ input.inject({}) do |memo, (k,v)|
276
+ memo.merge(k => cleanup_preserve(v))
270
277
  end
271
278
  end
272
- output
279
+ else
280
+ input
281
+ end
282
+ end
283
+
284
+ ##
285
+ # Replace `@null` with `null`, removing it from arrays.
286
+ #
287
+ # @param [Array, Hash] input
288
+ # @return [Array, Hash]
289
+ def cleanup_null(input)
290
+ result = case input
291
+ when Array
292
+ # If, after replacement, an array contains only the value null remove the value, leaving an empty array.
293
+ input.map {|o| cleanup_null(o)}.compact
294
+ when Hash
295
+ input.inject({}) do |memo, (k,v)|
296
+ memo.merge(k => cleanup_null(v))
297
+ end
273
298
  when '@null'
274
- # If the value from the key-pair is @null, replace the value with nul
299
+ # If the value from the key-pair is @null, replace the value with null
275
300
  nil
276
301
  else
277
302
  input
@@ -304,7 +329,7 @@ module JSON::LD
304
329
  #
305
330
  # Matches either based on explicit type inclusion where the node has any type listed in the frame. If the frame has empty types defined matches nodes not having a @type. If the frame has a type of {} defined matches nodes having any type defined.
306
331
  #
307
- # Otherwise, does duck typing, where the node must have all of the properties defined in the frame.
332
+ # Otherwise, does duck typing, where the node must have any or all of the properties defined in the frame, depending on the `requireAll` flag.
308
333
  #
309
334
  # @param [Hash{String => Object}] subject the subject to check.
310
335
  # @param [Hash{String => Object}] frame the frame to check.
@@ -324,24 +349,39 @@ module JSON::LD
324
349
  ids = v || []
325
350
 
326
351
  # Match on specific @id.
327
- return ids.include?(subject['@id']) if !ids.empty? && ids != [{}]
328
- match_this = true
352
+ match_this = case ids
353
+ when [], [{}]
354
+ # Match on no @id or any @id
355
+ true
356
+ else
357
+ # Match on specific @id
358
+ ids.include?(subject['@id'])
359
+ end
360
+ return match_this if !flags[:requireAll]
329
361
  when '@type'
330
362
  # No longer a wildcard pattern
331
363
  wildcard = false
332
-
364
+
333
365
  match_this = case v
334
366
  when []
335
- # Don't Match on no @type
367
+ # Don't match with any @type
336
368
  return false if !node_values.empty?
337
369
  true
338
370
  when [{}]
339
- # Match on wildcard @type
371
+ # Match with any @type
340
372
  !node_values.empty?
341
373
  else
342
- # Match on specific @type
343
- return !(v & node_values).empty?
374
+ # Treat a map with @default like an empty map
375
+ if v.first.is_a?(Hash) && v.first.keys == %w(@default)
376
+ true
377
+ elsif (v & node_values).empty?
378
+ # Match on specific @type
379
+ false
380
+ else
381
+ true
382
+ end
344
383
  end
384
+ return match_this if !flags[:requireAll]
345
385
  when /@/
346
386
  # Skip other keywords
347
387
  next
@@ -352,7 +392,6 @@ module JSON::LD
352
392
  has_default = v.has_key?('@default')
353
393
  end
354
394
 
355
-
356
395
  # No longer a wildcard pattern if frame has any non-keyword properties
357
396
  wildcard = false
358
397
 
@@ -362,42 +401,40 @@ module JSON::LD
362
401
  # If frame value is empty, don't match if subject has any value
363
402
  return false if !node_values.empty? && is_empty
364
403
 
365
- match_this = case v
366
- when nil
404
+ match_this = case
405
+ when v.nil?
367
406
  # node does not match if values is not empty and the value of property in frame is match none.
368
407
  return false unless node_values.empty?
369
408
  true
370
- when Hash # Empty other than framing keywords
371
- # node matches if values is not empty and the value of property in frame is wildcard
409
+ when v.is_a?(Hash) && (v.keys - FRAMING_KEYWORDS).empty?
410
+ # node matches if values is not empty and the value of property in frame is wildcard (frame with properties other than framing keywords)
372
411
  !node_values.empty?
373
- else
374
- if value?(v)
412
+ when value?(v)
413
+ # Match on any matching value
414
+ node_values.any? {|nv| value_match?(v, nv)}
415
+ when node?(v) || node_reference?(v)
416
+ node_values.any? do |nv|
417
+ node_match?(v, nv, state, flags)
418
+ end
419
+ when list?(v)
420
+ vv = v['@list'].first
421
+ node_values = list?(node_values.first) ?
422
+ node_values.first['@list'] :
423
+ false
424
+ if !node_values
425
+ false # Lists match Lists
426
+ elsif value?(vv)
375
427
  # Match on any matching value
376
- node_values.any? {|nv| value_match?(v, nv)}
377
- elsif node?(v) || node_reference?(v)
428
+ node_values.any? {|nv| value_match?(vv, nv)}
429
+ elsif node?(vv) || node_reference?(vv)
378
430
  node_values.any? do |nv|
379
- node_match?(v, nv, state, flags)
380
- end
381
- elsif list?(v)
382
- vv = v['@list'].first
383
- node_values = list?(node_values.first) ?
384
- node_values.first['@list'] :
385
- false
386
- if !node_values
387
- false # Lists match Lists
388
- elsif value?(vv)
389
- # Match on any matching value
390
- node_values.any? {|nv| value_match?(vv, nv)}
391
- elsif node?(vv) || node_reference?(vv)
392
- node_values.any? do |nv|
393
- node_match?(vv, nv, state, flags)
394
- end
395
- else
396
- false
431
+ node_match?(vv, nv, state, flags)
397
432
  end
398
433
  else
399
- false # No matching on non-value or node values
434
+ false
400
435
  end
436
+ else
437
+ false # No matching on non-value or node values
401
438
  end
402
439
  end
403
440
 
@@ -412,9 +449,18 @@ module JSON::LD
412
449
  end
413
450
 
414
451
  def validate_frame(frame)
415
- raise InvalidFrame::Syntax,
416
- "Invalid JSON-LD syntax; a JSON-LD frame must be an object: #{frame.inspect}" unless
452
+ raise JsonLdError::InvalidFrame,
453
+ "Invalid JSON-LD frame syntax; a JSON-LD frame must be an object: #{frame.inspect}" unless
417
454
  frame.is_a?(Hash) || (frame.is_a?(Array) && frame.first.is_a?(Hash) && frame.length == 1)
455
+ frame = frame.first if frame.is_a?(Array)
456
+
457
+ # Check values of @id and @type
458
+ raise JsonLdError::InvalidFrame,
459
+ "Invalid JSON-LD frame syntax; invalid value of @id: #{frame['@id']}" unless
460
+ Array(frame['@id']) == [{}] || Array(frame['@id']).all?{|v| RDF::URI(v).valid?}
461
+ raise JsonLdError::InvalidFrame,
462
+ "Invalid JSON-LD frame syntax; invalid value of @type: #{frame['@type']}" unless
463
+ Array(frame['@type']).all?{|v| v.is_a?(Hash) && (v.keys - %w(@default)).empty? || RDF::URI(v).valid?}
418
464
  end
419
465
 
420
466
  # Checks the current subject stack to see if embedding the given subject would cause a circular reference.
@@ -443,10 +489,12 @@ module JSON::LD
443
489
  rval = rval.values.first if value?(rval)
444
490
  if name == :embed
445
491
  rval = case rval
446
- when true then '@last'
492
+ when true then '@once'
447
493
  when false then '@never'
448
- when '@always', '@never', '@link' then rval
449
- else '@last'
494
+ when '@always', '@first', '@last', '@link', '@once', '@never' then rval
495
+ else
496
+ raise JsonLdError::InvalidEmbedValue,
497
+ "Invalid JSON-LD frame syntax; invalid value of @embed: #{rval}"
450
498
  end
451
499
  end
452
500
  rval
@@ -542,12 +590,14 @@ module JSON::LD
542
590
  # * @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 `[]`.
543
591
  def value_match?(pattern, value)
544
592
  v1, t1, l1 = value['@value'], value['@type'], value['@language']
545
- v2, t2, l2 = Array(pattern['@value']), Array(pattern['@type']), Array(pattern['@language'])
593
+ v2, t2, l2 = Array(pattern['@value']), Array(pattern['@type']), Array(pattern['@language']).map {|v| v.is_a?(String) ? v.downcase : v}
546
594
  return true if (v2 + t2 + l2).empty?
547
595
  return false unless v2.include?(v1) || v2 == [{}]
548
596
  return false unless t2.include?(t1) || t1 && t2 == [{}] || t1.nil? && (t2 || []).empty?
549
- return false unless l2.include?(l1) || l1 && l2 == [{}] || l1.nil? && (l2 || []).empty?
597
+ return false unless l2.include?(l1.to_s.downcase) || l1 && l2 == [{}] || l1.nil? && (l2 || []).empty?
550
598
  true
551
599
  end
600
+
601
+ FRAMING_KEYWORDS = %w(@default @embed @explicit @omitDefault @requireAll).freeze
552
602
  end
553
603
  end