class Eco::Data::Locations::NodeLevel module Cleaner include Eco::Language::AuxiliarLogger include Eco::Data::Locations::Convert # Prevents repeated node ids/tags, decouples merged levels, # covers gaps (jumping multiple levels) # @note # 1. It first discards node ids/tags that have been already pulled (discard repeated) # 2. For non repeated, it identifies if there's a gap (jump of multiple levels) # 3. It covers the gap if present by decoupling merged parent(s) from the same node (see node.decouple) # 4. Then, it delegates the filling in of parents to `fill_in_parents` function. # @return [Array] child to parent relationships solved and no double-ups. def tidy_nodes(nodes, prev_level: 0, main: true) reset_trackers! if main nodes.each_with_object([]) do |node, out| node_id = node.id if done_ids.include?(node_id) repeated_ids << "#{node_id} (level: #{node.level})" else level = node.actual_level if level > prev_level + 1 gap = level - (prev_level + 1) msg = "(Row: #{node.row_num}) ID/Tag '#{node_id}' (lev #{level}) jumps #{gap} level(s) (expected #{prev_level + 1})." #puts " " + node.tags_array.pretty_inspect missing_nodes = node.decouple(gap) msg << "\n Adding missing upper level(s): " + missing_nodes.map(&:raw_tag).pretty_inspect log(:info) { msg } # The very top missing node (first in list) should be checked against prev_level # alongside any descendants in missing_nodes (when gap 2+) tidied_nodes = tidy_nodes(missing_nodes, prev_level: prev_level, main: false) out.push(*tidied_nodes) #level = prev_level + 1 # <= we are actually on level and filled in the gaps end out << node done_ids << node_id prev_level = level end end.yield_self do |out| report_repeated_node_ids(repeated_ids) if main fill_in_parents(out) end end # Sets the `parentId` property. # Although with normalized nodes parents are self-contained # we use this method def fill_in_parents(nodes) nodes.tap do |nodes| prev_nodes = empty_level_tracker_hash(11) nodes.each do |node| if parent_node = prev_nodes[node.actual_level - 1] node.parentId = parent_node.id end prev_nodes[node.raw_level] = node end end end # Tracker helper (those repeated) def repeated_ids @repeated_ids ||= [] end # Tracker helper (those done) def done_ids @done_ids ||= [] end def reset_trackers! @done_ids = [] @repeated_ids = [] end end end