Module: JSON::LD::Expand

Includes:
Utils
Included in:
API
Defined in:
lib/json/ld/expand.rb

Overview

Expand module, used as part of API

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) ⇒ 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) (defaults to: false)

    (true) Ensure output objects have keys ordered properly

  • framing (Boolean) (defaults to: false)

    (false) Special rules for expanding a frame

Returns:

  • (Array<Hash{String => Object}>)


22
23
24
25
26
27
28
29
30
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
# File 'lib/json/ld/expand.rb', line 22

def expand(input, active_property, context, ordered: false, framing: false)
  #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
  framing = false if active_property == '@default'
  result = case input
  when Array
    # If element is an array,
    is_list = context.container(active_property) == %w(@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)

      # 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 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

    output_object = {}

    # See if keys mapping to @type have terms with a local context
    input.each_pair do |key, val|
      next unless context.expand_iri(key, vocab: true) == '@type'
      Array(val).sort.each do |term|
        term_context = context.term_definitions[term].context if context.term_definitions[term]
        context = term_context ? context.parse(term_context) : context
      end
    end

    # Process each key and value in element. Ignores @nesting content
    expand_object(input, active_property, context, output_object, 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 - %w(@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 !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 - %w(@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 (active_property || '@graph') == '@graph' &&
      (output_object.key?('@value') || output_object.key?('@list') ||
       (output_object.keys - %w(@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? || active_property == '@graph'
    context.expand_value(active_property, input, log_depth: @options[:log_depth])
  end

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