json-ld 2.0.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
1
3
  module JSON::LD
2
4
  ##
3
5
  # Expand module, used as part of API
@@ -10,330 +12,331 @@ module JSON::LD
10
12
  # @param [Array, Hash] input
11
13
  # @param [String] active_property
12
14
  # @param [Context] context
13
- # @param [Hash{Symbol => Object}] options
14
- # @option options [Boolean] :ordered (true)
15
+ # @param [Boolean] ordered (true)
15
16
  # Ensure output objects have keys ordered properly
17
+ # @param [Boolean] framing (false)
18
+ # @param [Boolean] keep_free_floating_notes (false)
16
19
  # @return [Array, Hash]
17
- def expand(input, active_property, context, options = {})
18
- options = {ordered: true}.merge!(options)
19
- log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
20
+ def expand(input, active_property, context, ordered: true, framing: false, keep_free_floating_nodes: false)
21
+ #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
20
22
  result = case input
21
23
  when Array
22
24
  # If element is an array,
23
- log_depth do
24
- is_list = context.container(active_property) == '@list'
25
- value = input.map do |v|
26
- # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
27
- v = expand(v, active_property, context, options)
28
-
29
- # 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.
30
- raise JsonLdError::ListOfLists,
31
- "A list may not contain another list" if
32
- is_list && (v.is_a?(Array) || list?(v))
33
- v
34
- end.flatten.compact
35
-
36
- value
37
- end
25
+ is_list = context.container(active_property) == '@list'
26
+ value = input.map do |v|
27
+ # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
28
+ v = expand(v, active_property, context, ordered: ordered)
29
+
30
+ # 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.
31
+ raise JsonLdError::ListOfLists,
32
+ "A list may not contain another list" if
33
+ is_list && (v.is_a?(Array) || list?(v))
34
+ v
35
+ end.flatten.compact
36
+
37
+ value
38
38
  when Hash
39
39
  # 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.
40
40
  if input.has_key?('@context')
41
41
  context = context.parse(input.delete('@context'))
42
- log_debug("expand") {"context: #{context.inspect}"}
42
+ #log_debug("expand") {"context: #{context.inspect}"}
43
43
  end
44
44
 
45
- log_depth do
46
- output_object = {}
47
- # Then, proceed and process each property and value in element as follows:
48
- keys = options[:ordered] ? input.keys.kw_sort : input.keys
49
- keys.each do |key|
50
- # For each key and value in element, ordered lexicographically by key:
51
- value = input[key]
52
- expanded_property = context.expand_iri(key, vocab: true, log_depth: @options[:log_depth])
53
-
54
- # If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
55
- next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
56
- expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
57
-
58
- log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
45
+ output_object = {}
46
+ # Then, proceed and process each property and value in element as follows:
47
+ keys = ordered ? input.keys.kw_sort : input.keys
48
+ keys.each do |key|
49
+ # For each key and value in element, ordered lexicographically by key:
50
+ value = input[key]
51
+ expanded_property = context.expand_iri(key, vocab: true, log_depth: @options[:log_depth])
59
52
 
60
- if expanded_property.nil?
61
- log_debug(" => ") {"skip nil property"}
62
- next
63
- end
53
+ # If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
54
+ next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
55
+ expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
64
56
 
65
- if KEYWORDS.include?(expanded_property)
66
- # If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
67
- raise JsonLdError::InvalidReversePropertyMap,
68
- "@reverse not appropriate at this point" if active_property == '@reverse'
57
+ #log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
69
58
 
70
- # If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
71
- raise JsonLdError::CollidingKeywords,
72
- "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
59
+ if expanded_property.nil?
60
+ #log_debug(" => ") {"skip nil property"}
61
+ next
62
+ end
73
63
 
74
- expanded_value = case expanded_property
75
- when '@id'
76
- # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
64
+ if KEYWORDS.include?(expanded_property)
65
+ # If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
66
+ raise JsonLdError::InvalidReversePropertyMap,
67
+ "@reverse not appropriate at this point" if active_property == '@reverse'
68
+
69
+ # If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
70
+ raise JsonLdError::CollidingKeywords,
71
+ "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
72
+
73
+ expanded_value = case expanded_property
74
+ when '@id'
75
+ # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
76
+ case value
77
+ when String
78
+ when Hash
77
79
  raise JsonLdError::InvalidIdValue,
