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