Module: JSON::LD::Frame

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

Instance Method Summary collapse

Methods included from Utils

#add_value, #as_resource, #blank_node?, #compare_values, #has_property, #has_value, #index?, #list?, #node?, #node_reference?, #value?

Instance Method Details

#cleanup_preserve(input) ⇒ Array, Hash

Replace @preserve keys with the values, also replace @null with null

Parameters:

Returns:



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
# File 'lib/json/ld/frame.rb', line 157

def cleanup_preserve(input)
  result = case input
  when Array
    # If, after replacement, an array contains only the value null remove the value, leaving an empty array.
    input.map {|o| cleanup_preserve(o)}.compact
  when Hash
    output = Hash.new
    input.each do |key, value|
      if key == '@preserve'
        # replace all key-value pairs where the key is @preserve with the value from the key-pair
        output = cleanup_preserve(value)
      else
        v = cleanup_preserve(value)

        # Because we may have added a null value to an array, we need to clean that up, if we possible
        v = v.first if v.is_a?(Array) && v.length == 1 &&
          context.expand_iri(key) != "@graph" && context.container(key).nil?
        output[key] = v
      end
    end
    output
  when '@null'
    # If the value from the key-pair is @null, replace the value with nul
    nil
  else
    input
  end
  result
end

#frame(state, subjects, frame, options = {}) ⇒ Object

Frame input. Input is expected in expanded form, but frame is in compacted form.

Parameters:

  • state (Hash{Symbol => Object})

    Current framing state

  • subjects (Array<String>)

    The subjects to filter

  • frame (Hash{String => Object})
  • options (Hash{Symbol => Object}) (defaults to: {})

    ({})

Options Hash (options):

  • :parent (Hash{String => Object}) — default: nil

    Parent subject or top-level array

  • :property (String) — default: nil

    The parent property.

Raises:



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

def frame(state, subjects, frame, options = {})
  parent, property = options[:parent], options[:property]
  # Validate the frame
  validate_frame(state, frame)
  frame = frame.first if frame.is_a?(Array)

  # Get values for embedOn and explicitOn
  flags = {
    embed: get_frame_flag(frame, options, :embed),
    explicit: get_frame_flag(frame, options, :explicit),
    requireAll: get_frame_flag(frame, options, :requireAll),
  }

  # Create a set of matched subjects by filtering subjects by checking the map of flattened subjects against frame
  # This gives us a hash of objects indexed by @id
  matches = filter_subjects(state, subjects, frame, flags)

  # For each id and node from the set of matched subjects ordered by id
  matches.keys.kw_sort.each do |id|
    subject = matches[id]

    if flags[:embed] == '@link' && state[:link].has_key?(id)
      # TODO: may want to also match an existing linked subject
      # against the current frame ... so different frames could
      # produce different subjects that are only shared in-memory
      # when the frames are the same

      # add existing linked subject
      add_frame_output(parent, property, state[:link][id])
      next
    end

    # Note: In order to treat each top-level match as a compartmentalized result, clear the unique embedded subjects map when the property is None, which only occurs at the top-level.
    state = state.merge(uniqueEmbeds: {}) if property.nil?

    output = {'@id' => id}
    state[:link][id] = output

    # if embed is @never or if a circular reference would be created by an embed, the subject cannot be embedded, just add the reference; note that a circular reference won't occur when the embed flag is `@link` as the above check will short-circuit before reaching this point
    if flags[:embed] == '@never' || creates_circular_reference(subject, state[:subjectStack])
      add_frame_output(parent, property, output)
      next
    end

    # if only the last match should be embedded
    if flags[:embed] == '@last'
      # remove any existing embed
      remove_embed(state, id) if state[:uniqueEmbeds].include?(id)
      state[:uniqueEmbeds][id] = {
        parent: parent,
        property: property
      }
    end

    # push matching subject onto stack to enable circular embed checks
    state[:subjectStack] << subject

    # iterate over subject properties in order
    subject.keys.kw_sort.each do |prop|
      objects = subject[prop]

      # copy keywords to output
      if prop.start_with?('@')
        output[prop] = objects.dup
        next
      end

      # explicit is on and property isn't in frame, skip processing
      next if flags[:explicit] && !frame.has_key?(prop)

      # add objects
      objects.each do |o|
        case
        when list?(o)
          # add empty list
          list = {'@list' => []}
          add_frame_output(output, prop, list)

          src = o['@list']
          src.each do |oo|
            if node_reference?(oo)
              subframe = frame[prop].first['@list'] if frame[prop].is_a?(Array) && frame[prop].first.is_a?(Hash)
              subframe ||= create_implicit_frame(flags)
              frame(state, [oo['@id']], subframe, options.merge(parent: list, property: '@list'))
            else
              add_frame_output(list, '@list', oo.dup)
            end
          end
        when node_reference?(o)
          # recurse into subject reference
          subframe = frame[prop] || create_implicit_frame(flags)
          frame(state, [o['@id']], subframe, options.merge(parent: output, property: prop))
        else
          # include other values automatically
          add_frame_output(output, prop, o.dup)
        end
      end
    end

    # handle defaults in order
    frame.keys.kw_sort.reject {|p| p.start_with?('@')}.each do |prop|
      # if omit default is off, then include default values for properties that appear in the next frame but are not in the matching subject
      n = frame[prop].first || {}
      omit_default_on = get_frame_flag(n, options, :omitDefault)
      if !omit_default_on && !output[prop]
        preserve = n.fetch('@default', '@null').dup
        preserve = [preserve] unless preserve.is_a?(Array)
        output[prop] = [{'@preserve' => preserve}]
      end
    end

    # If frame has @reverse, embed identified nodes having this subject as a value of the associated property.
    frame.fetch('@reverse', {}).each do |reverse_prop, subframe|
      state[:subjects].each do |r_id, node|
        if Array(node[reverse_prop]).any? {|v| v['@id'] == id}
          # Node has property referencing this subject
          # recurse into  reference
          (output['@reverse'] ||= {})[reverse_prop] ||= []
          frame(state, [r_id], subframe, options.merge(parent: output['@reverse'][reverse_prop]))
        end
      end
    end

    # add output to parent
    add_frame_output(parent, property, output)

    # pop matching subject from circular ref-checking stack
    state[:subjectStack].pop()
  end
end

#remove_dependents(id, embeds) ⇒ Object

recursively remove dependent dangling embeds



335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/json/ld/frame.rb', line 335

def remove_dependents(id, embeds)
  # get embed keys as a separate array to enable deleting keys in map
  embeds.each do |id_dep, e|
    p = e.fetch(:parent, {}) if e.is_a?(Hash)
    next unless p.is_a?(Hash)
    pid = p.fetch('@id', nil)
    if pid == id
      embeds.delete(id_dep)
      remove_dependents(id_dep, embeds)
    end
  end
end