Module: JSON::LD::Frame
Instance Method Summary collapse
-
#cleanup_preserve(input) ⇒ Array, Hash
Replace @preserve keys with the values, also replace @null with null.
-
#flatten ⇒ Array{Hash}
Flatten input, used in framing.
-
#frame(state, subjects, frame, parent, property) ⇒ Object
Frame input.
-
#get_framing_subjects(subjects, input, namer) ⇒ Object
Build hash of subjects used for framing.
-
#remove_dependents(id, embeds) ⇒ Object
recursively remove dependent dangling embeds.
Methods included from Utils
#blank_node?, #list?, #subject?, #subject_reference?, #value?
Instance Method Details
#cleanup_preserve(input) ⇒ Array, Hash
Replace @preserve keys with the values, also replace @null with null
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/json/ld/frame.rb', line 255 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.(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 |
#flatten ⇒ Array{Hash}
Flatten input, used in framing.
This algorithm works by transforming input to statements, and then back to JSON-LD
239 240 241 242 243 244 245 246 247 248 |
# File 'lib/json/ld/frame.rb', line 239 def flatten debug("flatten") = depth {self.(self.value, nil, context)} statements = [] depth {self.statements("", , nil, nil, nil ) {|s| statements << s}} debug("flatten") {"statements: #{statements.map(&:to_nquads).join("\n")}"} # Transform back to JSON-LD, not flattened depth {self.from_statements(statements)} end |
#frame(state, subjects, frame, parent, property) ⇒ Object
Frame input. Input is expected in expanded form, but frame is in compacted form.
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 |
# File 'lib/json/ld/frame.rb', line 18 def frame(state, subjects, frame, parent, property) raise ProcessingError, "why isn't @subjects a hash?: #{@subjects.inspect}" unless @subjects.is_a?(Hash) depth do debug("frame") {"state: #{state.inspect}"} debug("frame") {"subjects: #{subjects.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 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) debug("frame") {"matches: #{matches.keys.inspect}"} # Get values for embedOn and explicitOn = get_frame_flag(state, frame, 'embed'); explicit = get_frame_flag(state, frame, 'explicit'); debug("frame") {"embed: #{.inspect}, explicit: #{explicit.inspect}"} # For each id and subject from the set of matched subjects ordered by id matches.keys.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 = {:parent => parent, :property => property} # If embedOn is true, and id is in map of embeds from state if && (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 = false = 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 subject definition. Set embedOn to true if any of the items in parent property is a subject definition or subject 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: #{.inspect}"} # If embedOn is true, existing is already embedded but can be overwritten (state, id) if end unless # 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] = debug("frame") {"add embedded_subject: #{.inspect}"} # Process each property and value in the matched subject as follows element.keys.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 subject 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 subject in output using subject as element and property as active property (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 subject_reference?(listitem) itemid = listitem['@id'] debug("frame") {"list item of #{prop} recurse for #{itemid.inspect}"} # If listitem is a subject reference process listitem recursively using this algorithm passing a new map of subjects that contains the @id of listitem as the key and the subject 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 => @subjects[itemid]}, frame[prop].first, list, '@list') else # Otherwise, append a copy of listitem to @list in list. debug("frame") {"list item of #{prop} non-subject ref #{listitem.inspect}"} add_frame_output(state, list, '@list', listitem) end end elsif subject_reference?(item) # If item is a subject 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 subjects that contains the @id of item as the key and the subject 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 => @subjects[itemid]}, frame[prop].first, output, prop) else # Otherwise, append a copy of item to active property in output. debug("frame") {"value property #{prop} non-subject 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.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 |
#get_framing_subjects(subjects, input, namer) ⇒ Object
Build hash of subjects used for framing. Also returns flattened representation of input.
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 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/json/ld/frame.rb', line 178 def get_framing_subjects(subjects, input, namer) depth do debug("framing subjects") {"input: #{input.inspect}"} case input when Array input.map {|o| get_framing_subjects(subjects, o, namer)} when Hash case when subject?(input) || subject_reference?(input) # Get name for subject, mapping old blank node identifiers to new name = blank_node?(input) ? namer.get_name(input.fetch('@id', nil)) : input['@id'] debug("framing subjects") {"new subject: #{name.inspect}"} unless subjects.has_key?(name) subject = subjects[name] ||= {'@id' => name} # In property order input.keys.sort.each do |prop| value = input[prop] case prop when '@id' # Skip @id, already assigned when /^@/ # Copy other keywords subject[prop] = value else case value when Hash # Special case @list, which is not in expanded form raise InvalidFrame::Syntax, "Unexpected hash value: #{value.inspect}" unless value.has_key?('@list') # Map entries replacing subjects with subject references subject[prop] = {"@list" => value['@list'].map {|o| get_framing_subjects(subjects, o, namer)} } when Array # Map array entries subject[prop] = get_framing_subjects(subjects, value, namer) else raise InvalidFrame::Syntax, "unexpected value: #{value.inspect}" end end end # Return as subject reference {"@id" => name} else # At this point, it's not a subject or a reference, just return input input end else # Returns equivalent representation input end end end |
#remove_dependents(id, embeds) ⇒ Object
recursively remove dependent dangling embeds
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'lib/json/ld/frame.rb', line 376 def remove_dependents(id, ) debug("frame") {"remove dependents for #{id}"} depth do # get embed keys as a separate array to enable deleting keys in map .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"} .delete(id_dep) remove_dependents(id_dep, ) end end end end |