json-ld 3.1.3 → 3.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,9 +11,9 @@ module JSON::LD
11
11
  # The following constant is used to reduce object allocations
12
12
  CONTAINER_INDEX_ID_TYPE = Set['@index', '@id', '@type'].freeze
13
13
  KEY_ID = %w(@id).freeze
14
- KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction).freeze
14
+ KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction @annotation).freeze
15
15
  KEYS_SET_LIST_INDEX = %w(@set @list @index).freeze
16
- KEYS_INCLUDED_TYPE = %w(@included @type).freeze
16
+ KEYS_INCLUDED_TYPE_REVERSE = %w(@included @type @reverse).freeze
17
17
 
18
18
  ##
19
19
  # Expand an Array or Object given an active context and performing local context expansion.
@@ -21,20 +21,21 @@ module JSON::LD
21
21
  # @param [Array, Hash] input
22
22
  # @param [String] active_property
23
23
  # @param [Context] context
24
- # @param [Boolean] ordered (true)
25
- # Ensure output objects have keys ordered properly
26
24
  # @param [Boolean] framing (false)
27
25
  # Special rules for expanding a frame
28
26
  # @param [Boolean] from_map
29
27
  # Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
28
+ #
30
29
  # @return [Array<Hash{String => Object}>]
31
- def expand(input, active_property, context, ordered: false, framing: false, from_map: false)
32
- log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
30
+ def expand(input, active_property, context,
31
+ framing: false, from_map: false, log_depth: nil)
32
+ log_debug("expand", depth: log_depth.to_i) {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
33
33
  framing = false if active_property == '@default'
34
- expanded_active_property = context.expand_iri(active_property, vocab: true, as_string: true) if active_property
34
+ expanded_active_property = context.expand_iri(active_property, vocab: true, as_string: true, base: @options[:base]) if active_property
35
35
 
36
36
  # Use a term-specific context, if defined, based on the non-type-scoped context.
37
37
  property_scoped_context = context.term_definitions[active_property].context if active_property && context.term_definitions[active_property]
38
+ log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{property_scoped_context.inspect}"} unless property_scoped_context.nil?
38
39
 
39
40
  result = case input
40
41
  when Array
@@ -42,10 +43,19 @@ module JSON::LD
42
43
  is_list = context.container(active_property).include?('@list')
43
44
  value = input.each_with_object([]) do |v, memo|
44
45
  # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
45
- v = expand(v, active_property, context, ordered: ordered, framing: framing, from_map: from_map)
46
+ v = expand(v, active_property, context,
47
+ framing: framing,
48
+ from_map: from_map,
49
+ log_depth: log_depth.to_i + 1)
46
50
 
47
51
  # If the active property is @list or its container mapping is set to @list and v is an array, change it to a list object
48
- v = {"@list" => v} if is_list && v.is_a?(Array)
52
+ if is_list && v.is_a?(Array)
53
+ # Make sure that no member of v contains an annotation object
54
+ raise JsonLdError::InvalidAnnotation,
55
+ "A list element must not contain @annotation." if
56
+ v.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
57
+ v = {"@list" => v}
58
+ end
49
59
 
50
60
  case v
51
61
  when nil then nil
@@ -57,23 +67,29 @@ module JSON::LD
57
67
  value
58
68
  when Hash
59
69
  if context.previous_context
60
- expanded_key_map = input.keys.inject({}) {|memo, key| memo.merge(key => context.expand_iri(key, vocab: true, as_string: true))}
70
+ expanded_key_map = input.keys.inject({}) do |memo, key|
71
+ memo.merge(key => context.expand_iri(key, vocab: true, as_string: true, base: @options[:base]))
72
+ end
61
73
  # Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
62
74
  revert_context = !from_map &&
63
75
  !expanded_key_map.values.include?('@value') &&
64
76
  !(expanded_key_map.values == ['@id'])
65
77
 
66
78
  # If there's a previous context, the context was type-scoped
79
+ log_debug("expand", depth: log_depth.to_i) {"previous_context: #{context.previous_context.inspect}"} if revert_context
67
80
  context = context.previous_context if revert_context
68
81
  end
69
82
 
70
83
  # Apply property-scoped context after reverting term-scoped context
71
- context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context
84
+ unless property_scoped_context.nil?
85
+ context = context.parse(property_scoped_context, base: @options[:base], override_protected: true)
86
+ end
87
+ log_debug("expand", depth: log_depth.to_i) {"after property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
72
88
 
73
89
  # If element contains the key @context, set active context to the result of the Context Processing algorithm, passing active context and the value of the @context key as local context.
74
- if input.has_key?('@context')
75
- context = context.parse(input.delete('@context'))
76
- log_debug("expand") {"context: #{context.inspect}"}
90
+ if input.key?('@context')
91
+ context = context.parse(input.delete('@context'), base: @options[:base])
92
+ log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"}
77
93
  end
78
94
 
79
95
  # Set the type-scoped context to the context on input, for use later
@@ -84,25 +100,28 @@ module JSON::LD
84
100
  # See if keys mapping to @type have terms with a local context
85
101
  type_key = nil
86
102
  input.keys.sort.
87
- select {|k| context.expand_iri(k, vocab: true, quite: true) == '@type'}.
103
+ select {|k| context.expand_iri(k, vocab: true, base: @options[:base]) == '@type'}.
88
104
  each do |tk|
89
105
 
90
106
  type_key ||= tk # Side effect saves the first found key mapping to @type
91
107
  Array(input[tk]).sort.each do |term|
92
108
  term_context = type_scoped_context.term_definitions[term].context if type_scoped_context.term_definitions[term]
93
- context = term_context ? context.parse(term_context, propagate: false) : context
109
+ unless term_context.nil?
110
+ log_debug("expand", depth: log_depth.to_i) {"term_context: #{term_context.inspect}"}
111
+ context = context.parse(term_context, base: @options[:base], propagate: false)
112
+ end
94
113
  end
95
114
  end
96
115
 
97
116
  # Process each key and value in element. Ignores @nesting content
98
117
  expand_object(input, active_property, context, output_object,
99
118
  expanded_active_property: expanded_active_property,
100
- type_scoped_context: type_scoped_context,
119
+ framing: framing,
101
120
  type_key: type_key,
102
- ordered: ordered,
103
- framing: framing)
121
+ type_scoped_context: type_scoped_context,
122
+ log_depth: log_depth.to_i + 1)
104
123
 
105
- log_debug("output object") {output_object.inspect}
124
+ log_debug("output object", depth: log_depth.to_i) {output_object.inspect}
106
125
 
107
126
  # If result contains the key @value:
108
127
  if value?(output_object)
@@ -110,13 +129,13 @@ module JSON::LD
110
129
  unless (keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty?
111
130
  # The result must not contain any keys other than @direction, @value, @language, @type, and @index. It must not contain both the @language key and the @type key. Otherwise, an invalid value object error has been detected and processing is aborted.
112
131
  raise JsonLdError::InvalidValueObject,
113
- "value object has unknown keys: #{output_object.inspect}"
132
+ "value object has unknown keys: #{output_object.inspect}"
114
133
  end
115
134
 
116
135
  if keys.include?('@type') && !(keys & %w(@language @direction)).empty?
117
136
  # @type is inconsistent with either @language or @direction
118
137
  raise JsonLdError::InvalidValueObject,
119
- "value object must not include @type with either @language or @direction: #{output_object.inspect}"
138
+ "value object must not include @type with either @language or @direction: #{output_object.inspect}"
120
139
  end
121
140
 
122
141
  output_object.delete('@language') if output_object.key?('@language') && Array(output_object['@language']).empty?
@@ -129,13 +148,20 @@ module JSON::LD
129
148
 
130
149
  if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1')
131
150
  # Any value of @value is okay if @type: @json
132
- elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.has_key?('@language')
151
+ elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.key?('@language')
133
152
  # Otherwise, if the value of result's @value member is not a string and result contains the key @language, an invalid language-tagged value error has been detected (only strings can be language-tagged) and processing is aborted.
134
153
  raise JsonLdError::InvalidLanguageTaggedValue,
135
154
  "when @language is used, @value must be a string: #{output_object.inspect}"
136
- elsif !Array(output_object['@type']).all? {|t|
137
- t.is_a?(String) && RDF::URI(t).valid? && !t.start_with?('_:') ||
138
- t.is_a?(Hash) && t.empty?}
155
+ elsif output_object['@type'] &&
156
+ (!Array(output_object['@type']).all? {|t|
157
+ t.is_a?(String) && RDF::URI(t).valid? && !t.start_with?('_:') ||
158
+ t.is_a?(Hash) && t.empty?} ||
159
+ !framing && !output_object['@type'].is_a?(String))
160
+ # Otherwise, if the result has a @type member and its value is not an IRI, an invalid typed value error has been detected and processing is aborted.
161
+ raise JsonLdError::InvalidTypedValue,
162
+ "value of @type must be an IRI or '@json': #{output_object.inspect}"
163
+ elsif !framing && !output_object.fetch('@type', '').is_a?(String) &&
164
+ RDF::URI(t).valid? && !t.start_with?('_:')
139
165
  # Otherwise, if the result has a @type member and its value is not an IRI, an invalid typed value error has been detected and processing is aborted.
140
166
  raise JsonLdError::InvalidTypedValue,
141
167
  "value of @type must be an IRI or '@json': #{output_object.inspect}"
@@ -152,6 +178,18 @@ module JSON::LD
152
178
 
153
179
  # If result contains the key @set, then set result to the key's associated value.
154
180
  return output_object['@set'] if output_object.key?('@set')
181
+ elsif output_object['@annotation']
182
+ # Otherwise, if result contains the key @annotation,
183
+ # the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted.
184
+ raise JsonLdError::InvalidAnnotation,
185
+ "@annotation must reference node objects without @id." unless
186
+ output_object['@annotation'].all? {|o| node?(o) && !o.key?('@id')}
187
+
188
+ # Additionally, the property must not be used if there is no active property, or the expanded active property is @graph.
189
+ raise JsonLdError::InvalidAnnotation,
190
+ "@annotation must not be used on a top-level object." if
191
+ %w(@graph @included).include?(expanded_active_property || '@graph')
192
+
155
193
  end
156
194
 
157
195
  # If result contains only the key @language, set result to null.
@@ -161,12 +199,12 @@ module JSON::LD
161
199
  if (expanded_active_property || '@graph') == '@graph' &&
162
200
  (output_object.key?('@value') || output_object.key?('@list') ||
163
201
  (output_object.keys - KEY_ID).empty? && !framing)
164
- log_debug(" =>") { "empty top-level: " + output_object.inspect}
202
+ log_debug(" =>", depth: log_depth.to_i) { "empty top-level: " + output_object.inspect}
165
203
  return nil
166
204
  end
167
205
 
168
206
  # Re-order result keys if ordering
169
- if ordered
207
+ if @options[:ordered]
170
208
  output_object.keys.sort.each_with_object({}) {|kk, memo| memo[kk] = output_object[kk]}
171
209
  else
172
210
  output_object
@@ -176,12 +214,17 @@ module JSON::LD
176
214
  return nil if input.nil? || active_property.nil? || expanded_active_property == '@graph'
177
215
 
178
216
  # Apply property-scoped context
179
- context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context
217
+ unless property_scoped_context.nil?
218
+ context = context.parse(property_scoped_context,
219
+ base: @options[:base],
220
+ override_protected: true)
221
+ end
222
+ log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
180
223
 
181
- context.expand_value(active_property, input, log_depth: @options[:log_depth])
224
+ context.expand_value(active_property, input, base: @options[:base])
182
225
  end
183
226
 
184
- log_debug {" => #{result.inspect}"}
227
+ log_debug(depth: log_depth.to_i) {" => #{result.inspect}"}
185
228
  result
186
229
  end
187
230
 
@@ -190,21 +233,21 @@ module JSON::LD
190
233
  # Expand each key and value of element adding them to result
191
234
  def expand_object(input, active_property, context, output_object,
192
235
  expanded_active_property:,
193
- type_scoped_context:,
236
+ framing:,
194
237
  type_key:,
195
- ordered:,
196
- framing:)
238
+ type_scoped_context:,
239
+ log_depth: nil)
197
240
  nests = []
198
241
 
199
242
  input_type = Array(input[type_key]).last
200
- input_type = context.expand_iri(input_type, vocab: true, as_string: true) if input_type
243
+ input_type = context.expand_iri(input_type, vocab: true, as_string: true, base: @options[:base]) if input_type
201
244
 
202
245
  # Then, proceed and process each property and value in element as follows:
203
- keys = ordered ? input.keys.sort : input.keys
246
+ keys = @options[:ordered] ? input.keys.sort : input.keys
204
247
  keys.each do |key|
205
248
  # For each key and value in element, ordered lexicographically by key:
206
249
  value = input[key]
207
- expanded_property = context.expand_iri(key, vocab: true)
250
+ expanded_property = context.expand_iri(key, vocab: true, base: @options[:base])
208
251
 
209
252
  # If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
210
253
  next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
@@ -215,10 +258,10 @@ module JSON::LD
215
258
  expanded_property.to_s.start_with?("_:") &&
216
259
  context.processingMode('json-ld-1.1')
217
260
 
218
- #log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
261
+ #log_debug("expand property", depth: log_depth.to_i) {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
219
262
 
220
263
  if expanded_property.nil?
221
- #log_debug(" => ") {"skip nil property"}
264
+ #log_debug(" => ", depth: log_depth.to_i) {"skip nil property"}
222
265
  next
223
266
  end
224
267
 
@@ -229,31 +272,51 @@ module JSON::LD
229
272
 
230
273
  # If result has already an expanded property member (other than @type), an colliding keywords error has been detected and processing is aborted.
231
274
  raise JsonLdError::CollidingKeywords,
232
- "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property) && !KEYS_INCLUDED_TYPE.include?(expanded_property)
275
+ "#{expanded_property} already exists in result" if output_object.key?(expanded_property) && !KEYS_INCLUDED_TYPE_REVERSE.include?(expanded_property)
233
276
 
234
277
  expanded_value = case expanded_property
235
278
  when '@id'
279
+ # If expanded active property is `@annotation`, an invalid annotation error has been found and processing is aborted.
280
+ raise JsonLdError::InvalidAnnotation,
281
+ "an annotation must not contain a property expanding to @id" if
282
+ expanded_active_property == '@annotation' && @options[:rdfstar]
283
+
236
284
  # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
237
285
  e_id = case value
238
286
  when String
239
- context.expand_iri(value, documentRelative: true, as_string: true)
287
+ context.expand_iri(value, as_string: true, base: @options[:base], documentRelative: true)
240
288
  when Array
241
289
  # Framing allows an array of IRIs, and always puts values in an array
242
290
  raise JsonLdError::InvalidIdValue,
243
291
  "value of @id must be a string unless framing: #{value.inspect}" unless framing
244
- context.expand_iri(value, documentRelative: true, as_string: true)
292
+ context.expand_iri(value, as_string: true, base: @options[:base], documentRelative: true)
245
293
  value.map do |v|
246
294
  raise JsonLdError::InvalidTypeValue,
247
295
  "@id value must be a string or array of strings for framing: #{v.inspect}" unless v.is_a?(String)
248
- context.expand_iri(v, documentRelative: true, as_string: true)
296
+ context.expand_iri(v, as_string: true, base: @options[:base], documentRelative: true)
249
297
  end
250
298
  when Hash
251
- raise JsonLdError::InvalidIdValue,
252
- "value of @id must be a string unless framing: #{value.inspect}" unless framing
253
- raise JsonLdError::InvalidTypeValue,
254
- "value of @id must be a an empty object for framing: #{value.inspect}" unless
255
- value.empty?
256
- [{}]
299
+ if framing
300
+ raise JsonLdError::InvalidTypeValue,
301
+ "value of @id must be a an empty object for framing: #{value.inspect}" unless
302
+ value.empty?
303
+ [{}]
304
+ elsif @options[:rdfstar]
305
+ # Result must have just a single statement
306
+ rei_node = expand(value, nil, context, log_depth: log_depth.to_i + 1)
307
+
308
+ # Node must not contain @reverse
309
+ raise JsonLdError::InvalidEmbeddedNode,
310
+ "Embedded node with @reverse" if rei_node && rei_node.key?('@reverse')
311
+ statements = to_enum(:item_to_rdf, rei_node)
312
+ raise JsonLdError::InvalidEmbeddedNode,
313
+ "Embedded node with #{statements.size} statements" unless
314
+ statements.count == 1
315
+ rei_node
316
+ else
317
+ raise JsonLdError::InvalidIdValue,
318
+ "value of @id must be a string unless framing: #{value.inspect}" unless framing
319
+ end
257
320
  else
258
321
  raise JsonLdError::InvalidIdValue,
259
322
  "value of @id must be a string or hash if framing: #{value.inspect}"
@@ -268,7 +331,9 @@ module JSON::LD
268
331
  when '@included'
269
332
  # Included blocks are treated as an array of separate object nodes sharing the same referencing active_property. For 1.0, it is skipped as are other unknown keywords
270
333
  next if context.processingMode('json-ld-1.0')
271
- included_result = as_array(expand(value, active_property, context, ordered: ordered, framing: framing))
334
+ included_result = as_array(expand(value, active_property, context,
335
+ framing: framing,
336
+ log_depth: log_depth.to_i + 1))
272
337
 
273
338
  # Expanded values must be node objects
274
339
  raise JsonLdError::InvalidIncludedValue, "values of @included must expand to node objects" unless included_result.all? {|e| node?(e)}
@@ -276,27 +341,39 @@ module JSON::LD
276
341
  Array(output_object['@included']) + included_result
277
342
  when '@type'
278
343
  # If expanded property is @type and value is neither a string nor an array of strings, an invalid type value error has been detected and processing is aborted. Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, true for vocab, and true for document relative to expand the value or each of its items.
279
- #log_debug("@type") {"value: #{value.inspect}"}
344
+ #log_debug("@type", depth: log_depth.to_i) {"value: #{value.inspect}"}
280
345
  e_type = case value
281
346
  when Array
282
347
  value.map do |v|
283
348
  raise JsonLdError::InvalidTypeValue,
284
349
  "@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
285
- type_scoped_context.expand_iri(v, vocab: true, documentRelative: true, as_string: true)
350
+ type_scoped_context.expand_iri(v,
351
+ as_string: true,
352
+ base: @options[:base],
353
+ documentRelative: true,
354
+ vocab: true)
286
355
  end
287
356
  when String
288
- type_scoped_context.expand_iri(value, vocab: true, documentRelative: true, as_string: true)
357
+ type_scoped_context.expand_iri(value,
358
+ as_string: true,
359
+ base: @options[:base],
360
+ documentRelative: true,
361
+ vocab: true)
289
362
  when Hash
290
363
  if !framing
291
364
  raise JsonLdError::InvalidTypeValue,
292
365
  "@type value must be a string or array of strings: #{value.inspect}"
293
366
  elsif value.keys.length == 1 &&
294
- type_scoped_context.expand_iri(value.keys.first, vocab: true) == '@default'
367
+ type_scoped_context.expand_iri(value.keys.first, vocab: true, base: @options[:base]) == '@default'
295
368
  # Expand values of @default, which must be a string, or array of strings expanding to IRIs
296
369
  [{'@default' => Array(value['@default']).map do |v|
297
370
  raise JsonLdError::InvalidTypeValue,
298
371
  "@type default value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
299
- type_scoped_context.expand_iri(v, vocab: true, documentRelative: true, as_string: true)
372
+ type_scoped_context.expand_iri(v,
373
+ as_string: true,
374
+ base: @options[:base],
375
+ documentRelative: true,
376
+ vocab: true)
300
377
  end}]
301
378
  elsif !value.empty?
302
379
  raise JsonLdError::InvalidTypeValue,
@@ -314,7 +391,9 @@ module JSON::LD
314
391
  framing || e_type.length > 1 ? e_type : e_type.first
315
392
  when '@graph'
316
393
  # If expanded property is @graph, set expanded value to the result of using this algorithm recursively passing active context, @graph for active property, and value for element.
317
- value = expand(value, '@graph', context, ordered: ordered, framing: framing)
394
+ value = expand(value, '@graph', context,
395
+ framing: framing,
396
+ log_depth: log_depth.to_i + 1)
318
397
  as_array(value)
319
398
  when '@value'
320
399
  # If expanded property is @value and input contains @type: json, accept any value.
@@ -406,15 +485,24 @@ module JSON::LD
406
485
  next if (expanded_active_property || '@graph') == '@graph'
407
486
 
408
487
  # Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
409
- value = expand(value, active_property, context, ordered: ordered, framing: framing)
488
+ value = expand(value, active_property, context,
489
+ framing: framing,
490
+ log_depth: log_depth.to_i + 1)
410
491
 
411
492
  # Spec FIXME: need to be sure that result is an array
412
493
  value = as_array(value)
413
494
 
495
+ # Make sure that no member of value contains an annotation object
496
+ raise JsonLdError::InvalidAnnotation,
497
+ "A list element must not contain @annotation." if
498
+ value.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
499
+
414
500
  value
415
501
  when '@set'
416
502
  # If expanded property is @set, set expanded value to the result of using this algorithm recursively, passing active context, active property, and value for element.
417
- expand(value, active_property, context, ordered: ordered, framing: framing)
503
+ expand(value, active_property, context,
504
+ framing: framing,
505
+ log_depth: log_depth.to_i + 1)
418
506
  when '@reverse'
419
507
  # If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
420
508
  raise JsonLdError::InvalidReverseValue,
@@ -422,11 +510,13 @@ module JSON::LD
422
510
 
423
511
  # Otherwise
424
512
  # Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
425
- value = expand(value, '@reverse', context, ordered: ordered, framing: framing)
513
+ value = expand(value, '@reverse', context,
514
+ framing: framing,
515
+ log_depth: log_depth.to_i + 1)
426
516
 
427
517
  # If expanded value contains an @reverse member, i.e., properties that are reversed twice, execute for each of its property and item the following steps:
428
- if value.has_key?('@reverse')
429
- #log_debug("@reverse") {"double reverse: #{value.inspect}"}
518
+ if value.key?('@reverse')
519
+ #log_debug("@reverse", depth: log_depth.to_i) {"double reverse: #{value.inspect}"}
430
520
  value['@reverse'].each do |property, item|
431
521
  # If result does not have a property member, create one and set its value to an empty array.
432
522
  # Append item to the value of the property member of result.
@@ -455,19 +545,28 @@ module JSON::LD
455
545
  when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
456
546
  next unless framing
457
547
  # Framing keywords
458
- [expand(value, expanded_property, context, ordered: ordered, framing: framing)].flatten
548
+ [expand(value, expanded_property, context,
549
+ framing: framing,
550
+ log_depth: log_depth.to_i + 1)
551
+ ].flatten
459
552
  when '@nest'
460
553
  # Add key to nests
461
554
  nests << key
462
555
  # Continue with the next key from element
463
556
  next
557
+ when '@annotation'
558
+ # Skip unless rdfstar option is set
559
+ next unless @options[:rdfstar]
560
+ as_array(expand(value, '@annotation', context,
561
+ framing: framing,
562
+ log_depth: log_depth.to_i + 1))
464
563
  else
465
564
  # Skip unknown keyword
466
565
  next
467
566
  end
468
567
 
469
568
  # Unless expanded value is null, set the expanded property member of result to expanded value.
470
- #log_debug("expand #{expanded_property}") { expanded_value.inspect}
569
+ #log_debug("expand #{expanded_property}", depth: log_depth.to_i) { expanded_value.inspect}
471
570
  output_object[expanded_property] = expanded_value unless expanded_value.nil? && expanded_property == '@value' && input_type != '@json'
472
571
  next
473
572
  end
@@ -483,9 +582,9 @@ module JSON::LD
483
582
  ary = []
484
583
 
485
584
  # For each key-value pair language-language value in value, ordered lexicographically by language
486
- keys = ordered ? value.keys.sort : value.keys
585
+ keys = @options[:ordered] ? value.keys.sort : value.keys
487
586
  keys.each do |k|
488
- expanded_k = context.expand_iri(k, vocab: true, as_string: true)
587
+ expanded_k = context.expand_iri(k, vocab: true, as_string: true, base: @options[:base])
489
588
 
490
589
  if k !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_k != '@none'
491
590
  warn "@language must be valid BCP47: #{k.inspect}"
@@ -517,23 +616,35 @@ module JSON::LD
517
616
  context.previous_context
518
617
  elsif container.include?('@id') && context.term_definitions[key]
519
618
  id_context = context.term_definitions[key].context if context.term_definitions[key]
520
- id_context ? context.parse(id_context, propagate: false) : context
619
+ if id_context.nil?
620
+ context
621
+ else
622
+ log_debug("expand", depth: log_depth.to_i) {"id_context: #{id_context.inspect}"}
623
+ context.parse(id_context, base: @options[:base], propagate: false)
624
+ end
521
625
  else
522
626
  context
523
627
  end
524
628
 
525
629
  # For each key-value in the object:
526
- keys = ordered ? value.keys.sort : value.keys
630
+ keys = @options[:ordered] ? value.keys.sort : value.keys
527
631
  keys.each do |k|
528
632
  # If container mapping in the active context includes @type, and k is a term in the active context having a local context, use that context when expanding values
529
633
  map_context = container_context.term_definitions[k].context if container.include?('@type') && container_context.term_definitions[k]
530
- map_context = container_context.parse(map_context, propagate: false) if map_context
634
+ unless map_context.nil?
635
+ log_debug("expand", depth: log_depth.to_i) {"map_context: #{map_context.inspect}"}
636
+ map_context = container_context.parse(map_context, base: @options[:base],
637
+ propagate: false)
638
+ end
531
639
  map_context ||= container_context
532
640
 
533
- expanded_k = container_context.expand_iri(k, vocab: true, as_string: true)
641
+ expanded_k = container_context.expand_iri(k, vocab: true, as_string: true, base: @options[:base])
534
642
 
535
643
  # Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
536
- index_value = expand([value[k]].flatten, key, map_context, ordered: ordered, framing: framing, from_map: true)
644
+ index_value = expand([value[k]].flatten, key, map_context,
645
+ framing: framing,
646
+ from_map: true,
647
+ log_depth: log_depth.to_i + 1)
537
648
  index_value.each do |item|
538
649
  case
539
650
  when container.include?('@index')
@@ -547,8 +658,8 @@ module JSON::LD
547
658
  raise JsonLdError::InvalidValueObject, "Attempt to add illegal key to value object: #{index_key}"
548
659
  else
549
660
  # Expand key based on term
550
- expanded_k = k == '@none' ? '@none' : container_context.expand_value(index_key, k)
551
- index_property = container_context.expand_iri(index_key, vocab: true, as_string: true)
661
+ expanded_k = k == '@none' ? '@none' : container_context.expand_value(index_key, k, base: @options[:base])
662
+ index_property = container_context.expand_iri(index_key, vocab: true, as_string: true, base: @options[:base])
552
663
  item[index_property] = [expanded_k].concat(Array(item[index_property])) unless expanded_k == '@none'
553
664
  end
554
665
  when container.include?('@id')
@@ -557,7 +668,7 @@ module JSON::LD
557
668
  item = {'@graph' => as_array(item)}
558
669
  end
559
670
  # Expand k document relative
560
- expanded_k = container_context.expand_iri(k, documentRelative: true, as_string: true) unless expanded_k == '@none'
671
+ expanded_k = container_context.expand_iri(k, as_string: true, base: @options[:base], documentRelative: true) unless expanded_k == '@none'
561
672
  item['@id'] ||= expanded_k unless expanded_k == '@none'
562
673
  when container.include?('@type')
563
674
  item['@type'] = [expanded_k].concat(Array(item['@type'])) unless expanded_k == '@none'
@@ -570,26 +681,28 @@ module JSON::LD
570
681
  ary
571
682
  else
572
683
  # Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
573
- expand(value, key, context, ordered: ordered, framing: framing)
684
+ expand(value, key, context,
685
+ framing: framing,
686
+ log_depth: log_depth.to_i + 1)
574
687
  end
575
688
 
576
689
  # If expanded value is null, ignore key by continuing to the next key from element.
577
690
  if expanded_value.nil?
578
- #log_debug(" => skip nil value")
691
+ #log_debug(" => skip nil value", depth: log_depth.to_i)
579
692
  next
580
693
  end
581
- #log_debug {" => #{expanded_value.inspect}"}
694
+ #log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
582
695
 
583
696
  # If the container mapping associated to key in active context is @list and expanded value is not already a list object, convert expanded value to a list object by first setting it to an array containing only expanded value if it is not already an array, and then by setting it to a JSON object containing the key-value pair @list-expanded value.
584
697
  if container.first == '@list' && container.length == 1 && !list?(expanded_value)
585
- #log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
698
+ #log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
586
699
  expanded_value = {'@list' => as_array(expanded_value)}
587
700
  end
588
- #log_debug {" => #{expanded_value.inspect}"}
701
+ #log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
589
702
 
590
703
  # convert expanded value to @graph if container specifies it
591
704
  if container.first == '@graph' && container.length == 1
592
- #log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
705
+ #log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
593
706
  expanded_value = as_array(expanded_value).map do |v|
594
707
  {'@graph' => as_array(v)}
595
708
  end
@@ -626,17 +739,23 @@ module JSON::LD
626
739
  # For each key in nests, recusively expand content
627
740
  nests.each do |key|
628
741
  nest_context = context.term_definitions[key].context if context.term_definitions[key]
629
- nest_context = nest_context ? context.parse(nest_context, override_protected: true) : context
742
+ nest_context = if nest_context.nil?
743
+ context
744
+ else
745
+ log_debug("expand", depth: log_depth.to_i) {"nest_context: #{nest_context.inspect}"}
746
+ context.parse(nest_context, base: @options[:base],
747
+ override_protected: true)
748
+ end
630
749
  nested_values = as_array(input[key])
631
750
  nested_values.each do |nv|
632
751
  raise JsonLdError::InvalidNestValue, nv.inspect unless
633
- nv.is_a?(Hash) && nv.keys.none? {|k| nest_context.expand_iri(k, vocab: true) == '@value'}
752
+ nv.is_a?(Hash) && nv.keys.none? {|k| nest_context.expand_iri(k, vocab: true, base: @options[:base]) == '@value'}
634
753
  expand_object(nv, active_property, nest_context, output_object,
754
+ framing: framing,
635
755
  expanded_active_property: expanded_active_property,
636
- type_scoped_context: type_scoped_context,
637
756
  type_key: type_key,
638
- ordered: ordered,
639
- framing: framing)
757
+ type_scoped_context: type_scoped_context,
758
+ log_depth: log_depth.to_i + 1)
640
759
  end
641
760
  end
642
761
  end