class Arboretum::DocTree::Elements::Element
Generic element class Meant to be inherited rather than used directly All elements hold reference to a parent, children, and their left and right siblings
Attributes
Public Class Methods
Create a custom element with a given namespace, tag, attributes, and text child If only text is given, a TextElement
will be created rather than a tagged element If no tag is given, but attributes or namespace are given, a `div` element will be used by default
# File lib/arboretum/doctree.rb, line 464 def self.create(namespace: nil, tag: nil, attrs: {}, text: nil) tag = :div if tag.nil? and (!namespace.nil? or !attrs.empty? or text.nil?) created_element = nil if tag.nil? raise TypeError.new("Text must be a String or TextElement") if !(text.is_a?(String) or text.is_a?(TextElement)) created_element = (text.is_a?(String)) ? TextElement.new(text) : text else tag = tag.to_sym if !tag.is_a?(Symbol) raise TypeError.new("Tag must be a Symbol or String") if !tag.is_a?(Symbol) namespace = namespace.to_sym if namespace.kind_of?(String) and !namespace.nil? raise TypeError.new("Namespace must be a Symbol or String") if !namespace.is_a?(Symbol) and !namespace.nil? sanitary_attrs = {} attrs.each do |key, val| key = key.to_sym if !key.is_a?(Symbol) raise TypeError.new("Attribute name must be a Symbol or String") if !key.is_a?(Symbol) val = val.split if val.is_a?(String) # Ensure value is arrays of strings raise TypeError.new("Attribute value must be a String or Array of Strings") if !val.is_a?(Array) val.each{|sub_val| raise TypeError.new("Each attribute value in an array must be a String") if !sub_val.is_a?(String)} sanitary_attrs[key] = val end created_element = TaggedElement.new(namespace, tag, sanitary_attrs) if !text.nil? raise TypeError.new("Text must be a String or TextElement") if !(text.is_a?(String) or text.is_a?(TextElement)) if text.is_a?(String) text_child = TextElement.new(text) created_element.append_child(text_child) else created_element.append_child(text) end end end created_element.break_within = true if !created_element.is_a?(TextElement) yield created_element if block_given? created_element end
# File lib/arboretum/doctree.rb, line 503 def initialize # Family of elements @parent = nil # Element @sibling_prev = nil # Element @sibling_next = nil # Element @children = [] # Array of Elements # Tree residence @tree_residence = nil # Tree # Properties, references, and counters @break_within = false # Boolean @incrementers = Hash.new # Hash with key: Symbol (name), value: Incrementer @resetters = Hash.new # Hash with key: Symbol (name), value: Resetter @library = Hash.new # Hash with key: Symbol, value: Element/ElementGroup @history = Array.new # Array of arrays of form [method_called, arguments, callers] end
Class method to stitch together two elements as siblings, ordered Will stitch values even if one or both is nil When used in isolation, can cause a mismatch in the tree (because parent is not updated)
# File lib/arboretum/doctree.rb, line 456 def self.stitch!(prev_element, next_element) prev_element.set_sibling_next!(next_element) unless prev_element.nil? next_element.set_sibling_prev!(prev_element) unless next_element.nil? end
Public Instance Methods
Get list of all this element's ancesotrs in depth-first order
# File lib/arboretum/doctree.rb, line 853 def ancestors parent_list = [] current = self while not current.parent.nil? parent_list << current.parent current = current.parent end parent_list = parent_list.reverse parent_list.each {|parent| yield parent if block_given?} ElementGroup.new(parent_list) end
Get list of all of this element's ancestors in reversed depth-first order Used for slightly speedier and practical searching
# File lib/arboretum/doctree.rb, line 867 def ancestors_reverse parent_list = [] current = self while not current.parent.nil? yield current.parent if block_given? parent_list << current.parent current = current.parent end ElementGroup.new(parent_list) end
Add an element as this element's last child
# File lib/arboretum/doctree.rb, line 522 def append_child(*elements) placed = Array.new elements.each do |element| if !element.nil? element = TextElement.new(element) if element.is_a?(String) element = Element.create(element) if element.is_a?(Hash) element.graft_last_onto(self) element.listing.each do |member| yield member if block_given? placed << member end end end return nil if placed.empty? return placed.first if placed.length == 1 return ElementGroup.new(placed) end
# File lib/arboretum/doctree.rb, line 582 def append_sibling(*elements) placed = Array.new elements.each do |element| if !element.nil? element = TextElement.new(element) if element.is_a?(String) element = Element.create(element) if element.is_a?(Hash) element.graft_onto(self.parent, self.index_in_parent+1) element.listing.each do |member| yield member if block_given? placed << member end end end return nil if placed.empty? return placed.first if placed.length == 1 return ElementGroup.new(placed) end
# File lib/arboretum/doctree.rb, line 930 def can_have_children? false end
Special method to get an elements children as an AdjacentElementGroup
# File lib/arboretum/doctree.rb, line 660 def content group = AdjacentElementGroup.new group.base(@children[0]) if not @children.empty? group.fill end
General element deep copy method
# File lib/arboretum/doctree.rb, line 717 def copy element_copy = Element.new element_copy.set_children!(@children.map {|child| child.copy}) element_copy end
Add an counter incrementer to this element
# File lib/arboretum/doctree.rb, line 668 def count(counter_name) counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol) incrementers[counter_name] = Counters::Incrementer.new(counter_name) end
Get a CounterElement
associated with the incrementer of this element of the given name
# File lib/arboretum/doctree.rb, line 674 def counter(counter_name) counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol) CounterElement.new(incrementers[counter_name]) end
Get list of all this element's descendants in depth-first order
# File lib/arboretum/doctree.rb, line 879 def descendants(child_list=[]) self.children.each do |child| yield child if block_given? child_list << child child.descendants(child_list) end ElementGroup.new(child_list) end
Detach from current parent/siblings
# File lib/arboretum/doctree.rb, line 729 def detach Element.stitch!(@sibling_prev, @sibling_next) @parent.children.delete(self) unless @parent.nil? @parent, @sibling_prev, @sibling_next = nil end
Finds elements in relation to this one that fit a ScandentRule string
# File lib/arboretum/doctree.rb, line 889 def find(rule_string, silent: false) rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LOCATOR) selected = rule.locate(self) {|found_element| yield found_element if block_given?} puts "--Warning: Rule #{rule} did not match any elements!--" if selected.empty? and !silent ElementGroup.new(selected) end
Find the first element in relation to this one that fits a ScandentRule string
# File lib/arboretum/doctree.rb, line 916 def find_first(rule_string, silent: false) self.find_first_n(rule_string, 1, :silent => silent) {|found_element| yield found_element if block_given?}.first end
Finds up to `n` elements in relation to this one that fit a ScandentRule string
# File lib/arboretum/doctree.rb, line 898 def find_first_n(rule_string, limit, silent: false) if limit.zero? puts "--Warning: Rule #{rule} was given limit '0'. Returning nil...--" if selected.empty? and !silent return nil end selected = [] rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LOCATOR) rule.locate(self) do |found_element| return ElementGroup.new(selected) if selected.length >= limit yield found_element if block_given? selected << found_element end puts "--Warning: Rule #{rule} on #{self.to_s} did not match any elements!--" if selected.empty? and !silent ElementGroup.new(selected) end
Get list of all of this element's following siblings in depth-first order
# File lib/arboretum/doctree.rb, line 841 def following_siblings sibling_list = [] current = self while not current.sibling_next.nil? yield current.sibling_next if block_given? sibling_list << current.sibling_next current = current.sibling_next end ElementGroup.new(sibling_list) end
Graft onto another element of the tree as the first child
# File lib/arboretum/doctree.rb, line 771 def graft_first_onto(graft_parent) # Detach from current context self.detach # Update context @parent = graft_parent next_child = graft_parent.children[0] Element.stitch!(nil, self) Element.stitch!(self, next_child) self.update_tree_residence(graft_parent.tree_residence) if !self.tree_residence.eql?(graft_parent.tree_residence) # Insert graft group at the beginning of parent children graft_parent.children.insert(0, self) end
Graft onto another element of the tree as the last child
# File lib/arboretum/doctree.rb, line 789 def graft_last_onto(graft_parent) # Detach from current context self.detach # Update context @parent = graft_parent previous_child = graft_parent.children[-1] Element.stitch!(previous_child, self) Element.stitch!(self, nil) self.update_tree_residence(graft_parent.tree_residence) if !self.tree_residence.eql?(graft_parent.tree_residence) # Push graft group onto parent children graft_parent.children.push(self) end
Graft onto another element of the tree at any index of its children By default, it will graft as the last element of the other element's children
# File lib/arboretum/doctree.rb, line 741 def graft_onto(graft_parent, index=-1) # If index to small or large, graft to edges of graft_parent children if index.abs > graft_parent.children.length index = graft_parent.children.length * (index > 1 ? 1 : 0) end if index == graft_parent.children.length or index == -1 self.graft_last_onto(graft_parent) elsif index == 0 self.graft_first_onto(graft_parent) else # Detach from current context self.detach # Update context @parent = graft_parent previous_child = graft_parent.children[index-1] Element.stitch!(previous_child, self) next_child = graft_parent.children[index] Element.stitch!(self, next_child) self.update_tree_residence(graft_parent.tree_residence) if !self.tree_residence.eql?(graft_parent.tree_residence) # Graft group at index graft_parent.children.insert(index, self) end end
# File lib/arboretum/doctree.rb, line 926 def has_children? !@children.empty? end
# File lib/arboretum/doctree.rb, line 631 def index_in_parent self.parent.children.index(self) end
Insert an element as a child at a specified index it this element's children
# File lib/arboretum/doctree.rb, line 564 def insert_child(*elements, index) placed = Array.new elements.each do |element| if !element.nil? element = TextElement.new(element) if element.is_a?(String) element = Element.create(element) if element.is_a?(Hash) element.graft_onto(self, index) element.listing.each do |member| yield member if block_given? placed << member end end end return nil if placed.empty? return placed.first if placed.length == 1 return ElementGroup.new(placed) end
Temporary fix to prevent inspect from exploding due to child references Ideally will provide detailed state information rather than string output
# File lib/arboretum/doctree.rb, line 973 def inspect self.to_s end
Add an element to a category in this element's library
# File lib/arboretum/doctree.rb, line 686 def lib_add(other, category) raise TypeError.new("Cannot create a record for a non-element") if !other.is_a?(Element) and !other.is_a?(ElementGroup) category = category.to_sym if !category.is_a?(Symbol) if @library.has_key?(category) if other.is_a?(Element) @library[category] << other else @library[category] = @library[category] + other end else @library[category] = ElementGroup.new([other]) end end
Get record from the given category in this element's library
# File lib/arboretum/doctree.rb, line 704 def lib_get(category) category = category.to_sym if !category.is_a?(Symbol) @library[category] end
Provides a listing/array containing the element
# File lib/arboretum/doctree.rb, line 712 def listing [self] end
# File lib/arboretum/doctree.rb, line 921 def matches_rule?(rule_string) rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LISTENER) rule.valid_on?(self) end
# File lib/arboretum/doctree.rb, line 618 def overwrite!(args) replacement = Element.create(args) puts "Warning: Overwriting this element will delete its children" if self.has_children? and !replacement.can_have_children? replacement.graft_onto(self.parent, self.index_in_parent) self.content.graft_onto(replacement) replacement.break_within = self.break_within replacement.counters = self.counters replacement.resetters = self.resetters replacement.library = self.library self.detach replacement end
Get list of all of this element's preceding siblings
# File lib/arboretum/doctree.rb, line 815 def preceding_siblings sibling_list = [] current = self while not current.sibling_prev.nil? sibling_list << current.sibling_prev current = current.sibling_prev end sibling_list = sibling_list.reverse sibling_list.each {|sibling| yield sibling if block_given?} ElementGroup.new(sibling_list) end
Get list of all of this element's preceding siblings in reversed depth-first order Used for slightly speedier and practical searching
# File lib/arboretum/doctree.rb, line 829 def preceding_siblings_reverse sibling_list = [] current = self while not current.sibling_prev.nil? yield current.sibling_prev if block_given? sibling_list << current.sibling_prev current = current.sibling_prev end ElementGroup.new(sibling_list) end
Add an element as this element's first child
# File lib/arboretum/doctree.rb, line 543 def prepend_child(*elements) placed = Array.new elements.each do |element| if !element.nil? element = TextElement.new(element) if element.is_a?(String) element = Element.create(element) if element.is_a?(Hash) element.graft_first_onto(self) element.listing.each do |member| yield member if block_given? placed << member end end end return nil if placed.empty? return placed.first if placed.length == 1 return ElementGroup.new(placed) end
# File lib/arboretum/doctree.rb, line 600 def prepend_sibling(*elements) placed = Array.new elements.each do |element| if !element.nil? element = TextElement.new(element) if element.is_a?(String) element = Element.create(element) if element.is_a?(Hash) element.graft_onto(self.parent, self.index_in_parent) element.listing.each do |member| yield member if block_given? placed << member end end end return nil if placed.empty? return placed.first if placed.length == 1 return ElementGroup.new(placed) end
Add a counter resetter to this element
# File lib/arboretum/doctree.rb, line 680 def reset(counter_name) counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol) resetters[counter_name] = Counters::Resetter.new(counter_name) end
Does nothing but set children of this element If used in isolation, will cause a parent <=> child mismatch in the tree Should only be used for when set operations must be performed on element children
# File lib/arboretum/doctree.rb, line 958 def set_children!(other_arr) @children = other_arr self end
Does nothing but set parent of this element If used in isolation, will cause a parent <=> child mismatch in the tree
# File lib/arboretum/doctree.rb, line 950 def set_parent!(other) @parent = other self end
Does nothing but set sibling_next
of this element If used in isolation, will cause a sibling mismatch in the tree
# File lib/arboretum/doctree.rb, line 936 def set_sibling_next!(other) @sibling_next = other self end
Does nothing but set sibling_prev
of this element If used in isolation, will cause a sibling mismatch in the tree
# File lib/arboretum/doctree.rb, line 943 def set_sibling_prev!(other) @sibling_prev = other self end
Returns all text elements in this element's subtree Can be very expensive on large subtrees/documents, as it performs a full transversal of the subtree
# File lib/arboretum/doctree.rb, line 637 def text_elements(text_children=[]) if self.is_a? TextElement text_children << self elsif self.is_a? TaggedElement or self.is_a? DocRootElement @children.each {|child| child.text_elements(text_children)} end text_children end
Returns a string comprised of all text in this element's subtree Can be very expensive on large subtrees/documents, as it performs a full transversal of the subtree
# File lib/arboretum/doctree.rb, line 648 def text_string(full_string='') if self.is_a? TextElement full_string << self.text elsif self.is_a? TaggedElement or self.is_a? DocRootElement full_string << ' ' if self.break_within @children.each {|child| child.text_string(full_string)} full_string << ' ' if self.break_within end full_string end
# File lib/arboretum/doctree.rb, line 967 def to_s "<Generic_Element>" end
# File lib/arboretum/doctree.rb, line 963 def to_tree Tree.new(self) end
Unwrap the children of this Element
, deleting it, and it's children taking its original place in the tree
# File lib/arboretum/doctree.rb, line 807 def unwrap_children unwrapped_elements = self.content unwrapped_elements.graft_onto(self.parent, self.index_in_parent) self.detach unwrapped_elements end
# File lib/arboretum/doctree.rb, line 723 def update_tree_residence(update_tree) @tree_residence = update_tree @children.each {|child| child.update_tree_residence(update_tree)} end