json-ld 0.9.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/{README.markdown → README.md} +15 -3
  2. data/VERSION +1 -1
  3. data/lib/json/ld.rb +50 -87
  4. data/lib/json/ld/api.rb +85 -96
  5. data/lib/json/ld/compact.rb +103 -170
  6. data/lib/json/ld/context.rb +1137 -0
  7. data/lib/json/ld/expand.rb +212 -171
  8. data/lib/json/ld/extensions.rb +17 -1
  9. data/lib/json/ld/flatten.rb +145 -78
  10. data/lib/json/ld/frame.rb +1 -1
  11. data/lib/json/ld/from_rdf.rb +73 -103
  12. data/lib/json/ld/reader.rb +3 -1
  13. data/lib/json/ld/resource.rb +3 -3
  14. data/lib/json/ld/to_rdf.rb +98 -109
  15. data/lib/json/ld/utils.rb +54 -4
  16. data/lib/json/ld/writer.rb +5 -5
  17. data/spec/api_spec.rb +3 -28
  18. data/spec/compact_spec.rb +76 -113
  19. data/spec/{evaluation_context_spec.rb → context_spec.rb} +307 -563
  20. data/spec/expand_spec.rb +163 -187
  21. data/spec/flatten_spec.rb +119 -114
  22. data/spec/frame_spec.rb +5 -5
  23. data/spec/from_rdf_spec.rb +44 -24
  24. data/spec/suite_compact_spec.rb +11 -8
  25. data/spec/suite_error_expand_spec.rb +23 -0
  26. data/spec/suite_expand_spec.rb +3 -7
  27. data/spec/suite_flatten_spec.rb +3 -3
  28. data/spec/suite_frame_spec.rb +6 -6
  29. data/spec/suite_from_rdf_spec.rb +3 -3
  30. data/spec/suite_helper.rb +13 -6
  31. data/spec/suite_to_rdf_spec.rb +16 -10
  32. data/spec/test-files/test-1-rdf.ttl +4 -3
  33. data/spec/test-files/test-3-rdf.ttl +2 -1
  34. data/spec/test-files/test-4-compacted.json +1 -1
  35. data/spec/test-files/test-5-rdf.ttl +3 -2
  36. data/spec/test-files/test-6-rdf.ttl +3 -2
  37. data/spec/test-files/test-7-compacted.json +3 -3
  38. data/spec/test-files/test-7-expanded.json +3 -3
  39. data/spec/test-files/test-7-rdf.ttl +7 -6
  40. data/spec/test-files/test-9-compacted.json +1 -1
  41. data/spec/to_rdf_spec.rb +67 -75
  42. data/spec/writer_spec.rb +2 -0
  43. metadata +36 -24
  44. checksums.yaml +0 -15
  45. data/lib/json/ld/evaluation_context.rb +0 -984
@@ -9,289 +9,330 @@ module JSON::LD
9
9
  #
10
10
  # @param [Array, Hash] input
11
11
  # @param [String] active_property
12
- # @param [EvaluationContext] context
12
+ # @param [Context] context
13
13
  # @param [Hash{Symbol => Object}] options
14
14
  # @return [Array, Hash]
15
15
  def expand(input, active_property, context, options = {})
16
16
  debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
17
17
  result = case input
18
18
  when Array
19
- # If element is an array, process each item in element recursively using this algorithm,
20
- # passing copies of the active context and active property. If the expanded entry is null, drop it.
19
+ # If element is an array,
21
20
  depth do
22
21
  is_list = context.container(active_property) == '@list'
23
22
  value = input.map do |v|
24
- # If active property has a @container set to @list, and item is an array,
25
- # or the result of expanding any item is an object containing an @list property,
26
- # throw an exception as lists of lists are not allowed.
27
- raise ProcessingError::ListOfLists, "A list may not contain another list" if v.is_a?(Array) && is_list
23
+ # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
24
+ v = expand(v, active_property, context, options)
28
25
 
29
- expand(v, active_property, context, options)
26
+ # 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.
27
+ raise ProcessingError::ListOfLists,
28
+ "A list may not contain another list" if
29
+ is_list && (v.is_a?(Array) || list?(v))
30
+ v
30
31
  end.flatten.compact
31
32
 
32
- if is_list && value.any? {|v| v.is_a?(Hash) && v.has_key?('@list')}
33
- raise ProcessingError::ListOfLists, "A list may not contain another list"
34
- end
35
-
36
33
  value
