arboretum 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1566 @@
1
+ module Arboretum
2
+ module DocTree
3
+ module Counters
4
+
5
+ class Counter
6
+ attr_accessor :name, :incrementers, :resetters, :start_value, :gradient, :current_value
7
+ @@counters = Hash.new{|h, name| h[name] = Counter.new(name)}
8
+
9
+ def self.counters
10
+ @@counters
11
+ end
12
+
13
+ def initialize(name)
14
+ name = name.to_sym if !name.is_a?(Symbol)
15
+ @name = name
16
+ @incrementers = []
17
+ @resetters = []
18
+ @start_value = 1
19
+ @gradient = 1
20
+
21
+ @current_value = start_value
22
+ end
23
+
24
+ def reset
25
+ @current_value = start_value
26
+ end
27
+
28
+ def increment
29
+ @current_value += gradient
30
+ end
31
+ end
32
+
33
+ class Incrementer
34
+ attr_reader :name
35
+ attr_accessor :value
36
+
37
+ def initialize(name)
38
+ @name = name
39
+ @value = 0
40
+ Counter.counters[name].incrementers << self
41
+ end
42
+
43
+ def counter
44
+ Counter.counters[@name]
45
+ end
46
+ end
47
+
48
+ class Resetter
49
+ attr_reader :name
50
+
51
+ def initialize(name)
52
+ @name = name
53
+ Counter.counters[name].resetters << self
54
+ end
55
+
56
+ def counter
57
+ Counter.counters[@name]
58
+ end
59
+ end
60
+ end # Counters
61
+
62
+ module Elements
63
+ require 'forwardable'
64
+ require 'securerandom'
65
+ require_relative 'scandent'
66
+
67
+ # Tree is a representation of a tree data structure consisting of elements
68
+ # A Tree holds only reference to the root Element of the tree
69
+ # A tree is useful for contextual operations on elements with the root as an ancestor
70
+ class Tree
71
+ include Enumerable
72
+
73
+ attr_accessor :root
74
+
75
+ def initialize(root=nil)
76
+ @root = root # Element
77
+ @listeners = [] # Array of GroupListeners
78
+ end
79
+
80
+ # Redefine the `each` method to iterate through all elements in the tree in depth-first order
81
+ def each(element=self.root)
82
+ yield element
83
+ element.children.each {|child| self.each(child) {|c| yield c}} unless element.nil?
84
+ end
85
+
86
+ def each_with_level(element=self.root, level=0)
87
+ yield [element, level]
88
+ level += 1 unless element.is_a?(DocRootElement)
89
+ element.children.each {|child| self.each_with_level(child, level) {|c,l| yield [c, l]}} unless element.nil?
90
+ end
91
+
92
+ # Returns an array with all elements in the subtree of the root in depth-first order
93
+ def get_DF_elements
94
+ Array.new.tap {|list| self.each {|element| list << element}}
95
+ end
96
+
97
+ # Find any and all elements in the tree that match a given ScandentRule string
98
+ def scan(rule_string)
99
+ selected = []
100
+ rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LISTENER)
101
+ self.each do |element|
102
+ selected << element if rule.valid_on?(element)
103
+ yield element if rule.valid_on?(element) and block_given?
104
+ end
105
+ puts "--Warning: Rule #{rule_string} did not match any elements!--" if selected.empty?
106
+ ElementGroup.new(selected)
107
+ end
108
+
109
+ def listen(rule_string, &block)
110
+ listener = GroupListener.new(rule_string, block)
111
+ @listeners << listener
112
+ listener
113
+ end
114
+
115
+ def apply_listeners
116
+ self.each do |element|
117
+ @listeners.each do |listener|
118
+ if listener.rule.valid_on?(element)
119
+ listener << element
120
+ listener.exe_block.call(element) if !listener.exe_block.nil?
121
+ end
122
+ end
123
+ end
124
+ @listeners.each do |listener|
125
+ puts "--Warning: Rule #{listener.rule} did not match any elements!--" if listener.empty?
126
+ end
127
+ @listeners = []
128
+ end
129
+
130
+ def apply_counters
131
+ self.each do |element|
132
+ element.resetters.each {|name, r| r.counter.reset}
133
+ element.incrementers.each {|name, i| i.value = i.counter.current_value; i.counter.increment}
134
+ end
135
+ end
136
+
137
+ def to_s(root=true, pretty=true)
138
+ tree_string = (root ? "<<Tree: \n" : "")
139
+ if not self.root.nil?
140
+ tree_string << (root ? self.root.to_s + (pretty ? "\n" : "") : "")
141
+ self.root.children.each {|child| tree_string << self.r_to_s(child, 1, pretty)}
142
+ end
143
+ tree_string << (root ? ">>" : "")
144
+ end
145
+
146
+ def dump_markup(style=:pretty, type=:xml)
147
+ # The string containing all the markup
148
+ tree_string = ""
149
+ # Array of possible whitespace chars
150
+ whitespace_chars = [" ", "\t", "\n"]
151
+ # Whether there has been whitespace since the last text node
152
+ whitespace_trailing = true
153
+ # Whether the whitespace since the last text node has been honored
154
+ whitespace_honored = true
155
+ # To track which elements must be closed explicitly
156
+ open_elements = Array.new
157
+ if style.eql? :pretty
158
+ self.each_with_level do |element, level|
159
+ # Close elements that should close before the current element
160
+ until open_elements.empty? or level > open_elements.last[1]
161
+ # The element to be closed and its respective indentation level and whether it was a text-only element
162
+ closed_element, closed_level, text_only = open_elements.pop
163
+ closed_indent = " " * closed_level
164
+
165
+ # Whether the tail text of an element begins with whitespace e.g. `<div></div> I am tail text for the div` => true
166
+ tail_leading_space = (closed_element.sibling_next.is_a?(TextElement) and
167
+ whitespace_chars.include?(closed_element.sibling_next.text[0]))
168
+ # Whether element can break before the closing tag (to preserve whitespace):
169
+ # Element can break and still maintain whitespace if there has been whitespace since the
170
+ # last text element and the tail text of the current element
171
+ # Additionally, element can break if its instance variable :break_within is true
172
+ can_break_after = (whitespace_trailing or closed_element.break_within or tail_leading_space)
173
+
174
+ # Add a newline if it preserve whitespace and is not redundant and the element was not fit into a single line
175
+ tree_string << "\n" if can_break_after and !tree_string[-1].eql? "\n" and !text_only
176
+ # Add the indentation for this level if a newline occurred
177
+ tree_string << closed_indent if tree_string[-1].eql? "\n"
178
+ # If we added whitespace, then we have honored any whitespace in the document
179
+ whitespace_honored = true if whitespace_chars.include?(tree_string[-1])
180
+ # If we added whitespace, then the next element should be safe to break as well
181
+ whitespace_trailing = true if can_break_after
182
+ # Dump the closing tag of the element
183
+ tree_string << closed_element.dump_markup_close
184
+ end
185
+ # Determine the indentation level for the current element
186
+ indent = " " * level
187
+
188
+ # Handle element depending on its type
189
+ if element.is_a? TaggedElement
190
+ # Whether the element has any non-text children (text-only elements will be fit into a single line)
191
+ text_only = element.children.select{|c| !c.is_a?(TextElement)}.length == 0
192
+ # Whether element can break before the opening tag (to preserve whitespace):
193
+ # Element can break and still maintain whitespace if there has been whitespace since the
194
+ # last text element and the body text of the current element
195
+ # Additionally, element can break if its instance variable :break_within is true
196
+ can_break_before = (whitespace_trailing or element.break_within)
197
+
198
+ # Lookahead to determine if whitespace is in between element and next TextElement
199
+ # e.g. `<div> I am body text for the div <a>I am not</a></div>` => true
200
+ # Only takes place if the less expensive options didn't pan out
201
+ unless can_break_before
202
+ current = element
203
+ until current.nil? or current.is_a?(TextElement) or can_break_before
204
+ current = current.children.first
205
+ can_break_before = true if current.is_a?(TaggedElement) and current.break_within
206
+ end
207
+ can_break_before = true if current.is_a?(TextElement) and whitespace_chars.include?(current.text[0])
208
+ end
209
+
210
+ # Add a newline if it preserves whitespace and is not redundant
211
+ tree_string << "\n" if can_break_before and !tree_string[-1].eql?("\n")
212
+ # Add the indentation for this level if a newline occurs before the opening tag
213
+ tree_string << indent if tree_string[-1].eql? "\n"
214
+ # If we added whitespace, then we have honored any whitespace in the document
215
+ whitespace_honored = true if whitespace_chars.include?(tree_string[-1])
216
+ # If we added whitespace, then the next element should be safe to break as well
217
+ whitespace_trailing = true if can_break_before
218
+ # Dump the opening tag of the element
219
+ tree_string << element.dump_markup(type)
220
+ # Add another newline if it preserves whitespace and this elements has a non-text element
221
+ tree_string << "\n" if can_break_before and !text_only
222
+ # Mark the element for closing if it is paired
223
+ open_elements << [element, level, text_only] if element.paired?
224
+ elsif element.is_a? TextElement
225
+ # Whether this element has any non-text siblings (and will not be fit into a single line)
226
+ non_text_siblings = (element.preceding_siblings + element.following_siblings).select{|s| !s.is_a?(TextElement)}.length > 0
227
+ text_prev = element.sibling_prev.is_a? TextElement
228
+ text_next = element.sibling_next.is_a? TextElement
229
+
230
+ # The text of the element, to be modified before adding to the markup string
231
+ element_text = element.dump_markup(type)
232
+
233
+ element_trailing_space = whitespace_chars.include?(element_text[-1])
234
+ element_preceding_space = whitespace_chars.include?(element_text[0])
235
+ # Determine if the preceding space in the element is redundant or not needed
236
+ can_remove_preceding = (element_preceding_space and !text_prev and whitespace_trailing)
237
+
238
+ tree_string << "\n" if can_remove_preceding and !tree_string[-1].eql?("\n") and non_text_siblings
239
+ tree_string << indent if tree_string[-1].eql?("\n") and !element_text.strip.empty?
240
+ # If we added whitespace, then we have honored any whitespace in the document
241
+ whitespace_honored = true if whitespace_chars.include?(tree_string[-1])
242
+
243
+ # Tack on some whitespace or mark leading whitespace as not redundant if whitespace hasn't been honored yet
244
+ if whitespace_trailing and !whitespace_honored
245
+ if can_remove_preceding
246
+ can_remove_preceding = false
247
+ else
248
+ element_text = " " << element_text
249
+ end
250
+ end
251
+
252
+ # Strip redundant or unwanted whitespace
253
+ element_text[0] = "" if (can_remove_preceding and whitespace_chars.include?(tree_string[-1])) or
254
+ (can_remove_preceding and !non_text_siblings)
255
+ element_text[-1] = non_text_siblings ? "\n" : "" if element_trailing_space and !text_next and !element_text.strip.empty?
256
+
257
+ # Determine whether whitespace is trailing and if that trailing whitespace is honored by the end of the text node
258
+ whitespace_trailing = element_trailing_space
259
+ whitespace_honored = !(element_trailing_space and !whitespace_chars.include?(element_text[-1]))
260
+
261
+ tree_string << element_text unless tree_string[-1].eql?("\n") and element_text.strip.empty?
262
+ elsif element.is_a? DocRootElement
263
+ # Do nothing
264
+ else
265
+ # Just treat most elements like TaggedElements except for dumping a closing tag
266
+ #####
267
+ # Whether the element has any non-text children (text-only elements will be fit into a single line)
268
+ text_only = element.children.select{|c| !c.is_a?(TextElement)}.length == 0
269
+ # Whether element can break before the opening tag (to preserve whitespace):
270
+ # Element can break and still maintain whitespace if there has been whitespace since the
271
+ # last text element and the body text of the current element
272
+ # Additionally, element can break if its instance variable :break_within is true
273
+ can_break_before = (whitespace_trailing or element.break_within)
274
+
275
+ # Lookahead to determine if whitespace is in between element and next TextElement
276
+ # e.g. `<div> I am body text for the div <a>I am not</a></div>` => true
277
+ # Only takes place if the less expensive options didn't pan out
278
+ unless can_break_before
279
+ current = element
280
+ until current.nil? or current.is_a?(TextElement) or can_break_before
281
+ current = current.children.first
282
+ can_break_before = true if current.is_a?(TaggedElement) and current.break_within
283
+ end
284
+ can_break_before = true if current.is_a?(TextElement) and whitespace_chars.include?(current.text[0])
285
+ end
286
+
287
+ # Add a newline if it preserves whitespace and is not redundant
288
+ tree_string << "\n" if can_break_before and !tree_string[-1].eql?("\n")
289
+ # Add the indentation for this level if a newline occurs before the opening tag
290
+ tree_string << indent if tree_string[-1].eql? "\n"
291
+ # If we added whitespace, then we have honored any whitespace in the document
292
+ whitespace_honored = true if whitespace_chars.include?(tree_string[-1])
293
+ # If we added whitespace, then the next element should be safe to break as well
294
+ whitespace_trailing = true if can_break_before
295
+ # Dump the opening tag of the element
296
+ tree_string << element.dump_markup(type)
297
+ # Add another newline if it preserves whitespace and this elements has a non-text element
298
+ tree_string << "\n" if can_break_before and !text_only
299
+ end
300
+ end
301
+ # Close remaining
302
+ until open_elements.empty?
303
+ # The element to be closed and its respective indentation level and whether it was a text-only element
304
+ closed_element, closed_level, text_only = open_elements.pop
305
+ closed_indent = " " * closed_level
306
+
307
+ # Whether the tail text of an element begins with whitespace e.g. `<div></div> I am tail text for the div` => true
308
+ tail_leading_space = (closed_element.sibling_next.is_a?(TextElement) and
309
+ whitespace_chars.include?(closed_element.sibling_next.text[0]))
310
+ # Whether element can break before the closing tag (to preserve whitespace):
311
+ # Element can break and still maintain whitespace if there has been whitespace since the
312
+ # last text element and the tail text of the current element
313
+ # Additionally, element can break if its instance variable :break_within is true
314
+ can_break_after = (whitespace_trailing or closed_element.break_within or tail_leading_space)
315
+
316
+ # Add a newline if it preserve whitespace and is not redundant and the element was not fit into a single line
317
+ tree_string << "\n" if can_break_after and !tree_string[-1].eql? "\n" and !text_only
318
+ # Add the indentation for this level if a newline occurred
319
+ tree_string << closed_indent if tree_string[-1].eql? "\n"
320
+ # If we added whitespace, then we have honored any whitespace in the document
321
+ whitespace_honored = true if whitespace_chars.include?(tree_string[-1])
322
+ # If we added whitespace, then the next element should be safe to break as well
323
+ whitespace_trailing = true if can_break_after
324
+ # Dump the closing tag of the element
325
+ tree_string << closed_element.dump_markup_close
326
+ end
327
+ elsif style.eql? :compact
328
+ self.each_with_level do |element, level|
329
+ until open_elements.empty? or level > open_elements.last[1]
330
+ closed_element, closed_level = open_elements.pop
331
+ tree_string << " " if closed_element.break_within
332
+ whitespace_trailing = true if whitespace_chars.include?(tree_string[-1])
333
+ tree_string << closed_element.dump_markup_close
334
+ end
335
+ if element.is_a? TaggedElement
336
+ tree_string << element.dump_markup(type)
337
+ tree_string << " " if element.break_within
338
+ whitespace_trailing = true if whitespace_chars.include?(tree_string[-1])
339
+ open_elements << [element, level] if element.paired?
340
+ elsif element.is_a? TextElement
341
+ tree_string << element.dump_markup(type) unless whitespace_chars.include?(tree_string[-1]) and element.text.strip.empty?
342
+ whitespace_trailing = whitespace_chars.include?(tree_string[-1])
343
+ elsif element.is_a? DocRootElement
344
+ # Do nothing
345
+ else
346
+ tree_string << element.dump_markup(type)
347
+ tree_string << " " if element.break_within
348
+ whitespace_trailing = true if whitespace_chars.include?(tree_string[-1])
349
+ end
350
+ end
351
+ # Unknown print style
352
+ else
353
+ puts "Warning: Unknown print style. Using `:pretty`..."
354
+ tree_string = dump_markup(:pretty, type)
355
+ end
356
+ (whitespace_chars.include?(tree_string[0])) ? tree_string[1..-1] : tree_string
357
+ end
358
+
359
+ protected
360
+ def r_to_s(element, level, pretty)
361
+ element_string = (pretty ? " |"*level : "") + element.to_s + (pretty ? "\n" : "")
362
+ element.children.each do |child|
363
+ element_string << self.r_to_s(child, level + 1, pretty)
364
+ end
365
+ element_string
366
+ end
367
+
368
+ end
369
+
370
+ module Contextualized
371
+ # Swap locations of this ContextualGroup with another ContextualGroup
372
+ def swap(other)
373
+ puts "Warning: Can't swap with nil" if other.nil?
374
+ unless other.nil?
375
+ # Temp
376
+ other_parent = other.parent
377
+ other_index = other.index_in_parent
378
+
379
+ other.graft_onto(self.parent, self.index_in_parent)
380
+ self.graft_onto(other_parent, other_index)
381
+ end
382
+ end
383
+ # Wrap this ContextualGroup in an Element, with the wrapping Element taking this ContextualGroup's original place in the tree
384
+ # If the wrapping Element already exitst, then this ContextualGroup is pushed to the given index in the other's
385
+ # children (but also idk why would you need to do that?)
386
+ def wrap(element, index=-1)
387
+ element = Element.create(element) if element.is_a?(Hash)
388
+ element.graft_onto(self.parent, self.index_in_parent)
389
+ self.graft_onto(element, index)
390
+ element
391
+ end
392
+ end
393
+
394
+
395
+ module Loggable
396
+ def log(*method_names)
397
+ method_names.each do |name|
398
+ method = instance_method(name)
399
+ define_method(name) do |*args, &block|
400
+ self.history << [name, args, caller]
401
+ method.bind(self).(*args, &block)
402
+ end
403
+ end
404
+ end
405
+ def log_string
406
+ lines = ""
407
+ self.history.each {|h| lines << h.inspect << "\n"}
408
+ lines
409
+ end
410
+ end
411
+
412
+ # Generic element class
413
+ # Meant to be inherited rather than used directly
414
+ # All elements hold reference to a parent, children, and their left and right siblings
415
+ class Element
416
+ extend Forwardable
417
+ def_delegators :listing, :each
418
+ extend Loggable
419
+ include Loggable
420
+ include Enumerable
421
+ include Contextualized
422
+
423
+ protected
424
+ attr_writer :parent, :children, :sibling_prev, :sibling_next, :incrementers, :resetters, :library
425
+
426
+ public
427
+ attr_reader :parent, :children, :sibling_prev, :sibling_next, :incrementers, :resetters, :library
428
+ attr_accessor :break_within, :history
429
+
430
+ # Class method to stitch together two elements as siblings, ordered
431
+ # Will stitch values even if one or both is nil
432
+ # When used in isolation, can cause a mismatch in the tree (because parent is not updated)
433
+ def self.stitch!(prev_element, next_element)
434
+ prev_element.set_sibling_next!(next_element) unless prev_element.nil?
435
+ next_element.set_sibling_prev!(prev_element) unless next_element.nil?
436
+ end
437
+
438
+ # Create a custom element with a given namespace, tag, attributes, and text child
439
+ # If only text is given, a TextElement will be created rather than a tagged element
440
+ # If no tag is given, but attributes or namespace are given, a `div` element will be used by default
441
+ def self.create(namespace: nil, tag: nil, attrs: {}, text: nil)
442
+ tag = :div if tag.nil? and (!namespace.nil? or !attrs.empty? or text.nil?)
443
+ created_element = nil
444
+ if tag.nil?
445
+ raise TypeError.new("Text must be a String or TextElement") if !(text.is_a?(String) or text.is_a?(TextElement))
446
+ created_element = (text.is_a?(String)) ? TextElement.new(text) : text
447
+ else
448
+ tag = tag.to_sym if !tag.is_a?(Symbol)
449
+ raise TypeError.new("Tag must be a Symbol or String") if !tag.is_a?(Symbol)
450
+ namespace = namespace.to_sym if namespace.kind_of?(String) and !namespace.nil?
451
+ raise TypeError.new("Namespace must be a Symbol or String") if !namespace.is_a?(Symbol) and !namespace.nil?
452
+ sanitary_attrs = {}
453
+ attrs.each do |key, val|
454
+ key = key.to_sym if !key.is_a?(Symbol)
455
+ raise TypeError.new("Attribute name must be a Symbol or String") if !key.is_a?(Symbol)
456
+ val = val.split if val.is_a?(String)
457
+ # Ensure value is arrays of strings
458
+ raise TypeError.new("Attribute value must be a String or Array of Strings") if !val.is_a?(Array)
459
+ val.each{|sub_val| raise TypeError.new("Each attribute value in an array must be a String") if !sub_val.is_a?(String)}
460
+ sanitary_attrs[key] = val
461
+ end
462
+ created_element = TaggedElement.new(namespace, tag, sanitary_attrs)
463
+ if !text.nil?
464
+ raise TypeError.new("Text must be a string or TextElement") if !(text.is_a?(String) or text.is_a?(TextElement))
465
+ if text.is_a?(String)
466
+ text_child = TextElement.new(text)
467
+ created_element.append_child(text_child)
468
+ else
469
+ created_element.append_child(text)
470
+ end
471
+ end
472
+ end
473
+ created_element.break_within = true if !created_element.is_a?(TextElement)
474
+ yield created_element if block_given?
475
+ created_element
476
+ end
477
+ singleton_class.send(:alias_method, :make, :create)
478
+ singleton_class.send(:alias_method, :grow, :create)
479
+
480
+ def initialize
481
+ # Family of elements
482
+ @parent = nil # Element
483
+ @sibling_prev = nil # Element
484
+ @sibling_next = nil # Element
485
+ @children = [] # Array of Elements
486
+
487
+ # Properties, references, and counters
488
+ @break_within = false # Boolean
489
+ @incrementers = Hash.new # Hash with key: Symbol (name), value: Incrementer
490
+ @resetters = Hash.new # Hash with key: Symbol (name), value: Resetter
491
+ @library = Hash.new # Hash with key: Symbol, value: Element/ElementGroup
492
+ @history = Array.new # Array of arrays of form [method_called, arguments, callers]
493
+ end
494
+
495
+ # Add an element as this element's last child
496
+ def append_child(*elements)
497
+ placed = Array.new
498
+ elements.each do |element|
499
+ if !element.nil?
500
+ element = TextElement.new(element) if element.is_a?(String)
501
+ element = Element.create(element) if element.is_a?(Hash)
502
+ element.graft_last_onto(self)
503
+ element.listing.each do |member|
504
+ yield member if block_given?
505
+ placed << member
506
+ end
507
+ end
508
+ end
509
+ return nil if placed.empty?
510
+ return placed.first if placed.length == 1
511
+ return ElementGroup.new(placed)
512
+ end
513
+ alias_method :push, :append_child
514
+ alias_method :<<, :append_child
515
+
516
+ # Add an element as this element's first child
517
+ def prepend_child(*elements)
518
+ placed = Array.new
519
+ elements.each do |element|
520
+ if !element.nil?
521
+ element = TextElement.new(element) if element.is_a?(String)
522
+ element = Element.create(element) if element.is_a?(Hash)
523
+ element.graft_first_onto(self)
524
+ element.listing.each do |member|
525
+ yield member if block_given?
526
+ placed << member
527
+ end
528
+ end
529
+ end
530
+ return nil if placed.empty?
531
+ return placed.first if placed.length == 1
532
+ return ElementGroup.new(placed)
533
+ end
534
+ alias_method :place, :prepend_child
535
+ alias_method :unshift, :prepend_child
536
+
537
+ # Insert an element as a child at a specified index it this element's children
538
+ def insert_child(*elements)
539
+ placed = Array.new
540
+ elements.each do |element|
541
+ if !element.nil?
542
+ element = TextElement.new(element) if element.is_a?(String)
543
+ element = Element.create(element) if element.is_a?(Hash)
544
+ element.graft_onto(self, index)
545
+ element.listing.each do |member|
546
+ yield member if block_given?
547
+ placed << member
548
+ end
549
+ end
550
+ end
551
+ return nil if placed.empty?
552
+ return placed.first if placed.length == 1
553
+ return ElementGroup.new(placed)
554
+ end
555
+
556
+ def append_sibling(*elements)
557
+ placed = Array.new
558
+ elements.each do |element|
559
+ if !element.nil?
560
+ element = TextElement.new(element) if element.is_a?(String)
561
+ element = Element.create(element) if element.is_a?(Hash)
562
+ element.graft_onto(self.parent, self.index_in_parent+1)
563
+ element.listing.each do |member|
564
+ yield member if block_given?
565
+ placed << member
566
+ end
567
+ end
568
+ end
569
+ return nil if placed.empty?
570
+ return placed.first if placed.length == 1
571
+ return ElementGroup.new(placed)
572
+ end
573
+
574
+ def prepend_sibling(*elements)
575
+ placed = Array.new
576
+ elements.each do |element|
577
+ if !element.nil?
578
+ element = TextElement.new(element) if element.is_a?(String)
579
+ element = Element.create(element) if element.is_a?(Hash)
580
+ element.graft_onto(self.parent, self.index_in_parent)
581
+ element.listing.each do |member|
582
+ yield member if block_given?
583
+ placed << member
584
+ end
585
+ end
586
+ end
587
+ return nil if placed.empty?
588
+ return placed.first if placed.length == 1
589
+ return ElementGroup.new(placed)
590
+ end
591
+
592
+ def overwrite!(args)
593
+ replacement = Element.create(args)
594
+ puts "Warning: Overwriting this element will delete its children" if self.has_children? and !replacement.can_have_children?
595
+ replacement.graft_onto(self.parent, self.index_in_parent)
596
+ self.content.graft_onto(replacement)
597
+ replacement.break_within = self.break_within
598
+ replacement.counters = self.counters
599
+ replacement.resetters = self.resetters
600
+ replacement.library = self.library
601
+ self.detach
602
+ replacement
603
+ end
604
+
605
+ def index_in_parent
606
+ self.parent.children.index(self)
607
+ end
608
+
609
+ # Returns all text elements in this element's subtree
610
+ # Can be very expensive on large subtrees/documents, as it performs a full transversal of the subtree
611
+ def text_elements(text_children=[])
612
+ if self.is_a? TextElement
613
+ text_children << self
614
+ elsif self.is_a? TaggedElement or self.is_a? DocRootElement
615
+ @children.each {|child| child.text_elements(text_children)}
616
+ end
617
+ text_children
618
+ end
619
+
620
+ # Returns a string comprised of all text in this element's subtree
621
+ # Can be very expensive on large subtrees/documents, as it performs a full transversal of the subtree
622
+ def text_string(full_string='')
623
+ if self.is_a? TextElement
624
+ full_string << self.text
625
+ elsif self.is_a? TaggedElement or self.is_a? DocRootElement
626
+ full_string << ' ' if self.break_within
627
+ @children.each {|child| child.text_string(full_string)}
628
+ full_string << ' ' if self.break_within
629
+ end
630
+ full_string
631
+ end
632
+
633
+ # Special method to get an elements children as an AdjacentElementGroup
634
+ def content
635
+ group = AdjacentElementGroup.new
636
+ group.base(@children[0]) if not @children.empty?
637
+ group.fill
638
+ end
639
+ alias_method :contents, :content
640
+
641
+ # Add an counter incrementer to this element
642
+ def count(counter_name)
643
+ counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol)
644
+ incrementers[counter_name] = Counters::Incrementer.new(counter_name)
645
+ end
646
+
647
+ # Get a CounterElement associated with the incrementer of this element of the given name
648
+ def counter(counter_name)
649
+ counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol)
650
+ CounterElement.new(incrementers[counter_name])
651
+ end
652
+
653
+ # Add a counter resetter to this element
654
+ def reset(counter_name)
655
+ counter_name = counter_name.to_sym if !counter_name.is_a?(Symbol)
656
+ resetters[counter_name] = Counters::Resetter.new(counter_name)
657
+ end
658
+
659
+ # Add an element to a category in this element's library
660
+ def lib_add(other, category)
661
+ raise TypeError.new("Cannot create a record for a non-element") if !other.is_a?(Element)
662
+ if @library.has_key?(category)
663
+ record = @library[category]
664
+ if record.is_a?(Element)
665
+ @library[category] = ElementGroup.new([record, other])
666
+ else record.is_a?(ElementGroup)
667
+ record << other
668
+ end
669
+ else
670
+ @library[category] = other
671
+ end
672
+
673
+ end
674
+ alias_method :allude, :lib_add
675
+ alias_method :cite, :lib_add
676
+
677
+ # Get record from the given category in this element's library
678
+ def lib_get(category)
679
+ @library[category]
680
+ end
681
+ alias_method :[], :lib_get
682
+ alias_method :record, :lib_get
683
+
684
+ # Provides a listing/array containing the element
685
+ def listing
686
+ [self]
687
+ end
688
+
689
+ # General element deep copy method
690
+ def copy
691
+ element_copy = Element.new
692
+ element_copy.set_children!(@children.map {|child| child.copy})
693
+ element_copy
694
+ end
695
+
696
+ # Detach from current parent/siblings
697
+ def detach
698
+ Element.stitch!(@sibling_prev, @sibling_next)
699
+ @parent.children.delete(self) unless @parent.nil?
700
+ @parent, @sibling_prev, @sibling_next = nil
701
+ end
702
+ alias_method :prune, :detach
703
+ alias_method :delete, :detach
704
+
705
+ # Graft onto another element of the tree at any index of its children
706
+ # By default, it will graft as the last element of the other element's children
707
+ def graft_onto(graft_parent, index=-1)
708
+ # If index to small or large, graft to edges of graft_parent children
709
+ if index.abs > graft_parent.children.length
710
+ index = graft_parent.children.length * (index > 1 ? 1 : 0)
711
+ end
712
+ if index == graft_parent.children.length or index == -1
713
+ self.graft_last_onto(graft_parent)
714
+ elsif index == 0
715
+ self.graft_first_onto(graft_parent)
716
+ else
717
+ # Detach from current context
718
+ self.detach
719
+
720
+ # Update context
721
+ @parent = graft_parent
722
+
723
+ previous_child = graft_parent.children[index-1]
724
+ Element.stitch!(previous_child, self)
725
+
726
+ next_child = graft_parent.children[index]
727
+ Element.stitch!(self, next_child)
728
+
729
+ # Graft group at index
730
+ graft_parent.children.insert(index, self)
731
+ end
732
+ end
733
+
734
+ # Graft onto another element of the tree as the first child
735
+ def graft_first_onto(graft_parent)
736
+ # Detach from current context
737
+ self.detach
738
+
739
+ # Update context
740
+ @parent = graft_parent
741
+
742
+ next_child = graft_parent.children[0]
743
+ Element.stitch!(nil, self)
744
+ Element.stitch!(self, next_child)
745
+
746
+ # Insert graft group at the beginning of parent children
747
+ graft_parent.children.insert(0, self)
748
+ end
749
+
750
+ # Graft onto another element of the tree as the last child
751
+ def graft_last_onto(graft_parent)
752
+ # Detach from current context
753
+ self.detach
754
+
755
+ # Update context
756
+ @parent = graft_parent
757
+
758
+ previous_child = graft_parent.children[-1]
759
+ Element.stitch!(previous_child, self)
760
+ Element.stitch!(self, nil)
761
+
762
+ # Push graft group onto parent children
763
+ graft_parent.children.push(self)
764
+ end
765
+
766
+ # Unwrap the children of this Element, deleting it, and it's children taking its original place in the tree
767
+ def unwrap_children
768
+ unwrapped_elements = self.content
769
+ unwrapped_elements.graft_onto(self.parent, self.index_in_parent)
770
+ self.detach
771
+ unwrapped_elements
772
+ end
773
+
774
+ # Get list of all of this element's preceding siblings
775
+ def preceding_siblings
776
+ sibling_list = []
777
+ current = self
778
+ while not current.sibling_prev.nil?
779
+ sibling_list << current.sibling_prev
780
+ current = current.sibling_prev
781
+ end
782
+ sibling_list = sibling_list.reverse
783
+ sibling_list.each {|sibling| yield sibling if block_given?}
784
+ ElementGroup.new(sibling_list)
785
+ end
786
+
787
+ # Get list of all of this element's preceding siblings in reversed depth-first order
788
+ # Used for slightly speedier and practical searching
789
+ def preceding_siblings_reverse
790
+ sibling_list = []
791
+ current = self
792
+ while not current.sibling_prev.nil?
793
+ yield current.sibling_prev if block_given?
794
+ sibling_list << current.sibling_prev
795
+ current = current.sibling_prev
796
+ end
797
+ ElementGroup.new(sibling_list)
798
+ end
799
+
800
+ # Get list of all of this element's following siblings in depth-first order
801
+ def following_siblings
802
+ sibling_list = []
803
+ current = self
804
+ while not current.sibling_next.nil?
805
+ yield current.sibling_next if block_given?
806
+ sibling_list << current.sibling_next
807
+ current = current.sibling_next
808
+ end
809
+ ElementGroup.new(sibling_list)
810
+ end
811
+
812
+ # Get list of all this element's ancesotrs in depth-first order
813
+ def ancestors
814
+ parent_list = []
815
+ current = self
816
+ while not current.parent.nil?
817
+ parent_list << current.parent
818
+ current = current.parent
819
+ end
820
+ parent_list = parent_list.reverse
821
+ parent_list.each {|parent| yield parent if block_given?}
822
+ ElementGroup.new(parent_list)
823
+ end
824
+
825
+ # Get list of all of this element's ancestors in reversed depth-first order
826
+ # Used for slightly speedier and practical searching
827
+ def ancestors_reverse
828
+ parent_list = []
829
+ current = self
830
+ while not current.parent.nil?
831
+ yield current.parent if block_given?
832
+ parent_list << current.parent
833
+ current = current.parent
834
+ end
835
+ ElementGroup.new(parent_list)
836
+ end
837
+
838
+ # Get list of all this element's descendants in depth-first order
839
+ def descendants(child_list=[])
840
+ self.children.each do |child|
841
+ yield child if block_given?
842
+ child_list << child
843
+ child.descendants(child_list)
844
+ end
845
+ ElementGroup.new(child_list)
846
+ end
847
+
848
+ # Finds elements in relation to this one that fit a ScandentRule string
849
+ def find(rule_string)
850
+ rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LOCATOR)
851
+ selected = rule.locate(self) {|found_element| yield found_element if block_given?}
852
+ puts "--Warning: Rule #{rule} did not match any elements!--" if selected.empty?
853
+ ElementGroup.new(selected)
854
+ end
855
+ alias_method :locate, :find
856
+
857
+ # Finds up to `n` elements in relation to this one that fit a ScandentRule string
858
+ def find_first_n(rule_string, limit)
859
+ if limit.zero?
860
+ puts "--Warning: Rule #{rule} was given limit '0'. Returning nil...--" if selected.empty?
861
+ return nil
862
+ end
863
+ selected = []
864
+ rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LOCATOR)
865
+ rule.locate(self) do |found_element|
866
+ return ElementGroup.new(selected) if selected.length >= limit
867
+ yield found_element if block_given?
868
+ selected << found_element
869
+ end
870
+ puts "--Warning: Rule #{rule} did not match any elements!--" if selected.empty?
871
+ ElementGroup.new(selected)
872
+ end
873
+ alias_method :locate_first_n, :find_first_n
874
+
875
+ # Find the first element in relation to this one that fits a ScandentRule string
876
+ def find_first(rule_string)
877
+ self.find_first_n(rule_string, 1) {|found_element| yield found_element if block_given?}.first
878
+ end
879
+ alias_method :locate_first, :find_first
880
+
881
+ def matches_rule?(rule_string)
882
+ rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LISTENER)
883
+ rule.valid_on?(self)
884
+ end
885
+
886
+ def has_children?
887
+ !@children.empty?
888
+ end
889
+
890
+ def can_have_children?
891
+ false
892
+ end
893
+
894
+ # Does nothing but set sibling_next of this element
895
+ # If used in isolation, will cause a sibling mismatch in the tree
896
+ def set_sibling_next!(other)
897
+ @sibling_next = other
898
+ self
899
+ end
900
+
901
+ # Does nothing but set sibling_prev of this element
902
+ # If used in isolation, will cause a sibling mismatch in the tree
903
+ def set_sibling_prev!(other)
904
+ @sibling_prev = other
905
+ self
906
+ end
907
+
908
+ # Does nothing but set parent of this element
909
+ # If used in isolation, will cause a parent <=> child mismatch in the tree
910
+ def set_parent!(other)
911
+ @parent = other
912
+ self
913
+ end
914
+
915
+ # Does nothing but set children of this element
916
+ # If used in isolation, will cause a parent <=> child mismatch in the tree
917
+ # Should only be used for when set operations must be performed on element children
918
+ def set_children!(other_arr)
919
+ @children = other_arr
920
+ self
921
+ end
922
+
923
+ def to_tree
924
+ Tree.new(self)
925
+ end
926
+
927
+ def to_s
928
+ "<Generic_Element>"
929
+ end
930
+
931
+ # Temporary fix to prevent inspect from exploding due to child references
932
+ # Ideally will provide detailed state information rather than string output
933
+ def inspect
934
+ self.to_s
935
+ end
936
+
937
+ end
938
+
939
+ # Special type of element to represent the root element of a document
940
+ # A DocRootElement has no output but simply acts as a wrapper for the top level elements of an imported document
941
+ class DocRootElement < Element
942
+ def initialize
943
+ super()
944
+ end
945
+
946
+ def can_have_children?
947
+ true
948
+ end
949
+
950
+ # DocRootElement deep copy method
951
+ def copy
952
+ element_copy = DocRootElement.new
953
+ element_copy.set_children!(@children.map {|child| child.copy})
954
+ element_copy
955
+ end
956
+
957
+ def to_s
958
+ "<Root_Element>"
959
+ end
960
+
961
+ end
962
+
963
+ # A tagged element of a doctree
964
+ # Only TaggedElements have tags (duh!) and attributes, and TaggedElements hold no direct reference to text
965
+ # Ex: Both <p></p> or <br /> are considered tagged elements with no children
966
+ class TaggedElement < Element
967
+ @@unpaired_tags = [:'DOCTYPE', :'!DOCTYPE', :'area', :'base', :'br', :'col', :'command', :'embed', :'hr', :'img',
968
+ :'input', :'keygen', :'link', :'meta', :'param', :'source', :'track', :'wbr']
969
+ @@need_valid_xml = true
970
+
971
+ attr_accessor :namespace, :tag, :attrs
972
+
973
+ def initialize(namespace=nil, tag=nil, attrs={})
974
+ super()
975
+
976
+ # Element tag and attributes
977
+ @namespace = namespace # Symbol
978
+ @tag = tag # Symbol
979
+ @attrs = attrs # Hash with key: Symbol, String: Array of Strings
980
+ end
981
+
982
+ def self.paired?(tag)
983
+ !@@unpaired_tags.include?(tag)
984
+ end
985
+
986
+ # TaggedElement deep copy method
987
+ def copy
988
+ element_copy = TaggedElement.new(@namespace, @tag, @attrs.clone)
989
+ element_copy.set_children!(@children.map {|child| child.copy})
990
+ element_copy
991
+ end
992
+
993
+ # Returns the id of this element, or an auto-assigned one if none exists
994
+ def ref
995
+ if !self.has_attr?(:id) or self.attr_value_str(:id).nil?
996
+ auto_id = "auto_#{SecureRandom.uuid}"
997
+ self.attrs[:id] = [auto_id]
998
+ auto_id
999
+ else
1000
+ self.attrs[:id].join.gsub(' ', '')
1001
+ end
1002
+ end
1003
+
1004
+ def href
1005
+ "#" << self.ref
1006
+ end
1007
+
1008
+ # Returns the string value for an attribute of the given name
1009
+ def attr_value_str(attr_name)
1010
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1011
+ return nil if !self.has_attr?(attr_name)
1012
+ return self.attrs[attr_name].join(' ')
1013
+ end
1014
+
1015
+ def del_attr(attr_name)
1016
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1017
+ self.attrs.delete(attr_name)
1018
+ end
1019
+
1020
+ def set_attr_value(attr_name, attr_value)
1021
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1022
+ attr_value = attr_value.split if attr_value.is_a?(String)
1023
+
1024
+ # Ensure value is arrays of strings
1025
+ raise TypeError.new("Attribute value must be a String or Array of Strings") if !attr_value.is_a?(Array)
1026
+ attr_value.each{|sub_val| raise TypeError.new("Each attribute value in an array must be a String") if !sub_val.is_a?(String)}
1027
+
1028
+ self.attrs[attr_name] = attr_value
1029
+ end
1030
+
1031
+ def del_attr_value(attr_name, attr_value)
1032
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1033
+ attr_value = attr_value.split if attr_value.is_a?(String)
1034
+
1035
+ # Ensure value is arrays of strings
1036
+ raise TypeError.new("Attribute value must be a String or Array of Strings") if !attr_value.is_a?(Array)
1037
+ attr_value.each{|sub_val| raise TypeError.new("Each attribute value in an array must be a String") if !sub_val.is_a?(String)}
1038
+
1039
+ self.attrs[attr_name].delete_if {|sub_val| attr_value.include?(sub_val)}
1040
+ end
1041
+
1042
+ def add_attr_value(attr_name, attr_value)
1043
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1044
+ attr_value = attr_value.split if attr_value.is_a?(String)
1045
+
1046
+ # Ensure value is arrays of strings
1047
+ raise TypeError.new("Attribute value must be a String or Array of Strings") if !attr_value.is_a?(Array)
1048
+ attr_value.each{|sub_val| raise TypeError.new("Each attribute value in an array must be a String") if !sub_val.is_a?(String)}
1049
+ if self.has_attr?(attr_name)
1050
+ self.attrs[attr_name] += attr_value
1051
+ else
1052
+ self.attrs[attr_name] = attr_value
1053
+ end
1054
+ end
1055
+
1056
+ def set_tag(new_tag)
1057
+ new_tag = new_tag.to_sym if !new_tag.is_a?(Symbol)
1058
+ @tag = new_tag
1059
+ end
1060
+
1061
+ def set_namespace(new_ns)
1062
+ new_ns = new_tag.to_sym if !new_tag.is_a?(Symbol)
1063
+ @namespace = new_ns
1064
+ end
1065
+
1066
+ def del_namespace
1067
+ @namespace = nil
1068
+ end
1069
+
1070
+ # Returns whether or not the element is a paired element
1071
+ def paired?
1072
+ not @@unpaired_tags.include? self.tag
1073
+ end
1074
+
1075
+ def has_attr?(attr_name)
1076
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1077
+ self.attrs.has_key?(attr_name)
1078
+ end
1079
+
1080
+ def contains_attr_val?(attr_name, attr_value)
1081
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1082
+ self.has_attr?(attr_name) and self.attrs[attr_name].include?(attr_value)
1083
+ end
1084
+
1085
+ def equals_attr_val?(attr_name, attr_value)
1086
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1087
+ self.has_attr?(attr_name) and (self.attrs[attr_name]-attr_value).empty?
1088
+ end
1089
+
1090
+ def matches_attr_val?(attr_name, attr_regex)
1091
+ attr_name = attr_name.to_sym if !attr_name.is_a?(Symbol)
1092
+ self.has_attr?(attr_name) and !self.attrs[attr_name].grep(attr_regex).empty?
1093
+ end
1094
+
1095
+ def namespaced_tag
1096
+ self.namespace.nil? ? "#{self.tag}" : "#{self.namespace}:#{self.tag}"
1097
+ end
1098
+
1099
+ def can_have_children?
1100
+ true
1101
+ end
1102
+
1103
+ def dump_markup(type=:xml)
1104
+ element_string = "<#{self.namespaced_tag}"
1105
+ self.attrs.each do |key, values|
1106
+ element_string << " #{key}"
1107
+ element_string << "=\""
1108
+ values.each do |v|
1109
+ element_string << "#{v.gsub('&','&amp;').gsub('<', '&lt;').gsub('>','&gt;')} "
1110
+ end
1111
+ if not values.empty?
1112
+ element_string = element_string[0..-2]
1113
+ end
1114
+ element_string << "\""
1115
+ end
1116
+ # Close the tag if document must have valid xml
1117
+ if self.paired? or type == :html
1118
+ element_string << ">"
1119
+ else
1120
+ element_string << " />"
1121
+ end
1122
+ element_string
1123
+ end
1124
+
1125
+ def dump_markup_close
1126
+ "</#{self.namespaced_tag}>"
1127
+ end
1128
+
1129
+ def to_s
1130
+ element_string = "<#{self.namespaced_tag}"
1131
+ self.attrs.each do |key, values|
1132
+ element_string << " #{key}"
1133
+ element_string << "=\""
1134
+ values.each do |v|
1135
+ element_string << "#{v} "
1136
+ end
1137
+ if not values.empty?
1138
+ element_string = element_string[0..-2]
1139
+ end
1140
+ element_string << "\""
1141
+ end
1142
+ # Close the tag if document must have xml/xhtml
1143
+ if self.paired? or not @@need_valid_xml
1144
+ element_string << ">"
1145
+ else
1146
+ element_string << " />"
1147
+ end
1148
+ element_string
1149
+ end
1150
+
1151
+ def to_s_close
1152
+ "</#{self.namespaced_tag}>"
1153
+ end
1154
+
1155
+ end
1156
+
1157
+ # A text element of a doctree.
1158
+ # TextElements have no tags nor attributes and are only meant to represent document content
1159
+ # Ex: <p>Hello World</p> is a tagged element 'p' with a single text element child with text "Hello World"
1160
+ class TextElement < Element
1161
+ attr_writer :text
1162
+
1163
+ def initialize(text='')
1164
+ super()
1165
+
1166
+ # Element text
1167
+ @text = text # String
1168
+ end
1169
+
1170
+ # TextElement deep copy method
1171
+ def copy
1172
+ TextElement.new(@text)
1173
+ end
1174
+
1175
+ def text
1176
+ @text
1177
+ end
1178
+
1179
+ def dump_markup(type=:xml)
1180
+ self.text.gsub('&','&amp;').gsub('<', '&lt;').gsub('>','&gt;')
1181
+ end
1182
+
1183
+ def to_s
1184
+ self.text.inspect
1185
+ end
1186
+
1187
+ end
1188
+
1189
+ # A comment element of a doctree.
1190
+ # CommentElements have no tags nor attributes nor contribute to document content, but are preserved in the tree
1191
+ # Ex: <!-- This is an example comment -->
1192
+ class CommentElement < Element
1193
+ attr_accessor :text
1194
+
1195
+ def initialize(text='')
1196
+ super()
1197
+
1198
+ # Comment text
1199
+ @text = text # String
1200
+ end
1201
+
1202
+ # CommentElement deep copy method
1203
+ def copy
1204
+ CommentElement.new(@text)
1205
+ end
1206
+
1207
+ def dump_markup(type=:xml)
1208
+ "<!--#{self.text.gsub('&','&amp;').gsub('<', '&lt;').gsub('>','&gt;')}-->"
1209
+ end
1210
+
1211
+ def to_s
1212
+ "<!--#{self.text}-->"
1213
+ end
1214
+
1215
+ end
1216
+
1217
+ # An XML Processing Instruction element in the doctree
1218
+ # PIElements have no tags nor attributes nor contribute to document content, but are preserved in the tree
1219
+ # Ex: <?...?> represents a processing instruction i.e. <?PITarget PIContent?>
1220
+ class PIElement < Element
1221
+ attr_accessor :text
1222
+
1223
+ def initialize(text='')
1224
+ super()
1225
+
1226
+ # Element text
1227
+ @text = text # String
1228
+ end
1229
+
1230
+ # PIElement deep copy method
1231
+ def copy
1232
+ PIElement.new(@text)
1233
+ end
1234
+
1235
+ def dump_markup(type=:xml)
1236
+ "<?#{self.text.gsub('&','&amp;').gsub('<', '&lt;').gsub('>','&gt;')}?>"
1237
+ end
1238
+
1239
+ def to_s
1240
+ "<?#{self.text}?>"
1241
+ end
1242
+
1243
+ end
1244
+
1245
+ # A TextElement that holds a reference to an incrementer within the tree
1246
+ # CounterElements have no tags nor attributes and are only meant to represent document content
1247
+ class CounterElement < TextElement
1248
+ def initialize(incrementer)
1249
+ super()
1250
+ @incrementer = incrementer
1251
+ end
1252
+
1253
+ # CounterElement deep copy method
1254
+ def copy
1255
+ CounterElement.new(@incrementer)
1256
+ end
1257
+
1258
+ # The text representation of this element is the value of the referenced incrementere
1259
+ def text
1260
+ @incrementer.value.to_s
1261
+ end
1262
+ end
1263
+
1264
+ # An arbitrary group of elements
1265
+ # ElementGroups can be placed and moved to a single location like standard elements
1266
+ class ElementGroup
1267
+ extend Forwardable
1268
+ def_delegators :@group, :[], :each, :push, :<<, :length, :empty?, :first, :last
1269
+ include Enumerable
1270
+
1271
+ attr_accessor :group
1272
+
1273
+ def initialize(elements=[])
1274
+ @group = (elements.nil? or elements.empty?) ? [] : elements
1275
+ end
1276
+
1277
+ # Provides a listing/array containing the elements of the group
1278
+ def listing
1279
+ @group
1280
+ end
1281
+
1282
+ def +(other)
1283
+ ElementGroup.new(self.group + other.group)
1284
+ end
1285
+
1286
+ def adjacent?
1287
+ return true if @group.length <= 1
1288
+ @group.each_cons(2) do |current, following|
1289
+ return false if !current.sibling_next.eql? following or !following.sibling_prev.eql? current
1290
+ end
1291
+ @group.first.sibling_prev.nil? and @group.last.sibling_next.nil?
1292
+ end
1293
+
1294
+ def to_adjacent
1295
+ adj_group = AdjacentElementGroup.new
1296
+ adj_group.base(@group[0])
1297
+ adj_group.fill
1298
+ end
1299
+
1300
+ # Deep copy for ElementGroup
1301
+ def copy
1302
+ ElementGroup.new(@group.map {|member| member.copy})
1303
+ end
1304
+
1305
+ # Detach from current parent/siblings
1306
+ def detach
1307
+ @group.each do |member|
1308
+ member.detach
1309
+ end
1310
+ end
1311
+ alias_method :prune, :detach
1312
+ alias_method :delete, :detach
1313
+
1314
+ # Graft onto another element of the tree at any index of its children
1315
+ # By default, it will graft as the last element of the other element's children
1316
+ def graft_onto(graft_parent, index=-1)
1317
+ # If index to small or large, graft to edges of graft_parent children
1318
+ if index.abs > graft_parent.children.length
1319
+ index = graft_parent.children.length * (index > 1 ? 1 : 0)
1320
+ end
1321
+ if index == graft_parent.children.length or index == -1
1322
+ self.graft_last_onto(graft_parent)
1323
+ elsif index == 0
1324
+ self.graft_first_onto(graft_parent)
1325
+ else
1326
+ # Detach from current context
1327
+ self.detach
1328
+
1329
+ # Update context
1330
+ previous_child = graft_parent.children[index-1]
1331
+ next_child = graft_parent.children[index]
1332
+ @group.each do |member|
1333
+ member.set_parent!(graft_parent)
1334
+
1335
+ Element.stitch!(previous_child, member)
1336
+ previous_child = member
1337
+ end
1338
+ Element.stitch!(@group[-1], next_child)
1339
+
1340
+ # Graft group at index
1341
+ graft_parent.children.insert(index, *@group)
1342
+ end
1343
+ end
1344
+
1345
+ # Graft onto another element of the tree as the last child
1346
+ def graft_last_onto(graft_parent)
1347
+ # Detach from current context
1348
+ self.detach
1349
+
1350
+ # Update context
1351
+ previous_child = graft_parent.children[-1]
1352
+ @group.each do |member|
1353
+ member.set_parent!(graft_parent)
1354
+
1355
+ Element.stitch!(previous_child, member)
1356
+ previous_child = member
1357
+ end
1358
+ Element.stitch!(@group[-1], nil)
1359
+
1360
+ # Push graft group onto parent children
1361
+ graft_parent.children.push(*@group)
1362
+ end
1363
+
1364
+ # Graft onto another element of the tree as the first child
1365
+ def graft_first_onto(graft_parent)
1366
+ # Detach from current context
1367
+ self.detach
1368
+
1369
+ # Update context
1370
+ previous_child = nil
1371
+ next_child = graft_parent.children[0]
1372
+ @group.each do |member|
1373
+ member.set_parent!(graft_parent)
1374
+
1375
+ Element.stitch!(previous_child, member)
1376
+ previous_child = member
1377
+ end
1378
+ Element.stitch!(@group[-1], next_child)
1379
+
1380
+ # Graft group at index
1381
+ graft_parent.children.insert(0, *@group)
1382
+ end
1383
+
1384
+ end
1385
+
1386
+ class GroupListener < ElementGroup
1387
+ attr_accessor :rule, :exe_block
1388
+
1389
+ def initialize(rule_string, exe_block)
1390
+ super()
1391
+ @rule = Arboretum::Scandent::Parser.parse_rule_string(rule_string, :PATH_LISTENER)
1392
+ @exe_block = exe_block
1393
+ end
1394
+ end
1395
+
1396
+ # A group of adjacent/sibling elements
1397
+ # Must strictly be connected to one another continuously (in order)
1398
+ # AdjacentElementGroups have all functionality of ElementGroups
1399
+ # They can also wrapped and swapped with other Elements/AdjacentElementGroups
1400
+ class AdjacentElementGroup < ElementGroup
1401
+ extend Forwardable
1402
+ def_delegators :@group, :[], :each, :length, :empty?, :first, :last
1403
+ undef :<<, :push
1404
+ alias_method :to_adjacent, :itself
1405
+ include Enumerable
1406
+ include Contextualized
1407
+
1408
+ attr_accessor :group
1409
+
1410
+ def initialize(element=nil)
1411
+ @group = element.nil? ? [] : [element]
1412
+ end
1413
+
1414
+ # Assign starting element of the AdjacentElementGroup, if there is not already one
1415
+ def base(element)
1416
+ raise IndexError.new("Base element already selected") if not @group.empty?
1417
+ raise TypeError.new("Base element cannot be nil") if element.nil?
1418
+ @group << element
1419
+ self
1420
+ end
1421
+
1422
+ def +(other)
1423
+ ElementGroup.new(self.group + other.group)
1424
+ end
1425
+
1426
+ def adjacent?
1427
+ true
1428
+ end
1429
+
1430
+ # Deep copy for AdjacentElementGroup
1431
+ def copy
1432
+ group_copy = @group.map {|member| member.copy}
1433
+ Element.stitch!(nil, group_copy.first)
1434
+ group_copy.each_cons(2) {|current, following| Element.stitch!(current,following)}
1435
+ Element.stitch(group_copy.last, nil)
1436
+ adj_group_copy = AdjacentElementGroup.new
1437
+ adj_group_copy.base(group_copy.first)
1438
+ adj_group_copy.fill
1439
+ end
1440
+
1441
+ # Extend group as much as it can be extended
1442
+ def fill
1443
+ if not @group.empty?
1444
+ extend_prev until @group.first.sibling_prev.nil?
1445
+ extend_next until @group.last.sibling_next.nil?
1446
+ end
1447
+ self
1448
+ end
1449
+
1450
+ def index_in_parent
1451
+ self.parent.children.index(@group.first)
1452
+ end
1453
+
1454
+ def sibling_prev
1455
+ @group.first.sibling_prev
1456
+ end
1457
+
1458
+ def sibling_next
1459
+ @group.last.sibling_next
1460
+ end
1461
+
1462
+ def parent
1463
+ @group.first.parent
1464
+ end
1465
+
1466
+ # Extend the adjacent group to the last element's next sibling
1467
+ def extend_next
1468
+ @group << @group.last.sibling_next unless @group.last.sibling_next.nil?
1469
+ self
1470
+ end
1471
+ # Extend the adjacent group to the first element's previous sibling
1472
+ def extend_prev
1473
+ @group.unshift(@group.first.sibling_prev) unless @group.first.sibling_prev.nil?
1474
+ self
1475
+ end
1476
+
1477
+ # Provides a listing/array containing the elements of the group
1478
+ def listing
1479
+ @group
1480
+ end
1481
+
1482
+ # Detach from current parent/siblings, but maintain internal references between siblings
1483
+ def detach
1484
+ next_sibling = @group[-1].nil? ? nil : @group[-1].sibling_next
1485
+ prev_sibling = @group[0].nil? ? nil : @group[0].sibling_prev
1486
+ Element.stitch!(prev_sibling, next_sibling)
1487
+ @group[0].parent.set_children!(@group[0].parent.children - @group) unless (@group[0].nil? or @group[0].parent.nil?)
1488
+
1489
+ @group.each {|member| member.set_parent!(nil)}
1490
+ @group.first.set_sibling_prev!(nil)
1491
+ @group.last.set_sibling_next!(nil)
1492
+ end
1493
+ alias_method :prune, :detach
1494
+ alias_method :delete, :detach
1495
+
1496
+ # Graft onto another element of the tree at any index of its children
1497
+ # By default, it will graft as the last element of the other element's children
1498
+ def graft_onto(graft_parent, index=-1)
1499
+ # If index to small or large, graft to edges of graft_parent children
1500
+ if index.abs > graft_parent.children.length
1501
+ index = graft_parent.children.length * (index > 1 ? 1 : 0)
1502
+ end
1503
+ if index == graft_parent.children.length or index == -1
1504
+ self.graft_last_onto(graft_parent)
1505
+ elsif index == 0
1506
+ self.graft_first_onto(graft_parent)
1507
+ else
1508
+ # Detach from current context
1509
+ self.detach
1510
+
1511
+ # Update context
1512
+ @group.each do |member|
1513
+ member.set_parent!(graft_parent)
1514
+ end
1515
+
1516
+ previous_child = graft_parent.children[index-1]
1517
+ next_child = graft_parent.children[index]
1518
+ Element.stitch!(previous_child, @group[0])
1519
+ Element.stitch!(@group[-1], next_child)
1520
+
1521
+ # Graft group at index
1522
+ graft_parent.children.insert(index, *@group)
1523
+ end
1524
+ end
1525
+
1526
+ # Graft onto another element of the tree as the first child
1527
+ def graft_first_onto(graft_parent)
1528
+ # Detach from current context
1529
+ self.detach
1530
+
1531
+ # Update context
1532
+ @group.each do |member|
1533
+ member.set_parent!(graft_parent)
1534
+ end
1535
+
1536
+ next_child = graft_parent.children[0]
1537
+ Element.stitch!(nil, @group[0])
1538
+ Element.stitch!(@group[-1], next_child)
1539
+
1540
+ # Insert graft group at the beginning of parent children
1541
+ graft_parent.children.insert(0, *@group)
1542
+ end
1543
+
1544
+ # Graft onto another element of the tree as the last child
1545
+ def graft_last_onto(graft_parent)
1546
+ # Detach from current context
1547
+ self.detach
1548
+
1549
+ # Update context
1550
+ @group.each do |member|
1551
+ member.set_parent!(graft_parent)
1552
+ end
1553
+
1554
+ previous_child = graft_parent.children[-1]
1555
+ Element.stitch!(previous_child, @group[0])
1556
+ Element.stitch!(@group[-1], nil)
1557
+
1558
+ # Push graft group onto parent children
1559
+ graft_parent.children.push(*@group)
1560
+ end
1561
+
1562
+ end
1563
+
1564
+ end # Elements
1565
+ end # DocTree
1566
+ end # Arboretum