File Coverage

blib/lib/Quiki/Formatter/HTML.pm
Criterion Covered Total %
statement 157 178 88.2
branch 64 96 66.6
condition 10 21 47.6
subroutine 26 31 83.8
pod 1 1 100.0
total 258 327 78.9


line stmt bran cond sub pod time code
1             package Quiki::Formatter::HTML;
2              
3 2     2   5336 use CGI qw/:standard/;
  2         34007  
  2         14  
4 2     2   9768 use URI::Escape;
  2         2929  
  2         135  
5 2     2   1750 use Regexp::Common qw/URI/;
  2         5119  
  2         10  
6              
7             # Formatter (format)
8             #------------------------------------------------------------
9             # Receives a string. Splits in empty lines (LaTeX like).
10             # Note that lines with spaces are not empty.
11             # Each chunk is processed by _format_chunk.
12             sub format {
13 44     44 1 889 my ($Quiki, $string) = @_;
14              
15 44         119 $string =~ s/\r//g;
16              
17 44         260 my @chunks = split /^$(?:\n^$)*/m, $string;
18              
19 44         108 my $html = join("\n\n", map { _format_chunk($Quiki, $_) } @chunks);
  69         191  
20 44         476 return $html . "\n";
21             }
22              
23             sub _tds {
24 12     12   22 my ($Quiki, $content) = @_;
25              
26 12 100       43 if ($content =~ /^\S/) {
27 1         7 return td({-style=>"text-align: left"}, _inlines($Quiki, $content));
28             }
29              
30 11 100       37 if ($content =~ /\S$/) {
31 1         5 return td({-style=>"text-align: right"}, _inlines($Quiki, $content));
32             }
33              
34 10         44 return td({-style=>"text-align: center"}, _inlines($Quiki, $content));
35             }
36              
37             sub _format_table {
38 5     5   10 my ($Quiki, $chunk) = @_;
39              
40 5         17 my @c = split /\n/, $chunk;
41 5         9 my $table = "\n";
42              
43 5   66     46 while (@c && $c[0] =~ /^(\^|\|)/) {
44 7         27 $c[0] =~ s/^(.)//;
45 7 100       25 if ($1 eq "^") {
46 3         10 $table .= Tr(th([map { _inlines($Quiki, $_) } split /\^/, $c[0]])) . "\n";
  9         20  
47             } else {
48 4         35 $table .= Tr(join(" ",map { _tds($Quiki, $_) } split /\|(?![^\[]+\]\])/, $c[0])) . "\n";
  12         1167  
49             }
50 7         1948 shift @c;
51             }
52 5         11 $table .= "
\n";
53              
54 5 50       28 $chunk = $table . (@c ?
55             _format_chunk($Quiki, join("\n", @c)) :
56             "");
57             }
58              
59             sub _format_list {
60 9     9   13 my ($Quiki, $chunk) = @_;
61 9         15 my @level = ();
62 9         15 my $openitem = 0;
63 9         9 my $list;
64 9         37 my @c = split /\n/, $chunk;
65              
66 9   100     80 while (@c && $c[0] =~ /^((?:\s{2})+)([*-])(.*)$/) {
67 48         138 my $level = length($1)/2 - 1;
68 48         78 my $type = $2;
69 48         119 my $item = $3;
70 48 100       191 if ($level > $#level) {
    100          
71 18         35 push @level, $type;
72 18 100       47 $list .= ($type eq "*")?"
    ":"
      ";
73 18         24 $openitem = 0;
74 18         145 $list .= "\n";
75             }
76             elsif ($level < $#level) {
77 2 50       10 $list .= "\n" if $openitem;
78              
79 2         4 my $ctype = pop @level;
80 2 100       8 $list .= ($ctype eq "*")?"":"";
81 2         17 $list .= "\n";
82             }
83             else {
84 28 100       63 $list .= "\n" if $openitem;
85 28 100       77 if ($type ne $level[-1]) {
86 1         3 my $ctype = pop @level;
87 1 50       8 $list .= ($ctype eq "*")?"":"";
88 1         2 $list .= "\n";
89 1         2 push @level, $type;
90 1 50       5 $list .= ($type eq "*")?"
    ":"
      ";
91 1         2 $list .= "\n";
92             }
93 28         63 $list .= "
  • "._unbackslash(_inlines($Quiki, $item));
  • 94 28         50 $openitem = 1;
    95 28         243 shift @c;
    96             }
    97             }
    98 9         29 while (@level) {
    99 16         25 my $ctype = pop @level;
    100 16         22 $list .= "\n";
    101 16 100       38 $list .= ($ctype eq "*")?"":"";
    102 16         42 $list .= "\n";
    103             }
    104              
    105 9 100       43 return (@c)?($list . "\n\n" . _format_chunk($Quiki, join("\n", @c))):$list;
    106             }
    107              
    108             # _format_chunk
    109             #------------------------------------------------------------
    110             # Receives a chunk string. Analyzes it and calls the correct
    111             # formatter.
    112             sub _format_chunk {
    113 76     76   123 my ($Quiki, $chunk) = @_;
    114 76         307 $chunk =~ s/\n$//;
    115 76         150 $chunk =~ s/\n\s{1,2}\n/\n\n/g;
    116 76         169 $chunk =~ s/^\n//;
    117 76         161 $chunk = _protect($chunk);
    118              
    119 76 100       601 if ($chunk =~ /^(\^|\|)/) {
        100          
        100          
    120 5         16 $chunk = _format_table($Quiki, $chunk);
    121 5         17 $chunk = _unbackslash($chunk);
    122             }
    123             elsif ($chunk =~ /^\s{2}[*-]/) {
    124 9         32 $chunk = _format_list($Quiki, $chunk);
    125 9         21 $chunk = _unbackslash($chunk);
    126             }
    127             elsif ($chunk =~ /^\s{3}/) {
    128 4         21 $chunk = _format_verbatim($chunk);
    129              
    130             }
    131             else {
    132 58 100       238 if ($chunk =~ /^ -{10,} \s* (\n|$) /x) {
        100          
    133 6         20 $chunk =~ s/^ -+ \s* //x;
    134 6 100       24 $chunk = $chunk ? ('
    ' . _format_chunk($Quiki, $chunk)) : '
    ';
    135             }
    136             elsif ($chunk =~ /^(={1,6}) ((?:\\=|[^=]|\/[^=])+) \1\s*($|\n)/x) {
    137 7         23 my ($delim, $title) = ($1, $2);
    138 7         24 $chunk =~ s/.*($|\n)//;
    139              
    140 7         12 my $l = length($delim);
    141 7 100       22 $title = h6(_inlines($Quiki, $title)) if $l == 1;
    142 7 100       438 $title = h5(_inlines($Quiki, $title)) if $l == 2;
    143 7 100       428 $title = h4(_inlines($Quiki, $title)) if $l == 3;
    144 7 100       656 $title = h3(_inlines($Quiki, $title)) if $l == 4;
    145 7 100       464 $title = h2(_inlines($Quiki, $title)) if $l == 5;
    146 7 100       430 $title = h1(_inlines($Quiki, $title)) if $l == 6;
    147              
    148 7 100       569 $chunk = $chunk ? ($title . "\n\n" . _format_chunk($Quiki, $chunk)) : $title;
    149             }
    150             else {
    151 45         101 $chunk = p(_inlines($Quiki, $chunk));
    152             }
    153 58         7952 $chunk = _unbackslash($chunk);
    154             }
    155 76         293 return $chunk;
    156             }
    157              
    158             sub _expand_entities {
    159 123     123   200 my $string = shift;
    160 123         242 for ($string) {
    161 123         211 s/--/—/g;
    162 123         223 s/\(c\)/©/g;
    163 123         177 s/\(r\)/®/g;
    164 123         208 s/<->/↔/g;
    165 123         229 s/<=>/⇔/g;
    166 123         215 s/->/→/g;
    167 123         162 s/<-/←/g;
    168 123         149 s/=>/⇒/g;
    169 123         295 s/<=/⇐/g;
    170              
    171             }
    172 123         2880 return $string;
    173             }
    174              
    175             sub _format_verbatim {
    176 4     4   7 my $chunk = shift;
    177 4         5 my $pre;
    178 4         14 my @c = split /\n/, $chunk;
    179              
    180 4   100     32 while (@c && $c[0] =~ /^\s{3}(.*)/) {
    181 10         21 $pre .= $1 . "\n";
    182 10         51 shift @c;
    183             }
    184              
    185 4         66 $pre = pre($pre);
    186              
    187 4 100       633 return $pre . ( @c ? ("\n\n" . _format_chunk($Quiki, join("\n", @c))) : "");
    188             }
    189              
    190             our %SAVES;
    191             our $SAVES;
    192              
    193             sub _inlines {
    194 123     123   239 my ($Quiki, $chunk) = @_;
    195              
    196 123         214 my $script = $Quiki->{SCRIPT_NAME};
    197              
    198             sub _saveit {
    199 9     9   1646 my $text = shift;
    200 9         13 $SAVES++;
    201 9         39 $SAVES{"#$SAVES"} = $text;
    202 9         110 return "#$SAVES";
    203             }
    204 9     9   87 sub _loadit { $SAVES{$_[0]} }
    205              
    206             my @inline =
    207             (
    208             ## [[http://foo]] -- same as http://foo ?
    209 1     1   53 qr/\[\[(\w+:\/\/[^\]|]+)\]\]/ => sub { _saveit(a({-href=>$1}, $1)) },
    210             ## [[nodo]]
    211             qr/\[\[([^\]|]+)\]\]/ => sub {
    212 3     3   16 _saveit(a({-href=>"$script?node=".uri_escape($1) }, $1))
    213             },
    214             ## [[protocol://foo|descricao]]
    215             qr/\[\[(\w+:\/\/[^\]|]+)\|([^\]|]+)\]\]/ => sub {
    216 3     3   27 _saveit(a({-href=>$1}, _inlines($Quiki, $2)))
    217             },
    218             ## [[nodo|descricao]]
    219             qr/\[\[([^\]|]+)\|([^\]|]+)\]\]/ => sub {
    220 2     2   15 _saveit(a({-href=>"$script?node=".uri_escape($1) }, _inlines($Quiki, $2)))
    221             },
    222              
    223             ## ** foo **
    224 6     6   14 qr/\*\* ((?:\\\*|[^*]|\*[^*])+) \*\*/x => sub { b(_inlines($Quiki, $1)) },
    225             ## __ foo __
    226 3     3   9 qr/__ ((?:\\_|[^_]|_[^_])+) __/x => sub { u(_inlines($Quiki, $1)) },
    227             ## // foo //
    228 7     7   20 qr/\/\/ ((?:\\\/|[^\/]|\/[^\/])+) \/\//x => sub { i(_inlines($Quiki, $1)) },
    229             ## '' foo ''
    230 1     1   4 qr/'' ((?:\\'|[^']|'[^'])+) ''/x => sub { tt(_inlines($Quiki, $1)) },
    231              
    232             ## {{wiki: foo | desc }}
    233             qr/\{\{(\s*)wiki:([^}|]+)\|([^}]+?)(\s*)\}\}/ => sub {
    234 0 0 0 0   0 my $align = (length($1) && length($4))?"center":
        0          
        0          
    235             (length($1)?"right":
    236             (length($4)?"left":""));
    237 0         0 _inline_doc($Quiki, $2,$3, $align)
    238             },
    239             ## {{wiki: foo }}
    240             qr/\{\{(\s*)wiki:([^}]+?)(\s*)\}\}/ => sub {
    241 0 0 0 0   0 my $align = (length($1) && length($3))?"center":
        0          
        0          
    242             (length($1)?"right":
    243             (length($3)?"left":""));
    244 0         0 _inline_doc($Quiki, $2,$2, $align) },
    245              
    246             ## {{ foo | desc }}
    247             qr/\{\{(\s*)([^}|]+)\|([^}]+?)(\s*)\}\}/ => sub {
    248 0 0 0 0   0 my $align = (length($1) && length($4))?"center":
        0          
        0          
    249             (length($1)?"right":
    250             (length($4)?"left":""));
    251 0         0 _inline_pic($Quiki, $2, $3, $align);
    252             },
    253             ## {{ foo }}
    254             qr/\{\{(\s*)([^}]+?)(\s*)\}\}/ => sub {
    255 3 100 66 3   23 my $align = (length($1) && length($3))?"center":
        100          
        50          
    256             (length($1)?"right":
    257             (length($3)?"left":""));
    258 3         6 _inline_pic($Quiki, $2, $2, $align);
    259             },
    260              
    261             ## urls que nao sigam aspas
    262 0     0   0 qr/(? sub { a({-href=>$1}, $1) },
    263              
    264             ## savits
    265 9     9   26 qr/(\#\d+)/ => sub { _loadit($1) },
    266              
    267 123         13297 );
    268              
    269 123         28507 while (@inline) {
    270 1722         5751 my $re = shift @inline;
    271 1722         2342 my $code = shift @inline;
    272 1722         361445 $chunk =~ s/(?() /xeg;
      38         151  
    273             }
    274              
    275 123         437 return _expand_entities($chunk);
    276             }
    277              
    278             sub _inline_pic {
    279 3     3   8 my ($quiki, $url, $desc, $align) = @_;
    280              
    281 3 100       9 if ($align eq "right") {
    282 1         26 return img({-alt=>$desc, -title=>$desc,
    283             -src=>$url, -style=>"float: right"})
    284             }
    285              
    286 2 100       5 if ($align eq "left") {
    287 1         25 return img({-alt=>$desc, -title=>$desc,
    288             -src=>$url, -style=>"float: left"})
    289             }
    290              
    291 1 50       4 if ($align eq "center") {
    292 0         0 return div({-style=>"text-align: center"},
    293             img({-alt=>$desc, -title=>$desc,
    294             -src=>$url}))
    295             }
    296              
    297 1         12 return img({-alt=>$desc, -title=>$desc, -src=>$url})
    298             }
    299              
    300             sub _inline_doc {
    301 0     0   0 my ($quiki, $id, $desc, $align) = @_;
    302 0         0 my $node = $quiki->{node};
    303 0         0 my $mm = new File::MMagic;
    304 0         0 my $mime = $mm->checktype_filename("data/attach/$node/$id");
    305 0 0       0 if ($mime =~ /^image/) {
    306 0 0       0 if ($align eq "right") {
    307 0         0 return img({-alt=>$desc, -src=>"data/attach/$node/$id", -style=>"float: right"})
    308             }
    309              
    310 0 0       0 if ($align eq "left") {
    311 0         0 return img({-alt=>$desc, -src=>"data/attach/$node/$id", -style=>"float: left"})
    312             }
    313              
    314 0 0       0 if ($align eq "center") {
    315 0         0 return div({-style=>"text-align: center"},
    316             img({-alt=>$desc, -src=>"data/attach/$node/$id"}))
    317             }
    318              
    319 0         0 return img({-alt=>$desc, -src=>"data/attach/$node/$id"})
    320             }
    321             else {
    322 0         0 a({-href=>"data/attach/$node/$id", -target=>"_new"},
    323             img({-alt => "Attachment",
    324             -src => "images/mime_default.png"}), $desc)
    325             }
    326             }
    327              
    328             sub _unbackslash {
    329 100     100   152 my $string = shift;
    330 100         215 $string =~ s/\\(.)/$1/g;
    331 100         226 return $string;
    332             }
    333              
    334             sub _protect {
    335 76     76   122 my $string = shift;
    336 76         143 for ($string) {
    337 76         110 s/&/&/g;
    338 76         111 s/>/>/g;
    339 76         182 s/
    340             }
    341 76         174 return $string;
    342             }
    343              
    344             "false";
    345              
    346             =encoding UTF-8
    347              
    348             =head1 NAME
    349              
    350             Quiki::Formatter::HTML - Quiki HTML formatter module
    351              
    352             =head1 SYNOPSIS
    353              
    354             use Quiki::Formatter::HTML;
    355             my $html = Quiki::Formatter::HTML::format($string);
    356              
    357             =head1 DESCRIPTION
    358              
    359             Hides formatting subroutine.
    360              
    361             =head1 EXPORTS
    362              
    363             None. Use Quiki::Formatter::HTML::format.
    364              
    365             =head2 format
    366              
    367             Receives a string in Wiki syntax. Returns a string in HTML.
    368              
    369             =head2 format_page
    370              
    371             Receives a Wiki page. Returns it in HTML.
    372              
    373             =head1 SEE ALSO
    374              
    375             Quiki::Syntax, Quiki
    376              
    377             =head1 AUTHOR
    378              
    379             Alberto Simões, Eambs@cpan.orgE
    380             Nuno Carvalho, Esmash@cpan.orgE
    381              
    382             =head1 COPYRIGHT & LICENSE
    383              
    384             Copyright 2009-2010 Alberto Simoes and Nuno Carvalho.
    385              
    386             This program is free software; you can redistribute it and/or modify it
    387             under the terms of either: the GNU General Public License as published
    388             by the Free Software Foundation; or the Artistic License.
    389              
    390             See http://dev.perl.org/licenses/ for more information.
    391              
    392             =cut