class Arboretum::DocTree::Elements::Tree
Tree
is a representation of a tree data structure consisting of elements A Tree
holds only reference to the root Element
of the tree A tree is useful for contextual operations on elements with the root as an ancestor
Attributes
id_cache[RW]
root[RW]
Public Class Methods
new(root=nil)
click to toggle source
# File lib/arboretum/doctree.rb, line 75 def initialize(root=nil) @root = root # Element @listeners = Array.new # Array of GroupListeners @id_cache = Hash.new # Hash: String => Element root.update_tree_residence(self) end
Public Instance Methods
apply_counters()
click to toggle source
# File lib/arboretum/doctree.rb, line 152 def apply_counters self.each do |element| element.resetters.each {|name, r| r.counter.reset} element.incrementers.each {|name, i| i.value = i.counter.current_value; i.counter.increment} end end
apply_listeners()
click to toggle source
# File lib/arboretum/doctree.rb, line 137 def apply_listeners self.each do |element| @listeners.each do |listener| if listener.rule.valid_on?(element) listener << element listener.exe_block.call(element) if !listener.exe_block.nil? end end end @listeners.each do |listener| puts "--Warning: Rule #{listener.rule} did not match any elements!--" if listener.empty? end @listeners = [] end
dump_markup(style=:pretty, type=:xml)
click to toggle source
# File lib/arboretum/doctree.rb, line 168 def dump_markup(style=:pretty, type=:xml) # The string containing all the markup tree_string = "" # Array of possible whitespace chars whitespace_chars = [" ", "\t", "\n"] # Whether there has been whitespace since the last text node whitespace_trailing = true # Whether the whitespace since the last text node has been honored whitespace_honored = true # To track which elements must be closed explicitly open_elements = Array.new if style.eql? :pretty self.each_with_level do |element, level| # Close elements that should close before the current element until open_elements.empty? or level > open_elements.last[1] # The element to be closed and its respective indentation level and whether it was a text-only element closed_element, closed_level, text_only = open_elements.pop closed_indent = " " * closed_level # Whether the tail text of an element begins with whitespace e.g. `<div></div> I am tail text for the div` => true tail_leading_space = (closed_element.sibling_next.is_a?(TextElement) and whitespace_chars.include?(closed_element.sibling_next.text[0])) # Whether element can break before the closing tag (to preserve whitespace): # Element can break and still maintain whitespace if there has been whitespace since the # last text element and the tail text of the current element # Additionally, element can break if its instance variable :break_within is true can_break_after = (whitespace_trailing or closed_element.break_within or tail_leading_space) # Add a newline if it preserve whitespace and is not redundant and the element was not fit into a single line tree_string << "\n" if can_break_after and !tree_string[-1].eql? "\n" and !text_only # Add the indentation for this level if a newline occurred tree_string << closed_indent if tree_string[-1].eql? "\n" # If we added whitespace, then we have honored any whitespace in the document whitespace_honored = true if whitespace_chars.include?(tree_string[-1]) # If we added whitespace, then the next element should be safe to break as well whitespace_trailing = true if can_break_after # Dump the closing tag of the element tree_string << closed_element.dump_markup_close end # Determine the indentation level for the current element indent = " " * level # Handle element depending on its type if element.is_a? TaggedElement # Whether the element has any non-text children (text-only elements will be fit into a single line) text_only = element.children.select{|c| !c.is_a?(TextElement)}.length == 0 # Whether element can break before the opening tag (to preserve whitespace): # Element can break and still maintain whitespace if there has been whitespace since the # last text element and the body text of the current element # Additionally, element can break if its instance variable :break_within is true can_break_before = (whitespace_trailing or element.break_within) # Lookahead to determine if whitespace is in between element and next TextElement # e.g. `<div> I am body text for the div <a>I am not</a></div>` => true # Only takes place if the less expensive options didn't pan out unless can_break_before current = element until current.nil? or current.is_a?(TextElement) or can_break_before current = current.children.first can_break_before = true if current.is_a?(TaggedElement) and current.break_within end can_break_before = true if current.is_a?(TextElement) and whitespace_chars.include?(current.text[0]) end # Add a newline if it preserves whitespace and is not redundant tree_string << "\n" if can_break_before and !tree_string[-1].eql?("\n") # Add the indentation for this level if a newline occurs before the opening tag tree_string << indent if tree_string[-1].eql? "\n" # If we added whitespace, then we have honored any whitespace in the document whitespace_honored = true if whitespace_chars.include?(tree_string[-1]) # If we added whitespace, then the next element should be safe to break as well whitespace_trailing = true if can_break_before # Dump the opening tag of the element tree_string << element.dump_markup(type) # Add another newline if it preserves whitespace and this elements has a non-text element tree_string << "\n" if can_break_before and !text_only # Mark the element for closing if it is paired open_elements << [element, level, text_only] if element.paired? elsif element.is_a? TextElement # Whether this element has any non-text siblings (and will not be fit into a single line) non_text_siblings = (element.preceding_siblings + element.following_siblings).select{|s| !s.is_a?(TextElement)}.length > 0 text_prev = element.sibling_prev.is_a? TextElement text_next = element.sibling_next.is_a? TextElement # The text of the element, to be modified before adding to the markup string element_text = element.dump_markup(type) element_trailing_space = whitespace_chars.include?(element_text[-1]) element_preceding_space = whitespace_chars.include?(element_text[0]) # Determine if the preceding space in the element is redundant or not needed can_remove_preceding = (element_preceding_space and !text_prev and whitespace_trailing) tree_string << "\n" if can_remove_preceding and !tree_string[-1].eql?("\n") and non_text_siblings tree_string << indent if tree_string[-1].eql?("\n") and !element_text.strip.empty? # If we added whitespace, then we have honored any whitespace in the document whitespace_honored = true if whitespace_chars.include?(tree_string[-1]) # Tack on some whitespace or mark leading whitespace as not redundant if whitespace hasn't been honored yet if whitespace_trailing and !whitespace_honored if can_remove_preceding can_remove_preceding = false else element_text = " " << element_text end end # Strip redundant or unwanted whitespace element_text[0] = "" if (can_remove_preceding and whitespace_chars.include?(tree_string[-1])) or (can_remove_preceding and !non_text_siblings) element_text[-1] = non_text_siblings ? "\n" : "" if element_trailing_space and !text_next and !element_text.strip.empty? # Determine whether whitespace is trailing and if that trailing whitespace is honored by the end of the text node whitespace_trailing = element_trailing_space whitespace_honored = !(element_trailing_space and !whitespace_chars.include?(element_text[-1])) tree_string << element_text unless tree_string[-1].eql?("\n") and element_text.strip.empty? elsif element.is_a? DocRootElement # Do nothing else # Just treat most elements like TaggedElements except for dumping a closing tag ##### # Whether the element has any non-text children (text-only elements will be fit into a single line) text_only = element.children.select{|c| !c.is_a?(TextElement)}.length == 0 # Whether element can break before the opening tag (to preserve whitespace): # Element can break and still maintain whitespace if there has been whitespace since the # last text element and the body text of the current element # Additionally, element can break if its instance variable :break_within is true can_break_before = (whitespace_trailing or element.break_within) # Lookahead to determine if whitespace is in between element and next TextElement # e.g. `<div> I am body text for the div <a>I am not</a></div>` => true # Only takes place if the less expensive options didn't pan out unless can_break_before current = element until current.nil? or current.is_a?(TextElement) or can_break_before current = current.children.first can_break_before = true if current.is_a?(TaggedElement) and current.break_within end can_break_before = true if current.is_a?(TextElement) and whitespace_chars.include?(current.text[0]) end # Add a newline if it preserves whitespace and is not redundant tree_string << "\n" if can_break_before and !tree_string[-1].eql?("\n") # Add the indentation for this level if a newline occurs before the opening tag tree_string << indent if tree_string[-1].eql? "\n" # If we added whitespace, then we have honored any whitespace in the document whitespace_honored = true if whitespace_chars.include?(tree_string[-1]) # If we added whitespace, then the next element should be safe to break as well whitespace_trailing = true if can_break_before # Dump the opening tag of the element tree_string << element.dump_markup(type) # Add another newline if it preserves whitespace and this elements has a non-text element tree_string << "\n" if can_break_before and !text_only end end # Close remaining until open_elements.empty? # The element to be closed and its respective indentation level and whether it was a text-only element closed_element, closed_level, text_only = open_elements.pop closed_indent = " " * closed_level # Whether the tail text of an element begins with whitespace e.g. `<div></div> I am tail text for the div` => true tail_leading_space = (closed_element.sibling_next.is_a?(TextElement) and whitespace_chars.include?(closed_element.sibling_next.text[0])) # Whether element can break before the closing tag (to preserve whitespace): # Element can break and still maintain whitespace if there has been whitespace since the # last text element and the tail text of the current element # Additionally, element can break if its instance variable :break_within is true can_break_after = (whitespace_trailing or closed_element.break_within or tail_leading_space) # Add a newline if it preserve whitespace and is not redundant and the element was not fit into a single line tree_string << "\n" if can_break_after and !tree_string[-1].eql? "\n" and !text_only # Add the indentation for this level if a newline occurred tree_string << closed_indent if tree_string[-1].eql? "\n" # If we added whitespace, then we have honored any whitespace in the document whitespace_honored = true if whitespace_chars.include?(tree_string[-1]) # If we added whitespace, then the next element should be safe to break as well whitespace_trailing = true if can_break_after # Dump the closing tag of the element tree_string << closed_element.dump_markup_close end elsif style.eql? :compact self.each_with_level do |element, level| until open_elements.empty? or level > open_elements.last[1] closed_element, closed_level = open_elements.pop tree_string << " " if closed_element.break_within whitespace_trailing = true if whitespace_chars.include?(tree_string[-1]) tree_string << closed_element.dump_markup_close end if element.is_a? TaggedElement tree_string << element.dump_markup(type) tree_string << " " if element.break_within whitespace_trailing = true if whitespace_chars.include?(tree_string[-1]) open_elements << [element, level] if element.paired? elsif element.is_a? TextElement tree_string << element.dump_markup(type) unless whitespace_chars.include?(tree_string[-1]) and element.text.strip.empty? whitespace_trailing = whitespace_chars.include?(tree_string[-1]) elsif element.is_a? DocRootElement # Do nothing else tree_string << element.dump_markup(type) tree_string << " " if element.break_within whitespace_trailing = true if whitespace_chars.include?(tree_string[-1]) end end # Unknown print style else puts "Warning: Unknown print style. Using `:pretty`..." tree_string = dump_markup(:pretty, type) end (whitespace_chars.include?(tree_string[0])) ? tree_string[1..-1] : tree_string end
each(element=self.root) { |element| ... }
click to toggle source
Redefine the `each` method to iterate through all elements in the tree in depth-first order
# File lib/arboretum/doctree.rb, line 85 def each(element=self.root) yield element element.children.each {|child| self.each(child) {|c| yield c}} unless element.nil? end
each_with_level(element=self.root, level=0) { |element, level| ... }
click to toggle source
# File lib/arboretum/doctree.rb, line 90 def each_with_level(element=self.root, level=0) yield [element, level] level += 1 unless element.is_a?(DocRootElement) element.children.each {|child| self.each_with_level(child, level) {|c,l| yield [c, l]}} unless element.nil? end
element_from_id_hash(hash_id) { |element| ... }
click to toggle source
# File lib/arboretum/doctree.rb, line 114 def element_from_id_hash(hash_id) raise IndexError.new("Tried to get element from hash id with no hash: #{hash_id}") if !hash_id[0].eql?("#") self.id_cache_get(hash_id[1..-1]).tap {|element| yield element if block_given?} end
get_DF_elements()
click to toggle source
Returns an array with all elements in the subtree of the root in depth-first order
# File lib/arboretum/doctree.rb, line 97 def get_DF_elements Array.new.tap {|list| self.each {|element| list << element}} end
id_cache_add(id, element)
click to toggle source
# File lib/arboretum/doctree.rb, line 101 def id_cache_add(id, element) @id_cache[id] = element end
id_cache_get(id) { |element| ... }
click to toggle source
# File lib/arboretum/doctree.rb, line 109 def id_cache_get(id) @id_cache[id].tap {|element| yield element if block_given?} end
Also aliased as: element_from_id
id_cache_remove(id)
click to toggle source
# File lib/arboretum/doctree.rb, line 105 def id_cache_remove(id) @id_cache.delete(id) end
listen(rule_string, &block)
click to toggle source
# File lib/arboretum/doctree.rb, line 131 def listen(rule_string, &block) listener = GroupListener.new(rule_string, block) @listeners << listener listener end
scan(rule_string) { |element| ... }
click to toggle source
Find any and all elements in the tree that match a given ScandentRule string
# File lib/arboretum/doctree.rb, line 120 def scan(rule_string) selected = [] rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LISTENER) self.each do |element| selected << element if rule.valid_on?(element) yield element if rule.valid_on?(element) and block_given? end puts "--Warning: Rule #{rule_string} did not match any elements!--" if selected.empty? ElementGroup.new(selected) end
to_s(root=true, pretty=true)
click to toggle source
# File lib/arboretum/doctree.rb, line 159 def to_s(root=true, pretty=true) tree_string = (root ? "<<Tree: \n" : "") if not self.root.nil? tree_string << (root ? self.root.to_s + (pretty ? "\n" : "") : "") self.root.children.each {|child| tree_string << self.r_to_s(child, 1, pretty)} end tree_string << (root ? ">>" : "") end
Protected Instance Methods
r_to_s(element, level, pretty)
click to toggle source
# File lib/arboretum/doctree.rb, line 382 def r_to_s(element, level, pretty) element_string = (pretty ? " |"*level : "") + element.to_s + (pretty ? "\n" : "") element.children.each do |child| element_string << self.r_to_s(child, level + 1, pretty) end element_string end