File Coverage

lib/Web/DataService/PodParser.pm
Criterion Covered Total %
statement 9 335 2.6
branch 0 246 0.0
condition 0 77 0.0
subroutine 3 15 20.0
pod 2 8 25.0
total 14 681 2.0


} ) unless $table_def->{no_header}; } ) unless $table_def->{no_header}; \n\n} ) unless $table_def->{no_header}; } ); } ) if $wds->{listcol} > 0;
line stmt bran cond sub pod time code
1             #
2             # Web::DataService::PodParser
3             #
4             # This module implements a Pod-to-HTML formatter, subclassed from Pod::Simple.
5             #
6              
7 2     2   14 use strict;
  2         5  
  2         81  
8              
9             package Web::DataService::PodParser;
10 2     2   1229 use Pod::Simple;
  2         59173  
  2         71  
11 2     2   20 use Carp ();
  2         4  
  2         10256  
12              
13             our(@ISA) = qw(Pod::Simple);
14              
15              
16             # new ( options )
17             #
18             # Create a new Pod-to-HTML translator. $options must be a hashref, and may contain any of the
19             # following keys:
20             #
21             # target Currently, must be 'html'; other target formats may be added later.
22             # html_header If specified, this string will be used as the beginning of the HTML output. It
23             # should start with and end with a tag. If not specified, this
24             # module will generate the header.
25             # html_footer If specified, this string will be used as the end of the HTML output. It should
26             # include and . If not specified, those two closing tags will
27             # be appended to the end of the output.
28             # css URL of the stylesheet for the generated documentation. This may be site-relative,
29             # need not be absolute. If not specified, no stylesheet link will be generated.
30             # page_title Title for the generated HTML page. If the page contains a '=for wds_title'
31             # directive, its value will override this option value.
32             # url_formatter A code ref to be called for translating URL specifications for output.
33             # debug If true, then debugging output will be printed to STDERR
34             # no_tables If true, then item lists will be translated to HTML
lists
35             # instead of HTML tables.
36             #
37              
38             our (%FORMATTER_OPTION) = ( target => 1, html_header => 1, html_footer => 1, css =>1,
39             page_title => 1, url_formatter => 1, debug => 1, no_tables => 1 );
40              
41             sub new {
42              
43 0     0 1   my ($class, $options) = @_;
44            
45             # Create a new Pod::Simple formatter. We tell it to accept all targets
46             # because Pod::Simple behaves strangely when it encounters targets it
47             # doesn't know what to do with. We turn off the automatically generated
48             # errata section, since we will be generating this ourselves. Finally, we
49             # provide it a subroutine that will strip indentation from verbatim blocks
50             # according to the indentation on the first line.
51            
52 0           my $new = $class->SUPER::new;
53            
54 0           $new->accept_target_as_text('wds_nav');
55 0           $new->accept_targets('*');
56 0           $new->no_errata_section(1);
57             $new->strip_verbatim_indent(sub {
58 0     0     my $lines = shift;
59 0           (my $indent = $lines->[0]) =~ s/\S.*//;
60 0           return $indent;
61 0           });
62            
63             # Decorate the formatter with some fields relevant to this subclass.
64            
65 0           $new->{wds_fields} = { body => [ '' ], target => [ 'body' ],
66             listlevel => 0, listcol => 0 };
67            
68             # Add any options that were specified.
69            
70             Carp::croak "you must specify an options hash, with at least the option 'target'"
71 0 0 0       unless ref $options eq 'HASH' && defined $options->{target};
72            
73 0           foreach my $k ( keys %$options )
74             {
75 0 0         Carp::croak "invalid option '$k'" unless $FORMATTER_OPTION{$k};
76            
77 0           $new->{wds_fields}{options}{$k} = $options->{$k};
78             }
79            
80 0 0         Carp::croak "the only allowed target is 'html'" unless lc $options->{target} eq 'html';
81            
82             # Bless the new instance into the current package and return it.
83            
84 0           return bless $new;
85             }
86              
87              
88             # _handle_element_start ( parser, element_name, attr_hash )
89             #
90             # This method will be called automatically by the Pod::Simple parsing code at the beginning of each
91             # Pod element that it recognizes.
92              
93             sub _handle_element_start {
94            
95 0     0     my ($parser, $element_name, $attr_hash) = @_;
96            
97             # Shortcut access the object fields for this subclass.
98            
99 0           my $wds = $parser->{wds_fields};
100            
101             # If debugging mode is turned on, emit debugging output.
102            
103 0 0         if ( $wds->{options}{debug} )
104             {
105 0           print STDERR "START $element_name";
106            
107 0           foreach my $k (keys %$attr_hash)
108             {
109 0           print STDERR " $k=" . $attr_hash->{$k};
110             }
111            
112 0           print STDERR "\n";
113             }
114            
115             # If the last element found was '=for wds_table_header', the current element must be '=over'
116             # or else an error will be reported.
117            
118 0 0         if ( $wds->{pending_columns} )
119             {
120 0 0         unless ( $element_name eq 'over-text' )
121             {
122 0           push @{$wds->{errors}}, [ $wds->{header_source_line},
123 0           "improperly placed '=for wds_table_header': must immediately precede '=over'" ];
124 0           $wds->{header_source_line} = undef;
125 0           $wds->{table_no_header} = undef;
126 0           $wds->{pending_columns} = undef;
127             }
128             }
129            
130             # If we have found an ordinary paragraph and are not inside a list, generate a

tag.

131            
132 0 0 0       if ( $element_name eq 'Para' && ! $wds->{listlevel} )
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
133             {
134 0           my $attrs = qq{ class="pod_para"};
135            
136             # If we have a pending anchor, use it as the identifier for this paragraph.
137            
138 0 0         if ( defined $wds->{pending_anchor} )
139             {
140             $attrs .= qq{ id="$wds->{pending_anchor}"}
141 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
142 0           $wds->{pending_anchor} = undef;
143             }
144            
145 0           $parser->add_output_text( qq{\n\n} );
146             }
147            
148             # If we have found a data paragraph, the 'Data' element start/end will be surrounded by a
149             # 'for' element start/end. We handle any necessary processing on the latter.
150            
151             elsif ( $element_name eq 'Data' )
152             {
153             # nothing to do here
154             }
155            
156             # If we have found a Verbatim paragraph, generate a
 tag. 
157            
158             elsif ( $element_name eq 'Verbatim' )
159             {
160 0           my $attrs = qq{ class="pod_verbatim"};
161            
162             # If we have a pending anchor, use it as the identifier for this paragraph.
163            
164 0 0         if ( defined $wds->{pending_anchor} )
165             {
166             $attrs .= qq{ id="$wds->{pending_anchor}"}
167 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
168 0           $wds->{pending_anchor} = undef;
169             }
170            
171 0           $parser->add_output_text( qq{\n\n} );
172             }
173            
174             # If we have found =head1, =head2, etc., then start capturing heading text. We
175             # will generate the appropriate HTML tag when we finish. This is necessary because the default
176             # identifier for the heading tag will be the heading text.
177            
178             elsif ( $element_name =~ qr{ ^ head ( \d ) }xs )
179             {
180 0           $parser->capture_output_text('head');
181             }
182            
183             # If we have found =over where the first item indicates a bullet or a number, then we are starting a
184             # list. We will generate
    or
      as appropriate.
185            
186             elsif ( $element_name =~ qr{ ^ over-(bullet|number) $ }xs )
187             {
188 0 0         my $tag = $1 eq 'bullet' ? 'ul' : 'ol';
189 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_list2' : 'pod_list';
190 0           my $attrs = qq{ class="$class"};
191            
192             # If we have a pending anchor, use it as the identifier for this list..
193            
194 0 0         if ( defined $wds->{pending_anchor} )
195             {
196             $attrs .= qq{ id="$wds->{pending_anchor}"}
197 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
198 0           $wds->{pending_anchor} = undef;
199             }
200            
201 0           $parser->add_output_text( qq{\n\n<$tag$attrs>} );
202 0           $wds->{listlevel}++;
203             }
204            
205             # If we have found =item inside a bulleted or numbered list, then generate an
  • tag.
  • 206            
    207             elsif ( $element_name =~ qr{ ^ item-(bullet|number) $ }xs )
    208             {
    209             # We use a different CSS class for top-level lists than for sublists, but not a separate
    210             # one for sub-sublists.
    211            
    212 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_def2' : 'pod_def';
    213 0           my $attrs = qq{ class="$class"};
    214            
    215             # If an explicit number was specified, use that as the item value. This allows the author
    216             # to explicitly number list items if desired, with automatic numbering as a fallback.
    217            
    218 0 0 0       if ( $1 =~ qr{^n}i && defined $attr_hash->{'~orig_content'} && defined $attr_hash->{number} )
          0        
    219             {
    220 0           $attr_hash->{'~orig_content'} =~ qr{ (\d+) }xs;
    221 0 0         if ( $1 ne $attr_hash->{number} )
    222             {
    223 0           $attrs .= qq{ value="$1"};
    224             }
    225             }
    226            
    227             # If we have a pending anchor, use it as the identifier for this list item.
    228            
    229 0 0         if ( defined $wds->{pending_anchor} )
    230             {
    231             $attrs .= qq{ id="$wds->{pending_anchor}"}
    232 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
    233 0           $wds->{pending_anchor} = undef;
    234             }
    235            
    236 0           $parser->add_output_text( qq{\n\n} );
    237             }
    238            
    239             # If we have found =over where the first item is NOT a bullet or a number, then we are
    240             # generating a table. Unless, that is, this formatter was instantiated with the no_tables option
    241             # in which case we generate a definition-list using
    ,
    , and
    .
    242            
    243             elsif ( $element_name =~ qr{ ^ over-text $ }xs )
    244             {
    245 0 0         my $tag = $wds->{options}{no_tables} ? 'dl' : 'table';
    246 0 0         my $class = $wds->{listlevel} > 0 ? 'pod_list2' : 'pod_list';
    247 0           my $attrs = qq{ class="$class"};
    248            
    249             # If we have a pending anchor, use it as the identifier for this table or list.
    250            
    251 0 0         if ( defined $wds->{pending_anchor} )
    252             {
    253             $attrs .= qq{ id="$wds->{pending_anchor}"}
    254 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
    255 0           $wds->{pending_anchor} = undef;
    256             }
    257            
    258             # Emit the tag that opens the list or table.
    259            
    260 0           $parser->add_output_text( qq{\n\n<$tag$attrs>} );
    261            
    262             # If have a pending list of table column definitions, and this formatter was not instantiated
    263             # with the 'no_tables' option, then process that list. Unless the 'no_header' flag is
    264             # set, generate a header row now. We must process the column definitions regardless,
    265             # since they may affect the style of the ordinary table cells.
    266            
    267 0           my $table_def = { n_cols => 0, n_subs => 0 };
    268            
    269 0 0         $table_def->{no_header} = 1 if $wds->{table_no_header};
    270            
    271 0 0 0       if ( $wds->{pending_columns} && ! $wds->{options}{no_tables} )
    272             {
    273 0           my @columns;
    274            
    275 0 0         my $class = $wds->{listlevel} > 0 ? 'pod_th2' : 'pod_th';
    276            
    277             # Start the header row, unless 'no_header' is in effect for this table.
    278            
    279 0 0         $parser->add_output_text( qq{\n\n
    280            
    281             # Process each column definition in turn.
    282            
    283 0           foreach my $col ( @{$wds->{pending_columns}} )
      0            
    284             {
    285 0           my $col_def;
    286 0           my $attrs = '';
    287 0           my $multiplicity = 1;
    288            
    289 0           $table_def->{n_cols}++;
    290            
    291             # If the column definition ends in /n where n is an integer, then it represents n
    292             # separate columns. We must prepare to generate a subheader row with that many
    293             # cells under this one. In this case, the first =item subsequently encountered
    294             # will provide the labels for these cells. This feature is used to generate
    295             # response field name columns for each vocabulary when there are multiple
    296             # vocabularies.
    297             #
    298             # Note that /n only works on the FIRST COLUMN.
    299            
    300 0 0         if ( $col =~ qr{ ^ (.+) / ( \d+ ) $ }xs )
    301             {
    302             # Strip off the suffix we just recognized.
    303            
    304 0           $col = $1;
    305            
    306             # If this is the first column, then give this cell a 'colspan' attribute equal
    307             # to the column multiplicity. Also note the number of subheader cells we are
    308             # expecting and set the 'expect_subheader' flag. This flag will cause the
    309             # first =item paragraph we encounter to be treated specially as a list of
    310             # subheader labels.
    311            
    312 0 0         unless ( @columns )
    313             {
    314 0           $multiplicity = $2;
    315 0           $attrs = qq{ colspan="$multiplicity"};
    316 0           $table_def->{n_subs} += $multiplicity;
    317 0           $table_def->{expect_subheader} = 1;
    318             }
    319            
    320             # If this is not the first column, then the suffix is ignored.
    321             }
    322            
    323             # If this is not the first column and the first column has subheaders, then
    324             # set the "rowspan" attribute for this header cell.
    325            
    326 0 0 0       if ( @columns && $table_def->{n_subs} )
    327             {
    328 0           $attrs = qq{ rowspan="2"};
    329             }
    330            
    331             # If the column definition ends in *, then we set the 'term' flag to indicate that
    332             # the cells in this column should have a different style class. Strip the * suffix
    333             # off the definition, and use the rest as the column name.
    334            
    335 0 0         if ( $col =~ qr{ ^ (.*) [*] $ }xs )
    336             {
    337 0           $col = $1;
    338 0           $col_def = { name => $col, term => 1 };
    339             }
    340            
    341             # Otherwise, just use the column definition as the column name.
    342            
    343             else
    344             {
    345 0           $col_def = { name => $col };
    346             }
    347            
    348             # Add this column definition record to the column list for this table. If the
    349             # definition had an /n suffix, then add it that many times.
    350            
    351 0           push @columns, $col_def foreach 1..$multiplicity;
    352            
    353             # Use the remaining column definition after any suffixes have been stripped as the
    354             # label in the header cell.
    355            
    356 0 0         $parser->add_output_text( qq{$col
    357             }
    358            
    359             # Save the generated list of column definition records, for use in generating the
    360             # body rows.
    361            
    362 0           $table_def->{columns} = \@columns;
    363            
    364             # Close the header row, if we are generating one.
    365            
    366 0 0         $parser->add_output_text( qq{
    367             }
    368            
    369             # We keep a stack of table definitions, because we may be generating a table inside
    370             # another table cell. In particular, this can happen when a table of acceptable values is
    371             # given inside the definition of a parameter or response field.
    372            
    373 0           unshift @{$wds->{table_def}}, $table_def;
      0            
    374            
    375             # Clear any table flags that had been set, since they are only valid for a table that
    376             # immediately follows them.
    377            
    378 0           $wds->{pending_columns} = undef;
    379 0           $wds->{header_source_line} = undef;
    380 0           $wds->{table_no_header} = undef;
    381            
    382             # Indicate that we are now inside a list/table or sublist/subtable, and note that we are
    383             # about to start a new row. The value of 'listcol' can be either 0 or 2. The value
    384             # 0 means that we are at the start of a row, and 2 means that we are in the middle of a
    385             # row and must close it before we start another one.
    386            
    387 0           $wds->{listlevel}++;
    388 0           $wds->{listcol} = 0;
    389             }
    390            
    391             # If we have found =item inside a list that is not bulleted or numbered, then we must start
    392             # capturing the item text to be processed and output when the item is done.
    393            
    394             elsif ( $element_name =~ qr{ ^ item-text $ }xsi )
    395             {
    396             # If the value of listcol is not 0, then we have an unclosed table row or
    section and
    397             # must close it before we start the new item.
    398            
    399 0 0         if ( $wds->{listcol} > 0 )
    400             {
    401 0 0         if ( $wds->{options}{no_tables} )
        0          
    402             {
    403 0           $parser->add_output_text( qq{\n} );
    404             }
    405            
    406             elsif ( $wds->{listcol} == 2 )
    407             {
    408 0           $parser->add_output_text( qq{\n
    409             }
    410             }
    411            
    412             # Start capturing the item text. We will process it when we have it all.
    413            
    414 0           $parser->capture_output_text('item-text');
    415             }
    416            
    417             # If we have found a paragraph inside of a list, then append a

    tag to the list item we are

    418             # currently processing. The style of a paragraph inside a list is different from an ordinary
    419             # paragraph not in a list. We have defined a separate style for paragraphs inside sublists.
    420            
    421             elsif ( $element_name eq 'Para' && $wds->{listlevel} )
    422             {
    423 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_def2' : 'pod_def';
    424 0           my $attrs = qq{ class="$class"};
    425            
    426             # If we have a pending anchor, use it as the identifier for this paragraph.
    427            
    428 0 0         if ( $wds->{pending_anchor} )
    429             {
    430             $attrs .= qq{ id="$wds->{pending_anchor}"}
    431 0 0 0       if $wds->{pending_anchor} ne '' && $wds->{pending_anchor} ne '!';
    432 0           $wds->{pending_anchor} = undef;
    433             }
    434            
    435 0           $parser->add_output_text( qq{\n} );
    436            
    437             # Note that we are in the middle of a list item, in case this has not already been set.
    438            
    439 0           $wds->{listcol} = 2;
    440             }
    441            
    442             # If we have found an L<...> section, then we get all of its attributes with the start of the
    443             # element. So we can immediately generate an tag.
    444            
    445             elsif ( $element_name eq 'L' )
    446             {
    447 0           my $href; # this will hold the target of the link
    448            
    449             # The Pod::Simple code improperly handles certain kinds of links where there is also link
    450             # content. I am writing this comment a few months after I wrote the code, and can no longer
    451             # remember what the exact problem was. At any rate, we ignore Pod::Simple's attempt to
    452             # parse the link content out from the link target and do it ourselves from the raw contents
    453             # which Pod::Simple fortunately provides us.
    454            
    455 0 0         if ( $attr_hash->{raw} =~ qr{ ^ (?: [^|]* [|] )? (.*) }xs )
    456             {
    457 0           $href = $1;
    458             }
    459            
    460             # If there is no link content, then the link target will be given by the "to" attribute
    461             # passed to us by Pod::Simple. Unless the link started with "/", in which case it will be
    462             # in the "section" attribute instead.
    463            
    464             else
    465             {
    466 0   0       $href = $attr_hash->{to} || "/$attr_hash->{section}";
    467             }
    468            
    469             # If a url_formatter attribute was provided to this formatter, then call it on the link
    470             # target value. This will translate something that looks like "node:a/b" into a
    471             # site-relative URL that links to the documentation page for node a/b; it will translate
    472             # something that looks like "op:a/b?foo" into a site-relative URL that will call the
    473             # operation a/b with argument foo. The exact form of the URL will depend on which features
    474             # are set for this data service.
    475            
    476 0           my $url_gen = $wds->{options}{url_formatter};
    477 0 0 0       $href = $url_gen->($href) if $href && ref $url_gen eq 'CODE';
    478            
    479             # If the "content-implicit" flag was set by Pod::Simple, it means that there is no link
    480             # content and that we should use the link target as the content.
    481            
    482 0 0         $wds->{override_text} = $href if $attr_hash->{'content-implicit'};
    483            
    484             # If the link target looks like an external URL (in other words, if it is not
    485             # site-relative) then add the attribute 'target="_blank"' so that it will open in a new
    486             # window or tab when activated.
    487            
    488 0           my $attrs = '';
    489 0 0         $attrs = qq{ target="_blank"} if $href =~ qr{ ^ \w+:// }xsi;
    490            
    491             # Output the tag.
    492            
    493 0           $parser->add_output_text( qq{} );
    494             }
    495            
    496             # If we have found one of the other formatting sections, then we generate a tag with
    497             # the appropriate class. The tricky part is that we are re-purposing part of the Pod spec by
    498             # defining C> to indicate the pod_term style class and B> to indicate the
    499             # pod_term2 style class. Otherwise, the style class will be pod_B, pod_I, etc.
    500            
    501             elsif ( $element_name =~ qr{ ^ ( B | I | F | C | S ) $ }xs )
    502             {
    503 0           my $code = $1;
    504            
    505             # I tried using , , etc. but decided to give it up and just us with the
    506             # appropriate style classes.
    507            
    508 0           my $tag = 'span';
    509            
    510             # If the output generated so far ends in or and
    511             # this section has the opposite formatting code, then rewrite that tag to have the appropriate
    512             # style class. Then set the 'no_span' flag to indicate that we should not generate
    513             # at the end of this section because the enclosing element will already be generating it.
    514            
    515 0 0         if ( $wds->{body}[0] =~ qr{<(?:span|strong|em|code) class="pod_(.)">$}s )
    516             {
    517 0           my $enclosing = $1;
    518            
    519 0 0 0       if ( $enclosing eq 'B' && $code eq 'C' )
        0 0        
    520             {
    521 0           $wds->{body}[0] =~ s{<[^>]+>$}{}s;
    522 0           $wds->{no_span} = 1;
    523             }
    524            
    525             elsif ( $enclosing eq 'C' && $code eq 'B' )
    526             {
    527 0           $wds->{body}[0] =~ s{<[^>]+>$}{}s;
    528 0           $wds->{no_span} = 1;
    529             }
    530             }
    531            
    532             # Otherwise, just add a new tag.
    533            
    534             else
    535             {
    536 0           $parser->add_output_text( qq{<$tag class="pod_$code">} );
    537             }
    538             }
    539            
    540             # If we have found an X<...> or Z<...> section, then we capture the text inside and throw it
    541             # away.
    542            
    543             elsif ( $element_name =~ qr{ ^ ( X | Z ) $ }xs )
    544             {
    545 0           $parser->capture_output_text('xz');
    546             }
    547            
    548             # If we have found a data paragraph, we will need to process its contents specially. Note
    549             # that Pod::Simple uses the element name 'for' to indicate data paragraphs even if they are
    550             # actually bounded by =begin and =end.
    551            
    552             elsif ( $element_name eq 'for' )
    553             {
    554             # Ignore any colon at the beginning of the data section identifier. Pod::Simple will have
    555             # already figured out whether or not it is supposed to be parsing the contents for Pod
    556             # elements, based on the presence or absence of a colon. There is nothing we need to do
    557             # differently.
    558            
    559 0           my $identifier = $attr_hash->{target};
    560 0           $identifier =~ s{^:}{};
    561            
    562             # Start capturing the data paragraph text.
    563            
    564 0           $parser->capture_output_text($identifier);
    565            
    566             # If the identifier is 'wds_nav', set a flag to indicate that the contents should be
    567             # processed specially. This data section is processed as Pod text even if it is not
    568             # preceded by a colon (see the call to accept_target_as_text in &new).
    569            
    570 0 0         if ( $identifier eq 'wds_nav' )
    571             {
    572 0           $wds->{in_wds_nav} = 1;
    573             }
    574             }
    575            
    576             # Any other elements passed to us by Pod::Simple are ignored. This might not be the best
    577             # approach, but it is probably better than displaying error messages. If Pod::Simple ever
    578             # changes the set of elements that it sends to this subroutine, then (a) this module will have
    579             # to be updated, and (b) any users who update Pod::Simple but not Web::DataService will be
    580             # screwed.
    581            
    582 0           my $a = 1; # we can stop here when debugging
    583             }
    584              
    585              
    586             # _handle_element_end ( parser, element_name, attr_hash )
    587             #
    588             # This method will be called automatically by the Pod::Simple parsing code at the end of each Pod
    589             # element that it recognizes.
    590              
    591             sub _handle_element_end {
    592            
    593 0     0     my ($parser, $element_name, $attr_hash) = @_;
    594            
    595             # Shortcut access the object fields for this subclass.
    596            
    597 0           my $wds = $parser->{wds_fields};
    598            
    599             # If debugging mode is turned on, emit debugging output.
    600            
    601 0 0         if ( $wds->{options}{debug} )
    602             {
    603 0           print STDERR "END $element_name";
    604            
    605 0           foreach my $k (keys %$attr_hash)
    606             {
    607 0           print STDERR " $k=" . $attr_hash->{$k};
    608             }
    609            
    610 0           print STDERR "\n";
    611             }
    612            
    613             # If we are done processing an ordinary paragraph, generate the

    tag. This is the same whether or
    614             # not the paragraph is in a list/table.
    615            
    616 0 0         if ( $element_name eq 'Para' )
        0          
        0          
        0          
        0          
        0          
        0          
        0          
        0          
        0          
        0          
        0          
    617             {
    618 0           $parser->add_output_text( qq{

    } );
    619             }
    620            
    621             # If we are done processing a verbatim paragraph, generate the tag.
    622            
    623             elsif ( $element_name eq 'Verbatim' )
    624             {
    625 0           $parser->add_output_text( qq{} );
    626             }
    627            
    628             # If we are done processing a data paragraph, the 'Data' element start/end will be surrounded by
    629             # a 'for' element start/end. We handle any necessary processing on the latter.
    630            
    631             elsif ( $element_name eq 'Data' )
    632             {
    633             # nothing to do here
    634             }
    635            
    636             # If we are done processing a =head paragraph, then we can retrieve the captured paragraph
    637             # text and can generate

    ,

    , etc. as appropriate. The default identifier for a

    638             # heading is the heading text, which allows for URL fragments targeting a documentation
    639             # section, i.e. #PARAMETERS.
    640            
    641             elsif ( $element_name =~ qr{ ^ head ( \d ) $ }xsi )
    642             {
    643 0           my $level = $1;
    644 0           my $attrs = qq{ class="pod_heading"};
    645            
    646             # Finish capturing the paragraph text.
    647            
    648 0           my ($heading_text) = $parser->end_capture_text;
    649            
    650             # If we have a pending anchor, use it as the identifier for this heading unless the value
    651             # is '!'. Note that a value of '!' will result in no identifier at all for this heading.
    652            
    653 0 0 0       if ( $wds->{pending_anchor} )
        0          
    654             {
    655 0 0         $attrs .= qq{ id="$wds->{pending_anchor}"} if $wds->{pending_anchor} ne '!';
    656 0           $wds->{pending_anchor} = undef;
    657             }
    658            
    659             # Otherwise, use the heading text as the identifier.
    660            
    661             elsif ( $heading_text ne '' && ! $wds->{in_wds_nav} )
    662             {
    663 0           $attrs .= qq{ id="$heading_text"}
    664             }
    665            
    666             # Generate the heading tag, text, and closing tag.
    667            
    668 0           $parser->add_output_text( qq{\n\n} );
    669 0           $parser->add_output_text( $heading_text );
    670 0           $parser->add_output_text( qq{} );
    671             }
    672            
    673             # If we are done processing a bulleted or numbered list, we simply need generate a closing tag
    674             # and decrement the list level.
    675            
    676             elsif ( $element_name =~ qr{ ^ over-(bullet|number) $ }xs )
    677             {
    678 0 0         my $tag = $1 eq 'bullet' ? 'ul' : 'ol';
    679 0           $parser->add_output_text( qq{\n\n} );
    680 0           $wds->{listlevel}--;
    681             }
    682            
    683             # If we are done processing a bulleted or numbered list item, we simply need to generate a
    684             # tag.
    685            
    686             elsif ( $element_name =~ qr{ ^ item-(bullet|number) $ }xs )
    687             {
    688 0           $parser->add_output_text( qq{} );
    689             }
    690            
    691             # If we are done processing a list that not bulleted or numbered, then we need to generate the
    692             # appropriate closing tag. But if 'listcol' is greater than zero then we are in the middle of
    693             # a list item and need to close it first. We then decrement the list level.
    694            
    695             elsif ( $element_name eq 'over-text' )
    696             {
    697             # Generate the appropriate closing tag for the list, and for the last item if it is still
    698             # unclosed.
    699            
    700 0 0         if ( $wds->{options}{no_tables} )
    701             {
    702 0 0         $parser->add_output_text( qq{} ) if $wds->{listcol} > 0;
    703 0           $parser->add_output_text( qq{\n\n} );
    704             }
    705            
    706             else
    707             {
    708 0 0         $parser->add_output_text( qq{\n
    709 0           $parser->add_output_text( qq{\n\n
    } ); 710             711             # Remove the top element from the table definition stack. If we just ended a sub-table, 712             # then this will return us to the definition of the enclosing table. 713             714 0           shift @{$wds->{table_def}};   0             715             } 716             717             # Decrement the list level. If we are still inside a list, then set listcol to 2 because 718             # we must still be inside a list item. 719             720 0           $wds->{listlevel}--; 721 0 0         $wds->{listcol} = $wds->{listlevel} > 0 ? 2 : 0; 722             } 723             724             # If we are done processing a list item that is not bulleted or numbered, then retrieve the 725             # captured item text. Use this to generate either a
    ...
    or to fill in all but the 726             # last table column. Any ordinary or verbatim paragraphs following this =item will go into 727             # either a
    ...
    or into the last table column. 728             729             elsif ( $element_name eq 'item-text' ) 730             { 731 0           my ($item_text) = $parser->end_capture_text; 732             733             # See if we have a table definition available. 734             735 0           my $table_def = $wds->{table_def}[0]; 736             737             # If we do, then handle the item text according to the table definition. Items will 738             # generally be of the form "=item a / b / c" or "=item a / b / c ( d )". The values a, b, 739             # c, d will be used to fill in all but the last column of the table. Any paragraphs 740             # following the =item will be placed into the last table column. This allows us to 741             # generate multi-column tables defining parameter values and output field values, while 742             # still following the basic Pod specification. The Pod text could be turned into a manual 743             # page instead, or into some other format, in which case the value lists in the item text will 744             # still be intelligible. 745             746 0 0         if ( ref $table_def->{columns} eq 'ARRAY' ) 747             { 748 0           my $last; 749             750             # If the item text looks like ... ( d ) then split off d. 751             752 0 0         if ( $item_text =~ qr{ (.*) \s+ [(] \s+ ( [^)]+ ) \s+ [)] }xs ) 753             { 754 0           $item_text = $1; 755 0           $last = $2; 756             } 757             758             # If the rest of the item text looks like a / b / c, then split out this list of 759             # components. Add the item split off above, if there is one. 760             761 0           my @values = split qr{ \s+ [|/] \s+ }xs, $item_text; 762 0 0 0       push @values, $last if defined $last && $last ne ''; 763             764             # If we are expecting a subheader (because this is the first =item and the definition 765             # of the first column ended with the suffix /n where n is an integer) then the list of 766             # values we just computed will be the labels for the subheader cells. 767             768 0 0         if ( $table_def->{expect_subheader} ) 769             { 770             # Clear the expect_subheader flag. The first =item encountered in the list should 771             # give the subheaders, and the rest are processed normally. 772             773 0           $table_def->{expect_subheader} = undef; 774             775             # Set the style class differently for a subtable than for a top-level table. 776             777 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_th2' : 'pod_th'; 778             779             # Add the subheader row with one cell for each of the values that we split out 780             # above. If there are not enough values, the remaining cells will be empty. If 781             # there are too many values, the extras will be ignored. 782             783 0           $parser->add_output_text( qq{\n\n} ); 784             785 0           foreach my $i ( 0 .. $table_def->{n_subs} - 1 ) 786             { 787 0 0         my $v = @values ? shift(@values) : ''; 788 0           $parser->add_output_text( qq{$v} ); 789             } 790             791             # Close the subheader row. We set listcol to 0 to indicate that we are ready to 792             # start a new row. 793             794 0           $parser->add_output_text( qq{\n\n\n} ); 795 0           $wds->{listcol} = 0; 796             } 797             798             # Otherwise, we process the item text as an ordinary table row. 799             800             else 801             { 802             # Generate a tag to open the row. 803             804 0           $parser->add_output_text( qq{\n\n} ); 805             806             # Get a list of the column definitions, and discard the last one. The last column 807             # will be filled with whatever paragraphs follow this =item. 808             809 0           my @cols = @{$table_def->{columns}};   0             810 0           pop @cols; 811             812 0           foreach my $col ( @cols ) 813             { 814 0 0         my $v = @values ? shift(@values) : ''; 815 0           my $attrs = ''; 816             817             # If there is a pending anchor, use its value as the identifier of the first 818             # table cell. The anchor is then cleared, to make sure that it appears only 819             # on the first cell. 820             821 0 0         if ( $wds->{pending_anchor} ) 822             { 823 0 0         $attrs .= qq{ id="$wds->{pending_anchor}"} if $wds->{pending_anchor} ne '!'; 824 0           $wds->{pending_anchor} = undef; 825             } 826             827             # If this column has the 'term' flag set, then give it the "pod_term" or 828             # "pod_term2" style depending upon whether this is a top-level list or a 829             # sublist. 830             831 0 0         if ( $col->{term} ) 832             { 833 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_term2' : 'pod_term'; 834 0           $attrs .= qq{ class="$class"}; 835             } 836             837             # Otherwise, give it the "pod_def" or "pod_def2" style. 838             839             else 840             { 841 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_def2' : 'pod_def'; 842 0           $attrs .= qq{ class="$class"}; 843             } 844             845             # Generate the table cell for this column. 846             847 0           $parser->add_output_text( qq{$v\n} ); 848             } 849             850             # Now generate the opening tag for the final cell in the row. Any subsequent 851             # paragraphs until the next =item or =back will go into this cell. We set 852             # 'listcol' to 2 indicating that we are in the middle of a table row that will 853             # need to be closed. 854             855 0 0         my $class = $wds->{listlevel} > 1 ? 'pod_def2' : 'pod_def'; 856 0           $parser->add_output_text( qq{} ); 857 0           $wds->{listcol} = 2; 858             } 859             } 860             861             # If we do not have a table definition, then we either generate a
    ,
    pair or a 862             # table row with two clumns. This latter is the default for text lists when tables are 863             # being generated. The first column gets the item text, and has the "pod_term" style. The 864             # second gets any subsequent paragraphs, and has the "pod_def" style. 865             866             else 867             { 868 0 0         my $termclass = $wds->{listlevel} > 1 ? 'pod_term2' : 'pod_term'; 869 0 0         my $defclass = $wds->{listlevel} > 1 ? 'pod_def2' : 'pod_def'; 870 0           my $attrs = ''; $attrs .= qq{ class="$termclass"};   0             871             872             # If we have a pending anchor, use its value as the identifier of this list item. 873             874 0 0         if ( $wds->{pending_anchor} ) 875             { 876 0 0         $attrs .= qq{ id="$wds->{pending_anchor}"} if $wds->{pending_anchor} ne '!'; 877 0           $wds->{pending_anchor} = undef; 878             } 879             880             # If we are not generating tables, then output a
    ...
    . 881             882 0 0         if ( $wds->{options}{no_tables} ) 883             { 884 0           $parser->add_output_text( qq{\n\n$item_text\n
    } ); 885             } 886             887             # If we are generating tables, then output .... 888             889             else 890             { 891 0           $parser->add_output_text( qq{\n\n$item_text\n} ); 892             } 893             894             # In either case, set 'listcol' to 2 indicating that we are in the middle of a list 895             # item that will need to be closed. 896             897 0           $wds->{listcol} = 2; 898             } 899             } 900             901             # If we are done processing an L<...> section, we just need to add the tag. If we had any 902             # override text for this section, clear it now just in case it was not cleared already. 903             904             elsif ( $element_name eq 'L' ) 905             { 906 0           $parser->add_output_text( qq{} ); 907 0           $wds->{override_text} = undef; 908             } 909             910             # If we are done processing a formatting section, add the tag unless the no_span flag 911             # was set. 912             913             elsif ( $element_name =~ qr{ ^ ( B | I | F | C | S ) $ }xs ) 914             { 915 0 0         if ( $wds->{no_span} ) 916             { 917 0           $wds->{no_span} = undef; 918             } 919             920             else 921             { 922 0           $parser->add_output_text( qq{} ); 923             } 924             } 925             926             # If we are dong processing an X<...> or Z<...> section, then discard the captured text. 927             928             elsif ( $element_name =~ qr{ ^ ( X | Z ) $ }xs ) 929             { 930 0           $parser->end_capture_text; 931             } 932             933             # If we are done processing a data paragraph, then we must handle the captured text specially 934             # depending on the data section identifier. This is used to implement special directives for 935             # Web::DataService documentation pages, and to include html text and markup. Note that Pod::Simple 936             # uses the 'for' element to indicate data paragraphs even if they were actually delimited by 937             # =begin and =end. 938             939             elsif ( $element_name eq 'for' ) 940             { 941             # Retrieve the text and identifier of this data section. 942             943 0           my ($body, $identifier) = $parser->end_capture_text; 944             945             # If the identifier is 'wds_title', then remember this value and use it for the HTML page 946             # title. For example: 947             # 948             # =for wds_title The Title For This Page 949             950 0 0 0       if ( $identifier eq 'wds_title' )     0 0             0               0               0               0               0           951             { 952 0           $wds->{title} = $body; 953             } 954             955             # If the identifier is 'wds_anchor', then remember the value and use it as the "id" 956             # attribute for the next major document element (heading, table cell, list item, or 957             # paragraph) that is generated. If the value is '!', then generate no identifier at 958             # all. This last can be used to remove the automatically generated identifier for a 959             # heading. For example, the following will generate

    An example of... 960             # 961             # =for wds_anchor example 962             # 963             # An example of... 964             965             elsif ( $identifier eq 'wds_anchor' ) 966             { 967 0           $wds->{pending_anchor} = $body; 968             } 969             970             # If the identifier is 'wds_table_header' or 'wds_table_no_header', then the value should 971             # be a list of table column definitions separated by ' | '. This directive is only valid 972             # immediately preceding an =over paragraph that starts a text list, and specifies how the 973             # contents of the list should be mapped into table columns. In general, the text of each 974             # =item will be used to fill all but the last column of one table row, and any subsequent 975             # paragraphs will fill the last column. If the identifier is 'wds_table_no_header' then no 976             # header row will be generated but the column definitions are still applied to the table 977             # body. For example: 978             # 979             # =for wds_table_header Value* | Definition 980             # 981             # =over 982             # 983             # =item foo 984             # 985             # Foo speciifes bar. 986             987             elsif ( $identifier =~ qr{ ^ wds_table_ (no_)? header $ }xs ) 988             { 989 0 0         $wds->{table_no_header} = 1 if $1; 990 0           my @columns = split qr{ \s+ [|] \s+ }xs, $body; 991 0           $wds->{pending_columns} = \@columns; 992 0           $wds->{header_source_line} = $attr_hash->{start_line}; 993             } 994             995             # If the identifier is 'wds_nav' then we just output the captured text without further 996             # processing. The contents of this section are processed as Pod (see the call to 997             # accept_target_as_text in &new), so the major effect of this section is that the content 998             # will be ignored by all other Pod formatters. This allows for content that will be be 999             # translated to HTML by this formatter and will be ignored when the Pod is used for any other 1000             # purpose. This allows for navigational links that are only relevant in the context of web 1001             # pages. For example: 1002             # 1003             # =begin wds_nav 1004             # 1005             # =head3 L

    1006             # 1007             # =end wds_nav 1008             1009             elsif ( $identifier eq 'wds_nav' ) 1010             { 1011 0           $parser->add_output_text( $body ); 1012             1013             # Clear the 'in_wds_nav' flag, which was set at the start of this element. This flag 1014             # is currently only used to suppress the automatic generation of "id" attributes for 1015             # headings. 1016             1017 0           $wds->{in_wds_nav} = undef; 1018             } 1019             1020             # If the identifier is 'wds_pod', then the value should be either 'on' or 'off'. If it is 1021             # 'on', then the 'suppress_output' flag is set. If 'off', the flag is cleared. All output 1022             # is suppressed while this flag is true. The purpose of this directive is to indicate 1023             # content that should be ignored by this formatter, but will be processed normally by 1024             # other formatters. In this sense, it is the inverse of wds_nav. This can, for example, be 1025             # used to indicate Pod content to be substituted for an 'html' section. For example: 1026             # 1027             # =begin html 1028             # 1029             # 1030             # 1031             # =end html 1032             # 1033             # =for wds_pod on 1034             # 1035             # =head1 Data Service Documentation 1036             # 1037             # =for wds_pod off 1038             1039             elsif ( $identifier eq 'wds_pod' ) 1040             { 1041             # If the value is 'on', then set 'suppress_output'. If wds_pod is already "on" 1042             # then display an error message. This directive is not meant to be nested. 1043             1044 0 0         if ( lc $body eq 'on' )     0           1045             { 1046 0 0         if ( $wds->{wds_pod_start_line} ) 1047             { 1048 0           my $line = $wds->{wds_pod_start_line}; 1049 0           push @{$wds->{errors}}, [ $attr_hash->{start_line}, "you already turned 'wds_pod' on at line '$line'" ];   0             1050             } 1051             1052 0           $wds->{wds_pod_start_line} = $attr_hash->{start_line}; 1053 0           $wds->{suppress_output} = 1; 1054             } 1055             1056             elsif ( lc $body eq 'off' ) 1057             { 1058 0           $wds->{wds_pod_start_line} = undef; 1059 0           $wds->{suppress_output} = undef; 1060             } 1061             1062             else 1063             { 1064 0           push @{$wds->{errors}}, [ $attr_hash->{start_line}, "unrecognized value '$body' for data section 'wds_pod'" ];   0             1065             } 1066             } 1067             1068             # If the identifier is 'html', then output the captured text unchanged. If the HTML 1069             # contains links to site-local resources, then the template from which the documentation page 1070             # is generated should use the URL() function to generate proper site-relative URLs from a 1071             # base specification. For example: 1072             # 1073             # =begin html 1074             # 1075             # 1076             # 1077             # =end html 1078             1079             elsif ( $identifier eq 'html' ) 1080             { 1081             # my $url_gen = $wds->{options}{url_formatter}; 1082             1083             # if ( ref $url_gen eq 'CODE' ) 1084             # { 1085             # $body =~ s{ (href|src)=" ([^"]+) " }{ $1 . '="' . $url_gen->($2) . '"' }xsie; 1086             # } 1087             1088 0           $parser->add_output_text( $body ); 1089             } 1090             1091             # If the identfier is 'comment' or 'wds_comment', discard the captured text. Similarly if 1092             # the identifier is 'wds_node'. This last is used to specify node attributes to go along 1093             # with the documentation page being formatted. It is read by the code in 1094             # Web::DataService::Document.pm, and can be ignored here. 1095             1096             elsif ( $identifier eq 'comment' || $identifier eq 'wds_comment' || $identifier eq 'wds_node' ) 1097             { 1098             # ignore content 1099             } 1100             1101             # If the identifier is anything else, display an error message. 1102             1103             else 1104             { 1105 0           push @{$wds->{errors}}, [ $attr_hash->{start_line}, "unrecognized data section identifier '$identifier'" ];   0             1106             } 1107             } 1108             1109 0           my $a = 1; # we can stop here when debugging 1110             } 1111               1112               1113             # _handle_text ( parser, text ) 1114             # 1115             # This method will be called automatically by the Pod::Simple parsing code for each run of text 1116             # found in the document (other than commands and formatting codes). 1117               1118             sub _handle_text { 1119             1120 0     0     my ($parser, $text) = @_; 1121             1122             # Shortcut access the object fields for this subclass. 1123             1124 0           my $wds = $parser->{wds_fields}; 1125             1126             # If debugging mode is turned on, emit debugging output. 1127             1128 0 0         if ( $wds->{options}{debug} ) 1129             { 1130 0           print STDERR "TEXT $text\n"; 1131             } 1132             1133             # If the 'override_text' field is set, discard the text that was recognized by Pod::Simple and 1134             # substitute that value. Then clear 'override_text'. 1135             1136 0 0         if ( defined $wds->{override_text} ) 1137             { 1138 0           $text = $wds->{override_text}; 1139 0           $wds->{override_text} = undef; 1140             } 1141             1142             # All text that is not part of an 'html' data section should be HTML-escaped before being 1143             # output. The latter will be passed through unprocessed. 1144             1145 0 0         unless ( $wds->{target}[0] eq 'html' ) 1146             { 1147 0           $parser->html_escape(\$text); 1148             } 1149             1150             # Add this text to the output stream. 1151             1152 0           $parser->add_output_text( $text ); 1153             } 1154               1155               1156             # add_output_text ( parser, text ) 1157             # 1158             # This method adds the specified text to the current output stream. The output stream starts out 1159             # as 'body', but can be diverted using the capture_output_text method. All text added to 'body' 1160             # will be part of the eventual output. Text diverted to other targets may be captured for further 1161             # processing before eventually being added to 'body', or in some cases just discarded. 1162             # 1163             # Note: any text added to 'body' that hasn't already passed through _handle_text must be 1164             # HTML-escaped first unless you are SURE that the contents are already proper HTML. 1165               1166             sub add_output_text { 1167             1168 0     0 0   my $wds = $_[0]{wds_fields}; 1169             1170             # Ignore this text if the 'suppress_output' flag is true and output has not been redirected 1171             # away from the default 'body' output stream. 1172             1173 0 0 0       return if $wds->{suppress_output} and @{$wds->{body}} == 1;   0             1174             1175             # Otherwise, add this output to the current output stream. 1176             1177 0           $wds->{body}[0] .= $_[1]; 1178             } 1179               1180               1181             # capture_output_text ( target ) 1182             # 1183             # Redirect output to a different output stream. This is accomplished by pushing an empty string 1184             # onto the 'body' stack and adding the specified target name to the 'target' stack. Subsequent 1185             # output will be added to this empty string until 'end_capture_text' is called, which will pop and 1186             # return the collected text. 1187               1188             sub capture_output_text { 1189             1190 0     0 0   my ($parser, $target) = @_; 1191             1192 0           unshift @{$parser->{wds_fields}{body}}, '';   0             1193 0           unshift @{$parser->{wds_fields}{target}}, $target;   0             1194             } 1195               1196               1197             # end_capture_text ( ) 1198             # 1199             # Pop the top output stream off the 'body' stack and return its collected text. Also pop and 1200             # return the top value from the 'target' stack. Whatever output stream is next in the stack will 1201             # then become the current one. 1202               1203             sub end_capture_text { 1204             1205 0     0 0   my ($parser) = @_; 1206             1207 0           my $text = shift @{$parser->{wds_fields}{body}};   0             1208 0           my $target = shift@{$parser->{wds_fields}{target}};   0             1209             1210 0           return ($text, $target); 1211             } 1212               1213               1214             # current_target ( ) 1215             # 1216             # Report the top value on the 'target' stack, indicating which output stream any output is 1217             # currently going to. 1218               1219             sub current_target { 1220             1221 0     0 0   my ($parser) = @_; 1222             1223 0           return $parser->{wds_fields}{target}[0]; 1224             } 1225               1226               1227             # html_escape ( text_ref ) 1228             # 1229             # HTML-escape the contents of the specified scalar ref. 1230               1231             our (%HTML_ENTITY) = ( '&' => '&', '>' => '>', '<' => '<', q{"} => '"', 1232             q{'} => ''', q{`} => '`', '{' => '{', '}' => '}' ); 1233               1234             sub html_escape { 1235             1236 0     0 0   my ($parser, $text_ref) = @_; 1237             1238 0 0         $$text_ref =~ s/([&><"'`{}])/$HTML_ENTITY{$1}/ge #' for poor editors   0             1239             if defined $$text_ref; 1240             } 1241               1242               1243             # error_output ( ) 1244             # 1245             # Collect up all of the error messages (if any) that have been generated during formatting of this 1246             # content, and return it as an HTML formatted list. 1247               1248             sub error_output { 1249             1250 0     0 0   my ($parser) = @_; 1251             1252 0           my $wds = $parser->{wds_fields}; 1253             1254 0           my $error_output = ''; 1255 0           my @error_lines; 1256             1257 0           foreach my $error ( @{$wds->{errors}} )   0             1258             { 1259 0           push @error_lines, qq{
  • Line $error->[0]: $error->[1]
  • \n}; 1260             } 1261             1262 0           my $errata = $parser->errata_seen; 1263             1264 0 0 0       if ( ref $errata eq 'HASH' && %$errata ) 1265             { 1266 0           my @lines = sort { $a <=> $b } keys %$errata;   0             1267             1268 0           foreach my $line ( @lines ) 1269             { 1270 0           foreach my $message ( @{$errata->{$line}} )   0             1271             { 1272 0 0         next if $message =~ qr{ alternative \s text .* non-escaped \s [|] }xs; 1273             1274 0           push @error_lines, qq{
  • line $line: $message
  • \n}; 1275             } 1276             } 1277             } 1278             1279 0 0         if ( @error_lines ) 1280             { 1281 0           $error_output .= "

    Errors were found in the source for this page:

    \n\n
      \n"; 1282 0           $error_output .= $_ foreach @error_lines; 1283 0           $error_output .= "
    \n\n"; 1284             } 1285             1286 0           return $error_output; 1287             } 1288               1289               1290             # output ( ) 1291             # 1292             # Generate a complete HTML page from the Pod that has been processed so far. This will include a 1293             # header, followed by the generated body text, followed by a list of errors (if any occurred) and 1294             # a foter. Return this text. 1295               1296             sub output { 1297             1298 0     0 1   my ($parser) = @_; 1299             1300 0           my $wds = $parser->{wds_fields}; 1301             1302             # If a header and/or footer were specified when this formatter was instantiated, use them. 1303             1304 0           my $header = $wds->{options}{html_header}; 1305 0           my $footer = $wds->{options}{html_footer}; 1306             1307             # If Pod::Simple was able to determine the encoding of this data, use that value. Otherwise, 1308             # default to ISO-8859-1. 1309             1310 0   0       my $encoding = $parser->detected_encoding() || 'ISO-8859-1'; 1311             1312             # If a stylesheet link was specified when this formatter was instantiated, use it. 1313             1314 0           my $css = $wds->{options}{css}; 1315             1316             # If no html header was provided, generate a default one. 1317             1318 0 0         unless ( $header ) 1319             { 1320 0   0       my $title = $wds->{title} || $wds->{options}{page_title}; 1321             1322 0           $header = ""; 1323 0 0 0       $header .= "$title" if defined $title && $title ne ''; 1324 0           $header .= "\n"; 1325 0           $header .= "\n"; 1326 0 0         $header .= "\n" if $css; 1327 0           $header .= "\n\n"; 1328             1329 0           $header .= "\n\n"; 1330 0           $header .= "\n"; 1331             } 1332             1333             # If errors occurred, list them now. 1334             1335 0           my $error_output = $parser->error_output; 1336             1337             # If no html footer was provided, generate a default one. 1338             1339 0 0         unless ( $footer ) 1340             { 1341 0           $footer = "\n\n"; 1342 0           $footer .= "\n"; 1343             } 1344             1345 0           return $header . $parser->{wds_fields}{body}[0] . $error_output . $footer; 1346             } 1347               1348               1349             1; 1350               1351               1352             =head1 NAME 1353               1354             Web::DataService::PodParser - Pod-to-HTML formatter for Web::DataService 1355               1356             =head1 SYNOPSIS 1357               1358             This module is a subclass of Pod::Simple, providing an engine that can parse Pod and generate 1359             HTML for use in generating data service documentation pages. It is used as follows: 1360               1361             my $parser = Web::DataService::PodParser->new({ target => 'html', ... }); 1362             1363             $parser->parse_string_document($doc_string); 1364             1365             my $doc_html = $parser->output; 1366               1367             Several custom data sections are recognized, allowing for directives specific to the 1368             Web::DataService system. In addition, formatting codes and L<...> sections are treated specially. 1369               1370             =head1 METHODS 1371               1372             This module provides the following methods: 1373               1374             =head2 new ( options ) 1375               1376             This class method creates a new instance of the parser. The argument must be an options hash, 1377             including any of the following keys: 1378               1379             =head3 target 1380               1381             Specifies the format into which the Pod should be translated. Currently, the only value accepted 1382             is 'html'. 1383               1384             =head3 url_formatter 1385               1386             The value of this attribute should be a code ref. This subroutine will be called once for each URL 1387             in the formatted document, and the return value will be substituted for that URL. 1388               1389             =head3 css 1390               1391             The value of this attribute should be the URL of a stylesheet, which will be included via an HTML 1392             tag. This URL will be passed through the url_formatter if one is specified. 1393               1394             =head3 html_header 1395               1396             If specified, this string will be included at the beginning of the HTML output. It should start 1397             with and end with a tag. If not specified, this module will generate a header 1398             automatically. 1399               1400             =head3 html_footer 1401               1402             If specified, this string will be included at the end of the HTML output. It should include 1403             and . If not specified, these two closing tags will be appended to the end of the 1404             formatted output. 1405               1406             =head3 no_tables 1407               1408             If this option has a true value, then Pod lists that are neither numbered nor bulleted will be 1409             rendered using the
    ,
    , and
    tags. Otherwise, and by default, they will be rendered as 1410             tables. 1411               1412             =head3 debug 1413               1414             If this option has a true value, then voluminous debugging output will be written to STDERR. 1415               1416             =head2 parse_string_document 1417               1418             This method takes a single argument, which must be a string containing Pod text. This text is 1419             parsed and formatted into HTML. 1420               1421             =head2 output 1422               1423             This method returns the formatted HTML content as a single string, which can then be sent as the 1424             body of a response message. 1425               1426             =head1 SYNTAX 1427               1428             This module is a subclass of Pod::Simple, and as such can handle all valid Pod syntax. Certain 1429             constructs are treated specially, as indicated here: 1430               1431             =head2 URLs 1432               1433             When this class is instantiated by Web::DataService::Documentation, it is passed a reference to a 1434             URL formatter. This is used to process all C<< L<...> >> sections according to the L 1435             URL specification|Web::DataService::Documentation.pod#Embedded-links>. 1436               1437             =head2 Text formatting 1438               1439             The formatting codes C<< B<...> >> and C<< C<...> >> can be mixed in order to format text 1440             according to the CSS styles "pod_term" and "pod_term2". This allows you to style parameter or 1441             field names in description text to match the occurrences of these terms in the first column of the 1442             parameter and field name tables. The sequence C<<< B> >>> will generate a text span with 1443             the style class "pod_term", while C<<< C> >>> will generate a span with hte style class 1444             "pod_term2". 1445               1446             =head2 Special directives 1447               1448             Several directives can be included in a Web::DataService documentation page through the use of 1449             particular data section identifiers. These either be delimited with C<=begin> and C<=end>, or 1450             specified using C<=for>. 1451               1452             =head3 wds_node 1453               1454             This directive specifies attributes for the Web::DataService node corresponding to the 1455             documentation page on which it appears. This means that you can create documentation pages in the 1456             appropriate directory and give them the necessary attributes without having to add C 1457             calls to your data service application code. For example: 1458               1459             =for wds_node title=General notes about this data service 1460               1461             =head3 wds_title 1462               1463             This directive specifies the title for the page. It overrides any value set using 1464             C<=for wds_node> or C. 1465               1466             =head3 $$$ 1467               1468             =head1 AUTHOR 1469               1470             mmcclenn "at" cpan.org 1471               1472             =head1 BUGS 1473               1474             Please report any bugs or feature requests to C, or through 1475             the web interface at L. I will be notified, and then you'll 1476             automatically be notified of progress on your bug as I make changes. 1477               1478             =head1 COPYRIGHT & LICENSE 1479               1480             Copyright 2014 Michael McClennen, all rights reserved. 1481               1482             This program is free software; you can redistribute it and/or modify it 1483             under the same terms as Perl itself. 1484               1485             =cut 1486