37
34
  end
38
35
  when Hash
39
- # Otherwise, if element is an object
40
- # If element has a @context property, update the active context according to the steps outlined
41
- # in Context Processing and remove the @context property.
36
+ # 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.
42
37
  if input.has_key?('@context')
43
38
  context = context.parse(input.delete('@context'))
44
- debug("expand") {"evaluation context: #{context.inspect}"}
39
+ debug("expand") {"context: #{context.inspect}"}
45
40
  end
46
41
 
47
42
  depth do
48
43
  output_object = Hash.ordered
49
44
  # Then, proceed and process each property and value in element as follows:
50
- input.keys.kw_sort.each do |property|
51
- value = input[property]
52
- expanded_property = context.expand_iri(property, :position => :predicate, :quiet => true, :namer => namer)
53
-
54
- if expanded_property.is_a?(Array)
55
- # If expanded property is an array, remove every element which is not a absolute IRI.
56
- expanded_property = expanded_property.map {|p| p.to_s if p && p.uri? && p.absolute? || p.node?}.compact
57
- expanded_property = nil if expanded_property.empty?
58
- elsif expanded_property.is_a?(RDF::Resource)
59
- expanded_property = expanded_property.to_s
60
- end
45
+ input.keys.kw_sort.each do |key|
46
+ # For each key and value in element, ordered lexicographically by key:
47
+ value = input[key]
48
+ expanded_property = context.expand_iri(key, :vocab => true, :depth => @depth)
49
+
50
+ # If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
51
+ next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
52
+ expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
61
53
 
62
54
  debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
63
55
 
64
- # If expanded property is an empty array, or null, continue with the next property from element
65
56
  if expanded_property.nil?
66
57
  debug(" => ") {"skip nil property"}
67
58
  next
68
59
  end
69
- expanded_property
70
60
 
71
- if expanded_property.is_a?(String) && expanded_property[0,1] == '@'
61
+ if KEYWORDS.include?(expanded_property)
62
+ # If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
63
+ raise ProcessingError::InvalidReversePropertyMap,
64
+ "@reverse not appropriate at this point" if active_property == '@reverse'
65
+
66
+ # If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
67
+ raise ProcessingError::CollidingKeywords,
68
+ "#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
69
+
72
70
  expanded_value = case expanded_property
73
71
  when '@id'
74
- # If expanded property is @id, value must be a string. Set the @id member in result to the result of expanding value according the IRI Expansion algorithm relative to the document base and re-labeling Blank Nodes.
75
- context.expand_iri(value, :position => :subject, :quiet => true, :namer => namer).to_s
72
+ # If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
73
+ raise ProcessingError::InvalidIdValue,
74
+ "value of @id must be a string: #{value.inspect}" unless value.is_a?(String)
75
+
76
+ # Otherwise, set expanded value to the result of using the IRI Expansion algorithm, passing active context, value, and true for document relative.
77
+ context.expand_iri(value, :documentRelative => true, :depth => @depth).to_s
76
78
  when '@type'
77
- # If expanded property is @type, value must be a string or array of strings. Set the @type member of result to the result of expanding value according the IRI Expansion algorithm relative to the document base and re-labeling Blank Nodes, unless that result is an empty array.
79
+ # 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.
78
80
  debug("@type") {"value: #{value.inspect}"}
79
81
  case value
80
82
  when Array
81
83
  depth do
82
84
  [value].flatten.map do |v|
83
- v = v['@id'] if node_reference?(v)
84
- raise ProcessingError, "Object value must be a string or a node reference: #{v.inspect}" unless v.is_a?(String)
85
- context.expand_iri(v, options.merge(:position => :subject, :quiet => true, :namer => namer)).to_s
85
+ raise ProcessingError::InvalidTypeValue,
86
+ "@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
87
+ context.expand_iri(v, :vocab => true, :documentRelative => true, :quiet => true, :depth => @depth).to_s
86
88
  end
87
89
  end
90
+ when String
91
+ context.expand_iri(value, :vocab => true, :documentRelative => true, :quiet => true, :depth => @depth).to_s
88
92
  when Hash
89
- # Empty object used for @type wildcard or node reference
90
- if node_reference?(value)
91
- context.expand_iri(value['@id'], options.merge(:position => :property, :quiet => true, :namer => namer)).to_s
92
- elsif !value.empty?
93
- raise ProcessingError, "Object value of @type must be empty or a node reference: #{value.inspect}"
94
- else
95
- value
96
- end
93
+ # For framing
94
+ raise ProcessingError::InvalidTypeValue,
95
+ "@type value must be a an empty object for framing: #{value.inspect}" unless
96
+ value.empty?
97
97
  else
