blib/lib/EBook/MOBI/Driver/POD.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 235 | 246 | 95.5 |
branch | 120 | 140 | 85.7 |
condition | 26 | 39 | 66.6 |
subroutine | 23 | 23 | 100.0 |
pod | 4 | 10 | 40.0 |
total | 408 | 458 | 89.0 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package EBook::MOBI::Driver::POD; | ||||||
2 | |||||||
3 | our $VERSION = '0.71'; # TRIAL VERSION (hook for Dist::Zilla::Plugin::OurPkgVersion) | ||||||
4 | |||||||
5 | 9 | 9 | 912 | use strict; | |||
9 | 14 | ||||||
9 | 222 | ||||||
6 | 9 | 9 | 41 | use warnings; | |||
9 | 15 | ||||||
9 | 254 | ||||||
7 | |||||||
8 | 9 | 9 | 39 | use Pod::Parser; | |||
9 | 14 | ||||||
9 | 402 | ||||||
9 | 9 | 9 | 4330 | use EBook::MOBI::Driver; | |||
9 | 21 | ||||||
9 | 341 | ||||||
10 | our @ISA = qw(Pod::Parser EBook::MOBI::Driver); | ||||||
11 | |||||||
12 | 9 | 9 | 5796 | use Text::Trim; | |||
9 | 4610 | ||||||
9 | 642 | ||||||
13 | 9 | 9 | 6695 | use HTML::Entities; | |||
9 | 51792 | ||||||
9 | 656 | ||||||
14 | 9 | 9 | 56 | use Carp; | |||
9 | 15 | ||||||
9 | 440 | ||||||
15 | 9 | 9 | 5219 | use EBook::MOBI::Converter; | |||
9 | 20 | ||||||
9 | 303 | ||||||
16 | 9 | 9 | 5381 | use IO::String; | |||
9 | 19475 | ||||||
9 | 492 | ||||||
17 | |||||||
18 | # This constants are used for internal replacement | ||||||
19 | # See interior_sequence() and _html_enc() for usage | ||||||
20 | 9 | 1086 | use constant { GT => '1_qpdhcn_thisStringShouldNeverOccurInInput', | ||||
21 | LT => '2_udtcqk_thisStringShouldNeverOccurInInput', | ||||||
22 | AMP => '3_pegjyq_thisStringShouldNeverOccurInInput', | ||||||
23 | COL => '4_jdkmso_thisStringShouldNeverOccurInInput', | ||||||
24 | QUO => '5_wuehlo_thisStringShouldNeverOccurInInput', | ||||||
25 | DQUO=> '6_jrgwpm_thisStringShouldNeverOccurInInput', | ||||||
26 | 9 | 9 | 53 | }; | |||
9 | 13 | ||||||
27 | |||||||
28 | # IMPORTANT | ||||||
29 | # This constant ist JUST a shortcut for readability. | ||||||
30 | # Because it is used in hases ($parser->{}) a + is used so that it is not | ||||||
31 | # interpreted as a string, so it looks like this: $parser->{+P . 'bla'} | ||||||
32 | # See http://perldoc.perl.org/constant.html for details | ||||||
33 | 9 | 9 | 40 | use constant { P => 'EBook_MOBI_Pod2Mhtml_' }; | |||
9 | 16 | ||||||
9 | 31702 | ||||||
34 | |||||||
35 | # Overwrite sub of Pod::Parser | ||||||
36 | sub begin_input { | ||||||
37 | 33 | 33 | 0 | 1561 | my $parser = shift; | ||
38 | 33 | 151 | my $out_fh = $parser->output_handle(); # handle for parsing output | ||||
39 | |||||||
40 | 33 | 206 | $parser->{+P . 'toMobi'} = EBook::MOBI::Converter->new(); | ||||
41 | |||||||
42 | 33 | 129 | $parser->debug_msg('found POD, parsing...'); | ||||
43 | |||||||
44 | # make sure that this variable is set to 0 at beginning | ||||||
45 | 33 | 67 | $parser->{+P . 'listcontext'} = 0; | ||||
46 | 33 | 51 | $parser->{+P . 'listjustwentback'} = 0; | ||||
47 | 33 | 693 | $parser->{+P . 'begin'} = ''; | ||||
48 | } | ||||||
49 | |||||||
50 | # Overwrite sub of Pod::Parser | ||||||
51 | sub end_input { | ||||||
52 | 33 | 33 | 0 | 1457 | my $parser = shift; | ||
53 | 33 | 109 | my $out_fh = $parser->output_handle(); | ||||
54 | |||||||
55 | 33 | 210 | $parser->debug_msg('...end of POD reached'); | ||||
56 | } | ||||||
57 | |||||||
58 | # Overwrite sub of Pod::Parser | ||||||
59 | # Here all POD commands starting with '=' are handled | ||||||
60 | sub command { | ||||||
61 | 181 | 181 | 0 | 11658 | my ($parser, $command, $paragraph, $line_num) = @_; | ||
62 | 181 | 606 | my $out_fh = $parser->output_handle(); # handle for parsing output | ||||
63 | |||||||
64 | # IMAGE is an unofficial command introduced by Renee, its very simple: | ||||||
65 | # =image PATH_TO_IMAGE ANY TEXT FOLLOWING UNTIL END OF LINE | ||||||
66 | 181 | 100 | 446 | if ($command eq 'image') { | |||
67 | |||||||
68 | 1 | 207 | |||||
69 | "WARNING: the unofficial POD command '=image' is deprecated.\n"; | ||||||
70 | |||||||
71 | # With this regex we parse the content, coming with the command. | ||||||
72 | # An example could look like this: | ||||||
73 | # $paragraph = '/home/user/picture.jpg Pic1: A Camel' | ||||||
74 | 1 | 50 | 9 | if ($paragraph =~ m/(\S*)\s*(.*)/g) { | |||
75 | 1 | 3 | my $img_path = $1; # e.g.: '/home/user/picture.jpg' | ||||
76 | 1 | 3 | my $img_desc = $2; # e.g.: 'A Camel' | ||||
77 | |||||||
78 | # We convert special chars to HTML, but only in the | ||||||
79 | # description, not in the path! | ||||||
80 | 1 | 4 | $img_desc = _html_enc($img_desc); | ||||
81 | |||||||
82 | # We print out an html image tag. | ||||||
83 | # e.g.: ![]() |
||||||
84 | # recindex is MOBI specific, its the number of the picture, | ||||||
85 | # pointing into the picture records of the Mobi-format | ||||||
86 | print $out_fh | ||||||
87 | 1 | 6 | $parser->{+P . 'toMobi'} | ||||
88 | ->image($img_path, $img_desc); | ||||||
89 | } | ||||||
90 | } | ||||||
91 | # POD compatible additional syntax to process images | ||||||
92 | # =for image PATH_TO_IMAGE ANY TEXT FOLLOWING UNTIL END OF LINE | ||||||
93 | 181 | 100 | 857 | if ($command eq 'for') { | |||
100 | |||||||
100 | |||||||
50 | |||||||
100 | |||||||
100 | |||||||
94 | |||||||
95 | # With this regex we parse the content, coming with the command. | ||||||
96 | # An example could look like this: | ||||||
97 | # $paragraph = 'image /home/user/picture.jpg Pic1: A Camel' | ||||||
98 | 1 | 50 | 7 | if ($paragraph =~ m/image\s*(\S*)\s*(.*)/g) { | |||
99 | 1 | 2 | my $img_path = $1; # e.g.: '/home/user/picture.jpg' | ||||
100 | 1 | 3 | my $img_desc = $2; # e.g.: 'A Camel' | ||||
101 | |||||||
102 | # We convert special chars to HTML, but only in the | ||||||
103 | # description, not in the path! | ||||||
104 | 1 | 3 | $img_desc = _html_enc($img_desc); | ||||
105 | |||||||
106 | # We print out an html image tag. | ||||||
107 | # e.g.: ![]() |
||||||
108 | # recindex is MOBI specific, its the number of the picture, | ||||||
109 | # pointing into the picture records of the Mobi-format | ||||||
110 | print $out_fh | ||||||
111 | 1 | 13 | $parser->{+P . 'toMobi'} | ||||
112 | ->image($img_path, $img_desc); | ||||||
113 | } | ||||||
114 | } | ||||||
115 | # Lists are a bit complex. The commands 'over', 'back' and 'item' | ||||||
116 | # are used. They exchange state over a global variable. This state | ||||||
117 | # is the listcontext, which can be: 'begin', 'ul' or 'ol'. | ||||||
118 | # OVER: starts the listcontext | ||||||
119 | elsif ($command eq 'over') { | ||||||
120 | |||||||
121 | # If we reach an 'over' command we can't do anything yet | ||||||
122 | # because we don't know if it will be an ordered or an | ||||||
123 | # unordered list! So we just set a global variable to 'begin', | ||||||
124 | # the first item call can then know that it is the first item | ||||||
125 | # and that it defines the rest of the list type. | ||||||
126 | |||||||
127 | 25 | 100 | 59 | if (exists $parser->{+P . 'list'}) { | |||
128 | # if we reach here, this means that this is a nested list | ||||||
129 | 9 | 17 | $parser->{+P . 'listlvl'}++; | ||||
130 | } | ||||||
131 | else { | ||||||
132 | 16 | 77 | $parser->{+P . 'listlvl'} = 0; | ||||
133 | } | ||||||
134 | |||||||
135 | |||||||
136 | 25 | 31 | push @{$parser->{+P . 'list'}} | ||||
25 | 1290 | ||||||
137 | , { | ||||||
138 | type => '' , | ||||||
139 | items => 0 , | ||||||
140 | state => 'over' , | ||||||
141 | contentInCmd => 1 , | ||||||
142 | blockquotes => 0 , | ||||||
143 | }; | ||||||
144 | } | ||||||
145 | # BACK: ends the listcontext | ||||||
146 | elsif ($command eq 'back') { | ||||||
147 | |||||||
148 | 25 | 36 | my $lvl = $parser->{+P . 'listlvl'}; | ||||
149 | |||||||
150 | # print end-tag according to the lists type | ||||||
151 | 25 | 100 | 85 | if ($parser->{+P . 'list'}->[$lvl]->{type} eq 'ul') { | |||
100 | |||||||
50 | |||||||
152 | 14 | 47 | print $out_fh '' . "\n"; # close last item | ||||
153 | 14 | 251 | print $out_fh '' . "\n"; | ||||
154 | } | ||||||
155 | elsif ($parser->{+P . 'list'}->[$lvl]->{type} eq 'ol') { | ||||||
156 | 6 | 20 | print $out_fh '' . "\n"; # close last item | ||||
157 | 6 | 109 | print $out_fh '' . "\n"; | ||||
158 | } | ||||||
159 | elsif | ||||||
160 | ($parser->{+P . 'list'}->[$lvl]->{type} | ||||||
161 | eq 'blockquote') { | ||||||
162 | # list is processed | ||||||
163 | # there where no items... | ||||||
164 | } | ||||||
165 | else { | ||||||
166 | carp 'POD parsing error. Undefined listcontext: ' | ||||||
167 | 0 | 0 | . $parser->{+P . 'listcontext'}; | ||||
168 | } | ||||||
169 | |||||||
170 | # DELETE if list is finish | ||||||
171 | 25 | 100 | 344 | if ($parser->{+P . 'listlvl'} == 0) { | |||
172 | 16 | 31 | delete $parser->{+P . 'listlvl'}; | ||||
173 | 16 | 47 | delete $parser->{+P . 'list'}; | ||||
174 | 16 | 347 | delete $parser->{+P . 'listjustwentback'}; | ||||
175 | } | ||||||
176 | else { | ||||||
177 | 9 | 34 | $parser->{+P . 'list'}->[$lvl]->{state} = 'back'; | ||||
178 | 9 | 15 | $parser->{+P . 'listlvl'}--; | ||||
179 | 9 | 481 | $parser->{+P . 'listjustwentback'} = 1; | ||||
180 | } | ||||||
181 | } | ||||||
182 | # CUT: end of POD | ||||||
183 | elsif ($command eq 'cut') { | ||||||
184 | # We don't need to do anything here... | ||||||
185 | } | ||||||
186 | elsif ($command eq 'begin') { | ||||||
187 | 1 | 50 | 6 | if ($paragraph =~ m/^\W*(\w+)\W*$/) { | |||
188 | 1 | 3 | my $begin_name = $1; | ||||
189 | 1 | 36 | $parser->{+P . 'begin'} = $begin_name; | ||||
190 | } | ||||||
191 | } | ||||||
192 | elsif ($command eq 'end') { | ||||||
193 | 1 | 50 | 6 | if ($paragraph =~ m/^\W*(\w+)\W*$/) { | |||
194 | 1 | 3 | my $end_name = $1; | ||||
195 | 1 | 50 | 3 | if ($parser->{+P . 'begin'} eq $end_name) { | |||
196 | 1 | 38 | $parser->{+P . 'begin'} = ''; | ||||
197 | } | ||||||
198 | else { | ||||||
199 | 0 | 0 | croak 'no nested begin/end supported'; | ||||
200 | } | ||||||
201 | } | ||||||
202 | } | ||||||
203 | # if we reach this ELSE, this means that the command can only be | ||||||
204 | # of type HEAD or ITEM (so they contain some text!) | ||||||
205 | else { | ||||||
206 | # first we remove all whitespace from begin and end of the title | ||||||
207 | 128 | 323 | trim $paragraph; | ||||
208 | # then we call interpolate so that 'interior_sequence' is called. | ||||||
209 | # this is replacing inline POD. | ||||||
210 | 128 | 7715 | my $expansion = $parser->interpolate($paragraph, $line_num); | ||||
211 | # then we replace special chars with HTML entities | ||||||
212 | 128 | 303 | $expansion = _html_enc($expansion); | ||||
213 | |||||||
214 | # Now we just need to print the text with the matching HTML tag | ||||||
215 | 128 | 100 | 622 | if ($command eq 'head0') { | |||
100 | |||||||
100 | |||||||
100 | |||||||
100 | |||||||
100 | |||||||
216 | # head0 gets only printed if the option is set! | ||||||
217 | # (head0 is not official POD standard) | ||||||
218 | 17 | 100 | 41 | if ($parser->head0_mode()) { | |||
219 | # before every head1 we insert a "mobi-pagebreak" | ||||||
220 | # but not before the first one! | ||||||
221 | 9 | 100 | 66 | 44 | if (exists $parser->{+P . 'firstH1passed'} | ||
66 | |||||||
222 | and exists $parser->{+P . 'pages'} | ||||||
223 | and $parser->{+P . 'pages'} | ||||||
224 | ) { | ||||||
225 | print $out_fh | ||||||
226 | 3 | 9 | $parser->{+P . 'toMobi'}->pagebreak(); | ||||
227 | } | ||||||
228 | else { | ||||||
229 | 6 | 12 | $parser->{+P . 'firstH1passed'} = 1; | ||||
230 | } | ||||||
231 | |||||||
232 | print $out_fh | ||||||
233 | 9 | 69 | $parser->{+P . 'toMobi'}->title($expansion, 1); | ||||
234 | } | ||||||
235 | } | ||||||
236 | elsif ($command eq 'head1') { | ||||||
237 | # we need to check to which level we translate the headings... | ||||||
238 | 38 | 100 | 87 | if ($parser->head0_mode()) { | |||
239 | print $out_fh | ||||||
240 | 9 | 28 | $parser->{+P . 'toMobi'}->title($expansion, 2); | ||||
241 | } | ||||||
242 | else { | ||||||
243 | # before every head1 we insert a "mobi-pagebreak" | ||||||
244 | # but not before the first one! | ||||||
245 | 29 | 100 | 66 | 139 | if (exists $parser->{+P . 'firstH1passed'} | ||
66 | |||||||
246 | and exists $parser->{+P . 'pages'} | ||||||
247 | and $parser->{+P . 'pages'} | ||||||
248 | ) { | ||||||
249 | print $out_fh | ||||||
250 | 2 | 18 | $parser->{+P . 'toMobi'}->pagebreak(); | ||||
251 | } | ||||||
252 | else { | ||||||
253 | 27 | 58 | $parser->{+P . 'firstH1passed'} = 1; | ||||
254 | } | ||||||
255 | |||||||
256 | print $out_fh | ||||||
257 | 29 | 161 | $parser->{+P . 'toMobi'}->title($expansion, 1); | ||||
258 | } | ||||||
259 | } | ||||||
260 | elsif ($command eq 'head2') { | ||||||
261 | # we need to check to which level we translate the headings... | ||||||
262 | 12 | 100 | 66 | if ($parser->head0_mode()) { | |||
263 | print $out_fh | ||||||
264 | 4 | 13 | $parser->{+P . 'toMobi'}->title($expansion, 3); | ||||
265 | } | ||||||
266 | else { | ||||||
267 | print $out_fh | ||||||
268 | 8 | 37 | $parser->{+P . 'toMobi'}->title($expansion, 2); | ||||
269 | } | ||||||
270 | } | ||||||
271 | elsif ($command eq 'head3') { | ||||||
272 | # we need to check to which level we translate the headings... | ||||||
273 | 1 | 50 | 10 | if ($parser->head0_mode()) { | |||
274 | print $out_fh | ||||||
275 | 0 | 0 | $parser->{+P . 'toMobi'}->title($expansion, 4); | ||||
276 | } | ||||||
277 | else { | ||||||
278 | print $out_fh | ||||||
279 | 1 | 12 | $parser->{+P . 'toMobi'}->title($expansion, 3); | ||||
280 | } | ||||||
281 | } | ||||||
282 | elsif ($command eq 'head4') { | ||||||
283 | # we need to check to which level we translate the headings... | ||||||
284 | 1 | 50 | 10 | if ($parser->head0_mode()) { | |||
285 | print $out_fh | ||||||
286 | 0 | 0 | $parser->{+P . 'toMobi'}->title($expansion, 5); | ||||
287 | } | ||||||
288 | else { | ||||||
289 | print $out_fh | ||||||
290 | 1 | 6 | $parser->{+P . 'toMobi'}->title($expansion, 4); | ||||
291 | } | ||||||
292 | } | ||||||
293 | # ITEM: lists items | ||||||
294 | elsif ($command eq 'item') { | ||||||
295 | |||||||
296 | # If we are still in listcontext 'begin' this means that this is | ||||||
297 | # the first item of the list, which will be used to figure out | ||||||
298 | # the type of the list. | ||||||
299 | 58 | 84 | my $lvl = $parser->{+P . 'listlvl'}; | ||||
300 | |||||||
301 | 58 | 89 | $parser->{+P . 'list'}->[$lvl]->{items}++; | ||||
302 | |||||||
303 | 58 | 100 | 126 | if ($parser->{+P . 'list'}->[$lvl]->{items} == 1){ | |||
304 | |||||||
305 | # if we are already in a list... | ||||||
306 | 20 | 100 | 66 | 132 | if ($parser->{+P . 'list'}->[$lvl]->{state} | ||
100 | |||||||
307 | eq 'over' | ||||||
308 | and $lvl > 0 | ||||||
309 | and | ||||||
310 | $parser->{+P . 'list'}->[$lvl-1]->{items} | ||||||
311 | > 0 | ||||||
312 | ) { | ||||||
313 | # we need to close the last item! | ||||||
314 | 6 | 25 | print $out_fh '' . "\n"; | ||||
315 | } | ||||||
316 | |||||||
317 | # is there a digit at first, if yes this is an ordered list | ||||||
318 | 20 | 100 | 223 | if ($expansion =~ /^\s*\d+\s*(.*)$/) { | |||
100 | |||||||
100 | |||||||
319 | 6 | 13 | $expansion = $1; | ||||
320 | $parser->{+P . 'list'}->[$lvl] | ||||||
321 | 6 | 15 | ->{type} = 'ol'; | ||||
322 | |||||||
323 | 6 | 100 | 18 | if ($expansion =~ /[[:alnum:][:punct:]]+/) { | |||
324 | 5 | 19 | print $out_fh '
|
||||
325 | } | ||||||
326 | else { | ||||||
327 | 1 | 4 | $parser->{+P . 'list'}->[$lvl]->{contentInCmd} = 0; | ||||
328 | 1 | 3 | print $out_fh "
|
||||
329 | } | ||||||
330 | } | ||||||
331 | # is there a '*' at first, if yes this is an unordered list | ||||||
332 | elsif ($expansion =~ /^\s*\*{1}\s*(.*)$/) { | ||||||
333 | 11 | 29 | $expansion = $1; | ||||
334 | 11 | 24 | $parser->{+P . 'list'}->[$lvl]->{type} = 'ul'; | ||||
335 | |||||||
336 | 11 | 100 | 29 | if ($expansion =~ /[[:alnum:][:punct:]]+/) { | |||
337 | 9 | 38 | print $out_fh '
|
||||
338 | } | ||||||
339 | else { | ||||||
340 | 2 | 4 | $parser->{+P . 'list'}->[$lvl]->{contentInCmd} = 0; | ||||
341 | 2 | 6 | print $out_fh "
|
||||
342 | #\n"; | ||||||
343 | } | ||||||
344 | } | ||||||
345 | # are there only prinable chars? We default to unordered | ||||||
346 | elsif ($expansion =~ /[[:alnum:][:punct:]]+/) { | ||||||
347 | 1 | 2 | $parser->{+P . 'list'}->[$lvl]->{type} = 'ul'; | ||||
348 | 1 | 4 | print $out_fh '
|
||||
349 | # do nothing | ||||||
350 | } | ||||||
351 | # The lists text may be in a normal text section... | ||||||
352 | # we default to unordered | ||||||
353 | else { | ||||||
354 | 2 | 3 | $parser->{+P . 'list'}->[$lvl]->{type} = 'ul'; | ||||
355 | 2 | 5 | $parser->{+P . 'list'}->[$lvl]->{contentInCmd} = 0; | ||||
356 | 2 | 6 | print $out_fh "
|
||||
357 | } | ||||||
358 | } | ||||||
359 | |||||||
360 | # if it is not the first item we save the checks for list-type | ||||||
361 | else { | ||||||
362 | |||||||
363 | # but first we need to close the last item! | ||||||
364 | 38 | 100 | 81 | if ($parser->{+P . 'listjustwentback'}) { | |||
365 | 7 | 16 | $parser->{+P . 'listjustwentback'} = 0; | ||||
366 | } | ||||||
367 | else { | ||||||
368 | # we need to close the last item! | ||||||
369 | 31 | 106 | print $out_fh '' . "\n"; | ||||
370 | } | ||||||
371 | |||||||
372 | my $type = | ||||||
373 | 38 | 554 | $parser->{+P . 'list'}->[$lvl]->{type}; | ||||
374 | |||||||
375 | # then we check the type and extract the content | ||||||
376 | 38 | 100 | 97 | if ($type eq 'ol') { | |||
377 | 10 | 50 | 43 | if ($expansion =~ /^\s*\d+\s*(.*)$/) { | |||
378 | 10 | 25 | $expansion = $1; | ||||
379 | } | ||||||
380 | } | ||||||
381 | 38 | 100 | 80 | if ($type eq 'ul') { | |||
382 | 28 | 100 | 119 | if ($expansion =~ /^\s*\*{1}\s*(.*)$/) { | |||
383 | 22 | 57 | $expansion = $1; | ||||
384 | } | ||||||
385 | } | ||||||
386 | } | ||||||
387 | |||||||
388 | # we print the item... but we don't close it! | ||||||
389 | # it get's closed by the next item or the =back call | ||||||
390 | 58 | 507 | print $out_fh ' |
||||
391 | } | ||||||
392 | } | ||||||
393 | } | ||||||
394 | |||||||
395 | # Overwrite sub of Pod::Parser | ||||||
396 | # Here all code parts of POD get parsed | ||||||
397 | sub verbatim { | ||||||
398 | 1 | 1 | 0 | 86 | my ($parser, $paragraph, $line_num) = @_; | ||
399 | 1 | 5 | my $out_fh = $parser->output_handle(); # handle for parsing output | ||||
400 | |||||||
401 | # We have to escape the case where there is only a newline, because | ||||||
402 | # Pod::Parser calls verbatim() with $paragraph="\n" every time an empty | ||||||
403 | # line is found in the Pod. But that is not what we are looking for! | ||||||
404 | # We are looking for code-blocks here... | ||||||
405 | 1 | 50 | 3 | if ($paragraph eq "\n") { return } | |||
0 | 0 | ||||||
406 | |||||||
407 | # we look for POD inline commands | ||||||
408 | 1 | 41 | my $expansion = $parser->interpolate($paragraph, $line_num); | ||||
409 | # then for special chars | ||||||
410 | 1 | 4 | $expansion = _html_enc($expansion); | ||||
411 | # and last but not least we replace whitespace with a HTML tag. | ||||||
412 | # this we do only for the verbatim command! | ||||||
413 | # this is so, that code format (indenting) is keeped in html | ||||||
414 | 1 | 4 | $expansion = _nbsp($expansion); | ||||
415 | |||||||
416 | # also only in verbatim we replace newline with the tag |
||||||
417 | # this is so, that code format is keeped in html | ||||||
418 | 1 | 7 | $expansion =~ s/\n/ \n/g; |
||||
419 | |||||||
420 | # trim must be last, | ||||||
421 | # otherwise _nbsp() is not working for the first line | ||||||
422 | 1 | 3 | trim $expansion; | ||||
423 | |||||||
424 | # ok, we are done and print out the result | ||||||
425 | 1 | 21 | print $out_fh "$expansion \n"; |
||||
426 | } | ||||||
427 | |||||||
428 | # Overwrite sub of Pod::Parser | ||||||
429 | # Here normal POD text paragraphs get parsed | ||||||
430 | sub textblock { | ||||||
431 | 86 | 86 | 0 | 5706 | my ($parser, $paragraph, $line_num) = @_; | ||
432 | 86 | 401 | my $out_fh = $parser->output_handle(); # handle for parsing output | ||||
433 | |||||||
434 | # we could be in a =begin block so we just check that and return if | ||||||
435 | # this is the case | ||||||
436 | 86 | 100 | 235 | if ($parser->{+P . 'begin'} eq 'html') { | |||
437 | # we are in a html block, so just print the plain thing | ||||||
438 | 1 | 3 | print $out_fh " \n"; |
||||
439 | 1 | 17 | print $out_fh $paragraph; | ||||
440 | 1 | 16 | print $out_fh "\n"; | ||||
441 | return | ||||||
442 | 1 | 55 | } | ||||
443 | |||||||
444 | # no begin block... so do the rest of this complicate code! | ||||||
445 | |||||||
446 | # ok, this one is tricky... | ||||||
447 | # textblock() can be called when the parser is actually parsing a list. | ||||||
448 | # this happens if the list is written like that: | ||||||
449 | # =over | ||||||
450 | # | ||||||
451 | # =item | ||||||
452 | # | ||||||
453 | # Text that appears in this sub as $paragraph | ||||||
454 | # | ||||||
455 | # =back | ||||||
456 | # If the text is on the SAME LINE as the =item command, this will not | ||||||
457 | # happen. It is only when the text is separated with newline. | ||||||
458 | # Ok... we need to check here if we are in a list.. and then do some | ||||||
459 | # stuffe to handle that case. | ||||||
460 | |||||||
461 | # we translate the POD inline commands... | ||||||
462 | 85 | 4337 | my $expansion = $parser->interpolate($paragraph, $line_num); | ||||
463 | # remove leading and trailing whitespace... | ||||||
464 | 85 | 250 | trim $expansion; | ||||
465 | # and translate special chars to HTML | ||||||
466 | 85 | 1372 | $expansion = _html_enc($expansion); | ||||
467 | |||||||
468 | # store the list-nesting in a local variable (just for readability) | ||||||
469 | 85 | 162 | my $lvl = $parser->{+P . 'listlvl'}; | ||||
470 | |||||||
471 | # if there is no list WE ARE LUCKY and just print the text as paragraph | ||||||
472 | 85 | 100 | 100 | 362 | if (not exists $parser->{+P . 'list'}) { | ||
100 | |||||||
100 | |||||||
50 | |||||||
473 | 56 | 249 | print $out_fh ' ' . $expansion . ' ' . "\n"; |
||||
474 | } | ||||||
475 | # NOOOOOOO... we have a list | ||||||
476 | # ok... let's try to figure out what to do! | ||||||
477 | |||||||
478 | # items and some content found already in the command... | ||||||
479 | # ... so we add a before the following textblock. |
||||||
480 | elsif ($parser->{+P . 'list'}->[$lvl]->{items} > 0 | ||||||
481 | and $parser->{+P . 'list'}->[$lvl]->{contentInCmd} == 1 | ||||||
482 | ) { | ||||||
483 | 2 | 8 | print $out_fh ' ' . $expansion; |
||||
484 | } | ||||||
485 | # if there was not yet content found we just print what we have now | ||||||
486 | elsif ($parser->{+P . 'list'}->[$lvl]->{items} > 0) { | ||||||
487 | 12 | 33 | print $out_fh $expansion; | ||||
488 | } | ||||||
489 | # if there where no items yet this can only mean that we are in a list | ||||||
490 | # without any items but with pure text... so we do blockquotes for | ||||||
491 | # each paragraph | ||||||
492 | elsif ($parser->{+P . 'list'}->[$lvl]->{items} == 0) { | ||||||
493 | |||||||
494 | # we set the listtype | ||||||
495 | 15 | 34 | $parser->{+P . 'list'}->[$lvl]->{type} = 'blockquote'; | ||||
496 | 15 | 25 | $parser->{+P . 'list'}->[$lvl]->{blockquotes}++; | ||||
497 | |||||||
498 | 15 | 100 | 100 | 82 | if ($parser->{+P . 'list'}->[$lvl]->{blockquotes} == 1 | ||
100 | |||||||
499 | and $lvl > 0 | ||||||
500 | and $parser->{+P . 'list'}->[$lvl-1]->{items} > 0 | ||||||
501 | ) { | ||||||
502 | 1 | 4 | print $out_fh "\n"; | ||||
503 | } | ||||||
504 | |||||||
505 | # we do some pseudo-indenting | ||||||
506 | # TODO: more nice would be real nesting... | ||||||
507 | 15 | 56 | for (0..$lvl) { | ||||
508 | 21 | 173 | print $out_fh ''; |
||||
509 | } | ||||||
510 | 15 | 323 | print $out_fh $expansion; | ||||
511 | 15 | 322 | for (0..$lvl) { | ||||
512 | 21 | 165 | print $out_fh '' ."\n"; | ||||
513 | } | ||||||
514 | } | ||||||
515 | else { | ||||||
516 | # we should not reach here... | ||||||
517 | 0 | 0 | croak "POD parsing error. Found undefined textblock in a list."; | ||||
518 | } | ||||||
519 | } | ||||||
520 | |||||||
521 | # Overwrite sub of Pod::Parser | ||||||
522 | # This method is called for handling inline POD, like e.g. B |
||||||
523 | sub interior_sequence { | ||||||
524 | 19 | 19 | 0 | 37 | my ($parser, $cmd, $arg) = @_; | ||
525 | |||||||
526 | # IMPORTANT here we do some tricky stuff... | ||||||
527 | # what we actually want is this: | ||||||
528 | # B |
||||||
529 | # but this is not possible, because then the <> would be replaced by | ||||||
530 | # HTML entities later on! | ||||||
531 | # So that is why we replace like this: | ||||||
532 | # < -> constant: LT | ||||||
533 | # and | ||||||
534 | # > -> constant: GT | ||||||
535 | # So B |
||||||
536 | # The function which is doing the HTML translation must then replace | ||||||
537 | # this words again with < and > (this is what _html_enc() is doing) | ||||||
538 | 19 | 100 | 274 | return LT . 'b' . GT . $arg . LT . '/b' . GT if ($cmd eq 'B'); | |||
539 | 16 | 100 | 142 | return LT . 'code' . GT . $arg . LT . '/code' . GT if ($cmd eq 'C'); | |||
540 | 15 | 100 | 132 | return LT . 'code' . GT . $arg . LT . '/code' . GT if ($cmd eq 'F'); | |||
541 | 14 | 100 | 166 | return LT . 'i' . GT . $arg . LT . '/i' . GT if ($cmd eq 'I'); | |||
542 | 12 | 100 | 100 | return AMP . $arg . COL if ($cmd eq 'E'); | |||
543 | |||||||
544 | # if there is an L<> we have to take care a little bit more | ||||||
545 | 11 | 50 | 23 | if ($cmd eq 'L') { | |||
546 | |||||||
547 | # if we have this: | ||||||
548 | # L |
||||||
549 | # this means that CHI::Driver::File is the name to be displayed | ||||||
550 | # and "File" is the link... which we direct to metacpan... | ||||||
551 | |||||||
552 | # empty vars | ||||||
553 | 11 | 14 | my $text = ''; | ||||
554 | 11 | 14 | my $link = ''; | ||||
555 | |||||||
556 | # if named we set the vars | ||||||
557 | 11 | 100 | 46 | if ($arg =~ m/^(.*)\|(.*)$/) { | |||
558 | 5 | 9 | $text = $1; | ||||
559 | 5 | 12 | $link = $2; | ||||
560 | } | ||||||
561 | |||||||
562 | # in case this is not set, we set it to original value | ||||||
563 | 11 | 100 | 21 | $link = $arg unless $link; | |||
564 | |||||||
565 | # the case | ||||||
566 | # L | ||||||
567 | # for relative sections is not handled well here because we | ||||||
568 | # don't know the module like that! | ||||||
569 | # so we just print the text as is | ||||||
570 | 11 | 100 | 100 | 81 | if($link =~ m%^/(.*)%) { | ||
100 | |||||||
100 | |||||||
571 | 2 | 4 | my $section = $1; | ||||
572 | 2 | 100 | 6 | if ($text) { | |||
573 | 1 | 39 | return "$text ($section)"; | ||||
574 | } | ||||||
575 | else { | ||||||
576 | 1 | 39 | return DQUO . $section . DQUO; | ||||
577 | } | ||||||
578 | # EXIT | ||||||
579 | } | ||||||
580 | |||||||
581 | # if the links seems to be http we also just return! | ||||||
582 | elsif ($link =~ /^http.*$/ | ||||||
583 | or $link =~ /^.*\.{1}\w{2,5}$/ ) { | ||||||
584 | # this is a weblink! | ||||||
585 | # keep on going... | ||||||
586 | } | ||||||
587 | |||||||
588 | # if no special case we continue... | ||||||
589 | elsif ($link =~ m%(.*)/(.*)%) { | ||||||
590 | 2 | 4 | my $module = $1; | ||||
591 | 2 | 3 | my $section = $2; | ||||
592 | 2 | 4 | $section =~ s/"//; | ||||
593 | |||||||
594 | 2 | 50 | 33 | 10 | if ($module && $section) { | ||
0 | 0 | ||||||
0 | 0 | ||||||
595 | 2 | 10 | $link = "$module#$section"; | ||||
596 | } | ||||||
597 | elsif ($module && not $section) { | ||||||
598 | 0 | 0 | $link = $module; | ||||
599 | } | ||||||
600 | elsif (not $module && $section) { | ||||||
601 | # this case should not happen but you never know | ||||||
602 | # (it should be handled in the first if!) | ||||||
603 | 0 | 0 | return "\"$section\""; | ||||
604 | } | ||||||
605 | |||||||
606 | # this URL should be valid now | ||||||
607 | 2 | 5 | $link = "https://metacpan.org/module/$link"; | ||||
608 | |||||||
609 | } | ||||||
610 | # normal module name | ||||||
611 | else { | ||||||
612 | # this URL should be valid now | ||||||
613 | 2 | 5 | $link = "https://metacpan.org/module/$link"; | ||||
614 | } | ||||||
615 | |||||||
616 | # in case this is not set, we set it to original value | ||||||
617 | 9 | 100 | 21 | $text = $arg unless $text; | |||
618 | |||||||
619 | 9 | 490 | return LT.'a href='.QUO.$link.QUO.GT.$text.LT.'/a'.GT | ||||
620 | } | ||||||
621 | |||||||
622 | # if nothing matches we return the content unformated 'as is' | ||||||
623 | 0 | 0 | return $arg; | ||||
624 | } | ||||||
625 | |||||||
626 | sub parse { | ||||||
627 | 11 | 11 | 1 | 20 | my ($parser, $input) = @_; | ||
628 | |||||||
629 | # INPUT: | ||||||
630 | 11 | 73 | my $input_fh = IO::String->new($input); | ||||
631 | |||||||
632 | # OUTPUT: | ||||||
633 | # We create this IO-object because Pod::Parser does not provide | ||||||
634 | # pure string-data as return of result data | ||||||
635 | 11 | 465 | my $buffer4html; # this variable will contain the result!!! | ||||
636 | 11 | 44 | my $buffer4html_handle = IO::String->new($buffer4html); | ||||
637 | |||||||
638 | # we call the parser to parse, result will be in $buffer4html | ||||||
639 | 11 | 740 | $parser->parse_from_filehandle($input_fh, $buffer4html_handle); | ||||
640 | |||||||
641 | 11 | 29 | return $buffer4html; | ||||
642 | }; | ||||||
643 | |||||||
644 | sub set_options { | ||||||
645 | 5 | 5 | 1 | 7 | my $self = shift; | ||
646 | 5 | 6 | my $args = shift; | ||||
647 | |||||||
648 | 5 | 50 | 13 | if (ref($args) eq "HASH") { | |||
649 | 5 | 100 | 17 | $self->head0_mode($args->{head0_mode}) if (exists $args->{head0_mode}); | |||
650 | 5 | 100 | 17 | $self->pagemode ($args->{pagemode}) if (exists $args->{pagemode}); | |||
651 | } | ||||||
652 | else { | ||||||
653 | 0 | 0 | $self->debug_msg('Plugin options are not in a HASH'); | ||||
654 | } | ||||||
655 | } | ||||||
656 | |||||||
657 | sub pagemode { | ||||||
658 | 25 | 25 | 1 | 42475 | my ($self, $boolean) = @_; | ||
659 | |||||||
660 | 25 | 50 | 71 | if (@_ > 1) { | |||
661 | 25 | 88 | $self->{+P . 'pages'} = $boolean; | ||||
662 | } | ||||||
663 | else { | ||||||
664 | 0 | 0 | return $self->{+P . 'pages'}; | ||||
665 | } | ||||||
666 | } | ||||||
667 | |||||||
668 | sub head0_mode { | ||||||
669 | 74 | 74 | 1 | 802 | my ($self, $boolean) = @_; | ||
670 | |||||||
671 | 74 | 100 | 151 | if (@_ > 1) { | |||
672 | 5 | 13 | $self->{+P . 'head0_mode'} = $boolean; | ||||
673 | } | ||||||
674 | else { | ||||||
675 | 69 | 280 | return $self->{+P . 'head0_mode'}; | ||||
676 | } | ||||||
677 | } | ||||||
678 | |||||||
679 | # encode_entities() from HTML::Entities does not translate it correctly | ||||||
680 | # this is why I make it here manually as a quick fix | ||||||
681 | # don't reall know where how to handle this utf8 problem for now... | ||||||
682 | sub _html_enc { | ||||||
683 | 216 | 216 | 304 | my $string = shift; | |||
684 | |||||||
685 | 216 | 573 | $string = encode_entities($string); | ||||
686 | # ^ | ||||||
687 | 216 | 2654 | my $lt = LT; # | | ||||
688 | 216 | 283 | my $gt = GT; # | | ||||
689 | 216 | 245 | my $am = AMP; # | | ||||
690 | 216 | 319 | my $co = COL; # |-- don't change this order! | ||||
691 | 216 | 246 | my $qu = QUO; # | | ||||
692 | 216 | 269 | my $dqu= DQUO; # | | ||||
693 | 216 | 510 | $string =~ s/$lt/ | ||||
694 | 216 | 402 | $string =~ s/$gt/>/g; # | | ||||
695 | 216 | 363 | $string =~ s/$am/&/g; # | | ||||
696 | 216 | 361 | $string =~ s/$co/;/g; # | | ||||
697 | 216 | 359 | $string =~ s/$qu/'/g; # | | ||||
698 | 216 | 331 | $string =~ s/$dqu/"/g; #<---| | ||||
699 | |||||||
700 | 216 | 532 | return $string; | ||||
701 | } | ||||||
702 | |||||||
703 | ## replaces whitespace with html entitie | ||||||
704 | sub _nbsp { | ||||||
705 | 1 | 1 | 2 | my $string = shift; | |||
706 | |||||||
707 | 1 | 15 | $string =~ s/\ / /g; | ||||
708 | |||||||
709 | 1 | 3 | return $string; | ||||
710 | } | ||||||
711 | |||||||
712 | 1; | ||||||
713 | |||||||
714 | __END__ |