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