98
- context.expand_iri(value, options.merge(:position => :property, :quiet => true, :namer => namer)).to_s
98
+ raise ProcessingError::InvalidTypeValue,
99
+ "@type value must be a string or array of strings: #{value.inspect}"
99
100
  end
101
+ when '@graph'
102
+ # 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.
103
+ depth { expand(value, '@graph', context, options) }
100
104
  when '@value'
101
- # If expanded property is @value, value must be a scalar or null. Set the @value member of result to value.
102
- raise ProcessingError::Lossy, "Value of #{expanded_property} must be a string, was #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
105
+ # 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.
106
+ raise ProcessingError::InvalidValueObjectValue,
107
+ "Value of #{expanded_property} must be a scalar or null: #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
108
+ if value.nil?
109
+ output_object['@value'] = nil
110
+ next;
111
+ end
103
112
  value
104
113
  when '@language'
105
- # If expanded property is @language, value must be a string with the lexical form described in [BCP47] or null. Set the @language member of result to the lowercased value.
106
- raise ProcessingError::Lossy, "Value of #{expanded_property} must be a string, was #{value.inspect}" if value.is_a?(Hash) || value.is_a?(Array)
107
- value.to_s.downcase
114
+ # 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.
115
+ raise ProcessingError::InvalidLanguageTaggedString,
116
+ "Value of #{expanded_property} must be a string: #{value.inspect}" unless value.is_a?(String)
117
+ value.downcase
108
118
  when '@index'
109
- # If expanded property is @index value must be a string. Set the @index member of result to value.
110
- value = value.first if value.is_a?(Array) && value.length == 1
111
- raise ProcessingError, "Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
112
- value.to_s
113
- when '@list', '@set', '@graph'
114
- # If expanded property is @set, @list, or @graph, set the expanded property member of result to the result of expanding value by recursively using this algorithm, along with the active context and active property. If expanded property is @list and active property is null or @graph, pass @list as active property instead.
119
+ # 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.
120
+ raise ProcessingError::InvalidIndexValue,
121
+ "Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
122
+ value
123
+ when '@list'
124
+ # If expanded property is @list:
125
+
126
+ # If active property is null or @graph, continue with the next key from element to remove the free-floating list.
127
+ next if (active_property || '@graph') == '@graph'
128
+
129
+ # Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
130
+ value = depth { expand(value, active_property, context, options) }
131
+
132
+ # Spec FIXME: need to be sure that result is an array
115
133
  value = [value] unless value.is_a?(Array)
116
- ap = expanded_property == '@list' && ((active_property || '@graph') == '@graph') ? '@list' : active_property
117
- value = depth { expand(value, ap, context, options) }
118
134
 
119
- # If expanded property is @list, and any expanded value
120
- # is an object containing an @list property, throw an exception, as lists of lists are not supported
121
- if expanded_property == '@list' && value.any? {|v| v.is_a?(Hash) && v.has_key?('@list')}
122
- raise ProcessingError::ListOfLists, "A list may not contain another list"
123
- end
135
+ # If expanded value is a list object, a list of lists error has been detected and processing is aborted.
136
+ # Spec FIXME: Also look at each object if result is an array
137
+ raise ProcessingError::ListOfLists,
138
+ "A list may not contain another list" if value.any? {|v| list?(v)}
124
139
 
125
140
  value
