File Coverage

blib/lib/Text/Textile.pm
Criterion Covered Total %
statement 360 1208 29.8
branch 122 872 13.9
condition 23 269 8.5
subroutine 28 59 47.4
pod 40 44 90.9
total 573 2452 23.3


$row_out}; };
line stmt bran cond sub pod time code
1             package Text::Textile;
2              
3 7     7   206330 use strict;
  7         19  
  7         386  
4 7     7   38 use warnings;
  7         13  
  7         315  
5              
6 7     7   37 use base 'Exporter';
  7         16  
  7         12361  
7             our @EXPORT_OK = qw(textile);
8             our $VERSION = 2.13;
9             our $debug = 0;
10              
11             sub new {
12 8     8 1 1299 my $class = shift;
13 8         28 my %options = @_;
14 8   50     209 $options{filters} ||= {};
15 8   50     60 $options{charset} ||= 'iso-8859-1';
16              
17 8         22 for ( qw( char_encoding do_quotes smarty_mode ) ) {
18 24 50       106 $options{$_} = 1 unless exists $options{$_};
19             }
20 8         26 for ( qw( trim_spaces preserve_spaces head_offset disable_encode_entities ) ) {
21 32 100       128 $options{$_} = 0 unless exists $options{$_};
22             }
23              
24 8         56 my $self = bless \%options, $class;
25 8 50       70 if (exists $options{css}) {
26 0         0 $self->css($options{css});
27             }
28 8   33     70 $options{macros} ||= $self->default_macros();
29 8 50       44 if (exists $options{flavor}) {
30 0         0 $self->flavor($options{flavor});
31             } else {
32 8         46 $self->flavor('xhtml1/css');
33             }
34 8         24 return $self;
35             }
36              
37             # getter/setter methods...
38              
39             sub set {
40 0     0 1 0 my $self = shift;
41 0         0 my $opt = shift;
42 0 0       0 if (ref $opt eq 'HASH') {
43 0         0 $self->set($_, $opt->{$_}) foreach %{$opt};
  0         0  
44             } else {
45 0         0 my $value = shift;
46             # the following options have special set methods
47             # that activate upon setting:
48 0 0       0 if ($opt eq 'charset') {
    0          
    0          
49 0         0 $self->charset($value);
50             } elsif ($opt eq 'css') {
51 0         0 $self->css($value);
52             } elsif ($opt eq 'flavor') {
53 0         0 $self->flavor($value);
54             } else {
55 0         0 $self->{$opt} = $value;
56             }
57             }
58 0         0 return;
59             }
60              
61             sub get {
62 0     0 1 0 my $self = shift;
63 0 0       0 return $self->{shift} if @_;
64 0         0 return undef;
65             }
66              
67             sub disable_html {
68 0     0 1 0 my $self = shift;
69 0 0       0 if (@_) {
70 0         0 $self->{disable_html} = shift;
71             }
72 0   0     0 return $self->{disable_html} || 0;
73             }
74              
75             sub head_offset {
76 0     0 0 0 my $self = shift;
77 0 0       0 if (@_) {
78 0         0 $self->{head_offset} = shift;
79             }
80 0   0     0 return $self->{head_offset} || 0;
81             }
82              
83             sub flavor {
84 8     8 1 18 my $self = shift;
85 8 50       39 if (@_) {
86 8         27 my $flavor = shift;
87 8         112 $self->{flavor} = $flavor;
88 8 50       111 if ($flavor =~ m/^xhtml(\d)?(\D|$)/) {
    0          
89 8 50       93 if ($1 eq '2') {
90 0         0 $self->{_line_open} = '';
91 0         0 $self->{_line_close} = '';
92 0         0 $self->{_blockcode_open} = '';
93 0         0 $self->{_blockcode_close} = '';
94 0         0 $self->{css_mode} = 1;
95             } else {
96             # xhtml 1.x
97 8         26 $self->{_line_open} = '';
98 8         21 $self->{_line_close} = '
';
99 8         17 $self->{_blockcode_open} = '
'; 
100 8         26 $self->{_blockcode_close} = '';
101 8         32 $self->{css_mode} = 1;
102             }
103             } elsif ($flavor =~ m/^html/) {
104 0         0 $self->{_line_open} = '';
105 0         0 $self->{_line_close} = '
';
106 0         0 $self->{_blockcode_open} = '
'; 
107 0         0 $self->{_blockcode_close} = '';
108 0         0 $self->{css_mode} = $flavor =~ m/\/css/;
109             }
110 8 50 33     107 $self->_css_defaults() if $self->{css_mode} && !exists $self->{css};
111             }
112 8         20 return $self->{flavor};
113             }
114              
115             sub css {
116 8     8 1 16 my $self = shift;
117 8 50       31 if (@_) {
118 8         13 my $css = shift;
119 8 50       29 if (ref $css eq 'HASH') {
120 8         22 $self->{css} = $css;
121 8         19 $self->{css_mode} = 1;
122             } else {
123 0         0 $self->{css_mode} = $css;
124 0 0 0     0 $self->_css_defaults() if $self->{css_mode} && !exists $self->{css};
125             }
126             }
127 8 50       51 return $self->{css_mode} ? $self->{css} : 0;
128             }
129              
130             sub charset {
131 0     0 1 0 my $self = shift;
132 0 0       0 if (@_) {
133 0         0 $self->{charset} = shift;
134 0 0       0 if ($self->{charset} =~ m/^utf-?8$/i) {
135 0         0 $self->char_encoding(0);
136             } else {
137 0         0 $self->char_encoding(1);
138             }
139             }
140 0         0 return $self->{charset};
141             }
142              
143             sub docroot {
144 0     0 1 0 my $self = shift;
145 0 0       0 $self->{docroot} = shift if @_;
146 0         0 return $self->{docroot};
147             }
148              
149             sub trim_spaces {
150 0     0 1 0 my $self = shift;
151 0 0       0 $self->{trim_spaces} = shift if @_;
152 0         0 return $self->{trim_spaces};
153             }
154              
155             sub filter_param {
156 0     0 1 0 my $self = shift;
157 0 0       0 $self->{filter_param} = shift if @_;
158 0         0 return $self->{filter_param};
159             }
160              
161             sub preserve_spaces {
162 0     0 1 0 my $self = shift;
163 0 0       0 $self->{preserve_spaces} = shift if @_;
164 0         0 return $self->{preserve_spaces};
165             }
166              
167             sub filters {
168 0     0 1 0 my $self = shift;
169 0 0       0 $self->{filters} = shift if @_;
170 0         0 return $self->{filters};
171             }
172              
173             sub char_encoding {
174 0     0 1 0 my $self = shift;
175 0 0       0 $self->{char_encoding} = shift if @_;
176 0         0 return $self->{char_encoding};
177             }
178              
179             sub disable_encode_entities {
180 0     0 1 0 my $self = shift;
181 0 0       0 $self->{disable_encode_entities} = shift if @_;
182 0         0 return $self->{disable_encode_entities};
183             }
184              
185             sub handle_quotes {
186 0     0 1 0 my $self = shift;
187 0 0       0 $self->{do_quotes} = shift if @_;
188 0         0 return $self->{do_quotes};
189             }
190              
191             # end of getter/setter methods
192              
193             # a URL discovery regex. This is from Mastering Regex from O'Reilly.
194             # Some modifications by Brad Choate
195 7         45313 use vars qw($urlre $blocktags $clstyre $clstypadre $clstyfiltre
196             $alignre $valignre $halignre $imgalignre $tblalignre
197 7     7   62 $codere $punct);
  7         13  
