Module: JSON::LD::Frame

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

Instance Method Summary collapse

Methods included from Utils

#as_resource, #blank_node?, #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:



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

def cleanup_preserve(input)
  depth do
    #debug("cleanup preserve") {input.inspect}
    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.ordered
      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
    #debug(" => ") {result.inspect}
    result
  end
end

#frame(state, nodes, frame, parent, property) ⇒ Object

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

Parameters:

  • state (Hash{Symbol => Object})

    Current framing state

  • nodes (Hash{String => Hash})

    Map of flattened nodes

  • frame (Hash{String => Object})
  • parent (Hash{String => Object})

    Parent node or top-level array

  • property (String)

    Property referencing this frame, or null for array.

Raises:



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

def frame(state, nodes, frame, parent, property)
  depth do
    debug("frame") {"state: #{state.inspect}"}
    debug("frame") {"nodes: #{nodes.keys.inspect}"}
    debug("frame") {"frame: #{frame.to_json(JSON_STATE)}"}
    debug("frame") {"parent: #{parent.to_json(JSON_STATE)}"}
    debug("frame") {"property: #{property.inspect}"}
    # Validate the frame
    validate_frame(state, frame)

    # Create a set of matched nodes by filtering nodes by checking the map of flattened nodes against frame
    # This gives us a hash of objects indexed by @id
    matches = filter_nodes(state, nodes, frame)
    debug("frame") {"matches: #{matches.keys.inspect}"}

    # Get values for embedOn and explicitOn
    embed = get_frame_flag(state, frame, 'embed');
    explicit = get_frame_flag(state, frame, 'explicit');
    debug("frame") {"embed: #{embed.inspect}, explicit: #{explicit.inspect}"}
  
    # For each id and node from the set of matched nodes ordered by id
    matches.keys.kw_sort.each do |id|
      element = matches[id]
      # If the active property is null, set the map of embeds in state to an empty map
      state = state.merge(:embeds => {}) if property.nil?

      output = {'@id' => id}
    
      # prepare embed meta info
      embedded_node = {:parent => parent, :property => property}
    
      # If embedOn is true, and id is in map of embeds from state
      if embed && (existing = state[:embeds].fetch(id, nil))
        # only overwrite an existing embed if it has already been added to its
        # parent -- otherwise its parent is somewhere up the tree from this
        # embed and the embed would occur twice once the tree is added
        embed = false
      
        embed = if existing[:parent].is_a?(Array)
          # If existing has a parent which is an array containing a JSON object with @id equal to id, element has already been embedded and can be overwritten, so set embedOn to true
          existing[:parent].detect {|p| p['@id'] == id}
        else
          # Otherwise, existing has a parent which is a node definition. Set embedOn to true if any of the items in parent property is a node definition or node reference for id because the embed can be overwritten
          existing[:parent].fetch(existing[:property], []).any? do |v|
            v.is_a?(Hash) && v.fetch('@id', nil) == id
          end
        end
        debug("frame") {"embed now: #{embed.inspect}"}

        # If embedOn is true, existing is already embedded but can be overwritten
        remove_embed(state, id) if embed
      end

      unless embed
        # not embedding, add output without any other properties
        add_frame_output(state, parent, property, output)
      else
        # Add embed to map of embeds for id
        state[:embeds][id] = embedded_node
        debug("frame") {"add embedded_node: #{embedded_node.inspect}"}
    
        # Process each property and value in the matched node as follows
        element.keys.kw_sort.each do |prop|
          value = element[prop]
          if prop[0,1] == '@'
            # If property is a keyword, add property and a copy of value to output and continue with the next property from node
            output[prop] = value.dup
            next
          end

          # If property is not in frame:
          unless frame.has_key?(prop)
            debug("frame") {"non-framed property #{prop}"}
            # If explicitOn is false, Embed values from node in output using node as element and property as active property
            embed_values(state, element, prop, output) unless explicit
            
            # Continue to next property
            next
          end
      
          # Process each item from value as follows
          value.each do |item|
            debug("frame") {"value property #{prop.inspect} == #{item.inspect}"}
            
            # FIXME: If item is a JSON object with the key @list
            if list?(item)
              # create a JSON object named list with the key @list and the value of an empty array
              list = {'@list' => []}
              
              # Append list to property in output
              add_frame_output(state, output, prop, list)
              
              # Process each listitem in the @list array as follows
              item['@list'].each do |listitem|
                if node_reference?(listitem)
                  itemid = listitem['@id']
                  debug("frame") {"list item of #{prop} recurse for #{itemid.inspect}"}

                  # If listitem is a node reference process listitem recursively using this algorithm passing a new map of nodes that contains the @id of listitem as the key and the node reference as the value. Pass the first value from frame for property as frame, list as parent, and @list as active property.
                  frame(state, {itemid => @node_map[itemid]}, frame[prop].first, list, '@list')
                else
                  # Otherwise, append a copy of listitem to @list in list.
                  debug("frame") {"list item of #{prop} non-node ref #{listitem.inspect}"}
                  add_frame_output(state, list, '@list', listitem)
                end
              end
            elsif node_reference?(item)
              # If item is a node reference process item recursively
              # Recurse into sub-objects
              itemid = item['@id']
              debug("frame") {"value property #{prop} recurse for #{itemid.inspect}"}
              
              # passing a new map as nodes that contains the @id of item as the key and the node reference as the value. Pass the first value from frame for property as frame, output as parent, and property as active property
              frame(state, {itemid => @node_map[itemid]}, frame[prop].first, output, prop)
            else
              # Otherwise, append a copy of item to active property in output.
              debug("frame") {"value property #{prop} non-node ref #{item.inspect}"}
              add_frame_output(state, output, prop, item)
            end
          end
        end

        # Process each property and value in frame in lexographical order, where property is not a keyword, as follows:
        frame.keys.kw_sort.each do |prop|
          next if prop[0,1] == '@' || output.has_key?(prop)
          property_frame = frame[prop]
          debug("frame") {"frame prop: #{prop.inspect}. property_frame: #{property_frame.inspect}"}

          # Set property frame to the first item in value or a newly created JSON object if value is empty.
          property_frame = property_frame.first || {}

          # Skip to the next property in frame if property is in output or if property frame contains @omitDefault which is true or if it does not contain @omitDefault but the value of omit default flag true.
          next if output.has_key?(prop) || get_frame_flag(state, property_frame, 'omitDefault')

          # Set the value of property in output to a new JSON object with a property @preserve and a value that is a copy of the value of @default in frame if it exists, or the string @null otherwise
          default = property_frame.fetch('@default', '@null').dup
          default = [default] unless default.is_a?(Array)
          output[prop] = [{"@preserve" => default.compact}]
          debug("=>") {"add default #{output[prop].inspect}"}
        end
      
        # Add output to parent
        add_frame_output(state, parent, property, output)
      end
    end
  end
end

#remove_dependents(id, embeds) ⇒ Object

recursively remove dependent dangling embeds



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/json/ld/frame.rb', line 294

def remove_dependents(id, embeds)
  debug("frame") {"remove dependents for #{id}"}

  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
        debug("frame") {"remove #{id_dep} from embeds"}
        embeds.delete(id_dep)
        remove_dependents(id_dep, embeds)
      end
    end
  end
end