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_resource, #blank_node?, #compare_values, #has_property, #has_value, #index?, #list?, #node?, #node_or_ref?, #node_reference?, #value?

Instance Method Details

#expand(input, active_property, context, ordered: true) ⇒ 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: true)

    (true) Ensure output objects have keys ordered properly

Returns:

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


18
19
20
21
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
# File 'lib/json/ld/expand.rb', line 18

def expand(input, active_property, context, ordered: true)
  framing = @options[:processingMode].include?("expand-frame")
  #log_debug("expand") {"input: #{input.inspect}, active_property: #{active_property.inspect}, context: #{context.inspect}"}
  result = case input
  when Array
    # If element is an array,
    is_list = context.container(active_property) == '@list'
    value = input.map do |v|
      # 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)

      # If the active property is @list or its container mapping is set to @list, the expanded item must not be an array or a list object, otherwise a list of lists error has been detected and processing is aborted.
      raise JsonLdError::ListOfLists,
            "A list may not contain another list" if
            is_list && (v.is_a?(Array) || list?(v))
      v
    end.flatten.compact

    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 = {}
    keys = ordered ? input.keys.kw_sort : input.keys

    # See if keys mapping to @type have terms with a local context
    input.keys.select do |key|
      context.expand_iri(key, vocab: true) == '@type'
    end.each do |key|
      Array(input[key]).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)

    #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.keys & %w(@language @type)).length < 2
        # 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 Array(output_object['@language']).join('').to_s.empty?
      output_object.delete('@type') if Array(output_object['@type']).join('').to_s.empty?

      # If the value of result's @value key is null, then set result to null.
      return nil if Array(output_object['@value']).empty?

      if !Array(output_object['@value']).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.fetch('@type', "")).all? {|t|
              t.is_a?(String) && context.expand_iri(t, vocab: true, log_depth: @options[:log_depth]).is_a?(RDF::URI) ||
              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.keys.any? {|k| %w(@set @list).include?(k)}
      # 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.keys.include?('@set')
    end

    # If result contains only the key @language, set result to null.
    return nil if output_object.keys == %w(@language)

    # If active property is null or @graph, drop free-floating values as follows:
    if (active_property || '@graph') == '@graph' &&
      (output_object.keys.any? {|k| %w(@value @list).include?(k)} ||
       (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.kw_sort.inject({}) {|map, kk| map[kk] = output_object[kk]; map}
    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