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 | 5 | |||||||
2 | 77 | |||||||
8 | ||||||||
9 | package Web::DataService::PodParser; | |||||||
10 | 2 | 2 | 1275 | use Pod::Simple; | ||||
2 | 58517 | |||||||
2 | 67 | |||||||
11 | 2 | 2 | 39 | use Carp (); | ||||
2 | 6 | |||||||
2 | 9757 | |||||||
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...