Class: Arboretum::DocTree::Elements::Element

Inherits:
Object
  • Object
show all
Extended by:
Loggable, Forwardable
Includes:
Contextualized, Loggable, Enumerable
Defined in:
lib/arboretum/doctree.rb

Overview

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Loggable

log, log_string

Methods included from Contextualized

#swap, #wrap

Constructor Details

#initializeElement

Returns a new instance of Element.



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# 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

Instance Attribute Details

#break_withinObject

Returns the value of attribute break_within.



451
452
453
# File 'lib/arboretum/doctree.rb', line 451

def break_within
  @break_within
end

#childrenObject

Returns the value of attribute children.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def children
  @children
end

#historyObject

Returns the value of attribute history.



451
452
453
# File 'lib/arboretum/doctree.rb', line 451

def history
  @history
end

#incrementersObject

Returns the value of attribute incrementers.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def incrementers
  @incrementers
end

#libraryObject

Returns the value of attribute library.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def library
  @library
end

#parentObject

Returns the value of attribute parent.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def parent
  @parent
end

#resettersObject

Returns the value of attribute resetters.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def resetters
  @resetters
end

#sibling_nextObject

Returns the value of attribute sibling_next.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def sibling_next
  @sibling_next
end

#sibling_prevObject

Returns the value of attribute sibling_prev.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def sibling_prev
  @sibling_prev
end

#tree_residenceObject (readonly)

Returns the value of attribute tree_residence.



450
451
452
# File 'lib/arboretum/doctree.rb', line 450

def tree_residence
  @tree_residence
end

Class Method Details

.create(namespace: nil, tag: nil, attrs: {}, text: nil) {|created_element| ... } ⇒ Object

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

Yields:

  • (created_element)


464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# 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

.stitch!(prev_element, next_element) ⇒ Object

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)



456
457
458
459
# 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

Instance Method Details

#ancestorsObject

Get list of all this element’s ancesotrs in depth-first order



853
854
855
856
857
858
859
860
861
862
863
# 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

#ancestors_reverseObject

Get list of all of this element’s ancestors in reversed depth-first order Used for slightly speedier and practical searching



867
868
869
870
871
872
873
874
875
876
# 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

#append_child(*elements) ⇒ Object Also known as: push, <<

Add an element as this element’s last child



522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# 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

#append_sibling(*elements) ⇒ Object



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
# 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

#can_have_children?Boolean

Returns:

  • (Boolean)


930
931
932
# File 'lib/arboretum/doctree.rb', line 930

def can_have_children?
  false
end

#contentObject Also known as: contents

Special method to get an elements children as an AdjacentElementGroup



660
661
662
663
664
# File 'lib/arboretum/doctree.rb', line 660

def content
  group = AdjacentElementGroup.new
  group.base(@children[0]) if not @children.empty?
  group.fill
end

#copyObject

General element deep copy method



717
718
719
720
721
# 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

#count(counter_name) ⇒ Object

Add an counter incrementer to this element



668
669
670
671
# 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

#counter(counter_name) ⇒ Object

Get a CounterElement associated with the incrementer of this element of the given name



674
675
676
677
# 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

#descendants(child_list = []) ⇒ Object

Get list of all this element’s descendants in depth-first order



879
880
881
882
883
884
885
886
# 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

#detachObject Also known as: prune, delete, clear, remove

Detach from current parent/siblings



729
730
731
732
733
# 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

#find(rule_string, silent: false) ⇒ Object Also known as: locate

Finds elements in relation to this one that fit a ScandentRule string



889
890
891
892
893
894
# 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_first(rule_string, silent: false) ⇒ Object Also known as: locate_first

Find the first element in relation to this one that fits a ScandentRule string



916
917
918
# 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

#find_first_n(rule_string, limit, silent: false) ⇒ Object Also known as: locate_first_n

