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:



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

def cleanup_preserve(input)
  depth do
    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.size)
      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
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:



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

def frame(state, subjects, frame, options = {})
  depth do
    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

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

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

#remove_dependents(id, embeds) ⇒ Object

recursively remove dependent dangling embeds



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
# File 'lib/json/ld/frame.rb', line 338

def remove_dependents(id, embeds)

  depth do
    # 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
end