json-ld 3.2.3 → 3.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/json/ld/api.rb +807 -764
- data/lib/json/ld/compact.rb +304 -304
- data/lib/json/ld/conneg.rb +179 -161
- data/lib/json/ld/context.rb +2080 -1945
- data/lib/json/ld/expand.rb +745 -666
- data/lib/json/ld/extensions.rb +14 -13
- data/lib/json/ld/flatten.rb +257 -247
- data/lib/json/ld/format.rb +202 -194
- data/lib/json/ld/frame.rb +525 -502
- data/lib/json/ld/from_rdf.rb +224 -166
- data/lib/json/ld/html/nokogiri.rb +123 -121
- data/lib/json/ld/html/rexml.rb +151 -147
- data/lib/json/ld/reader.rb +107 -100
- data/lib/json/ld/resource.rb +224 -205
- data/lib/json/ld/streaming_reader.rb +574 -507
- data/lib/json/ld/streaming_writer.rb +93 -92
- data/lib/json/ld/to_rdf.rb +171 -167
- data/lib/json/ld/utils.rb +270 -264
- data/lib/json/ld/version.rb +24 -14
- data/lib/json/ld/writer.rb +334 -311
- data/lib/json/ld.rb +103 -96
- metadata +78 -209
- data/spec/api_spec.rb +0 -132
- data/spec/compact_spec.rb +0 -3482
- data/spec/conneg_spec.rb +0 -373
- data/spec/context_spec.rb +0 -2036
- data/spec/expand_spec.rb +0 -4496
- data/spec/flatten_spec.rb +0 -1203
- data/spec/format_spec.rb +0 -115
- data/spec/frame_spec.rb +0 -2498
- data/spec/from_rdf_spec.rb +0 -1005
- data/spec/matchers.rb +0 -20
- data/spec/rdfstar_spec.rb +0 -25
- data/spec/reader_spec.rb +0 -883
- data/spec/resource_spec.rb +0 -76
- data/spec/spec_helper.rb +0 -281
- data/spec/streaming_reader_spec.rb +0 -237
- data/spec/streaming_writer_spec.rb +0 -145
- data/spec/suite_compact_spec.rb +0 -22
- data/spec/suite_expand_spec.rb +0 -36
- data/spec/suite_flatten_spec.rb +0 -34
- data/spec/suite_frame_spec.rb +0 -29
- data/spec/suite_from_rdf_spec.rb +0 -22
- data/spec/suite_helper.rb +0 -411
- data/spec/suite_html_spec.rb +0 -22
- data/spec/suite_http_spec.rb +0 -35
- data/spec/suite_remote_doc_spec.rb +0 -22
- data/spec/suite_to_rdf_spec.rb +0 -30
- data/spec/support/extensions.rb +0 -44
- data/spec/test-files/test-1-compacted.jsonld +0 -10
- data/spec/test-files/test-1-context.jsonld +0 -7
- data/spec/test-files/test-1-expanded.jsonld +0 -5
- data/spec/test-files/test-1-input.jsonld +0 -10
- data/spec/test-files/test-1-rdf.ttl +0 -8
- data/spec/test-files/test-2-compacted.jsonld +0 -20
- data/spec/test-files/test-2-context.jsonld +0 -7
- data/spec/test-files/test-2-expanded.jsonld +0 -16
- data/spec/test-files/test-2-input.jsonld +0 -20
- data/spec/test-files/test-2-rdf.ttl +0 -14
- data/spec/test-files/test-3-compacted.jsonld +0 -11
- data/spec/test-files/test-3-context.jsonld +0 -8
- data/spec/test-files/test-3-expanded.jsonld +0 -10
- data/spec/test-files/test-3-input.jsonld +0 -11
- data/spec/test-files/test-3-rdf.ttl +0 -8
- data/spec/test-files/test-4-compacted.jsonld +0 -10
- data/spec/test-files/test-4-context.jsonld +0 -7
- data/spec/test-files/test-4-expanded.jsonld +0 -6
- data/spec/test-files/test-4-input.jsonld +0 -10
- data/spec/test-files/test-4-rdf.ttl +0 -5
- data/spec/test-files/test-5-compacted.jsonld +0 -13
- data/spec/test-files/test-5-context.jsonld +0 -7
- data/spec/test-files/test-5-expanded.jsonld +0 -9
- data/spec/test-files/test-5-input.jsonld +0 -13
- data/spec/test-files/test-5-rdf.ttl +0 -7
- data/spec/test-files/test-6-compacted.jsonld +0 -10
- data/spec/test-files/test-6-context.jsonld +0 -7
- data/spec/test-files/test-6-expanded.jsonld +0 -10
- data/spec/test-files/test-6-input.jsonld +0 -10
- data/spec/test-files/test-6-rdf.ttl +0 -6
- data/spec/test-files/test-7-compacted.jsonld +0 -23
- data/spec/test-files/test-7-context.jsonld +0 -4
- data/spec/test-files/test-7-expanded.jsonld +0 -20
- data/spec/test-files/test-7-input.jsonld +0 -23
- data/spec/test-files/test-7-rdf.ttl +0 -14
- data/spec/test-files/test-8-compacted.jsonld +0 -34
- data/spec/test-files/test-8-context.jsonld +0 -11
- data/spec/test-files/test-8-expanded.jsonld +0 -24
- data/spec/test-files/test-8-frame.jsonld +0 -18
- data/spec/test-files/test-8-framed.jsonld +0 -25
- data/spec/test-files/test-8-input.jsonld +0 -30
- data/spec/test-files/test-8-rdf.ttl +0 -15
- data/spec/test-files/test-9-compacted.jsonld +0 -20
- data/spec/test-files/test-9-context.jsonld +0 -13
- data/spec/test-files/test-9-expanded.jsonld +0 -14
- data/spec/test-files/test-9-input.jsonld +0 -12
- data/spec/to_rdf_spec.rb +0 -1551
- data/spec/writer_spec.rb +0 -427
data/lib/json/ld/expand.rb
CHANGED
@@ -1,761 +1,840 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
require 'set'
|
4
4
|
|
5
|
-
module JSON
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
module JSON
|
6
|
+
module LD
|
7
|
+
##
|
8
|
+
# Expand module, used as part of API
|
9
|
+
module Expand
|
10
|
+
include Utils
|
11
|
+
|
12
|
+
# The following constant is used to reduce object allocations
|
13
|
+
CONTAINER_INDEX_ID_TYPE = Set['@index', '@id', '@type'].freeze
|
14
|
+
KEY_ID = %w[@id].freeze
|
15
|
+
KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION = %w[@value @language @type @index @direction @annotation].freeze
|
16
|
+
KEYS_SET_LIST_INDEX = %w[@set @list @index].freeze
|
17
|
+
KEYS_INCLUDED_TYPE_REVERSE = %w[@included @type @reverse].freeze
|
18
|
+
|
19
|
+
##
|
20
|
+
# Expand an Array or Object given an active context and performing local context expansion.
|
21
|
+
#
|
22
|
+
# @param [Array, Hash] input
|
23
|
+
# @param [String] active_property
|
24
|
+
# @param [Context] context
|
25
|
+
# @param [Boolean] framing (false)
|
26
|
+
# Special rules for expanding a frame
|
27
|
+
# @param [Boolean] from_map
|
28
|
+
# Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
|
29
|
+
#
|
30
|
+
# @return [Array<Hash{String => Object}>]
|
31
|
+
def expand(input, active_property, context,
|
32
|
+
framing: false, from_map: false, log_depth: nil)
|
33
|
+
# log_debug("expand", depth: log_depth.to_i) {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
|
34
|
+
framing = false if active_property == '@default'
|
35
|
+
if active_property
|
36
|
+
expanded_active_property = context.expand_iri(active_property, vocab: true, as_string: true,
|
37
|
+
base: @options[:base])
|
38
|
+
end
|
10
39
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
40
|
+
# Use a term-specific context, if defined, based on the non-type-scoped context.
|
41
|
+
if active_property && context.term_definitions[active_property]
|
42
|
+
property_scoped_context = context.term_definitions[active_property].context
|
43
|
+
end
|
44
|
+
# log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{property_scoped_context.inspect}"} unless property_scoped_context.nil?
|
45
|
+
|
46
|
+
case input
|
47
|
+
when Array
|
48
|
+
# If element is an array,
|
49
|
+
is_list = context.container(active_property).include?('@list')
|
50
|
+
input.each_with_object([]) do |v, memo|
|
51
|
+
# Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
|
52
|
+
v = expand(v, active_property, context,
|
53
|
+
framing: framing,
|
54
|
+
from_map: from_map,
|
55
|
+
log_depth: log_depth.to_i + 1)
|
17
56
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# Expanding from a map, which could be an `@type` map, so don't clear out context term definitions
|
28
|
-
#
|
29
|
-
# @return [Array<Hash{String => Object}>]
|
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
|
-
framing = false if active_property == '@default'
|
34
|
-
expanded_active_property = context.expand_iri(active_property, vocab: true, as_string: true, base: @options[:base]) if active_property
|
35
|
-
|
36
|
-
# Use a term-specific context, if defined, based on the non-type-scoped context.
|
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?
|
39
|
-
|
40
|
-
result = case input
|
41
|
-
when Array
|
42
|
-
# If element is an array,
|
43
|
-
is_list = context.container(active_property).include?('@list')
|
44
|
-
value = input.each_with_object([]) do |v, memo|
|
45
|
-
# Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element.
|
46
|
-
v = expand(v, active_property, context,
|
47
|
-
framing: framing,
|
48
|
-
from_map: from_map,
|
49
|
-
log_depth: log_depth.to_i + 1)
|
57
|
+
# 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
|
58
|
+
if is_list && v.is_a?(Array)
|
59
|
+
# Make sure that no member of v contains an annotation object
|
60
|
+
if v.any? { |n| n.is_a?(Hash) && n.key?('@annotation') }
|
61
|
+
raise JsonLdError::InvalidAnnotation,
|
62
|
+
"A list element must not contain @annotation."
|
63
|
+
end
|
64
|
+
v = { "@list" => v }
|
65
|
+
end
|
50
66
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
v.any? {|n| n.is_a?(Hash) && n.key?('@annotation')}
|
57
|
-
v = {"@list" => v}
|
67
|
+
case v
|
68
|
+
when nil then nil
|
69
|
+
when Array then memo.concat(v)
|
70
|
+
else memo << v
|
71
|
+
end
|
58
72
|
end
|
59
73
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
when Hash
|
75
|
+
if context.previous_context
|
76
|
+
expanded_key_map = input.keys.inject({}) do |memo, key|
|
77
|
+
memo.merge(key => context.expand_iri(key, vocab: true, as_string: true, base: @options[:base]))
|
78
|
+
end
|
79
|
+
# Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
|
80
|
+
revert_context = !from_map &&
|
81
|
+
!expanded_key_map.value?('@value') &&
|
82
|
+
expanded_key_map.values != ['@id']
|
83
|
+
|
84
|
+
# If there's a previous context, the context was type-scoped
|
85
|
+
# log_debug("expand", depth: log_depth.to_i) {"previous_context: #{context.previous_context.inspect}"} if revert_context
|
86
|
+
context = context.previous_context if revert_context
|
64
87
|
end
|
65
|
-
end
|
66
88
|
|
67
|
-
|
68
|
-
|
69
|
-
|
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]))
|
89
|
+
# Apply property-scoped context after reverting term-scoped context
|
90
|
+
unless property_scoped_context.nil?
|
91
|
+
context = context.parse(property_scoped_context, base: @options[:base], override_protected: true)
|
72
92
|
end
|
73
|
-
#
|
74
|
-
revert_context = !from_map &&
|
75
|
-
!expanded_key_map.values.include?('@value') &&
|
76
|
-
!(expanded_key_map.values == ['@id'])
|
77
|
-
|
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
|
80
|
-
context = context.previous_context if revert_context
|
81
|
-
end
|
93
|
+
# log_debug("expand", depth: log_depth.to_i) {"after property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
|
82
94
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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.
|
90
|
-
if input.key?('@context')
|
91
|
-
context = context.parse(input['@context'], base: @options[:base])
|
92
|
-
log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"}
|
93
|
-
end
|
94
|
-
|
95
|
-
# Set the type-scoped context to the context on input, for use later
|
96
|
-
type_scoped_context = context
|
95
|
+
# 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.
|
96
|
+
if input.key?('@context')
|
97
|
+
context = context.parse(input['@context'], base: @options[:base])
|
98
|
+
# log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"}
|
99
|
+
end
|
97
100
|
|
98
|
-
|
101
|
+
# Set the type-scoped context to the context on input, for use later
|
102
|
+
type_scoped_context = context
|
99
103
|
|
100
|
-
|
101
|
-
type_key = nil
|
102
|
-
(input.keys - %w(@context)).sort.
|
103
|
-
select {|k| context.expand_iri(k, vocab: true, base: @options[:base]) == '@type'}.
|
104
|
-
each do |tk|
|
104
|
+
output_object = {}
|
105
105
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
# See if keys mapping to @type have terms with a local context
|
107
|
+
type_key = nil
|
108
|
+
(input.keys - %w[@context]).sort
|
109
|
+
.select { |k| context.expand_iri(k, vocab: true, base: @options[:base]) == '@type' }
|
110
|
+
.each do |tk|
|
111
|
+
type_key ||= tk # Side effect saves the first found key mapping to @type
|
112
|
+
Array(input[tk]).sort.each do |term|
|
113
|
+
if type_scoped_context.term_definitions[term]
|
114
|
+
term_context = type_scoped_context.term_definitions[term].context
|
115
|
+
end
|
116
|
+
unless term_context.nil?
|
117
|
+
# log_debug("expand", depth: log_depth.to_i) {"term_context[#{term}]: #{term_context.inspect}"}
|
118
|
+
context = context.parse(term_context, base: @options[:base], propagate: false)
|
119
|
+
end
|
112
120
|
end
|
113
121
|
end
|
114
|
-
end
|
115
122
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
log_debug("output object", depth: log_depth.to_i) {output_object.inspect}
|
125
|
-
|
126
|
-
# If result contains the key @value:
|
127
|
-
if value?(output_object)
|
128
|
-
keys = output_object.keys
|
129
|
-
unless (keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty?
|
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.
|
131
|
-
raise JsonLdError::InvalidValueObject,
|
132
|
-
"value object has unknown keys: #{output_object.inspect}"
|
133
|
-
end
|
123
|
+
# Process each key and value in element. Ignores @nesting content
|
124
|
+
expand_object(input, active_property, context, output_object,
|
125
|
+
expanded_active_property: expanded_active_property,
|
126
|
+
framing: framing,
|
127
|
+
type_key: type_key,
|
128
|
+
type_scoped_context: type_scoped_context,
|
129
|
+
log_depth: log_depth.to_i + 1)
|
134
130
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
131
|
+
# log_debug("output object", depth: log_depth.to_i) {output_object.inspect}
|
132
|
+
|
133
|
+
# If result contains the key @value:
|
134
|
+
if value?(output_object)
|
135
|
+
keys = output_object.keys
|
136
|
+
unless (keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty?
|
137
|
+
# 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.
|
138
|
+
raise JsonLdError::InvalidValueObject,
|
139
|
+
"value object has unknown keys: #{output_object.inspect}"
|
140
|
+
end
|
140
141
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
142
|
+
if keys.include?('@type') && !(keys & %w[@language @direction]).empty?
|
143
|
+
# @type is inconsistent with either @language or @direction
|
144
|
+
raise JsonLdError::InvalidValueObject,
|
145
|
+
"value object must not include @type with either @language or @direction: #{output_object.inspect}"
|
146
|
+
end
|
147
|
+
|
148
|
+
if output_object.key?('@language') && Array(output_object['@language']).empty?
|
149
|
+
output_object.delete('@language')
|
150
|
+
end
|
151
|
+
type_is_json = output_object['@type'] == '@json'
|
152
|
+
output_object.delete('@type') if output_object.key?('@type') && Array(output_object['@type']).empty?
|
153
|
+
|
154
|
+
# If the value of result's @value key is null, then set result to null and @type is not @json.
|
155
|
+
ary = Array(output_object['@value'])
|
156
|
+
return nil if ary.empty? && !type_is_json
|
157
|
+
|
158
|
+
if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1')
|
159
|
+
# Any value of @value is okay if @type: @json
|
160
|
+
elsif !ary.all? { |v| v.is_a?(String) || (v.is_a?(Hash) && v.empty?) } && output_object.key?('@language')
|
161
|
+
# 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.
|
162
|
+
raise JsonLdError::InvalidLanguageTaggedValue,
|
163
|
+
"when @language is used, @value must be a string: #{output_object.inspect}"
|
164
|
+
elsif output_object['@type'] &&
|
165
|
+
(!Array(output_object['@type']).all? do |t|
|
166
|
+
(t.is_a?(String) && RDF::URI(t).valid? && !t.start_with?('_:')) ||
|
167
|
+
(t.is_a?(Hash) && t.empty?)
|
168
|
+
end ||
|
169
|
+
(!framing && !output_object['@type'].is_a?(String)))
|
170
|
+
# 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.
|
171
|
+
raise JsonLdError::InvalidTypedValue,
|
172
|
+
"value of @type must be an IRI or '@json': #{output_object.inspect}"
|
173
|
+
elsif !framing && !output_object.fetch('@type', '').is_a?(String) &&
|
164
174
|
RDF::URI(t).valid? && !t.start_with?('_:')
|
165
|
-
|
166
|
-
|
167
|
-
|
175
|
+
# 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.
|
176
|
+
raise JsonLdError::InvalidTypedValue,
|
177
|
+
"value of @type must be an IRI or '@json': #{output_object.inspect}"
|
178
|
+
end
|
179
|
+
elsif !output_object.fetch('@type', []).is_a?(Array)
|
180
|
+
# Otherwise, if result contains the key @type and its associated value is not an array, set it to an array containing only the associated value.
|
181
|
+
output_object['@type'] = [output_object['@type']]
|
182
|
+
elsif output_object.key?('@set') || output_object.key?('@list')
|
183
|
+
# Otherwise, if result contains the key @set or @list:
|
184
|
+
# The result must contain at most one other key and that key must be @index. Otherwise, an invalid set or list object error has been detected and processing is aborted.
|
185
|
+
unless (output_object.keys - KEYS_SET_LIST_INDEX).empty?
|
186
|
+
raise JsonLdError::InvalidSetOrListObject,
|
187
|
+
"@set or @list may only contain @index: #{output_object.keys.inspect}"
|
188
|
+
end
|
189
|
+
|
190
|
+
# If result contains the key @set, then set result to the key's associated value.
|
191
|
+
return output_object['@set'] if output_object.key?('@set')
|
192
|
+
elsif output_object['@annotation']
|
193
|
+
# Otherwise, if result contains the key @annotation,
|
194
|
+
# the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted.
|
195
|
+
unless output_object['@annotation'].all? { |o| node?(o) && !o.key?('@id') }
|
196
|
+
raise JsonLdError::InvalidAnnotation,
|
197
|
+
"@annotation must reference node objects without @id."
|
198
|
+
end
|
199
|
+
|
200
|
+
# Additionally, the property must not be used if there is no active property, or the expanded active property is @graph.
|
201
|
+
if %w[@graph @included].include?(expanded_active_property || '@graph')
|
202
|
+
raise JsonLdError::InvalidAnnotation,
|
203
|
+
"@annotation must not be used on a top-level object."
|
204
|
+
end
|
205
|
+
|
168
206
|
end
|
169
|
-
elsif !output_object.fetch('@type', []).is_a?(Array)
|
170
|
-
# Otherwise, if result contains the key @type and its associated value is not an array, set it to an array containing only the associated value.
|
171
|
-
output_object['@type'] = [output_object['@type']]
|
172
|
-
elsif output_object.key?('@set') || output_object.key?('@list')
|
173
|
-
# Otherwise, if result contains the key @set or @list:
|
174
|
-
# The result must contain at most one other key and that key must be @index. Otherwise, an invalid set or list object error has been detected and processing is aborted.
|
175
|
-
raise JsonLdError::InvalidSetOrListObject,
|
176
|
-
"@set or @list may only contain @index: #{output_object.keys.inspect}" unless
|
177
|
-
(output_object.keys - KEYS_SET_LIST_INDEX).empty?
|
178
|
-
|
179
|
-
# If result contains the key @set, then set result to the key's associated value.
|
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
|
-
|
193
|
-
end
|
194
207
|
|
195
|
-
|
196
|
-
|
208
|
+
# If result contains only the key @language, set result to null.
|
209
|
+
return nil if output_object.length == 1 && output_object.key?('@language')
|
197
210
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
211
|
+
# If active property is null or @graph, drop free-floating values as follows:
|
212
|
+
if (expanded_active_property || '@graph') == '@graph' &&
|
213
|
+
(output_object.key?('@value') || output_object.key?('@list') ||
|
214
|
+
((output_object.keys - KEY_ID).empty? && !framing))
|
215
|
+
# log_debug(" =>", depth: log_depth.to_i) { "empty top-level: " + output_object.inspect}
|
216
|
+
return nil
|
217
|
+
end
|
205
218
|
|
206
|
-
|
207
|
-
|
208
|
-
|
219
|
+
# Re-order result keys if ordering
|
220
|
+
if @options[:ordered]
|
221
|
+
output_object.keys.sort.each_with_object({}) { |kk, memo| memo[kk] = output_object[kk] }
|
222
|
+
else
|
223
|
+
output_object
|
224
|
+
end
|
209
225
|
else
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
226
|
+
# Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property.
|
227
|
+
return nil if input.nil? || active_property.nil? || expanded_active_property == '@graph'
|
228
|
+
|
229
|
+
# Apply property-scoped context
|
230
|
+
unless property_scoped_context.nil?
|
231
|
+
context = context.parse(property_scoped_context,
|
232
|
+
base: @options[:base],
|
233
|
+
override_protected: true)
|
234
|
+
end
|
235
|
+
# log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
|
236
|
+
|
237
|
+
context.expand_value(active_property, input, base: @options[:base])
|
221
238
|
end
|
222
|
-
log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil?
|
223
239
|
|
224
|
-
|
240
|
+
# log_debug(depth: log_depth.to_i) {" => #{result.inspect}"}
|
225
241
|
end
|
226
242
|
|
227
|
-
|
228
|
-
result
|
229
|
-
end
|
243
|
+
private
|
230
244
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
log_depth: nil)
|
240
|
-
nests = []
|
241
|
-
|
242
|
-
input_type = Array(input[type_key]).last
|
243
|
-
input_type = context.expand_iri(input_type, vocab: true, as_string: true, base: @options[:base]) if input_type
|
244
|
-
|
245
|
-
# Then, proceed and process each property and value in element as follows:
|
246
|
-
keys = @options[:ordered] ? input.keys.sort : input.keys
|
247
|
-
keys.each do |key|
|
248
|
-
# For each key and value in element, ordered lexicographically by key:
|
249
|
-
value = input[key]
|
250
|
-
expanded_property = context.expand_iri(key, vocab: true, base: @options[:base])
|
251
|
-
|
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.
|
253
|
-
next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
|
254
|
-
expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
|
255
|
-
|
256
|
-
warn "[DEPRECATION] Blank Node properties deprecated in JSON-LD 1.1." if
|
257
|
-
@options[:validate] &&
|
258
|
-
expanded_property.to_s.start_with?("_:") &&
|
259
|
-
context.processingMode('json-ld-1.1')
|
260
|
-
|
261
|
-
log_debug("expand property", depth: log_depth.to_i) {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
|
262
|
-
|
263
|
-
if expanded_property.nil?
|
264
|
-
log_debug(" => ", depth: log_depth.to_i) {"skip nil property"}
|
265
|
-
next
|
266
|
-
end
|
245
|
+
# Expand each key and value of element adding them to result
|
246
|
+
def expand_object(input, active_property, context, output_object,
|
247
|
+
expanded_active_property:,
|
248
|
+
framing:,
|
249
|
+
type_key:,
|
250
|
+
type_scoped_context:,
|
251
|
+
log_depth: nil)
|
252
|
+
nests = []
|
267
253
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
254
|
+
input_type = Array(input[type_key]).last
|
255
|
+
input_type = context.expand_iri(input_type, vocab: true, as_string: true, base: @options[:base]) if input_type
|
256
|
+
|
257
|
+
# Then, proceed and process each property and value in element as follows:
|
258
|
+
keys = @options[:ordered] ? input.keys.sort : input.keys
|
259
|
+
keys.each do |key|
|
260
|
+
# For each key and value in element, ordered lexicographically by key:
|
261
|
+
value = input[key]
|
262
|
+
expanded_property = context.expand_iri(key, vocab: true, base: @options[:base])
|
263
|
+
|
264
|
+
# If expanded property is null or it neither contains a colon (:) nor it is a keyword, drop key by continuing to the next key.
|
265
|
+
next if expanded_property.is_a?(RDF::URI) && expanded_property.relative?
|
266
|
+
|
267
|
+
expanded_property = expanded_property.to_s if expanded_property.is_a?(RDF::Resource)
|
268
|
+
|
269
|
+
warn "[DEPRECATION] Blank Node properties deprecated in JSON-LD 1.1." if
|
270
|
+
@options[:validate] &&
|
271
|
+
expanded_property.to_s.start_with?("_:") &&
|
272
|
+
context.processingMode('json-ld-1.1')
|
273
|
+
|
274
|
+
# log_debug("expand property", depth: log_depth.to_i) {"ap: #{active_property.inspect}, expanded: #{expanded_property.inspect}, value: #{value.inspect}"}
|
275
|
+
|
276
|
+
if expanded_property.nil?
|
277
|
+
# log_debug(" => ", depth: log_depth.to_i) {"skip nil property"}
|
278
|
+
next
|
279
|
+
end
|
280
|
+
|
281
|
+
if KEYWORDS.include?(expanded_property)
|
282
|
+
# If active property equals @reverse, an invalid reverse property map error has been detected and processing is aborted.
|
283
|
+
if expanded_active_property == '@reverse'
|
284
|
+
raise JsonLdError::InvalidReversePropertyMap,
|
285
|
+
"@reverse not appropriate at this point"
|
286
|
+
end
|
287
|
+
|
288
|
+
# If result has already an expanded property member (other than @type), an colliding keywords error has been detected and processing is aborted.
|
289
|
+
if output_object.key?(expanded_property) && !KEYS_INCLUDED_TYPE_REVERSE.include?(expanded_property)
|
290
|
+
raise JsonLdError::CollidingKeywords,
|
291
|
+
"#{expanded_property} already exists in result"
|
292
|
+
end
|
293
|
+
|
294
|
+
expanded_value = case expanded_property
|
295
|
+
when '@id'
|
296
|
+
# If expanded active property is `@annotation`, an invalid annotation error has been found and processing is aborted.
|
297
|
+
if expanded_active_property == '@annotation' && @options[:rdfstar]
|
298
|
+
raise JsonLdError::InvalidAnnotation,
|
299
|
+
"an annotation must not contain a property expanding to @id"
|
297
300
|
end
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
"
|
314
|
-
|
315
|
-
|
301
|
+
|
302
|
+
# If expanded property is @id and value is not a string, an invalid @id value error has been detected and processing is aborted
|
303
|
+
e_id = case value
|
304
|
+
when String
|
305
|
+
context.expand_iri(value, as_string: true, base: @options[:base], documentRelative: true)
|
306
|
+
when Array
|
307
|
+
# Framing allows an array of IRIs, and always puts values in an array
|
308
|
+
unless framing
|
309
|
+
raise JsonLdError::InvalidIdValue,
|
310
|
+
"value of @id must be a string unless framing: #{value.inspect}"
|
311
|
+
end
|
312
|
+
context.expand_iri(value, as_string: true, base: @options[:base], documentRelative: true)
|
313
|
+
value.map do |v|
|
314
|
+
unless v.is_a?(String)
|
315
|
+
raise JsonLdError::InvalidTypeValue,
|
316
|
+
"@id value must be a string or array of strings for framing: #{v.inspect}"
|
317
|
+
end
|
318
|
+
context.expand_iri(v, as_string: true, base: @options[:base], documentRelative: true)
|
319
|
+
end
|
320
|
+
when Hash
|
321
|
+
if framing
|
322
|
+
unless value.empty?
|
323
|
+
raise JsonLdError::InvalidTypeValue,
|
324
|
+
"value of @id must be a an empty object for framing: #{value.inspect}"
|
325
|
+
end
|
326
|
+
[{}]
|
327
|
+
elsif @options[:rdfstar]
|
328
|
+
# Result must have just a single statement
|
329
|
+
rei_node = expand(value, nil, context, log_depth: log_depth.to_i + 1)
|
330
|
+
|
331
|
+
# Node must not contain @reverse
|
332
|
+
if rei_node&.key?('@reverse')
|
333
|
+
raise JsonLdError::InvalidEmbeddedNode,
|
334
|
+
"Embedded node with @reverse"
|
335
|
+
end
|
336
|
+
statements = to_enum(:item_to_rdf, rei_node)
|
337
|
+
unless statements.count == 1
|
338
|
+
raise JsonLdError::InvalidEmbeddedNode,
|
339
|
+
"Embedded node with #{statements.size} statements"
|
340
|
+
end
|
341
|
+
rei_node
|
342
|
+
else
|
343
|
+
unless framing
|
344
|
+
raise JsonLdError::InvalidIdValue,
|
345
|
+
"value of @id must be a string unless framing: #{value.inspect}"
|
346
|
+
end
|
347
|
+
end
|
316
348
|
else
|
317
349
|
raise JsonLdError::InvalidIdValue,
|
318
|
-
|
350
|
+
"value of @id must be a string or hash if framing: #{value.inspect}"
|
319
351
|
end
|
320
|
-
else
|
321
|
-
raise JsonLdError::InvalidIdValue,
|
322
|
-
"value of @id must be a string or hash if framing: #{value.inspect}"
|
323
|
-
end
|
324
352
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
end
|
331
|
-
when '@included'
|
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
|
333
|
-
next if context.processingMode('json-ld-1.0')
|
334
|
-
included_result = as_array(expand(value, active_property, context,
|
335
|
-
framing: framing,
|
336
|
-
log_depth: log_depth.to_i + 1))
|
337
|
-
|
338
|
-
# Expanded values must be node objects
|
339
|
-
raise JsonLdError::InvalidIncludedValue, "values of @included must expand to node objects" unless included_result.all? {|e| node?(e)}
|
340
|
-
# As other properties may alias to @included, add this to any other previously expanded values
|
341
|
-
Array(output_object['@included']) + included_result
|
342
|
-
when '@type'
|
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.
|
344
|
-
log_debug("@type", depth: log_depth.to_i) {"value: #{value.inspect}"}
|
345
|
-
e_type = case value
|
346
|
-
when Array
|
347
|
-
value.map do |v|
|
348
|
-
raise JsonLdError::InvalidTypeValue,
|
349
|
-
"@type value must be a string or array of strings: #{v.inspect}" unless v.is_a?(String)
|
350
|
-
type_scoped_context.expand_iri(v,
|
351
|
-
as_string: true,
|
352
|
-
base: @options[:base],
|
353
|
-
documentRelative: true,
|
354
|
-
vocab: true)
|
353
|
+
# Use array form if framing
|
354
|
+
if framing
|
355
|
+
as_array(e_id)
|
356
|
+
else
|
357
|
+
e_id
|
355
358
|
end
|
356
|
-
when
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
359
|
+
when '@included'
|
360
|
+
# 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
|
361
|
+
next if context.processingMode('json-ld-1.0')
|
362
|
+
|
363
|
+
included_result = as_array(expand(value, active_property, context,
|
364
|
+
framing: framing,
|
365
|
+
log_depth: log_depth.to_i + 1))
|
366
|
+
|
367
|
+
# Expanded values must be node objects
|
368
|
+
unless included_result.all? do |e|
|
369
|
+
node?(e)
|
370
|
+
end
|
371
|
+
raise JsonLdError::InvalidIncludedValue,
|
372
|
+
"values of @included must expand to node objects"
|
373
|
+
end
|
374
|
+
|
375
|
+
# As other properties may alias to @included, add this to any other previously expanded values
|
376
|
+
Array(output_object['@included']) + included_result
|
377
|
+
when '@type'
|
378
|
+
# 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.
|
379
|
+
# log_debug("@type", depth: log_depth.to_i) {"value: #{value.inspect}"}
|
380
|
+
e_type = case value
|
381
|
+
when Array
|
382
|
+
value.map do |v|
|
383
|
+
unless v.is_a?(String)
|
384
|
+
raise JsonLdError::InvalidTypeValue,
|
385
|
+
"@type value must be a string or array of strings: #{v.inspect}"
|
386
|
+
end
|
372
387
|
type_scoped_context.expand_iri(v,
|
373
388
|
as_string: true,
|
374
389
|
base: @options[:base],
|
375
390
|
documentRelative: true,
|
376
391
|
vocab: true)
|
377
|
-
end
|
378
|
-
|
379
|
-
|
380
|
-
|
392
|
+
end
|
393
|
+
when String
|
394
|
+
type_scoped_context.expand_iri(value,
|
395
|
+
as_string: true,
|
396
|
+
base: @options[:base],
|
397
|
+
documentRelative: true,
|
398
|
+
vocab: true)
|
399
|
+
when Hash
|
400
|
+
if !framing
|
401
|
+
raise JsonLdError::InvalidTypeValue,
|
402
|
+
"@type value must be a string or array of strings: #{value.inspect}"
|
403
|
+
elsif value.keys.length == 1 &&
|
404
|
+
type_scoped_context.expand_iri(value.keys.first, vocab: true, base: @options[:base]) == '@default'
|
405
|
+
# Expand values of @default, which must be a string, or array of strings expanding to IRIs
|
406
|
+
[{ '@default' => Array(value['@default']).map do |v|
|
407
|
+
unless v.is_a?(String)
|
408
|
+
raise JsonLdError::InvalidTypeValue,
|
409
|
+
"@type default value must be a string or array of strings: #{v.inspect}"
|
410
|
+
end
|
411
|
+
type_scoped_context.expand_iri(v,
|
412
|
+
as_string: true,
|
413
|
+
base: @options[:base],
|
414
|
+
documentRelative: true,
|
415
|
+
vocab: true)
|
416
|
+
end }]
|
417
|
+
elsif !value.empty?
|
418
|
+
raise JsonLdError::InvalidTypeValue,
|
419
|
+
"@type value must be a an empty object for framing: #{value.inspect}"
|
420
|
+
else
|
421
|
+
[{}]
|
422
|
+
end
|
381
423
|
else
|
382
|
-
|
424
|
+
raise JsonLdError::InvalidTypeValue,
|
425
|
+
"@type value must be a string or array of strings: #{value.inspect}"
|
383
426
|
end
|
384
|
-
else
|
385
|
-
raise JsonLdError::InvalidTypeValue,
|
386
|
-
"@type value must be a string or array of strings: #{value.inspect}"
|
387
|
-
end
|
388
427
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
428
|
+
e_type = Array(output_object['@type']) + Array(e_type)
|
429
|
+
# Use array form if framing
|
430
|
+
framing || e_type.length > 1 ? e_type : e_type.first
|
431
|
+
when '@graph'
|
432
|
+
# 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.
|
433
|
+
value = expand(value, '@graph', context,
|
434
|
+
framing: framing,
|
435
|
+
log_depth: log_depth.to_i + 1)
|
436
|
+
as_array(value)
|
437
|
+
when '@value'
|
438
|
+
# If expanded property is @value and input contains @type: json, accept any value.
|
439
|
+
# If expanded property is @value and value is not a scalar or null, an invalid value object value error has been detected and processing is aborted. (In 1.1, @value can have any JSON value of @type is @json or the property coerces to @json).
|
440
|
+
# 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.
|
441
|
+
# If framing, always use array form, unless null
|
442
|
+
if input_type == '@json' && context.processingMode('json-ld-1.1')
|
443
|
+
value
|
444
|
+
else
|
445
|
+
case value
|
446
|
+
when String, TrueClass, FalseClass, Numeric then (framing ? [value] : value)
|
447
|
+
when nil
|
448
|
+
output_object['@value'] = nil
|
449
|
+
next
|
450
|
+
when Array
|
451
|
+
unless framing
|
452
|
+
raise JsonLdError::InvalidValueObjectValue,
|
453
|
+
"@value value may not be an array unless framing: #{value.inspect}"
|
454
|
+
end
|
455
|
+
value
|
456
|
+
when Hash
|
457
|
+
unless value.empty? && framing
|
458
|
+
raise JsonLdError::InvalidValueObjectValue,
|
459
|
+
"@value value must be a an empty object for framing: #{value.inspect}"
|
460
|
+
end
|
461
|
+
[value]
|
462
|
+
else
|
463
|
+
raise JsonLdError::InvalidValueObjectValue,
|
464
|
+
"Value of #{expanded_property} must be a scalar or null: #{value.inspect}"
|
465
|
+
end
|
466
|
+
end
|
467
|
+
when '@language'
|
468
|
+
# If expanded property is @language and value is not a string, an invalid language-tagged string error has been detected and processing is aborted. Otherwise, set expanded value to lowercased value.
|
469
|
+
# If framing, always use array form, unless null
|
406
470
|
case value
|
407
|
-
when String
|
408
|
-
|
409
|
-
|
410
|
-
|
471
|
+
when String
|
472
|
+
unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(value)
|
473
|
+
warn "@language must be valid BCP47: #{value.inspect}"
|
474
|
+
end
|
475
|
+
if @options[:lowercaseLanguage]
|
476
|
+
(framing ? [value.downcase] : value.downcase)
|
477
|
+
else
|
478
|
+
(framing ? [value] : value)
|
479
|
+
end
|
411
480
|
when Array
|
412
|
-
|
413
|
-
|
414
|
-
|
481
|
+
unless framing
|
482
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
483
|
+
"@language value may not be an array unless framing: #{value.inspect}"
|
484
|
+
end
|
485
|
+
value.each do |v|
|
486
|
+
unless /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/.match?(v)
|
487
|
+
warn "@language must be valid BCP47: #{v.inspect}"
|
488
|
+
end
|
489
|
+
end
|
490
|
+
@options[:lowercaseLanguage] ? value.map(&:downcase) : value
|
415
491
|
when Hash
|
416
|
-
|
417
|
-
|
418
|
-
|
492
|
+
unless value.empty? && framing
|
493
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
494
|
+
"@language value must be a an empty object for framing: #{value.inspect}"
|
495
|
+
end
|
419
496
|
[value]
|
420
497
|
else
|
421
|
-
raise JsonLdError::
|
422
|
-
|
423
|
-
end
|
424
|
-
end
|
425
|
-
when '@language'
|
426
|
-
# If expanded property is @language and value is not a string, an invalid language-tagged string error has been detected and processing is aborted. Otherwise, set expanded value to lowercased value.
|
427
|
-
# If framing, always use array form, unless null
|
428
|
-
case value
|
429
|
-
when String
|
430
|
-
if value !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/
|
431
|
-
warn "@language must be valid BCP47: #{value.inspect}"
|
498
|
+
raise JsonLdError::InvalidLanguageTaggedString,
|
499
|
+
"Value of #{expanded_property} must be a string: #{value.inspect}"
|
432
500
|
end
|
433
|
-
|
434
|
-
|
501
|
+
when '@direction'
|
502
|
+
# If expanded property is @direction and value is not either 'ltr' or 'rtl', an invalid base direction error has been detected and processing is aborted. Otherwise, set expanded value to value.
|
503
|
+
# If framing, always use array form, unless null
|
504
|
+
case value
|
505
|
+
when 'ltr', 'rtl' then (framing ? [value] : value)
|
506
|
+
when Array
|
507
|
+
unless framing
|
508
|
+
raise JsonLdError::InvalidBaseDirection,
|
509
|
+
"@direction value may not be an array unless framing: #{value.inspect}"
|
510
|
+
end
|
511
|
+
unless value.all? do |v|
|
512
|
+
%w[
|
513
|
+
ltr rtl
|
514
|
+
].include?(v) || (v.is_a?(Hash) && v.empty?)
|
515
|
+
end
|
516
|
+
raise JsonLdError::InvalidBaseDirection,
|
517
|
+
"@direction must be one of 'ltr', 'rtl', or an array of those if framing #{value.inspect}"
|
518
|
+
end
|
519
|
+
value
|
520
|
+
when Hash
|
521
|
+
unless value.empty? && framing
|
522
|
+
raise JsonLdError::InvalidBaseDirection,
|
523
|
+
"@direction value must be a an empty object for framing: #{value.inspect}"
|
524
|
+
end
|
525
|
+
[value]
|
435
526
|
else
|
436
|
-
|
527
|
+
raise JsonLdError::InvalidBaseDirection,
|
528
|
+
"Value of #{expanded_property} must be one of 'ltr' or 'rtl': #{value.inspect}"
|
437
529
|
end
|
438
|
-
when
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
warn "@language must be valid BCP47: #{v.inspect}"
|
444
|
-
end
|
530
|
+
when '@index'
|
531
|
+
# If expanded property is @index and value is not a string, an invalid @index value error has been detected and processing is aborted. Otherwise, set expanded value to value.
|
532
|
+
unless value.is_a?(String)
|
533
|
+
raise JsonLdError::InvalidIndexValue,
|
534
|
+
"Value of @index is not a string: #{value.inspect}"
|
445
535
|
end
|
446
|
-
@options[:lowercaseLanguage] ? value.map(&:downcase) : value
|
447
|
-
when Hash
|
448
|
-
raise JsonLdError::InvalidLanguageTaggedString,
|
449
|
-
"@language value must be a an empty object for framing: #{value.inspect}" unless
|
450
|
-
value.empty? && framing
|
451
|
-
[value]
|
452
|
-
else
|
453
|
-
raise JsonLdError::InvalidLanguageTaggedString,
|
454
|
-
"Value of #{expanded_property} must be a string: #{value.inspect}"
|
455
|
-
end
|
456
|
-
when '@direction'
|
457
|
-
# If expanded property is @direction and value is not either 'ltr' or 'rtl', an invalid base direction error has been detected and processing is aborted. Otherwise, set expanded value to value.
|
458
|
-
# If framing, always use array form, unless null
|
459
|
-
case value
|
460
|
-
when 'ltr', 'rtl' then (framing ? [value] : value)
|
461
|
-
when Array
|
462
|
-
raise JsonLdError::InvalidBaseDirection,
|
463
|
-
"@direction value may not be an array unless framing: #{value.inspect}" unless framing
|
464
|
-
raise JsonLdError::InvalidBaseDirection,
|
465
|
-
"@direction must be one of 'ltr', 'rtl', or an array of those if framing #{value.inspect}" unless value.all? {|v| %w(ltr rtl).include?(v) || v.is_a?(Hash) && v.empty?}
|
466
536
|
value
|
467
|
-
when
|
468
|
-
|
469
|
-
"@direction value must be a an empty object for framing: #{value.inspect}" unless
|
470
|
-
value.empty? && framing
|
471
|
-
[value]
|
472
|
-
else
|
473
|
-
raise JsonLdError::InvalidBaseDirection,
|
474
|
-
"Value of #{expanded_property} must be one of 'ltr' or 'rtl': #{value.inspect}"
|
475
|
-
end
|
476
|
-
when '@index'
|
477
|
-
# If expanded property is @index and value is not a string, an invalid @index value error has been detected and processing is aborted. Otherwise, set expanded value to value.
|
478
|
-
raise JsonLdError::InvalidIndexValue,
|
479
|
-
"Value of @index is not a string: #{value.inspect}" unless value.is_a?(String)
|
480
|
-
value
|
481
|
-
when '@list'
|
482
|
-
# If expanded property is @graph:
|
483
|
-
|
484
|
-
# If active property is null or @graph, continue with the next key from element to remove the free-floating list.
|
485
|
-
next if (expanded_active_property || '@graph') == '@graph'
|
486
|
-
|
487
|
-
# Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
|
488
|
-
value = expand(value, active_property, context,
|
489
|
-
framing: framing,
|
490
|
-
log_depth: log_depth.to_i + 1)
|
537
|
+
when '@list'
|
538
|
+
# If expanded property is @graph:
|
491
539
|
|
492
|
-
|
493
|
-
|
540
|
+
# If active property is null or @graph, continue with the next key from element to remove the free-floating list.
|
541
|
+
next if (expanded_active_property || '@graph') == '@graph'
|
494
542
|
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
543
|
+
# Otherwise, initialize expanded value to the result of using this algorithm recursively passing active context, active property, and value for element.
|
544
|
+
value = expand(value, active_property, context,
|
545
|
+
framing: framing,
|
546
|
+
log_depth: log_depth.to_i + 1)
|
499
547
|
|
500
|
-
|
501
|
-
|
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.
|
503
|
-
expand(value, active_property, context,
|
504
|
-
framing: framing,
|
505
|
-
log_depth: log_depth.to_i + 1)
|
506
|
-
when '@reverse'
|
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.
|
508
|
-
raise JsonLdError::InvalidReverseValue,
|
509
|
-
"@reverse value must be an object: #{value.inspect}" unless value.is_a?(Hash)
|
510
|
-
|
511
|
-
# Otherwise
|
512
|
-
# Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
|
513
|
-
value = expand(value, '@reverse', context,
|
514
|
-
framing: framing,
|
515
|
-
log_depth: log_depth.to_i + 1)
|
548
|
+
# Spec FIXME: need to be sure that result is an array
|
549
|
+
value = as_array(value)
|
516
550
|
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
551
|
+
# Make sure that no member of value contains an annotation object
|
552
|
+
if value.any? { |n| n.is_a?(Hash) && n.key?('@annotation') }
|
553
|
+
raise JsonLdError::InvalidAnnotation,
|
554
|
+
"A list element must not contain @annotation."
|
555
|
+
end
|
556
|
+
|
557
|
+
value
|
558
|
+
when '@set'
|
559
|
+
# 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.
|
560
|
+
expand(value, active_property, context,
|
561
|
+
framing: framing,
|
562
|
+
log_depth: log_depth.to_i + 1)
|
563
|
+
when '@reverse'
|
564
|
+
# If expanded property is @reverse and value is not a JSON object, an invalid @reverse value error has been detected and processing is aborted.
|
565
|
+
unless value.is_a?(Hash)
|
566
|
+
raise JsonLdError::InvalidReverseValue,
|
567
|
+
"@reverse value must be an object: #{value.inspect}"
|
568
|
+
end
|
569
|
+
|
570
|
+
# Otherwise
|
571
|
+
# Initialize expanded value to the result of using this algorithm recursively, passing active context, @reverse as active property, and value as element.
|
572
|
+
value = expand(value, '@reverse', context,
|
573
|
+
framing: framing,
|
574
|
+
log_depth: log_depth.to_i + 1)
|
575
|
+
|
576
|
+
# 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:
|
577
|
+
if value.key?('@reverse')
|
578
|
+
# log_debug("@reverse", depth: log_depth.to_i) {"double reverse: #{value.inspect}"}
|
579
|
+
value['@reverse'].each do |property, item|
|
580
|
+
# If result does not have a property member, create one and set its value to an empty array.
|
581
|
+
# Append item to the value of the property member of result.
|
582
|
+
(output_object[property] ||= []).concat([item].flatten.compact)
|
583
|
+
end
|
524
584
|
end
|
525
|
-
end
|
526
585
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
586
|
+
# If expanded value contains members other than @reverse:
|
587
|
+
if !value.key?('@reverse') || value.length > 1
|
588
|
+
# If result does not have an @reverse member, create one and set its value to an empty JSON object.
|
589
|
+
reverse_map = output_object['@reverse'] ||= {}
|
590
|
+
value.each do |property, items|
|
591
|
+
next if property == '@reverse'
|
592
|
+
|
593
|
+
items.each do |item|
|
594
|
+
if value?(item) || list?(item)
|
595
|
+
raise JsonLdError::InvalidReversePropertyValue,
|
596
|
+
item.inspect
|
597
|
+
end
|
598
|
+
merge_value(reverse_map, property, item)
|
537
599
|
end
|
538
|
-
merge_value(reverse_map, property, item)
|
539
600
|
end
|
540
601
|
end
|
602
|
+
|
603
|
+
# Continue with the next key from element
|
604
|
+
next
|
605
|
+
when '@default', '@embed', '@explicit', '@omitDefault', '@preserve', '@requireAll'
|
606
|
+
next unless framing
|
607
|
+
|
608
|
+
# Framing keywords
|
609
|
+
[expand(value, expanded_property, context,
|
610
|
+
framing: framing,
|
611
|
+
log_depth: log_depth.to_i + 1)].flatten
|
612
|
+
when '@nest'
|
613
|
+
# Add key to nests
|
614
|
+
nests << key
|
615
|
+
# Continue with the next key from element
|
616
|
+
next
|
617
|
+
when '@annotation'
|
618
|
+
# Skip unless rdfstar option is set
|
619
|
+
next unless @options[:rdfstar]
|
620
|
+
|
621
|
+
as_array(expand(value, '@annotation', context,
|
622
|
+
framing: framing,
|
623
|
+
log_depth: log_depth.to_i + 1))
|
624
|
+
else
|
625
|
+
# Skip unknown keyword
|
626
|
+
next
|
541
627
|
end
|
542
628
|
|
543
|
-
#
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
framing: framing,
|
550
|
-
log_depth: log_depth.to_i + 1)
|
551
|
-
].flatten
|
552
|
-
when '@nest'
|
553
|
-
# Add key to nests
|
554
|
-
nests << key
|
555
|
-
# Continue with the next key from element
|
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))
|
563
|
-
else
|
564
|
-
# Skip unknown keyword
|
629
|
+
# Unless expanded value is null, set the expanded property member of result to expanded value.
|
630
|
+
# log_debug("expand #{expanded_property}", depth: log_depth.to_i) { expanded_value.inspect}
|
631
|
+
unless expanded_value.nil? && expanded_property == '@value' && input_type != '@json'
|
632
|
+
output_object[expanded_property] =
|
633
|
+
expanded_value
|
634
|
+
end
|
565
635
|
next
|
566
636
|
end
|
567
637
|
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
638
|
+
container = context.container(key)
|
639
|
+
expanded_value = if context.coerce(key) == '@json'
|
640
|
+
# In JSON-LD 1.1, values can be native JSON
|
641
|
+
{ "@value" => value, "@type" => "@json" }
|
642
|
+
elsif container.include?('@language') && value.is_a?(Hash)
|
643
|
+
# Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
|
573
644
|
|
574
|
-
|
575
|
-
|
576
|
-
# In JSON-LD 1.1, values can be native JSON
|
577
|
-
{"@value" => value, "@type" => "@json"}
|
578
|
-
elsif container.include?('@language') && value.is_a?(Hash)
|
579
|
-
# Otherwise, if key's container mapping in active context is @language and value is a JSON object then value is expanded from a language map as follows:
|
580
|
-
|
581
|
-
# Set multilingual array to an empty array.
|
582
|
-
ary = []
|
583
|
-
|
584
|
-
# For each key-value pair language-language value in value, ordered lexicographically by language
|
585
|
-
keys = @options[:ordered] ? value.keys.sort : value.keys
|
586
|
-
keys.each do |k|
|
587
|
-
expanded_k = context.expand_iri(k, vocab: true, as_string: true, base: @options[:base])
|
588
|
-
|
589
|
-
if k !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_k != '@none'
|
590
|
-
warn "@language must be valid BCP47: #{k.inspect}"
|
591
|
-
end
|
645
|
+
# Set multilingual array to an empty array.
|
646
|
+
ary = []
|
592
647
|
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
648
|
+
# For each key-value pair language-language value in value, ordered lexicographically by language
|
649
|
+
keys = @options[:ordered] ? value.keys.sort : value.keys
|
650
|
+
keys.each do |k|
|
651
|
+
expanded_k = context.expand_iri(k, vocab: true, as_string: true, base: @options[:base])
|
597
652
|
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
v['@direction'] = context.direction(key) if context.direction(key)
|
602
|
-
ary << v if item
|
603
|
-
end
|
604
|
-
end
|
653
|
+
if k !~ /^[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*$/ && expanded_k != '@none'
|
654
|
+
warn "@language must be valid BCP47: #{k.inspect}"
|
655
|
+
end
|
605
656
|
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
index_key = context.term_definitions[key].index || '@index'
|
613
|
-
|
614
|
-
# While processing index keys, if container includes @type, clear type-scoped term definitions
|
615
|
-
container_context = if container.include?('@type') && context.previous_context
|
616
|
-
context.previous_context
|
617
|
-
elsif container.include?('@id') && context.term_definitions[key]
|
618
|
-
id_context = context.term_definitions[key].context if context.term_definitions[key]
|
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
|
625
|
-
else
|
626
|
-
context
|
627
|
-
end
|
657
|
+
[value[k]].flatten.each do |item|
|
658
|
+
# item must be a string, otherwise an invalid language map value error has been detected and processing is aborted.
|
659
|
+
unless item.nil? || item.is_a?(String)
|
660
|
+
raise JsonLdError::InvalidLanguageMapValue,
|
661
|
+
"Expected #{item.inspect} to be a string"
|
662
|
+
end
|
628
663
|
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
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)
|
664
|
+
# Append a JSON object to expanded value that consists of two key-value pairs: (@value-item) and (@language-lowercased language).
|
665
|
+
v = { '@value' => item }
|
666
|
+
v['@language'] = (@options[:lowercaseLanguage] ? k.downcase : k) unless expanded_k == '@none'
|
667
|
+
v['@direction'] = context.direction(key) if context.direction(key)
|
668
|
+
ary << v if item
|
669
|
+
end
|
638
670
|
end
|
639
|
-
map_context ||= container_context
|
640
671
|
|
641
|
-
|
672
|
+
ary
|
673
|
+
elsif container.intersect?(CONTAINER_INDEX_ID_TYPE) && value.is_a?(Hash)
|
674
|
+
# Otherwise, if key's container mapping in active context contains @index, @id, @type and value is a JSON object then value is expanded from an index map as follows:
|
675
|
+
|
676
|
+
# Set ary to an empty array.
|
677
|
+
ary = []
|
678
|
+
index_key = context.term_definitions[key].index || '@index'
|
679
|
+
|
680
|
+
# While processing index keys, if container includes @type, clear type-scoped term definitions
|
681
|
+
container_context = if container.include?('@type') && context.previous_context
|
682
|
+
context.previous_context
|
683
|
+
elsif container.include?('@id') && context.term_definitions[key]
|
684
|
+
id_context = context.term_definitions[key].context if context.term_definitions[key]
|
685
|
+
if id_context.nil?
|
686
|
+
context
|
687
|
+
else
|
688
|
+
# log_debug("expand", depth: log_depth.to_i) {"id_context: #{id_context.inspect}"}
|
689
|
+
context.parse(id_context, base: @options[:base], propagate: false)
|
690
|
+
end
|
691
|
+
else
|
692
|
+
context
|
693
|
+
end
|
642
694
|
|
643
|
-
#
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
#
|
652
|
-
|
653
|
-
|
654
|
-
end
|
655
|
-
if index_key == '@index'
|
656
|
-
item['@index'] ||= k unless expanded_k == '@none'
|
657
|
-
elsif value?(item)
|
658
|
-
raise JsonLdError::InvalidValueObject, "Attempt to add illegal key to value object: #{index_key}"
|
659
|
-
else
|
660
|
-
# Expand key based on term
|
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])
|
663
|
-
item[index_property] = [expanded_k].concat(Array(item[index_property])) unless expanded_k == '@none'
|
664
|
-
end
|
665
|
-
when container.include?('@id')
|
666
|
-
# Indexed graph by graph name
|
667
|
-
if !graph?(item) && container.include?('@graph')
|
668
|
-
item = {'@graph' => as_array(item)}
|
669
|
-
end
|
670
|
-
# Expand k document relative
|
671
|
-
expanded_k = container_context.expand_iri(k, as_string: true, base: @options[:base], documentRelative: true) unless expanded_k == '@none'
|
672
|
-
item['@id'] ||= expanded_k unless expanded_k == '@none'
|
673
|
-
when container.include?('@type')
|
674
|
-
item['@type'] = [expanded_k].concat(Array(item['@type'])) unless expanded_k == '@none'
|
695
|
+
# For each key-value in the object:
|
696
|
+
keys = @options[:ordered] ? value.keys.sort : value.keys
|
697
|
+
keys.each do |k|
|
698
|
+
# 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
|
699
|
+
if container.include?('@type') && container_context.term_definitions[k]
|
700
|
+
map_context = container_context.term_definitions[k].context
|
701
|
+
end
|
702
|
+
unless map_context.nil?
|
703
|
+
# log_debug("expand", depth: log_depth.to_i) {"map_context: #{map_context.inspect}"}
|
704
|
+
map_context = container_context.parse(map_context, base: @options[:base],
|
705
|
+
propagate: false)
|
675
706
|
end
|
707
|
+
map_context ||= container_context
|
708
|
+
|
709
|
+
expanded_k = container_context.expand_iri(k, vocab: true, as_string: true, base: @options[:base])
|
710
|
+
|
711
|
+
# Initialize index value to the result of using this algorithm recursively, passing active context, key as active property, and index value as element.
|
712
|
+
index_value = expand([value[k]].flatten, key, map_context,
|
713
|
+
framing: framing,
|
714
|
+
from_map: true,
|
715
|
+
log_depth: log_depth.to_i + 1)
|
716
|
+
index_value.each do |item|
|
717
|
+
if container.include?('@index')
|
718
|
+
# Indexed graph by graph name
|
719
|
+
item = { '@graph' => as_array(item) } if !graph?(item) && container.include?('@graph')
|
720
|
+
if index_key == '@index'
|
721
|
+
item['@index'] ||= k unless expanded_k == '@none'
|
722
|
+
elsif value?(item)
|
723
|
+
raise JsonLdError::InvalidValueObject, "Attempt to add illegal key to value object: #{index_key}"
|
724
|
+
else
|
725
|
+
# Expand key based on term
|
726
|
+
expanded_k = if k == '@none'
|
727
|
+
'@none'
|
728
|
+
else
|
729
|
+
container_context.expand_value(index_key, k,
|
730
|
+
base: @options[:base])
|
731
|
+
end
|
732
|
+
index_property = container_context.expand_iri(index_key, vocab: true, as_string: true,
|
733
|
+
base: @options[:base])
|
734
|
+
item[index_property] = [expanded_k].concat(Array(item[index_property])) unless expanded_k == '@none'
|
735
|
+
end
|
736
|
+
elsif container.include?('@id')
|
737
|
+
# Indexed graph by graph name
|
738
|
+
item = { '@graph' => as_array(item) } if !graph?(item) && container.include?('@graph')
|
739
|
+
# Expand k document relative
|
740
|
+
unless expanded_k == '@none'
|
741
|
+
expanded_k = container_context.expand_iri(k, as_string: true, base: @options[:base],
|
742
|
+
documentRelative: true)
|
743
|
+
end
|
744
|
+
item['@id'] ||= expanded_k unless expanded_k == '@none'
|
745
|
+
elsif container.include?('@type')
|
746
|
+
item['@type'] = [expanded_k].concat(Array(item['@type'])) unless expanded_k == '@none'
|
747
|
+
end
|
676
748
|
|
677
|
-
|
678
|
-
|
749
|
+
# Append item to expanded value.
|
750
|
+
ary << item
|
751
|
+
end
|
679
752
|
end
|
753
|
+
ary
|
754
|
+
else
|
755
|
+
# Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
|
756
|
+
expand(value, key, context,
|
757
|
+
framing: framing,
|
758
|
+
log_depth: log_depth.to_i + 1)
|
680
759
|
end
|
681
|
-
ary
|
682
|
-
else
|
683
|
-
# Otherwise, initialize expanded value to the result of using this algorithm recursively, passing active context, key for active property, and value for element.
|
684
|
-
expand(value, key, context,
|
685
|
-
framing: framing,
|
686
|
-
log_depth: log_depth.to_i + 1)
|
687
|
-
end
|
688
760
|
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
|
761
|
+
# If expanded value is null, ignore key by continuing to the next key from element.
|
762
|
+
if expanded_value.nil?
|
763
|
+
# log_debug(" => skip nil value", depth: log_depth.to_i)
|
764
|
+
next
|
765
|
+
end
|
695
766
|
|
696
|
-
|
697
|
-
if container.first == '@list' && container.length == 1 && !list?(expanded_value)
|
698
|
-
log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
|
699
|
-
expanded_value = {'@list' => as_array(expanded_value)}
|
700
|
-
end
|
701
|
-
log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
|
767
|
+
# log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
|
702
768
|
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
{'@graph' => as_array(v)}
|
769
|
+
# 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.
|
770
|
+
if container.first == '@list' && container.length == 1 && !list?(expanded_value)
|
771
|
+
# log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
|
772
|
+
expanded_value = { '@list' => as_array(expanded_value) }
|
708
773
|
end
|
709
|
-
|
774
|
+
# log_debug(depth: log_depth.to_i) {" => #{expanded_value.inspect}"}
|
710
775
|
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
# If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
|
718
|
-
raise JsonLdError::InvalidReversePropertyValue,
|
719
|
-
item.inspect if value?(item) || list?(item)
|
720
|
-
|
721
|
-
# If reverse map has no expanded property member, create one and initialize its value to an empty array.
|
722
|
-
# Append item to the value of the expanded property member of reverse map.
|
723
|
-
merge_value(reverse_map, expanded_property, item)
|
776
|
+
# convert expanded value to @graph if container specifies it
|
777
|
+
if container.first == '@graph' && container.length == 1
|
778
|
+
# log_debug(" => ", depth: log_depth.to_i) { "convert #{expanded_value.inspect} to list"}
|
779
|
+
expanded_value = as_array(expanded_value).map do |v|
|
780
|
+
{ '@graph' => as_array(v) }
|
781
|
+
end
|
724
782
|
end
|
725
|
-
|
726
|
-
# Otherwise, if key is
|
727
|
-
#
|
728
|
-
(
|
729
|
-
#
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
783
|
+
|
784
|
+
# Otherwise, if the term definition associated to key indicates that it is a reverse property
|
785
|
+
# Spec FIXME: this is not an otherwise.
|
786
|
+
if (td = context.term_definitions[key]) && td.reverse_property
|
787
|
+
# If result has no @reverse member, create one and initialize its value to an empty JSON object.
|
788
|
+
reverse_map = output_object['@reverse'] ||= {}
|
789
|
+
[expanded_value].flatten.each do |item|
|
790
|
+
# If item is a value object or list object, an invalid reverse property value has been detected and processing is aborted.
|
791
|
+
if value?(item) || list?(item)
|
792
|
+
raise JsonLdError::InvalidReversePropertyValue,
|
793
|
+
item.inspect
|
794
|
+
end
|
795
|
+
|
796
|
+
# If reverse map has no expanded property member, create one and initialize its value to an empty array.
|
797
|
+
# Append item to the value of the expanded property member of reverse map.
|
798
|
+
merge_value(reverse_map, expanded_property, item)
|
799
|
+
end
|
800
|
+
else
|
801
|
+
# Otherwise, if key is not a reverse property:
|
802
|
+
# If result does not have an expanded property member, create one and initialize its value to an empty array.
|
803
|
+
(output_object[expanded_property] ||= []).tap do |memo|
|
804
|
+
# expanded_value is either Array[Hash] or Hash; in both case append to memo without flatten
|
805
|
+
if expanded_value.is_a?(Array)
|
806
|
+
memo.concat(expanded_value)
|
807
|
+
else # Hash
|
808
|
+
memo << expanded_value
|
809
|
+
end
|
734
810
|
end
|
735
811
|
end
|
736
812
|
end
|
737
|
-
end
|
738
813
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
814
|
+
# For each key in nests, recusively expand content
|
815
|
+
nests.each do |key|
|
816
|
+
nest_context = context.term_definitions[key].context if context.term_definitions[key]
|
817
|
+
nest_context = if nest_context.nil?
|
818
|
+
context
|
819
|
+
else
|
820
|
+
# log_debug("expand", depth: log_depth.to_i) {"nest_context: #{nest_context.inspect}"}
|
821
|
+
context.parse(nest_context, base: @options[:base],
|
822
|
+
override_protected: true)
|
823
|
+
end
|
824
|
+
nested_values = as_array(input[key])
|
825
|
+
nested_values.each do |nv|
|
826
|
+
raise JsonLdError::InvalidNestValue, nv.inspect unless
|
827
|
+
nv.is_a?(Hash) && nv.keys.none? do |k|
|
828
|
+
nest_context.expand_iri(k, vocab: true, base: @options[:base]) == '@value'
|
829
|
+
end
|
830
|
+
|
831
|
+
expand_object(nv, active_property, nest_context, output_object,
|
832
|
+
framing: framing,
|
833
|
+
expanded_active_property: expanded_active_property,
|
834
|
+
type_key: type_key,
|
835
|
+
type_scoped_context: type_scoped_context,
|
836
|
+
log_depth: log_depth.to_i + 1)
|
837
|
+
end
|
759
838
|
end
|
760
839
|
end
|
761
840
|
end
|