File Coverage

blib/lib/LaTeX/Pod.pm
Criterion Covered Total %
statement 310 333 93.0
branch 74 94 78.7
condition 14 20 70.0
subroutine 71 81 87.6
pod 2 2 100.0
total 471 530 88.8


line stmt bran cond sub pod time code
1             package LaTeX::Pod;
2              
3 2     2   76834 use strict;
  2         10  
  2         57  
4 2     2   10 use warnings;
  2         4  
  2         64  
5 2     2   888 use boolean qw(true false);
  2         7168  
  2         8  
6 2     2   150 use constant do_exec => 1;
  2         4  
  2         219  
7 2     2   13 use constant no_exec => 0;
  2         4  
  2         93  
8              
9 2     2   11 use Carp qw(croak);
  2         4  
  2         80  
10 2     2   1033 use LaTeX::TOM ();
  2         32123  
  2         86  
11 2     2   1267 use List::MoreUtils qw(any);
  2         29085  
  2         12  
12 2     2   3270 use Params::Validate ':all';
  2         18944  
  2         10519  
13              
14             our ($VERSION, $DEBUG);
15              
16             $VERSION = '0.22';
17             $DEBUG = false;
18              
19             validation_options(
20             on_fail => sub
21             {
22             my ($error) = @_;
23             chomp $error;
24             croak $error;
25             },
26             stack_skip => 2,
27             );
28              
29             my $regex_list_type = join '|', qw(description enumerate itemize);
30             my @text_node_types = qw(text tag);
31              
32             sub new
33             {
34 1     1 1 102 my $class = shift;
35              
36 1   33     7 my $self = bless {}, ref($class) || $class;
37              
38 1         6 $self->_init_check(@_);
39 1         5 $self->_init(@_);
40              
41 1         4 return $self;
42             }
43              
44             sub convert
45             {
46 2     2 1 2252 my $self = shift;
47              
48 2         7 $self->_init_check($self->{file});
49 2         15 $self->_init_vars;
50              
51 2         6 my $nodes = $self->_init_tom;
52              
53 2     34   11 my $sort = sub { my $order = pop; sort { $order->{$a} <=> $order->{$b} } keys %{$_[0]} };
  34         45  
  34         44  
  748         1033  
  34         137  
54              
55 2         5 my ($command_keyword, @queue);
56              
57 2         8 foreach my $i (0 .. $#$nodes) {
58 206         580 my $node = $nodes->[$i];
59 206         372 my $type = $node->getNodeType;
60              
61 206 100       962 if ($type eq 'TEXT') {
    100          
    100          
    50          
62 136 100 100     228 next if $node->getNodeText !~ /\w+/
63             or $node->getNodeText =~ /^\\\w+$/m;
64              
65 88 50 66     963 if ($node->getNodeText =~ /\\item/) {
    100          
    100          
    100          
66 22     22   160 push @queue, [ sub { shift->_process_text_item(@_) }, $node ];
  22         47  
67             }
68             elsif (!defined $command_keyword && ($i >= 1
69             ? !$self->_is_environment_type($node->getParent, 'verbatim') : true)
70             ) {
71 28     28   450 push @queue, [ sub { shift->_process_text(@_) }, $node ];
  28         94  
72             }
73             elsif (defined $command_keyword) {
74 34         237 push @queue, [ $self->{dispatch_text}->{$command_keyword}, $node ];
75 34         64 undef $command_keyword;
76             }
77             }
78             elsif ($type eq 'COMMENT') {
79 6     6   51 push @queue, [ sub { shift->_process_comment(@_) }, $node ];
  6         17  
80             }
81             elsif ($type eq 'ENVIRONMENT') {
82 30         54 my $class = $node->getEnvironmentClass;
83 30 100       270 if ($class eq 'abstract') {
    100          
    100          
84 2         5 next;
85             }
86             elsif ($class =~ /^($regex_list_type)$/) {
87 22     22   130 push @queue, [ sub { shift->_process_start_item(@_) }, $node, $1 ];
  22         55  
88             }
89             elsif ($class eq 'verbatim') {
90 4     4   25 push @queue, [ sub { shift->_process_text_verbatim(@_) }, $node->getFirstChild ];
  4         11  
91             }
92             }
93             elsif ($type eq 'COMMAND') {
94 34         65 my $cmd_name = $node->getCommandName;
95 34         197 foreach my $keyword ($sort->(map $self->{$_}, qw(command_checks command_order))) {
96 168 100       283 if ($self->{command_checks}->{$keyword}->($cmd_name)) {
97 34         51 my ($code, $exec) = @{$self->{dispatch_command}->{$keyword}};
  34         73  
98 34 50 66     95 if (defined $exec && $exec) {
99 0         0 $code->($self);
100             }
101             else {
102 34 100       84 push @queue, [ $code, $node ] if defined $exec;
103 34         52 $command_keyword = $keyword;
104             }
105 34         97 last;
106             }
107             }
108             }
109             }
110              
111 2         15 foreach my $dispatch (@queue) {
112 136         758 my ($code, $node, @args) = @$dispatch;
113 136         253 $code->($self, $node, @args);
114             }
115 2         32 $self->_process_end;
116              
117 2         24 return $self->_pod_finalize;
118             }
119              
120             sub _init_check
121             {
122 3     3   6 my $self = shift;
123              
124 3         65 validate_pos(@_, { type => SCALAR });
125              
126 3         12 my ($file) = @_;
127             my $error = sub
128             {
129 3 50   3   65 return 'does not exist' unless -e shift;
130 3 50       11 return 'is not a file' unless -f _;
131 3 50       8 return 'is empty' unless -s _;
132 3         7 return undef;
133              
134 3         13 }->($file);
135              
136 3 50       29 defined $error and croak "Cannot open `$file': $error";
137             }
138              
139             sub _init
140             {
141 1     1   2 my $self = shift;
142 1         2 my ($file) = @_;
143              
144 1         6 $self->{file} = $file;
145              
146 1         2 my $i = 0;
147 1         3 %{$self->{command_order}} = map { $_ => $i++ } qw(directive title author chapter section subsection textbf textsf emph);
  1         7  
  9         16  
148              
149 1         8 %{$self->{command_checks}} = (
150 34     34   144 directive => sub { $_[0] =~ /^(?:documentclass|usepackage|pagestyle)$/ },
151 30     30   70 title => sub { $_[0] eq 'title' },
152 28     28   60 author => sub { $_[0] eq 'author' },
153 26     26   62 chapter => sub { $_[0] eq 'chapter' },
154 22     22   43 section => sub { $_[0] eq 'section' },
155 16     16   75 subsection => sub { $_[0] =~ /^(?:sub){1,2}section$/ },
156 6     6   16 textbf => sub { $_[0] eq 'textbf' },
157 4     4   10 textsf => sub { $_[0] eq 'textsf' },
158 2     2   5 emph => sub { $_[0] eq 'emph' },
159 1         25 );
160 1         5 %{$self->{dispatch_command}} = (
161       0     directive => [ sub {}, undef ],
162       0     title => [ sub {}, undef ],
163       0     author => [ sub {}, undef ],
164 4     4   12 chapter => [ sub { shift->_process_chapter(@_) }, no_exec ],
165 6     6   26 section => [ sub { shift->_process_section(@_) }, no_exec ],
166 10     10   22 subsection => [ sub { shift->_process_subsection(@_) }, no_exec ],
167       0     textbf => [ sub {}, undef ],
168       0     textsf => [ sub {}, undef ],
169 1     0   17 emph => [ sub {}, undef ],
170             );
171 1         6 %{$self->{dispatch_text}} = (
172 4     4   11 directive => sub { shift->_process_directive(shift, 'directive') },
173 2     2   6 title => sub { shift->_process_directive(shift, 'title') },
174 2     2   5 author => sub { shift->_process_directive(shift, 'author') },
175 4     4   14 chapter => sub { shift->_process_text_title(@_) },
176 6     6   35 section => sub { shift->_process_text_title(@_) },
177 10     10   22 subsection => sub { shift->_process_text_title(@_) },
178 2     2   10 textbf => sub { shift->_process_tag(shift, 'textbf') },
179 2     2   9 textsf => sub { shift->_process_tag(shift, 'textsf') },
180 2     2   13 emph => sub { shift->_process_tag(shift, 'emph') },
181 1         17 );
182             }
183              
184             sub _init_vars
185             {
186 2     2   4 my $self = shift;
187              
188 2         9 delete @$self{qw(list node previous)};
189              
190 2         10 $self->{pod} = [];
191              
192 2         4 %{$self->{title_inc}} = (
  2         8  
193             title => 1,
194             chapter => 1,
195             );
196             }
197              
198             sub _init_tom
199             {
200 2     2   3 my $self = shift;
201              
202 2         15 my $parser = LaTeX::TOM->new(2); # silently discard warnings about unparseable LaTeX
203 2         172 my $document = $parser->parseFile($self->{file});
204 2         77621 my $nodes = $document->getAllNodes;
205              
206 2         1058 return $nodes;
207             }
208              
209             sub _process_directive
210             {
211 8     8   11 my $self = shift;
212 8         13 my ($node, $directive) = @_;
213              
214 8 100   12   63 return if any { $directive eq $_ } qw(directive author);
  12         39  
215              
216 2 50       18 if ($directive eq 'title') {
217 2         20 $self->_pod_add('=head' . "$self->{title_inc}{title} " . $node->getNodeText);
218             }
219             }
220              
221             sub _process_comment
222             {
223 6     6   8 my $self = shift;
224 6         10 my ($node) = @_;
225              
226 6         15 $self->_process_end_item($node);
227              
228 6         20 $self->_unregister_previous(@text_node_types);
229              
230 6         15 my $text = $node->getNodeText;
231              
232 6         40 $self->_scrub_newlines(\$text);
233              
234 6         30 $text =~ s/^ \s*? \% \s*? (?=\S)//x;
235              
236 6         22 $self->_pod_add("=for comment $text");
237             }
238              
239             sub _process_text_title
240             {
241 20     20   25 my $self = shift;
242 20         43 my ($node) = @_;
243              
244 20         43 my $text = $node->getNodeText;
245              
246 20         108 $self->_process_spec_chars(\$text);
247              
248 20         46 $self->_pod_append($text);
249             }
250              
251             sub _process_text_verbatim
252             {
253 4     4   6 my $self = shift;
254 4         6 my ($node) = @_;
255              
256 4         9 $self->_process_end_item($node);
257              
258 4         12 $self->_unregister_previous(@text_node_types);
259              
260 4         11 my $text = $node->getNodeText;
261              
262 4         24 $self->_scrub_newlines(\$text);
263 4         10 $self->_process_spec_chars(\$text);
264 4         23 $self->_prepend_spaces(\$text);
265              
266 4         11 $self->_pod_add($text);
267             }
268              
269             sub _process_start_item
270             {
271 22     22   28 my $self = shift;
272 22         39 my ($node, $type) = @_;
273              
274 22         46 $self->_process_end_item($node);
275              
276 22         142 $self->_unregister_previous(@text_node_types);
277              
278 22         43 my $nested = $self->_list_nestedness($node);
279              
280 22 100       50 if ($nested) {
281 8         20 $self->_pod_add('=back');
282             }
283              
284 22         80 %{$self->{list}{$nested}} = (
  22         89  
285             type => $type,
286             enum => 1,
287             );
288              
289 22         81 $self->_pod_add('=over ' . (4 + $nested));
290              
291 22         198 $self->_register_previous('list');
292             }
293              
294             sub _process_text_item
295             {
296 22     22   35 my $self = shift;
297 22         36 my ($node) = @_;
298              
299 22         48 $self->_unregister_previous(@text_node_types);
300              
301 22         43 my $nested = $self->_list_nestedness($node) - 1;
302              
303 22 100       46 if ($self->_is_environment_type($node->getPreviousSibling, qr/^(?:$regex_list_type)$/)) {
304 4         54 $self->_pod_add('=back');
305 4         35 $self->_pod_add('=over ' . (4 + $nested));
306             }
307              
308 22         73 my $text = $node->getNodeText;
309              
310 22         107 my $type = $self->{list}{$nested}{type};
311 22         42 my $enum = \$self->{list}{$nested}{enum};
312              
313             LOOP: {
314 22         31 local ($1, $2);
  56         140  
315 56 100       375 if ($text =~ /\G \s*? \\item (?:\s*?\[(.+?)\]\s+?|\s+?) (\S.+)?$/cgmx) {
    100          
316 32         49 my $pod = '=item ';
317 32 100       97 if ($type eq 'description') {
    100          
    50          
318 2 50       14 $pod .= defined $1 ? "B<$1> " : '';
319             }
320             elsif ($type eq 'enumerate') {
321 14 100       49 $pod .= defined $1 ? "$1 " : ($$enum++ . '. ');
322             }
323             elsif ($type eq 'itemize') {
324 16 100       54 $pod .= defined $1 ? "$1 " : '* ';
325             }
326 32 100       78 $pod .= defined $2 ? $2 : '';
327 32         108 $self->_process_spec_chars(\$pod);
328 32         80 $self->_pod_add($pod);
329 32         277 redo;
330             }
331             elsif ($text =~ /\G \s*? (\S.+?) \s*? (?:(?=\\item)|\z)/gsx) {
332 2         7 my $pod = $1;
333 2         5 $self->_process_spec_chars(\$pod);
334 2         7 $self->_pod_add($pod);
335 2         18 redo;
336             }
337             }
338             }
339              
340             sub _process_end_item
341             {
342 86     86   114 my $self = shift;
343 86         116 my ($node) = @_;
344              
345 86 100       137 return unless $self->_is_set_previous('list');
346              
347 24         64 my $parent = $node->getParent;
348              
349 24 100       121 $parent = $parent->getParent if $self->_is_environment_type($parent, 'verbatim');
350              
351 24 100       369 if ($self->_is_environment_type($parent, 'document')) {
352 12         187 $self->_pod_add('=back');
353 12         96 $self->_unregister_previous('list', @text_node_types);
354             }
355             }
356              
357             sub _process_text
358             {
359 28     28   43 my $self = shift;
360 28         41 my ($node) = @_;
361              
362 28         64 $self->_process_end_item($node);
363              
364 28 100       97 if ($self->_is_environment_type($node->getPreviousSibling, 'abstract')) {
365 2         36 $self->_unregister_previous(@text_node_types);
366             }
367              
368 28         253 my $text = $node->getNodeText;
369              
370 28         156 $self->_scrub_newlines(\$text);
371 28         80 $self->_process_spec_chars(\$text);
372              
373 28         71 $self->_text_setter($text);
374              
375 28         223 $self->_register_previous('text');
376             }
377              
378             sub _process_chapter
379             {
380 4     4   6 my $self = shift;
381 4         5 my ($node) = @_;
382              
383 4         12 $self->_process_end_item($node);
384              
385 4         21 $self->_unregister_previous(@text_node_types);
386              
387 4   66     18 $self->{title_inc}{section} ||= $self->{title_inc}{chapter} + 1;
388              
389 4         18 $self->_pod_add('=head' . $self->{title_inc}{chapter} . ' ');
390             }
391              
392             sub _process_section
393             {
394 6     6   8 my $self = shift;
395 6         10 my ($node) = @_;
396              
397 6         15 $self->_process_end_item($node);
398              
399 6         19 $self->_unregister_previous(@text_node_types);
400              
401 6   50     33 $self->{title_inc}{section} ||= 1;
402              
403 6         28 $self->_pod_add('=head' . "$self->{title_inc}{section} ");
404             }
405              
406             sub _process_subsection
407             {
408 10     10   13 my $self = shift;
409 10         15 my ($node) = @_;
410              
411 10         21 $self->_process_end_item($node);
412              
413 10         33 $self->_unregister_previous(@text_node_types);
414              
415 10         25 my $cmd_name = $node->getCommandName;
416              
417 10         52 my $nested = 0;
418 10         61 $nested++ while $cmd_name =~ /\Gsub/g;
419              
420 10         41 $self->_pod_add('=head' . ($self->{title_inc}{section} + $nested) . ' ');
421             }
422              
423             sub _process_spec_chars
424             {
425 86     86   126 my $self = shift;
426 86         126 my ($text) = @_;
427              
428 86         290 my %umlauts = (a => 'ä',
429             A => 'Ä',
430             u => 'ü',
431             U => 'Ü',
432             o => 'ö',
433             O => 'Ö');
434              
435 86         242 while (my ($from, $to) = each %umlauts) {
436 516         4190 $$text =~ s/\\\"$from/$to/g;
437             }
438              
439 86         184 foreach my $escape ('#', qw($ % & _ { })) {
440 602         3666 $$text =~ s/\\\Q$escape\E/$escape/g;
441             }
442              
443 86         174 $$text =~ s/\\ldots/.../g;
444              
445 86         141 $$text =~ s/\\verb\*?(.)(.+?)\1/C<$2>/g;
446 86         260 $$text =~ s/(?:\\\\|\\newline)/\n/g;
447             }
448              
449             sub _process_tag
450             {
451 6     6   15 my $self = shift;
452 6         10 my ($node, $tag) = @_;
453              
454 6         22 $self->_process_end_item($node);
455              
456 6 50       34 if ($self->_is_environment_type($node->getPreviousSibling, 'abstract')) {
457 0         0 $self->_unregister_previous(@text_node_types);
458             }
459              
460 6         13 my $text = $node->getNodeText;
461              
462 6         39 my %tags = (textbf => 'B',
463             textsf => 'C',
464             emph => 'I');
465              
466 6         23 $self->_text_setter("$tags{$tag}<$text>");
467              
468 6         50 $self->_register_previous('tag');
469             }
470              
471             sub _process_end
472             {
473 2     2   4 my $self = shift;
474              
475 2 50       6 if ($self->_is_set_previous('list')) {
476 2         10 $self->_pod_add('=back');
477 2         27 $self->_unregister_previous('list');
478             }
479             }
480              
481             sub _is_environment_type
482             {
483 220     220   1289 my $self = shift;
484 220         378 my ($node, $type) = @_;
485              
486 220 50       1819 $type = qr/^$type$/ unless ref $type eq 'REGEXP';
487              
488 220   100     700 return ($node
489             && $node->getNodeType eq 'ENVIRONMENT'
490             && $node->getEnvironmentClass =~ $type);
491             }
492              
493             sub _list_nestedness
494             {
495 44     44   66 my $self = shift;
496 44         64 my ($node) = @_;
497              
498 44         54 my $nested = 0;
499              
500 44         89 for (my $parent = $node->getParent;
501             $self->_is_environment_type($parent, qr/^(?:$regex_list_type)$/);
502             $parent = $parent->getParent
503             ) {
504 40         572 $nested++;
505             }
506              
507 44         646 return $nested;
508             }
509              
510             sub _prepend_spaces
511             {
512 4     4   7 my $self = shift;
513 4         7 my ($text) = @_;
514              
515 4 50       11 unless (length $$text) {
516 0         0 $$text =~ s/^/ /;
517 0         0 return;
518             }
519              
520 4         33 $$text =~ s/^/ /gm;
521             }
522              
523             sub _text_setter
524             {
525 34     34   51 my $self = shift;
526 34         55 my ($text) = @_;
527              
528 34     88   164 my $append = any { $self->_is_set_previous($_) } ('list', @text_node_types);
  88         169  
529 34 100       106 my $setter = $append ? '_pod_append' : '_pod_add';
530              
531 34         95 $self->$setter($text);
532             }
533              
534             sub _pod_add
535             {
536 144     144   202 my $self = shift;
537 144         237 my ($pod) = @_;
538              
539 144 100       167 if (@{$self->{pod}}) {
  144         310  
540 142         515 $self->{pod}->[-1] =~ s/[\ \t]+$//gm;
541             }
542              
543 144         196 push @{$self->{pod}}, $pod;
  144         303  
544              
545 144 50       332 $self->_debug($pod) if $DEBUG;
546             }
547              
548             sub _pod_append
549             {
550 30     30   55 my $self = shift;
551 30         53 my ($pod) = @_;
552              
553 30         63 $self->{pod}->[-1] .= $pod;
554              
555 30 50       67 $self->_debug($pod) if $DEBUG;
556             }
557              
558             sub _debug
559             {
560 0     0   0 my $self = shift;
561 0         0 my ($pod) = @_;
562              
563 0         0 my $re = qr/^.+::(.+)$/;
564              
565 0 0       0 my $frame = (caller(2))[3] =~ /^.+::_text_setter$/ ? 1 : 0;
566              
567 0         0 my ($sub) = (caller(2 + $frame))[3] =~ $re;
568 0         0 my $line = (caller(1 + $frame))[2];
569 0         0 my ($setter) = (caller(1 + $frame))[3] =~ $re;
570              
571 0         0 my $index = @{$self->{pod}} - 1;
  0         0  
572              
573 0         0 printf STDERR ("%-12s(%-25s:%03d):[%3d]\t'%s'\n", $setter, $sub, $line, $index, $pod);
574             }
575              
576             sub _scrub_newlines
577             {
578 38     38   48 my $self = shift;
579 38         52 my ($text) = @_;
580              
581 38         149 $$text =~ s/^\n+//;
582 38         145 $$text =~ s/\n+$//;
583             }
584              
585             sub _pod_get
586             {
587 3     3   111 my $self = shift;
588              
589 3         155 return $self->{pod};
590             }
591              
592             sub _pod_finalize
593             {
594 2     2   4 my $self = shift;
595              
596 2         6 $self->_pod_add("=cut\n");
597              
598 2         20 return join "\n\n", @{$self->_pod_get};
  2         8  
599             }
600              
601             sub _register_node
602             {
603 0     0   0 my $self = shift;
604 0         0 my ($item) = @_;
605              
606 0         0 $self->{node}{$item} = true;
607             }
608              
609             sub _is_set_node
610             {
611 0     0   0 my $self = shift;
612 0         0 my ($item) = @_;
613              
614 0 0       0 return $self->{node}{$item} ? true : false;
615             }
616              
617             sub _unregister_node
618             {
619 0     0   0 my $self = shift;
620 0         0 my ($item) = @_;
621              
622 0         0 delete $self->{node}{$item};
623             }
624              
625             sub _register_previous
626             {
627 56     56   79 my $self = shift;
628 56         96 my ($item) = @_;
629              
630 56         93 $self->{previous}{$item} = true;
631             }
632              
633             sub _is_set_previous
634             {
635 176     176   222 my $self = shift;
636 176         364 my @items = @_;
637              
638 176         320 my $ok = eval true; # eval in order to avoid fatal errors on some older perls
639              
640 176         583 foreach my $item (@items) {
641 176 100       516 $ok &= $self->{previous}{$item} ? true : false;
642             }
643              
644 176         1525 return $ok;
645             }
646              
647             sub _unregister_previous
648             {
649 90     90   114 my $self = shift;
650 90         168 my @items = @_;
651              
652 90         144 foreach my $item (@items) {
653 190         340 delete $self->{previous}{$item};
654             }
655             }
656              
657             =head1 NAME
658              
659             LaTeX::Pod - Transform LaTeX source files to POD (Plain old documentation)
660              
661             =head1 SYNOPSIS
662              
663             use LaTeX::Pod;
664              
665             $parser = LaTeX::Pod->new('/path/to/source');
666             print $parser->convert;
667              
668             =head1 DESCRIPTION
669              
670             C converts LaTeX sources to Perl's POD (Plain old documentation).
671             Currently only a subset of the available LaTeX language is supported;
672             see L for further details.
673              
674             =head1 CONSTRUCTOR
675              
676             =head2 new
677              
678             The constructor requires that the path to the LaTeX source is defined:
679              
680             $parser = LaTeX::Pod->new('/path/to/source');
681              
682             Returns the parser object.
683              
684             =head1 METHODS
685              
686             =head2 convert
687              
688             There is one public I available, namely C:
689              
690             $pod = $parser->convert;
691              
692             Returns the computed POD as a string.
693              
694             =head1 SUPPORTED LANGUAGE SUBSET
695              
696             LaTeX currently supported:
697              
698             =over 4
699              
700             =item * abstracts
701              
702             =item * chapters
703              
704             =item * sections/subsections/subsubsections
705              
706             =item * description, enumerate and itemize lists
707              
708             =item * verbatim blocks (and indentation)
709              
710             =item * plain text
711              
712             =item * bold/italic/code font tags
713              
714             =item * umlauts
715              
716             =item * newlines
717              
718             =item * comments
719              
720             =back
721              
722             =head1 IMPLEMENTATION DETAILS
723              
724             The current implementation is based upon L (the framework being
725             used for parsing the LaTeX source) and its clear distinction between various
726             types of nodes. As an example, a C<\chapter> command has a separate text
727             associated with it as its content. C uses a "look-behind" mechanism
728             for commands and their corresponding texts since they currently cannot be easily
729             detected without such a mechanism.
730              
731             Thus C was designed with the intention to be I
732             aware. This is also being aimed at by eventually registering which type of node
733             has been seen before the current one -- useful when constructing logical paragraphs
734             made out of two or more nodes. C then finally unregisters the type
735             of node seen when it is no longer required. In addition, a dispatch queue is built
736             internally which is executed after all nodes have been processed.
737              
738             Considering that the POD format has a limited subset of directives, the complexity
739             of keeping track of node occurences appears to be bearable. Leading and trailing
740             newlines will be removed from the node's text extracted where needed; furthermore,
741             trailing spaces and tabs will also be purged from each line of POD resulting.
742              
743             =head1 BUGS & CAVEATS
744              
745             It is highly recommended to ensure that the structure of the LaTeX input file
746             follows the format specification strictly or the parser may B succeed.
747              
748             =head1 SEE ALSO
749              
750             L
751              
752             =head1 AUTHOR
753              
754             Steven Schubiger
755              
756             =head1 LICENSE
757              
758             This program is free software; you may redistribute it and/or
759             modify it under the same terms as Perl itself.
760              
761             See L
762              
763             =cut