141
+ when '@set'
142
+ # 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.
143
+ depth { expand(value, active_property, context, options) }
144
+ when '@reverse'
145
+ # If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
146
+ raise ProcessingError::InvalidReverseValueError,
147
+ "@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
148
+
149
+ # Otherwise
150
+ # Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
151
+ value = depth { expand(value, '@reverse', context, options) }
152
+
153
+ # 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:
154
+ if value.has_key?('@reverse')
155
+ debug("@reverse") {"double reverse: #{value.inspect}"}
156
+ value['@reverse'].each do |property, item|
157
+ # If result does not have a property member, create one and set its value to an empty array.
158
+ # Append item to the value of the property member of result.
159
+ (output_object[property] ||= []).concat([item].flatten.compact)
160
+ end
161
+ end
162
+
163
+ # If expanded value contains members other than @reverse:
164
+ unless value.keys.reject {|k| k == '@reverse'}.empty?
165
+ # If result does not have an @reverse member, create one and set its value to an empty JSON object.
166
+ reverse_map = output_object['@reverse'] ||= {}
167
+ value.each do |property, items|
168
+ next if property == '@reverse'
169
+ items.each do |item|
170
+ if value?(item) || list?(item)
171
+ raise ProcessingError::InvalidReversePropertyValue,
172
+ "invalid reverse property value: #{item.inspect}"
173
+ end
174
+ merge_value(reverse_map, property, item)
175
+ end
176
+ end
177
+ end
178
+
179
+ # Continue with the next key from element
180
+ next
181
+ when '@explicit', '@default', '@embed', '@embedChildren', '@omitDefault'
182
+ # Framing keywords
183
+ depth { [expand(value, expanded_property, context, options)].flatten }
126
184
  else
127
185
  # Skip unknown keyword
128
186
  next
129
187
  end
130
188
 
189
+ # Unless expanded value is null, set the expanded property member of result to expanded value.
131
190
  debug("expand #{expanded_property}") { expanded_value.inspect}
132
- output_object[expanded_property] = expanded_value
191
+ output_object[expanded_property] = expanded_value unless expanded_value.nil?
133
192
  next
134
193
  end
135
194
 
136
- expanded_value = if context.container(property) == '@language' && value.is_a?(Hash)
137
- # Otherwise, if value is a JSON object and property is not a keyword and its associated term entry in the active context has a @container key associated with a value of @language, process the associated value as a language map:
195
+ expanded_value = if context.container(key) == '@language' && value.is_a?(Hash)
196
+ # 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:
138
197
 
139
198
  # Set multilingual array to an empty array.
140
- language_map_values = []
199
+ ary = []
141
200
 
142
- # For each key-value in the language map:
201
+ # For each key-value pair language-language value in value, ordered lexicographically by language
143
202
  value.keys.sort.each do |k|
144
- [value[k]].flatten.each do |v|
145
- # Create a new JSON Object, referred to as an expanded language object.
146
- expanded_language_object = Hash.new
147
-
148
- # Add a key-value pair to the expanded language object where the key is @value and the value is the value associated with the key in the language map.
149
- raise ProcessingError::LanguageMap, "Expected #{vv.inspect} to be a string" unless v.is_a?(String)
150
- expanded_language_object['@value'] = v
151
-
152
- # Add a key-value pair to the expanded language object where the key is @language, and the value is the key in the language map, transformed to lowercase.
153
- # FIXME: check for BCP47 conformance
154
- expanded_language_object['@language'] = k.downcase
155
- # Append the expanded language object to the multilingual array.
156
- language_map_values << expanded_language_object
203
+ [value[k]].flatten.each do |item|
204
+ # item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
205
+ raise ProcessingError::InvalidLanguageMapValue,
206
+ "Expected #{item.inspect} to be a string" unless item.is_a?(String)
207
+
208
+ # Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
209
+ ary << {
210
+ '@value' => item,
211
+ '@language' => k.downcase
212
+ }
157
213
  end
158
214
  end
159
- # Set the value associated with property to the multilingual array.
160
- language_map_values
161
- elsif context.container(property) == '@index' && value.is_a?(Hash)
162
- # Otherwise, if value is a JSON object and property is not a keyword and its associated term entry in the active context has a @container key associated with a value of @index, process the associated value as a annotation:
215
+
216
+ ary
217
+ elsif context.container(key) == '@index' && value.is_a?(Hash)
218
+ # 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:
163
219
 
164
220
  # Set ary to an empty array.
165
- annotation_map_values = []
221
+ ary = []
166
222
 
167
223
  # For each key-value in the object:
168
224
  value.keys.sort.each do |k|
169
- [value[k]].flatten.each do |v|
170
- # Expand the value, adding an '@index' key with value equal to the key
171
- expanded_value = depth { expand(v, property, context, options) }
172
- next unless expanded_value
173
- expanded_value['@index'] ||= k
174
- annotation_map_values << expanded_value
225
+ # Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
226
+ index_value = depth { expand([value[k]].flatten, key, context, options) }
227
+ index_value.each do |item|
228
+ item['@index'] ||= k
229
+ ary << item
175
230
  end
176
231
  end
177
- # Set the value associated with property to the multilingual array.
178
- annotation_map_values
232
+ ary
179
233
  else
