json-ld 3.0.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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