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   68008 use strict;
  2         10  
  2         51  
4 2     2   8 use warnings;
  2         4  
  2         58  
5 2     2   744 use boolean qw(true false);
  2         5932  
  2         9  
6 2     2   143 use constant do_exec => 1;
  2         4  
  2         176  
7 2     2   11 use constant no_exec => 0;
  2         4  
  2         76  
8              
9 2     2   10 use Carp qw(croak);
  2         3  
  2         73  
10 2     2   1161 use LaTeX::TOM ();
  2         26535  
  2         60  
11 2     2   1117 use List::MoreUtils qw(any);
  2         23873  
  2         12  
12 2     2   2737 use Params::Validate ':all';
  2         15789  
  2         8321  
13              
14             our ($VERSION, $DEBUG);
15              
16             $VERSION = '0.21_01';
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 93 my $class = shift;
35              
36 1   33     6 my $self = bless {}, ref($class) || $class;
37              
38 1         6 $self->_init_check(@_);
39 1         4 $self->_init(@_);
40              
41 1         4 return $self;
42             }
43              
44             sub convert
45             {
46 2     2 1 1600 my $self = shift;
47              
48 2         7 $self->_init_check($self->{file});
49 2         8 $self->_init_vars;
50              
51 2         5 my $nodes = $self->_init_tom;
52              
53 2     34   12 my $sort = sub { my $order = pop; sort { $order->{$a} <=> $order->{$b} } keys %{$_[0]} };
  34         47  
  34         35  
  714         820  
  34         112  
54              
55 2         5 my ($command_keyword, @queue);
56              
57 2         9 foreach my $i (0 .. $#$nodes) {
58 206         487 my $node = $nodes->[$i];
59 206         378 my $type = $node->getNodeType;
60              
61 206 100       783 if ($type eq 'TEXT') {
    100          
    100          
    50          
62 136 100 100     176 next if $node->getNodeText !~ /\w+/
63             or $node->getNodeText =~ /^\\\w+$/m;
64              
65 88 50 66     813 if ($node->getNodeText =~ /\\item/) {
    100          
    100          
    100          
66 22     22   136 push @queue, [ sub { shift->_process_text_item(@_) }, $node ];
  22         39  
67             }
68             elsif (!defined $command_keyword && ($i >= 1
69             ? !$self->_is_environment_type($node->getParent, 'verbatim') : true)
70             ) {
71 28     28   390 push @queue, [ sub { shift->_process_text(@_) }, $node ];
  28         80  
72             }
73             elsif (defined $command_keyword) {
74 34         201 push @queue, [ $self->{dispatch_text}->{$command_keyword}, $node ];
75 34         57 undef $command_keyword;
76             }
77             }
78             elsif ($type eq 'COMMENT') {
79 6     6   65 push @queue, [ sub { shift->_process_comment(@_) }, $node ];
  6         14  
80             }
81             elsif ($type eq 'ENVIRONMENT') {
82 30         45 my $class = $node->getEnvironmentClass;
83 30 100       255 if ($class eq 'abstract') {
    100          
    100          
84 2         5 next;
85             }
86             elsif ($class =~ /^($regex_list_type)$/) {
87 22     22   104 push @queue, [ sub { shift->_process_start_item(@_) }, $node, $1 ];
  22         43  
88             }
89             elsif ($class eq 'verbatim') {
90 4     4   18 push @queue, [ sub { shift->_process_text_verbatim(@_) }, $node->getFirstChild ];
  4         9  
91             }
92             }
93             elsif ($type eq 'COMMAND') {
94 34         52 my $cmd_name = $node->getCommandName;
95 34         186 foreach my $keyword ($sort->(map $self->{$_}, qw(command_checks command_order))) {
96 168 100       229 if ($self->{command_checks}->{$keyword}->($cmd_name)) {
97 34         39 my ($code, $exec) = @{$self->{dispatch_command}->{$keyword}};
  34         59  
98 34 50 66     83 if (defined $exec && $exec) {
99 0         0 $code->($self);
100             }
101             else {
102 34 100       74 push @queue, [ $code, $node ] if defined $exec;
103 34         43 $command_keyword = $keyword;
104             }
105 34         74 last;
106             }
107             }
108             }
109             }
110              
111 2         11 foreach my $dispatch (@queue) {
112 136         656 my ($code, $node, @args) = @$dispatch;
113 136         241 $code->($self, $node, @args);
114             }
115 2         12 $self->_process_end;
116              
117 2         6 return $self->_pod_finalize;
118             }
119              
120             sub _init_check
121             {
122 3     3   6 my $self = shift;
123              
124 3         55 validate_pos(@_, { type => SCALAR });
125              
126 3         10 my ($file) = @_;
127             my $error = sub
128             {
129 3 50   3   59 return 'does not exist' unless -e shift;
130 3 50       10 return 'is not a file' unless -f _;
131 3 50       8 return 'is empty' unless -s _;
132 3         6 return undef;
133              
134 3         10 }->($file);
135              
136 3 50       16 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         5 $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         13  
148              
149 1         7 %{$self->{command_checks}} = (
150 34     34   108 directive => sub { $_[0] =~ /^(?:documentclass|usepackage|pagestyle)$/ },
151 30     30   57 title => sub { $_[0] eq 'title' },
152 28     28   52 author => sub { $_[0] eq 'author' },
153 26     26   44 chapter => sub { $_[0] eq 'chapter' },
154 22     22   37 section => sub { $_[0] eq 'section' },
155 16     16   48 subsection => sub { $_[0] =~ /^(?:sub){1,2}section$/ },
156 6     6   13 textbf => sub { $_[0] eq 'textbf' },
157 4     4   10 textsf => sub { $_[0] eq 'textsf' },
158 2     2   4 emph => sub { $_[0] eq 'emph' },
159 1         22 );
160 1         4 %{$self->{dispatch_command}} = (
161       0     directive => [ sub {}, undef ],
162       0     title => [ sub {}, undef ],
163       0     author => [ sub {}, undef ],
164 4     4   11 chapter => [ sub { shift->_process_chapter(@_) }, no_exec ],
165 6     6   13 section => [ sub { shift->_process_section(@_) }, no_exec ],
166 10     10   23 subsection => [ sub { shift->_process_subsection(@_) }, no_exec ],
167       0     textbf => [ sub {}, undef ],
168       0     textsf => [ sub {}, undef ],
169 1     0   16 emph => [ sub {}, undef ],
170             );
171 1         4 %{$self->{dispatch_text}} = (
172 4     4   11 directive => sub { shift->_process_directive(shift, 'directive') },
173 2     2   4 title => sub { shift->_process_directive(shift, 'title') },
174 2     2   5 author => sub { shift->_process_directive(shift, 'author') },
175 4     4   9 chapter => sub { shift->_process_text_title(@_) },
176 6     6   15 section => sub { shift->_process_text_title(@_) },
177 10     10   18 subsection => sub { shift->_process_text_title(@_) },
178 2     2   6 textbf => sub { shift->_process_tag(shift, 'textbf') },
179 2     2   5 textsf => sub { shift->_process_tag(shift, 'textsf') },
180 2     2   7 emph => sub { shift->_process_tag(shift, 'emph') },
181 1         11 );
182             }
183              
184             sub _init_vars
185             {
186 2     2   3 my $self = shift;
187              
188 2         9 delete @$self{qw(list node previous)};
189              
190 2         9 $self->{pod} = [];
191              
192 2         4 %{$self->{title_inc}} = (
  2         7  
193             title => 1,
194             chapter => 1,
195             );
196             }
197              
198             sub _init_tom
199             {
200 2     2   3 my $self = shift;
201              
202 2         14 my $parser = LaTeX::TOM->new(2); # silently discard warnings about unparseable LaTeX
203 2         142 my $document = $parser->parseFile($self->{file});
204 2         63224 my $nodes = $document->getAllNodes;
205              
206 2         883 return $nodes;
207             }
208              
209             sub _process_directive
210             {
211 8     8   9 my $self = shift;
212 8         11 my ($node, $directive) = @_;
213              
214 8 100   12   41 return if any { $directive eq $_ } qw(directive author);
  12         32  
215              
216 2 50       15 if ($directive eq 'title') {
217 2         17 $self->_pod_add('=head' . "$self->{title_inc}{title} " . $node->getNodeText);
218             }
219             }
220              
221             sub _process_comment
222             {
223 6     6   7 my $self = shift;
224 6         11 my ($node) = @_;
225              
226 6         14 $self->_process_end_item($node);
227              
228 6         15 $self->_unregister_previous(@text_node_types);
229              
230 6         14 my $text = $node->getNodeText;
231              
232 6         28 $self->_scrub_newlines(\$text);
233              
234 6         27 $text =~ s/^ \s*? \% \s*? (?=\S)//x;
235              
236 6         19 $self->_pod_add("=for comment $text");
237             }
238              
239             sub _process_text_title
240             {
241 20     20   23 my $self = shift;
242 20         21 my ($node) = @_;
243              
244 20         42 my $text = $node->getNodeText;
245              
246 20         116 $self->_process_spec_chars(\$text);
247              
248 20         39 $self->_pod_append($text);
249             }
250              
251             sub _process_text_verbatim
252             {
253 4     4   5 my $self = shift;
254 4         6 my ($node) = @_;
255              
256 4         9 $self->_process_end_item($node);
257              
258 4         10 $self->_unregister_previous(@text_node_types);
259              
260 4         10 my $text = $node->getNodeText;
261              
262 4         22 $self->_scrub_newlines(\$text);
263 4         10 $self->_process_spec_chars(\$text);
264 4         10 $self->_prepend_spaces(\$text);
265              
266 4         9 $self->_pod_add($text);
267             }
268              
269             sub _process_start_item
270             {
271 22     22   24 my $self = shift;
272 22         30 my ($node, $type) = @_;
273              
274 22         39 $self->_process_end_item($node);
275              
276 22         122 $self->_unregister_previous(@text_node_types);
277              
278 22         32 my $nested = $self->_list_nestedness($node);
279              
280 22 100       33 if ($nested) {
281 8         14 $self->_pod_add('=back');
282             }
283              
284 22         70 %{$self->{list}{$nested}} = (
  22         81  
285             type => $type,
286             enum => 1,
287             );
288              
289 22         65 $self->_pod_add('=over ' . (4 + $nested));
290              
291 22         136 $self->_register_previous('list');
292             }
293              
294             sub _process_text_item
295             {
296 22     22   26 my $self = shift;
297 22         29 my ($node) = @_;
298              
299 22         39 $self->_unregister_previous(@text_node_types);
300              
301 22         34 my $nested = $self->_list_nestedness($node) - 1;
302              
303 22 100       42 if ($self->_is_environment_type($node->getPreviousSibling, qr/^(?:$regex_list_type)$/)) {
304 4         44 $self->_pod_add('=back');
305 4         30 $self->_pod_add('=over ' . (4 + $nested));
306             }
307              
308 22         65 my $text = $node->getNodeText;
309              
310 22         93 my $type = $self->{list}{$nested}{type};
311 22         31 my $enum = \$self->{list}{$nested}{enum};
312              
313             LOOP: {
314 22         24 local ($1, $2);
  56         112  
315 56 100       295 if ($text =~ /\G \s*? \\item (?:\s*?\[(.+?)\]\s+?|\s+?) (\S.+)?$/cgmx) {
    100          
316 32         44 my $pod = '=item ';
317 32 100       63 if ($type eq 'description') {
    100          
    50          
318 2 50       7 $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       42 $pod .= defined $1 ? "$1 " : '* ';
325             }
326 32 100       67 $pod .= defined $2 ? $2 : '';
327 32         66 $self->_process_spec_chars(\$pod);
328 32         65 $self->_pod_add($pod);
329 32         227 redo;
330             }
331             elsif ($text =~ /\G \s*? (\S.+?) \s*? (?:(?=\\item)|\z)/gsx) {
332 2         4 my $pod = $1;
333 2         6 $self->_process_spec_chars(\$pod);
334 2         4 $self->_pod_add($pod);
335 2         14 redo;
336             }
337             }
338             }
339              
340             sub _process_end_item
341             {
342 86     86   99 my $self = shift;
343 86         112 my ($node) = @_;
344              
345 86 100       117 return unless $self->_is_set_previous('list');
346              
347 24         54 my $parent = $node->getParent;
348              
349 24 100       102 $parent = $parent->getParent if $self->_is_environment_type($parent, 'verbatim');
350              
351 24 100       298 if ($self->_is_environment_type($parent, 'document')) {
352 12         144 $self->_pod_add('=back');
353 12         78 $self->_unregister_previous('list', @text_node_types);
354             }
355             }
356              
357             sub _process_text
358             {
359 28     28   32 my $self = shift;
360 28         37 my ($node) = @_;
361              
362 28         47 $self->_process_end_item($node);
363              
364 28 100       87 if ($self->_is_environment_type($node->getPreviousSibling, 'abstract')) {
365 2         30 $self->_unregister_previous(@text_node_types);
366             }
367              
368 28         233 my $text = $node->getNodeText;
369              
370 28         127 $self->_scrub_newlines(\$text);
371 28         59 $self->_process_spec_chars(\$text);
372              
373 28         58 $self->_text_setter($text);
374              
375 28         196 $self->_register_previous('text');
376             }
377              
378             sub _process_chapter
379             {
380 4     4   7 my $self = shift;
381 4         5 my ($node) = @_;
382              
383 4         10 $self->_process_end_item($node);
384              
385 4         9 $self->_unregister_previous(@text_node_types);
386              
387 4   66     19 $self->{title_inc}{section} ||= $self->{title_inc}{chapter} + 1;
388              
389 4         14 $self->_pod_add('=head' . $self->{title_inc}{chapter} . ' ');
390             }
391              
392             sub _process_section
393             {
394 6     6   9 my $self = shift;
395 6         11 my ($node) = @_;
396              
397 6         10 $self->_process_end_item($node);
398              
399 6         16 $self->_unregister_previous(@text_node_types);
400              
401 6   50     13 $self->{title_inc}{section} ||= 1;
402              
403 6         21 $self->_pod_add('=head' . "$self->{title_inc}{section} ");
404             }
405              
406             sub _process_subsection
407             {
408 10     10   13 my $self = shift;
409 10         13 my ($node) = @_;
410              
411 10         19 $self->_process_end_item($node);
412              
413 10         21 $self->_unregister_previous(@text_node_types);
414              
415 10         30 my $cmd_name = $node->getCommandName;
416              
417 10         39 my $nested = 0;
418 10         48 $nested++ while $cmd_name =~ /\Gsub/g;
419              
420 10         34 $self->_pod_add('=head' . ($self->{title_inc}{section} + $nested) . ' ');
421             }
422              
423             sub _process_spec_chars
424             {
425 86     86   95 my $self = shift;
426 86         117 my ($text) = @_;
427              
428 86         244 my %umlauts = (a => 'ä',
429             A => 'Ä',
430             u => 'ü',
431             U => 'Ü',
432             o => 'ö',
433             O => 'Ö');
434              
435 86         207 while (my ($from, $to) = each %umlauts) {
436 516         3434 $$text =~ s/\\\"$from/$to/g;
437             }
438              
439 86         174 foreach my $escape ('#', qw($ % & _ { })) {
440 602         3057 $$text =~ s/\\\Q$escape\E/$escape/g;
441             }
442              
443 86         137 $$text =~ s/\\ldots/.../g;
444              
445 86         111 $$text =~ s/\\verb\*?(.)(.+?)\1/C<$2>/g;
446 86         192 $$text =~ s/(?:\\\\|\\newline)/\n/g;
447             }
448              
449             sub _process_tag
450             {
451 6     6   8 my $self = shift;
452 6         9 my ($node, $tag) = @_;
453              
454 6         18 $self->_process_end_item($node);
455              
456 6 50       26 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         49 my %tags = (textbf => 'B',
463             textsf => 'C',
464             emph => 'I');
465              
466 6         21 $self->_text_setter("$tags{$tag}<$text>");
467              
468 6         41 $self->_register_previous('tag');
469             }
470              
471             sub _process_end
472             {
473 2     2   4 my $self = shift;
474              
475 2 50       4 if ($self->_is_set_previous('list')) {
476 2         6 $self->_pod_add('=back');
477 2         13 $self->_unregister_previous('list');
478             }
479             }
480              
481             sub _is_environment_type
482             {
483 220     220   1125 my $self = shift;
484 220         351 my ($node, $type) = @_;
485              
486 220 50       1556 $type = qr/^$type$/ unless ref $type eq 'REGEXP';
487              
488 220   100     594 return ($node
489             && $node->getNodeType eq 'ENVIRONMENT'
490             && $node->getEnvironmentClass =~ $type);
491             }
492              
493             sub _list_nestedness
494             {
495 44     44   48 my $self = shift;
496 44         56 my ($node) = @_;
497              
498 44         45 my $nested = 0;
499              
500 44         82 for (my $parent = $node->getParent;
501             $self->_is_environment_type($parent, qr/^(?:$regex_list_type)$/);
502             $parent = $parent->getParent
503             ) {
504 40         501 $nested++;
505             }
506              
507 44         543 return $nested;
508             }
509              
510             sub _prepend_spaces
511             {
512 4     4   6 my $self = shift;
513 4         5 my ($text) = @_;
514              
515 4 50       10 unless (length $$text) {
516 0         0 $$text =~ s/^/ /;
517 0         0 return;
518             }
519              
520 4         27 $$text =~ s/^/ /gm;
521             }
522              
523             sub _text_setter
524             {
525 34     34   39 my $self = shift;
526 34         53 my ($text) = @_;
527              
528 34     88   132 my $append = any { $self->_is_set_previous($_) } ('list', @text_node_types);
  88         148  
529 34 100       95 my $setter = $append ? '_pod_append' : '_pod_add';
530              
531 34         75 $self->$setter($text);
532             }
533              
534             sub _pod_add
535             {
536 144     144   168 my $self = shift;
537 144         203 my ($pod) = @_;
538              
539 144 100       147 if (@{$self->{pod}}) {
  144         262  
540 142         416 $self->{pod}->[-1] =~ s/[\ \t]+$//gm;
541             }
542              
543 144         157 push @{$self->{pod}}, $pod;
  144         244  
544              
545 144 50       282 $self->_debug($pod) if $DEBUG;
546             }
547              
548             sub _pod_append
549             {
550 30     30   33 my $self = shift;
551 30         40 my ($pod) = @_;
552              
553 30         56 $self->{pod}->[-1] .= $pod;
554              
555 30 50       62 $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   40 my $self = shift;
579 38         50 my ($text) = @_;
580              
581 38         120 $$text =~ s/^\n+//;
582 38         116 $$text =~ s/\n+$//;
583             }
584              
585             sub _pod_get
586             {
587 3     3   65 my $self = shift;
588              
589 3         157 return $self->{pod};
590             }
591              
592             sub _pod_finalize
593             {
594 2     2   3 my $self = shift;
595              
596 2         5 $self->_pod_add("=cut\n");
597              
598 2         12 return join "\n\n", @{$self->_pod_get};
  2         4  
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   62 my $self = shift;
628 56         73 my ($item) = @_;
629              
630 56         83 $self->{previous}{$item} = true;
631             }
632              
633             sub _is_set_previous
634             {
635 176     176   193 my $self = shift;
636 176         236 my @items = @_;
637              
638 176         258 my $ok = eval true; # eval in order to avoid fatal errors on some older perls
639              
640 176         524 foreach my $item (@items) {
641 176 100       446 $ok &= $self->{previous}{$item} ? true : false;
642             }
643              
644 176         1239 return $ok;
645             }
646              
647             sub _unregister_previous
648             {
649 90     90   102 my $self = shift;
650 90         140 my @items = @_;
651              
652 90         116 foreach my $item (@items) {
653 190         289 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