Module: JSON::LD::Compact

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

Instance Method Summary collapse

Methods included from Utils

#blank_node?, #index?, #list?, #node?, #node_reference?, #value?

Instance Method Details

#compact(element, property = nil) ⇒ Array, Hash

Compact an expanded Array or Hash given an active property and a context.

Parameters:

  • element (Array, Hash)
  • property (String) (defaults to: nil)

    (nil)

Returns:



11
12
13
14
15
16
17
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
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
# File 'lib/json/ld/compact.rb', line 11

def compact(element, property = nil)
  if property.nil?
    debug("compact") {"element: #{element.inspect}, ec: #{context.inspect}"}
  else
    debug("compact") {"property: #{property.inspect}"}
  end
  case element
  when Array
    # 1) If value is an array, process each item in value recursively using
    #    this algorithm, passing copies of the active context and the
    #    active property.
    debug("compact") {"Array #{element.inspect}"}
    result = depth {element.map {|v| compact(v, property)}}

    # If element has a single member and the active property has no
    # @container mapping to @list or @set, the compacted value is that
    # member; otherwise the compacted value is element
    if result.length == 1 && @options[:compactArrays]
      debug("=> extract single element: #{result.first.inspect}")
      result.first
    else
      debug("=> array result: #{result.inspect}")
      result
    end
  when Hash
    # 2) Otherwise, if element is an object:
    result = {}

    if k = %w(@list @set @value).detect {|container| element.has_key?(container)}
      debug("compact") {"#{k}: container(#{property}) = #{context.container(property)}"}
    end

    k ||= '@id' if element.keys == ['@id']

    case k
    when '@value', '@id'
      # If element has an @value property or element is a node reference, return the result of performing Value Compaction on element using active property.
      v = context.compact_value(property, element, :depth => @depth)
      debug("compact") {"value optimization for #{property}, return as #{v.inspect}"}
      return v
    when '@list'
      # Otherwise, if the active property has a @container mapping to @list and element has a corresponding @list property, recursively compact that property's value passing a copy of the active context and the active property ensuring that the result is an array with all null values removed.

      # If there already exists a value for active property in element and the full IRI of property is also coerced to @list, return an error.
      # FIXME: check for full-iri list coercion

      # Otherwise store the resulting array as value of active property if empty or property otherwise.
      compacted_key = context.compact_iri('@list', :position => :predicate, :depth => @depth)
      v = depth { compact(element[k], property) }

      # Return either the result as an array, as an object with a key of @list (or appropriate alias from active context
      v = [v].compact unless v.is_a?(Array)
      unless context.container(property) == '@list'
        v = {compacted_key => v}
        if element['@index']
          compacted_key = context.compact_iri('@index', :position => :predicate, :depth => @depth)
          v[compacted_key] = element['@index']
        end
      end
      debug("compact") {"@list result, return as #{v.inspect}"}
      return v
    end

    # Check for property generators before continuing with other elements
    # For each term pg in the active context which is a property generator
    # Select property generator terms by shortest term
    context.mappings.keys.sort.each do |term|
      next unless context.mapping(term).is_a?(Array)
      # Using the first expanded IRI p associated with the property generator
      expanded_iris = context.mapping(term).map(&:to_s)
      p = expanded_iris.first.to_s

      # Skip to the next property generator term unless p is a property of element
      next unless element.has_key?(p)

      debug("compact") {"check pg #{term}: #{expanded_iris}"}

      # For each node n which is a value of p in element
      node_values = []
      element[p].dup.each do |n|
        # For each expanded IRI pi associated with the property generator other than p
        next unless expanded_iris[1..-1].all? do |pi|
          debug("compact") {"check #{pi} for (#{n.inspect})"}
          element.has_key?(pi) && element[pi].any? do |ni|
            nodesEquivalent?(n, ni)
          end
        end

        # Remove n as a value of all p and pi in element
        debug("compact") {"removed matched value #{n.inspect} from #{expanded_iris.inspect}"}
        expanded_iris.each do |pi|
          # FIXME: This removes all values equivalent to n, not just the first
          element[pi] = element[pi].reject {|ni| nodesEquivalent?(n, ni)}
        end

        # Add the result of performing the compaction algorithm on n to pg to output
        node_values << n
      end

      # If there are node_values, or all the values from expanded_iris are empty, add node_values to result, and remove the expanded_iris as keys from element
      if node_values.length > 0 || expanded_iris.all? {|pi| element.has_key?(pi) && element[pi].empty?}
        debug("compact") {"compact extracted pg values"}
        result[term] = depth { compact(node_values, term)}
        result[term] = [result[term]] if !result[term].is_a?(Array) && context.container(term) == '@set'

        debug("compact") {"remove empty pg keys from element"}
        expanded_iris.each do |pi|
          debug(" =>") {"#{pi}? #{element.fetch(pi, []).empty?}"}
          element.delete(pi) if element.fetch(pi, []).empty?
        end
      end
    end

    # Otherwise, for each property and value in element:
    element.each do |key, value|
      debug("compact") {"#{key}: #{value.inspect}"}

      if %(@id @type).include?(key)
        position = key == '@id' ? :subject : :type
        compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)

        result[compacted_key] = case value
        when String
          # If value is a string, the compacted value is the result of performing IRI Compaction on value.
          debug {" => compacted string for #{key}"}
          context.compact_iri(value, :position => position, :depth => @depth)
        when Array
          # Otherwise, value must be an array. Perform IRI Compaction on every entry of value. If value contains just one entry, value is set to that entry
          compacted_value = value.map {|v2| context.compact_iri(v2, :position => position, :depth => @depth)}
          debug {" => compacted value(#{key}): #{compacted_value.inspect}"}
          compacted_value = compacted_value.first if compacted_value.length == 1 && @options[:compactArrays]
          compacted_value
        end
      elsif key == '@index' && context.container(property) == '@index'
        # Skip the annotation key if annotations being applied
        next
      else
        if value.empty?
          # Make sure that an empty array is preserved
          compacted_key = context.compact_iri(key, :position => :predicate, :depth => @depth)
          next if compacted_key.nil?
          result[compacted_key] = value
          next
        end

        # For each item in value:
        value = [value] if key == '@index' && value.is_a?(String)
        raise ProcessingError, "found #{value.inspect} for #{key} of #{element.inspect}" unless value.is_a?(Array)
        value.each do |item|
          compacted_key = context.compact_iri(key, :position => :predicate, :value => item, :depth => @depth)

          # Result for this item, typically the output object itself
          item_result = result
          item_key = compacted_key
          debug {" => compacted key: #{compacted_key.inspect} for #{item.inspect}"}
          next if compacted_key.nil?

          # Language maps and annotations
          if field = %w(@language @index).detect {|kk| context.container(compacted_key) == kk}
            item_result = result[compacted_key] ||= Hash.new
            item_key = item[field]
          end

          compacted_item = depth {self.compact(item, compacted_key)}
          debug {" => compacted value: #{compacted_value.inspect}"}

          case item_result[item_key]
          when Array
            item_result[item_key] << compacted_item
          when nil
            if !compacted_value.is_a?(Array) && context.container(compacted_key) == '@set'
              compacted_item = [compacted_item].compact
              debug {" => as @set: #{compacted_item.inspect}"}
            end
            item_result[item_key] = compacted_item
          else
            item_result[item_key] = [item_result[item_key], compacted_item]
          end
        end
      end
    end

    # Re-order result keys
    r = Hash.ordered
    result.keys.kw_sort.each {|kk| r[kk] = result[kk]}
    r
  else
    # For other types, the compacted value is the element value
    debug("compact") {element.class.to_s}
    element
  end
end