Module: JSON::LD::Expand
Overview
Expand module, used as part of API
Constant Summary collapse
- CONTAINER_INDEX_ID_TYPE =
The following constant is used to reduce object allocations
Set['@index', '@id', '@type'].freeze
- KEY_ID =
%w[@id].freeze
- KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION =
%w[@value @language @type @index @direction @annotation].freeze
- KEYS_SET_LIST_INDEX =
%w[@set @list @index].freeze
- KEYS_INCLUDED_TYPE_REVERSE =
%w[@included @type @reverse].freeze
Instance Method Summary collapse
-
#expand(input, active_property, context, framing: false, from_map: false, log_depth: nil) ⇒ Array<Hash{String => Object}>
Expand an Array or Object given an active context and performing local context expansion.
Methods included from Utils
#add_value, #as_array, #as_resource, #blank_node?, #compare_values, #graph?, #has_value?, #index?, #list?, #node?, #node_or_ref?, #node_reference?, #property?, #simple_graph?, #value?
Instance Method Details
#expand(input, active_property, context, framing: false, from_map: false, log_depth: nil) ⇒ Array<Hash{String => Object}>
Expand an Array or Object given an active context and performing local context expansion.
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/json/ld/expand.rb', line 31 def (input, active_property, context, framing: false, from_map: false, log_depth: nil) # log_debug("expand", depth: log_depth.to_i) {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"} framing = false if active_property == '@default' if active_property = context.(active_property, vocab: true, as_string: true, base: @options[:base]) end # Use a term-specific context, if defined, based on the non-type-scoped context. if active_property && context.term_definitions[active_property] property_scoped_context = context.term_definitions[active_property].context end # log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{property_scoped_context.inspect}"} unless property_scoped_context.nil? case input when Array # If element is an array, is_list = context.container(active_property).include?('@list') input.each_with_object([]) do |v, memo| # Initialize expanded item to the result of using this algorithm recursively, passing active context, active property, and item as element. v = (v, active_property, context, framing: framing, from_map: from_map, log_depth: log_depth.to_i + 1) # 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 if is_list && v.is_a?(Array) # Make sure that no member of v contains an annotation object if v.any? { |n| n.is_a?(Hash) && n.key?('@annotation') } raise JsonLdError::InvalidAnnotation, "A list element must not contain @annotation." end v = { "@list" => v } end case v when nil then nil when Array then memo.concat(v) else memo << v end end when Hash if context.previous_context = input.keys.inject({}) do |memo, key| memo.merge(key => context.(key, vocab: true, as_string: true, base: @options[:base])) end # Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference revert_context = !from_map && !.value?('@value') && .values != ['@id'] # If there's a previous context, the context was type-scoped # log_debug("expand", depth: log_depth.to_i) {"previous_context: #{context.previous_context.inspect}"} if revert_context context = context.previous_context if revert_context end # Apply property-scoped context after reverting term-scoped context unless property_scoped_context.nil? context = context.parse(property_scoped_context, base: @options[:base], override_protected: true) end # log_debug("expand", depth: log_depth.to_i) {"after property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil? # 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. if input.key?('@context') context = context.parse(input['@context'], base: @options[:base]) # log_debug("expand", depth: log_depth.to_i) {"context: #{context.inspect}"} end # Set the type-scoped context to the context on input, for use later type_scoped_context = context output_object = {} # See if keys mapping to @type have terms with a local context type_key = nil (input.keys - %w[@context]).sort .select { |k| context.(k, vocab: true, base: @options[:base]) == '@type' } .each do |tk| type_key ||= tk # Side effect saves the first found key mapping to @type Array(input[tk]).sort.each do |term| if type_scoped_context.term_definitions[term] term_context = type_scoped_context.term_definitions[term].context end unless term_context.nil? # log_debug("expand", depth: log_depth.to_i) {"term_context[#{term}]: #{term_context.inspect}"} context = context.parse(term_context, base: @options[:base], propagate: false) end end end # Process each key and value in element. Ignores @nesting content (input, active_property, context, output_object, expanded_active_property: , framing: framing, type_key: type_key, type_scoped_context: type_scoped_context, log_depth: log_depth.to_i + 1) # log_debug("output object", depth: log_depth.to_i) {output_object.inspect} # If result contains the key @value: if value?(output_object) keys = output_object.keys unless (keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX_DIRECTION).empty? # 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. raise JsonLdError::InvalidValueObject, "value object has unknown keys: #{output_object.inspect}" end if keys.include?('@type') && !(keys & %w[@language @direction]).empty? # @type is inconsistent with either @language or @direction raise JsonLdError::InvalidValueObject, "value object must not include @type with either @language or @direction: #{output_object.inspect}" end if output_object.key?('@language') && Array(output_object['@language']).empty? output_object.delete('@language') end type_is_json = output_object['@type'] == '@json' output_object.delete('@type') if output_object.key?('@type') && Array(output_object['@type']).empty? # If the value of result's @value key is null, then set result to null and @type is not @json. ary = Array(output_object['@value']) return nil if ary.empty? && !type_is_json if output_object['@type'] == '@json' && context.processingMode('json-ld-1.1') # Any value of @value is okay if @type: @json elsif !ary.all? { |v| v.is_a?(String) || (v.is_a?(Hash) && v.empty?) } && output_object.key?('@language') # 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. raise JsonLdError::InvalidLanguageTaggedValue, "when @language is used, @value must be a string: #{output_object.inspect}" elsif output_object['@type'] && (!Array(output_object['@type']).all? do |t| (t.is_a?(String) && RDF::URI(t).valid? && !t.start_with?('_:')) || (t.is_a?(Hash) && t.empty?) end || (!framing && !output_object['@type'].is_a?(String))) # 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. raise JsonLdError::InvalidTypedValue, "value of @type must be an IRI or '@json': #{output_object.inspect}" elsif !framing && !output_object.fetch('@type', '').is_a?(String) && RDF::URI(t).valid? && !t.start_with?('_:') # 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. raise JsonLdError::InvalidTypedValue, "value of @type must be an IRI or '@json': #{output_object.inspect}" end elsif !output_object.fetch('@type', []).is_a?(Array) # 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. output_object['@type'] = [output_object['@type']] elsif output_object.key?('@set') || output_object.key?('@list') # Otherwise, if result contains the key @set or @list: # 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. unless (output_object.keys - KEYS_SET_LIST_INDEX).empty? raise JsonLdError::InvalidSetOrListObject, "@set or @list may only contain @index: #{output_object.keys.inspect}" end # If result contains the key @set, then set result to the key's associated value. return output_object['@set'] if output_object.key?('@set') elsif output_object['@annotation'] # Otherwise, if result contains the key @annotation, # the array value must all be node objects without an @id property, otherwise, an invalid annotation error has been detected and processing is aborted. unless output_object['@annotation'].all? { |o| node?(o) && !o.key?('@id') } raise JsonLdError::InvalidAnnotation, "@annotation must reference node objects without @id." end # Additionally, the property must not be used if there is no active property, or the expanded active property is @graph. if %w[@graph @included].include?( || '@graph') raise JsonLdError::InvalidAnnotation, "@annotation must not be used on a top-level object." end end # If result contains only the key @language, set result to null. return nil if output_object.length == 1 && output_object.key?('@language') # If active property is null or @graph, drop free-floating values as follows: if ( || '@graph') == '@graph' && (output_object.key?('@value') || output_object.key?('@list') || ((output_object.keys - KEY_ID).empty? && !framing)) # log_debug(" =>", depth: log_depth.to_i) { "empty top-level: " + output_object.inspect} return nil end # Re-order result keys if ordering if @options[:ordered] output_object.keys.sort.each_with_object({}) { |kk, memo| memo[kk] = output_object[kk] } else output_object end else # Otherwise, unless the value is a number, expand the value according to the Value Expansion rules, passing active property. return nil if input.nil? || active_property.nil? || == '@graph' # Apply property-scoped context unless property_scoped_context.nil? context = context.parse(property_scoped_context, base: @options[:base], override_protected: true) end # log_debug("expand", depth: log_depth.to_i) {"property_scoped_context: #{context.inspect}"} unless property_scoped_context.nil? context.(active_property, input, base: @options[:base]) end # log_debug(depth: log_depth.to_i) {" => #{result.inspect}"} end |