180
- # Otherwise, expand value by recursively using this algorithm, passing copies of the active context and property as active property.
181
- depth { expand(value, property, context, options) }
234
+ # Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
235
+ depth { expand(value, key, context, options) }
182
236
  end
183
237
 
184
- # Continue to the next property-value pair from element if value is null.
238
+ # If expanded value is null, ignore key by continuing to the next key from element.
185
239
  if expanded_value.nil?
186
240
  debug(" => skip nil value")
187
241
  next
188
242
  end
243
+ debug {" => #{expanded_value.inspect}"}
189
244
 
190
- # If property's container mapping is set to @list and value is not a JSON object or is a JSON object without a @list member, replace value with a JSON object having a @list member whose value is set to value, ensuring that value is an array.
191
- if context.container(property) == '@list' &&
192
- (!expanded_value.is_a?(Hash) || !expanded_value.fetch('@list', false))
193
-
245
+ # 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.
246
+ if context.container(key) == '@list' && !list?(expanded_value)
194
247
  debug(" => ") { "convert #{expanded_value.inspect} to list"}
195
248
  expanded_value = {'@list' => [expanded_value].flatten}
196
249
  end
250
+ debug {" => #{expanded_value.inspect}"}
197
251
 
198
- # Convert value to array form
199
- debug(" => ") {"expanded property: #{expanded_property.inspect}"}
200
- expanded_value = [expanded_value] unless expanded_value.is_a?(Array)
252
+ # Otherwise, if the term definition associated to key indicates that it is a reverse property
253
+ # Spec FIXME: this is not an otherwise.
254
+ if (td = context.term_definitions[key]) && td.reverse_property
255
+ # If result has no @reverse member, create one and initialize its value to an empty JSON object.
256
+ reverse_map = output_object['@reverse'] ||= {}
257
+ [expanded_value].flatten.each do |item|
258
+ # If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
259
+ raise ProcessingError::InvalidReversePropertyValue,
260
+ "invalid reverse property value: #{item.inspect}" if value?(item) || list?(item)
201
261
 
202
- if expanded_property.is_a?(Array)
203
- label_blanknodes(expanded_value)
204
- expanded_property.map(&:to_s).each do |prop|
205
- # label all blank nodes in value with blank node identifiers by using the Label Blank Nodes Algorithm.
206
- output_object[prop] ||= []
207
- output_object[prop] += expanded_value.dup
262
+ # If reverse map has no expanded property member, create one and initialize its value to an empty array.
263
+ # Append item to the value of the expanded property member of reverse map.
264
+ merge_value(reverse_map, expanded_property, item)
208
265
  end
209
266
  else
210
- if output_object.has_key?(expanded_property)
211
- # If element already contains a expanded_property property, append value to the existing value.
212
- output_object[expanded_property] += expanded_value
213
- else
214
- # Otherwise, create a property property with value as value.
215
- output_object[expanded_property] = expanded_value
216
- end
267
+ # Otherwise, if key is not a reverse property:
268
+ # If result does not have an expanded property member, create one and initialize its value to an empty array.
269
+ (output_object[expanded_property] ||= []).concat([expanded_value].flatten)
217
270
  end
218
- debug {" => #{expanded_value.inspect}"}
219
271
  end
220
272
 
221
273
  debug("output object") {output_object.inspect}
222
274
 
223
- # If the active property is null or @graph and element has a @value member without an @index member, or element consists of only an @id member, set element to null.
224
- debug("output object(ap)") {((active_property || '@graph') == '@graph').inspect}
225
- if (active_property || '@graph') == '@graph' &&
226
- ((output_object.has_key?('@value') && !output_object.has_key?('@index')) ||
227
- (output_object.keys - %w(@id)).empty?)
228
- debug("empty top-level") {output_object.inspect}
229
- return nil
230
- end
275
+ # If result contains the key @value:
276
+ if value?(output_object)
277
+ unless (output_object.keys - %w(@value @language @type @index)).empty?
278
+ # 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.
279
+ raise ProcessingError::InvalidValueObjectError,
280
+ "value object has unknown keys: #{output_object.inspect}"
281
+ end
231
282
 
232
- # If the processed element has an @value property
233
- if output_object.has_key?('@value')
234
283
  output_object.delete('@language') if output_object['@language'].to_s.empty?
235
284
  output_object.delete('@type') if output_object['@type'].to_s.empty?
