json-ld 2.2.1 → 3.0.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.
@@ -30,7 +30,8 @@ module JSON::LD
30
30
  # If element has a single member and the active property has no
31
31
  # @container mapping to @list or @set, the compacted value is that
32
32
  # member; otherwise the compacted value is element
33
- if result.length == 1 && !context.as_array?(property) && @options[:compactArrays]
33
+ if result.length == 1 &&
34
+ !context.as_array?(property) && @options[:compactArrays]
34
35
  #log_debug("=> extract single element: #{result.first.inspect}")
35
36
  result.first
36
37
  else
@@ -51,26 +52,34 @@ module JSON::LD
51
52
  end
52
53
  end
53
54
 
55
+ # If expanded property is @list and we're contained within a list container, recursively compact this item to an array
56
+ if list?(element) && context.container(property) == %w(@list)
57
+ return compact(element['@list'], property: property)
58
+ end
59
+
60
+
54
61
  inside_reverse = property == '@reverse'
55
62
  result, nest_result = {}, nil
56
63
 
64
+ # Apply any context defined on an alias of @type
65
+ # If key is @type and any compacted value is a term having a local context, overlay that context.
66
+ Array(element['@type']).
67
+ map {|expanded_type| context.compact_iri(expanded_type, vocab: true)}.
68
+ sort.
69
+ each do |term|
70
+ term_context = self.context.term_definitions[term].context if context.term_definitions[term]
71
+ self.context = context.parse(term_context) if term_context
72
+ end
73
+
57
74
  element.keys.sort.each do |expanded_property|
58
75
  expanded_value = element[expanded_property]
59
76
  #log_debug("") {"#{expanded_property}: #{expanded_value.inspect}"}
60
77
 
61
78
  if expanded_property == '@id' || expanded_property == '@type'
62
- compacted_value = [expanded_value].flatten.compact.map do |expanded_type|
79
+ compacted_value = Array(expanded_value).map do |expanded_type|
63
80
  context.compact_iri(expanded_type, vocab: (expanded_property == '@type'), log_depth: @options[:log_depth])
64
81
  end
65
82
 
66
- # If key is @type and any compacted value is a term having a local context, overlay that context.
67
- if expanded_property == '@type'
68
- compacted_value.each do |term|
69
- term_context = self.context.term_definitions[term].context if context.term_definitions[term]
70
- self.context = context.parse(term_context) if term_context
71
- end
72
- end
73
-
74
83
  compacted_value = compacted_value.first if compacted_value.length == 1
75
84
 
76
85
  al = context.compact_iri(expanded_property, vocab: true, quiet: true)
@@ -85,11 +94,8 @@ module JSON::LD
85
94
  # handle double-reversed properties
86
95
  compacted_value.each do |prop, value|
87
96
  if context.reverse?(prop)
88
- value = [value] if !value.is_a?(Array) &&
89
- (context.as_array?(prop) || !@options[:compactArrays])
90
- #log_debug("") {"merge #{prop} => #{value.inspect}"}
91
-
92
- merge_compacted_value(result, prop, value)
97
+ add_value(result, prop, value,
98
+ property_is_array: context.as_array?(prop) || !@options[:compactArrays])
93
99
  compacted_value.delete(prop)
94
100
  end
95
101
  end
@@ -136,11 +142,11 @@ module JSON::LD
136
142
 
137
143
  if nest_prop = context.nest(item_active_property)
138
144
  result[nest_prop] ||= {}
139
- iap = result[result[nest_prop]] ||= []
140
- result[nest_prop][item_active_property] = [iap] unless iap.is_a?(Array)
145
+ add_value(result[nest_prop], item_active_property, [],
146
+ property_is_array: true)
141
147
  else
142
- iap = result[item_active_property] ||= []
143
- result[item_active_property] = [iap] unless iap.is_a?(Array)
148
+ add_value(result, item_active_property, [],
149
+ property_is_array: true)
144
150
  end
145
151
  end
146
152
 
@@ -162,7 +168,7 @@ module JSON::LD
162
168
  end
163
169
 
164
170
  container = context.container(item_active_property)
165
- as_array = context.as_array?(item_active_property)
171
+ as_array = !@options[:compactArrays] || context.as_array?(item_active_property)
166
172
 
167
173
  value = case
168
174
  when list?(expanded_item) then expanded_item['@list']
@@ -175,7 +181,7 @@ module JSON::LD
175
181
 
176
182
  # handle @list
177
183
  if list?(expanded_item)
178
- compacted_item = [compacted_item] unless compacted_item.is_a?(Array)
184
+ compacted_item = as_array(compacted_item)
179
185
  unless container == %w(@list)