78
- "value of @id must be a string: #{value.inspect}" unless value.is_a?(String)
79
-
80
- # Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
81
- context.expand_iri(value, documentRelative: true, log_depth: @options[:log_depth]).to_s
82
- when '@type'
83
- # 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.
84
- log_debug("@type") {"value: #{value.inspect}"}
85
- case value
86
- when Array
87
- log_depth do
88
- value.map do |v|
89
- raise JsonLdError::InvalidTypeValue,
90
- "@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
91
- context.expand_iri(v, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
92
- end
93
- end
94
- when String
95
- context.expand_iri(value, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
96
- when Hash
97
- # For framing
98
- raise JsonLdError::InvalidTypeValue,
99
- "@type value must be a an empty object for framing: #{value.inspect}" unless
100
- value.empty?
101
- else
80
+ "value of @id must be a string unless framing: #{value.inspect}" unless framing
81
+ else
82
+ raise JsonLdError::InvalidIdValue,
83
+ "value of @id must be a string or hash if framing: #{value.inspect}"
84
+ end
85
+
86
+ # Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
87
+ context.expand_iri(value, documentRelative: true, log_depth: @options[:log_depth]).to_s
88
+ when '@type'
89
+ # 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.
90
+ #log_debug("@type") {"value: #{value.inspect}"}
91
+ case value
92
+ when Array
93
+ value.map do |v|
102
94
  raise JsonLdError::InvalidTypeValue,
103
- "@type value must be a string or array of strings: #{value.inspect}"
104
- end
105
- when '@graph'
106
- # 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.
107
- log_depth { expand(value, '@graph', context, options) }
108
- when '@value'
109
- # 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.
110
- raise JsonLdError::InvalidValueObjectValue,
111
- "Value of #{expanded_property} must be a scalar or null: #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
112
- if value.nil?
113
- output_object['@value'] = nil
114
- next;
95
+ "@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
96
+ context.expand_iri(v, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
115
97
  end
116
- value
117
- when '@language'
118
- # If expanded property is @language and value is not a string, an invalid language-tagged string error has been detected and processing is aborted. Otherwise, set expanded value to lowercased value.
119
- raise JsonLdError::InvalidLanguageTaggedString,
120
- "Value of #{expanded_property} must be a string: #{value.inspect}" unless value.is_a?(String)
121
- value.downcase
122
- when '@index'
123
- # If expanded property is @index and value is not a string, an invalid @index value error has been detected and processing is aborted. Otherwise, set expanded value to value.
124
- raise JsonLdError::InvalidIndexValue,
125
- "Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
126
- value
127
- when '@list'
128
- # If expanded property is @list:
129
-
130
- # If active property is null or @graph, continue with the next key from element to remove the free-floating list.
131
- next if (active_property || '@graph') == '@graph'
132
-
133
- # Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
134
- value = log_depth { expand(value, active_property, context, options) }
135
-
136
- # Spec FIXME: need to be sure that result is an array
137
- value = [value] unless value.is_a?(Array)
138
-
139
- # If expanded value is a list object, a list of lists error has been detected and processing is aborted.
140
- # Spec FIXME: Also look at each object if result is an array
141
- raise JsonLdError::ListOfLists,
142
- "A list may not contain another list" if value.any? {|v| list?(v)}
143
-
144
- value
145
- when '@set'
146
- # 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.
147
- log_depth { expand(value, active_property, context, options) }
148
- when '@reverse'
149
- # If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
150
- raise JsonLdError::InvalidReverseValue,
151
- "@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
152
-
153
- # Otherwise
154
- # Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
155
- value = log_depth { expand(value, '@reverse', context, options) }
156
-
157
- # 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:
158
- if value.has_key?('@reverse')
159
- log_debug("@reverse") {"double reverse: #{value.inspect}"}
160
- value['@reverse'].each do |property, item|
161
- # If result does not have a property member, create one and set its value to an empty array.
162
- # Append item to the value of the property member of result.
163
- (output_object[property] ||= []).concat([item].flatten.compact)
164
- end
98
+ when String
99
+ context.expand_iri(value, vocab: true, documentRelative: true, quiet: true, log_depth: @options[:log_depth]).to_s
100
+ when Hash
101
+ # For framing
102
+ raise JsonLdError::InvalidTypeValue,
103
+ "@type value must be a an empty object for framing: #{value.inspect}" unless
104
+ value.empty?
105
+ else
106
+ raise JsonLdError::InvalidTypeValue,
107
+ "@type value must be a string or array of strings: #{value.inspect}"
108
+ end
109
+ when '@graph'
110
+ # 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.
111
+ expand(value, '@graph', context, ordered: ordered)
112
+ when '@value'
113
+ # 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.
114
+ raise JsonLdError::InvalidValueObjectValue,
115
+ "Value of #{expanded_property} must be a scalar or null: #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
116
+ if value.nil?
117
+ output_object['@value'] = nil
118
+ next;
119
+ end
120
+ value
121
+ when '@language'
122
+ # If expanded property is @language and value is not a string, an invalid language-tagged string error has been detected and processing is aborted. Otherwise, set expanded value to lowercased value.
123
+ raise JsonLdError::InvalidLanguageTaggedString,
124
+ "Value of #{expanded_property} must be a string: #{value.inspect}" unless value.is_a?(String)
125
+ value.downcase
126
+ when '@index'
127
+ # If expanded property is @index and value is not a string, an invalid @index value error has been detected and processing is aborted. Otherwise, set expanded value to value.
128
+ raise JsonLdError::InvalidIndexValue,
129
+ "Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
130
+ value
131
+ when '@list'
132
+ # If expanded property is @list:
133
+
134
+ # If active property is null or @graph, continue with the next key from element to remove the free-floating list.
135
+ next if (active_property || '@graph') == '@graph'
136
+
137
+ # Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
138
+ value = expand(value, active_property, context, ordered: ordered)
139
+
140
+ # Spec FIXME: need to be sure that result is an array
141
+ value = [value] unless value.is_a?(Array)
142
+
143
+ # If expanded value is a list object, a list of lists error has been detected and processing is aborted.
144
+ # Spec FIXME: Also look at each object if result is an array
145
+ raise JsonLdError::ListOfLists,
146
+ "A list may not contain another list" if value.any? {|v| list?(v)}
147
+
148
+ value
149
+ when '@set'
150
+ # 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.
151
+ expand(value, active_property, context, ordered: ordered)
152
+ when '@reverse'
153
+ # If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
154
+ raise JsonLdError::InvalidReverseValue,
155
+ "@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
156
+
157
+ # Otherwise
158
+ # Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
159
+ value = expand(value, '@reverse', context, ordered: ordered)
160
+
161
+ # 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:
162
+ if value.has_key?('@reverse')
163
+ #log_debug("@reverse") {"double reverse: #{value.inspect}"}
164
+ value['@reverse'].each do |property, item|
165
+ # If result does not have a property member, create one and set its value to an empty array.
166
+ # Append item to the value of the property member of result.
167
+ (output_object[property] ||= []).concat([item].flatten.compact)
165
168
  end
169
+ end
166
170
 
167
- # If expanded value contains members other than @reverse:
168
- unless value.keys.reject {|k| k == '@reverse'}.empty?
169
- # If result does not have an @reverse member, create one and set its value to an empty JSON object.
170
- reverse_map = output_object['@reverse'] ||= {}
171
- value.each do |property, items|
172
- next if property == '@reverse'
173
- items.each do |item|
174
- if value?(item) || list?(item)
175
- raise JsonLdError::InvalidReversePropertyValue,
176
- item.inspect
177
- end
178
- merge_value(reverse_map, property, item)
171
+ # If expanded value contains members other than @reverse:
172
+ unless value.keys.reject {|k| k == '@reverse'}.empty?
173
+ # If result does not have an @reverse member, create one and set its value to an empty JSON object.
174
+ reverse_map = output_object['@reverse'] ||= {}
175
+ value.each do |property, items|
176
+ next if property == '@reverse'
177
+ items.each do |item|
178
+ if value?(item) || list?(item)
179
+ raise JsonLdError::InvalidReversePropertyValue,
180
+ item.inspect
179
181
  end
182
+ merge_value(reverse_map, property, item)
180
183
  end
181
184
  end
182
-
183
- # Continue with the next key from element
184
- next
185
- when '@explicit', '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
186
- # Framing keywords
187
- log_depth { [expand(value, expanded_property, context, options)].flatten }
188
- else
189
- # Skip unknown keyword
190
- next
191
185
  end
192
186
 
193
- # Unless expanded value is null, set the expanded property member of result to expanded value.
194
- log_debug("expand #{expanded_property}") { expanded_value.inspect}
195
- output_object[expanded_property] = expanded_value unless expanded_value.nil?
187
+ # Continue with the next key from element
188
+ next
189
+ when '@explicit', '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
190
+ # Framing keywords
191
+ [expand(value, expanded_property, context, ordered: ordered)].flatten
192
+ else
193
+ # Skip unknown keyword
196
194
  next
197
195
  end
198
196
 
199
- expanded_value = if context.container(key) == '@language' && value.is_a?(Hash)
200
- # Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
201
-
202
- # Set multilingual array to an empty array.
203
- ary = []
204
-
205
- # For each key-value pair language-language value in value, ordered lexicographically by language
206
- keys = options[:ordered] ? value.keys.sort : value.keys
207
- keys.each do |k|
208
- [value[k]].flatten.each do |item|
209
- # item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
210
- raise JsonLdError::InvalidLanguageMapValue,
211
- "Expected #{item.inspect} to be a string" unless item.is_a?(String)
212
-
213
- # Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
214
- ary << {
215
- '@value' => item,
216
- '@language' => k.downcase
217
- }
218
- end
219
- end
197
+ # Unless expanded value is null, set the expanded property member of result to expanded value.
198
+ #log_debug("expand #{expanded_property}") { expanded_value.inspect}
199
+ output_object[expanded_property] = expanded_value unless expanded_value.nil?
200
+ next
201
+ end
220
202
 
221
- ary
222
- elsif context.container(key) == '@index' && value.is_a?(Hash)
223
- # Otherwise, if key's container mapping in active context is @index and value is a JSON object then value is expanded from an index map as follows:
224
-
225
- # Set ary to an empty array.
226
- ary = []
227
-
228
- # For each key-value in the object:
229
- keys = options[:ordered] ? value.keys.sort : value.keys
230
- keys.each do |k|
231
- # Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
232
- index_value = log_depth { expand([value[k]].flatten, key, context, options) }
233
- index_value.each do |item|
234
- item['@index'] ||= k
235
- ary << item
236
- end
203
+ expanded_value = if context.container(key) == '@language' && value.is_a?(Hash)
204
+ # Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
205
+
206
+ # Set multilingual array to an empty array.
207
+ ary = []
208
+
209
+ # For each key-value pair language-language value in value, ordered lexicographically by language
210
+ keys = ordered ? value.keys.sort : value.keys
211
+ keys.each do |k|
212
+ [value[k]].flatten.each do |item|
213
+ # item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
214
+ raise JsonLdError::InvalidLanguageMapValue,
215
+ "Expected #{item.inspect} to be a string" unless item.is_a?(String)
216
+
217
+ # Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
218
+ ary << {
219
+ '@value' => item,
220
+ '@language' => k.downcase
221
+ }
237
222
  end
238
- ary
239
- else
240
- # Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
241
- log_depth { expand(value, key, context, options) }
242
- end
243
-
244
- # If expanded value is null, ignore key by continuing to the next key from element.
245
- if expanded_value.nil?
246
- log_debug(" => skip nil value")
247
- next
248
223
  end
249
- log_debug {" => #{expanded_value.inspect}"}
250
224
 
251
- # 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.
252
- if context.container(key) == '@list' && !list?(expanded_value)
253
- log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
254
- expanded_value = {'@list' => [expanded_value].flatten}
255
- end
256
- log_debug {" => #{expanded_value.inspect}"}
257
-
258
- # Otherwise, if the term definition associated to key indicates that it is a reverse property
259
- # Spec FIXME: this is not an otherwise.
260
- if (td = context.term_definitions[key]) && td.reverse_property
261
- # If result has no @reverse member, create one and initialize its value to an empty JSON object.
262
- reverse_map = output_object['@reverse'] ||= {}
263
- [expanded_value].flatten.each do |item|
264
- # If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
265
- raise JsonLdError::InvalidReversePropertyValue,
266
- item.inspect if value?(item) || list?(item)
267
-
268
- # If reverse map has no expanded property member, create one and initialize its value to an empty array.
269
- # Append item to the value of the expanded property member of reverse map.
270
- merge_value(reverse_map, expanded_property, item)
225
+ ary
226
+ elsif context.container(key) == '@index' && value.is_a?(Hash)
227
+ # Otherwise, if key's container mapping in active context is @index and value is a JSON object then value is expanded from an index map as follows:
228
+
229
+ # Set ary to an empty array.
230
+ ary = []
231
+
232
+ # For each key-value in the object:
233
+ keys = ordered ? value.keys.sort : value.keys
234
+ keys.each do |k|
235
+ # Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
236
+ index_value = expand([value[k]].flatten, key, context, ordered: ordered)
237
+ index_value.each do |item|
238
+ item['@index'] ||= k
239
+ ary << item
271
240
  end
272
- else
273
- # Otherwise, if key is not a reverse property:
274
- # If result does not have an expanded property member, create one and initialize its value to an empty array.
275
- (output_object[expanded_property] ||= []).concat([expanded_value].flatten)
276
241
  end
242
+ ary
243
+ else
244
+ # Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
245
+ expand(value, key, context, ordered: ordered)
277
246
  end
278
247
 
279
- log_debug("output object") {output_object.inspect}
280
-
281
- # If result contains the key @value:
282
- if value?(output_object)
283
- unless (output_object.keys - %w(@value @language @type @index)).empty? &&
284
- (output_object.keys & %w(@language @type)).length < 2
285
- # The result must not contain any keys other than @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.
286
- raise JsonLdError::InvalidValueObject,
287
- "value object has unknown keys: #{output_object.inspect}"
288
- end
248
+ # If expanded value is null, ignore key by continuing to the next key from element.
249
+ if expanded_value.nil?
250
+ #log_debug(" => skip nil value")
251
+ next
252
+ end
253
+ #log_debug {" => #{expanded_value.inspect}"}
289
254
 
290
- output_object.delete('@language') if output_object['@language'].to_s.empty?
291
- output_object.delete('@type') if output_object['@type'].to_s.empty?
292
-
293
- # If the value of result's @value key is null, then set result to null.
294
- return nil if output_object['@value'].nil?
295
-
296
- if !output_object['@value'].is_a?(String) && output_object.has_key?('@language')
297
- # 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.
298
- raise JsonLdError::InvalidLanguageTaggedValue,
299
- "when @language is used, @value must be a string: #{@value.inspect}"
300
- elsif !output_object.fetch('@type', "").is_a?(String) ||
301
- !context.expand_iri(output_object.fetch('@type', ""), vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI)
302
- # 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.
303
- raise JsonLdError::InvalidTypedValue,
304
- "value of @type must be an IRI: #{output_object['@type'].inspect}"
255
+ # 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.
256
+ if context.container(key) == '@list' && !list?(expanded_value)
257
+ #log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
258
+ expanded_value = {'@list' => [expanded_value].flatten}
259
+ end
260
+ #log_debug {" => #{expanded_value.inspect}"}
261
+
262
+ # Otherwise, if the term definition associated to key indicates that it is a reverse property
263
+ # Spec FIXME: this is not an otherwise.
264
+ if (td = context.term_definitions[key]) && td.reverse_property
265
+ # If result has no @reverse member, create one and initialize its value to an empty JSON object.
266
+ reverse_map = output_object['@reverse'] ||= {}
267
+ [expanded_value].flatten.each do |item|
268
+ # If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
269
+ raise JsonLdError::InvalidReversePropertyValue,
270
+ item.inspect if value?(item) || list?(item)
271
+
272
+ # If reverse map has no expanded property member, create one and initialize its value to an empty array.
273
+ # Append item to the value of the expanded property member of reverse map.
274
+ merge_value(reverse_map, expanded_property, item)
305
275
  end
306
- elsif !output_object.fetch('@type', []).is_a?(Array)
307
- # Otherwise, if result contains the key @type and its associated value is not an array, set it to an array containing only the associated value.
308
- output_object['@type'] = [output_object['@type']]
309
- elsif output_object.keys.any? {|k| %w(@set @list).include?(k)}
310
- # Otherwise, if result contains the key @set or @list:
311
- # The result must contain at most one other key and that key must be @index. Otherwise, an invalid set or list object error has been detected and processing is aborted.
312
- raise JsonLdError::InvalidSetOrListObject,
313
- "@set or @list may only contain @index: #{output_object.keys.inspect}" unless
314
- (output_object.keys - %w(@set @list @index)).empty?
315
-
316
- # If result contains the key @set, then set result to the key's associated value.
317
- return output_object['@set'] if output_object.keys.include?('@set')
276
+ else
277
+ # Otherwise, if key is not a reverse property:
278
+ # If result does not have an expanded property member, create one and initialize its value to an empty array.
279
+ (output_object[expanded_property] ||= []).concat([expanded_value].flatten)
318
280
  end
281
+ end
319
282
 
320
- # If result contains only the key @language, set result to null.
321
- return nil if output_object.keys == %w(@language)
283
+ #log_debug("output object") {output_object.inspect}
322
284
 
323
- # If active property is null or @graph, drop free-floating values as follows:
324
- if (active_property || '@graph') == '@graph' &&
325
- (output_object.keys.any? {|k| %w(@value @list).include?(k)} ||
326
- (output_object.keys - %w(@id)).empty?)
327
- log_debug(" =>") { "empty top-level: " + output_object.inspect}
328
- return nil
285
+ # If result contains the key @value:
286
+ if value?(output_object)
287
+ unless (output_object.keys - %w(@value @language @type @index)).empty? &&
288
+ (output_object.keys & %w(@language @type)).length < 2
289
+ # The result must not contain any keys other than @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.
290
+ raise JsonLdError::InvalidValueObject,
291
+ "value object has unknown keys: #{output_object.inspect}"
329
292
  end
330
293
 
331
- # Re-order result keys if ordering
332
- if options[:ordered]
333
- output_object.keys.kw_sort.inject({}) {|map, kk| map[kk] = output_object[kk]; map}
334
- else
335
- output_object
294
+ output_object.delete('@language') if output_object['@language'].to_s.empty?
295
+ output_object.delete('@type') if output_object['@type'].to_s.empty?
296
+
297
+ # If the value of result's @value key is null, then set result to null.
298
+ return nil if output_object['@value'].nil?
299
+
300
+ if !output_object['@value'].is_a?(String) && output_object.has_key?('@language')
301
+ # 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.
302
+ raise JsonLdError::InvalidLanguageTaggedValue,
303
+ "when @language is used, @value must be a string: #{@value.inspect}"
304
+ elsif !output_object.fetch('@type', "").is_a?(String) ||
305
+ !context.expand_iri(output_object.fetch('@type', ""), vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI)
306
+ # 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.
307
+ raise JsonLdError::InvalidTypedValue,
308
+ "value of @type must be an IRI: #{output_object['@type'].inspect}"
336
309
  end
310
+ elsif !output_object.fetch('@type', []).is_a?(Array)
311
+ # Otherwise, if result contains the key @type and its associated value is not an array, set it to an array containing only the associated value.
312
+ output_object['@type'] = [output_object['@type']]
313
+ elsif output_object.keys.any? {|k| %w(@set @list).include?(k)}
314
+ # Otherwise, if result contains the key @set or @list:
315
+ # The result must contain at most one other key and that key must be @index. Otherwise, an invalid set or list object error has been detected and processing is aborted.
316
+ raise JsonLdError::InvalidSetOrListObject,
317
+ "@set or @list may only contain @index: #{output_object.keys.inspect}" unless
318
+ (output_object.keys - %w(@set @list @index)).empty?
319
+
320
+ # If result contains the key @set, then set result to the key's associated value.
321
+ return output_object['@set'] if output_object.keys.include?('@set')
322
+ end
323
+
324
+ # If result contains only the key @language, set result to null.
325
+ return nil if output_object.keys == %w(@language)
326
+
327
+ # If active property is null or @graph, drop free-floating values as follows:
328
+ if (active_property || '@graph') == '@graph' &&
329
+ (output_object.keys.any? {|k| %w(@value @list).include?(k)} ||
330
+ (output_object.keys - %w(@id)).empty? && !keep_free_floating_nodes)
331
+ #log_debug(" =>") { "empty top-level: " + output_object.inspect}
332
+ return nil
333
+ end
334
+
335
+ # Re-order result keys if ordering
336
+ if ordered
337
+ output_object.keys.kw_sort.inject({}) {|map, kk| map[kk] = output_object[kk]; map}
338
+ else
339
+ output_object
337
340
  end
338
341
  else
339
342
  # Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
@@ -341,7 +344,7 @@ module JSON::LD
341
344
  context.expand_value(active_property, input, log_depth: @options[:log_depth])
342
345
  end
343
346
 
344
- log_debug {" => #{result.inspect}"}
347
+ #log_debug {" => #{result.inspect}"}
345
348
  result
346
349
  end
347
350
  end