json-ld 2.2.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|