blib/lib/Text/WikiCreole.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 93 | 128 | 72.6 |
branch | 37 | 66 | 56.0 |
condition | 8 | 15 | 53.3 |
subroutine | 12 | 20 | 60.0 |
pod | 9 | 14 | 64.2 |
total | 159 | 243 | 65.4 |
line | stmt | bran | cond | sub | pod | time | code | |
---|---|---|---|---|---|---|---|---|
1 | package Text::WikiCreole; | |||||||
2 | require Exporter; | |||||||
3 | @ISA = (Exporter); | |||||||
4 | @EXPORT = qw(creole_parse creole_plugin creole_tag creole_img creole_customimgs | |||||||
5 | creole_link creole_barelink creole_customlinks creole_custombarelinks); | |||||||
6 | 8 | 8 | 148258 | use vars qw($VERSION); | ||||
8 | 26 | |||||||
8 | 424 | |||||||
7 | 8 | 8 | 50 | use strict; | ||||
8 | 16 | |||||||
8 | 295 | |||||||
8 | 8 | 8 | 50 | use warnings; | ||||
8 | 19 | |||||||
8 | 43505 | |||||||
9 | ||||||||
10 | our $VERSION = "0.07"; | |||||||
11 | ||||||||
12 | sub strip_head_eq { # strip lead/trail white/= from headings | |||||||
13 | 4 | 4 | 0 | 23 | $_[0] =~ s/^\s*=*\s*//o; | |||
14 | 4 | 98 | $_[0] =~ s/\s*=*\s*$//o; | |||||
15 | 4 | 16 | return $_[0]; | |||||
16 | } | |||||||
17 | ||||||||
18 | sub strip_list { # strip list markup trickery | |||||||
19 | 19 | 19 | 0 | 90 | $_[0] =~ s/(?:`*| *)[\*\#]/`/o; | |||
20 | 19 | 120 | $_[0] =~ s/\n(?:`*| *)[\*\#]/\n`/gso; | |||||
21 | 19 | 69 | return $_[0]; | |||||
22 | } | |||||||
23 | ||||||||
24 | # characters that may indicate inline wiki markup | |||||||
25 | my @specialchars = ('^', '\\', '*', '/', '_', ',', '{', '[', | |||||||
26 | '<', '~', '|', "\n", '#', ':', ';', '(', '-', '.'); | |||||||
27 | # plain characters - auto-generated below (ascii printable minus @specialchars) | |||||||
28 | my @plainchars; | |||||||
29 | ||||||||
30 | # non-plain text inline widgets | |||||||
31 | my @inline = ('strong', 'em', 'br', 'esc', 'img', 'link', 'ilink', | |||||||
32 | 'inowiki', 'sub', 'sup', 'mono', 'u', 'plug', 'plug2', 'tm', | |||||||
33 | 'reg', 'copy', 'ndash', 'ellipsis', 'amp'); | |||||||
34 | my @all_inline = (@inline, 'plain', 'any'); # including plain text | |||||||
35 | ||||||||
36 | # blocks | |||||||
37 | my @blocks = ('h1', 'h2', 'h3', 'hr', 'nowiki', 'h4', 'h5', 'h6', | |||||||
38 | 'ul', 'ol', 'table', 'p', 'ip', 'dl', 'plug', 'plug2', 'blank'); | |||||||
39 | ||||||||
40 | # handy - used several times in %chunks | |||||||
41 | my $eol = '(?:\n|$)'; # end of line (or string) | |||||||
42 | my $bol = '(?:^|\n)'; # beginning of line (or string) | |||||||
43 | ||||||||
44 | # user-supplied plugin parser function | |||||||
45 | my $plugin_function; | |||||||
46 | # user-supplied link URL parser function | |||||||
47 | my $link_function; | |||||||
48 | # user-supplied bare link parser function | |||||||
49 | my $barelink_function; | |||||||
50 | # user-supplied image URL parser function | |||||||
51 | my $img_function; | |||||||
52 | ||||||||
53 | # initialize once | |||||||
54 | my $initialized = 0; | |||||||
55 | ||||||||
56 | my %chunks = ( | |||||||
57 | top => { | |||||||
58 | contains => \@blocks, | |||||||
59 | }, | |||||||
60 | blank => { | |||||||
61 | curpat => "(?= *$eol)", | |||||||
62 | fwpat => "(?=(?:^|\n) *$eol)", | |||||||
63 | stops => '(?=\S)', | |||||||
64 | hint => ["\n"], | |||||||
65 | filter => sub { return ""; }, # whitespace into the bit bucket | |||||||
66 | open => "", close => "", | |||||||
67 | }, | |||||||
68 | p => { | |||||||
69 | curpat => '(?=.)', | |||||||
70 | stops => ['blank', 'ip', 'h', 'hr', 'nowiki', 'ul', 'ol', 'dl', 'table'], | |||||||
71 | hint => \@plainchars, | |||||||
72 | contains => \@all_inline, | |||||||
73 | filter => sub { chomp $_[0]; return $_[0]; }, | |||||||
74 | open => " ", close => " \n\n", |
|||||||
75 | }, | |||||||
76 | ip => { | |||||||
77 | curpat => '(?=:)', | |||||||
78 | fwpat => '\n(?=:)', | |||||||
79 | stops => ['blank', 'h', 'hr', 'nowiki', 'ul', 'ol', 'dl', 'table'], | |||||||
80 | hint => [':'], | |||||||
81 | contains => ['p', 'ip'], | |||||||
82 | filter => sub { | |||||||
83 | $_[0] =~ s/://o; | |||||||
84 | $_[0] =~ s/\n:/\n/so; | |||||||
85 | return $_[0]; | |||||||
86 | }, | |||||||
87 | open => " ", close => " \n", |
|||||||
88 | }, | |||||||
89 | dl => { | |||||||
90 | curpat => '(?=;)', | |||||||
91 | fwpat => '\n(?=;)', | |||||||
92 | stops => ['blank', 'h', 'hr', 'nowiki', 'ul', 'ol', 'table'], | |||||||
93 | hint => [';'], | |||||||
94 | contains => ['dt', 'dd'], | |||||||
95 | open => "
|
|||||||
96 | }, | |||||||
97 | dt => { | |||||||
98 | curpat => '(?=;)', | |||||||
99 | fwpat => '\n(?=;)', | |||||||
100 | stops => '(?=:|\n)', | |||||||
101 | hint => [';'], | |||||||
102 | contains => \@all_inline, | |||||||
103 | filter => sub { $_[0] =~ s/^;\s*//o; return $_[0]; }, | |||||||
104 | open => " |
|||||||
105 | }, | |||||||
106 | dd => { | |||||||
107 | curpat => '(?=\n|:)', | |||||||
108 | fwpat => '(?:\n|:)', | |||||||
109 | stops => '(?=:)|\n(?=;)', | |||||||
110 | hint => [':', "\n"], | |||||||
111 | contains => \@all_inline, | |||||||
112 | filter => sub { | |||||||
113 | $_[0] =~ s/(?:\n|:)\s*//so; | |||||||
114 | $_[0] =~ s/\s*$//so; | |||||||
115 | return $_[0]; | |||||||
116 | }, | |||||||
117 | open => " |
|||||||
118 | }, | |||||||
119 | table => { | |||||||
120 | curpat => '(?= *\|.)', | |||||||
121 | fwpat => '\n(?= *\|.)', | |||||||
122 | stops => '\n(?= *[^\|])', | |||||||
123 | contains => ['tr'], | |||||||
124 | hint => ['|', ' '], | |||||||
125 | open => " |
|||||||
126 | }, | |||||||
127 | tr => { | |||||||
128 | curpat => '(?= *\|)', | |||||||
129 | stops => '\n', | |||||||
130 | contains => ['td', 'th'], | |||||||
131 | hint => ['|', ' '], | |||||||
132 | filter => sub { $_[0] =~ s/^ *//o; $_[0] =~ s/\| *$//o; return $_[0]; }, | |||||||
133 | open => " | |||||||
134 | }, | |||||||
135 | td => { | |||||||
136 | curpat => '(?=\|[^=])', | |||||||
137 | # this gnarly regex fixes ambiguous '|' for links/imgs/nowiki in tables | |||||||
138 | stops => '[^~](?=\|(?!(?:[^\[]*\]\])|(?:[^\{]*\}\})))', | |||||||
139 | contains => \@all_inline, | |||||||
140 | hint => ['|'], | |||||||
141 | filter => sub {$_[0] =~ s/^ *\| *//o; $_[0] =~ s/\s*$//so; return $_[0]; }, | |||||||
142 | open => " | ", close => " | \n",||||||
143 | }, | |||||||
144 | th => { | |||||||
145 | curpat => '(?=\|=)', | |||||||
146 | # this gnarly regex fixes ambiguous '|' for links/imgs/nowiki in tables | |||||||
147 | stops => '[^~](?=\|(?!(?:[^\[]*\]\])|(?:[^\{]*\}\})))', | |||||||
148 | contains => \@all_inline, | |||||||
149 | hint => ['|'], | |||||||
150 | filter => sub {$_[0] =~ s/^ *\|= *//o; $_[0] =~ s/\s*$//so; return $_[0]; }, | |||||||
151 | open => " | ", close => " | \n",||||||
152 | }, | |||||||
153 | ul => { | |||||||
154 | curpat => '(?=(?:`| *)\*[^\*])', | |||||||
155 | fwpat => '(?=\n(?:`| *)\*[^\*])', | |||||||
156 | stops => ['blank', 'ip', 'h', 'nowiki', 'li', 'table', 'hr', 'dl'], | |||||||
157 | contains => ['ul', 'ol', 'li'], | |||||||
158 | hint => ['*', ' '], | |||||||
159 | filter => \&strip_list, | |||||||
160 | open => "
|
|||||||
161 | }, | |||||||
162 | ol => { | |||||||
163 | curpat => '(?=(?:`| *)\#[^\#])', | |||||||
164 | fwpat => '(?=\n(?:`| *)\#[^\#])', | |||||||
165 | stops => ['blank', 'ip', 'h', 'nowiki', 'li', 'table', 'hr', 'dl'], | |||||||
166 | contains => ['ul', 'ol', 'li'], | |||||||
167 | hint => ['#', ' '], | |||||||
168 | filter => \&strip_list, | |||||||
169 | open => "
|
|||||||
170 | }, | |||||||
171 | li => { | |||||||
172 | curpat => '(?=`[^\*\#])', | |||||||
173 | fwpat => '\n(?=`[^\*\#])', | |||||||
174 | stops => '\n(?=`)', | |||||||
175 | hint => ['`'], | |||||||
176 | filter => sub { | |||||||
177 | $_[0] =~ s/` *//o; | |||||||
178 | chomp $_[0]; | |||||||
179 | return $_[0]; | |||||||
180 | }, | |||||||
181 | contains => \@all_inline, | |||||||
182 | open => " |
|||||||
183 | }, | |||||||
184 | nowiki => { | |||||||
185 | curpat => '(?=\{\{\{ *\n)', | |||||||
186 | fwpat => '\n(?=\{\{\{ *\n)', | |||||||
187 | stops => "\n\}\}\} *$eol", | |||||||
188 | hint => ['{'], | |||||||
189 | filter => sub { | |||||||
190 | substr($_[0], 0, 3, ''); | |||||||
191 | $_[0] =~ s/\}\}\}\s*$//o; | |||||||
192 | $_[0] =~ s/&/&/go; | |||||||
193 | $_[0] =~ s/</go; | |||||||
194 | $_[0] =~ s/>/>/go; | |||||||
195 | return $_[0]; | |||||||
196 | }, | |||||||
197 | open => "", close => "\n\n", |
|||||||
198 | }, | |||||||
199 | hr => { | |||||||
200 | curpat => "(?= *-{4,} *$eol)", | |||||||
201 | fwpat => "\n(?= *-{4,} *$eol)", | |||||||
202 | hint => ['-', ' '], | |||||||
203 | stops => $eol, | |||||||
204 | open => " \n\n", close => "", |
|||||||
205 | filter => sub { return ""; } # ----- into the bit bucket | |||||||
206 | }, | |||||||
207 | h => { curpat => '(?=(?:^|\n) *=)' }, # matches any heading | |||||||
208 | h1 => { | |||||||
209 | curpat => '(?= *=[^=])', | |||||||
210 | hint => ['=', ' '], | |||||||
211 | stops => '\n', | |||||||
212 | contains => \@all_inline, | |||||||
213 | open => "", close => "\n\n", |
|||||||
214 | filter => \&strip_head_eq, | |||||||
215 | }, | |||||||
216 | h2 => { | |||||||
217 | curpat => '(?= *={2}[^=])', | |||||||
218 | hint => ['=', ' '], | |||||||
219 | stops => '\n', | |||||||
220 | contains => \@all_inline, | |||||||
221 | open => "", close => "\n\n", |
|||||||
222 | filter => \&strip_head_eq, | |||||||
223 | }, | |||||||
224 | h3 => { | |||||||
225 | curpat => '(?= *={3}[^=])', | |||||||
226 | hint => ['=', ' '], | |||||||
227 | stops => '\n', | |||||||
228 | contains => \@all_inline, | |||||||
229 | open => "", close => "\n\n", |
|||||||
230 | filter => \&strip_head_eq, | |||||||
231 | }, | |||||||
232 | h4 => { | |||||||
233 | curpat => '(?= *={4}[^=])', | |||||||
234 | hint => ['=', ' '], | |||||||
235 | stops => '\n', | |||||||
236 | contains => \@all_inline, | |||||||
237 | open => "", close => "\n\n", |
|||||||
238 | filter => \&strip_head_eq, | |||||||
239 | }, | |||||||
240 | h5 => { | |||||||
241 | curpat => '(?= *={5}[^=])', | |||||||
242 | hint => ['=', ' '], | |||||||
243 | stops => '\n', | |||||||
244 | contains => \@all_inline, | |||||||
245 | open => "", close => "\n\n", |
|||||||
246 | filter => \&strip_head_eq, | |||||||
247 | }, | |||||||
248 | h6 => { | |||||||
249 | curpat => '(?= *={6,})', | |||||||
250 | hint => ['=', ' '], | |||||||
251 | stops => '\n', | |||||||
252 | contains => \@all_inline, | |||||||
253 | open => "", close => "\n\n", |
|||||||
254 | filter => \&strip_head_eq, | |||||||
255 | }, | |||||||
256 | plain => { | |||||||
257 | curpat => '(?=[^\*\/_\,\^\\\\{\[\<\|])', | |||||||
258 | stops => \@inline, | |||||||
259 | hint => \@plainchars, | |||||||
260 | open => '', close => '' | |||||||
261 | }, | |||||||
262 | any => { # catch-all | |||||||
263 | curpat => '(?=.)', | |||||||
264 | stops => \@inline, | |||||||
265 | open => '', close => '' | |||||||
266 | }, | |||||||
267 | br => { | |||||||
268 | curpat => '(?=\\\\\\\\)', | |||||||
269 | stops => '\\\\\\\\', | |||||||
270 | hint => ['\\'], | |||||||
271 | filter => sub { return ''; }, | |||||||
272 | open => ' ', close => '', |
|||||||
273 | }, | |||||||
274 | esc => { | |||||||
275 | curpat => '(?=~[\S])', | |||||||
276 | stops => '~.', | |||||||
277 | hint => ['~'], | |||||||
278 | filter => sub { substr($_[0], 0, 1, ''); return $_[0]; }, | |||||||
279 | open => '', close => '', | |||||||
280 | }, | |||||||
281 | inowiki => { | |||||||
282 | curpat => '(?=\{{3}.*?\}*\}{3})', | |||||||
283 | stops => '.*?\}*\}{3}', | |||||||
284 | hint => ['{'], | |||||||
285 | filter => sub { | |||||||
286 | substr($_[0], 0, 3, ''); | |||||||
287 | $_[0] =~ s/\}{3}$//o; | |||||||
288 | $_[0] =~ s/&/&/go; | |||||||
289 | $_[0] =~ s/</go; | |||||||
290 | $_[0] =~ s/>/>/go; | |||||||
291 | return $_[0]; | |||||||
292 | }, | |||||||
293 | open => "", close => "", | |||||||
294 | }, | |||||||
295 | plug => { | |||||||
296 | curpat => '(?=\<{3}.*?\>*\>{3})', | |||||||
297 | stops => '.*?\>*\>{3}', | |||||||
298 | hint => ['<'], | |||||||
299 | filter => sub { | |||||||
300 | substr($_[0], 0, 3, ''); | |||||||
301 | $_[0] =~ s/\>{3}$//o; | |||||||
302 | if($plugin_function) { | |||||||
303 | return &$plugin_function($_[0]); | |||||||
304 | } | |||||||
305 | return "<<<$_[0]>>>"; | |||||||
306 | }, | |||||||
307 | open => "", close => "", | |||||||
308 | }, | |||||||
309 | plug2 => { | |||||||
310 | curpat => '(?=\<{2}.*?\>*\>{2})', | |||||||
311 | stops => '.*?\>*\>{2}', | |||||||
312 | hint => ['<'], | |||||||
313 | filter => sub { | |||||||
314 | substr($_[0], 0, 2, ''); | |||||||
315 | $_[0] =~ s/\>{2}$//o; | |||||||
316 | if($plugin_function) { | |||||||
317 | return &$plugin_function($_[0]); | |||||||
318 | } | |||||||
319 | return "<<$_[0]>>"; | |||||||
320 | }, | |||||||
321 | open => "", close => "", | |||||||
322 | }, | |||||||
323 | ilink => { | |||||||
324 | curpat => '(?=(?:https?|ftp):\/\/)', | |||||||
325 | stops => '(?=[[:punct:]]?(?:\s|$))', | |||||||
326 | hint => ['h', 'f'], | |||||||
327 | filter => sub { | |||||||
328 | $_[0] =~ s/^\s*//o; | |||||||
329 | $_[0] =~ s/\s*$//o; | |||||||
330 | if($barelink_function) { | |||||||
331 | $_[0] = &$barelink_function($_[0]); | |||||||
332 | } | |||||||
333 | return "href=\"$_[0]\">$_[0]"; }, | |||||||
334 | open => " "", | |||||||
335 | }, | |||||||
336 | link => { | |||||||
337 | curpat => '(?=\[\[[^\n]+?\]\])', | |||||||
338 | stops => '\]\]', | |||||||
339 | hint => ['['], | |||||||
340 | contains => ['href', 'atext'], | |||||||
341 | filter => sub { | |||||||
342 | substr($_[0], 0, 2, ''); | |||||||
343 | substr($_[0], -2, 2, ''); | |||||||
344 | $_[0] .= "|$_[0]" unless $_[0] =~ tr/|/|/; # text = url unless given | |||||||
345 | return $_[0]; | |||||||
346 | }, | |||||||
347 | open => " "", | |||||||
348 | }, | |||||||
349 | href => { | |||||||
350 | curpat => '(?=[^\|])', | |||||||
351 | stops => '(?=\|)', | |||||||
352 | filter => sub { | |||||||
353 | $_[0] =~ s/^\s*//o; | |||||||
354 | $_[0] =~ s/\s*$//o; | |||||||
355 | if($link_function) { | |||||||
356 | $_[0] = &$link_function($_[0]); | |||||||
357 | } | |||||||
358 | return $_[0]; | |||||||
359 | }, | |||||||
360 | open => 'href="', close => '">', | |||||||
361 | }, | |||||||
362 | atext => { | |||||||
363 | curpat => '(?=\|)', | |||||||
364 | stops => '\n', | |||||||
365 | hint => ['|'], | |||||||
366 | contains => \@all_inline, | |||||||
367 | filter => sub { | |||||||
368 | $_[0] =~ s/^\|\s*//o; | |||||||
369 | $_[0] =~ s/\s*$//o; | |||||||
370 | return $_[0]; | |||||||
371 | }, | |||||||
372 | open => '', close => '', | |||||||
373 | }, | |||||||
374 | img => { | |||||||
375 | curpat => '(?=\{\{[^\{][^\n]*?\}\})', | |||||||
376 | stops => '\}\}', | |||||||
377 | hint => ['{'], | |||||||
378 | contains => ['imgsrc', 'imgalt'], | |||||||
379 | filter => sub { | |||||||
380 | substr($_[0], 0, 2, ''); | |||||||
381 | $_[0] =~ s/\}\}$//o; | |||||||
382 | return $_[0]; | |||||||
383 | }, | |||||||
384 | open => " " />", | |||||||
385 | }, | |||||||
386 | imgalt => { | |||||||
387 | curpat => '(?=\|)', | |||||||
388 | stops => '\n', | |||||||
389 | hint => ['|'], | |||||||
390 | filter => sub { $_[0] =~ s/^\|\s*//o; $_[0] =~ s/\s*$//o; return $_[0]; }, | |||||||
391 | open => ' alt="', close => '"', | |||||||
392 | }, | |||||||
393 | imgsrc => { | |||||||
394 | curpat => '(?=[^\|])', | |||||||
395 | stops => '(?=\|)', | |||||||
396 | filter => sub { | |||||||
397 | $_[0] =~ s/^\s*//o; | |||||||
398 | $_[0] =~ s/\s*$//o; | |||||||
399 | if($img_function) { | |||||||
400 | $_[0] = &$img_function($_[0]); | |||||||
401 | } | |||||||
402 | return $_[0]; | |||||||
403 | }, | |||||||
404 | open => 'src="', close => '"', | |||||||
405 | }, | |||||||
406 | strong => { | |||||||
407 | curpat => '(?=\*\*)', | |||||||
408 | stops => '\*\*.*?\*\*', | |||||||
409 | hint => ['*'], | |||||||
410 | contains => \@all_inline, | |||||||
411 | filter => sub { | |||||||
412 | substr($_[0], 0, 2, ''); | |||||||
413 | $_[0] =~ s/\*\*$//o; | |||||||
414 | return $_[0]; | |||||||
415 | }, | |||||||
416 | open => "", close => "", | |||||||
417 | }, | |||||||
418 | em => { | |||||||
419 | curpat => '(?=\/\/)', | |||||||
420 | stops => '\/\/.*?(? | |||||||
421 | hint => ['/'], | |||||||
422 | contains => \@all_inline, | |||||||
423 | filter => sub { | |||||||
424 | substr($_[0], 0, 2, ''); | |||||||
425 | $_[0] =~ s/\/\/$//o; | |||||||
426 | return $_[0]; | |||||||
427 | }, | |||||||
428 | open => "", close => "", | |||||||
429 | }, | |||||||
430 | mono => { | |||||||
431 | curpat => '(?=\#\#)', | |||||||
432 | stops => '\#\#.*?\#\#', | |||||||
433 | hint => ['#'], | |||||||
434 | contains => \@all_inline, | |||||||
435 | filter => sub { | |||||||
436 | substr($_[0], 0, 2, ''); | |||||||
437 | $_[0] =~ s/\#\#$//o; | |||||||
438 | return $_[0]; | |||||||
439 | }, | |||||||
440 | open => "", close => "", | |||||||
441 | }, | |||||||
442 | sub => { | |||||||
443 | curpat => '(?=,,)', | |||||||
444 | stops => ',,.*?,,', | |||||||
445 | hint => [','], | |||||||
446 | contains => \@all_inline, | |||||||
447 | filter => sub { | |||||||
448 | substr($_[0], 0, 2, ''); | |||||||
449 | $_[0] =~ s/\,\,$//o; | |||||||
450 | return $_[0]; | |||||||
451 | }, | |||||||
452 | open => "", close => "", | |||||||
453 | }, | |||||||
454 | sup => { | |||||||
455 | curpat => '(?=\^\^)', | |||||||
456 | stops => '\^\^.*?\^\^', | |||||||
457 | hint => ['^'], | |||||||
458 | contains => \@all_inline, | |||||||
459 | filter => sub { | |||||||
460 | substr($_[0], 0, 2, ''); | |||||||
461 | $_[0] =~ s/\^\^$//o; | |||||||
462 | return $_[0]; | |||||||
463 | }, | |||||||
464 | open => "", close => "", | |||||||
465 | }, | |||||||
466 | u => { | |||||||
467 | curpat => '(?=__)', | |||||||
468 | stops => '__.*?__', | |||||||
469 | hint => ['_'], | |||||||
470 | contains => \@all_inline, | |||||||
471 | filter => sub { | |||||||
472 | substr($_[0], 0, 2, ''); | |||||||
473 | $_[0] =~ s/__$//o; | |||||||
474 | return $_[0]; | |||||||
475 | }, | |||||||
476 | open => "", close => "", | |||||||
477 | }, | |||||||
478 | amp => { | |||||||
479 | curpat => '(?=\&(?!\w+\;))', | |||||||
480 | stops => '.', | |||||||
481 | hint => ['&'], | |||||||
482 | filter => sub { return "&"; }, | |||||||
483 | open => "", close => "", | |||||||
484 | }, | |||||||
485 | tm => { | |||||||
486 | curpat => '(?=\(TM\))', | |||||||
487 | stops => '\(TM\)', | |||||||
488 | hint => ['('], | |||||||
489 | filter => sub { return "™"; }, | |||||||
490 | open => "", close => "", | |||||||
491 | }, | |||||||
492 | reg => { | |||||||
493 | curpat => '(?=\(R\))', | |||||||
494 | stops => '\(R\)', | |||||||
495 | hint => ['('], | |||||||
496 | filter => sub { return "®"; }, | |||||||
497 | open => "", close => "", | |||||||
498 | }, | |||||||
499 | copy => { | |||||||
500 | curpat => '(?=\(C\))', | |||||||
501 | stops => '\(C\)', | |||||||
502 | hint => ['('], | |||||||
503 | filter => sub { return "©"; }, | |||||||
504 | open => "", close => "", | |||||||
505 | }, | |||||||
506 | ndash => { | |||||||
507 | curpat => '(?=--)', | |||||||
508 | stops => '--', | |||||||
509 | hint => ['-'], | |||||||
510 | filter => sub { return "–"; }, | |||||||
511 | open => "", close => "", | |||||||
512 | }, | |||||||
513 | ellipsis => { | |||||||
514 | curpat => '(?=\.\.\.)', | |||||||
515 | stops => '\.\.\.', | |||||||
516 | hint => ['.'], | |||||||
517 | filter => sub { return "…"; }, | |||||||
518 | open => "", close => "", | |||||||
519 | }, | |||||||
520 | ); | |||||||
521 | ||||||||
522 | ||||||||
523 | sub parse; # predeclared because it's recursive | |||||||
524 | ||||||||
525 | sub parse { | |||||||
526 | 173 | 173 | 0 | 379 | my ($tref, $chunk) = @_; | |||
527 | 173 | 180 | my ($html, $ch); | |||||
528 | 173 | 303 | my $pos = 0; my $lpos = 0; | |||||
173 | 187 | |||||||
529 | 173 | 192 | while(1) { | |||||
530 | 605 | 100 | 1244 | if($ch) { # if we already know what kind of chunk this is | ||||
531 | 432 | 100 | 7910 | if ($$tref =~ /$chunks{$ch}{delim}/g) { # find where it stops... | ||||
532 | 279 | 472 | $pos = pos($$tref); # another chunk | |||||
533 | } else { | |||||||
534 | 153 | 283 | $pos = length $$tref; # end of string | |||||
535 | } | |||||||
536 | ||||||||
537 | 432 | 1003 | $html .= $chunks{$ch}{open}; # print the open tag | |||||
538 | ||||||||
539 | 432 | 1538 | my $t = substr($$tref, $lpos, $pos - $lpos); # grab the chunk | |||||
540 | 432 | 100 | 1233 | if($chunks{$ch}{filter}) { # filter it, if applicable | ||||
541 | 260 | 293 | $t = &{$chunks{$ch}{filter}}($t); | |||||
260 | 742 | |||||||
542 | } | |||||||
543 | 432 | 800 | $lpos = $pos; # remember where this chunk ends (where next begins) | |||||
544 | 432 | 100 | 100 | 2550 | if($t && $chunks{$ch}{contains}) { # if it contains other chunks... | |||
545 | 165 | 434 | $html .= parse(\$t, $ch); # recurse. | |||||
546 | } else { | |||||||
547 | 267 | 628 | $html .= $t; # otherwise, print it | |||||
548 | } | |||||||
549 | 432 | 1280 | $html .= $chunks{$ch}{close}; # print the close tag | |||||
550 | } | |||||||
551 | ||||||||
552 | 605 | 100 | 100 | 17836 | if($pos && $pos == length($$tref)) { # we've eaten the whole string | |||
553 | 173 | 275 | last; | |||||
554 | } else { # more string to come | |||||||
555 | 432 | 627 | $ch = undef; | |||||
556 | 432 | 1110 | my $fc = substr($$tref, $pos, 1); # get a hint about the next chunk | |||||
557 | 432 | 583 | foreach (@{$chunks{$chunk}{hints}{$fc}}) { | |||||
432 | 1548 | |||||||
558 | # print "trying $_ for -$fc- on -" . substr($$tref, $pos, 2) . "-\n"; | |||||||
559 | 438 | 100 | 2851 | if($$tref =~ $chunks{$_}{curpatcmp}) { # hint helped id the chunk | ||||
560 | 382 | 576 | $ch = $_; last; | |||||
382 | 554 | |||||||
561 | } | |||||||
562 | } | |||||||
563 | 432 | 100 | 1284 | unless($ch) { # hint didn't help | ||||
564 | 50 | 71 | foreach (@{$chunks{$chunk}{contains}}) { # check all possible chunks | |||||
50 | 142 | |||||||
565 | # print "trying $_ on -" . substr($$tref, $pos, 2) . "-\n"; | |||||||
566 | 844 | 100 | 4499 | if ($$tref =~ $chunks{$_}{curpatcmp}) { # found one | ||||
567 | 50 | 83 | $ch = $_; last; | |||||
50 | 89 | |||||||
568 | } | |||||||
569 | } | |||||||
570 | 50 | 50 | 185 | last unless $ch; # no idea what this is. ditch the rest and give up. | ||||
571 | } | |||||||
572 | } | |||||||
573 | } | |||||||
574 | 173 | 784 | return $html; # voila! | |||||
575 | } | |||||||
576 | ||||||||
577 | # compile a regex that matches any of the patterns that interrupt the | |||||||
578 | # current chunk. | |||||||
579 | sub delim { | |||||||
580 | 376 | 100 | 376 | 0 | 960 | if(ref $chunks{$_[0]}{stops}) { | ||
581 | 56 | 77 | my $regex; | |||||
582 | 56 | 82 | foreach(@{$chunks{$_[0]}{stops}}) { | |||||
56 | 155 | |||||||
583 | 640 | 100 | 1384 | if($chunks{$_}{fwpat}) { | ||||
584 | 280 | 743 | $regex .= "$chunks{$_}{fwpat}|"; | |||||
585 | } else { | |||||||
586 | 360 | 791 | $regex .= "$chunks{$_}{curpat}|"; | |||||
587 | } | |||||||
588 | } | |||||||
589 | 56 | 138 | chop $regex; | |||||
590 | 56 | 4735 | return qr/$regex/s; | |||||
591 | } else { | |||||||
592 | 320 | 4568 | return qr/$chunks{$_[0]}{stops}/s; | |||||
593 | } | |||||||
594 | } | |||||||
595 | ||||||||
596 | # one-time optimization of the grammar - speeds the parser up a ton | |||||||
597 | sub init { | |||||||
598 | 8 | 50 | 8 | 0 | 45 | return if $initialized; | ||
599 | ||||||||
600 | 8 | 20 | $initialized = 1; | |||||
601 | ||||||||
602 | # build an array of "plain content" characters by subtracting @specialchars | |||||||
603 | # from ascii printable (ascii 32 to 126) | |||||||
604 | 8 | 33 | my %is_special = map({$_ => 1} @specialchars); | |||||
144 | 478 | |||||||
605 | 8 | 46 | for (32 .. 126) { | |||||
606 | 760 | 100 | 7916 | push(@plainchars, chr($_)) unless $is_special{chr($_)}; | ||||
607 | } | |||||||
608 | ||||||||
609 | # precompile a bunch of regexes | |||||||
610 | 8 | 162 | foreach my $c (keys %chunks) { | |||||
611 | 392 | 100 | 1152 | if($chunks{$c}{curpat}) { | ||||
612 | 384 | 7888 | $chunks{$c}{curpatcmp} = qr/\G$chunks{$c}{curpat}/s; | |||||
613 | } | |||||||
614 | 392 | 100 | 1346 | if($chunks{$c}{stops}) { | ||||
615 | 376 | 747 | $chunks{$c}{delim} = delim $c; | |||||
616 | } | |||||||
617 | 392 | 100 | 1544 | if($chunks{$c}{contains}) { # store hints about each chunk to speed id | ||||
618 | 224 | 286 | foreach my $ct (@{$chunks{$c}{contains}}) { | |||||
224 | 578 | |||||||
619 | 3616 | 3880 | foreach (@{$chunks{$ct}{hint}}) { | |||||
3616 | 8087 | |||||||
620 | 16664 | 18200 | push @{$chunks{$c}{hints}{$_}}, $ct; | |||||
16664 | 61206 | |||||||
621 | } | |||||||
622 | } | |||||||
623 | } | |||||||
624 | } | |||||||
625 | } | |||||||
626 | ||||||||
627 | sub creole_parse { | |||||||
628 | 8 | 50 | 33 | 8 | 1 | 1081 | return unless defined $_[0] && length $_[0] > 0; | |
629 | 8 | 27 | my $text = $_[0]; | |||||
630 | 8 | 44 | init; | |||||
631 | 8 | 81 | my $html = parse(\$text, "top"); | |||||
632 | 8 | 77 | return $html; | |||||
633 | } | |||||||
634 | ||||||||
635 | sub creole_plugin { | |||||||
636 | 1 | 50 | 1 | 1 | 17 | return unless defined $_[0]; | ||
637 | 1 | 5 | $plugin_function = $_[0]; | |||||
638 | } | |||||||
639 | ||||||||
640 | sub creole_link { | |||||||
641 | 1 | 50 | 1 | 1 | 17 | return unless defined $_[0]; | ||
642 | 1 | 4 | $link_function = $_[0]; | |||||
643 | } | |||||||
644 | ||||||||
645 | sub creole_customlinks { | |||||||
646 | 0 | 0 | 1 | 0 | $chunks{href}{open} = ""; | |||
647 | 0 | 0 | $chunks{href}{close} = ""; | |||||
648 | 0 | 0 | $chunks{link}{open} = ""; | |||||
649 | 0 | 0 | $chunks{link}{close} = ""; | |||||
650 | 0 | 0 | delete $chunks{link}{contains}; | |||||
651 | $chunks{link}{filter} = sub { | |||||||
652 | 0 | 0 | 0 | 0 | if($link_function) { | |||
653 | 0 | 0 | $_[0] = &$link_function($_[0]); | |||||
654 | } | |||||||
655 | 0 | 0 | return $_[0]; | |||||
656 | } | |||||||
657 | 0 | 0 | } | |||||
658 | ||||||||
659 | sub creole_barelink { | |||||||
660 | 0 | 0 | 0 | 1 | 0 | return unless defined $_[0]; | ||
661 | 0 | 0 | $barelink_function = $_[0]; | |||||
662 | } | |||||||
663 | ||||||||
664 | sub creole_custombarelinks { | |||||||
665 | 0 | 0 | 1 | 0 | $chunks{ilink}{open} = ""; | |||
666 | 0 | 0 | $chunks{ilink}{close} = ""; | |||||
667 | $chunks{ilink}{filter} = sub { | |||||||
668 | 0 | 0 | 0 | 0 | if($barelink_function) { | |||
669 | 0 | 0 | $_[0] = &$barelink_function($_[0]); | |||||
670 | } | |||||||
671 | 0 | 0 | return $_[0]; | |||||
672 | } | |||||||
673 | 0 | 0 | } | |||||
674 | ||||||||
675 | sub creole_customimgs { | |||||||
676 | 0 | 0 | 1 | 0 | $chunks{img}{open} = ""; | |||
677 | 0 | 0 | $chunks{img}{close} = ""; | |||||
678 | 0 | 0 | delete $chunks{img}{contains}; | |||||
679 | $chunks{img}{filter} = sub { | |||||||
680 | 0 | 0 | 0 | 0 | if($img_function) { | |||
681 | 0 | 0 | $_[0] = &$img_function($_[0]); | |||||
682 | } | |||||||
683 | 0 | 0 | return $_[0]; | |||||
684 | } | |||||||
685 | 0 | 0 | } | |||||
686 | ||||||||
687 | sub creole_img { | |||||||
688 | 0 | 0 | 0 | 1 | 0 | return unless defined $_[0]; | ||
689 | 0 | 0 | $img_function = $_[0]; | |||||
690 | } | |||||||
691 | ||||||||
692 | sub creole_tag { | |||||||
693 | 1 | 1 | 1 | 14 | my ($tag, $type, $text) = @_; | |||
694 | 1 | 50 | 5 | if(! $tag) { | ||||
695 | 0 | 0 | foreach (sort keys %chunks) { | |||||
696 | 0 | 0 | my $o = $chunks{$_}{open}; | |||||
697 | 0 | 0 | my $c = $chunks{$_}{close}; | |||||
698 | 0 | 0 | 0 | 0 | next unless $o && $o =~ / | |||
699 | 0 | 0 | 0 | $o =~ s/\n/\\n/gso if $o; $o = "" unless $o; | ||||
0 | 0 | 0 | ||||||
700 | 0 | 0 | 0 | $c =~ s/\n/\\n/gso if $c; $c = "" unless $c; | ||||
0 | 0 | 0 | ||||||
701 | 0 | 0 | print "$_: open($o) close($c)\n"; | |||||
702 | } | |||||||
703 | } else { | |||||||
704 | 1 | 50 | 33 | 8 | return unless ($type eq "open" || $type eq "close"); | |||
705 | 1 | 50 | 7 | return unless $chunks{$tag}; | ||||
706 | 1 | 50 | 8 | $chunks{$tag}{$type} = $text ? $text : ""; | ||||
707 | } | |||||||
708 | } | |||||||
709 | ||||||||
710 | 1; | |||||||
711 | __END__ |