File Coverage

blib/lib/Tree/Compat/Tree/DAG_Node.pm
Criterion Covered Total %
statement 50 565 8.8
branch 6 264 2.2
condition 3 60 5.0
subroutine 14 76 18.4
pod 0 52 0.0
total 73 1017 7.1


line stmt bran cond sub pod time code
1             package Tree::Compat::Tree::DAG_Node;
2              
3 1     1   6 use strict;
  1         2  
  1         31  
4 1     1   5 use warnings;
  1         2  
  1         55  
5              
6             our $VERSION = '1.00';
7              
8             package Tree::DAG_Node;
9              
10             # Set %INC so that require() thinks Tree::DAG_Node has already been loaded
11             $INC{'Tree/DAG_Node.pm'} = $INC{'Tree::Compat::Tree::DAG_Node'};
12              
13 1     1   5 use strict;
  1         2  
  1         24  
14 1     1   5 use warnings;
  1         1  
  1         25  
15              
16 1     1   5 use Carp ();
  1         2  
  1         24  
17 1     1   12 use Scalar::Util qw( blessed weaken );
  1         2  
  1         90  
18 1     1   925 use Tree;
  1         5684  
  1         10  
19              
20             our $Debug = 0;
21              
22             sub new {
23 1     1 0 668 my $class = shift;
24 1 50       8 $class = blessed($class) if blessed($class);
25              
26 1         8 my $tree = Tree->new();
27 1         47 $tree->error_handler( $tree->DIE );
28              
29 1         13 my $self = bless \$tree, $class;
30              
31 1         5 $tree->meta->{compat}{object} = $self;
32 1         8 weaken( $self );
33              
34 1 50       18 print "Constructing $self in class $class\n" if $Debug;
35              
36 1         3 $self->_init();
37              
38 1         2 return $self;
39             }
40              
41 1     1 0 382 sub REAL_TREE { ${+shift} }
  1         5  
