File Coverage

blib/lib/Pod/WikiDoc.pm
Criterion Covered Total %
statement 183 201 91.0
branch 94 106 88.6
condition 23 32 71.8
subroutine 24 26 92.3
pod 4 4 100.0
total 328 369 88.8


line stmt bran cond sub pod time code
1             package Pod::WikiDoc;
2 24     24   433344 use strict;
  24         62  
  24         578  
3 24     24   113 use warnings;
  24         48  
  24         784  
4             # ABSTRACT: Generate Pod from inline wiki style text
5              
6             our $VERSION = '0.21';
7              
8 24     24   413 use 5.006;
  24         83  
9 24     24   106 use Carp;
  24         52  
  24         1396  
10 24     24   6827 use IO::String 1.06;
  24         50428  
  24         717  
11 24     24   145 use Scalar::Util 1.02 qw( blessed );
  24         406  
  24         1685  
12 24     24   34203 use Pod::WikiDoc::Parser;
  24         83  
  24         51815  
13              
14             #--------------------------------------------------------------------------#
15             # PREAMBLE DOCUMENTATION
16             #--------------------------------------------------------------------------#
17              
18             #pod =begin wikidoc
19             #pod
20             #pod = SYNOPSIS
21             #pod
22             #pod In a source file, Pod format-block style:
23             #pod =begin wikidoc
24             #pod
25             #pod = POD FORMAT-BLOCK STYLE
26             #pod
27             #pod Write documentation with *bold*, ~italic~ or {code}
28             #pod markup. Create a link to [Pod::WikiDoc].
29             #pod Substitute for user-defined %%KEYWORD%%.
30             #pod
31             #pod Indent for verbatim paragraphs
32             #pod
33             #pod * bullet
34             #pod * point
35             #pod * list
36             #pod
37             #pod 0 sequentially
38             #pod 0 numbered
39             #pod 0 list
40             #pod
41             #pod =end wikidoc
42             #pod
43             #pod In a source file, wikidoc comment-block style:
44             #pod ### = WIKIDOC COMMENT-BLOCK STYLE
45             #pod ###
46             #pod ### Optionally, [Pod::WikiDoc] can extract from
47             #pod ### specially-marked comment blocks
48             #pod
49             #pod Generate Pod from wikidoc, programmatically:
50             #pod use Pod::WikiDoc;
51             #pod my $parser = Pod::WikiDoc->new( {
52             #pod comment_blocks => 1,
53             #pod keywords => { KEYWORD => "foo" },
54             #pod } );
55             #pod $parser->filter(
56             #pod { input => "my_module.pm", output => "my_module.pod" }
57             #pod );
58             #pod
59             #pod Generate Pod from wikidoc, via command line:
60             #pod $ wikidoc -c my_module.pm my_module.pod
61             #pod
62             #pod = DESCRIPTION
63             #pod
64             #pod Pod works well, but writing it can be time-consuming and tedious. For example,
65             #pod commonly used layouts like lists require numerous lines of text to make just
66             #pod a couple of simple points. An alternative approach is to write documentation
67             #pod in a wiki-text shorthand (referred to here as ~wikidoc~) and use Pod::WikiDoc
68             #pod to extract it and convert it into its corresponding Pod as a separate {.pod}
69             #pod file.
70             #pod
71             #pod Documentation written in wikidoc may be embedded in Pod format blocks, or,
72             #pod optionally, in specially marked comment blocks. Wikidoc uses simple text-based
73             #pod markup like wiki websites to indicate formatting and links. (See
74             #pod [/WIKIDOC MARKUP], below.)
75             #pod
76             #pod Pod::WikiDoc processes text files (or text strings) by extracting both
77             #pod existing Pod and wikidoc, converting the wikidoc to Pod, and then writing
78             #pod the combined document back to a file or standard output.
79             #pod
80             #pod Summary of major features of Pod::WikiDoc:
81             #pod
82             #pod * Extracts and converts wikidoc from Pod format blocks or special
83             #pod wikidoc comment blocks
84             #pod * Extracts and preserves existing Pod
85             #pod * Provides bold, italic, code, and link markup
86             #pod * Substitutes user-defined keywords
87             #pod * Automatically converts special symbols in wikidoc to their
88             #pod Pod escape equivalents, e.g. \E\, \E\
89             #pod * Preserves other Pod escape sequences, e.g. \E\
90             #pod
91             #pod In addition, Pod::WikiDoc provides a command-line utility, [wikidoc],
92             #pod to simplify wikidoc translation.
93             #pod
94             #pod See the [Pod::WikiDoc::Cookbook] for more detailed usage examples,
95             #pod including how to automate {.pod} generation when using [Module::Build].
96             #pod
97             #pod = INTERFACE
98             #pod
99             #pod =end wikidoc
100             #pod
101             #pod =cut
102              
103             #--------------------------------------------------------------------------#
104             # PUBLIC METHODS
105             #--------------------------------------------------------------------------#
106              
107             #pod =begin wikidoc
108             #pod
109             #pod == {new}
110             #pod
111             #pod $parser = Pod::WikiDoc->new( \%args );
112             #pod
113             #pod Constructor for a new Pod::WikiDoc object. It takes a single, optional
114             #pod argument: a hash reference with the following optional keys:
115             #pod
116             #pod * {comment_blocks}: if true, Pod::WikiDoc will scan for wikidoc in comment
117             #pod blocks. Default is false.
118             #pod * {comment_prefix_length}: the number of leading sharp (#) symbols to
119             #pod denote a comment block. Default is 3.
120             #pod * {keywords}: a hash reference with keywords and values for keyword
121             #pod substitution
122             #pod
123             #pod =end wikidoc
124             #pod
125             #pod =cut
126              
127             my %default_args = (
128             comment_blocks => 0,
129             comment_prefix_length => 3,
130             keywords => {},
131             );
132              
133             sub new {
134 28     28 1 5156 my ( $class, $args ) = @_;
135              
136 28 100       256 croak "Error: Class method new() can't be called on an object"
137             if ref $class;
138              
139 27 100 100     275 croak "Error: Argument to new() must be a hash reference"
140             if $args && ref $args ne 'HASH';
141              
142 26         170 my $self = { %default_args };
143              
144             # pick up any specified arguments;
145 26         100 for my $key ( keys %default_args ) {
146 78 100       240 if ( exists $args->{$key} ) {
147 8         20 $self->{$key} = $args->{$key};
148             }
149             }
150              
151             # load up a parser
152 26         248 $self->{parser} = Pod::WikiDoc::Parser->new();
153              
154 26         154 return bless $self, $class;
155             }
156              
157             #pod =begin wikidoc
158             #pod
159             #pod == {convert}
160             #pod
161             #pod my $pod_text = $parser->convert( $input_text );
162             #pod
163             #pod Given a string with valid Pod and/or wikidoc markup, filter/translate it to
164             #pod Pod. This is really just a wrapper around {filter} for working with
165             #pod strings rather than files, and provides similar behavior, including adding
166             #pod a 'Generated by' header.
167             #pod
168             #pod =end wikidoc
169             #pod
170             #pod =cut
171              
172             sub convert {
173 22     22 1 15414 my ($self, $input_string) = @_;
174              
175 22 100       251 croak "Error: Argument to convert() must be a scalar"
176             if ( ref \$input_string ne 'SCALAR' );
177              
178 21         160 my $input_fh = IO::String->new( $input_string );
179 21         1056 my $output_fh = IO::String->new();
180 21         650 _filter_podfile( $self, $input_fh, $output_fh );
181              
182 21         37 return ${ $output_fh->string_ref() };
  21         76  
183             }
184              
185             #pod =begin wikidoc
186             #pod
187             #pod == {filter}
188             #pod
189             #pod $parser->filter( \%args );
190             #pod
191             #pod Filters from an input file for Pod and wikidoc, translating it to Pod
192             #pod and writing it to an output file. The output file will be prefixed with
193             #pod a 'Generated by' comment with the version of Pod::WikiDoc and timestamp,
194             #pod as required by [perlpodspec].
195             #pod
196             #pod {filter} takes a single, optional argument: a hash reference with
197             #pod the following optional keys:
198             #pod
199             #pod * {input}: a filename or filehandle to read from. Defaults to STDIN.
200             #pod * {output}: a filename or filehandle to write to. If given a filename
201             #pod and the file already exists, it will be clobbered. Defaults to STDOUT.
202             #pod
203             #pod =end wikidoc
204             #pod
205             #pod =cut
206              
207             sub filter {
208 27     27 1 57867 my ( $self, $args_ref ) = @_;
209              
210 27 100 66     355 croak "Error: Argument to filter() must be a hash reference"
211             if defined $args_ref && ref($args_ref) ne 'HASH';
212             # setup input
213 26         51 my $input_fh;
214 26 50 66     256 if ( ! $args_ref->{input} ) {
    100 66        
    100 66        
215 0         0 $input_fh = \*STDIN;
216             }
217             elsif ( ( blessed $args_ref->{input} && $args_ref->{input}->isa('GLOB') )
218             || ( ref $args_ref->{input} eq 'GLOB' )
219             || ( ref \$args_ref->{input} eq 'GLOB' ) ) {
220             # filehandle or equivalent
221 13         209 $input_fh = $args_ref->{input};
222             }
223             elsif ( ref \$args_ref->{input} eq 'SCALAR' ) {
224             # filename
225             open( $input_fh, "<", $args_ref->{input} )
226 12 100       255 or croak "Error: Couldn't open input file '$args_ref->{input}': $!";
227             }
228             else {
229 1         64 croak "Error: 'input' parameter for filter() must be a filename or filehandle"
230             }
231              
232             # setup output
233 24         62 my $output_fh;
234 24 50 66     187 if ( ! $args_ref->{output} ) {
    100 66        
    100 66        
235 0         0 $output_fh = \*STDOUT;
236             }
237             elsif ( ( blessed $args_ref->{output} && $args_ref->{output}->isa('GLOB') )
238             || ( ref $args_ref->{output} eq 'GLOB' )
239             || ( ref \$args_ref->{output} eq 'GLOB' ) ) {
240             # filehandle or equivalent
241 11         121 $output_fh = $args_ref->{output};
242             }
243             elsif ( ref \$args_ref->{output} eq 'SCALAR' ) {
244             # filename
245             open( $output_fh, ">", $args_ref->{output} )
246 12 100       319 or croak "Error: Couldn't open output file '$args_ref->{output}': $!";
247             }
248             else {
249 1         63 croak "Error: 'output' parameter for filter() must be a filename or filehandle"
250             }
251              
252 22         92 _filter_podfile( $self, $input_fh, $output_fh );
253 22         746 return;
254             }
255              
256             #pod =begin wikidoc
257             #pod
258             #pod == {format}
259             #pod
260             #pod my $pod_text = $parser->format( $wiki_text );
261             #pod
262             #pod Given a string with valid Pod and/or wikidoc markup, filter/translate it to
263             #pod Pod. Unlike {convert}, no 'Generated by' comment is added. This
264             #pod function is used internally by Pod::WikiDoc, but is being made available
265             #pod as a public method for users who want more granular control of the
266             #pod translation process or who want to convert wikidoc to Pod for other
267             #pod creative purposes using the Pod::WikiDoc engine.
268             #pod
269             #pod =end wikidoc
270             #pod
271             #pod =cut
272              
273             sub format { ## no critic
274 117     117 1 62378 my ($self, $wikitext) = @_;
275              
276 117 100       638 croak "Error: Argument to format() must be a scalar"
277             if ( ref \$wikitext ne 'SCALAR' );
278              
279 116         1068 my $wiki_tree = $self->{parser}->WikiDoc( $wikitext ) ;
280 116         1882 for my $node ( @$wiki_tree ) {
281 193 50       585 undef $node if ! ref $node;
282             }
283              
284 116         526 return _wiki2pod( $wiki_tree, $self->{keywords} );
285             }
286              
287             #--------------------------------------------------------------------------#
288             # PRIVATE METHODS
289             #--------------------------------------------------------------------------#
290              
291             #--------------------------------------------------------------------------#
292             # _comment_block_regex
293             #
294             # construct a regex dynamically for the right comment prefix
295             #--------------------------------------------------------------------------#
296              
297             sub _comment_block_regex {
298 101     101   188 my ( $self ) = @_;
299 101         227 my $length = $self->{comment_prefix_length};
300 101         748 return qr/\A#{$length}(?:\s(.*))?\z/ms;
301             }
302              
303             #--------------------------------------------------------------------------#
304             # _input_iterator
305             #
306             # return an iterator that streams a filehandle. Action arguments:
307             # 'peek' -- lookahead at the next line without consuming it
308             # 'next' and 'drop' -- synonyms to consume and return the next line
309             #--------------------------------------------------------------------------#
310              
311             sub _input_iterator {
312 55     55   127 my ($self, $fh) = @_;
313 55         100 my @head;
314             return sub {
315 1433     1433   2314 my ($action) = @_;
316 1433 100 66     3817 if ($action eq 'peek') {
    50          
317 810 100       2755 push @head, scalar <$fh> unless @head;
318 810         10142 return $head[0];
319             }
320             elsif ( $action eq 'drop' || $action eq 'next' ) {
321 623 50       1996 return shift @head if @head;
322 0         0 return scalar <$fh>;
323             }
324             else {
325 0         0 croak "Unrecognized iterator action '$action'\n";
326             }
327             }
328 55         278 }
329              
330             #--------------------------------------------------------------------------#
331             # _exhaust_iterator
332             #
333             # needed to help abort processing
334             #--------------------------------------------------------------------------#
335              
336             sub _exhaust_iterator {
337 0     0   0 my ($self, $iter) = @_;
338 0         0 1 while $iter->();
339 0         0 return;
340             }
341              
342             #--------------------------------------------------------------------------#
343             # _output_iterator
344             #
345             # returns an output "iterator" that streams to a filehandle. Inputs
346             # are array refs of the form [ $FORMAT, @LINES ]. Format 'pod' is
347             # printed to the filehandle immediately. Format 'wikidoc' is accumulated
348             # until the next 'pod' then converted to wikidoc and printed to the file
349             # handle
350             #--------------------------------------------------------------------------#
351              
352             sub _output_iterator {
353 55     55   119 my ($self, $fh) = @_;
354 55         89 my @wikidoc;
355             return sub {
356 473     473   815 my ($chunk) = @_;
357 473 100       1096 if ($chunk eq 'flush') {
358 55 100       139 print {$fh} $self->format( join(q{}, splice(@wikidoc,0) ) )
  16         85  
359             if @wikidoc;
360 55         454 return;
361             }
362 418 50       915 return unless ref($chunk) eq 'ARRAY';
363 418         695 my ($format, @lines) = grep { defined $_ } @$chunk;
  868         1877  
364 418 100       984 if ( $format eq 'wikidoc' ) {
    50          
365 211         369 push @wikidoc, @lines;
366             }
367             elsif ( $format eq 'pod' ) {
368 207 100       431 print {$fh} $self->format( join(q{}, splice(@wikidoc,0) ) )
  35         172  
369             if @wikidoc;
370 207         901 print {$fh} @lines;
  207         476  
371             }
372 418         3184 return;
373             }
374 55         230 }
375              
376             #--------------------------------------------------------------------------#
377             # _filter_podfile()
378             #
379             # extract Pod from input and pass through to output, converting any wikidoc
380             # markup to Pod in the process
381             #--------------------------------------------------------------------------#
382              
383             my $BLANK_LINE = qr{\A \s* \z}xms;
384             my $NON_BLANK_LINE = qr{\A \s* \S }xms;
385             my $FORMAT_LABEL = qr{:? [-a-zA-Z0-9_]+}xms;
386             my $POD_CMD = qr{\A =[a-zA-Z]+}xms;
387             my $BEGIN = qr{\A =begin \s+ ($FORMAT_LABEL) \s* \z}xms;
388             my $END = qr{\A =end \s+ ($FORMAT_LABEL) \s* \z}xms;
389             my $FOR = qr{\A =for \s+ ($FORMAT_LABEL) [ \t]* (.*) \z}xms;
390             my $POD = qr{\A =pod \s* \z}xms;
391             my $CUT = qr{\A =cut \s* \z}xms;
392              
393             sub _filter_podfile {
394 55     55   12933 my ($self, $input_fh, $output_fh) = @_;
395              
396             # open output with tag and Pod marker
397 55         294 print $output_fh
398             "# Generated by Pod::WikiDoc version $Pod::WikiDoc::VERSION\n\n";
399 55         893 print $output_fh "=pod\n\n";
400              
401             # setup iterators
402 55         788 my $in_iter = $self->_input_iterator( $input_fh );
403 55         189 my $out_iter = $self->_output_iterator( $output_fh );
404              
405             # starting filter mode is code
406 55         195 $self->_filter_code( $in_iter, $out_iter );
407 55         152 $out_iter->('flush');
408              
409 55         722 return;
410             }
411              
412             #--------------------------------------------------------------------------#
413             # _filter_code
414             #
415             # we need a "cutting" flag -- if we got here from a =cut, then we return to
416             # caller ( pod or format ) when we see pod. Otherwise we're just starting
417             # and need to start a new pod filter when we see pod
418             #
419             # perlpodspec says starting Pod with =cut is an error and that we
420             # *must* halt parsing and *should* issue a warning. Here we might be
421             # far down the call stack and don't want to just return where the caller
422             # might continue processing. To avoid this, we exhaust the input first.
423             #--------------------------------------------------------------------------#
424              
425             sub _filter_code {
426 89     89   205 my ($self, $in_iter, $out_iter, $cutting) = @_;
427 89         233 my $CBLOCK = _comment_block_regex($self);
428 89         242 CODE: while ( defined( my $peek = $in_iter->('peek') ) ) {
429 111 100       474 $peek =~ $CBLOCK && do {
430 12         60 $self->_filter_cblock( $in_iter, $out_iter );
431 12         30 next CODE;
432             };
433 99 50       343 $peek =~ $CUT && do {
434 0         0 warn "Can't start Pod with '$peek'\n";
435 0         0 $self->_exhaust_iterator( $in_iter );
436 0         0 last CODE;
437             };
438 99 100       414 $peek =~ $POD_CMD && do {
439 44 100       122 last CODE if $cutting;
440 43         153 $self->_filter_pod( $in_iter, $out_iter );
441 43         112 next CODE;
442             };
443 55         82 do { $in_iter->('drop') };
  55         104  
444             }
445 89         238 return;
446             }
447              
448             #--------------------------------------------------------------------------#
449             # _filter_pod
450             #
451             # Pass through lines to the output iterators, but flag wikidoc lines
452             # differently so that they can be converted on output
453             #
454             # If we find an =end that is out of order, perlpodspec says we *must* warn
455             # and *may* halt. Instead of halting, we return to the caller in the
456             # hopes that an earlier format might match this =end.
457             #--------------------------------------------------------------------------#
458              
459             sub _filter_pod {
460 43     43   102 my ($self, $in_iter, $out_iter) = @_;
461 43         90 my @format = (); # no format to start
462             # process the pod block -- recursing as necessary
463 43         97 LINE: while ( defined( my $peek = $in_iter->('peek') ) ) {
464 508 100       1712 $peek =~ $POD && do {
465 13         40 $in_iter->('drop');
466 13         39 next LINE;
467             };
468 495 100       1412 $peek =~ $CUT && do {
469 34         99 $in_iter->('drop');
470 34         148 $self->_filter_code( $in_iter, $out_iter, 1 );
471 34         96 next LINE;
472             };
473 461 100       1209 $peek =~ $FOR && do {
474 21         89 $self->_filter_for( $in_iter, $out_iter );
475 21         60 next LINE;
476             };
477 440 100       1179 $peek =~ $END && do {
478 38 50       198 if ( ! @format ) {
    50          
    100          
479 0         0 warn "Error: '$peek' doesn't match any '=begin $1'\n";
480 0         0 $in_iter->('drop');
481 0         0 next LINE;
482             }
483             elsif ( $format[-1] ne $1 ) {
484 0         0 warn "Error: '$peek' doesn't match '=begin $format[-1]'\n";
485 0         0 pop @format; # try an earlier format
486 0         0 redo LINE;
487             }
488             elsif ( $format[-1] eq 'wikidoc' ) {
489 25         171 pop @format;
490 25         79 $in_iter->('drop');
491 25         69 next LINE;
492             }
493             else {
494 13         24 pop @format;
495             # and let it fall through to the output iterator
496             }
497             };
498 415 100       1221 $peek =~ $BEGIN && do {
499 39 100       138 if ( $1 eq 'wikidoc' ) {
500 26         60 push @format, 'wikidoc';
501 26         77 $in_iter->('drop');
502 26         73 next LINE;
503             }
504             else {
505 13         34 push @format, $1;
506             # and let it fall through to the output iterator
507             }
508             };
509 389         572 do {
510 389 100 100     1368 my $out_type =
511             ( @format && $format[-1] eq 'wikidoc' ) ? 'wikidoc' : 'pod' ;
512 389         728 $out_iter->( [ $out_type, $in_iter->('next') ] )
513             };
514             }
515 43         96 return;
516             }
517              
518             #--------------------------------------------------------------------------#
519             # _filter_for
520             #--------------------------------------------------------------------------#
521              
522             sub _filter_for {
523 21     21   55 my ($self, $in_iter, $out_iter) = @_;
524 21         48 my $for_line = $in_iter->('next');
525 21         115 my ($format, $rest) = $for_line =~ $FOR;
526 21   50     64 $rest ||= "\n";
527              
528 21 100       75 my @lines = ( $format eq 'wikidoc' ? $rest : $for_line );
529              
530 21         54 LINE: while ( defined( my $peek = $in_iter->('peek') ) ) {
531 25 100       133 $peek =~ $BLANK_LINE && do {
532 16         45 last LINE;
533             };
534 9         15 do {
535 9         46 push @lines, $in_iter->('next');
536             };
537             }
538 21 100       63 if ($format eq 'wikidoc' ) {
539 8         22 $in_iter->('drop'); # wikidoc will append \n
540             }
541             else {
542 13         34 push @lines, $in_iter->('next');
543             }
544 21 100       65 my $out_type = $format eq 'wikidoc' ? 'wikidoc' : 'pod' ;
545 21         69 $out_iter->( [ $out_type, @lines ] );
546 21         54 return;
547             }
548              
549             #--------------------------------------------------------------------------#
550             # _filter_cblock
551             #--------------------------------------------------------------------------#
552              
553             sub _filter_cblock {
554 12     12   34 my ($self, $in_iter, $out_iter) = @_;
555 12 50       71 my @lines = ($1 ? $1 : "\n"); ## no critic
556 12         40 $in_iter->('next');
557 12         34 my $CBLOCK = _comment_block_regex($self);
558 12         34 LINE: while ( defined( my $peek = $in_iter->('peek') ) ) {
559 27 100       146 last LINE if $peek !~ $CBLOCK;
560 18 100       70 push @lines, ($1 ? $1 : "\n");
561 18         46 $in_iter->('next');
562             }
563 12 100       64 $out_iter->( [ 'wikidoc', @lines ] ) if $self->{comment_blocks};
564 12         36 return;
565             }
566              
567              
568             #--------------------------------------------------------------------------#
569             # Translation functions and tables
570             #--------------------------------------------------------------------------#
571              
572             #--------------------------------------------------------------------------#
573             # Tables for formatting
574             #--------------------------------------------------------------------------#
575              
576             # Used in closure for counting numbered lists
577             my $numbered_bullet;
578              
579             # Text to print at start of entity from parse tree, or a subroutine
580             # to generate the text programmatically
581             my %opening_of = (
582             Paragraph => q{},
583             Unordered_List => "=over\n\n",
584             Ordered_List => sub { $numbered_bullet = 1; return "=over\n\n" },
585             Preformat => q{},
586             Header => sub {
587             my $node = shift;
588             my $level = $node->{level} > 4
589             ? 4 : $node->{level};
590             return "=head$level "
591             },
592             Bullet_Item => "=item *\n\n",
593             Numbered_Item => sub {
594             return "=item " . $numbered_bullet++
595             . ".\n\n"
596             },
597             Indented_Line => q{ },
598             Plain_Line => q{},
599             Empty_Line => q{ },
600             Parens => "(",
601             RegularText => q{},
602             EscapedChar => q{},
603             WhiteSpace => q{},
604             InlineCode => "C<<< ",
605             BoldText => 'B<',
606             ItalicText => 'I<',
607             KeyWord => q{},
608             LinkContent => 'L<',
609             LinkLabel => q{},
610             LinkTarget => q{},
611             );
612              
613             # Text to print at end of entity from parse tree, or a subroutine
614             # to generate the text programmatically
615             my %closing_of = (
616             Paragraph => "\n",
617             Unordered_List => "=back\n\n",
618             Ordered_List => "=back\n\n",
619             Preformat => "\n",
620             Header => "\n\n",
621             Bullet_Item => "\n\n",
622             Numbered_Item => "\n\n",
623             Indented_Line => "\n",
624             Plain_Line => "\n",
625             Empty_Line => "\n",
626             Parens => ")",
627             RegularText => q{},
628             EscapedChar => q{},
629             WhiteSpace => q{},
630             InlineCode => " >>>",
631             BoldText => ">",
632             ItalicText => ">",
633             KeyWord => q{},
634             LinkContent => q{>},
635             LinkLabel => q{|},
636             LinkTarget => q{},
637             );
638              
639             # Subroutine to handle actual raw content from different node types
640             # from the parse tree
641             my %content_handler_for = (
642             RegularText => \&_escape_pod,
643             Empty_Line => sub { q{} },
644             KeyWord => \&_keyword_expansion,
645             );
646              
647             # Table of character to E<> code conversion
648             my %escape_code_for = (
649             q{>} => "E",
650             q{<} => "E",
651             q{|} => "E",
652             q{/} => "E",
653             );
654              
655             # List of characters that need conversion
656             my $specials = join q{}, keys %escape_code_for;
657              
658             #--------------------------------------------------------------------------#
659             # _escape_pod()
660             #
661             # After removing backslash escapes from a text string, translates characters
662             # that must be escaped in Pod <, >, |, and / to their Pod E<> code equivalents
663             #
664             #--------------------------------------------------------------------------#
665              
666             sub _escape_pod {
667              
668 674     674   980 my $node = shift;
669              
670 674         1148 my $input_text = $node->{content};
671              
672             # remove backslash escaping
673 674         1156 $input_text =~ s{ \\(.) }
674             {$1}gxms;
675              
676             # replace special symbols with corresponding escape code
677 674         2205 $input_text =~ s{ ( [$specials] ) }
678             {$escape_code_for{$1}}gxms;
679              
680 674         1546 return $input_text;
681             }
682              
683             #--------------------------------------------------------------------------#
684             # _keyword_expansion
685             #
686             # Given a keyword, return the corresponding value from the keywords
687             # hash or the keyword itself
688             #--------------------------------------------------------------------------#
689              
690             sub _keyword_expansion {
691 8     8   17 my ($node, $keywords) = @_;
692 8         16 my $key = $node->{content};
693 8         20 my $value = $keywords->{$key};
694 8 100       57 return defined $value ? $value : q{%%} . $key . q{%%} ;
695             }
696              
697              
698             #--------------------------------------------------------------------------#
699             # _translate_wikidoc()
700             #
701             # given an array of wikidoc lines, joins them and runs them through
702             # the formatter
703             #--------------------------------------------------------------------------#
704              
705             sub _translate_wikidoc {
706 0     0   0 my ( $self, $wikidoc_ref ) = @_;
707 0         0 return $self->format( join q{}, @$wikidoc_ref );
708             }
709              
710             #--------------------------------------------------------------------------#
711             # _wiki2pod()
712             #
713             # recursive function that walks a Pod::WikiDoc::Parser tree and generates
714             # a string with the corresponding Pod
715             #--------------------------------------------------------------------------#
716              
717             sub _wiki2pod {
718 388     388   818 my ($nodelist, $keywords, $insert_space) = @_;
719 388         640 my $result = q{};
720 388         701 for my $node ( @$nodelist ) {
721             # XXX print "$node\n" if ref $node ne 'HASH';
722 1597         3042 my $opening = $opening_of{ $node->{type} };
723 1597         2663 my $closing = $closing_of{ $node->{type} };
724              
725 1597 100       3089 $result .= ref $opening eq 'CODE' ? $opening->($node) : $opening;
726 1597 100       2945 if ( ref $node->{content} eq 'ARRAY' ) {
727             $result .= _wiki2pod(
728             $node->{content},
729             $keywords,
730 272 100       1013 $node->{type} eq 'Preformat' ? 1 : 0
731             );
732             }
733             else {
734 1325         2271 my $handler = $content_handler_for{ $node->{type} };
735             $result .= defined $handler
736             ? $handler->( $node, $keywords ) : $node->{content}
737 1325 100       2777 ;
738             }
739 1597 50       3607 $result .= ref $closing eq 'CODE' ? $closing->($node) : $closing;
740             }
741 388         1615 return $result;
742             }
743              
744             1; #this line is important and will help the module return a true value
745              
746             __END__