198             $urlre = qr{
199             # Must start out right...
200             (?=[a-zA-Z0-9./#])
201             # Match the leading part (proto://hostname, or just hostname)
202             (?:
203             # ftp://, http://, or https:// leading part
204             (?:ftp|https?|telnet|nntp)://(?:\w+(?::\w+)?@)?[-\w]+(?:\.\w[-\w]*)+
205             |
206             (?:mailto:)?[-\+\w]+\@[-\w]+(?:\.\w[-\w]*)+
207             |
208             # or, try to find a hostname with our more specific sub-expression
209             (?i: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \. )+ # sub domains
210             # Now ending .com, etc. For these, require lowercase
211             (?-i: com\b
212             | edu\b
213             | biz\b
214             | gov\b
215             | in(?:t|fo)\b # .int or .info
216             | mil\b
217             | net\b
218             | org\b
219             | museum\b
220             | aero\b
221             | coop\b
222             | name\b
223             | pro\b
224             | [a-z][a-z]\b # two-letter country codes
225             )
226             )?
227              
228             # Allow an optional port number
229             (?: : \d+ )?
230              
231             # The rest of the URL is optional, and begins with / . . .
232             (?:
233             /?
234             # The rest are heuristics for what seems to work well
235             [^.!,?;:"'<>()\[\]{}\s\x7F-\xFF]*
236             (?:
237             [.!,?;:]+ [^.!,?;:"'<>()\[\]{}\s\x7F-\xFF]+ #'"
238             )*
239             )?
240             }x;
241              
242             $punct = qr{[\!"#\$%&'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{\|}\~]};
243             $valignre = qr/[\-^~]/;
244             $tblalignre = qr/[<>=]/;
245             $halignre = qr/(?:<>|[<>=])/;
246             $alignre = qr/(?:$valignre|<>$valignre?|$valignre?<>|$valignre?$halignre?|$halignre?$valignre?)(?!\w)/;
247             $imgalignre = qr/(?:[<>]|$valignre){1,2}/;
248              
249             $clstypadre = qr/
250             (?:\([A-Za-z0-9_\- \#]+\))
251             |
252             (?:{
253             (?: \( [^)]+ \) | [^}] )+
254             })
255             |
256             (?:\(+? (?![A-Za-z0-9_\-\#]) )
257             |
258             (?:\)+?)
259             |
260             (?: \[ [a-zA-Z\-]+? \] )
261             /x;
262              
263             $clstyre = qr/
264             (?:\([A-Za-z0-9_\- \#]+\))
265             |
266             (?:{
267             [A-Za-z0-9_\-](?: \( [^)]+ \) | [^}] )+
268             })
269             |
270             (?: \[ [a-zA-Z\-]+? \] )
271             /x;
272              
273             $clstyfiltre = qr/
274             (?:\([A-Za-z0-9_\- \#]+\))
275             |
276             (?:{
277             [A-Za-z0-9_\-](?: \( [^)]+ \) | [^}] )+
278             })
279             |
280             (?:\|[^\|]+\|)
281             |
282             (?:\(+?(?![A-Za-z0-9_\-\#]))
283             |
284             (?:\)+)
285             |
286             (?: \[ [a-zA-Z]+? \] )
287             /x;
288              
289             $codere = qr/
290             (?:
291             [\[{]
292             @ # opening
293             (?:\[([A-Za-z0-9]+)\])? # $1: language id
294             (.+?) # $2: code
295             @ # closing
296             [\]}]
297             )
298             |
299             (?:
300             (?:^|(?<=[\s\(]))
301             @ # opening
302             (?:\[([A-Za-z0-9]+)\])? # $3: language id
303             ([^\s].*?[^\s]?) # $4: code itself
304             @ # closing
305             (?:$|(?=$punct{1,2}|\s))
306             )
307             /x;
308              
309             $blocktags = qr{
310             <
311             (( /? ( h[1-6]
312             | p
313             | pre
314             | div
315             | table
316             | t[rdh]
317             | [ou]l
318             | li
319             | block(?:quote|code)
320             | form
321             | input
322             | select
323             | option
324             | textarea
325             )
326             [ >]
327             )
328             | !--
329             )
330             }x;
331              
332             sub process {
333 2     2 1 15 my $self = shift;
334 2         15 return $self->textile(@_);
335             }
336              
337             sub textile {
338 8     8 1 1053 my $self = shift;
339 8         19 my ($str) = @_;
340              
341             # disable warnings for the sake of various regex that
342             # have optional matches
343 8         45 local $^W = 0;
344              
345 8 100       36 if (!ref $self) {
346             # oops -- procedural technique used, so make
347             # set $str to $self and instantiate a new object
348             # for self
349 6         14 $str = $self;
350 6         53 $self = new Text::Textile;
351             }
352            
353 8 50       30 return "" unless defined($str);
354              
355             # quick translator for abbreviated block names
356             # to their tag
357 8         38 my %macros = ('bq' => 'blockquote');
358              
359             # an array to hold any portions of the text to be preserved
360             # without further processing by Textile
361 8         16 my @repl;
362              
363             # strip out extra newline characters. we're only matching for \n herein
364             #$str =~ s!(?:\r?\n|\r)!\n!g;
365 8         234 $str =~ s!(?:\015?\012|\015)!\n!g;
366              
367             # optionally remove trailing spaces
368 8 50       35 $str =~ s/ +$//gm if $self->{trim_spaces};
369              
370             # preserve contents of the '==', 'pre', 'blockcode' sections
371 8         29 $str =~ s{(^|\n\n)==(.+?)==($|\n\n)}
372 0         0 {$1."\n\n"._repl(\@repl, $self->format_block(text => $2))."\n\n".$3}ges;
373              
374 8 50       43 unless ($self->{disable_html}) {
375             # preserve style, script tag contents
376 8         24 $str =~ s{(<(style|script)(?:>| .+?>).*?)}{_repl(\@repl, $1)}ges;
  0         0  
377              
378             # preserve HTML comments
379 8         32 $str =~ s{()}{_repl(\@repl, $1)}ges;
  0         0  
380              
381             # preserve pre block contents, encode contents by default
382 8         19 my $pre_start = scalar(@repl);
383 8         109 $str =~ s{(]*)?>)(.+?)()}
384 0         0 {"\n\n"._repl(\@repl, $1.$self->encode_html($2, 1).$3)."\n\n"}ges;
385             # fix code tags within pre blocks we just saved.
386 8         40 for (my $i = $pre_start; $i < scalar(@repl); $i++) {
387 0         0 $repl[$i] =~ s{<(/?)code(.*?)>}{<$1code$2>}gs;
388             }
389              
390             # preserve code blocks by default, encode contents
391 8         22 $str =~ s{(]+)?>)(.+?)()}
392 0         0 {_repl(\@repl, $1.$self->encode_html($2, 1).$3)}ges;
393              
394             # encode blockcode tag (an XHTML 2 tag) and encode it's
395             # content by default
396 8         29 $str =~ s{(]+)?>)(.+?)()}
397 0         0 {"\n\n"._repl(\@repl, $1.$self->encode_html($2, 1).$3)."\n\n"}ges;
398              
399             # preserve PHPish, ASPish code
400 8         26 $str =~ s!(<([\?\%]).*?(\2)>)!_repl(\@repl, $1)!ges;
  0         0  
401             }
402              
403             # pass through and remove links that follow this format
404             # [id_without_spaces (optional title text)]url
405             # lines like this are stripped from the content, and can be
406             # referred to using the "link text":id_without_spaces syntax
407 8         13 my %links;
408 8         29 $str =~ s{(?:\n|^) [ ]* \[ ([^ ]+?) [ ]*? (?:\( (.+?) \) )? \] ((?:(?:ftp|https?|telnet|nntp)://|/)[^ ]+?) [ ]* (\n|$)}
409 0         0 {($links{$1} = {url => $3, title => $2}),"$4"}gemx;
410 8         35 local $self->{links} = \%links;
411              
412             # eliminate starting/ending blank lines
413 8         19 $str =~ s/^\n+//s;
414 8         47 $str =~ s/\n+$//s;
415              
416             # split up text into paragraph blocks, capturing newlines too
417 8         80 my @para = split /(\n{2,})/, $str;
418 8         18 my ($block, $bqlang, $filter, $class, $sticky, @lines,
419             $style, $stickybuff, $lang, $clear);
420              
421 8         24 my $out = '';
422              
423 8         22 foreach my $para (@para) {
424 32 100       130 if ($para =~ m/^\n+$/s) {
425 12 50 33     41 if ($sticky && defined $stickybuff) {
426 0         0 $stickybuff .= $para;
427             } else {
428 12         21 $out .= $para;
429             }
430 12         26 next;
431             }
432              
433 20 50       53 if ($sticky) {
434 0         0 $sticky++;
435             } else {
436 20         31 $block = undef;
437 20         25 $class = undef;
438 20         34 $style = '';
439 20         34 $lang = undef;
440             }
441              
442 20         30 my ($id, $cite, $align, $padleft, $padright, @lines, $buffer);
443 20 50 33     5869 if ($para =~ m/^(h[1-6]|p|bq|bc|fn\d+)
    50 0        
    50 33        
    50          
    100          
    50          
    50          
444             ((?:$clstyfiltre*|$halignre)*)
445             (\.\.?)
446             (?::(\d+|$urlre))?\ /gx) {
447 0 0       0 if ($sticky) {
448 0 0       0 if ($block eq 'bc') {
    0          
    0          
    0          
449             # close our blockcode section
450 0         0 $out =~ s/\n\n$//;
451 0         0 $out .= $self->{_blockcode_close}."\n\n";
452             } elsif ($block eq 'bq') {
453 0         0 $out =~ s/\n\n$//;
454 0         0 $out .= ''."\n\n";
455             } elsif ($block eq 'table') {
456 0         0 my $table_out = $self->format_table(text => $stickybuff);
457 0 0       0 $table_out = '' if !defined $table_out;
458 0         0 $out .= $table_out;
459 0         0 $stickybuff = undef;
460             } elsif ($block eq 'dl') {
461 0         0 my $dl_out = $self->format_deflist(text => $stickybuff);
462 0 0       0 $dl_out = '' if !defined $dl_out;
463 0         0 $out .= $dl_out;
464 0         0 $stickybuff = undef;
465             }
466 0         0 $sticky = 0;
467             }
468             # block macros: h[1-6](class)., bq(class)., bc(class)., p(class).
469             #warn "paragraph: [[$para]]\n\tblock: $1\n\tparams: $2\n\tcite: $4";
470 0         0 $block = $1;
471 0         0 my $params = $2;
472 0         0 $cite = $4;
473 0 0       0 if ($3 eq '..') {
474 0         0 $sticky = 1;
475             } else {
476 0         0 $sticky = 0;
477 0         0 $class = undef;
478 0         0 $bqlang = undef;
479 0         0 $lang = undef;
480 0         0 $style = '';
481 0         0 $filter = undef;
482             }
483 0 0       0 if ($block =~ m/^h([1-6])$/) {
484 0 0       0 if ($self->{head_offset}) {
485 0         0 $block = 'h' . ($1 + $self->{head_offset});
486             }
487             }
488 0 0       0 if ($params =~ m/($halignre+)/) {
489 0         0 $align = $1;
490 0         0 $params =~ s/$halignre+//;
491             }
492 0 0       0 if (defined $params) {
493 0 0       0 if ($params =~ m/\|(.+)\|/) {
494 0         0 $filter = $1;
495 0         0 $params =~ s/\|.+?\|//;
496             }
497 0 0       0 if ($params =~ m/{([^}]+)}/) {
498 0         0 $style = $1;
499 0         0 $style =~ s/\n/ /g;
500 0         0 $params =~ s/{[^}]+}//g;
501             }
502 0 0 0     0 if ($params =~ m/\(([A-Za-z0-9_\-\ ]+?)(?:\#(.+?))?\)/ ||
503             $params =~ m/\(([A-Za-z0-9_\-\ ]+?)?(?:\#(.+?))\)/) {
504 0 0 0     0 if ($1 || $2) {
505 0         0 $class = $1;
506 0         0 $id = $2;
507 0 0       0 if ($class) {
    0          
508 0         0 $params =~ s/\([A-Za-z0-9_\-\ ]+?(#.*?)?\)//g;
509             } elsif ($id) {
510 0         0 $params =~ s/\(#.+?\)//g;
511             }
512             }
513             }
514 0 0       0 if ($params =~ m/(\(+)/) {
515 0         0 $padleft = length($1);
516 0         0 $params =~ s/\(+//;
517             }
518 0 0       0 if ($params =~ m/(\)+)/) {
519 0         0 $padright = length($1);
520 0         0 $params =~ s/\)+//;
521             }
522 0 0       0 if ($params =~ m/\[(.+?)\]/) {
523 0         0 $lang = $1;
524 0 0       0 if ($block eq 'bc') {
525 0         0 $bqlang = $lang;
526 0         0 $lang = undef;
527             }
528 0         0 $params =~ s/\[.+?\]//;
529             }
530             }
531             #warn "settings:\n\tblock: $block\n\tpadleft: $padleft\n\tpadright: $padright\n\tclass: $class\n\tstyle: $style\n\tid: $id\n\tfilter: $filter\n\talign: $align\n\tlang: $lang\n\tsticky: $sticky";
532 0         0 $para = substr($para, pos($para));
533             } elsif ($para =~ m/^$/) {
534 0         0 $buffer = $repl[$1-1];
535             } elsif ($para =~ m/^clear([<>]+)?\.$/) {
536 0 0       0 if ($1 eq '<') {
    0          
537 0         0 $clear = 'left';
538             } elsif ($1 eq '>') {
539 0         0 $clear = 'right';
540             } else {
541 0         0 $clear = 'both';
542             }
543 0         0 next;
544             } elsif ($sticky && (defined $stickybuff) &&
545             ($block eq 'table' || $block eq 'dl')) {
546 0         0 $stickybuff .= $para;
547 0         0 next;
548             } elsif ($para =~ m/^(?:$halignre|$clstypadre*)*
549             [\*\#]
550             (?:$halignre|$clstypadre*)*
551             \ /x) {
552             # '*', '#' prefix means a list
553 5         29 $buffer = $self->format_list(text => $para);
554             } elsif ($para =~ m/^(?:table(?:$tblalignre|$clstypadre*)*
555             (\.\.?)\s+)?
556             (?:_|$alignre|$clstypadre*)*\|/x) {
557             # handle wiki-style tables
558 0 0 0     0 if (defined $1 && ($1 eq '..')) {
559 0         0 $block = 'table';
560 0         0 $stickybuff = $para;
561 0         0 $sticky = 1;
562 0         0 next;
563             } else {
564 0         0 $buffer = $self->format_table(text => $para);
565             }
566             } elsif ($para =~ m/^(?:dl(?:$clstyre)*(\.\.?)\s+)/) {
567             # handle definition lists
568 0 0 0     0 if (defined $1 && ($1 eq '..')) {
569 0         0 $block = 'dl';
570 0         0 $stickybuff = $para;
571 0         0 $sticky = 1;
572 0         0 next;
573             } else {
574 0         0 $buffer = $self->format_deflist(text => $para);
575             }
576             }
577 20 100       180 if (defined $buffer) {
578 5         11 $out .= $buffer;
579 5         21 next;
580             }
581 15         62 @lines = split /\n/, $para;
582 15 50       47 next unless @lines;
583              
584 15   50     95 $block ||= 'p';
585              
586 15         24 $buffer = '';
587 15         27 my $pre = '';
588 15         31 my $post = '';
589              
590 15 50       118 if ($block eq 'bc') {
    50          
    50          
591 0 0       0 if ($sticky <= 1) {
592 0         0 $pre .= $self->{_blockcode_open};
593 0         0 $pre =~ s/>$//s;
594 0 0       0 $pre .= qq{ language="$bqlang"} if $bqlang;
595 0 0       0 if ($align) {
596 0         0 my $alignment = _halign($align);
597 0 0       0 if ($self->{css_mode}) {
598 0 0 0     0 if (($padleft || $padright) &&
      0        
      0        
599             (($alignment eq 'left') || ($alignment eq 'right'))) {
600 0         0 $style .= ';float:'.$alignment;
601             } else {
602 0         0 $style .= ';text-align:'.$alignment;
603             }
604 0   0     0 $class .= ' '.$self->{css}{"class_align_$alignment"} || $alignment;
605             } else {
606 0 0       0 $pre .= qq{ align="$alignment"} if $alignment;
607             }
608             }
609 0 0       0 $style .= qq{;padding-left:${padleft}em} if $padleft;
610 0 0       0 $style .= qq{;padding-right:${padright}em} if $padright;
611 0 0       0 $style .= qq{;clear:${clear}} if $clear;
612 0 0       0 $class =~ s/^ // if $class;
613 0 0       0 $pre .= qq{ class="$class"} if $class;
614 0 0       0 $pre .= qq{ id="$id"} if $id;
615 0 0       0 $style =~ s/^;// if $style;
616 0 0       0 $pre .= qq{ style="$style"} if $style;
617 0 0       0 $pre .= qq{ lang="$lang"} if $lang;
618 0         0 $pre .= '>';
619 0         0 $lang = undef;
620 0         0 $bqlang = undef;
621 0         0 $clear = undef;
622             }
623 0         0 $para =~ s{(?:^|(?<=[\s>])|([{[]))
624             ==(.+?)==
625 0         0 (?:$|([\]}])|(?=$punct{1,2}|\s))}
626             {_repl(\@repl, $self->format_block(text => $2, inline => 1, pre => $1, post => $3))}gesx;
627 0         0 $buffer .= $self->encode_html_basic($para, 1);
628 0         0 $buffer =~ s/<textile#(\d+)>//g;
629 0 0       0 if ($sticky == 0) {
630 0         0 $post .= $self->{_blockcode_close};
631             }
632 0         0 $out .= $pre . $buffer . $post;
633 0         0 next;
634             } elsif ($block eq 'bq') {
635 0 0       0 if ($sticky <= 1) {
636 0         0 $pre .= '
637 0 0       0 if ($align) {
638 0         0 my $alignment = _halign($align);
639 0 0       0 if ($self->{css_mode}) {
640 0 0 0     0 if (($padleft || $padright) &&
      0        
      0        
641             (($alignment eq 'left') || ($alignment eq 'right'))) {
642 0         0 $style .= ';float:'.$alignment;
643             } else {
644 0         0 $style .= ';text-align:'.$alignment;
645             }
646 0   0     0 $class .= ' '.$self->{css}{"class_align_$alignment"} || $alignment;
647             } else {
648 0 0       0 $pre .= qq{ align="$alignment"} if $alignment;
649             }
650             }
651 0 0       0 $style .= qq{;padding-left:${padleft}em} if $padleft;
652 0 0       0 $style .= qq{;padding-right:${padright}em} if $padright;
653 0 0       0 $style .= qq{;clear:${clear}} if $clear;
654 0 0       0 $class =~ s/^ // if $class;
655 0 0       0 $pre .= qq{ class="$class"} if $class;
656 0 0       0 $pre .= qq{ id="$id"} if $id;
657 0 0       0 $style =~ s/^;// if $style;
658 0 0       0 $pre .= qq{ style="$style"} if $style;
659 0 0       0 $pre .= qq{ lang="$lang"} if $lang;
660 0 0       0 $pre .= q{ cite="} . $self->format_url(url => $cite) . '"' if defined $cite;
661 0         0 $pre .= '>';
662 0         0 $clear = undef;
663             }
664 0         0 $pre .= '

';

665             } elsif ($block =~ m/fn(\d+)/) {
666 0         0 my $fnum = $1;
667 0         0 $pre .= '
668 0 0       0 $class .= ' '.$self->{css}{class_footnote} if $self->{css}{class_footnote};
669 0 0       0 if ($align) {
670 0         0 my $alignment = _halign($align);
671 0 0       0 if ($self->{css_mode}) {
672 0 0 0     0 if (($padleft || $padright) &&
      0        
      0        
673             (($alignment eq 'left') || ($alignment eq 'right'))) {
674 0         0 $style .= ';float:'.$alignment;
675             } else {
676 0         0 $style .= ';text-align:'.$alignment;
677             }
678 0   0     0 $class .= $self->{css}{"class_align_$alignment"} || $alignment;
679             } else {
680 0         0 $pre .= qq{ align="$alignment"};
681             }
682             }
683 0 0       0 $style .= qq{;padding-left:${padleft}em} if $padleft;
684 0 0       0 $style .= qq{;padding-right:${padright}em} if $padright;
685 0 0       0 $style .= qq{;clear:${clear}} if $clear;
686 0 0       0 $class =~ s/^ // if $class;
687 0 0       0 $pre .= qq{ class="$class"} if $class;
688 0   0     0 $pre .= qq{ id="}.($self->{css}{id_footnote_prefix}||'fn').$fnum.'"';
689 0 0       0 $style =~ s/^;// if $style;
690 0 0       0 $pre .= qq{ style="$style"} if $style;
691 0 0       0 $pre .= qq{ lang="$lang"} if $lang;
692 0         0 $pre .= '>';
693 0         0 $pre .= ''.$fnum.' ';
694             # we can close like a regular paragraph tag now
695 0         0 $block = 'p';
696 0         0 $clear = undef;
697             } else {
698 15   33     196 $pre .= '<' . ($macros{$block} || $block);
699 15 50       43 if ($align) {
700 0         0 my $alignment = _halign($align);
701 0 0       0 if ($self->{css_mode}) {
702 0 0 0     0 if (($padleft || $padright) &&
      0        
      0        
703             (($alignment eq 'left') || ($alignment eq 'right'))) {
704 0         0 $style .= ';float:'.$alignment;
705             } else {
706 0         0 $style .= ';text-align:'.$alignment;
707             }
708 0   0     0 $class .= ' '.$self->{css}{"class_align_$alignment"} || $alignment;
709             } else {
710 0         0 $pre .= qq{ align="$alignment"};
711             }
712             }
713 15 50       113 $style .= qq{;padding-left:${padleft}em} if $padleft;
714 15 50       40 $style .= qq{;padding-right:${padright}em} if $padright;
715 15 50       38 $style .= qq{;clear:${clear}} if $clear;
716 15 50       40 $class =~ s/^ // if $class;
717 15 50       36 $pre .= qq{ class="$class"} if $class;
718 15 50       40 $pre .= qq{ id="$id"} if $id;
719 15 50       45 $style =~ s/^;// if $style;
720 15 50       38 $pre .= qq{ style="$style"} if $style;
721 15 50       36 $pre .= qq{ lang="$lang"} if $lang;
722 15 50 33     48 $pre .= qq{ cite="} . $self->format_url(url => $cite) . '"' if defined $cite && $block eq 'bq'; #'
723 15         28 $pre .= '>';
724 15         30 $clear = undef;
725             }
726              
727 15         61 $buffer = $self->format_paragraph(text => $para);
728              
729 15 50       48 if ($block eq 'bq') {
730 0 0       0 $post .= '

' if $buffer !~ m/]/;
731 0 0       0 if ($sticky == 0) {
732 0         0 $post .= '';
733             }
734             } else {
735 15         40 $post .= '';
736             }
737              
738 15 50       97 if ($buffer =~ m/$blocktags/) {
739 0         0 $buffer =~ s/^\n\n//s;
740 0         0 $out .= $buffer;
741             } else {
742 15 50       43 $buffer = $self->format_block(text => "|$filter|".$buffer, inline => 1) if defined $filter;
743 15         95 $out .= $pre . $buffer . $post;
744             }
745             }
746              
747 8 50       31 if ($sticky) {
748 0 0 0     0 if ($block eq 'bc') {
    0 0        
    0          
    0          
749             # close our blockcode section
750 0         0 $out .= $self->{_blockcode_close}; # . "\n\n";
751             } elsif ($block eq 'bq') {
752 0         0 $out .= ''; # . "\n\n";
753             } elsif (($block eq 'table') && ($stickybuff)) {
754 0         0 my $table_out = $self->format_table(text => $stickybuff);
755 0 0       0 $out .= $table_out if defined $table_out;
756             } elsif (($block eq 'dl') && ($stickybuff)) {
757 0         0 my $dl_out = $self->format_deflist(text => $stickybuff);
758 0 0       0 $out .= $dl_out if defined $dl_out;
759             }
760             }
761              
762             # cleanup-- restore preserved blocks
763 8         17 my $i = scalar(@repl);
764 8         37 $out =~ s!(?:<|<)textile#$i(?:>|>)!$_!, $i-- while local $_ = pop @repl;
765              
766             # scan for br, hr tags that are not closed and close them
767             # only for xhtml! just the common ones -- don't fret over input
768             # and the like.
769 8 50       45 if ($self->{flavor} =~ m/^xhtml/i) {
770 8         54 $out =~ s/(<(?:img|br|hr)[^>]*?(?/$1 \/>/g;
771             }
772              
773 8         492 return $out;
774             }
775              
776             sub format_paragraph {
777 31     31 1 52 my $self = shift;
778 31         91 my (%args) = @_;
779 31 50       97 my $buffer = defined $args{text} ? $args{text} : '';
780              
781 31         41 my @repl;
782 31         476 $buffer =~ s{(?:^|(?<=[\s>])|([{[]))
783             ==(.+?)==
784 0         0 (?:$|([\]}])|(?=$punct{1,2}|\s))}
785             {_repl(\@repl, $self->format_block(text => $2, inline => 1, pre => $1, post => $3))}gesx;
786              
787 31         48 my $tokens;
788 31 100 66     135 if ($buffer =~ m/{disable_html})) { # optimization -- no point in tokenizing if we
789             # have no tags to tokenize
790 2         7 $tokens = _tokenize($buffer);
791             } else {
792 29         98 $tokens = [['text', $buffer]];
793             }
794 31         54 my $result = '';
795 31         39 foreach my $token (@{$tokens}) {
  31         83  
796 31         57 my $text = $token->[1];
797 31 50       70 if ($token->[0] eq 'tag') {
798 0         0 $text =~ s/&(?!amp;)/&/g;
799 0         0 $result .= $text;
800             } else {
801 31         115 $text = $self->format_inline(text => $text);
802 31         128 $result .= $text;
803             }
804             }
805              
806             # now, add line breaks for lines that contain plaintext
807 31         110 my @lines = split /\n/, $result;
808 31         53 $result = '';
809 31         39 my $needs_closing = 0;
810 31         59 foreach my $line (@lines) {
811 35 50 66     922 if (($line !~ m/($blocktags)/)
      33        
812             && (($line =~ m/^[^<]/ || $line =~ m/>[^<]/)
813             || ($line !~ m/
814 35 50       93 if ($self->{_line_open}) {
815 0 0       0 $result .= "\n" if $result ne '';
816 0         0 $result .= $self->{_line_open} . $line . $self->{_line_close};
817             } else {
818 35 100       71 if ($needs_closing) {
819 4         19 $result .= $self->{_line_close} ."\n";
820             } else {
821 31         44 $needs_closing = 1;
822 31 50       95 $result .= "\n" if $result ne '';
823             }
824 35         124 $result .= $line;
825             }
826             } else {
827 0 0       0 if ($needs_closing) {
828 0         0 $result .= $self->{_line_close} ."\n";
829             } else {
830 0 0       0 $result .= "\n" if $result ne '';
831             }
832 0         0 $result .= $line;
833 0         0 $needs_closing = 0;
834             }
835             }
836              
837             # at this point, we will restore the \001's to \n's (reversing
838             # the step taken in _tokenize).
839             #$result =~ s/\r/\n/g;
840 31         63 $result =~ s/\001/\n/g;
841              
842 31         43 my $i = scalar(@repl);
843 31         116 $result =~ s||$_|, $i-- while local $_ = pop @repl;
844              
845             # quotalize
846 31 50       85 if ($self->{do_quotes}) {
847 31         94 $result = $self->process_quotes($result);
848             }
849              
850 31         170 return $result;
851             }
852              
853             {
854             my @qtags = (['**', 'b', '(?
855             ['__', 'i', '(?
856             ['??', 'cite', '\?\?(?!\?)', '\?'],
857             ['*', 'strong', '(?
858             ['_', 'em', '(?
859             ['-', 'del', '(?
860             ['+', 'ins', '(?
861             ['++', 'big', '(?
862             ['--', 'small', '(?
863             ['~', 'sub', '(?
864              
865              
866             sub format_inline {
867 31     31 1 56 my $self = shift;
868 31         94 my (%args) = @_;
869 31 50       106 my $text = defined $args{text} ? $args{text} : '';
870              
871 31         41 my @repl;
872              
873 7     7   89 no warnings 'uninitialized';
  7         16  
  7         111788  
874 31         365 $text =~ s{$codere}{_repl(\@repl, $self->format_code(text => $2.$4, lang => $1.$3))}gem;
  5         28  
875              
876             # images must be processed before encoding the text since they might
877             # have the <, > alignment specifiers...
878              
879             # !blah (alt)! -> image
880 31         3374 $text =~ s!(?:^|(?<=[\s>])|([{[])) # $1: open brace/bracket
881             \! # opening
882 0   0     0 ($imgalignre?) # $2: optional alignment
883             ($clstypadre*) # $3: optional CSS class/id
884             ($imgalignre?) # $4: optional alignment
885             (?:(?<=[^\!])\s+)? # optional space between alignment/css stuff
886             ([^\s\(\!]+) # $5: filename
887             (\s*[^\(\!]*(?:\([^\)]+\))?[^\!]*) # $6: extras (alt text)
888             \! # closing
889             (?::(\d+|$urlre))? # $7: optional URL
890             (?:$|([\]}])|(?=$punct{1,2}|\s))# $8: closing brace/bracket
891             !_repl(\@repl, $self->format_image(pre => $1, src => $5, align => $2||$4, extra => $6, url => $7, clsty => $3, post => $8))!gemx;
892              
893 31         3640 $text =~ s!(?:^|(?<=[\s>])|([{[])) # $1: open brace/bracket
894             \% # opening
895 0   0     0 ($halignre?) # $2: optional alignment
896             ($clstyre*) # $3: optional CSS class/id
897             ($halignre?) # $4: optional alignment
898             (?:\s*) # spacing
899             ([^\%]+?) # $5: text
900             \% # closing
901             (?::(\d+|$urlre))? # $6: optional URL
902             (?:$|([\]}])|(?=$punct{1,2}|\s))# $7: closing brace/bracket
903             !_repl(\@repl, $self->format_span(pre => $1,text => $5,align => $2||$4, cite => $6, clsty => $3, post => $7))!gemx;
904              
905 31         125 $text = $self->encode_html($text);
906 31         77 $text =~ s!<textile#(\d+)>!!g;
907 31         54 $text =~ s!&quot;!"!g;
908 31         92 $text =~ s!&(([a-zA-Z0-9]+|#\d+|#x[0-9A-Fa-f]+);)!&$1!g;
909 31         59 $text =~ s!"!"!g; #"
910              
911             # These create markup with entities. Do first and 'save' result for later:
912             # "text":url -> hyperlink
913             # links with brackets surrounding
914 31         125 my $parenre = qr/\( (?: [^()] )* \)/x;
915 31         1128 $text =~ s!(
916             [{[]
917             (?:
918             (?:" # quote character
919 0 0       0 ($clstyre*)? # $2: optional CSS class/id
    0          
    0          
920             ([^"]+?) # $3: link text
921             (?:\( ( (?:[^()]|$parenre)*) \))? # $4: optional link title
922             " # closing quote
923             )
924             |
925             (?:' # open single quote
926             ($clstyre*)? # $5: optional CSS class/id
927             ([^']+?) # $6: link text
928             (?:\( ( (?:[^()]|$parenre)*) \))? # $7: optional link title
929             ' # closing quote
930             )
931             )
932             :(.+?) # $8: URL suffix
933             [\]}]
934             )
935             !_repl(\@repl,
936             $self->format_link(
937             text => $1,
938             linktext => defined $3 ? $3 : $6,
939             title => $self->encode_html_basic( defined $4 ? $4 : $7 ),
940             url => $8,
941             clsty => defined $2 ? $2 : $5)
942             )!gemx;
943              
944 31         3619 $text =~ s!((?:^|(?<=[\s>\(])) # $1: open brace/bracket
945             (?: (?:" # quote character "
946 4 50       91 ($clstyre*)? # $2: optional CSS class/id
    50          
    50          
947             ([^"]+?) # $3: link text "
948             (?:\( ( (?:[^()]|$parenre)*) \))? # $4: optional link title
949             " # closing quote # "
950             )
951             |
952             (?:' # open single quote '
953             ($clstyre*)? # $5: optional CSS class/id
954             ([^']+?) # $6: link text '
955             (?:\( ( (?:[^()]|$parenre)*) \))? # $7: optional link title
956             ' # closing quote '
957             )
958             )
959             :(\d+|$urlre) # $8: URL suffix
960             (?:$|(?=$punct{1,2}|\s))) # $9: closing brace/bracket
961             !_repl(\@repl,
962             $self->format_link(
963             text => $1,
964             linktext => defined $3 ? $3 : $6,
965             title => $self->encode_html_basic( defined $4 ? $4 : $7 ),
966             url => $8,
967             clsty => defined $2 ? $2 : $5)
968             )!gemx;
969              
970 31 50       231 if ($self->{flavor} =~ m/^xhtml2/) {
971             # citation with cite link
972 0         0 $text =~ s!(?:^|(?<=[\s>'"\(])|([{[])) # $1: open brace/bracket '
973             \?\? # opening '??'
974             ([^\?]+?) # $2: characters (can't contain '?')
975             \?\? # closing '??'
976 0         0 :(\d+|$urlre) # $3: optional citation URL
977             (?:$|([\]}])|(?=$punct{1,2}|\s))# $4: closing brace/bracket
978             !_repl(\@repl, $self->format_cite(pre => $1,text => $2,cite => $3,post => $4))!gemx;
979             }
980              
981             # footnotes
982 31 50       96 if ($text =~ m/[^ ]\[\d+\]/) {
983 0         0 my $fntag = '
984 0 0       0 $fntag .= ' class="'.$self->{css}{class_footnote}.'"' if $self->{css}{class_footnote};
985 0   0     0 $fntag .= '> 986 0         0 $text =~ s{([^ ])\[(\d+)\]}{$1$fntag$2">$2}g;
987             }
988              
989             # translate macros:
990 31         57 $text =~ s{(\{)(.+?)(\})}
991 0         0 {$self->format_macro(pre => $1, post => $3, macro => $2)}gex;
992              
993             # these were present with textile 1 and are common enough
994             # to not require macro braces...
995             # (tm) -> ™
996 31         96 $text =~ s{[\(\[]TM[\)\]]}{™}gi;
997             # (c) -> ©
998 31         54 $text =~ s{[\(\[]C[\)\]]}{©}gi;
999             # (r) -> ®
1000 31         49 $text =~ s{[\(\[]R[\)\]]}{®}gi;
1001              
1002 31 50       89 if ($self->{preserve_spaces}) {
1003             # replace two spaces with an em space
1004 0         0 $text =~ s/(?
1005             }
1006              
1007 31         92 $text = $self->format_phrase_modifiers( text => $text );
1008              
1009             # ABC(Aye Bee Cee) -> acronym
1010 31         56 $text =~ s{\b([A-Z][A-Za-z0-9]*?[A-Z0-9]+?)\b(?:[(]([^)]*)[)])}
1011 0         0 {_repl(\@repl,qq{$1})}ge;
1012              
1013             # ABC -> 'capped' span
1014 31 50       132 if (my $caps = $self->{css}{class_caps}) {
1015 31         260 $text =~ s/(^|[^"][>\s]) # "
1016             ((?:[A-Z](?:[A-Z0-9\.,']|\&){2,}\ *)+?) # '
1017             (?=[^A-Z\.0-9]|$)
1018 0         0 /$1._repl(\@repl, qq{$2<\/span>})/gemx;
1019             }
1020              
1021             # nxn -> n×n
1022 31         78 $text =~ s{((?:[0-9\.]0|[1-9]|\d['"])\ ?)x(\ ?\d)}{$1×$2}g;
1023              
1024             # translate these entities to the Unicode equivalents:
1025 31         51 $text =~ s/…/…/g;
1026 31         49 $text =~ s/‘/‘/g;
1027 31         46 $text =~ s/’/’/g;
1028 31         43 $text =~ s/“/“/g;
1029 31         46 $text =~ s/”/”/g;
1030 31         43 $text =~ s/–/–/g;
1031 31         45 $text =~ s/—/—/g;
1032              
1033             # Restore replacements done earlier:
1034 31         49 my $i = scalar(@repl);
1035 31         231 $text =~ s||$_|, $i-- while local $_ = pop @repl;
1036              
1037             # translate entities to characters for highbit stuff since
1038             # we're using utf8
1039             # removed for backward compatability with older versions of Perl
1040             #if ($self->{charset} =~ m/^utf-?8$/i) {
1041             # # translate any unicode entities to native UTF-8
1042             # $text =~ s/\&\#(\d+);/($1 > 127) ? pack('U',$1) : chr($1)/ge;
1043             #}
1044              
1045 31         192 $text;
1046             }
1047              
1048             sub format_phrase_modifiers {
1049 31     31 0 49 my $self = shift;
1050 31         90 my (%args) = @_;
1051 31 50       95 my $text = defined $args{text} ? $args{text} : '';
1052              
1053 31         70 my $redo = $text =~ m/[\*_\?\-\+\^\~]/;
1054 31         47 my $last = $text;
1055 31         86 while ($redo) {
1056             # simple replacements...
1057 2         3 $redo = 0;
1058 2         4 foreach my $tag (@qtags) {
1059 20         26 my ($f, $r, $qf, $cls) = @{$tag};
  20         50  
1060 20 50       2336 if ($text =~ s/(?:^|(?<=[\s>'"])|([{[])) # "' $1 - pre
1061 0         0 $qf #
1062             (?:($clstyre*))? # $2 - attributes
1063             ([^$cls\s].*?) # $3 - content
1064             (?<=\S)$qf #
1065             (?:$|([\]}])|(?=$punct{1,2}|\s)) # $4 - post
1066             /$self->format_tag(tag => $r, marker => $f, pre => $1, text => $3, clsty => $2, post => $4)/gemx) {
1067 0   0     0 $redo ||= $last ne $text;
1068 0         0 $last = $text;
1069             }
1070             }
1071             }
1072              
1073             # superscript is an even simpler replacement...
1074 31         51 $text =~ s/(?$1<\/sup>/g;
1075              
1076 31         109 return $text;
1077              
1078             }
1079              
1080             }
1081              
1082             {
1083             # pull in charnames, but only for Perl 5.8 or later (and
1084             # disable strict subs for backward compatability
1085             my $Have_Charnames = 0;
1086             if ($] >= 5.008) {
1087 7     7   8053 eval 'use charnames qw(:full);';
  7         279692  
  7         52  
1088             $Have_Charnames = 1;
1089             }
1090              
1091             sub format_macro {
1092 0     0 1 0 my $self = shift;
1093 0         0 my %attrs = @_;
1094 0         0 my $macro = $attrs{macro};
1095 0 0       0 if (defined $self->{macros}->{$macro}) {
1096 0         0 return $self->{macros}->{$macro};
1097             }
1098              
1099             # handle full unicode name translation
1100 0 0       0 if ($Have_Charnames) {
1101             # charnames::vianame is only available in Perl 5.8.0 and later...
1102 0 0       0 if (defined (my $unicode = charnames::vianame(uc($macro)))) {
1103 0         0 return '&#'.$unicode.';';
1104             }
1105             }
1106              
1107 0         0 return $attrs{pre}.$macro.$attrs{post};
1108             }
1109             }
1110              
1111             sub format_cite {
1112 0     0 1 0 my $self = shift;
1113 0         0 my (%args) = @_;
1114 0 0       0 my $pre = defined $args{pre} ? $args{pre} : '';
1115 0 0       0 my $text = defined $args{text} ? $args{text} : '';
1116 0 0       0 my $post = defined $args{post} ? $args{post} : '';
1117 0         0 my $cite = $args{cite};
1118 0         0 _strip_borders(\$pre, \$post);
1119 0         0 my $tag = $pre.'
1120 0 0 0     0 if (($self->{flavor} =~ m/^xhtml2/) && defined $cite && $cite) {
      0        
1121 0         0 $cite = $self->format_url(url => $cite);
1122 0         0 $tag .= qq{ cite="$cite"};
1123             } else {
1124 0         0 $post .= ':';
1125             }
1126 0         0 $tag .= '>';
1127 0         0 return $tag . $self->format_inline(text => $text) . ''.$post;
1128             }
1129              
1130             sub format_code {
1131 5     5 1 7 my $self = shift;
1132 5         11 my (%args) = @_;
1133 5 50       15 my $code = defined $args{text} ? $args{text} : '';
1134 5         7 my $lang = $args{lang};
1135 5         11 $code = $self->encode_html($code, 1);
1136 5         6 $code =~ s/<textile#(\d+)>//g;
1137 5         8 my $tag = '
1138 5 50       9 $tag .= " language=\"$lang\"" if $lang;
1139 5         21 return $tag . '>' . $code . '';
1140             }
1141              
1142             sub format_classstyle {
1143 4     4 1 7 my $self = shift;
1144 4         17 my ($clsty, $class, $style) = @_;
1145              
1146 4 50       17 $style = '' if not defined $style;
1147 4 50       21 $class =~ s/^ // if defined $class;
1148              
1149 4         9 my ($lang, $padleft, $padright, $id);
1150 4 50 33     18 if ($clsty && ($clsty =~ m/{([^}]+)}/)) {
1151 0         0 my $_style = $1;
1152 0         0 $_style =~ s/\n/ /g;
1153 0         0 $style .= ';'.$_style;
1154 0         0 $clsty =~ s/{[^}]+}//g;
1155             }
1156 4 0 0     13 if ($clsty && ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ ||
      33        
1157             $clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/)) {
1158 0 0 0     0 if ($1 || $2) {
1159 0 0       0 if ($class) {
1160 0         0 $class = $1 . ' ' . $class;
1161             } else {
1162 0         0 $class = $1;
1163             }
1164 0         0 $id = $2;
1165 0 0       0 if ($class) {
1166 0         0 $clsty =~ s/\([A-Za-z0-9_\- ]+?(#.*?)?\)//g;
1167             }
1168 0 0       0 if ($id) {
1169 0         0 $clsty =~ s/\(#.+?\)//g;
1170             }
1171             }
1172             }
1173 4 50 33     16 if ($clsty && ($clsty =~ m/(\(+)/)) {
1174 0         0 $padleft = length($1);
1175 0         0 $clsty =~ s/\(+//;
1176             }
1177 4 50 33     15 if ($clsty && ($clsty =~ m/(\)+)/)) {
1178 0         0 $padright = length($1);
1179 0         0 $clsty =~ s/\)+//;
1180             }
1181 4 50 33     16 if ($clsty && ($clsty =~ m/\[(.+?)\]/)) {
1182 0         0 $lang = $1;
1183 0         0 $clsty =~ s/\[.+?\]//g;
1184             }
1185 4         8 my $attrs = '';
1186              
1187 4 50       11 $style .= qq{;padding-left:${padleft}em} if $padleft;
1188 4 50       12 $style .= qq{;padding-right:${padright}em} if $padright;
1189 4         9 $style =~ s/^;//;
1190              
1191 4 50       13 if ( $class ) {
1192 0         0 $class =~ s/^ //;
1193 0         0 $class =~ s/ $//;
1194 0         0 $attrs .= qq{ class="$class"};
1195             }
1196 4 50       12 $attrs .= qq{ id="$id"} if $id;
1197 4 50       10 $attrs .= qq{ style="$style"} if $style;
1198 4 50       19 $attrs .= qq{ lang="$lang"} if $lang;
1199 4         8 $attrs =~ s/^ //;
1200              
1201 4         15 return $attrs;
1202             }
1203              
1204             sub format_tag {
1205 0     0 1 0 my $self = shift;
1206 0         0 my (%args) = @_;
1207 0         0 my $tagname = $args{tag};
1208 0 0       0 my $text = defined $args{text} ? $args{text} : '';
1209 0 0       0 my $pre = defined $args{pre} ? $args{pre} : '';
1210 0 0       0 my $post = defined $args{post} ? $args{post} : '';
1211 0 0       0 my $clsty = defined $args{clsty} ? $args{clsty} : '';
1212 0         0 _strip_borders(\$pre, \$post);
1213 0         0 my $tag = "<$tagname";
1214 0         0 my $attr = $self->format_classstyle($clsty);
1215 0 0       0 $tag .= qq{ $attr} if $attr;
1216 0         0 $tag .= qq{>$text};
1217              
1218 0         0 return $pre.$tag.$post;
1219             }
1220              
1221             sub format_deflist {
1222 0     0 0 0 my $self = shift;
1223 0         0 my (%args) = @_;
1224 0 0       0 my $str = defined $args{text} ? $args{text} : '';
1225 0         0 my $clsty;
1226 0         0 my @lines = split /\n/, $str;
1227 0 0       0 if ($lines[0] =~ m/^(dl($clstyre*?)\.\.?(?:\ +|$))/) {
1228 0         0 $clsty = $2;
1229 0         0 $lines[0] = substr($lines[0], length($1));
1230             }
1231              
1232              
1233 0         0 my ($dt, $dd);
1234 0         0 my $out = '';
1235 0         0 foreach my $line (@lines) {
1236 0 0       0 if ($line =~ m/^((?:$clstyre*)(?:[^\ ].*?)(?
1237 0 0 0     0 $out .= add_term($self, $dt, $dd) if ($dt && $dd);
1238 0         0 $dt = $1;
1239 0         0 $dd = $2;
1240             } else {
1241 0         0 $dd .= "\n" . $line;
1242             }
1243             }
1244 0 0 0     0 $out .= add_term($self, $dt, $dd) if $dt && $dd;
1245              
1246 0         0 my $tag = '
1247 0         0 my $attr;
1248 0 0       0 $attr = $self->format_classstyle($clsty) if $clsty;
1249 0 0       0 $tag .= qq{ $attr} if $attr;
1250 0         0 $tag .= '>'."\n";
1251              
1252 0         0 return $tag.$out."\n";
1253             }
1254              
1255             sub add_term {
1256 0     0 0 0 my ($self, $dt, $dd) = @_;
1257 0         0 my ($dtattr, $ddattr);
1258 0         0 my $dtlang;
1259 0 0       0 if ($dt =~ m/^($clstyre*)/) {
1260 0         0 my $param = $1;
1261 0         0 $dtattr = $self->format_classstyle($param);
1262 0 0       0 if ($param =~ m/\[([A-Za-z]+?)\]/) {
1263 0         0 $dtlang = $1;
1264             }
1265 0         0 $dt = substr($dt, length($param));
1266             }
1267 0 0       0 if ($dd =~ m/^($clstyre*)/) {
1268 0         0 my $param = $1;
1269             # if the language was specified for the term,
1270             # then apply it to the definition as well (unless
1271             # already specified of course)
1272 0 0 0     0 if ($dtlang && ($param =~ m/\[([A-Za-z]+?)\]/)) {
1273 0         0 undef $dtlang;
1274             }
1275 0 0       0 $ddattr = $self->format_classstyle(($dtlang ? "[$dtlang]" : '') . $param);
1276 0         0 $dd = substr($dd, length($param));
1277             }
1278 0         0 my $out = '
1279 0 0       0 $out .= qq{ $dtattr} if $dtattr;
1280 0         0 $out .= '>' . $self->format_inline(text => $dt) . '' . "\n";
1281 0 0       0 if ($dd =~ m/\n\n/) {
1282 0 0       0 $dd = $self->textile($dd) if $dd =~ m/\n\n/;
1283             } else {
1284 0         0 $dd = $self->format_paragraph(text => $dd);
1285             }
1286 0         0 $out .= '
1287 0 0       0 $out .= qq{ $ddattr} if $ddattr;
1288 0         0 $out .= '>' . $dd . '' . "\n";
1289              
1290 0         0 return $out;
1291             }
1292              
1293              
1294             sub format_list {
1295 5     5 1 12 my $self = shift;
1296 5         16 my (%args) = @_;
1297 5 50       23 my $str = defined $args{text} ? $args{text} : '';
1298              
1299 5         21 my %list_tags = ('*' => 'ul', '#' => 'ol');
1300              
1301 5         24 my @lines = split /\n/, $str;
1302              
1303 5         11 my @stack;
1304 5         9 my $last_depth = 0;
1305 5         11 my $item = '';
1306 5         9 my $out = '';
1307 5         18 foreach my $line (@lines) {
1308 12 50       684 if ($line =~ m/^((?:$clstypadre*|$halignre)*)
1309             ([\#\*]+)
1310             ((?:$halignre|$clstypadre*)*)
1311             \ (.+)$/x) {
1312 12 100       34 if ($item ne '') {
1313 7 50       28 if ($item =~ m/\n/) {
1314 0 0       0 if ($self->{_line_open}) {
1315 0         0 $item =~ s/(]*>|^)/$1$self->{_line_open}/gm;
1316 0         0 $item =~ s/(\n|$)/$self->{_line_close}$1/gs;
1317             } else {
1318 0         0 $item =~ s/(\n)/$self->{_line_close}$1/gs;
1319             }
1320             }
1321 7         13 $out .= $item;
1322 7         13 $item = '';
1323             }
1324 12         47 my $type = substr($2, 0, 1);
1325 12         23 my $depth = length($2);
1326 12         26 my $blockparam = $1;
1327 12         98 my $itemparam = $3;
1328 12         32 $line = $4;
1329 12         15 my ($blockclsty, $blockalign, $blockattr, $itemattr, $itemclsty,
1330             $itemalign);
1331 12 50       270 if ($blockparam =~ m/($clstypadre+)/) {
1332 0         0 $blockclsty = $1;
1333             }
1334 12 50       108 if ($blockparam =~ m/($halignre+)/) {
1335 0         0 $blockalign = $1;
1336             }
1337 12 50       258 if ($itemparam =~ m/($clstypadre+)/) {
1338 0         0 $itemclsty = $1;
1339             }
1340 12 50       99 if ($itemparam =~ m/($halignre+)/) {
1341 0         0 $itemalign = $1;
1342             }
1343 12 50       32 $itemattr = $self->format_classstyle($itemclsty) if $itemclsty;
1344 12 100       51 if ($depth > $last_depth) {
    50          
1345 5         22 for (my $j = $last_depth; $j < $depth; $j++) {
1346 5         14 $out .= qq{<$list_tags{$type}};
1347 5         20 push @stack, $type;
1348 5 50       17 if ($blockclsty) {
1349 0         0 $blockattr = $self->format_classstyle($blockclsty);
1350 0 0       0 $out .= ' '.$blockattr if $blockattr;
1351             }
1352 5         9 $out .= ">\n
1353 5 50       14 $out .= qq{ $itemattr} if $itemattr;
1354 5         21 $out .= ">";
1355             }
1356             } elsif ($depth < $last_depth) {
1357 0         0 for (my $j = $depth; $j < $last_depth; $j++) {
1358 0 0       0 $out .= "\n" if $j == $depth;
1359 0         0 my $type = pop @stack;
1360 0         0 $out .= qq{\n\n};
1361             }
1362 0 0       0 if ($depth) {
1363 0         0 $out .= '
1364 0 0       0 $out .= qq{ $itemattr} if $itemattr;
1365 0         0 $out .= '>';
1366             }
1367             } else {
1368 7         14 $out .= "\n
1369 7 50       25 $out .= qq{ $itemattr} if $itemattr;
1370 7         13 $out .= '>';
1371             }
1372 12         120 $last_depth = $depth;
1373             }
1374 12 50       74 $item .= "\n" if $item ne '';
1375 12         40 $item .= $self->format_paragraph(text => $line);
1376             }
1377              
1378 5 50       22 if ($item =~ m/\n/) {
1379 0 0       0 if ($self->{_line_open}) {
1380 0         0 $item =~ s/(]*>|^)/$1$self->{_line_open}/gm;
1381 0         0 $item =~ s/(\n|$)/$self->{_line_close}$1/gs;
1382             } else {
1383 0         0 $item =~ s/(\n)/$self->{_line_close}$1/gs;
1384             }
1385             }
1386 5         11 $out .= $item;
1387              
1388 5         22 for (my $j = 1; $j <= $last_depth; $j++) {
1389 5 50       21 $out .= '' if $j == 1;
1390 5         13 my $type = pop @stack;
1391 5         13 $out .= "\n".'';
1392 5 50       26 $out .= '' if $j != $last_depth;
1393             }
1394              
1395 5         23 return $out;
1396             }
1397              
1398             sub format_block {
1399 0     0 1 0 my $self = shift;
1400 0         0 my (%args) = @_;
1401 0 0       0 my $str = defined $args{text} ? $args{text} : '';
1402 0 0       0 my $pre = defined $args{pre} ? $args{pre} : '';
1403 0 0       0 my $post = defined $args{post} ? $args{post} : '';
1404 0         0 my $inline = $args{inline};
1405 0         0 _strip_borders(\$pre, \$post);
1406 0         0 my ($filters) = $str =~ m/^(\|(?:(?:[a-z0-9_\-]+)\|)+)/;
1407 0 0       0 if ($filters) {
1408 0         0 my $filtreg = quotemeta($filters);
1409 0         0 $str =~ s/^$filtreg//;
1410 0         0 $filters =~ s/^\|//;
1411 0         0 $filters =~ s/\|$//;
1412 0         0 my @filters = split /\|/, $filters;
1413 0         0 $str = $self->apply_filters(text => $str, filters => \@filters);
1414 0         0 my $count = scalar(@filters);
1415 0 0       0 if ($str =~ s!(

){$count}!$1!gs) {

1416 0         0 $str =~ s!(

){$count}!$1!gs;
1417 0         0 $str =~ s!(){$count}!$1!gs;
1418             }
1419             }
1420 0 0       0 if ($inline) {
1421             # strip off opening para, closing para, since we're
1422             # operating within an inline block
1423 0         0 $str =~ s/^\s*]*>//;
1424 0         0 $str =~ s/<\/p>\s*$//;
1425             }
1426              
1427 0         0 return $pre.$str.$post;
1428             }
1429              
1430             sub format_link {
1431 4     4 1 9 my $self = shift;
1432 4         38 my (%args) = @_;
1433 4 50       21 my $text = defined $args{text} ? $args{text} : '';
1434 4 50       18 my $linktext = defined $args{linktext} ? $args{linktext} : '';
1435 4         10 my $title = $args{title};
1436 4         9 my $url = $args{url};
1437 4         10 my $clsty = $args{clsty};
1438              
1439 4 50 33     44 if (!defined $url || $url eq '') {
1440 0         0 return $text;
1441             }
1442 4 50 33     35 if ($self->{links} && $self->{links}{$url}) {
1443 0   0     0 $title ||= $self->{links}{$url}{title};
1444 0         0 $url = $self->{links}{$url}{url};
1445             }
1446 4         12 $linktext =~ s/ +$//;
1447 4         36 $linktext = $self->format_paragraph(text => $linktext);
1448 4         24 $url = $self->format_url(linktext => $linktext, url => $url);
1449 4         13 my $tag = qq{
1450 4         20 my $attr = $self->format_classstyle($clsty);
1451 4 50       12 $tag .= qq{ $attr} if $attr;
1452 4 50       33 if (defined $title) {
1453 4         7 $title =~ s/^\s+//;
1454 4 50       21 $tag .= qq{ title="$title"} if length($title);
1455             }
1456 4         15 $tag .= qq{>$linktext};
1457              
1458 4         26 return $tag;
1459             }
1460              
1461             sub format_url {
1462 4     4 1 8 my $self = shift;
1463 4         15 my (%args) = @_;
1464 4 50       19 my $url = defined $args{url} ? $args{url} : '';
1465 4 50       19 if ($url =~ m/^(mailto:)?([-\+\w]+\@[-\w]+(\.\w[-\w]*)+)$/) {
1466 0         0 $url = 'mailto:'.$self->mail_encode($2);
1467             }
1468 4 50       20 if ($url !~ m{^(/|\./|\.\./|#)}) {
1469 4 50       23 $url = "http://$url" if $url !~ m{^(?:https?|ftp|mailto|nntp|telnet)};
1470             }
1471 4         10 $url =~ s/&(?!amp;)/&/g;
1472 4         11 $url =~ s/ /\+/g;
1473 4         26 $url =~ s/^((?:.+?)\?)(.+)$/$1.$self->encode_url($2)/ge;
  0         0  
1474              
1475 4         14 return $url;
1476             }
1477              
1478             sub format_span {
1479 0     0 1 0 my $self = shift;
1480 0         0 my (%args) = @_;
1481 0 0       0 my $text = defined $args{text} ? $args{text} : '';
1482 0 0       0 my $pre = defined $args{pre} ? $args{pre} : '';
1483 0 0       0 my $post = defined $args{post} ? $args{post} : '';
1484 0 0       0 my $cite = defined $args{cite} ? $args{cite} : '';
1485 0         0 my $align = $args{align};
1486 0         0 my $clsty = $args{clsty};
1487 0         0 _strip_borders(\$pre, \$post);
1488 0         0 my ($class, $style);
1489 0         0 my $tag = qq{
1490 0         0 $style = '';
1491 0 0       0 if (defined $align) {
1492 0 0       0 if ($self->{css_mode}) {
1493 0         0 my $alignment = _halign($align);
1494 0 0       0 $style .= qq{;float:$alignment} if $alignment;
1495 0 0       0 $class .= ' '.$self->{css}{"class_align_$alignment"} if $alignment;
1496             } else {
1497 0   0     0 my $alignment = _halign($align) || _valign($align);
1498 0 0       0 $tag .= qq{ align="$alignment"} if $alignment;
1499             }
1500             }
1501 0         0 my $attr = $self->format_classstyle($clsty, $class, $style);
1502 0 0       0 $tag .= qq{ $attr} if $attr;
1503 0 0       0 if (defined $cite) {
1504 0         0 $cite =~ s/^://;
1505 0         0 $cite = $self->format_url(url => $cite);
1506 0         0 $tag .= qq{ cite="$cite"};
1507             }
1508              
1509 0         0 return $pre.$tag.'>'.$self->format_paragraph(text => $text).''.$post;
1510             }
1511              
1512             sub format_image {
1513 0     0 1 0 my $self = shift;
1514 0         0 my (%args) = @_;
1515 0 0       0 my $src = defined $args{src} ? $args{src} : '';
1516 0 0       0 my $pre = defined $args{pre} ? $args{pre} : '';
1517 0 0       0 my $post = defined $args{post} ? $args{post} : '';
1518 0         0 my $extra = $args{extra};
1519 0         0 my $align = $args{align};
1520 0         0 my $link = $args{url};
1521 0         0 my $clsty = $args{clsty};
1522 0         0 _strip_borders(\$pre, \$post);
1523 0 0       0 return $pre.'!!'.$post if length($src) == 0;
1524 0         0 my $tag;
1525 0 0       0 if ($self->{flavor} =~ m/^xhtml2/) {
1526 0         0 my $type; # poor man's mime typing. need to extend this externally
1527 0 0       0 if ($src =~ m/(?:\.jpeg|\.jpg)$/i) {
    0          
    0          
    0          
1528 0         0 $type = 'image/jpeg';
1529             } elsif ($src =~ m/\.gif$/i) {
1530 0         0 $type = 'image/gif';
1531             } elsif ($src =~ m/\.png$/i) {
1532 0         0 $type = 'image/png';
1533             } elsif ($src =~ m/\.tiff$/i) {
1534 0         0 $type = 'image/tiff';
1535             }
1536 0         0 $tag = qq{
1537 0 0       0 $tag .= qq{ type="$type"} if $type;
1538 0         0 $tag .= qq{ data="$src"};
1539             } else {
1540 0         0 $tag = qq{
1541             }
1542 0         0 my ($class, $style);
1543 0 0       0 if (defined $align) {
1544 0 0       0 if ($self->{css_mode}) {
1545 0         0 my $alignment = _halign($align);
1546 0 0       0 $style .= qq{;float:$alignment} if $alignment;
1547 0 0       0 $class .= ' '.$alignment if $alignment;
1548 0         0 $alignment = _valign($align);
1549 0 0       0 if ($alignment) {
1550 0 0       0 my $imgvalign = ($alignment =~ m/(top|bottom)/ ? 'text-' . $alignment : $alignment);
1551 0 0       0 $style .= qq{;vertical-align:$imgvalign} if $imgvalign;
1552 0 0       0 $class .= ' '.$self->{css}{"class_align_$alignment"} if $alignment;
1553             }
1554             } else {
1555 0   0     0 my $alignment = _halign($align) || _valign($align);
1556 0 0       0 $tag .= qq{ align="$alignment"} if $alignment;
1557             }
1558             }
1559 0         0 my ($pctw, $pcth, $w, $h, $alt);
1560 0 0       0 if (defined $extra) {
1561 0         0 ($alt) = $extra =~ m/\(([^\)]+)\)/;
1562 0         0 $extra =~ s/\([^\)]+\)//;
1563 0         0 my ($pct) = ($extra =~ m/(^|\s)(\d+)%(\s|$)/)[1];
1564 0 0       0 if (!$pct) {
1565 0         0 ($pctw, $pcth) = ($extra =~ m/(^|\s)(\d+)%x(\d+)%(\s|$)/)[1,2];
1566             } else {
1567 0         0 $pctw = $pcth = $pct;
1568             }
1569 0 0 0     0 if (!$pctw && !$pcth) {
1570 0         0 ($w,$h) = ($extra =~ m/(^|\s)(\d+|\*)x(\d+|\*)(\s|$)/)[1,2];
1571 0 0 0     0 $w = '' if $w && $w eq '*';
1572 0 0 0     0 $h = '' if $h && $h eq '*';
1573 0 0       0 if (!$w) {
1574 0         0 ($w) = ($extra =~ m/(^|[,\s])(\d+)w([\s,]|$)/)[1];
1575             }
1576 0 0       0 if (!$h) {
1577 0         0 ($h) = ($extra =~ m/(^|[,\s])(\d+)h([\s,]|$)/)[1];
1578             }
1579             }
1580             }
1581 0 0       0 $alt = '' unless defined $alt;
1582 0 0       0 if ($self->{flavor} !~ m/^xhtml2/) {
1583 0         0 $tag .= ' alt="' . $self->encode_html_basic($alt) . '"';
1584             }
1585 0 0 0     0 if ($w && $h) {
1586 0 0       0 if ($self->{flavor} !~ m/^xhtml2/) {
1587 0         0 $tag .= qq{ height="$h" width="$w"};
1588             } else {
1589 0         0 $style .= qq{;height:$h}.qq{px;width:$w}.q{px};
1590             }
1591             } else {
1592 0         0 my ($image_w, $image_h) = $self->image_size($src);
1593 0 0 0     0 if (($image_w && $image_h) && ($w || $h)) {
      0        
      0        
1594             # image size determined, but only width or height specified
1595 0 0 0     0 if ($w && !$h) {
    0 0        
1596             # width defined, scale down height proportionately
1597 0         0 $h = int($image_h * ($w / $image_w));
1598             } elsif ($h && !$w) {
1599 0         0 $w = int($image_w * ($h / $image_h));
1600             }
1601             } else {
1602 0         0 $w = $image_w;
1603 0         0 $h = $image_h;
1604             }
1605 0 0 0     0 if ($w && $h) {
1606 0 0 0     0 if ($pctw || $pcth) {
1607 0         0 $w = int($w * $pctw / 100);
1608 0         0 $h = int($h * $pcth / 100);
1609             }
1610 0 0       0 if ($self->{flavor} !~ m/^xhtml2/) {
1611 0         0 $tag .= qq{ height="$h" width="$w"};
1612             } else {
1613 0         0 $style .= qq{;height:$h}.qq{px;width:$w}.q{px};
1614             }
1615             }
1616             }
1617 0         0 my $attr = $self->format_classstyle($clsty, $class, $style);
1618 0 0       0 $tag .= qq{ $attr} if $attr;
1619 0 0       0 if ($self->{flavor} =~ m/^xhtml2/) {
    0          
1620 0         0 $tag .= '>

' . $self->encode_html_basic($alt) . '

';
1621             } elsif ($self->{flavor} =~ m/^xhtml/) {
1622 0         0 $tag .= ' />';
1623             } else {
1624 0         0 $tag .= '>';
1625             }
1626 0 0       0 if (defined $link) {
1627 0         0 $link =~ s/^://;
1628 0         0 $link = $self->format_url(url => $link);
1629 0         0 $tag = ''.$tag.'';
1630             }
1631              
1632 0         0 return $pre.$tag.$post;
1633             }
1634              
1635             sub format_table {
1636 0     0 1 0 my $self = shift;
1637 0         0 my (%args) = @_;
1638 0 0       0 my $str = defined $args{text} ? $args{text} : '';
1639              
1640 0         0 my @lines = split /\n/, $str;
1641 0         0 my @rows;
1642 0         0 my $line_count = scalar(@lines);
1643 0         0 for (my $i = 0; $i < $line_count; $i++) {
1644 0 0       0 if ($lines[$i] !~ m/\|\s*$/) {
1645 0 0       0 if ($i + 1 < $line_count) {
1646 0 0       0 $lines[$i+1] = $lines[$i] . "\n" . $lines[$i+1] if $i+1 <= $#lines;
1647             } else {
1648 0         0 push @rows, $lines[$i];
1649             }
1650             } else {
1651 0         0 push @rows, $lines[$i];
1652             }
1653             }
1654 0         0 my ($tid, $tpadl, $tpadr, $tlang);
1655 0         0 my $tclass = '';
1656 0         0 my $tstyle = '';
1657 0         0 my $talign = '';
1658 0 0       0 if ($rows[0] =~ m/^table[^\.]/) {
1659 0         0 my $row = $rows[0];
1660 0         0 $row =~ s/^table//;
1661 0         0 my $params = 1;
1662             # process row parameters until none are left
1663 0         0 while ($params) {
1664 0 0       0 if ($row =~ m/^($tblalignre)/) {
1665             # found row alignment
1666 0         0 $talign .= $1;
1667 0 0       0 $row = substr($row, length($1)) if $1;
1668 0 0       0 redo if $1;
1669             }
1670 0 0       0 if ($row =~ m/^($clstypadre)/) {
1671             # found a class/id/style/padding indicator
1672 0         0 my $clsty = $1;
1673 0 0       0 $row = substr($row, length($clsty)) if $clsty;
1674 0 0       0 if ($clsty =~ m/{([^}]+)}/) {
1675 0         0 $tstyle = $1;
1676 0         0 $clsty =~ s/{([^}]+)}//;
1677 0 0       0 redo if $tstyle;
1678             }
1679 0 0 0     0 if ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ ||
1680             $clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/) {
1681 0 0 0     0 if ($1 || $2) {
1682 0         0 $tclass = $1;
1683 0         0 $tid = $2;
1684 0         0 redo;
1685             }
1686             }
1687 0 0       0 $tpadl = length($1) if $clsty =~ m/(\(+)/;
1688 0 0       0 $tpadr = length($1) if $clsty =~ m/(\)+)/;
1689 0 0       0 $tlang = $1 if $clsty =~ m/\[(.+?)\]/;
1690 0 0       0 redo if $clsty;
1691             }
1692 0         0 $params = 0;
1693             }
1694 0         0 $row =~ s/\.\s+//;
1695 0         0 $rows[0] = $row;
1696             }
1697 0         0 my $out = '';
1698 0         0 my @cols = split /\|/, $rows[0].' ';
1699 0         0 my (@colalign, @rowspans);
1700 0         0 foreach my $row (@rows) {
1701 0         0 my @cols = split /\|/, $row.' ';
1702 0         0 my $colcount = $#cols;
1703 0         0 pop @cols;
1704 0         0 my $colspan = 0;
1705 0         0 my $row_out = '';
1706 0         0 my ($rowclass, $rowid, $rowalign, $rowstyle, $rowheader);
1707 0 0       0 $cols[0] = '' if !defined $cols[0];
1708 0 0       0 if ($cols[0] =~ m/_/) {
1709 0         0 $cols[0] =~ s/_//g;
1710 0         0 $rowheader = 1;
1711             }
1712 0 0       0 if ($cols[0] =~ m/{([^}]+)}/) {
1713 0         0 $rowstyle = $1;
1714 0         0 $cols[0] =~ s/{[^}]+}//g;
1715             }
1716 0 0       0 if ($cols[0] =~ m/\(([^\#]+?)?(#(.+))?\)/) {
1717 0         0 $rowclass = $1;
1718 0         0 $rowid = $3;
1719 0         0 $cols[0] =~ s/\([^\)]+\)//g;
1720             }
1721 0 0       0 $rowalign = $1 if $cols[0] =~ m/($alignre)/;
1722 0         0 for (my $c = $colcount - 1; $c > 0; $c--) {
1723 0 0       0 if ($rowspans[$c]) {
1724 0         0 $rowspans[$c]--;
1725 0 0       0 next if $rowspans[$c] > 1;
1726             }
1727 0         0 my ($colclass, $colid, $header, $colparams, $colpadl, $colpadr, $collang);
1728 0         0 my $colstyle = '';
1729 0         0 my $colalign = $colalign[$c];
1730 0         0 my $col = pop @cols;
1731 0   0     0 $col ||= '';
1732 0         0 my $attrs = '';
1733 0 0       0 if ($col =~ m/^(((_|[\/\\]\d+|$alignre|$clstypadre)+)\. )/) {
1734 0         0 my $colparams = $2;
1735 0         0 $col = substr($col, length($1));
1736 0         0 my $params = 1;
1737             # keep processing column parameters until there
1738             # are none left...
1739 0         0 while ($params) {
1740 0 0       0 if ($colparams =~ m/^(_|$alignre)/g) {
1741             # found alignment or heading indicator
1742 0         0 $attrs .= $1;
1743 0 0       0 $colparams = substr($colparams, pos($colparams)) if $1;
1744 0 0       0 redo if $1;
1745             }
1746 0 0       0 if ($colparams =~ m/^($clstypadre)/g) {
1747             # found a class/id/style/padding marker
1748 0         0 my $clsty = $1;
1749 0 0       0 $colparams = substr($colparams, pos($colparams)) if $clsty;
1750 0 0       0 if ($clsty =~ m/{([^}]+)}/) {
1751 0         0 $colstyle = $1;
1752 0         0 $clsty =~ s/{([^}]+)}//;
1753             }
1754 0 0 0     0 if ($clsty =~ m/\(([A-Za-z0-9_\- ]+?)(?:#(.+?))?\)/ ||
1755             $clsty =~ m/\(([A-Za-z0-9_\- ]+?)?(?:#(.+?))\)/) {
1756 0 0 0     0 if ($1 || $2) {
1757 0         0 $colclass = $1;
1758 0         0 $colid = $2;
1759 0 0       0 if ($colclass) {
    0          
1760 0         0 $clsty =~ s/\([A-Za-z0-9_\- ]+?(#.*?)?\)//g;
1761             } elsif ($colid) {
1762 0         0 $clsty =~ s/\(#.+?\)//g;
1763             }
1764             }
1765             }
1766 0 0       0 if ($clsty =~ m/(\(+)/) {
1767 0         0 $colpadl = length($1);
1768 0         0 $clsty =~ s/\(+//;
1769             }
1770 0 0       0 if ($clsty =~ m/(\)+)/) {
1771 0         0 $colpadr = length($1);
1772 0         0 $clsty =~ s/\)+//;
1773             }
1774 0 0       0 if ($clsty =~ m/\[(.+?)\]/) {
1775 0         0 $collang = $1;
1776 0         0 $clsty =~ s/\[.+?\]//;
1777             }
1778 0 0       0 redo if $clsty;
1779             }
1780 0 0       0 if ($colparams =~ m/^\\(\d+)/) {
1781 0         0 $colspan = $1;
1782 0         0 $colparams = substr($colparams, length($1)+1);
1783 0 0       0 redo if $1;
1784             }
1785 0 0       0 if ($colparams =~ m/\/(\d+)/) {
1786 0 0       0 $rowspans[$c] = $1 if $1;
1787 0         0 $colparams = substr($colparams, length($1)+1);
1788 0 0       0 redo if $1;
1789             }
1790 0         0 $params = 0;
1791             }
1792             }
1793 0 0       0 if (length($attrs)) {
1794 0 0       0 $header = 1 if $attrs =~ m/_/;
1795 0 0 0     0 $colalign = '' if $attrs =~ m/($alignre)/ && length($1);
1796             # determine column alignment
1797 0 0       0 if ($attrs =~ m/<>/) {
    0          
    0          
    0          
1798 0         0 $colalign .= '<>';
1799             } elsif ($attrs =~ m/
1800 0         0 $colalign .= '<';
1801             } elsif ($attrs =~ m/=/) {
1802 0         0 $colalign = '=';
1803             } elsif ($attrs =~ m/>/) {
1804 0         0 $colalign = '>';
1805             }
1806 0 0       0 if ($attrs =~ m/\^/) {
    0          
    0          
1807 0         0 $colalign .= '^';
1808             } elsif ($attrs =~ m/~/) {
1809 0         0 $colalign .= '~';
1810             } elsif ($attrs =~ m/-/) {
1811 0         0 $colalign .= '-';
1812             }
1813             }
1814 0 0       0 $header = 1 if $rowheader;
1815 0 0       0 $colalign[$c] = $colalign if $header;
1816 0         0 $col =~ s/^ +//; $col =~ s/ +$//;
  0         0  
1817 0 0       0 if (length($col)) {
1818             # create one cell tag
1819 0   0     0 my $rowspan = $rowspans[$c] || 0;
1820 0 0       0 my $col_out = '<' . ($header ? 'th' : 'td');
1821 0 0       0 if (defined $colalign) {
1822             # horizontal, vertical alignment
1823 0         0 my $halign = _halign($colalign);
1824 0 0       0 $col_out .= qq{ align="$halign"} if $halign;
1825 0         0 my $valign = _valign($colalign);
1826 0 0       0 $col_out .= qq{ valign="$valign"} if $valign;
1827             }
1828             # apply css attributes, row, column spans
1829 0 0       0 $colstyle .= qq{;padding-left:${colpadl}em} if $colpadl;
1830 0 0       0 $colstyle .= qq{;padding-right:${colpadr}em} if $colpadr;
1831 0 0       0 $col_out .= qq{ class="$colclass"} if $colclass;
1832 0 0       0 $col_out .= qq{ id="$colid"} if $colid;
1833 0 0       0 $colstyle =~ s/^;// if $colstyle;
1834 0 0       0 $col_out .= qq{ style="$colstyle"} if $colstyle;
1835 0 0       0 $col_out .= qq{ lang="$collang"} if $collang;
1836 0 0       0 $col_out .= qq{ colspan="$colspan"} if $colspan > 1;
1837 0 0 0     0 $col_out .= qq{ rowspan="$rowspan"} if ($rowspan||0) > 1;
1838 0         0 $col_out .= '>';
1839             # if the content of this cell has newlines OR matches
1840             # our paragraph block signature, process it as a full-blown
1841             # textile document
1842 0 0 0     0 if (($col =~ m/\n\n/) ||
1843             ($col =~ m/^(?:$halignre|$clstypadre*)*
1844             [\*\#]
1845             (?:$clstypadre*|$halignre)*\ /x)) {
1846 0         0 $col_out .= $self->textile($col);
1847             } else {
1848 0         0 $col_out .= $self->format_paragraph(text => $col);
1849             }
1850 0 0       0 $col_out .= '';
1851 0         0 $row_out = $col_out . $row_out;
1852 0 0       0 $colspan = 0 if $colspan;
1853             } else {
1854 0 0       0 $colspan = 1 if $colspan == 0;
1855 0         0 $colspan++;
1856             }
1857             }
1858 0 0       0 if ($colspan > 1) {
1859             # handle the spanned column if we came up short
1860 0         0 $colspan--;
1861 0 0       0 $row_out = q{
1862             . ($colspan>1 ? qq{ colspan="$colspan"} : '')
1863             . qq{>
1864             }
1865              
1866             # build one table row
1867 0         0 $out .= q{
1868 0 0       0 if ($rowalign) {
1869 0         0 my $valign = _valign($rowalign);
1870 0 0       0 $out .= qq{ valign="$valign"} if $valign;
1871             }
1872 0 0       0 $out .= qq{ class="$rowclass"} if $rowclass;
1873 0 0       0 $out .= qq{ id="$rowid"} if $rowid;
1874 0 0       0 $out .= qq{ style="$rowstyle"} if $rowstyle;
1875 0         0 $out .= qq{>$row_out
1876             }
1877              
1878             # now, form the table tag itself
1879 0         0 my $table = '';
1880 0         0 $table .= q{
1881 0 0       0 if ($talign) {
1882 0 0       0 if ($self->{css_mode}) {
1883             # horizontal alignment
1884 0         0 my $alignment = _halign($talign);
1885 0 0       0 if ($talign eq '=') {
1886 0         0 $tstyle .= ';margin-left:auto;margin-right:auto';
1887             } else {
1888 0 0       0 $tstyle .= ';float:'.$alignment if $alignment;
1889             }
1890 0 0       0 $tclass .= ' '.$alignment if $alignment;
1891             } else {
1892 0         0 my $alignment = _halign($talign);
1893 0 0       0 $table .= qq{ align="$alignment"} if $alignment;
1894             }
1895             }
1896 0 0       0 $tstyle .= qq{;padding-left:${tpadl}em} if $tpadl;
1897 0 0       0 $tstyle .= qq{;padding-right:${tpadr}em} if $tpadr;
1898 0 0       0 $tclass =~ s/^ // if $tclass;
1899 0 0       0 $table .= qq{ class="$tclass"} if $tclass;
1900 0 0       0 $table .= qq{ id="$tid"} if $tid;
1901 0 0       0 $tstyle =~ s/^;// if $tstyle;
1902 0 0       0 $table .= qq{ style="$tstyle"} if $tstyle;
1903 0 0       0 $table .= qq{ lang="$tlang"} if $tlang;
1904 0 0 0     0 $table .= q{ cellspacing="0"} if $tclass || $tid || $tstyle;
      0        
1905 0         0 $table .= qq{>$out
}; 1906               1907 0 0       0 if ($table =~ m{}) { 1908             # exception -- something isn't right so return fail case 1909 0         0 return undef; 1910             } 1911               1912 0         0 return $table; 1913             } 1914               1915             sub apply_filters { 1916 0     0 1 0 my $self = shift; 1917 0         0 my (%args) = @_; 1918 0         0 my $text = $args{text}; 1919 0 0       0 return '' unless defined $text; 1920 0         0 my $list = $args{filters}; 1921 0         0 my $filters = $self->{filters}; 1922 0 0       0 return $text unless (ref $filters) eq 'HASH'; 1923               1924 0         0 my $param = $self->filter_param; 1925 0         0 foreach my $filter (@{$list}) {   0         0   1926 0 0       0 next unless $filters->{$filter}; 1927 0 0       0 if ((ref $filters->{$filter}) eq 'CODE') { 1928 0         0 $text = $filters->{$filter}->($text, $param); 1929             } 1930             } 1931 0         0 return $text; 1932             } 1933               1934             # minor utility / formatting routines 1935               1936             { 1937 7     7   6948 my $Have_Entities = eval 'use HTML::Entities; 1' ? 1 : 0;   7         46344     7         596   1938               1939             sub encode_html { 1940 36     36 1 57 my $self = shift; 1941 36         63 my($html, $can_double_encode) = @_; 1942 36 50       122 return '' unless defined $html; 1943 36 100       119 return $html if $self->{disable_encode_entities}; 1944 29 50 33     155 if ($Have_Entities && $self->{char_encoding}) { 1945 29         153 $html = HTML::Entities::encode_entities($html); 1946             } else { 1947 0         0 $html = $self->encode_html_basic($html, $can_double_encode); 1948             } 1949               1950 29         589 return $html; 1951             } 1952               1953             sub decode_html { 1954 0     0 1 0 my $self = shift; 1955 0         0 my ($html) = @_; 1956 0         0 $html =~ s{"}{"}g; 1957 0         0 $html =~ s{&}{&}g; 1958 0         0 $html =~ s{<}{<}g; 1959 0         0 $html =~ s{>}{>}g; 1960               1961 0         0 return $html; 1962             } 1963               1964             sub encode_html_basic { 1965 4     4 1 12 my $self = shift; 1966 4         12 my($html, $can_double_encode) = @_; 1967 4 50       47 return '' unless defined $html; 1968 0 0       0 return $html unless $html =~ m/[^\w\s]/; 1969 0 0       0 if ($can_double_encode) { 1970 0         0 $html =~ s{&}{&}g; 1971             } else { 1972             ## Encode any & not followed by something that looks like 1973             ## an entity, numeric or otherwise. 1974 0         0 $html =~ s/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w{1,8});)/&/g; 1975             } 1976 0         0 $html =~ s{"}{"}g; 1977 0         0 $html =~ s{<}{<}g; 1978 0         0 $html =~ s{>}{>}g; 1979               1980 0         0 return $html; 1981             } 1982               1983             } 1984               1985             { 1986 7     7   2954 my $Have_ImageSize = eval 'use Image::Size; 1' ? 1 : 0;   0         0     0         0   1987               1988             sub image_size { 1989 0     0 1 0 my $self = shift; 1990 0         0 my ($file) = @_; 1991 0 0       0 if ($Have_ImageSize) { 1992 0 0       0 if (-f $file) { 1993 0         0 return Image::Size::imgsize($file); 1994             } else { 1995 0 0       0 if (my $docroot = $self->docroot) { 1996 0         0 require File::Spec; 1997 0         0 my $fullpath = File::Spec->catfile($docroot, $file); 1998 0 0       0 if (-f $fullpath) { 1999 0         0 return Image::Size::imgsize($fullpath); 2000             } 2001             } 2002             } 2003             } 2004 0         0 return undef; 2005             } 2006             } 2007               2008             sub encode_url { 2009 0     0 1 0 my $self = shift; 2010 0         0 my($str) = @_; 2011 0         0 $str =~ s!([^A-Za-z0-9_\.\-\+\&=\%;])! 2012 0 0       0 ord($1) > 255 ? '%u' . (uc sprintf("%04x", ord($1))) 2013             : '%' . (uc sprintf("%02x", ord($1)))!egx; 2014 0         0 return $str; 2015             } 2016               2017             sub mail_encode { 2018 0     0 1 0 my $self = shift; 2019 0         0 my ($addr) = @_; 2020             # granted, this is simple, but it gives off warm fuzzies 2021 0         0 $addr =~ s!([^\$])! 2022 0 0       0 ord($1) > 255 ? '%u' . (uc sprintf("%04x", ord($1))) 2023             : '%' . (uc sprintf("%02x", ord($1)))!egx; 2024 0         0 return $addr; 2025             } 2026               2027             sub process_quotes { 2028             # stub routine for now. subclass and implement. 2029 31     31 1 45 my $self = shift; 2030 31         50 my ($str) = @_; 2031 31         74 return $str; 2032             } 2033               2034             # a default set of macros for the {...} macro syntax 2035             # just a handy way to write a lot of the international characters 2036             # and some commonly used symbols 2037               2038             sub default_macros { 2039 8     8 1 15 my $self = shift; 2040             # <, >, " must be html entities in the macro text since 2041             # those values are escaped by the time they are processed 2042             # for macros. 2043             return { 2044 8         957 'c|' => '¢', # CENT SIGN 2045             '|c' => '¢', # CENT SIGN 2046             'L-' => '£', # POUND SIGN 2047             '-L' => '£', # POUND SIGN 2048             'Y=' => '¥', # YEN SIGN 2049             '=Y' => '¥', # YEN SIGN 2050             '(c)' => '©', # COPYRIGHT SIGN 2051             '<<' => '«', # LEFT-POINTING DOUBLE ANGLE QUOTATION 2052             '(r)' => '®', # REGISTERED SIGN 2053             '+_' => '±', # PLUS-MINUS SIGN 2054             '_+' => '±', # PLUS-MINUS SIGN 2055             '>>' => '»', # RIGHT-POINTING DOUBLE ANGLE QUOTATION 2056             '1/4' => '¼', # VULGAR FRACTION ONE QUARTER 2057             '1/2' => '½', # VULGAR FRACTION ONE HALF 2058             '3/4' => '¾', # VULGAR FRACTION THREE QUARTERS 2059             'A`' => 'À', # LATIN CAPITAL LETTER A WITH GRAVE 2060             '`A' => 'À', # LATIN CAPITAL LETTER A WITH GRAVE 2061             'A\'' => 'Á', # LATIN CAPITAL LETTER A WITH ACUTE 2062             '\'A' => 'Á', # LATIN CAPITAL LETTER A WITH ACUTE 2063             'A^' => 'Â', # LATIN CAPITAL LETTER A WITH CIRCUMFLEX 2064             '^A' => 'Â', # LATIN CAPITAL LETTER A WITH CIRCUMFLEX 2065             'A~' => 'Ã', # LATIN CAPITAL LETTER A WITH TILDE 2066             '~A' => 'Ã', # LATIN CAPITAL LETTER A WITH TILDE 2067             'A"' => 'Ä', # LATIN CAPITAL LETTER A WITH DIAERESIS 2068             '"A' => 'Ä', # LATIN CAPITAL LETTER A WITH DIAERESIS 2069             'Ao' => 'Å', # LATIN CAPITAL LETTER A WITH RING ABOVE 2070             'oA' => 'Å', # LATIN CAPITAL LETTER A WITH RING ABOVE 2071             'AE' => 'Æ', # LATIN CAPITAL LETTER AE 2072             'C,' => 'Ç', # LATIN CAPITAL LETTER C WITH CEDILLA 2073             ',C' => 'Ç', # LATIN CAPITAL LETTER C WITH CEDILLA 2074             'E`' => 'È', # LATIN CAPITAL LETTER E WITH GRAVE 2075             '`E' => 'È', # LATIN CAPITAL LETTER E WITH GRAVE 2076             'E\'' => 'É', # LATIN CAPITAL LETTER E WITH ACUTE 2077             '\'E' => 'É', # LATIN CAPITAL LETTER E WITH ACUTE 2078             'E^' => 'Ê', # LATIN CAPITAL LETTER E WITH CIRCUMFLEX 2079             '^E' => 'Ê', # LATIN CAPITAL LETTER E WITH CIRCUMFLEX 2080             'E"' => 'Ë', # LATIN CAPITAL LETTER E WITH DIAERESIS 2081             '"E' => 'Ë', # LATIN CAPITAL LETTER E WITH DIAERESIS 2082             'I`' => 'Ì', # LATIN CAPITAL LETTER I WITH GRAVE 2083             '`I' => 'Ì', # LATIN CAPITAL LETTER I WITH GRAVE 2084             'I\'' => 'Í', # LATIN CAPITAL LETTER I WITH ACUTE 2085             '\'I' => 'Í', # LATIN CAPITAL LETTER I WITH ACUTE 2086             'I^' => 'Î', # LATIN CAPITAL LETTER I WITH CIRCUMFLEX 2087             '^I' => 'Î', # LATIN CAPITAL LETTER I WITH CIRCUMFLEX 2088             'I"' => 'Ï', # LATIN CAPITAL LETTER I WITH DIAERESIS 2089             '"I' => 'Ï', # LATIN CAPITAL LETTER I WITH DIAERESIS 2090             'D-' => 'Ð', # LATIN CAPITAL LETTER ETH 2091             '-D' => 'Ð', # LATIN CAPITAL LETTER ETH 2092             'N~' => 'Ñ', # LATIN CAPITAL LETTER N WITH TILDE 2093             '~N' => 'Ñ', # LATIN CAPITAL LETTER N WITH TILDE 2094             'O`' => 'Ò', # LATIN CAPITAL LETTER O WITH GRAVE 2095             '`O' => 'Ò', # LATIN CAPITAL LETTER O WITH GRAVE 2096             'O\'' => 'Ó', # LATIN CAPITAL LETTER O WITH ACUTE 2097             '\'O' => 'Ó', # LATIN CAPITAL LETTER O WITH ACUTE 2098             'O^' => 'Ô', # LATIN CAPITAL LETTER O WITH CIRCUMFLEX 2099             '^O' => 'Ô', # LATIN CAPITAL LETTER O WITH CIRCUMFLEX 2100             'O~' => 'Õ', # LATIN CAPITAL LETTER O WITH TILDE 2101             '~O' => 'Õ', # LATIN CAPITAL LETTER O WITH TILDE 2102             'O"' => 'Ö', # LATIN CAPITAL LETTER O WITH DIAERESIS 2103             '"O' => 'Ö', # LATIN CAPITAL LETTER O WITH DIAERESIS 2104             'O/' => 'Ø', # LATIN CAPITAL LETTER O WITH STROKE 2105             '/O' => 'Ø', # LATIN CAPITAL LETTER O WITH STROKE 2106             'U`' => 'Ù', # LATIN CAPITAL LETTER U WITH GRAVE 2107             '`U' => 'Ù', # LATIN CAPITAL LETTER U WITH GRAVE 2108             'U\'' => 'Ú', # LATIN CAPITAL LETTER U WITH ACUTE 2109             '\'U' => 'Ú', # LATIN CAPITAL LETTER U WITH ACUTE 2110             'U^' => 'Û', # LATIN CAPITAL LETTER U WITH CIRCUMFLEX 2111             '^U' => 'Û', # LATIN CAPITAL LETTER U WITH CIRCUMFLEX 2112             'U"' => 'Ü', # LATIN CAPITAL LETTER U WITH DIAERESIS 2113             '"U' => 'Ü', # LATIN CAPITAL LETTER U WITH DIAERESIS 2114             'Y\'' => 'Ý', # LATIN CAPITAL LETTER Y WITH ACUTE 2115             '\'Y' => 'Ý', # LATIN CAPITAL LETTER Y WITH ACUTE 2116             'a`' => 'à', # LATIN SMALL LETTER A WITH GRAVE 2117             '`a' => 'à', # LATIN SMALL LETTER A WITH GRAVE 2118             'a\'' => 'á', # LATIN SMALL LETTER A WITH ACUTE 2119             '\'a' => 'á', # LATIN SMALL LETTER A WITH ACUTE 2120             'a^' => 'â', # LATIN SMALL LETTER A WITH CIRCUMFLEX 2121             '^a' => 'â', # LATIN SMALL LETTER A WITH CIRCUMFLEX 2122             'a~' => 'ã', # LATIN SMALL LETTER A WITH TILDE 2123             '~a' => 'ã', # LATIN SMALL LETTER A WITH TILDE 2124             'a"' => 'ä', # LATIN SMALL LETTER A WITH DIAERESIS 2125             '"a' => 'ä', # LATIN SMALL LETTER A WITH DIAERESIS 2126             'ao' => 'å', # LATIN SMALL LETTER A WITH RING ABOVE 2127             'oa' => 'å', # LATIN SMALL LETTER A WITH RING ABOVE 2128             'ae' => 'æ', # LATIN SMALL LETTER AE 2129             'c,' => 'ç', # LATIN SMALL LETTER C WITH CEDILLA 2130             ',c' => 'ç', # LATIN SMALL LETTER C WITH CEDILLA 2131             'e`' => 'è', # LATIN SMALL LETTER E WITH GRAVE 2132             '`e' => 'è', # LATIN SMALL LETTER E WITH GRAVE 2133             'e\'' => 'é', # LATIN SMALL LETTER E WITH ACUTE 2134             '\'e' => 'é', # LATIN SMALL LETTER E WITH ACUTE 2135             'e^' => 'ê', # LATIN SMALL LETTER E WITH CIRCUMFLEX 2136             '^e' => 'ê', # LATIN SMALL LETTER E WITH CIRCUMFLEX 2137             'e"' => 'ë', # LATIN SMALL LETTER E WITH DIAERESIS 2138             '"e' => 'ë', # LATIN SMALL LETTER E WITH DIAERESIS 2139             'i`' => 'ì', # LATIN SMALL LETTER I WITH GRAVE 2140             '`i' => 'ì', # LATIN SMALL LETTER I WITH GRAVE 2141             'i\'' => 'í', # LATIN SMALL LETTER I WITH ACUTE 2142             '\'i' => 'í', # LATIN SMALL LETTER I WITH ACUTE 2143             'i^' => 'î', # LATIN SMALL LETTER I WITH CIRCUMFLEX 2144             '^i' => 'î', # LATIN SMALL LETTER I WITH CIRCUMFLEX 2145             'i"' => 'ï', # LATIN SMALL LETTER I WITH DIAERESIS 2146             '"i' => 'ï', # LATIN SMALL LETTER I WITH DIAERESIS 2147             'n~' => 'ñ', # LATIN SMALL LETTER N WITH TILDE 2148             '~n' => 'ñ', # LATIN SMALL LETTER N WITH TILDE 2149             'o`' => 'ò', # LATIN SMALL LETTER O WITH GRAVE 2150             '`o' => 'ò', # LATIN SMALL LETTER O WITH GRAVE 2151             'o\'' => 'ó', # LATIN SMALL LETTER O WITH ACUTE 2152             '\'o' => 'ó', # LATIN SMALL LETTER O WITH ACUTE 2153             'o^' => 'ô', # LATIN SMALL LETTER O WITH CIRCUMFLEX 2154             '^o' => 'ô', # LATIN SMALL LETTER O WITH CIRCUMFLEX 2155             'o~' => 'õ', # LATIN SMALL LETTER O WITH TILDE 2156             '~o' => 'õ', # LATIN SMALL LETTER O WITH TILDE 2157             'o"' => 'ö', # LATIN SMALL LETTER O WITH DIAERESIS 2158             '"o' => 'ö', # LATIN SMALL LETTER O WITH DIAERESIS 2159             ':-' => '÷', # DIVISION SIGN 2160             '-:' => '÷', # DIVISION SIGN 2161             'o/' => 'ø', # LATIN SMALL LETTER O WITH STROKE 2162             '/o' => 'ø', # LATIN SMALL LETTER O WITH STROKE 2163             'u`' => 'ù', # LATIN SMALL LETTER U WITH GRAVE 2164             '`u' => 'ù', # LATIN SMALL LETTER U WITH GRAVE 2165             'u\'' => 'ú', # LATIN SMALL LETTER U WITH ACUTE 2166             '\'u' => 'ú', # LATIN SMALL LETTER U WITH ACUTE 2167             'u^' => 'û', # LATIN SMALL LETTER U WITH CIRCUMFLEX 2168             '^u' => 'û', # LATIN SMALL LETTER U WITH CIRCUMFLEX 2169             'u"' => 'ü', # LATIN SMALL LETTER U WITH DIAERESIS 2170             '"u' => 'ü', # LATIN SMALL LETTER U WITH DIAERESIS 2171             'y\'' => 'ý', # LATIN SMALL LETTER Y WITH ACUTE 2172             '\'y' => 'ý', # LATIN SMALL LETTER Y WITH ACUTE 2173             'y"' => 'ÿ', # LATIN SMALL LETTER Y WITH DIAERESIS 2174             '"y' => 'ÿ', # LATIN SMALL LETTER Y WITH DIAERESIS 2175             'OE' => 'Œ', # LATIN CAPITAL LIGATURE OE 2176             'oe' => 'œ', # LATIN SMALL LIGATURE OE 2177             '*' => 'ߦ', # BULLET 2178             'Fr' => '₣', # FRENCH FRANC SIGN 2179             'L=' => '₤', # LIRA SIGN 2180             '=L' => '₤', # LIRA SIGN 2181             'Rs' => '₨', # RUPEE SIGN 2182             'C=' => '€', # EURO SIGN 2183             '=C' => '€', # EURO SIGN 2184             'tm' => '™', # TRADE MARK SIGN 2185             '<-' => '←', # LEFTWARDS ARROW 2186             '->' => '→', # RIGHTWARDS ARROW 2187             '<=' => '⇐', # LEFTWARDS DOUBLE ARROW 2188             '=>' => '⇒', # RIGHTWARDS DOUBLE ARROW 2189             '=/' => '≠', # NOT EQUAL TO 2190             '/=' => '≠', # NOT EQUAL TO 2191             '<_' => '≤', # LESS-THAN OR EQUAL TO 2192             '_<' => '≤', # LESS-THAN OR EQUAL TO 2193             '>_' => '≥', # GREATER-THAN OR EQUAL TO 2194             '_>' => '≥', # GREATER-THAN OR EQUAL TO 2195             ':(' => '☹', # WHITE FROWNING FACE 2196             ':)' => '☺', # WHITE SMILING FACE 2197             'spade' => '♠', # BLACK SPADE SUIT 2198             'club' => '♣', # BLACK CLUB SUIT 2199             'heart' => '♥', # BLACK HEART SUIT 2200             'diamond' => '♦', # BLACK DIAMOND SUIT 2201             }; 2202             } 2203               2204             # "private", internal routines 2205               2206             sub _css_defaults { 2207 8     8   15 my $self = shift; 2208 8         102 my %css_defaults = ( 2209             class_align_right => 'right', 2210             class_align_left => 'left', 2211             class_align_center => 'center', 2212             class_align_top => 'top', 2213             class_align_bottom => 'bottom', 2214             class_align_middle => 'middle', 2215             class_align_justify => 'justify', 2216             class_caps => 'caps', 2217             class_footnote => 'footnote', 2218             id_footnote_prefix => 'fn', 2219             ); 2220 8         32 return $self->css(\%css_defaults); 2221             } 2222               2223             sub _halign { 2224 0     0   0 my ($align) = @_; 2225               2226 0 0       0 if ($align =~ m/<>/) {     0               0               0           2227 0         0 return 'justify'; 2228             } elsif ($align =~ m/ 2229 0         0 return 'left'; 2230             } elsif ($align =~ m/>/) { 2231 0         0 return 'right'; 2232             } elsif ($align =~ m/=/) { 2233 0         0 return 'center'; 2234             } 2235 0         0 return ''; 2236             } 2237               2238             sub _valign { 2239 0     0   0 my ($align) = @_; 2240               2241 0 0       0 if ($align =~ m/\^/) {     0               0           2242 0         0 return 'top'; 2243             } elsif ($align =~ m/~/) { 2244 0         0 return 'bottom'; 2245             } elsif ($align =~ m/-/) { 2246 0         0 return 'middle'; 2247             } 2248 0         0 return ''; 2249             } 2250               2251             sub _imgalign { 2252 0     0   0 my ($align) = @_; 2253               2254 0         0 $align =~ s/(<>|=)//g; 2255 0   0     0 return _valign($align) || _halign($align); 2256             } 2257               2258             sub _strip_borders { 2259 0     0   0 my ($pre, $post) = @_; 2260 0 0 0     0 if (${$post} && ${$pre} && ((my $open = substr(${$pre}, 0, 1)) =~ m/[{[]/)) {   0   0     0     0         0     0         0   2261 0         0 my $close = substr(${$post}, 0, 1);   0         0   2262 0 0 0     0 if ((($open eq '{') && ($close eq '}')) ||       0               0         2263             (($open eq '[') && ($close eq ']'))) { 2264 0         0 ${$pre} = substr(${$pre}, 1);   0         0     0         0   2265 0         0 ${$post} = substr(${$post}, 1);   0         0     0         0   2266             } else { 2267 0 0       0 $close = substr(${$post}, -1, 1) if $close !~ m/[}\]]/;   0         0   2268 0 0 0     0 if ((($open eq '{') && ($close eq '}')) ||       0               0         2269             (($open eq '[') && ($close eq ']'))) { 2270 0         0 ${$pre} = substr(${$pre}, 1);   0         0     0         0   2271 0         0 ${$post} = substr(${$post}, 0, length(${$post}) - 1);   0         0     0         0     0         0   2272             } 2273             } 2274             } 2275 0         0 return; 2276             } 2277               2278             sub _repl { 2279 9     9   44 push @{$_[0]}, $_[1];   9         24   2280               2281 9         13 return '';   9         68   2282             } 2283               2284             sub _tokenize { 2285 2     2   5 my $str = shift; 2286 2         4 my $pos = 0; 2287 2         5 my $len = length $str; 2288 2         3 my @tokens; 2289               2290 2         3 my $depth = 6; 2291 2         15 my $nested_tags = join('|', ('(?:]') x $depth) 2292             . (')*>)' x $depth); 2293 2         190 my $match = qr/(?s: )| # comment 2294             (?s: <\? .*? \?> )| # processing instruction 2295             (?s: <\% .*? \%> )| # ASP-like 2296             (?:$nested_tags)| 2297             (?:$codere)/x; # nested tags 2298               2299 2         229 while ($str =~ m/($match)/g) { 2300 0         0 my $whole_tag = $1; 2301 0         0 my $sec_start = pos $str; 2302 0         0 my $tag_start = $sec_start - length $whole_tag; 2303 0 0       0 if ($pos < $tag_start) { 2304 0         0 push @tokens, ['text', substr($str, $pos, $tag_start - $pos)]; 2305             } 2306 0 0       0 if ($whole_tag =~ m/^[[{]?\@/) { 2307 0         0 push @tokens, ['text', $whole_tag]; 2308             } else { 2309             # this clever hack allows us to preserve \n within tags. 2310             # this is restored at the end of the format_paragraph method 2311             #$whole_tag =~ s/\n/\r/g; 2312 0         0 $whole_tag =~ s/\n/\001/g; 2313 0         0 push @tokens, ['tag', $whole_tag]; 2314             } 2315 0         0 $pos = pos $str; 2316             } 2317 2 50       18 push @tokens, ['text', substr($str, $pos, $len - $pos)] if $pos < $len; 2318               2319 2         11 return \@tokens; 2320             } 2321               2322             1; 2323             __END__