File Coverage

blib/lib/LaTeX/TOM/Tree.pm
Criterion Covered Total %
statement 108 147 73.4
branch 33 56 58.9
condition 13 22 59.0
subroutine 15 21 71.4
pod 0 12 0.0
total 169 258 65.5


line stmt bran cond sub pod time code
1             ###############################################################################
2             #
3             # LaTeX::TOM::Tree
4             #
5             # This package defines a TOM Tree object.
6             #
7             ###############################################################################
8              
9             package LaTeX::TOM::Tree;
10              
11 10     10   88 use strict;
  10         24  
  10         337  
12              
13 10     10   59 use Carp qw(croak);
  10         16  
  10         20628  
14              
15             our $VERSION = '0.08';
16              
17             # "constructor"
18             #
19             sub _new {
20 186     186   304 my $class = shift;
21 186   50     362 my $nodes = shift || []; # empty array for tree structure
22              
23 186         411 my $self = {
24             nodes => $nodes,
25             };
26              
27 186         788 return bless $self;
28             }
29              
30             # make a copy of a tree, recursively
31             #
32             sub copy {
33 13     13 0 21 my $tree = shift; # input tree
34              
35 13         19 my @output; # output array (to become tree)
36              
37 13         14 foreach my $node (@{$tree->{nodes}}) {
  13         24  
38              
39             # make a copy of the node's hash definition
40             #
41 25         55 my $nodecopy = $node->copy();
42              
43             # grab a copy of children, if any exist
44             #
45 25 100       45 if ($node->{children}) {
46 6         13 my $children = $node->{children}->copy();
47 6         18 $nodecopy->{children} = $children;
48             }
49              
50             # add hashref to new node to array for this level
51 25         43 push @output, $nodecopy;
52             }
53              
54             # each subtree is a tree
55 13         39 return __PACKAGE__->_new([@output]);
56             }
57              
58             sub print {
59 0     0 0 0 shift->_debug_tree(@_, sub { print STDOUT $_[0] });
  0     0   0  
60             }
61             sub _warn {
62 0     0   0 shift->_debug_tree(@_, sub { print STDERR $_[0] });
  0     0   0  
63             }
64              
65             # Print out the LaTeX "TOM" tree. Good for debugging our parser.
66             #
67             sub _debug_tree {
68 0     0   0 my $tree = shift;
69 0         0 my $output_handler = pop;
70 0         0 my ($level) = @_;
71              
72 0   0     0 $level ||= 0;
73              
74 0         0 foreach my $node (@{$tree->{nodes}}) {
  0         0  
75 0         0 my $spacer = ' ' x ($level * 2);
76              
77 0         0 $output_handler->($spacer);
78              
79             # print grouping/command info
80 0 0       0 if ($node->{type} eq 'COMMAND') {
    0          
    0          
    0          
81             $output_handler->(sprintf
82             "($node->{type}) \\$node->{command} %s @ [$node->{start}, $node->{end}]",
83 0 0       0 $node->{opts} ? "[$node->{opts}]" : "\b",
84             );
85             }
86              
87             elsif ($node->{type} eq 'GROUP') {
88 0         0 $output_handler->("($node->{type}) [$node->{start}, $node->{end}]");
89             }
90              
91             elsif ($node->{type} eq 'ENVIRONMENT') {
92 0         0 $output_handler->("($node->{type}) $node->{class} @ inner [$node->{start}, $node->{end}] outer [$node->{ostart}, $node->{oend}]");
93             }
94              
95             elsif ($node->{type} =~ /^(?:TEXT|COMMENT)$/) {
96 0         0 my $space_out = do {
97 0         0 local $_ = "$spacer $node->{type} |";
98 0         0 s/[A-Z]/ /go;
99 0         0 $_;
100             };
101 0         0 my $max_len = 80 - length($space_out);
102 0         0 my $print_text = do {
103 0         0 local $_ = $node->{content};
104 0         0 s/^(.{0,$max_len}).*$/$1/gm;
105 0         0 s/\n/\n$space_out/gs;
106 0         0 $_;
107             };
108 0         0 $output_handler->("($node->{type}) |$print_text\"");
109             }
110              
111 0 0       0 $output_handler->(' ** math mode **') if $node->{math};
112 0 0       0 $output_handler->(' ** plaintext **') if $node->{plaintext};
113              
114 0         0 $output_handler->("\n");
115              
116             # recur
117 0 0       0 if (defined $node->{children}) {
118 0         0 my ($wrapper) = (caller(1))[3] =~ /.+::(.+)$/;
119 0         0 $node->{children}->$wrapper($level + 1);
120             }
121             }
122             }
123              
124             # pull out the plain text (non-math) TEXT nodes. returns an array of strings.
125             #
126             sub plainText {
127 14     14 0 25 my $tree = shift;
128              
129 14         33 my $stringlist = [];
130              
131 14         20 foreach my $node (@{$tree->{nodes}}) {
  14         25  
132              
133 38 100 100     96 if ($node->{type} eq 'TEXT' && $node->{plaintext}) {
134 8         18 push @$stringlist, $node->{content};
135             }
136              
137 38 100       63 if ($node->{children}) {
138 12         17 push @$stringlist, @{$node->{children}->plainText()};
  12         32  
139             }
140             }
141              
142 14         41 return $stringlist;
143             }
144              
145             # Get the plaintext of a LaTeX DOM and whittle it down into a word list
146             # suitable for indexing.
147             #
148             sub indexableText {
149 1     1 0 2 my $tree = shift;
150              
151 1         3 my $pt = $tree->plainText();
152 1         4 my $text = join (' ', @$pt);
153              
154             # kill leftover commands
155 1         10 $text =~ s/\\\w+\*?//gso;
156              
157             # kill nonpunctuation
158 1         4 $text =~ s/[^\w\-0-9\s]//gso;
159              
160             # kill non-intraword hyphens
161 1         2 $text =~ s/(\W)\-+(\W)/$1 $2/gso;
162 1         2 $text =~ s/(\w)\-+(\W)/$1 $2/gso;
163 1         2 $text =~ s/(\W)\-+(\w)/$1 $2/gso;
164              
165             # kill small words
166 1         4 $text =~ s/\b[^\s]{1,2}\b//gso;
167              
168             # kill purely numerical "words"
169 1         4 $text =~ s/\b[0-9]+\b//gso;
170              
171             # compress whitespace
172 1         4 $text =~ s/\s+/ /gso;
173              
174 1         6 return $text;
175             }
176              
177             # Convert tree to LaTeX. If our output doesn't compile to the same final
178             # document, something is amiss (we don't, however, guarantee that the output
179             # TeX will be identical to the input, due to certain normalizations.)
180             #
181             sub toLaTeX {
182 57     57 0 113 my $tree = shift;
183 57         69 my $parent = shift;
184              
185 57         87 my $str = "";
186              
187 57         76 foreach my $node (@{$tree->{nodes}}) {
  57         100  
188              
189 162 100 66     468 if ($node->{type} eq 'TEXT' ||
    100          
    100          
    50          
190             $node->{type} eq 'COMMENT') {
191              
192 118         204 $str .= $node->{content};
193             }
194              
195             elsif ($node->{type} eq 'GROUP') {
196 2         8 $str .= '{' . $node->{children}->toLaTeX($node) . '}';
197             }
198              
199             elsif ($node->{type} eq 'COMMAND') {
200 23         51 my $cmd = "\\$node->{command}";
201 23 100       67 $cmd .= "[$node->{opts}]" if (defined $node->{opts});
202              
203 23 100       69 if ($node->{position} eq 'outer') {
    50          
    0          
204 21         95 $str .= $cmd . '{' . $node->{children}->toLaTeX($node) . '}';
205             }
206             elsif ($node->{position} eq 'inner') {
207 2 50 33     13 if (defined $parent && # dont add superfluous braces
      33        
208             $parent->{start} == $node->{start} &&
209             $parent->{end} == $node->{end}) {
210 0         0 $str .= $cmd . ' ' . $node->{children}->toLaTeX($node);
211             } else {
212 2         7 $str .= '{' . $cmd . $node->{children}->toLaTeX($node) . '}';
213             }
214             }
215             elsif ($node->{braces} == 0) {
216 0         0 $str .= $cmd . ' ' . $node->{children}->toLaTeX($node);
217             }
218             }
219              
220             elsif ($node->{type} eq 'ENVIRONMENT') {
221             # handle special math mode envs
222 19         34 my $MATHBRACKETS = \%LaTeX::TOM::MATHBRACKETS;
223 19 50       48 if (defined $MATHBRACKETS->{$node->{class}}) {
224             # print with left and lookup right brace.
225 0         0 $str .= $node->{class} . $node->{children}->toLaTeX($node) . $MATHBRACKETS->{$node->{class}};
226             }
227              
228             # standard \begin/\end envs
229             else {
230 19         80 $str .= "\\begin{$node->{class}}" . $node->{children}->toLaTeX($node) . "\\end{$node->{class}}";
231             }
232             }
233             }
234              
235 57         362 return $str;
236             }
237              
238             # Augment the nodes in the tree with pointers to all neighboring nodes, so
239             # traversal is easier for the user who is given a lone node. This is a hack,
240             # we should really be maintaining this all along.
241             #
242             # Note that child pointers are already taken care of.
243             #
244             sub listify {
245 111     111 0 189 my $tree = shift;
246 111         153 my $parent = shift;
247              
248 111         156 for (my $i = 0; $i < scalar @{$tree->{nodes}}; $i++) {
  410         877  
249              
250 299         379 my $prev = undef;
251 299         353 my $next = undef;
252              
253 299 100       590 $prev = $tree->{nodes}[$i - 1] if ($i > 0);
254 299 100       417 $next = $tree->{nodes}[$i + 1] if ($i + 1 < scalar @{$tree->{nodes}});
  299         692  
255              
256 299         510 $tree->{nodes}[$i]->{'prev'} = $prev;
257 299         598 $tree->{nodes}[$i]->{'next'} = $next;
258 299         431 $tree->{nodes}[$i]->{'parent'} = $parent;
259              
260             # recur, with parent info
261 299 100       618 if ($tree->{nodes}[$i]->{children}) {
262 84         254 $tree->{nodes}[$i]->{children}->listify($tree->{nodes}[$i]);
263             }
264             }
265             }
266              
267             ###############################################################################
268             # "Tree walking" methods.
269             #
270              
271             sub getTopLevelNodes {
272 2     2 0 6 my $tree = shift;
273              
274 2         4 return @{$tree->{nodes}};
  2         18  
275             }
276              
277             sub getAllNodes {
278 14     14 0 64 my $tree = shift;
279              
280 14         19 my @nodelist;
281              
282 14         18 foreach my $node (@{$tree->{nodes}}) {
  14         34  
283              
284 38         58 push @nodelist, $node;
285              
286 38 100       74 if ($node->{children}) {
287 12         18 push @nodelist, @{$node->{children}->getAllNodes()};
  12         26  
288             }
289             }
290              
291 14         47 return [@nodelist];
292             }
293              
294             sub getNodesByCondition {
295 35     35 0 58 my $tree = shift;
296 35         43 my $condition = shift;
297              
298             # XXX rt #48551 - string eval no longer supported (12/08/2009)
299 35 50       90 unless (ref $condition eq 'CODE') {
300 0         0 croak 'getNodesByCondition(): code reference expected';
301             }
302              
303 35         43 my @nodelist;
304              
305 35         42 foreach my $node (@{$tree->{nodes}}) {
  35         70  
306              
307             # evaluate the perl code condition and if the result evaluates to true,
308             # push this node
309             #
310 93 100       141 if ($condition->($node)) {
311 8         13 push @nodelist, $node;
312             }
313              
314 93 100       238 if ($node->{children}) {
315 29         39 push @nodelist, @{$node->{children}->getNodesByCondition($condition)};
  29         74  
316             }
317             }
318              
319 35         92 return [@nodelist];
320             }
321              
322             sub getCommandNodesByName {
323 2     2 0 18 my $tree = shift;
324 2         5 my $name = shift;
325              
326             return $tree->getNodesByCondition(
327 29   100 29   41 sub { my $node = shift; return ($node->{type} eq 'COMMAND' && $node->{command} eq $name); }
  29         85  
328 2         28 );
329             }
330              
331             sub getEnvironmentsByName {
332 2     2 0 794 my $tree = shift;
333 2         4 my $name = shift;
334              
335             return $tree->getNodesByCondition(
336 29   66 29   38 sub { my $node = shift; return ($node->{type} eq 'ENVIRONMENT' && $node->{class} eq $name); }
  29         85  
337 2         15 );
338             }
339              
340             sub getFirstNode {
341 0     0 0   my $tree = shift;
342 0           return $tree->{nodes}[0];
343             }
344              
345             1;