Finds up to ‘n` elements in relation to this one that fit a ScandentRule string



898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
# 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

#following_siblingsObject

Get list of all of this element’s following siblings in depth-first order



841
842
843
844
845
846
847
848
849
850
# 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_first_onto(graft_parent) ⇒ Object

Graft onto another element of the tree as the first child



771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
# 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_last_onto(graft_parent) ⇒ Object

Graft onto another element of the tree as the last child



789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
# 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(graft_parent, index = -1)) ⇒ Object

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



741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
# 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

#has_children?Boolean

Returns:

  • (Boolean)


926
927
928
# File 'lib/arboretum/doctree.rb', line 926

def has_children?
  !@children.empty?
end

#index_in_parentObject



631
632
633
# File 'lib/arboretum/doctree.rb', line 631

def index_in_parent
  self.parent.children.index(self)
end

#insert_child(*elements, index) ⇒ Object

Insert an element as a child at a specified index it this element’s children



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
# 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

#inspectObject

Temporary fix to prevent inspect from exploding due to child references Ideally will provide detailed state information rather than string output



973
974
975
# File 'lib/arboretum/doctree.rb', line 973

def inspect
  self.to_s
end

#lib_add(other, category) ⇒ Object Also known as: allude, cite

Add an element to a category in this element’s library

Raises:

  • (TypeError)


686
687
688
689
690
691
692
693
694
695
696
697
698
699
# 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

#lib_get(category) ⇒ Object Also known as: [], record

Get record from the given category in this element’s library



704
705
706
707
# File 'lib/arboretum/doctree.rb', line 704

def lib_get(category)
  category = category.to_sym if !category.is_a?(Symbol)
  @library[category]
end

#listingObject

Provides a listing/array containing the element



712
713
714
# File 'lib/arboretum/doctree.rb', line 712

def listing
  [self]
end

#matches_rule?(rule_string) ⇒ Boolean

Returns:

  • (Boolean)


921
922
923
924
# 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

#overwrite!(args) ⇒ Object



618
619
620
621
622
623
624
625
626
627
628
629
# 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

#preceding_siblingsObject

Get list of all of this element’s preceding siblings



815
816
817
818
819
820
821
822
823
824
825
# 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

#preceding_siblings_reverseObject

Get list of all of this element’s preceding siblings in reversed depth-first order Used for slightly speedier and practical searching



829
830
831
832
833
834
835
836
837
838
# 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

#prepend_child(*elements) ⇒ Object Also known as: place, unshift

Add an element as this element’s first child



543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# 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

#prepend_sibling(*elements) ⇒ Object



600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# 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

#reset(counter_name) ⇒ Object

Add a counter resetter to this element



680
681
682
683
# 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

#set_children!(other_arr) ⇒ Object

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



958
959
960
961
# File 'lib/arboretum/doctree.rb', line 958

def set_children!(other_arr)
  @children = other_arr
  self
end

#set_parent!(other) ⇒ Object

Does nothing but set parent of this element If used in isolation, will cause a parent <=> child mismatch in the tree



950
951
952
953
# File 'lib/arboretum/doctree.rb', line 950

def set_parent!(other)
  @parent = other
  self
end

#set_sibling_next!(other) ⇒ Object

Does nothing but set sibling_next of this element If used in isolation, will cause a sibling mismatch in the tree



936
937
938
939
# File 'lib/arboretum/doctree.rb', line 936

def set_sibling_next!(other)
  @sibling_next = other
  self
end

#set_sibling_prev!(other) ⇒ Object

Does nothing but set sibling_prev of this element If used in isolation, will cause a sibling mismatch in the tree



943
944
945
946
# File 'lib/arboretum/doctree.rb', line 943

def set_sibling_prev!(other)
  @sibling_prev = other
  self
end

#text_elements(text_children = []) ⇒ Object

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



637
638
639
640
641
642
643
644
# 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

#text_string(full_string = '') ⇒ Object

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



648
649
650
651
652
653
654
655
656
657
# 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

#to_sObject



967
968
969
# File 'lib/arboretum/doctree.rb', line 967

def to_s
  "<Generic_Element>"
end

#to_treeObject



963
964
965
# File 'lib/arboretum/doctree.rb', line 963

def to_tree
  Tree.new(self)
end

#unwrap_childrenObject

Unwrap the children of this Element, deleting it, and it’s children taking its original place in the tree



807
808
809
810
811
812
# 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

#update_tree_residence(update_tree) ⇒ Object



723
724
725
726
# 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