236
- if (%w(@index @language @type) - output_object.keys).empty?
237
- raise ProcessingError, "element must not have more than one other property other than @index, which can either be @language or @type with a string value." unless value.is_a?(String)
238
- end
239
285
 
240
- # if the value of @value equals null, replace element with the value of null.
286
+ # If the value of result's @value key is null, then set result to null.
241
287
  return nil if output_object['@value'].nil?
288
+
289
+ if !output_object['@value'].is_a?(String) && output_object.has_key?('@language')
290
+ # 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.
291
+ raise ProcessingError::InvalidLanguageTaggedValue,
292
+ "when @language is used, @value must be a string: #{@value.inspect}"
293
+ elsif !output_object.fetch('@type', "").is_a?(String)
294
+ # Otherwise, if the result has a @type member and its value is not a string, an invalid typed value error has been detected and processing is aborted.
295
+ raise ProcessingError::InvalidTypedValue,
296
+ "value of @type must be a string: #{output_object['@type'].inspect}"
297
+ end
242
298
  elsif !output_object.fetch('@type', []).is_a?(Array)
243
- # Otherwise, if element has an @type property and it's value is not in the form of an array,
244
- # convert it to an array.
299
+ # 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.
245
300
  output_object['@type'] = [output_object['@type']]
301
+ elsif output_object.keys.any? {|k| %w(@set @list).include?(k)}
302
+ # Otherwise, if result contains the key @set or @list:
303
+ # 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.
304
+ raise ProcessingError::InvalidSetOrListObject,
305
+ "@set or @list may only contain @index: #{output_object.keys.inspect}" unless
306
+ (output_object.keys - %w(@set @list @index)).empty?
307
+
308
+ # If result contains the key @set, then set result to the key's associated value.
309
+ return output_object['@set'] if output_object.keys.include?('@set')
246
310
  end
247
311
 
248
- # If element has an @set or @list property, it must be the only property (other tha @index). Set element to the value of @set;
249
- # leave @list untouched.
250
- if !(%w(@set @list) & output_object.keys).empty?
251
- o_keys = output_object.keys - %w(@set @list @index)
252
- raise ProcessingError, "element must have only @set or @list: #{output_object.keys.inspect}" if o_keys.length > 1
253
-
254
- output_object = output_object.values.first unless output_object.has_key?('@list')
312
+ # If result contains only the key @language, set result to null.
313
+ return nil if output_object.keys == %w(@language)
314
+
315
+ # If active property is null or @graph, drop free-floating values as follows:
316
+ if (active_property || '@graph') == '@graph' &&
317
+ (output_object.keys.any? {|k| %w(@value @list).include?(k)} ||
318
+ (output_object.keys - %w(@id)).empty?)
319
+ debug(" =>") { "empty top-level: " + output_object.inspect}
320
+ return nil
255
321
  end
256
322
 
257
323
  # Re-order result keys
258
- if output_object.is_a?(Hash) && output_object.keys == %w(@language)
259
- # If element has just a @language property, set element to null.
260
- nil
261
- elsif output_object.is_a?(Hash)
262
- r = Hash.ordered
263
- output_object.keys.kw_sort.each {|k| r[k] = output_object[k]}
264
- r
265
- else
266
- output_object
267
- end
324
+ r = Hash.ordered
325
+ output_object.keys.kw_sort.each {|k| r[k] = output_object[k]}
326
+ r
268
327
  end
269
328
  else
270
329
  # Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
271
- context.expand_value(active_property, input,
272
- :position => :subject, :namer => namer, :depth => @depth
273
- ) unless input.nil? || active_property.nil? || active_property == '@graph'
330
+ return nil if input.nil? || active_property.nil? || active_property == '@graph'
331
+ context.expand_value(active_property, input, :depth => @depth)
274
332
  end
275
333
 
276
334
  debug {" => #{result.inspect}"}
277
335
  result
278
336
  end
279
-
280
- protected
281
- # @param [Array, Hash] input
282
- def label_blanknodes(element)
283
- if element.is_a?(Array)
284
- element.each {|e| label_blanknodes(e)}
285
- elsif list?(element)
286
- element['@list'].each {|e| label_blanknodes(e)}
287
- elsif element.is_a?(Hash)
288
- element.keys.sort.each do |k|
289
- label_blanknodes(element[k])
290
- end
291
- if node?(element) and !element.has_key?('@id')
292
- element['@id'] = namer.get_name(nil)
293
- end
294
- end
295
- end
296
337
  end
297
338
  end