json-ld 3.0.2 → 3.1.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.
- checksums.yaml +4 -4
- data/AUTHORS +1 -1
- data/README.md +90 -53
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/bin/jsonld +4 -4
- data/lib/json/ld.rb +27 -10
- data/lib/json/ld/api.rb +325 -96
- data/lib/json/ld/compact.rb +75 -27
- data/lib/json/ld/conneg.rb +188 -0
- data/lib/json/ld/context.rb +677 -292
- data/lib/json/ld/expand.rb +240 -75
- data/lib/json/ld/flatten.rb +5 -3
- data/lib/json/ld/format.rb +19 -19
- data/lib/json/ld/frame.rb +135 -85
- data/lib/json/ld/from_rdf.rb +44 -17
- data/lib/json/ld/html/nokogiri.rb +151 -0
- data/lib/json/ld/html/rexml.rb +186 -0
- data/lib/json/ld/reader.rb +25 -5
- data/lib/json/ld/resource.rb +2 -2
- data/lib/json/ld/streaming_writer.rb +3 -1
- data/lib/json/ld/to_rdf.rb +47 -17
- data/lib/json/ld/utils.rb +4 -2
- data/lib/json/ld/writer.rb +75 -14
- data/spec/api_spec.rb +13 -34
- data/spec/compact_spec.rb +968 -9
- data/spec/conneg_spec.rb +373 -0
- data/spec/context_spec.rb +447 -53
- data/spec/expand_spec.rb +1872 -416
- data/spec/flatten_spec.rb +434 -47
- data/spec/frame_spec.rb +979 -344
- data/spec/from_rdf_spec.rb +305 -5
- data/spec/spec_helper.rb +177 -0
- data/spec/streaming_writer_spec.rb +4 -4
- data/spec/suite_compact_spec.rb +2 -2
- data/spec/suite_expand_spec.rb +14 -2
- data/spec/suite_flatten_spec.rb +10 -2
- data/spec/suite_frame_spec.rb +3 -2
- data/spec/suite_from_rdf_spec.rb +2 -2
- data/spec/suite_helper.rb +55 -20
- data/spec/suite_html_spec.rb +22 -0
- data/spec/suite_http_spec.rb +35 -0
- data/spec/suite_remote_doc_spec.rb +2 -2
- data/spec/suite_to_rdf_spec.rb +14 -3
- data/spec/support/extensions.rb +5 -1
- data/spec/test-files/test-4-input.json +3 -3
- data/spec/test-files/test-5-input.json +2 -2
- data/spec/test-files/test-8-framed.json +14 -18
- data/spec/to_rdf_spec.rb +606 -16
- data/spec/writer_spec.rb +5 -5
- metadata +144 -88
data/lib/json/ld/expand.rb
CHANGED
@@ -8,6 +8,18 @@ module JSON::LD
|
|
8
8
|
module Expand
|
9
9
|
include Utils
|
10
10
|
|
11
|
+
# The following constant is used to reduce object allocations
|
12
|
+
CONTAINER_INDEX_ID_TYPE = Set.new(%w(@index @id @type)).freeze
|
13
|
+
CONTAINER_GRAPH_INDEX = %w(@graph @index).freeze
|
14
|
+
CONTAINER_INDEX = %w(@index).freeze
|
15
|
+
CONTAINER_ID = %w(@id).freeze
|
16
|
+
CONTAINER_LIST = %w(@list).freeze
|
17
|
+
CONTAINER_TYPE = %w(@type).freeze
|
18
|
+
CONTAINER_GRAPH_ID = %w(@graph @id).freeze
|
19
|
+
KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w(@value @language @type @index @direction).freeze
|
20
|
+
KEYS_SET_LIST_INDEX = %w(@set @list @index).freeze
|
21
|
+
KEYS_INCLUDED_TYPE = %w(@included @type).freeze
|
22
|
+
|
11
23
|
##
|
12
24
|
# Expand an Array or Object given an active context and performing local context expansion.
|
13
25
|
#
|
@@ -18,17 +30,24 @@ module JSON::LD
|
|
18
30
|
# Ensure output objects have keys ordered properly
|
19
31
|
# @param [Boolean] framing (false)
|
20
32
|
# Special rules for expanding a frame
|
33
|
+
# @param [Boolean] from_map
|
34
|
+
# Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
|
21
35
|
# @return [Array<Hash{String => Object}>]
|
22
|
-
def expand(input, active_property, context, ordered: false, framing: false)
|
36
|
+
def expand(input, active_property, context, ordered: false, framing: false, from_map: false)
|
23
37
|
#log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
|
24
38
|
framing = false if active_property == '@default'
|
39
|
+
expanded_active_property = context.expand_iri(active_property, vocab: true).to_s if active_property
|
40
|
+
|
41
|
+
# Use a term-specific context, if defined, based on the non-type-scoped context.
|
42
|
+
property_scoped_context = context.term_definitions[active_property].context if active_property && context.term_definitions[active_property]
|
43
|
+
|
25
44
|
result = case input
|
26
45
|
when Array
|
27
46
|
# If element is an array,
|
28
|
-
is_list = context.container(active_property) ==
|
47
|
+
is_list = context.container(active_property) == CONTAINER_LIST
|
29
48
|
value = input.each_with_object([]) do |v, memo|
|
30
49
|
# Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
|
31
|
-
v = expand(v, active_property, context, ordered: ordered, framing: framing)
|
50
|
+
v = expand(v, active_property, context, ordered: ordered, framing: framing, from_map: from_map)
|
32
51
|
|
33
52
|
# 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
53
|
v = {"@list" => v} if is_list && v.is_a?(Array)
|
@@ -42,45 +61,80 @@ module JSON::LD
|
|
42
61
|
|
43
62
|
value
|
44
63
|
when Hash
|
64
|
+
if context.previous_context
|
65
|
+
expanded_key_map = input.keys.inject({}) {|memo, key| memo.merge(key => context.expand_iri(key, vocab: true).to_s)}
|
66
|
+
# Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
|
67
|
+
revert_context = !from_map &&
|
68
|
+
!expanded_key_map.values.include?('@value') &&
|
69
|
+
!(expanded_key_map.values == ['@id'])
|
70
|
+
|
71
|
+
# If there's a previous context, the context was type-scoped
|
72
|
+
context = context.previous_context if revert_context
|
73
|
+
end
|
74
|
+
|
75
|
+
# Apply property-scoped context after reverting term-scoped context
|
76
|
+
context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context
|
77
|
+
|
45
78
|
# 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.
|
46
79
|
if input.has_key?('@context')
|
47
80
|
context = context.parse(input.delete('@context'))
|
48
81
|
#log_debug("expand") {"context: #{context.inspect}"}
|
49
82
|
end
|
50
83
|
|
84
|
+
# Set the type-scoped context to the context on input, for use later
|
85
|
+
type_scoped_context = context
|
86
|
+
|
51
87
|
output_object = {}
|
52
88
|
|
53
89
|
# See if keys mapping to @type have terms with a local context
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
90
|
+
type_key = nil
|
91
|
+
input.keys.sort.
|
92
|
+
select {|k| context.expand_iri(k, vocab: true, quite: true) == '@type'}.
|
93
|
+
each do |tk|
|
94
|
+
|
95
|
+
type_key ||= tk # Side effect saves the first found key mapping to @type
|
96
|
+
Array(input[tk]).sort.each do |term|
|
97
|
+
term_context = type_scoped_context.term_definitions[term].context if type_scoped_context.term_definitions[term]
|
98
|
+
context = term_context ? context.parse(term_context, propagate: false) : context
|
59
99
|
end
|
60
100
|
end
|
61
101
|
|
62
102
|
# Process each key and value in element. Ignores @nesting content
|
63
|
-
expand_object(input, active_property, context, output_object,
|
103
|
+
expand_object(input, active_property, context, output_object,
|
104
|
+
expanded_active_property: expanded_active_property,
|
105
|
+
type_scoped_context: type_scoped_context,
|
106
|
+
type_key: type_key,
|
107
|
+
ordered: ordered,
|
108
|
+
framing: framing)
|
64
109
|
|
65
110
|
#log_debug("output object") {output_object.inspect}
|
66
111
|
|
67
112
|
# If result contains the key @value:
|
68
113
|
if value?(output_object)
|
69
|
-
|
70
|
-
|
71
|
-
# 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.
|
114
|
+
keys = output_object.keys
|
115
|
+
unless (keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty?
|
116
|
+
# The result must not contain any keys other than @direction, @value, @language, @type, and @index. It must not contain both the @language key and the @type key. Otherwise, an invalid value object error has been detected and processing is aborted.
|
72
117
|
raise JsonLdError::InvalidValueObject,
|
73
118
|
"value object has unknown keys: #{output_object.inspect}"
|
74
119
|
end
|
75
120
|
|
121
|
+
if keys.include?('@type') && !(keys & %w(@language @direction)).empty?
|
122
|
+
# @type is inconsistent with either @language or @direction
|
123
|
+
raise JsonLdError::InvalidValueObject,
|
124
|
+
"value object must not include @type with either @language or @direction: #{output_object.inspect}"
|
125
|
+
end
|
126
|
+
|
76
127
|
output_object.delete('@language') if output_object.key?('@language') && Array(output_object['@language']).empty?
|
128
|
+
type_is_json = output_object['@type'] == '@json'
|
77
129
|
output_object.delete('@type') if output_object.key?('@type') && Array(output_object['@type']).empty?
|
78
130
|
|
79
|
-
# If the value of result's @value key is null, then set result to null.
|
131
|
+
# If the value of result's @value key is null, then set result to null and @type is not @json.
|
80
132
|
ary = Array(output_object['@value'])
|
81
|
-
return nil if ary.empty?
|
133
|
+
return nil if ary.empty? && !type_is_json
|
82
134
|
|
83
|
-
if
|
135
|
+
if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1')
|
136
|
+
# Any value of @value is okay if @type: @json
|
137
|
+
elsif !ary.all? {|v| v.is_a?(String) || v.is_a?(Hash) && v.empty?} && output_object.has_key?('@language')
|
84
138
|
# 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.
|
85
139
|
raise JsonLdError::InvalidLanguageTaggedValue,
|
86
140
|
"when @language is used, @value must be a string: #{output_object.inspect}"
|
@@ -89,7 +143,7 @@ module JSON::LD
|
|
89
143
|
t.is_a?(Hash) && t.empty?}
|
90
144
|
# 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.
|
91
145
|
raise JsonLdError::InvalidTypedValue,
|
92
|
-
"value of @type must be an IRI: #{output_object.inspect}"
|
146
|
+
"value of @type must be an IRI or '@json': #{output_object.inspect}"
|
93
147
|
end
|
94
148
|
elsif !output_object.fetch('@type', []).is_a?(Array)
|
95
149
|
# 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.
|
@@ -99,7 +153,7 @@ module JSON::LD
|
|
99
153
|
# 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.
|
100
154
|
raise JsonLdError::InvalidSetOrListObject,
|
101
155
|
"@set or @list may only contain @index: #{output_object.keys.inspect}" unless
|
102
|
-
(output_object.keys -
|
156
|
+
(output_object.keys - KEYS_SET_LIST_INDEX).empty?
|
103
157
|
|
104
158
|
# If result contains the key @set, then set result to the key's associated value.
|
105
159
|
return output_object['@set'] if output_object.key?('@set')
|
@@ -109,9 +163,9 @@ module JSON::LD
|
|
109
163
|
return nil if output_object.length == 1 && output_object.key?('@language')
|
110
164
|
|
111
165
|
# If active property is null or @graph, drop free-floating values as follows:
|
112
|
-
if (
|
166
|
+
if (expanded_active_property || '@graph') == '@graph' &&
|
113
167
|
(output_object.key?('@value') || output_object.key?('@list') ||
|
114
|
-
(output_object.keys -
|
168
|
+
(output_object.keys - CONTAINER_ID).empty? && !framing)
|
115
169
|
#log_debug(" =>") { "empty top-level: " + output_object.inspect}
|
116
170
|
return nil
|
117
171
|
end
|
@@ -124,7 +178,11 @@ module JSON::LD
|
|
124
178
|
end
|
125
179
|
else
|
126
180
|
# Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
|
127
|
-
return nil if input.nil? || active_property.nil? ||
|
181
|
+
return nil if input.nil? || active_property.nil? || expanded_active_property == '@graph'
|
182
|
+
|
183
|
+
# Apply property-scoped context
|
184
|
+
context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context
|
185
|
+
|
128
186
|
context.expand_value(active_property, input, log_depth: @options[:log_depth])
|
129
187
|
end
|
130
188
|
|
@@ -133,12 +191,19 @@ module JSON::LD
|
|
133
191
|
end
|
134
192
|
|
135
193
|
private
|
136
|
-
CONTAINER_MAPPING_INDEX_ID_TYPE = Set.new(%w(@index @id @type)).freeze
|
137
194
|
|
138
195
|
# Expand each key and value of element adding them to result
|
139
|
-
def expand_object(input, active_property, context, output_object,
|
196
|
+
def expand_object(input, active_property, context, output_object,
|
197
|
+
expanded_active_property:,
|
198
|
+
type_scoped_context:,
|
199
|
+
type_key:,
|
200
|
+
ordered:,
|
201
|
+
framing:)
|
140
202
|
nests = []
|
141
203
|
|
204
|
+
input_type = Array(input[type_key]).last
|
205
|
+
input_type = context.expand_iri(input_type, vocab: true, quiet: true) if input_type
|
206
|
+
|
142
207
|
# Then, proceed and process each property and value in element as follows:
|
143
208
|
keys = ordered ? input.keys.sort : input.keys
|
144
209
|
keys.each do |key|
|
@@ -150,6 +215,11 @@ module JSON::LD
|
|
150
215
|
next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
|
151
216
|
expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
|
152
217
|
|
218
|
+
warn "[DEPRECATION] Blank Node properties deprecated in JSON-LD 1.1." if
|
219
|
+
@options[:validate] &&
|
220
|
+
expanded_property.to_s.start_with?("_:") &&
|
221
|
+
context.processingMode('json-ld-1.1')
|
222
|
+
|
153
223
|
#log_debug("expand property") {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
|
154
224
|
|
155
225
|
if expanded_property.nil?
|
@@ -160,11 +230,11 @@ module JSON::LD
|
|
160
230
|
if KEYWORDS.include?(expanded_property)
|
161
231
|
# If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
|
162
232
|
raise JsonLdError::InvalidReversePropertyMap,
|
163
|
-
"@reverse not appropriate at this point" if
|
233
|
+
"@reverse not appropriate at this point" if expanded_active_property == '@reverse'
|
164
234
|
|
165
|
-
# If result has already an expanded property member, an colliding keywords error has been detected and processing is aborted.
|
235
|
+
# If result has already an expanded property member (other than @type), an colliding keywords error has been detected and processing is aborted.
|
166
236
|
raise JsonLdError::CollidingKeywords,
|
167
|
-
"#{expanded_property} already exists in result" if output_object.has_key?(expanded_property)
|
237
|
+
"#{expanded_property} already exists in result" if output_object.has_key?(expanded_property) && !KEYS_INCLUDED_TYPE.include?(expanded_property)
|
168
238
|
|
169
239
|
expanded_value = case expanded_property
|
170
240
|
when '@id'
|
@@ -200,62 +270,106 @@ module JSON::LD
|
|
200
270
|
else
|
201
271
|
e_id
|
202
272
|
end
|
273
|
+
when '@included'
|
274
|
+
# Included blocks are treated as an array of separate object nodes sharing the same referencing active_property. For 1.0, it is skipped as are other unknown keywords
|
275
|
+
next if context.processingMode('json-ld-1.0')
|
276
|
+
included_result = as_array(expand(value, active_property, context, ordered: ordered, framing: framing))
|
277
|
+
|
278
|
+
# Expanded values must be node objects
|
279
|
+
raise JsonLdError::InvalidIncludedValue, "values of @included must expand to node objects" unless included_result.all? {|e| node?(e)}
|
280
|
+
# As other properties may alias to @included, add this to any other previously expanded values
|
281
|
+
Array(output_object['@included']) + included_result
|
203
282
|
when '@type'
|
204
283
|
# 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.
|
205
284
|
#log_debug("@type") {"value: #{value.inspect}"}
|
206
|
-
case value
|
285
|
+
e_type = case value
|
207
286
|
when Array
|
208
287
|
value.map do |v|
|
209
288
|
raise JsonLdError::InvalidTypeValue,
|
210
289
|
"@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
|
211
|
-
|
290
|
+
type_scoped_context.expand_iri(v, vocab: true, documentRelative: true, quiet: true).to_s
|
212
291
|
end
|
213
292
|
when String
|
214
|
-
|
293
|
+
type_scoped_context.expand_iri(value, vocab: true, documentRelative: true, quiet: true).to_s
|
215
294
|
when Hash
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
295
|
+
if !framing
|
296
|
+
raise JsonLdError::InvalidTypeValue,
|
297
|
+
"@type value must be a string or array of strings: #{value.inspect}"
|
298
|
+
elsif value.keys.length == 1 &&
|
299
|
+
type_scoped_context.expand_iri(value.keys.first, vocab: true, quiet: true).to_s == '@default'
|
300
|
+
# Expand values of @default, which must be a string, or array of strings expanding to IRIs
|
301
|
+
[{'@default' => Array(value['@default']).map do |v|
|
302
|
+
raise JsonLdError::InvalidTypeValue,
|
303
|
+
"@type default value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
|
304
|
+
type_scoped_context.expand_iri(v, vocab: true, documentRelative: true, quiet: true).to_s
|
305
|
+
end}]
|
306
|
+
elsif !value.empty?
|
307
|
+
raise JsonLdError::InvalidTypeValue,
|
308
|
+
"@type value must be a an empty object for framing: #{value.inspect}"
|
309
|
+
else
|
310
|
+
[{}]
|
311
|
+
end
|
221
312
|
else
|
222
313
|
raise JsonLdError::InvalidTypeValue,
|
223
314
|
"@type value must be a string or array of strings: #{value.inspect}"
|
224
315
|
end
|
316
|
+
|
317
|
+
e_type = Array(output_object['@type']) + Array(e_type)
|
318
|
+
# Use array form if framing
|
319
|
+
framing || e_type.length > 1 ? e_type : e_type.first
|
225
320
|
when '@graph'
|
226
321
|
# 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
322
|
value = expand(value, '@graph', context, ordered: ordered, framing: framing)
|
228
323
|
as_array(value)
|
229
324
|
when '@value'
|
230
|
-
# If expanded property is @value and
|
325
|
+
# If expanded property is @value and input contains @type: json, accept any value.
|
326
|
+
# 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. (In 1.1, @value can have any JSON value of @type is @json or the property coerces to @json).
|
327
|
+
# 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
328
|
# If framing, always use array form, unless null
|
232
|
-
|
233
|
-
when String, TrueClass, FalseClass, Numeric then (framing ? [value] : value)
|
234
|
-
when nil
|
235
|
-
output_object['@value'] = nil
|
236
|
-
next;
|
237
|
-
when Array
|
238
|
-
raise JsonLdError::InvalidValueObjectValue,
|
239
|
-
"@value value may not be an array unless framing: #{value.inspect}" unless framing
|
329
|
+
if input_type == '@json' && context.processingMode('json-ld-1.1')
|
240
330
|
value
|
241
|
-
when Hash
|
242
|
-
raise JsonLdError::InvalidValueObjectValue,
|
243
|
-
"@value value must be a an empty object for framing: #{value.inspect}" unless
|
244
|
-
value.empty? && framing
|
245
|
-
[value]
|
246
331
|
else
|
247
|
-
|
248
|
-
|
332
|
+
case value
|
333
|
+
when String, TrueClass, FalseClass, Numeric then (framing ? [value] : value)
|
334
|
+
when nil
|
335
|
+
output_object['@value'] = nil
|
336
|
+
next;
|
337
|
+
when Array
|
338
|
+
raise JsonLdError::InvalidValueObjectValue,
|
339
|
+
"@value value may not be an array unless framing: #{value.inspect}" unless framing
|
340
|
+
value
|
341
|
+
when Hash
|
342
|
+
raise JsonLdError::InvalidValueObjectValue,
|
343
|
+
"@value value must be a an empty object for framing: #{value.inspect}" unless
|
344
|
+
value.empty? && framing
|
345
|
+
[value]
|
346
|
+
else
|
347
|
+
raise JsonLdError::InvalidValueObjectValue,
|
348
|
+
"Value of #{expanded_property} must be a scalar or null: #{value.inspect}"
|
349
|
+
end
|
249
350
|
end
|
250
351
|
when '@language'
|
251
352
|
# 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.
|
252
353
|
# If framing, always use array form, unless null
|
253
354
|
case value
|
254
|
-
when String
|
355
|
+
when String
|
356
|
+
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
357
|
+
warn "@language must be valid BCP47: #{value.inspect}"
|
358
|
+
end
|
359
|
+
if @options[:lowercaseLanguage]
|
360
|
+
(framing ? [value.downcase] : value.downcase)
|
361
|
+
else
|
362
|
+
(framing ? [value] : value)
|
363
|
+
end
|
255
364
|
when Array
|
256
365
|
raise JsonLdError::InvalidLanguageTaggedString,
|
257
366
|
"@language value may not be an array unless framing: #{value.inspect}" unless framing
|
258
|
-
value.
|
367
|
+
value.each do |v|
|
368
|
+
if v !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
369
|
+
warn "@language must be valid BCP47: #{v.inspect}"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
@options[:lowercaseLanguage] ? value.map(&:downcase) : value
|
259
373
|
when Hash
|
260
374
|
raise JsonLdError::InvalidLanguageTaggedString,
|
261
375
|
"@language value must be a an empty object for framing: #{value.inspect}" unless
|
@@ -265,16 +379,36 @@ module JSON::LD
|
|
265
379
|
raise JsonLdError::InvalidLanguageTaggedString,
|
266
380
|
"Value of #{expanded_property} must be a string: #{value.inspect}"
|
267
381
|
end
|
382
|
+
when '@direction'
|
383
|
+
# If expanded property is @direction and value is not either 'ltr' or 'rtl', an invalid base direction error has been detected and processing is aborted. Otherwise, set expanded value to value.
|
384
|
+
# If framing, always use array form, unless null
|
385
|
+
case value
|
386
|
+
when 'ltr', 'rtl' then (framing ? [value] : value)
|
387
|
+
when Array
|
388
|
+
raise JsonLdError::InvalidBaseDirection,
|
389
|
+
"@direction value may not be an array unless framing: #{value.inspect}" unless framing
|
390
|
+
raise JsonLdError::InvalidBaseDirection,
|
391
|
+
"@direction must be one of 'ltr', 'rtl', or an array of those if framing #{value.inspect}" unless value.all? {|v| %w(ltr rtl).include?(v) || v.is_a?(Hash) && v.empty?}
|
392
|
+
value
|
393
|
+
when Hash
|
394
|
+
raise JsonLdError::InvalidBaseDirection,
|
395
|
+
"@direction value must be a an empty object for framing: #{value.inspect}" unless
|
396
|
+
value.empty? && framing
|
397
|
+
[value]
|
398
|
+
else
|
399
|
+
raise JsonLdError::InvalidBaseDirection,
|
400
|
+
"Value of #{expanded_property} must be one of 'ltr' or 'rtl': #{value.inspect}"
|
401
|
+
end
|
268
402
|
when '@index'
|
269
403
|
# 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.
|
270
404
|
raise JsonLdError::InvalidIndexValue,
|
271
405
|
"Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
|
272
406
|
value
|
273
407
|
when '@list'
|
274
|
-
# If expanded property is @
|
408
|
+
# If expanded property is @graph:
|
275
409
|
|
276
410
|
# If active property is null or @graph, continue with the next key from element to remove the free-floating list.
|
277
|
-
next if (
|
411
|
+
next if (expanded_active_property || '@graph') == '@graph'
|
278
412
|
|
279
413
|
# Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
|
280
414
|
value = expand(value, active_property, context, ordered: ordered, framing: framing)
|
@@ -339,15 +473,15 @@ module JSON::LD
|
|
339
473
|
|
340
474
|
# Unless expanded value is null, set the expanded property member of result to expanded value.
|
341
475
|
#log_debug("expand #{expanded_property}") { expanded_value.inspect}
|
342
|
-
output_object[expanded_property] = expanded_value unless expanded_value.nil?
|
476
|
+
output_object[expanded_property] = expanded_value unless expanded_value.nil? && expanded_property == '@value' && input_type != '@json'
|
343
477
|
next
|
344
478
|
end
|
345
479
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
480
|
+
container = context.container(key)
|
481
|
+
expanded_value = if context.coerce(key) == '@json'
|
482
|
+
# In JSON-LD 1.1, values can be native JSON
|
483
|
+
{"@value" => value, "@type" => "@json"}
|
484
|
+
elsif container.length == 1 && container.first == '@language' && value.is_a?(Hash)
|
351
485
|
# 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:
|
352
486
|
|
353
487
|
# Set multilingual array to an empty array.
|
@@ -356,7 +490,12 @@ module JSON::LD
|
|
356
490
|
# For each key-value pair language-language value in value, ordered lexicographically by language
|
357
491
|
keys = ordered ? value.keys.sort : value.keys
|
358
492
|
keys.each do |k|
|
359
|
-
expanded_k =
|
493
|
+
expanded_k = context.expand_iri(k, vocab: true, quiet: true).to_s
|
494
|
+
|
495
|
+
if k !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_k != '@none'
|
496
|
+
warn "@language must be valid BCP47: #{k.inspect}"
|
497
|
+
end
|
498
|
+
|
360
499
|
[value[k]].flatten.each do |item|
|
361
500
|
# item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
|
362
501
|
raise JsonLdError::InvalidLanguageMapValue,
|
@@ -364,47 +503,68 @@ module JSON::LD
|
|
364
503
|
|
365
504
|
# Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
|
366
505
|
v = {'@value' => item}
|
367
|
-
v['@language'] = k.downcase unless expanded_k == '@none'
|
506
|
+
v['@language'] = (@options[:lowercaseLanguage] ? k.downcase : k) unless expanded_k == '@none'
|
507
|
+
v['@direction'] = context.direction(key) if context.direction(key)
|
368
508
|
ary << v if item
|
369
509
|
end
|
370
510
|
end
|
371
511
|
|
372
512
|
ary
|
373
|
-
elsif container.any? { |key|
|
513
|
+
elsif container.any? { |key| CONTAINER_INDEX_ID_TYPE.include?(key) } && value.is_a?(Hash)
|
374
514
|
# Otherwise, if key's container mapping in active context contains @index, @id, @type and value is a JSON object then value is expanded from an index map as follows:
|
375
515
|
|
376
516
|
# Set ary to an empty array.
|
377
517
|
ary = []
|
518
|
+
index_key = context.term_definitions[key].index || '@index'
|
519
|
+
|
520
|
+
# While processing index keys, if container includes @type, clear type-scoped term definitions
|
521
|
+
container_context = if container.include?('@type') && context.previous_context
|
522
|
+
context.previous_context
|
523
|
+
elsif container.include?('@id') && context.term_definitions[key]
|
524
|
+
id_context = context.term_definitions[key].context if context.term_definitions[key]
|
525
|
+
id_context ? context.parse(id_context, propagate: false) : context
|
526
|
+
else
|
527
|
+
context
|
528
|
+
end
|
378
529
|
|
379
530
|
# For each key-value in the object:
|
380
531
|
keys = ordered ? value.keys.sort : value.keys
|
381
532
|
keys.each do |k|
|
382
533
|
# If container mapping in the active context includes @type, and k is a term in the active context having a local context, use that context when expanding values
|
383
|
-
map_context =
|
384
|
-
map_context =
|
385
|
-
map_context ||=
|
534
|
+
map_context = container_context.term_definitions[k].context if container.include?('@type') && container_context.term_definitions[k]
|
535
|
+
map_context = container_context.parse(map_context, propagate: false) if map_context
|
536
|
+
map_context ||= container_context
|
386
537
|
|
387
|
-
expanded_k =
|
538
|
+
expanded_k = container_context.expand_iri(k, vocab: true, quiet: true).to_s
|
388
539
|
|
389
540
|
# Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
|
390
|
-
index_value = expand([value[k]].flatten, key, map_context, ordered: ordered, framing: framing)
|
541
|
+
index_value = expand([value[k]].flatten, key, map_context, ordered: ordered, framing: framing, from_map: true)
|
391
542
|
index_value.each do |item|
|
392
543
|
case container
|
393
|
-
when
|
544
|
+
when CONTAINER_GRAPH_INDEX, CONTAINER_INDEX
|
394
545
|
# Indexed graph by graph name
|
395
546
|
if !graph?(item) && container.include?('@graph')
|
396
547
|
item = {'@graph' => as_array(item)}
|
397
548
|
end
|
398
|
-
|
399
|
-
|
549
|
+
if index_key == '@index'
|
550
|
+
item['@index'] ||= k unless expanded_k == '@none'
|
551
|
+
elsif value?(item)
|
552
|
+
raise JsonLdError::InvalidValueObject, "Attempt to add illegal key to value object: #{index_key}"
|
553
|
+
else
|
554
|
+
# Expand key based on term
|
555
|
+
expanded_k = k == '@none' ? '@none' : container_context.expand_value(index_key, k)
|
556
|
+
index_property = container_context.expand_iri(index_key, vocab: true, quiet: true).to_s
|
557
|
+
item[index_property] = [expanded_k].concat(Array(item[index_property])) unless expanded_k == '@none'
|
558
|
+
end
|
559
|
+
when CONTAINER_GRAPH_ID, CONTAINER_ID
|
400
560
|
# Indexed graph by graph name
|
401
561
|
if !graph?(item) && container.include?('@graph')
|
402
562
|
item = {'@graph' => as_array(item)}
|
403
563
|
end
|
404
564
|
# Expand k document relative
|
405
|
-
expanded_k =
|
565
|
+
expanded_k = container_context.expand_iri(k, documentRelative: true, quiet: true).to_s unless expanded_k == '@none'
|
406
566
|
item['@id'] ||= expanded_k unless expanded_k == '@none'
|
407
|
-
when
|
567
|
+
when CONTAINER_TYPE
|
408
568
|
item['@type'] = [expanded_k].concat(Array(item['@type'])) unless expanded_k == '@none'
|
409
569
|
end
|
410
570
|
|
@@ -415,7 +575,7 @@ module JSON::LD
|
|
415
575
|
ary
|
416
576
|
else
|
417
577
|
# Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
|
418
|
-
expand(value, key,
|
578
|
+
expand(value, key, context, ordered: ordered, framing: framing)
|
419
579
|
end
|
420
580
|
|
421
581
|
# If expanded value is null, ignore key by continuing to the next key from element.
|
@@ -436,7 +596,7 @@ module JSON::LD
|
|
436
596
|
if container.first == '@graph' && container.length == 1
|
437
597
|
#log_debug(" => ") { "convert #{expanded_value.inspect} to list"}
|
438
598
|
expanded_value = as_array(expanded_value).map do |v|
|
439
|
-
|
599
|
+
{'@graph' => as_array(v)}
|
440
600
|
end
|
441
601
|
end
|
442
602
|
|
@@ -474,7 +634,12 @@ module JSON::LD
|
|
474
634
|
nested_values.each do |nv|
|
475
635
|
raise JsonLdError::InvalidNestValue, nv.inspect unless
|
476
636
|
nv.is_a?(Hash) && nv.keys.none? {|k| context.expand_iri(k, vocab: true) == '@value'}
|
477
|
-
expand_object(nv, active_property, context, output_object,
|
637
|
+
expand_object(nv, active_property, context, output_object,
|
638
|
+
expanded_active_property: expanded_active_property,
|
639
|
+
type_scoped_context: type_scoped_context,
|
640
|
+
type_key: type_key,
|
641
|
+
ordered: ordered,
|
642
|
+
framing: framing)
|
478
643
|
end
|
479
644
|
end
|
480
645
|
end
|