180
186
  al = context.compact_iri('@list', vocab: true, quiet: true)
181
187
  compacted_item = {al => compacted_item}
@@ -184,36 +190,35 @@ module JSON::LD
184
190
  compacted_item[key] = expanded_item['@index']
185
191
  end
186
192
  else
187
- raise JsonLdError::CompactionToListOfLists,
188
- "key cannot have more than one list value" if nest_result.has_key?(item_active_property)
189
- # Falls through to add list value below
193
+ add_value(nest_result, item_active_property, compacted_item,
194
+ value_is_array: true, allow_duplicate: true)
195
+ next
190
196
  end
191
197
  end
192
198
 
193
199
  # Graph object compaction cases:
194
200
  if graph?(expanded_item)
195
- if container.include?('@graph') && container.include?('@id')
201
+ if container.include?('@graph') &&
202
+ (container.include?('@id') || container.include?('@index') && simple_graph?(expanded_item))
196
203
  # container includes @graph and @id
197
204
  map_object = nest_result[item_active_property] ||= {}
198
- map_key = expanded_item['@id']
199
205
  # If there is no @id, create a blank node identifier to use as an index
200
- map_key = map_key ? context.compact_iri(map_key, quiet: true) : namer.get_name
201
- merge_compacted_value(map_object, map_key, compacted_item)
202
- elsif container.include?('@graph') && container.include?('@index') && simple_graph?(expanded_item)
203
- # container includes @graph and @index and value is a simple graph object
204
- map_object = nest_result[item_active_property] ||= {}
205
- # If there is no @index, use @none
206
- map_key = expanded_item['@index'] || '@none'
207
- merge_compacted_value(map_object, map_key, compacted_item)
206
+ map_key = if container.include?('@id') && expanded_item['@id']
207
+ context.compact_iri(expanded_item['@id'], quiet: true)
208
+ elsif container.include?('@index') && expanded_item['@index']
209
+ context.compact_iri(expanded_item['@index'], quiet: true)
210
+ else
211
+ context.compact_iri('@none', vocab: true, quiet: true)
212
+ end
213
+ add_value(map_object, map_key, compacted_item,
214
+ property_is_array: as_array)
208
215
  elsif container.include?('@graph') && simple_graph?(expanded_item)
209
216
  # container includes @graph but not @id or @index and value is a simple graph object
210
217
  # Drop through, where compacted_value will be added
211
- compacted_item = [compacted_item] if
212
- !compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
213
- merge_compacted_value(nest_result, item_active_property, compacted_item)
218
+ add_value(nest_result, item_active_property, compacted_item,
219
+ property_is_array: as_array)
214
220
  else
215
221
  # container does not include @graph or otherwise does not match one of the previous cases, redo compacted_item
216
- compacted_item = [compacted_item]
217
222
  al = context.compact_iri('@graph', vocab: true, quiet: true)
218
223
  compacted_item = {al => compacted_item}
219
224
  if expanded_item['@id']
@@ -224,47 +229,46 @@ module JSON::LD
224
229
  key = context.compact_iri('@index', vocab: true, quiet: true)
225
230
  compacted_item[key] = expanded_item['@index']
226
231
  end
227
- compacted_item = [compacted_item] if !@options[:compactArrays] || as_array
228
- merge_compacted_value(nest_result, item_active_property, compacted_item)
232
+ add_value(nest_result, item_active_property, compacted_item,
233
+ property_is_array: as_array)
229
234
  end
230
235
  elsif !(container & %w(@language @index @id @type)).empty? && !container.include?('@graph')
231
236
  map_object = nest_result[item_active_property] ||= {}
237
+ c = container.first
238
+ container_key = context.compact_iri(c, vocab: true, quiet: true)
232
239
  compacted_item = case container
233
240
  when %w(@id)
234
- id_prop = context.compact_iri('@id', vocab: true, quiet: true)
235
- map_key = compacted_item[id_prop]
236
- map_key = context.compact_iri(map_key, quiet: true)
237
- compacted_item.delete(id_prop)
241
+ map_key = compacted_item[container_key]
242
+ compacted_item.delete(container_key)
238
243
  compacted_item
239
244
  when %w(@index)
240
245
  map_key = expanded_item['@index']
246
+ compacted_item.delete(container_key) if compacted_item.is_a?(Hash)
241
247
  compacted_item
242
248
  when %w(@language)
243
249
  map_key = expanded_item['@language']
244
250
  value?(expanded_item) ? expanded_item['@value'] : compacted_item
245
251
  when %w(@type)
