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