42              
43             sub _init {
44 1     1   2 my $self = shift;
45 1 50       3 my $o = ref($_[0]) eq 'HASH' ? $_[0] : {};
46              
47 1         2 $self->$_($o) for map {
  4         9  
48             "_init_${_}"
49             } qw( mother daughters name attributes );
50              
51 1         2 return;
52             }
53              
54             sub _init_mother {
55 1     1   2 my $self = shift;
56 1         2 my ($o) = @_;
57              
58 1 50 33     5 if ( exists $o->{mother} && blessed $o->{mother} ) {
59 0         0 $o->{mother}->add_daughter( $self );
60             }
61             }
62              
63             sub _init_daughters {
64 1     1   1 my $self = shift;
65 1         2 my ($o) = @_;
66              
67 1 0 33     5 if ( exists $o->{daughters}
  0   33     0  
68             && ref($o->{daughters}) eq 'ARRAY'
69             && @{$o->{daughters}}
70             ) {
71 0         0 $self->set_daughters( @{$o->{daughters}} );
  0         0  
72             }
73             }
74              
75             sub _init_name {
76 1     1   1 my $self = shift;
77 1         1 my ($o) = @_;
78              
79 1 50       5 if ( exists $o->{name} ) {
80 0         0 $self->name( $o->{name} );
81             }
82             }
83              
84             sub _init_attributes {
85 1     1   1 my $self = shift;
86 1         1 my ($o) = @_;
87              
88 1 50       4 if ( exists $o->{attributes} ) {
89 0           $self->attributes( $o->{attributes} );
90             }
91             }
92              
93             sub daughters {
94 0     0 0   my $tree = ${+shift};
  0            
95 0 0         @_ && Carp::croak( "Don't set daughters with doughters anymore\n" );
96              
97 0           my @children = map { $_->meta->{compat}{object} } $tree->children;
  0            
98 0 0         return wantarray ? @children : \@children;
99             }
100              
101             sub mother {
102 0     0 0   my $tree = ${+shift};
  0            
103 0 0         @_ && Carp::croak( "Don't set daughters with doughters anymore\n" );
104              
105 0 0         if ( my $parent = $tree->parent ) {
106 0           return $parent->meta->{compat}{object};
107             }
108 0           return;
109             }
110              
111             sub add_daughter {
112 0     0 0   my $tree = ${+shift};
  0            
113 0 0         return unless @_;
114 0           $tree->add_child(map { ${$_} } @_);
  0            
  0            
115 0           return;
116             }
117             *add_daughters = \&add_daughter;
118              
119             sub add_daughter_left {
120 0     0 0   my $tree = ${+shift};
  0            
121 0 0         return unless @_;
122 0           $tree->add_child({ at => 0 }, map { ${$_} } @_);
  0            
  0            
123 0           return;
124             }
125             *add_daughters_left = \&add_daughter_left;
126              
127             sub new_daughter {
128 0     0 0   my $self = shift;
129              
130 0           my $child = $self->new( @_ );
131 0           $self->add_daughter( $child );
132 0           return $child;
133             }
134              
135             sub new_daughter_left {
136 0     0 0   my $self = shift;
137              
138 0           my $child = $self->new( @_ );
139 0           $self->add_daughter_left( $child );
140 0           return $child;
141             }
142              
143             sub remove_daughter {
144 0     0 0   my $self = shift;
145 0 0         Carp::croak "mother must be an object!" unless ref $self;
146              
147 0 0         return unless @_;
148              
149 0           ${$self}->remove_child( map { ${$_} } @_ );
  0            
  0            
  0            
150              
151 0           return;
152             }
153             *remove_daughters = \&remove_daughter;
154              
155             sub unlink_from_mother {
156 0     0 0   my $self = shift;
157 0 0         if ( my $parent = $self->mother ) {
158 0           $parent->remove_daughter( $self );
159 0           return $parent;
160             }
161 0           return;
162             }
163              
164             sub clear_daughters {
165 0     0 0   my $self = shift;
166 0 0         return unless @_;
167              
168 0           return map {
169 0           $_->meta->{compat}{object}
170 0           } ${$self}->remove_child( map { ${$_} } @_ );
  0            
  0            
171             }
172              
173             sub set_daughters {
174 0     0 0   my $self = shift;
175 0           $self->clear_daughters;
176 0 0         $self->add_daughters( @_ ) if @_;
177             }
178              
179             sub replace_with {
180 0     0 0   my $self = shift;
181 0           my @replacements = @_;
182              
183 0 0         if ( !$self->mother ) {
184 0           foreach my $node ( @replacements ) {
185 0 0         if ( my $parent = $node->mother ) {
186 0           $parent->remove_daughter( $node );
187             }
188             }
189             }
190             else {
191 0           my $parent = $self->mother;
192 0           @replacements = grep {
193 0           my $_parent = $_->mother;
194 0 0 0       $_ eq $self || !$_parent || $_parent ne $parent
195             } @replacements;
196              
197 0 0         $parent->set_daughters(
198             map {
199 0           $_ eq $self ? (@replacements) : $_
200             } $parent->daughters
201             );
202             }
203              
204 0           return( $self, @replacements );
205             }
206              
207             sub replace_with_daughters {
208 0     0 0   my $self = shift;
209 0           $self->replace_with( $self->clear_daughters );
210             }
211              
212             sub add_left_sister {
213 0     0 0   my $self = shift;
214 0 0         return unless @_;
215 0           my @sisters = $self->replace_with( @_, $self );
216 0           shift @sisters; pop @sisters; # Remove copies of $self.
  0            
217 0           return @sisters;
218             }
219             *add_left_sisters = \&add_left_sister;
220              
221             sub add_right_sister {
222 0     0 0   my $self = shift;
223 0 0         return unless @_;
224 0           my @sisters = $self->replace_with( $self, @_ );
225 0           shift @sisters; shift @sisters; # Remove copies of $self.
  0            
226 0           return @sisters;
227             }
228             *add_right_sisters = \&add_left_sister;
229              
230             sub name {
231 0     0 0   my $tree = ${+shift};
  0            
232 0 0         $tree->set_value( @_ ) if @_;
233 0           return $tree->value;
234             }
235              
236             sub attribute {
237 0     0 0   my $tree = ${+shift};
  0            
238 0 0         if ( @_ ) {
239 0 0         Carp::carp "my parameter must be a reference" unless ref($_[0]);
240 0           $tree->meta->{compat}{attributes} = $_[0];
241             }
242 0           return $tree->meta->{compat}{attributes};
243             }
244             *attributes = \&attribute;
245              
246 0     0 0   sub is_node { return 1; }
247              
248             sub ancestors {
249 0     0 0   my $self = shift;
250              
251 0 0         my @ancestors = ( $self->mother )
252             or return;
253              
254 0           while ( my $parent = $ancestors[-1]->mother ) {
255 0           push @ancestors, $parent;
256             }
257              
258 0           return @ancestors;
259             }
260              
261             sub root {
262 0     0 0   ${+shift}->root->meta->{compat}{object};
  0            
263             }
264              
265             sub is_daughter_of {
266 0     0 0   return $_[0]->parent eq $_[1];
267             }
268              
269             sub self_and_descendants {
270 0     0 0   return map { $_->meta->{compat}{object} } ${$_[0]}->traverse;
  0            
  0            
271             }
272              
273             sub descendants {
274 0     0 0   my $self = shift;
275 0           my @list = $self->self_and_descendants;
276 0           shift @list; # lose myself
277 0           return @list;
278             }
279              
280             sub leaves_under {
281 0     0 0   my $tree = ${+shift};
  0            
282              
283 0           my @list;
284              
285 0           my $traversal = $tree->traverse;
286 0           while ( my $node = $traversal->() ) {
287 0 0         push @list, $node->meta->{compat}{object}
288             if $node->is_leaf;
289             }
290              
291 0           return @list;
292             }
293              
294             sub depth_under {
295 0     0 0   my $tree = ${+shift};
  0            
296              
297 0           my $max_depth = my $depth = $tree->depth;
298 0           my $traversal = $tree->traverse;
299 0           while ( my $node = $traversal->() ) {
300 0 0         $max_depth = $node->depth if $node->depth > $max_depth;
301             }
302              
303 0           return $max_depth - $depth;
304             }
305              
306 0     0 0   sub generation {
307             }
308              
309 0     0 0   sub generation_under {
310             }
311              
312             sub self_and_sisters {
313 0     0 0   my $self = shift;
314 0 0         my $parent = $self->mother or return $self;
315 0           return $parent->daughters;
316             }
317              
318             sub sisters {
319 0     0 0   my $self = shift;
320 0           return grep { $_ ne $self } $self->self_and_sisters;
  0            
321             }
322              
323             sub left_sisters {
324 0     0 0   my $self = shift;
325 0 0         my $parent = $self->mother or return;
326              
327 0           my @sisters;
328 0           foreach my $sister ($parent->daughters) {
329 0 0         last if $sister eq $self;
330 0           push @sisters, $sister;
331             }
332              
333 0           return @sisters;
334             }
335              
336             sub left_sister {
337 0     0 0   my $self = shift;
338 0 0         my @sisters = $self->left_sisters
339             or return;
340 0           return $sisters[-1];
341             }
342              
343             sub right_sisters {
344 0     0 0   my $self = shift;
345 0 0         my $parent = $self->mother or return;
346              
347 0           my @sisters;
348 0           foreach my $sister (reverse $parent->daughters) {
349 0 0         last if $sister eq $self;
350 0           push @sisters, $sister;
351             }
352              
353 0           return reverse @sisters;
354             }
355              
356             sub right_sister {
357 0     0 0   my $self = shift;
358 0 0         my @sisters = $self->right_sisters
359             or return;
360 0           return $sisters[0];
361             }
362              
363             sub my_daughter_index {
364 0     0 0   my $self = shift;
365 0 0         my $parent = $self->mother
366             or return 0;
367              
368 0           return ${$parent}->get_index_for( ${$self} );
  0            
  0            
369             }
370              
371             sub address {
372 0     0 0   my $self = shift;
373 0           my ($address) = @_;
374              
375 0 0 0       if ( defined $address && length $address ) {
376 0           my @parts = map {$_ + 0}
  0            
377             $address =~ m/(\d+)/g; # generous!
378 0 0         Carp::croak "Address \"$address\" is an ill-formed address" unless @parts;
379 0 0         Carp::croak "Address \"$address\" must start with '0'" unless shift(@parts) == 0;
380              
381 0           my $current_node = $self->root;
382 0           while ( @parts ) {
383 0           my $index = shift @parts;
384 0           my @daughters = $current_node->daughters;
385              
386 0 0         if ( $#daughters < $index ) {
387 0           return;
388             }
389 0           $current_node = $daughters[$index];
390             }
391              
392 0           return $current_node;
393             }
394             else {
395 0           my @parts;
396 0           my $current_node = $self;
397 0           while ( my $parent = $current_node->mother ) {
398 0           unshift @parts, $current_node->my_daughter_index;
399 0           $current_node = $parent;
400             }
401 0           return join( ':', 0, @parts );
402             }
403             }
404              
405             sub common {
406 0     0 0   my ($first, @others) = @_;
407 0 0         return $first unless @others;
408              
409 0           foreach my $node (@others) {
410 0           my %first_lineage;
411 0           @first_lineage{$first, $first->ancestors} = undef;
412 0           my $higher = undef; # the common of $first and $node
413 0           my @my_lineage = $node->ancestors;
414              
415             Find_Common:
416 0           while (@my_lineage) {
417 0 0         if ( exists $first_lineage{$my_lineage[0]} ) {
418 0           $higher = $my_lineage[0];
419 0           last Find_Common;
420             }
421 0           shift @my_lineage;
422             }
423 0 0         return undef unless $higher;
424 0           $first = $higher;
425             }
426              
427 0           return $first;
428             }
429              
430             sub common_ancestor {
431 0     0 0   my ($first, @others) = @_;
432 0 0         return $first->parent unless @others;
433              
434 0           my %ones;
435 0           @ones{ @_ } = undef;
436              
437 0           my $common = $first->common( @others );
438 0 0         if ( exists( $ones{$common} ) ) {
439 0           return $common->mother;
440             } else {
441 0           return $common;
442             }
443             }
444              
445             sub walk_down {
446 0     0 0   my ($this,$o) = @_;
447              
448 0 0         Carp::croak "I need options!" unless ref($o);
449 0 0 0       Carp::croak "I need a callback or a callbackback" unless
450             ( ref($o->{'callback'}) || ref($o->{'callbackback'}) );
451              
452 0 0         my $callback = ref($o->{'callback'}) ? $o->{'callback'} : undef;
453 0 0         my $callbackback = ref($o->{'callbackback'}) ? $o->{'callbackback'} : undef;
454 0           my $callback_status = 1;
455              
456 0 0         $callback_status = &{ $callback }( $this, $o ) if $callback;
  0            
457              
458 0 0         if($callback_status) {
459 0 0         my @daughters = UNIVERSAL::can($this, 'is_node') ? @{$this->daughters} : ();
  0            
460 0 0         if(@daughters) {
461 0           $o->{'_depth'} += 1;
462 0           foreach my $one (@daughters) {
463 0 0         $one->walk_down($o) if UNIVERSAL::can($one, 'is_node');
464             }
465 0           $o->{'_depth'} -= 1;
466             }
467             }
468              
469 0 0         if($callbackback){
470 0 0         if(UNIVERSAL::can($this, 'is_node')) {
471 0           scalar( &{ $callbackback }( $this, $o ) );
  0            
472             }
473             }
474              
475 0           return;
476             }
477              
478             sub dump_names {
479 0     0 0   my($it, $o) = @_[0,1];
480 0 0         $o = {} unless ref $o;
481 0   0       $o->{'_depth'} ||= 0;
482 0   0       $o->{'indent'} ||= ' ';
483 0   0       $o->{'tick'} ||= '';
484              
485 0           my @out = ();
486             $o->{'callback'} = sub {
487 0     0     my($this, $o) = @_[0,1];
488 0   0       push(@out,
489             join('',
490             $o->{'indent'} x $o->{'_depth'},
491             $o->{'tick'},
492             &Tree::DAG_Node::_dump_quote($this->name || $this),
493             "\n"
494             )
495             );
496 0           return 1;
497 0           };
498              
499 0           $it->walk_down($o);
500 0           return @out;
501             }
502              
503             sub random_network {
504 0     0 0   Carp::croak( "random_network() unimplemented." );
505             }
506              
507             sub lol_to_tree {
508 0     0 0   my($class, $lol, $seen_r) = @_;
509 0 0         $seen_r = {} unless ref($seen_r) eq 'HASH';
510 0 0 0       return if ref($lol) && $seen_r->{$lol}++; # catch circularity
511              
512 0   0       $class = ref($class) || $class;
513 0           my $node = $class->new();
514              
515 0 0         unless(ref($lol) eq 'ARRAY') { # It's a terminal node.
516 0 0         $node->name($lol) if defined $lol;
517 0           return $node;
518             }
519 0 0         return $node unless @$lol; # It's a terminal node, oddly represented
520              
521             # It's a non-terminal node.
522              
523 0           my @options = @$lol;
524 0 0         unless(ref($options[-1]) eq 'ARRAY') {
525             # This is what separates this method from simple_lol_to_tree
526 0           $node->name(pop(@options));
527             }
528              
529 0           foreach my $d (@options) { # Scan daughters (whether scalars or listrefs)
530 0           $node->add_daughter( $class->lol_to_tree($d, $seen_r) ); # recurse!
531             }
532              
533 0           return $node;
534             }
535              
536             sub tree_to_lol_notation {
537 0     0 0   my($it, $o) = @_;
538 0           my $root = $it;
539              
540 0 0         $o = {} unless ref $o;
541 0           my @out = ();
542 0   0       $o->{'_depth'} ||= 0;
543 0 0         $o->{'multiline'} = 0 unless exists($o->{'multiline'});
544              
545 0           my $line_end;
546 0 0         if($o->{'multiline'}) {
547 0   0       $o->{'indent'} ||= ' ';
548 0           $line_end = "\n";
549             } else {
550 0   0       $o->{'indent'} ||= '';
551 0           $line_end = '';
552             }
553              
554             $o->{'callback'} = sub {
555 0     0     my($this, $o) = @_[0,1];
556 0           push(@out,
557             $o->{'indent'} x $o->{'_depth'},
558             "[$line_end",
559             );
560 0           return 1;
561 0           };
562              
563             $o->{'callbackback'} = sub {
564 0     0     my($this, $o) = @_[0,1];
565 0           my $name = $this->name;
566 0 0         if(!defined($name)) {
567 0           $name = 'undef';
568             } else {
569 0           $name = &Tree::DAG_Node::_dump_quote($name);
570             }
571 0           push(@out,
572             $o->{'indent'} x ($o->{'_depth'} + 1),
573             "$name$line_end",
574             $o->{'indent'} x $o->{'_depth'},
575             "], $line_end",
576             );
577 0           return 1;
578 0           };
579              
580 0           $it->walk_down($o);
581 0           return join('', @out);
582             }
583              
584             sub tree_to_lol {
585             # I haven't /rigorously/ tested this.
586 0     0 0   my($it, $o) = @_;
587 0 0         $o = {} unless ref $o;
588              
589 0           my $out = [];
590 0           my @lol_stack = ($out);
591             $o->{'callback'} = sub {
592 0     0     my($this, $o) = @_[0,1];
593 0           my $new = [];
594 0           push @{$lol_stack[-1]}, $new;
  0            
595 0           push(@lol_stack, $new);
596 0           return 1;
597 0           };
598              
599             $o->{'callbackback'} = sub {
600 0     0     my($this, $o) = @_[0,1];
601 0           push @{$lol_stack[-1]}, $this->name;
  0            
602 0           pop @lol_stack;
603 0           return 1;
604 0           };
605              
606 0           $it->walk_down($o);
607 0 0         die "totally bizarre error 12416" unless ref($out->[0]);
608 0           $out = $out->[0]; # the real root
609 0           return $out;
610             }
611              
612             sub simple_lol_to_tree {
613 0     0 0   my($class, $lol, $seen_r) = @_;
614 0   0       $class = ref($class) || $class;
615 0 0         $seen_r = {} unless ref($seen_r) eq 'HASH';
616 0 0 0       return if ref($lol) && $seen_r->{$lol}++; # catch circularity
617              
618 0           my $node = $class->new();
619              
620 0 0         unless(ref($lol) eq 'ARRAY') { # It's a terminal node.
621 0 0         $node->name($lol) if defined $lol;
622 0           return $node;
623             }
624              
625             # It's a non-terminal node.
626 0           foreach my $d (@$lol) { # scan daughters (whether scalars or listrefs)
627 0           $node->add_daughter( $class->simple_lol_to_tree($d, $seen_r) ); # recurse!
628             }
629              
630 0           return $node;
631             }
632              
633             sub tree_to_simple_lol {
634             # I haven't /rigorously/ tested this.
635 0     0 0   my $root = $_[0];
636              
637 0 0         return $root->name unless scalar($root->daughters);
638             # special case we have to nip in the bud
639              
640 0           my($it, $o) = @_[0,1]; # $o is currently unused anyway
641 0 0         $o = {} unless ref $o;
642              
643 0           my $out = [];
644 0           my @lol_stack = ($out);
645             $o->{'callback'} = sub {
646 0     0     my($this, $o) = @_[0,1];
647 0           my $new;
648 0 0         $new = scalar($this->daughters) ? [] : $this->name;
649             # Terminal nodes are scalars, the rest are listrefs we'll fill in
650             # as we recurse the tree below here.
651 0           push @{$lol_stack[-1]}, $new;
  0            
652 0           push(@lol_stack, $new);
653 0           return 1;
654 0           };
655              
656 0     0     $o->{'callbackback'} = sub { pop @lol_stack; return 1; };
  0            
  0            
657 0           $it->walk_down($o);
658 0 0         die "totally bizarre error 12416" unless ref($out->[0]);
659 0           $out = $out->[0]; # the real root
660 0           return $out;
661             }
662              
663             sub tree_to_simple_lol_notation {
664 0     0 0   my($it, $o) = @_[0,1];
665 0 0         $o = {} unless ref $o;
666 0           my @out = ();
667 0   0       $o->{'_depth'} ||= 0;
668 0 0         $o->{'multiline'} = 0 unless exists($o->{'multiline'});
669              
670 0           my $line_end;
671 0 0         if($o->{'multiline'}) {
672 0   0       $o->{'indent'} ||= ' ';
673 0           $line_end = "\n";
674             } else {
675 0   0       $o->{'indent'} ||= '';
676 0           $line_end = '';
677             }
678              
679             $o->{'callback'} = sub {
680 0     0     my($this, $o) = @_[0,1];
681 0 0         if(scalar($this->daughters)) { # Nonterminal
682 0           push(@out,
683             $o->{'indent'} x $o->{'_depth'},
684             "[$line_end",
685             );
686             } else { # Terminal
687 0           my $name = $this->name;
688 0 0         push @out,
689             $o->{'indent'} x $o->{'_depth'},
690             defined($name) ? &Tree::DAG_Node::_dump_quote($name) : 'undef',
691             ",$line_end";
692             }
693 0           return 1;
694 0           };
695              
696             $o->{'callbackback'} = sub {
697 0     0     my($this, $o) = @_[0,1];
698 0 0         push(@out,
699             $o->{'indent'} x $o->{'_depth'},
700             "], $line_end",
701             ) if scalar($this->daughters);
702 0           return 1;
703 0           };
704              
705 0           $it->walk_down($o);
706 0           return join('', @out);
707             }
708              
709             sub draw_ascii_tree {
710             # Make a "box" for this node and its possible daughters, recursively.
711              
712             # The guts of this routine are horrific AND recursive!
713              
714             # Feel free to send me better code. I worked on this until it
715             # gave me a headache and it worked passably, and then I stopped.
716              
717 0     0 0   my $it = $_[0];
718 0 0         my $o = ref($_[1]) ? $_[1] : {};
719 0           my(@box, @daughter_boxes, $width, @daughters);
720 0           @daughters = $it->daughters;
721              
722             # $it->no_cyclicity;
723              
724 0 0         $o->{'no_name'} = 0 unless exists $o->{'no_name'};
725 0 0         $o->{'h_spacing'} = 1 unless exists $o->{'h_spacing'};
726 0 0         $o->{'h_compact'} = 1 unless exists $o->{'h_compact'};
727 0 0         $o->{'v_compact'} = 1 unless exists $o->{'v_compact'};
728              
729 0           my $printable_name;
730 0 0         if($o->{'no_name'}) {
731 0           $printable_name = '*';
732             } else {
733 0   0       $printable_name = $it->name || $it;
734 0           $printable_name =~ tr<\cm\cj\t >< >s;
735 0           $printable_name = "<$printable_name>";
736             }
737              
738 0 0         if(!scalar(@daughters)) { # I am a leaf!
739             # Now add the top parts, and return.
740 0           @box = ("|", $printable_name);
741             } else {
742 0           @daughter_boxes = map { &draw_ascii_tree($_, $o) } @daughters;
  0            
743              
744 0           my $max_height = 0;
745 0           foreach my $box (@daughter_boxes) {
746 0           my $h = @$box;
747 0 0         $max_height = $h if $h > $max_height;
748             }
749              
750 0           @box = ('') x $max_height; # establish the list
751              
752 0           foreach my $one (@daughter_boxes) {
753 0           my $length = length($one->[0]);
754 0           my $height = @$one;
755              
756             #now make all the same height.
757 0           my $deficit = $max_height - $height;
758 0 0         if($deficit > 0) {
759 0           push @$one, ( scalar( ' ' x $length ) ) x $deficit;
760 0           $height = scalar(@$one);
761             }
762              
763              
764             # Now tack 'em onto @box
765             ##########################################################
766             # This used to be a sub of its own. Ho-hum.
767              
768 0           my($b1, $b2) = (\@box, $one);
769 0           my($h1, $h2) = (scalar(@$b1), scalar(@$b2));
770              
771 0           my(@diffs, $to_chop);
772 0 0         if($o->{'h_compact'}) { # Try for h-scrunching.
773 0           my @diffs;
774 0           my $min_diff = length($b1->[0]); # just for starters
775 0           foreach my $line (0 .. ($h1 - 1)) {
776 0           my $size_l = 0; # length of terminal whitespace
777 0           my $size_r = 0; # length of initial whitespace
778 0 0         $size_l = length($1) if $b1->[$line] =~ /( +)$/s;
779 0 0         $size_r = length($1) if $b2->[$line] =~ /^( +)/s;
780 0           my $sum = $size_l + $size_r;
781            
782 0 0         $min_diff = $sum if $sum < $min_diff;
783 0           push @diffs, [$sum, $size_l, $size_r];
784             }
785 0           $to_chop = $min_diff - $o->{'h_spacing'};
786 0 0         $to_chop = 0 if $to_chop < 0;
787             }
788              
789 0 0 0       if(not( $o->{'h_compact'} and $to_chop )) {
790             # No H-scrunching needed/possible
791 0           foreach my $line (0 .. ($h1 - 1)) {
792 0           $b1->[ $line ] .= $b2->[ $line ] . (' ' x $o->{'h_spacing'});
793             }
794             } else {
795             # H-scrunching is called for.
796 0           foreach my $line (0 .. ($h1 - 1)) {
797 0           my $r = $b2->[$line]; # will be the new line
798 0           my $remaining = $to_chop;
799 0 0         if($remaining) {
800 0           my($l_chop, $r_chop) = @{$diffs[$line]}[1,2];
  0            
801            
802 0 0         if($l_chop) {
803 0 0         if($l_chop > $remaining) {
    0          
804 0           $l_chop = $remaining;
805 0           $remaining = 0;
806             } elsif($l_chop == $remaining) {
807 0           $remaining = 0;
808             } else { # remaining > l_chop
809 0           $remaining -= $l_chop;
810             }
811             }
812 0 0         if($r_chop) {
813 0 0         if($r_chop > $remaining) {
    0          
814 0           $r_chop = $remaining;
815 0           $remaining = 0;
816             } elsif($r_chop == $remaining) {
817 0           $remaining = 0;
818             } else { # remaining > r_chop
819 0           $remaining -= $r_chop; # should never happen!
820             }
821             }
822              
823 0 0         substr($b1->[$line], -$l_chop) = '' if $l_chop;
824 0 0         substr($r, 0, $r_chop) = '' if $r_chop;
825             } # else no-op
826 0           $b1->[ $line ] .= $r . (' ' x $o->{'h_spacing'});
827             }
828             # End of H-scrunching ickyness
829             }
830             # End of ye big tack-on
831              
832             }
833             # End of the foreach daughter_box loop
834              
835             # remove any fencepost h_spacing
836 0 0         if($o->{'h_spacing'}) {
837 0           foreach my $line (@box) {
838 0 0         substr($line, -$o->{'h_spacing'}) = '' if length($line);
839             }
840             }
841              
842             # end of catenation
843 0 0         die "SPORK ERROR 958203: Freak!!!!!" unless @box;
844              
845             # Now tweak the pipes
846 0           my $new_pipes = $box[0];
847 0           my $pipe_count = $new_pipes =~ tr<|><+>;
848 0 0         if($pipe_count < 2) {
849 0           $new_pipes = "|";
850             } else {
851 0           my($init_space, $end_space);
852              
853             # Thanks to Gilles Lamiral for pointing out the need to set to '',
854             # to avoid -w warnings about undeffiness.
855              
856 0 0         if( $new_pipes =~ s<^( +)><>s ) {
857 0           $init_space = $1;
858             } else {
859 0           $init_space = '';
860             }
861              
862 0 0         if( $new_pipes =~ s<( +)$><>s ) {
863 0           $end_space = $1
864             } else {
865 0           $end_space = '';
866             }
867              
868 0           $new_pipes =~ tr< ><->;
869 0           substr($new_pipes,0,1) = "/";
870 0           substr($new_pipes,-1,1) = "\\";
871              
872 0           $new_pipes = $init_space . $new_pipes . $end_space;
873             # substr($new_pipes, int((length($new_pipes)), 1)) / 2) = "^"; # feh
874             }
875              
876             # Now tack on the formatting for this node.
877 0 0 0       if($o->{'v_compact'} == 2) {
    0          
878 0 0         if(@daughters == 1) {
879 0           unshift @box, "|", $printable_name;
880             } else {
881 0           unshift @box, "|", $printable_name, $new_pipes;
882             }
883             } elsif ($o->{'v_compact'} == 1 and @daughters == 1) {
884 0           unshift @box, "|", $printable_name;
885             } else { # general case
886 0           unshift @box, "|", $printable_name, $new_pipes;
887             }
888             }
889              
890             # Flush the edges:
891 0           my $max_width = 0;
892 0           foreach my $line (@box) {
893 0           my $w = length($line);
894 0 0         $max_width = $w if $w > $max_width;
895             }
896 0           foreach my $one (@box) {
897 0           my $space_to_add = $max_width - length($one);
898 0 0         next unless $space_to_add;
899 0           my $add_left = int($space_to_add / 2);
900 0           my $add_right = $space_to_add - $add_left;
901 0           $one = (' ' x $add_left) . $one . (' ' x $add_right);
902             }
903              
904 0           return \@box; # must not return a null list!
905             }
906              
907             sub copy_at_and_under {
908 0     0 0   my($from, $o) = @_[0,1];
909 0 0         $o = {} unless ref $o;
910 0           my @daughters = map($_->copy_at_and_under($o), $from->daughters);
911 0           my $to = $from->copy($o);
912 0 0         $to->set_daughters(@daughters) if @daughters;
913 0           return $to;
914             }
915              
916             sub copy_tree {
917 0     0 0   my($this, $o) = @_[0,1];
918 0           my $root = $this->root;
919 0 0         $o = {} unless ref $o;
920            
921 0           my $new_root = $root->copy_at_and_under($o);
922            
923 0           return $new_root;
924             }
925              
926             sub copy {
927 0     0 0   my $self = shift;
928 0           my ($o) = @_;
929 0 0         $o = {} unless $o;
930              
931 0 0         my $class = blessed($self) ? blessed($self) : $self;
932              
933 0           my $tree = Tree->new();
934 0           $tree->error_handler( $tree->DIE );
935              
936 0           my $clone = bless \$tree, $class;
937              
938 0           $tree->meta->{compat}{object} = $clone;
939 0           weaken( $clone );
940              
941 0           $clone->_init_mother;
942 0           $clone->_init_daughters;
943              
944 0 0         if($o->{'no_attribute_copy'}) {
945 0           $clone->attributes( $self->attributes );
946             }
947             else {
948 0 0         if(my $attrib_copy = ref $self->attributes) {
949 0 0         if($attrib_copy eq 'HASH') {
    0          
950 0           $clone->attributes( { %{$self->attributes} } );
  0            
951             } elsif ($attrib_copy = UNIVERSAL::can($self->attributes, 'copy') ) {
952 0           $clone->attributes( &{$attrib_copy}($self) );
  0            
953             }
954             }
955             }
956              
957 0           $o->{'from_to'}{$self} = $clone;
958 0           return $clone;
959             }
960              
961             sub delete_tree {
962 0     0 0   my $it = $_[0];
963             $it->root->walk_down({ # has to be callbackback, not callback
964             'callbackback' => sub {
965 0     0     ${$_[0]}->parent->remove_child( ${$_[0]} );
  0            
  0            
966 0           bless($_[0], 'DEADNODE');
967 0           return 1;
968             }
969 0           });
970 0           return;
971             }
972              
973 0     0     sub DEADNODE::delete_tree { return; }
974              
975             ###########################################################################
976             # stolen from MIDI.pm
977              
978             sub _dump_quote {
979 0     0     my @stuff = @_;
980             return
981             join(", ",
982             map
983             { # the cleaner-upper function
984 0 0         if(!length($_)) { # empty string
  0 0          
    0          
985 0           "''";
986             } elsif( m/^-?\d+(?:\.\d+)?$/s ) { # a number
987 0           $_;
988             } elsif( # text with junk in it
989             s<([^\x20\x21\x23\x27-\x3F\x41-\x5B\x5D-\x7E])>
990 0           <'\\x'.(unpack("H2",$1))>eg
991             ) {
992 0           "\"$_\"";
993             } else { # text with no junk in it
994 0           s<'><\\'>g;
995 0           "\'$_\'";
996             }
997             }
998             @stuff
999             );
1000             }
1001              
1002             ###########################################################################
1003              
1004             1;
1005             __END__