246
- type_prop = context.compact_iri('@type', vocab: true, quiet: true)
247
- map_key, *types = Array(compacted_item[type_prop])
248
- map_key = context.compact_iri(map_key, vocab: true, quiet: true)
252
+ map_key, *types = Array(compacted_item[container_key])
249
253
  case types.length
250
- when 0 then compacted_item.delete(type_prop)
251
- when 1 then compacted_item[type_prop] = types.first
252
- else compacted_item[type_prop] = types
254
+ when 0 then compacted_item.delete(container_key)
255
+ when 1 then compacted_item[container_key] = types.first
256
+ else compacted_item[container_key] = types
253
257
  end
254
258
  compacted_item
255
259
  end
256
- compacted_item = [compacted_item] if as_array && !compacted_item.is_a?(Array)
257
- merge_compacted_value(map_object, map_key, compacted_item)
260
+ map_key ||= context.compact_iri('@none', vocab: true, quiet: true)
261
+ add_value(map_object, map_key, compacted_item,
262
+ property_is_array: as_array)
258
263
  else
259
- compacted_item = [compacted_item] if
260
- !compacted_item.is_a?(Array) && (!@options[:compactArrays] || as_array)
261
- merge_compacted_value(nest_result, item_active_property, compacted_item)
264
+ add_value(nest_result, item_active_property, compacted_item,
265
+ property_is_array: as_array)
262
266
  end
263
267
  end
264
268
  end
265
269
 
266
270
  # Re-order result keys
267
- result.keys.kw_sort.each_with_object({}) {|kk, memo| memo[kk] = result[kk]}
271
+ result.keys.sort.each_with_object({}) {|kk, memo| memo[kk] = result[kk]}
268
272
  else
269
273
  # For other types, the compacted value is the element value
270
274
  #log_debug("compact") {element.class.to_s}
@@ -336,15 +336,15 @@ module JSON::LD
336
336
  end
337
337
 
338
338
  # If contex has a @version member, it's value MUST be 1.1, otherwise an "invalid @version value" has been detected, and processing is aborted.
339
- # If processingMode has been set, and "json-ld-1.1" is not a prefix of processingMode , a "processing mode conflict" has been detecting, and processing is aborted.
339
+ # If processingMode has been set, and it is not "json-ld-1.1", a "processing mode conflict" has been detecting, and processing is aborted.
340
340
  # @param [Number] vaule must be a decimal number
341
341
  def version=(value)
342
342
  case value
343
343
  when 1.1
344
- if processingMode && !processingMode.start_with?("json-ld-1.1")
344
+ if processingMode && processingMode < "json-ld-1.1"
345
345
  raise JsonLdError::ProcessingModeConflict, "#{value} not compatible with #{processingMode}"
346
346
  end
347
- @processingMode ||= "json-ld-1.1"
347
+ @processingMode = "json-ld-1.1"
348
348
  else
349
349
  raise JsonLdError::InvalidVersionValue, value
350
350
  end
@@ -357,13 +357,13 @@ module JSON::LD
357
357
  when /_:/
358
358
  value
359
359
  when String, RDF::URI
360
- v = as_resource(value.to_s)
361
- raise JsonLdError::InvalidVocabMapping, "@vocab must be an absolute IRI: #{value.inspect}" if v.uri? && v.relative? && @options[:validate]
360
+ v = as_resource(value.to_s, base)
361
+ raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}" if !v.valid? && @options[:validate]
362
362
  v
363
363
  when nil
364
364
  nil
365
365
  else
366
- raise JsonLdError::InvalidVocabMapping, "@vocab must be an absolute IRI: #{value.inspect}"
366
+ raise JsonLdError::InvalidVocabMapping, "@vocab must be an IRI: #{value.inspect}"
367
367
  end
368
368
  end
369
369
 
@@ -385,7 +385,7 @@ module JSON::LD
385
385
  result = self.dup
386
386
  result.provided_context = local_context if self.empty?
387
387
 
388
- local_context = [local_context] unless local_context.is_a?(Array)
388
+ local_context = as_array(local_context)
389
389
 
390
390
  local_context.each do |context|
391
391
  case context
@@ -419,6 +419,7 @@ module JSON::LD
419
419
 
420
420
  raise JsonLdError::RecursiveContextInclusion, "#{context}" if remote_contexts.include?(context.to_s)
421
421
  remote_contexts << context.to_s
422
+ raise JsonLdError::ContextOverflow, "#{context}" if remote_contexts.length >= MAX_CONTEXTS_LOADED
422
423
 
423
424
  context_no_base = result.dup
424
425
  context_no_base.base = nil
@@ -448,9 +449,6 @@ module JSON::LD
448
449
  end
