File Coverage

blib/lib/Pod/Abstract/Tree.pm
Criterion Covered Total %
statement 32 95 33.6
branch 4 20 20.0
condition 0 3 0.0
subroutine 5 10 50.0
pod 8 9 88.8
total 49 137 35.7


line stmt bran cond sub pod time code
1             package Pod::Abstract::Tree;
2 3     3   15 use strict;
  3         6  
  3         3792  
3              
4             our $VERSION = '0.20';
5              
6             =head1 NAME
7              
8             Pod::Abstract::Tree - Manage a level of Pod document tree Nodes.
9              
10             =head1 DESCRIPTION
11              
12             Pod::Abstract::Tree keeps track of a set of Pod::Abstract::Node
13             elements, and allows manipulation of that list of elements. Elements
14             are stored in an ordered set - a single node can appear once only in a
15             single document tree, so inserting a node at a point will also remove
16             it from it's previous location.
17              
18             This is an internal class to Pod::Abstract::Node, and should not
19             generally be used externally.
20              
21             =head1 METHODS
22              
23             =cut
24              
25             sub new {
26 43     43 0 53 my $class = shift;
27              
28 43         283 return bless {
29             id_map => { },
30             nodes => [ ],
31             }, $class;
32             }
33              
34             =head2 detach
35              
36             $tree->detach($node);
37              
38             Unparent the C<$node> from C<$tree>. All other elements will be
39             shifted to fill the empty spot.
40              
41             =cut
42              
43             sub detach {
44 2     2 1 3 my $self = shift;
45 2         3 my $node = shift;
46 2         2 my $id_map = $self->{id_map};
47 2         5 my $serial = $node->serial;
48            
49 2         6 my $idx = $id_map->{$node->serial};
50 2 100       9 return 0 unless defined $idx;
51 1 50       4 die "Wrong node ($idx/$serial)! Got: ", $self->{nodes}[$idx]->serial
52             unless $self->{nodes}[$idx]->serial == $serial;
53            
54             # Node is defined, remove it:
55 1         2 splice @{$self->{nodes}},$idx,1;
  1         4  
56 1         2 delete $id_map->{$serial};
57            
58             # Move all following nodes back by 1
59 1         2 my $length = scalar @{$self->{nodes}};
  1         2  
60 1         3 for(my $i = $idx; $i < $length; $i ++) {
61 0         0 my $s = $self->{nodes}[$i]->serial;
62 0         0 $id_map->{$s} --;
63             }
64            
65             # Node now has no parent.
66 1         3 $node->parent(undef);
67 1         2 return $node;
68             }
69              
70             =head2 push
71              
72             Add an element to the end of the node list.
73              
74             =cut
75              
76             sub push {
77 27     27 1 28 my $self = shift;
78 27         31 my $node = shift;
79            
80 27 50       63 if($node->attached) {
81 0         0 $node->detach;
82 0         0 warn "Implicit detach of node on push";
83             }
84            
85 27         74 my $s = $node->serial;
86 27         33 push @{$self->{nodes}}, $node;
  27         62  
87 27         30 $self->{id_map}{$s} = $#{$self->{nodes}};
  27         93  
88 27         75 return 1;
89             }
90              
91             =head2 pop
92              
93             Remove an element from the end of the node list.
94              
95             =cut
96              
97             sub pop {
98 0     0 1 0 my $self = shift;
99            
100 0         0 my $node = pop @{$self->{nodes}};
  0         0  
101 0         0 my $s = $node->serial;
102 0         0 delete $self->{id_map}{$s};
103 0         0 $node->parent(undef);
104              
105 0         0 return $node;
106             }
107              
108             =head2 insert_before
109              
110             $tree->insert_before($target,$node);
111              
112             Insert C<$node> before C<$target>. Both must be children of C<$tree>
113              
114             =cut
115              
116             sub insert_before {
117 0     0 1 0 my $self = shift;
118 0         0 my $target = shift;
119 0         0 my $node = shift;
120            
121 0         0 my $idx = $self->{id_map}{$target->serial};
122 0 0       0 return 0 unless defined $idx;
123            
124 0         0 splice(@{$self->{nodes}}, $idx, 0, $node);
  0         0  
125 0         0 $self->{id_map}{$node->serial} = $idx;
126              
127             # Push all following nodes forwards by 1.
128 0         0 my $length = scalar @{$self->{nodes}};
  0         0  
129 0         0 for( my $i = $idx + 1; $i < $length; $i ++) {
130 0         0 my $s = $self->{nodes}[$i]->serial;
131 0         0 $self->{id_map}{$s} ++;
132             }
133 0         0 return 1;
134             }
135              
136             =head2 insert_after
137              
138             $tree->insert_after($target,$node);
139              
140             Insert C<$node> after C<$target>. Both must be children of C<$tree>
141              
142             =cut
143              
144             sub insert_after {
145 0     0 1 0 my $self = shift;
146 0         0 my $target = shift;
147 0         0 my $node = shift;
148            
149 0         0 my $idx = $self->{id_map}{$target->serial};
150 0 0       0 die $target->serial, " not in index ", join(", ", keys %{$self->{id_map}})
  0         0  
151             unless defined $idx;
152 0         0 my $last_idx = $#{$self->{nodes}};
  0         0  
153 0 0       0 if($idx == $last_idx) {
154 0         0 return $self->push($node);
155             } else {
156 0         0 my $before_target = $self->{nodes}[$idx + 1];
157 0         0 return $self->insert_before($before_target, $node);
158             }
159             }
160              
161             =head2 unshift
162              
163             Remove the first node from the node list and return it.
164              
165             Unshift takes linear time - it has to relocate every other element in
166             id_map so that they stay in line.
167              
168             =cut
169              
170             sub unshift {
171 0     0 1 0 my $self = shift;
172 0         0 my $node = shift;
173              
174 0 0       0 if($node->attached) {
175 0         0 $node->detach;
176 0         0 warn "Implicit detach of node on unshift";
177             }
178            
179 0         0 my $s = $node->serial;
180 0         0 foreach my $k (keys %{$self->{id_map}}) {
  0         0  
181 0         0 $self->{id_map}{$k} ++;
182             }
183 0         0 unshift @{$self->{nodes}}, $node;
  0         0  
184 0         0 $self->{id_map}{$s} = 0;
185 0         0 return 1;
186             }
187              
188             =head2 children
189              
190             Returns the in-order node list.
191              
192             =cut
193              
194             sub children {
195 104     104 1 120 my $self = shift;
196 104         134 return @{$self->{nodes}};
  104         351  
197             }
198              
199             =head2 index_relative
200              
201             my $node = $tree->index_relative($target, $offset);
202              
203             This method will return a node at an offset of $offset (which may be
204             negative) from this tree structure. If there is no such node, undef
205             will be returned. For example, an offset of 1 will give the following
206             element of $node.
207              
208             =cut
209              
210             sub index_relative {
211 0     0 1   my $self = shift;
212 0           my $node = shift;
213 0           my $index = shift;
214 0           my $serial = $node->serial;
215            
216 0 0         die "index_relative called with unattached node"
217             unless $node->attached;
218 0           my $node_idx = $self->{id_map}{$serial};
219 0 0         die "index_relative called with node not present in tree"
220             unless defined $node_idx;
221 0           my $real_index = $node_idx + $index;
222 0           my $n_nodes = scalar @{$self->{nodes}};
  0            
223 0 0 0       if($real_index >= 0 && $real_index < $n_nodes) {
224 0           return $self->{nodes}[$real_index];
225             } else {
226 0           return undef;
227             }
228             }
229              
230             =head1 AUTHOR
231              
232             Ben Lilburne
233              
234             =head1 COPYRIGHT AND LICENSE
235              
236             Copyright (C) 2009 Ben Lilburne
237              
238             This program is free software; you can redistribute it and/or modify
239             it under the same terms as Perl itself.
240              
241             =cut
242              
243             1;