| 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__ |