449
450
  raise JsonLdError::InvalidRemoteContext, "#{context}" unless jo.is_a?(Hash) && jo.has_key?('@context')
450
451
  context = jo['@context']
451
- if (processingMode || 'json-ld-1.0') <= "json-ld-1.1"
452
- context_no_base.provided_context = context.dup
453
- end
454
452
  end
455
453
  rescue JsonLdError::LoadingDocumentFailed => e
456
454
  #log_debug("parse") {"Failed to retrieve @context from remote document at #{context_no_base.context_base.inspect}: #{e.message}"}
@@ -464,6 +462,7 @@ module JSON::LD
464
462
 
465
463
  # 3.2.6) Set context to the result of recursively calling this algorithm, passing context no base for active context, context for local context, and remote contexts.
466
464
  context = context_no_base.parse(context, remote_contexts.dup)
465
+ PRELOADED[context_canon.to_s] = context
467
466
  context.provided_context = result.provided_context
468
467
  end
469
468
  context.base ||= result.base
@@ -486,11 +485,9 @@ module JSON::LD
486
485
  end
487
486
  end
488
487
 
489
- # If not set explicitly, set processingMode to "json-ld-1.0"
490
- result.processingMode ||= "json-ld-1.0"
491
-
492
488
  defined = {}
493
- # For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
489
+
490
+ # For each key-value pair in context invoke the Create Term Definition subalgorithm, passing result for active context, context for local context, key, and defined
494
491
  context.each_key do |key|
495
492
  result.create_term_definition(context, key, defined)
496
493
  end
@@ -651,10 +648,10 @@ module JSON::LD
651
648
  raise JsonLdError::InvalidKeywordAlias, "expected value of @id to not be @context on term #{term.inspect}" if
652
649
  definition.id == '@context'
653
650
 
