Module: JSON::LD::Expand

Includes:
Utils
Included in:
API
Defined in:
vendor/bundler/ruby/2.6.0/bundler/gems/json-ld-94ee7791d19f/lib/json/ld/expand.rb

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.new(%w(@index @id @type)).freeze
CONTAINER_GRAPH_INDEX =
%w(@graph @index).freeze
CONTAINER_INDEX =
%w(@index).freeze
CONTAINER_ID =
%w(@id).freeze
CONTAINER_LIST =
%w(@list).freeze
CONTAINER_TYPE =
%w(@type).freeze
CONTAINER_GRAPH_ID =
%w(@graph @id).freeze
KEYS_VALUE_LANGUAGE_TYPE_INDEX =
%w(@value @language @type @index).freeze
KEYS_SET_LIST_INDEX =
%w(@set @list @index).freeze
KEYS_INCLUDED_TYPE =
%w(@included @type).freeze

Instance Method Summary collapse

Methods included from Utils

#add_value, #as_array, #as_resource, #blank_node?, #compare_values, #graph?, #has_property, #has_value, #index?, #list?, #node?, #node_or_ref?, #node_reference?, #simple_graph?, #value?

Instance Method Details

#expand(input, active_property, context, ordered: false, framing: false, from_map: false) ⇒ Array<Hash{String => Object}>

Expand an Array or Object given an active context and performing local context expansion.

Parameters:

  • input (Array, Hash)
  • active_property (String)
  • context (Context)
  • ordered (Boolean)

    (true) Ensure output objects have keys ordered properly

  • framing (Boolean)

    (false) Special rules for expanding a frame

  • from_map (Boolean)

    Expanding from a map, which could be an @type map, so don't clear out context term definitions

Returns:



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
# File 'vendor/bundler/ruby/2.6.0/bundler/gems/json-ld-94ee7791d19f/lib/json/ld/expand.rb', line 36

def expand(input, active_property, context, ordered: false, framing: false, from_map: false)
  #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
  framing = false if active_property == '@default'
  expanded_active_property = context.expand_iri(active_property, vocab: true).to_s if active_property

  # Use a term-specific context, if defined, based on the non-type-scoped context.
  property_scoped_context = context.term_definitions[active_property].context if active_property && context.term_definitions[active_property]

  result = case input
  when Array
    # If element is an array,
    is_list = context.container(active_property) == CONTAINER_LIST
    value = 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 = expand(v, active_property, context, ordered: ordered, framing: framing, from_map: from_map)

      # 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
      v = {"@list" => v} if is_list && v.is_a?(Array)

      case v
      when nil then nil
      when Array then memo.concat(v)
      else            memo << v
      end
    end

    value
  when Hash
    if context.previous_context
      expanded_key_map = input.keys.inject({}) {|memo, key| memo.merge(key => context.expand_iri(key, vocab: true).to_s)}
      # Revert any previously type-scoped term definitions, unless this is from a map, a value object or a subject reference
      revert_context = !from_map &&
        !expanded_key_map.values.include?('@value') &&
        !(expanded_key_map.values == ['@id'])

      # If there's a previous context, the context was type-scoped
      context = context.previous_context if revert_context
    end

    # Apply property-scoped context after reverting term-scoped context
    context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context

    # 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.has_key?('@context')
      context = context.parse(input.delete('@context'))
      #log_debug("expand") {"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.sort.
      select {|k| context.expand_iri(k, vocab: true, quite: true) == '@type'}.
      each do |tk|

      type_key ||= tk # Side effect saves the first found key mapping to @type
      Array(input[tk]).sort.each do |term|
        term_context = type_scoped_context.term_definitions[term].context if type_scoped_context.term_definitions[term]
        context = term_context ? context.parse(term_context, propagate: false) : context
      end
    end

    # Process each key and value in element. Ignores @nesting content
    expand_object(input, active_property, context, output_object,
                  expanded_active_property: expanded_active_property,
                  type_scoped_context: type_scoped_context,
                  type_key: type_key,
                  ordered: ordered,
                  framing: framing)

    #log_debug("output object") {output_object.inspect}

    # If result contains the key @value:
    if value?(output_object)
      unless (output_object.keys - KEYS_VALUE_LANGUAGE_TYPE_INDEX).empty? &&
             !(output_object.key?('@language') && output_object.key?('@type'))
        # The result must not contain any keys other than @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

      output_object.delete('@language') if output_object.key?('@language') && Array(output_object['@language']).empty?
      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.
      ary = Array(output_object['@value'])
      return nil if ary.empty?

      if (context.processingMode || 'json-ld-1.0') > 'json-ld-1.0' && output_object['@type'] == '@json'
        # 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.has_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 !Array(output_object['@type']).all? {|t|
              t.is_a?(String) && RDF::URI(t).absolute? && !t.start_with?('_:') ||
              t.is_a?(Hash) && t.empty?}
        # 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: #{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.
      raise JsonLdError::InvalidSetOrListObject,
            "@set or @list may only contain @index: #{output_object.keys.inspect}" unless
            (output_object.keys - KEYS_SET_LIST_INDEX).empty?

      # If result contains the key @set, then set result to the key's associated value.
      return output_object['@set'] if output_object.key?('@set')
    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 (expanded_active_property || '@graph') == '@graph' &&
      (output_object.key?('@value') || output_object.key?('@list') ||
       (output_object.keys - CONTAINER_ID).empty? && !framing)
      #log_debug(" =>") { "empty top-level: " + output_object.inspect}
      return nil
    end

    # Re-order result keys if ordering
    if 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? || expanded_active_property == '@graph'

    # Apply property-scoped context
    context = property_scoped_context ? context.parse(property_scoped_context, override_protected: true) : context

    context.expand_value(active_property, input, log_depth: @options[:log_depth])
  end

  #log_debug {" => #{result.inspect}"}
  result
end