| 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 | 
| 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 | 15 | use strict; | ||||
| 2 | 6 | |||||||
| 2 | 83 | |||||||
| 8 | ||||||||
| 9 | package Web::DataService::PodParser; | |||||||
| 10 | 2 | 2 | 1264 | use Pod::Simple; | ||||
| 2 | 59501 | |||||||
| 2 | 74 | |||||||
| 11 | 2 | 2 | 21 | use Carp (); | ||||
| 2 | 5 | |||||||
| 2 | 9879 | |||||||
| 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 
 | |||||||
| 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 
 
 | |||||||
| 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 | |||||||
| 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 
 | |||||||
| 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} ) unless $table_def->{no_header}; | ||||
| 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 | |||||||
| 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 | |||||||
| 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 ,  | |||||||
| 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$tag>} ); | ||||||
| 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 | 
An example of...
 
  %>)