654
- # If id ends with a gen-delim, it may be used as a prefix
655
- definition.prefix = true if !term.include?(':') &&
656
- definition.id.to_s.end_with?(*%w(: / ? # [ ] @)) &&
657
- (simple_term || ((processingMode || 'json-ld-1.0') == 'json-ld-1.0'))
651
+ # If id ends with a gen-delim, it may be used as a prefix for simple terms
652
+ definition.prefix = true if !term.include?(':') &&
653
+ definition.id.to_s.end_with?(*%w(: / ? # [ ] @)) &&
654
+ simple_term
658
655
  elsif term.include?(':')
659
656
  # If term is a compact IRI with a prefix that is a key in local context then a dependency has been found. Use this algorithm recursively passing active context, local context, the prefix as term, and defined.
660
657
  prefix, suffix = term.split(':', 2)
@@ -735,7 +732,7 @@ module JSON::LD
735
732
  #
736
733
  # @param [Hash{Symbol => Object}] options ({})
737
734
  # @return [Hash]
738
- def serialize(options = {})
735
+ def serialize(**options)
739
736
  # FIXME: not setting provided_context now
740
737
  use_context = case provided_context
741
738
  when String, RDF::URI
@@ -994,8 +991,14 @@ module JSON::LD
994
991
  create_term_definition(local_context, value, defined)
995
992
  end
996
993
 
994
+ if (v_td = term_definitions[value]) && KEYWORDS.include?(v_td.id)
995
+ #log_debug("") {"match with #{v_td.id}"} unless quiet
996
+ return v_td.id
997
+ end
998
+
999
+ # If active context has a term definition for value, and the associated mapping is a keyword, return that keyword.
997
1000
  # If vocab is true and the active context has a term definition for value, return the associated IRI mapping.
998
- if vocab && (v_td = term_definitions[value])
1001
+ if (v_td = term_definitions[value]) && (vocab || KEYWORDS.include?(v_td.id))
999
1002
  #log_debug("") {"match with #{v_td.id}"} unless quiet
1000
1003
  return v_td.id
1001
1004
  end
@@ -1030,7 +1033,7 @@ module JSON::LD
1030
1033
  result = if vocab && self.vocab
1031
1034
  # If vocab is true, and active context has a vocabulary mapping, return the result of concatenating the vocabulary mapping with value.
1032
1035
  self.vocab + value
1033
- elsif documentRelative && (base ||= self.base)
1036
+ elsif (documentRelative || self.vocab == '') && (base ||= self.base)
1034
1037
  # Otherwise, if document relative is true, set value to the result of resolving value against the base IRI. Only the basic algorithm in section 5.2 of [RFC3986] is used; neither Syntax-Based Normalization nor Scheme-Based Normalization are performed. Characters additionally allowed in IRI references are treated in the same way that unreserved characters are treated in URI references, per section 6.5 of [RFC3987].
1035
1038
  value = RDF::URI(value)
1036
1039
  value.absolute? ? value : RDF::URI(base).join(value)
@@ -1069,15 +1072,11 @@ module JSON::LD
1069
1072
  default_language = self.default_language || "@none"
1070
1073
  containers = []
1071
1074
  tl, tl_value = "@language", "@null"
1075
+ containers.concat(%w(@index @index@set)) if index?(value) && !graph?(value)
1072
1076
 
1073
1077
  # If the value is a JSON Object with the key @preserve, use the value of @preserve.
1074
1078
  value = value['@preserve'].first if value.is_a?(Hash) && value.has_key?('@preserve')
1075
1079
 
1076
- # If the value is a JSON Object, then for the keywords @index, @id, and @type, if the value contains that keyword, append it to containers.
1077
- %w(@index @id @type).each do |kw|
1078
- containers << kw if value.has_key?(kw)
1079
- end if value.is_a?(Hash)
1080
-
1081
1080
  if reverse
1082
1081
  tl, tl_value = "@type", "@reverse"
1083
1082
  containers << '@set'
@@ -1123,20 +1122,30 @@ module JSON::LD
1123
1122
  end
1124
1123
  #log_debug("") {"list: containers: #{containers.inspect}, type/language: #{tl.inspect}, type/language value: #{tl_value.inspect}"} unless quiet
1125
1124
  elsif graph?(value)
1126
- # TODO: support `@graphId`?
1127
- # TODO: "@graph@set"?
1128
- containers << '@graph'
1129
- containers << '@set'
1125
+ # Prefer @index and @id containers, then @graph, then @index
1126
+ containers.concat(%w(@graph@index @graph@index@set @index @index@set)) if index?(value)
1127
+ containers.concat(%w(@graph@id @graph@id@set)) if value.has_key?('@id')
1128
+
1129
+ # Prefer an @graph container next
1130
+ containers.concat(%w(@graph @graph@set @set))
1131
+
1132
+ # Lastly, in 1.1, any graph can be indexed on @index or @id, so add if we haven't already
1133
+ containers.concat(%w(@graph@index @graph@index@set)) unless index?(value)
1134
+ containers.concat(%w(@graph@id @graph@id@set)) unless value.has_key?('@id')
1135
+ containers.concat(%w(@index @index@set)) unless index?(value)
1130
1136
  else
1131
1137
  if value?(value)
1138
+ # In 1.1, an language map can be used to index values using @none
1132
1139
  if value.has_key?('@language') && !index?(value)
1133
1140
  tl_value = value['@language']
1134
- containers << '@language'
1141
+ containers.concat(%w(@language @language@set))
1135
1142
  elsif value.has_key?('@type')
1136
1143
  tl_value = value['@type']
1137
1144
  tl = '@type'
1138
1145
  end
1139
1146
  else
1147
+ # In 1.1, an id or type map can be used to index values using @none
1148
+ containers.concat(%w(@id @id@set @type @set@type))
1140
1149
  tl, tl_value = '@type', '@id'
1141
1150
  end
1142
1151
  containers << '@set'
@@ -1144,6 +1153,12 @@ module JSON::LD
1144
1153
  end
1145
1154
 
1146
1155
  containers << '@none'
1156
+
1157
+ # In 1.1, an index map can be used to index values using @none, so add as a low priority
1158
+ containers.concat(%w(@index @index@set)) unless index?(value)
1159
+ # Values without type or language can use @language map
1160
+ containers.concat(%w(@language @language@set)) if value?(value) && value.keys == %w(@value)
1161
+
1147
1162
  tl_value ||= '@null'
1148
1163
  preferred_values = []
1149
1164
  preferred_values << '@reverse' if tl_value == '@reverse'
@@ -1306,7 +1321,7 @@ module JSON::LD
1306
1321
  # @raise [JsonLdError] if the iri cannot be expanded
1307
1322
  # @see http://json-ld.org/spec/latest/json-ld-api/#value-compaction
1308
1323
  # FIXME: revisit the specification version of this.
1309
- def compact_value(property, value, options = {})
1324
+ def compact_value(property, value, **options)
1310
1325
  #log_debug("compact_value") {"property: #{property.inspect}, value: #{value.inspect}"}
1311
1326
 
1312
1327
  num_members = value.length
@@ -1521,14 +1536,11 @@ module JSON::LD
1521
1536
  end.each do |term|
1522
1537
  next unless td = term_definitions[term]
1523
1538
 
1524
- container = Array(td.container_mapping).sort.first
1525
- container ||= td.as_set? ? %(@set) : %(@none)
1526
- # FIXME: Alternative to consider
1527
- ## Creates "@language", "@language@set", "@set", or "@none"
1528
- ## for each of "@language", "@index", "@type", "@id", "@list", and "@graph"
1529
- #container = td.container_mapping.to_s
1530
- #container += '@set' if td.as_set?
1531
- #container = '@none' if container.empty?
1539
+ container = td.container_mapping.join('')
1540
+ if container.empty?
1541
+ container = td.as_set? ? %(@set) : %(@none)
1542
+ end
1543
+
1532
1544
  container_map = result[td.id.to_s] ||= {}
1533
1545
  tl_map = container_map[container] ||= {'@language' => {}, '@type' => {}, '@any' => {}}
1534
1546
  type_map = tl_map['@type']
@@ -16,22 +16,23 @@ module JSON::LD
16
16
  # @param [Context] context
17
17
  # @param [Boolean] ordered (true)
18
18
  # Ensure output objects have keys ordered properly
19
+ # @param [Boolean] framing (false)
20
+ # Special rules for expanding a frame
19
21
  # @return [Array<Hash{String => Object}>]
20
- def expand(input, active_property, context, ordered: true)
21
- framing = @options[:processingMode].include?("expand-frame")
22
+ def expand(input, active_property, context, ordered: true, framing: false)
22
23
  #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
24
+ framing = false if active_property == '@default'
23
25
  result = case input
24
26
  when Array
25
27
  # If element is an array,
26
28
  is_list = context.container(active_property) == %w(@list)
27
29
  value = input.each_with_object([]) do |v, memo|
28
30
  # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
29
- v = expand(v, active_property, context, ordered: ordered)
31
+ v = expand(v, active_property, context, ordered: ordered, framing: framing)
32
+
33
+ # 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
34
+ v = {"@list" => v} if is_list && v.is_a?(Array)
30
35
 
31
- # If the active property is @list or its container mapping is set to @list, the expanded item must not be an array or a list object, otherwise a list of lists error has been detected and processing is aborted.
32
- raise JsonLdError::ListOfLists,
33
- "A list may not contain another list" if
34
- is_list && (v.is_a?(Array) || list?(v))
35
36
  case v
36
37
  when nil then nil
37
38
  when Array then memo.concat(v)
@@ -52,14 +53,14 @@ module JSON::LD
52
53
  # See if keys mapping to @type have terms with a local context
53
54
  input.each_pair do |key, val|
54
55
  next unless context.expand_iri(key, vocab: true) == '@type'
55
- Array(val).each do |term|
56
+ Array(val).sort.each do |term|
56
57
  term_context = context.term_definitions[term].context if context.term_definitions[term]
57
58
  context = term_context ? context.parse(term_context) : context
58
59
  end
59
60
  end
60
61
 
61
62
  # Process each key and value in element. Ignores @nesting content
62
- expand_object(input, active_property, context, output_object, ordered: ordered)
63
+ expand_object(input, active_property, context, output_object, ordered: ordered, framing: framing)
63
64
 
64
65
  #log_debug("output object") {output_object.inspect}
65
66
 
@@ -83,8 +84,8 @@ module JSON::LD
83
84
  # 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.
84
85
  raise JsonLdError::InvalidLanguageTaggedValue,
85
86
  "when @language is used, @value must be a string: #{output_object.inspect}"
86
- elsif !Array(output_object.fetch('@type', "")).all? {|t|
87
- t.is_a?(String) && context.expand_iri(t, vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI) ||
87
+ elsif !Array(output_object['@type']).all? {|t|
88
+ t.is_a?(String) && RDF::URI(t).absolute? && !t.start_with?('_:') ||
88
89
  t.is_a?(Hash) && t.empty?}
89
90
  # 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.
90
91
  raise JsonLdError::InvalidTypedValue,
@@ -117,7 +118,7 @@ module JSON::LD
117
118
 
118
119
  # Re-order result keys if ordering
119
120
  if ordered
120
- output_object.keys.kw_sort.each_with_object({}) {|kk, memo| memo[kk] = output_object[kk]}
121
+ output_object.keys.sort.each_with_object({}) {|kk, memo| memo[kk] = output_object[kk]}
121
122
  else
122
123
  output_object
123
124
  end
@@ -135,12 +136,11 @@ module JSON::LD
135
136
  CONTAINER_MAPPING_INDEX_ID_TYPE = Set.new(%w(@index @id @type)).freeze
136
137
 
137
138
  # Expand each key and value of element adding them to result
138
- def expand_object(input, active_property, context, output_object, ordered: false)
139
- framing = @options[:processingMode].include?("expand-frame")
139
+ def expand_object(input, active_property, context, output_object, ordered:, framing:)
140
140
  nests = []
141
141
 
142
142
  # Then, proceed and process each property and value in element as follows:
143
- keys = ordered ? input.keys.kw_sort : input.keys
143
+ keys = ordered ? input.keys.sort : input.keys
144
144
  keys.each do |key|
145
145
  # For each key and value in element, ordered lexicographically by key:
146
146
  value = input[key]
@@ -195,8 +195,8 @@ module JSON::LD
195
195
  end
196
196
 
197
197
  # Use array form if framing
198
- if framing && !e_id.is_a?(Array)
199
- [e_id]
198
+ if framing
199
+ as_array(e_id)
200
200
  else
201
201
  e_id
202
202
  end
@@ -224,8 +224,8 @@ module JSON::LD
224
224
  end
225
225
  when '@graph'
226
226
  # 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.
227
- value = expand(value, '@graph', context, ordered: ordered)
228
- value.is_a?(Array) ? value : [value]
227
+ value = expand(value, '@graph', context, ordered: ordered, framing: framing)
228
+ as_array(value)
229
229
  when '@value'
230
230
  # If expanded property is @value and value is not a scalar or null, an invalid value object value error has been detected and processing is aborted. Otherwise, set expanded value to value. If expanded value is null, set the @value member of result to null and continue with the next key from element. Null values need to be preserved in this case as the meaning of an @type member depends on the existence of an @value member.
231
231
  # If framing, always use array form, unless null
@@ -277,20 +277,15 @@ module JSON::LD
277
277
  next if (active_property || '@graph') == '@graph'
278
278
 
279
279
  # Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
280
- value = expand(value, active_property, context, ordered: ordered)
280
+ value = expand(value, active_property, context, ordered: ordered, framing: framing)
281
281
 
282
282
  # Spec FIXME: need to be sure that result is an array
283
- value = [value] unless value.is_a?(Array)
284
-
285
- # If expanded value is a list object, a list of lists error has been detected and processing is aborted.
286
- # Spec FIXME: Also look at each object if result is an array
287
- raise JsonLdError::ListOfLists,
288
- "A list may not contain another list" if value.any? {|v| list?(v)}
283
+ value = as_array(value)
289
284
 
290
285
  value
291
286
  when '@set'
292
287
  # 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.
293
- expand(value, active_property, context, ordered: ordered)
288
+ expand(value, active_property, context, ordered: ordered, framing: framing)
294
289
  when '@reverse'
295
290
  # If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
296
291
  raise JsonLdError::InvalidReverseValue,
@@ -298,7 +293,7 @@ module JSON::LD
298
293
 
299
294
  # Otherwise
300
295
  # Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
301
- value = expand(value, '@reverse', context, ordered: ordered)
296
+ value = expand(value, '@reverse', context, ordered: ordered, framing: framing)
302
297
 
303
298
  # 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:
304
299
  if value.has_key?('@reverse')
@@ -331,7 +326,7 @@ module JSON::LD
331
326
  when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
332
327
  next unless framing
333
328
  # Framing keywords
334
- [expand(value, expanded_property, context, ordered: ordered)].flatten
329
+ [expand(value, expanded_property, context, ordered: ordered, framing: framing)].flatten
335
330
  when '@nest'
336
331
  # Add key to nests
337
332
  nests << key
@@ -361,16 +356,16 @@ module JSON::LD
361
356
  # For each key-value pair language-language value in value, ordered lexicographically by language
362
357
  keys = ordered ? value.keys.sort : value.keys
363
358
  keys.each do |k|
359
+ expanded_k = active_context.expand_iri(k, vocab: true, quiet: true).to_s
364
360
  [value[k]].flatten.each do |item|
365
361
  # item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
366
362
  raise JsonLdError::InvalidLanguageMapValue,
367
363
  "Expected #{item.inspect} to be a string" unless item.nil? || item.is_a?(String)
368
364
 
369
365
  # Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
370
- ary << {
371
- '@value' => item,
372
- '@language' => k.downcase
373
- } if item
366
+ v = {'@value' => item}
367
+ v['@language'] = k.downcase unless expanded_k == '@none'
368
+ ary << v if item
374
369
  end
375
370
  end
376
371
 
@@ -388,30 +383,29 @@ module JSON::LD
388
383
  map_context = active_context.term_definitions[k].context if container.include?('@type') && active_context.term_definitions[k]
389
384
  map_context = active_context.parse(map_context) if map_context
390
385
  map_context ||= active_context
391
-
386
+
387
+ expanded_k = active_context.expand_iri(k, vocab: true, quiet: true).to_s
388
+
392
389
  # Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
393
- index_value = expand([value[k]].flatten, key, map_context, ordered: ordered)
390
+ index_value = expand([value[k]].flatten, key, map_context, ordered: ordered, framing: framing)
394
391
  index_value.each do |item|
395
392
  case container
396
- when %w(@index) then item['@index'] ||= k
397
- when %w(@id)
398
- # Expand k document relative
399
- expanded_k = active_context.expand_iri(k, documentRelative: true, quiet: true).to_s
400
- item['@id'] ||= expanded_k
401
- when %w(@type)
402
- # Expand k vocabulary relative
403
- expanded_k = active_context.expand_iri(k, vocab: true, documentRelative: true, quiet: true).to_s
404
- item['@type'] = [expanded_k].concat(Array(item['@type']))
405
- when %w(@graph @index), %w(@graph @id)
393
+ when %w(@graph @index), %w(@index)
394
+ # Indexed graph by graph name
395
+ if !graph?(item) && container.include?('@graph')
396
+ item = {'@graph' => as_array(item)}
397
+ end
398
+ item['@index'] ||= k unless expanded_k == '@none'
399
+ when %w(@graph @id), %w(@id)
406
400
  # Indexed graph by graph name
407
- if !graph?(item)
408
- item = [item] unless expanded_value.is_a?(Array)
409
- item = {'@graph' => item}
401
+ if !graph?(item) && container.include?('@graph')
402
+ item = {'@graph' => as_array(item)}
410
403
  end
411
- expanded_k = container.include?('@index') ? k :
412
- active_context.expand_iri(k, documentRelative: true, quiet: true).to_s
413
404
  # Expand k document relative
414
- item[container.include?('@index') ? '@index' : '@id'] ||= k
405
+ expanded_k = active_context.expand_iri(k, documentRelative: true, quiet: true).to_s unless expanded_k == '@none'
406
+ item['@id'] ||= expanded_k unless expanded_k == '@none'
407
+ when %w(@type)
408
+ item['@type'] = [expanded_k].concat(Array(item['@type'])) unless expanded_k == '@none'
415
409
  end
416
410
 
417
411
  # Append item to expanded value.
@@ -421,7 +415,7 @@ module JSON::LD
421
415
  ary
422
416
  else
423
417
  # Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
424
- expand(value, key, active_context, ordered: ordered)
418
+ expand(value, key, active_context, ordered: ordered, framing: framing)
425
419
  end
426
420
 
427
421
  # If expanded value is null, ignore key by continuing to the next key from element.
@@ -434,17 +428,16 @@ module JSON::LD
434
428
  # 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.
435
429
  if active_context.container(key) == %w(@list) && !list?(expanded_value)
436
430
  #log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
437
- expanded_value = [expanded_value] unless expanded_value.is_a?(Array)
438
- expanded_value = {'@list' => expanded_value}
431
+ expanded_value = {'@list' => as_array(expanded_value)}
439
432
  end
440
433
  #log_debug {" => #{expanded_value.inspect}"}
441
434
 
442
435
  # convert expanded value to @graph if container specifies it
443
- # FIXME value may be a named graph, as well as a simple graph.
444
- if active_context.container(key) == %w(@graph) && !graph?(expanded_value)
436
+ if active_context.container(key) == %w(@graph)
445
437
  #log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
446
- expanded_value = [expanded_value] unless expanded_value.is_a?(Array)
447
- expanded_value = {'@graph' => expanded_value}
438
+ expanded_value = as_array(expanded_value).map do |v|
439
+ graph?(v) ? v : {'@graph' => as_array(v)}
440
+ end
448
441
  end
449
442
 
450
443
  # Otherwise, if the term definition associated to key indicates that it is a reverse property
@@ -470,12 +463,11 @@ module JSON::LD
470
463
 
471
464
  # For each key in nests, recusively expand content
472
465
  nests.each do |key|
473
- nested_values = input[key]
474
- nested_values = [input[key]] unless input[key].is_a?(Array)
466
+ nested_values = as_array(input[key])
475
467
  nested_values.each do |nv|
476
468
  raise JsonLdError::InvalidNestValue, nv.inspect unless
477
469
  nv.is_a?(Hash) && nv.keys.none? {|k| context.expand_iri(k, vocab: true) == '@value'}
478
- expand_object(nv, active_property, context, output_object, ordered: ordered)
470
+ expand_object(nv, active_property, context, output_object, ordered: ordered, framing: framing)
479
471
  end
480
472
  end
481
473
  end