File Coverage

blib/lib/Text/YAWikiFormater.pm
Criterion Covered Total %
statement 229 265 86.4
branch 76 118 64.4
condition 27 60 45.0
subroutine 17 20 85.0
pod 7 7 100.0
total 356 470 75.7


line stmt bran cond sub pod time code
1             package Text::YAWikiFormater;
2              
3 4     4   275462 use 5.010;
  4         45  
4 4     4   23 use strict;
  4         10  
  4         81  
5 4     4   19 use warnings;
  4         6  
  4         118  
6              
7 4     4   2311 use HTML::Entities qw(encode_entities);
  4         23921  
  4         335  
8 4     4   2796 use JSON qw(from_json);
  4         51461  
  4         28  
9              
10             our $VERSION = '0.51';
11              
12             my %plugins = (
13             toc => \&_handle_toc,
14             image => \&_handle_image,
15              
16             restore_code_block => \&_restore_code_block,
17             );
18              
19             my %namespaces = (
20             wp => { prefix => 'http://en.wikipedia.org/', category=>':' },
21             gs => { prefix => 'http://www.google.com/search?q=' },
22             );
23              
24             my %closed = (
25             b => qr{(?:(?
26             i => qr{(?
27             u => qr{__},
28             del => qr{(?
29             tt => qw{''},
30              
31             heads => [qr[^(?=!{1,6}\s)]msix, qr[$]msix, \&_header_id, undef,"\n"],
32              
33             code => [qr[^\{\{\{$]msix,qr[^\}\}\}$]msix, \&_escape_code],
34              
35             blockquote => [qr{^>\s}msix, qr{^(?!>)}msix, qr{^>\s}msix, '',"\n"],
36              
37             lists => [qr{^(?=[\*\#]+\s)}msix, qr{(?:^(?![\*\#\s])|\z)}msix, \&_do_lists],
38              
39             links => [qr{(?=\[\[)}, qr{(?<=\]\])},\&_do_links],
40             links2 => [qr{\s(?=http://)}, qr{\s},\&_do_links],
41              
42             br => [qr{^[\n\s]*(?=$)}msix, qr[$]msix, sub { "

",'',''}],
43              
44             comments => [qr{/\*}msix, qr{\*/}msix, sub{ '','',''}],
45             );
46              
47             my %nonclosed = (
48             hr => qr{^[-\*]{3,}\s*?$}msix,
49             );
50              
51             my @do_first = qw( code lists );
52             # for consistent order
53             my @do_second = qw( b i u del tt heads blockquote links links2 br comments );
54              
55             sub new {
56 4     4 1 268 my $class = shift;
57              
58 4         17 my $self = bless { @_ }, $class;
59              
60 4 50       26 die "body is a mandatory parameter" unless $self->{body};
61              
62 4         12 return $self;
63             }
64              
65             sub urls {
66 4     4 1 11 my $self= shift;
67 4         9 my $body = $self->{body};
68              
69 4 50       13 return unless $body;
70              
71 4         52 my @links = $body =~m{(\[\[(?:[^\|\]]*)(?:\|(?:[^\]]+))?\]\])}g;
72 4         48 push @links, $body =~m{\s(https?://\S+)\s}g;
73              
74 4   50     30 my $links = $self->{_links} ||= {};
75              
76             LINK:
77 4         12 for my $lnk ( @links ) {
78 20 50       47 next if $links->{$lnk};
79              
80 20   50     83 my $hlnk = $links->{$lnk} ||= {};
81              
82 20 100       48 if ($lnk =~ m{\Ahttps?://}) {
83 2         11 %$hlnk = ( title => $lnk, href => $lnk, _class => 'external' );
84 2         7 next LINK;
85             }
86            
87 18         100 ($lnk) = $lnk =~ m{\A\[\[\s*(.*)\s*\]\]\z}g;
88 18         108 $lnk=~s{\s*\z}[]g;
89              
90 18         117 my ($label,$link) = split qr{\s*\|\s*}, $lnk, 2;
91 18 100       55 unless ($link) {
92 7         13 $link = $label;
93 7 100       28 if ( $link =~ m{.*[\>\:]\s*([^\>]+)\z} ) {
94 5         13 $label = $1;
95             }
96             }
97              
98 18         40 $hlnk->{title} = $label;
99 18         31 $hlnk->{original_to} = $link;
100 18 100       38 if ($link =~ m{\Ahttps?://} ) {
101 1         3 $hlnk->{_class} = 'external';
102 1         2 $hlnk->{href} = $link;
103 1         4 next LINK;
104             }
105              
106 17         33 my ($base,$categ) = ('','/');
107 17 100       38 if ( $link =~ m{\A(\w+):} ) {
108 2         11 my ($namespace,$lnk) = split qr{:}, $link, 2;
109 2         5 $link = $lnk;
110 2 50       7 if ( my $nmsp = $namespaces{ $namespace } ){
111 2 50       6 if (ref $nmsp eq 'HASH' ) {
    0          
112 2 50       10 $base = $nmsp->{prefix} if $nmsp->{prefix};
113 2 50       5 $categ = $nmsp->{category} if $nmsp->{category};
114             } elsif (ref $nmsp eq 'CODE') {
115 0         0 ($base, $categ, $lnk) = $nmsp->($namespace,$link);
116 0 0 0     0 if ( $lnk and $lnk =~ m{\Ahttps?://} ) {
    0          
117 0         0 $hlnk->{href} = $lnk;
118 0         0 $hlnk->{_class}='external';
119 0         0 next LINK;
120             } elsif ( $lnk ) {
121 0         0 $link = $lnk;
122             }
123             }
124              
125             } else {
126 0         0 warn "Unknow namespace: $namespace on $lnk\n";
127             }
128             }
129            
130 17 50       35 if ( $categ ) {
131 17         45 $link =~ s{\s*\>\s*}{$categ}g;
132             }
133 17 100       34 if ( $base ) {
134 2         5 $link = $base.$link;
135             }
136 17 100       37 unless ( $link =~ m{\Ahttps?://} ) {
137 15         34 $link = urify( $link );
138             }
139 17         44 $hlnk->{href} = $link;
140             }
141              
142 4 100       17 return wantarray ? %{$self->{_links}} : $self->{_links};
  1         9  
143             }
144              
145             sub urify {
146 21     21 1 37 my $link = shift;
147 21   100     56 my $reg = shift || "^\\w\\-\\/\\s\\#";
148              
149 21         148 $link =~ s{\s*\z}{}g;
150 21 100       63 $link =~ s{\s*>\s*}{/}g unless $link =~ m{/};
151              
152 21         60 $link = encode_entities( $link, $reg );
153 21         1606 $link =~ s{\s+}{-}g;
154 21         65 while (my ($ent)=$link=~/\&(\#?\w+);/) {
155 0 0       0 my $ec=$ent=~/(acute|grave|circ|uml|ring|slash|tilde|cedil)$/i?
156             substr($ent,0,1):'_';
157 0         0 $link=~s/\&$ent;/$ec/ig;
158             }
159 21         46 $link="\L$link";
160 21         34 $link=~s/\_+$//g;
161 21         32 $link=~s/\_+/\_/g;
162              
163 21         43 return $link;
164             }
165              
166             sub set_links {
167 0     0 1 0 my ($self, $links) = @_;
168              
169 0         0 $self->{_links} = $links;
170              
171 0         0 return;
172             }
173              
174             sub format {
175 3     3 1 14 my $self = shift;
176 3         7 my $body = $self->{body};
177              
178 3         8 delete $self->{__headers};
179 3         5 delete $self->{__toc};
180              
181 3         8 my %done = ();
182              
183 3         11 $self->urls();
184              
185 3         6 $body =~ s{&}{&}g;
186 3         8 $body =~ s{<}{<}g;
187 3         14 $body =~ s{>}{>}g;
188              
189             # closed tags
190 3         23 for my $tag ( @do_first, @do_second, keys %closed ) {
191 78 100       217 next if $done{ $tag }++;
192              
193             my ($re1, $re2, $re3, $re4, $re5, $re6)
194             = ref $closed{ $tag } eq 'ARRAY'
195 24         72 ? @{ $closed{ $tag } }
196 39 100       113 : ( $closed{ $tag } );
197              
198 39 100       78 if (!$re2) {
199 15         22 my $in = 0;
200 15         155 while ( $body =~ m{$re1}msix ) {
201 10 100       27 my $tg = $in ? "" :"<$tag>";
202 10         67 $body=~s{$re1}{$tg}msix;
203 10         124 $in = 1 - $in;
204             }
205 15 50       43 $body.="" if $in;
206             } else {
207 24         787 while ($body =~ m{$re1(.*?)$re2}msix) {
208 43         121 my $in = $1;
209 43         107 my ($t1,$t2) = ("<$tag>","");
210 43 100       121 if (ref $re3 eq 'Regexp') {
    50          
211 4   50     12 $re4 //= '';
212 4         42 $in =~ s{ $re3 }{$re4}msixg;
213             } elsif (ref $re3 eq 'CODE') {
214 39         84 ($t1,$in,$t2) = $re3->($self, $t1, $in, $t2);
215             }
216 43   50     89 $t1 //= '';
217 43   50     74 $in //= '';
218 43   50     112 $t2 //= '';
219 43   100     92 $re5 //= '';
220 43         1396 $body =~ s{$re1(.*?)$re2}{$t1$in$t2$re5}smxi;
221             }
222             }
223             }
224              
225 3         15 for my $tag ( keys %nonclosed ) {
226 3         8 my ($re1) = ($nonclosed{ $tag } );
227              
228 3         59 $body =~ s{ $re1 }{<$tag />}msixg;
229             }
230              
231 3         20 while ($body =~ m[(?
232 2         9 my ($plugin, $params) = ($1,$2);
233 2         8 $params = _parse_plugin_params($params);
234              
235 2         5 my $res = '';
236 2 50       7 if ( $plugins{$plugin} ){
237 2   50     8 $res = $plugins{ $plugin }->( $self, $plugin, $params ) // '';
238             }
239              
240 2         33 $body =~ s[(?
241             }
242              
243 3         50 while ($body =~ m[\/\+\+(\w+)(?:[:\s*](.+))?\+\+\/]msix) {
244 1         9 my ($plugin, $params) = ($1,$2);
245 1         14 $params=~s{\A\s*}{};
246 1         8 my @params = split qr{\s*,\s*}, $params;
247              
248 1         3 my $res = '';
249 1 50       4 if ( $plugins{$plugin} ){
250 1   50     7 $res = $plugins{ $plugin }->( $self, $plugin, @params ) // '';
251             }
252              
253 1         38 $body =~ s[\/\+\+(\w+)(?:[:\s*](.+))?\+\+\/][$res]msix;
254             }
255            
256 3         16 return $body;
257             }
258              
259             sub register_namespace {
260 0     0 1 0 my $class = shift;
261              
262 0         0 my ($namespace, $info, $override) = @_;
263              
264             $namespaces{ $namespace } = $info
265 0 0 0     0 if $override or !$namespaces{ $namespace };
266             }
267              
268             sub register_plugin {
269 0     0 1 0 my $class = shift;
270              
271 0         0 my ($pluginname, $pluginref, $override) = @_;
272              
273             $plugins{ $pluginname } = $pluginref
274 0 0 0     0 if $override or !$plugins{ $pluginname };
275             }
276              
277             sub _header_id {
278 6     6   10 my $self = shift;
279 6   100     21 my $headers = $self->{__headers} ||= {};
280 6   100     17 my $headnames = $self->{__headnames} ||= {};
281 6   100     15 my $toc = $self->{__toc} ||= [];
282 6         14 my ($t1, $in, $t2) = @_;
283              
284 6         20 my ($type) = $in =~ m{^(!{1,6})\s};
285 6         21 $in =~ s{^!*\s}{};
286              
287 6         12 $t1 = 'h'.length($type);
288 6         13 $t2 = "";
289 6         9 $t1 = "<$t1>";
290              
291 6         12 my $id = urify($in, "^\\w\\-\\s");
292              
293 6 50       17 if ($headers->{$id}) {
294 0         0 my $cnt = 1;
295 0         0 $cnt++ while $headers->{"${id}_$cnt"};
296 0         0 $id .= "_$cnt";
297             }
298              
299 6         14 $headnames->{$id} = $in;
300 6         15 $headers->{$id} = substr($t1, 2, 1);
301 6         14 push @$toc, $id;
302              
303 6         17 substr($t1, -1, 0, " id='$id'");
304              
305 6         19 return $t1, $in, $t2;
306             }
307              
308             sub _escape_code {
309 1     1   2 my $self = shift;
310              
311 1         4 my ($t1, $in, $t2) = @_;
312              
313 1         6 $in=~s{\n}{
\n}gs;
314              
315 1         4 $self->{__codecnt}++;
316 1         4 $self->{__codeblock}->{$self->{__codecnt}} = $in;
317              
318 1         5 return '',"/++restore_code_block: $self->{__codecnt}++/", '';
319             }
320              
321             sub _do_lists {
322 2     2   5 my $self = shift;
323              
324 2         6 my ($t1, $in, $t2) = @_;
325              
326 2         16 my @lines = split qr{\n}ms, $in;
327 2         6 $in = '';
328 2         4 my $cl = '';
329 2         3 my $item;
330 2         6 for my $ln (@lines) {
331 12 50       30 if ( $ln !~ m{^\s} ) {
332 12 100       24 if ($item) {
333 10         21 $in .= "
  • $item
  • \n";
    334 10         14 $item = '';
    335             }
    336 12         45 my ($nl,$l) = $ln =~ m{^([\*\#]+)\s+(.*)$};
    337 12         20 $ln = $l;
    338 12         18 my $close = '';
    339 12         16 my $start = -1;
    340 12 100       24 if ($nl ne $cl) {
    341 11         25 for my $i (0..length($cl)-1) {
    342 23 100 100     74 next if !$close and substr($cl,$i,1) eq substr($nl, $i, 1);
    343 4 100       12 $start = $i unless $close;
    344 4 100       12 $close = (substr($cl,$i,1) eq '#' ? "" : "").$close;
    345             }
    346 11 100       23 $start = length($cl) if $start == -1;
    347 11 100       22 $in.=$close."\n" if $close;
    348 11         21 for my $i ($start..length($nl)-1) {
    349 11 100       43 $in.= substr($nl, $i, 1) eq '#'?"
      ":"
        ";
    350             }
    351 11         19 $cl = $nl;
    352             }
    353             }
    354 12         22 $item .= $ln;
    355             }
    356 2 50       6 if ($item) {
    357 2         5 $in .= "
  • $item
  • \n";
    358             }
    359 2 50       5 if ($cl) {
    360 2         7 for my $i (reverse 0..length($cl)-1) {
    361 7 50       16 $in.=substr($cl,$i,1) eq '#' ? "" : "";
    362             }
    363 2         4 $in.="\n";
    364             }
    365              
    366 2         9 return '',$in,'';
    367             }
    368              
    369             sub _do_links {
    370 11     11   14 my $self = shift;
    371              
    372 11         24 my (undef, $link, undef) = @_;
    373              
    374 11         23 $link =~s{\>}[>]g;
    375              
    376 11 50 33     45 $self->urls() unless $self->{_links} and $self->{_links}->{$link};
    377              
    378 11   50     39 my $lnk = $self->{_links}->{$link} || {};
    379              
    380 11         19 my ($t1,$t2) = ('','');
    381              
    382 11         23 $t1 = "
    383 11   100     42 my $class = $lnk->{class} || $lnk->{_class} || '';
    384 11 100       38 if ( $class ) {
    385 1         4 $t1.=" class='$class'";
    386             }
    387 11         20 $t1.='>';
    388              
    389 11         34 return $t1, $lnk->{title}, $t2;
    390             }
    391              
    392             sub _handle_toc {
    393 1     1   2 my ($self) = shift;
    394              
    395 1         3 my $toc = $self->{__toc};
    396 1         3 my $headers = $self->{__headers};
    397 1         3 my $headnames = $self->{__headnames};
    398              
    399 1         2 my $res = "\n";
    400 1         4 for my $head (@$toc) {
    401 6         15 $res.='*'x$headers->{$head};
    402            
    403 6         8 $res.=' ';
    404 6         14 $res.='[['.$headnames->{$head}.'|#'.$head."]]\n";
    405             }
    406 1         5 $res.="\n";
    407              
    408 1         5 my $wf = (ref $self)->new(body => $res);
    409 1         10 $res = $wf->format();
    410              
    411 1         5 $res = "
    $res
    ";
    412              
    413 1         15 return $res;
    414             }
    415              
    416             sub _handle_image {
    417 1     1   4 my ($self, $plugin, $params) = @_;
    418 1         2 my $src;
    419              
    420 1 50       5 if (ref $params eq 'ARRAY') {
    421 1         2 $src = shift @$params;
    422 1 50 33     5 if (@$params and ref $params->[0] eq 'HASH') {
    423 0         0 $params = $params->[0];
    424             } else {
    425 1         3 $params = { @$params };
    426             }
    427             } else {
    428 0         0 $src = delete $params->{src};
    429             }
    430              
    431 1 50       7 return '' unless $src;
    432              
    433 1 50 33     10 if ($src =~ m{\Ahttps?://} and $self->{image_filter}) {
        50          
    434 0         0 $src = $self->{image_filter}->($src, $params);
    435             } elsif ($self->{image_mapper}) {
    436 0         0 $src = $self->{image_mapper}->($src, $params);
    437             }
    438              
    439 1 50       4 return '' unless $src;
    440              
    441 1         6 my $res = "
    442 1 50       4 if ( $params->{size} ) {
    443 0         0 my ($w,$h) = $params->{size} =~ m{\A\d+x\d+\z};
    444              
    445 0 0 0     0 if ($w and $h) {
    446 0   0     0 $params->{width} ||= $w;
    447 0   0     0 $params->{height} ||= $h;
    448 0         0 delete $params->{size};
    449             }
    450             }
    451 1         15 for my $attr ( qw(alt title heigth width) ) {
    452 4 50       14 next unless $params->{ $attr };
    453 0         0 my $av = $params->{ $attr };
    454 0         0 $av =~ s{&}{&}g;
    455 0         0 $av =~ s{<}{>}g;
    456 0         0 $av =~ s{>}{<}g;
    457 0         0 $av =~ s{'}{'}g;
    458 0         0 $res.=" $attr='$av'";
    459             }
    460              
    461 1         3 $res.=' />';
    462              
    463             #MAYBETODO: support for caption, to allow to frame the images
    464             # and add a legend under the image.
    465              
    466 1         4 return $res;
    467             }
    468              
    469             sub _restore_code_block {
    470 1     1   3 my ($self, $plugin, $block) = @_;
    471              
    472 1         3 my $res = $self->{__codeblock}->{$block};
    473              
    474 1         6 return "$res";
    475             }
    476              
    477             sub _parse_plugin_params {
    478 2     2   4 my $paramstr = shift;
    479              
    480 2 100       6 return [] unless $paramstr;
    481              
    482 1 50       10 unless ($paramstr =~ m(\A\s*[\{\[]) ) {
    483 1         4 $paramstr = '['.$paramstr.']';
    484             }
    485              
    486 1 50       2 my $params = eval {
    487 1         9 from_json( $paramstr, { utf8 => 1 })
    488             } or do print STDERR "Error Parsing params: $paramstr ==> $@\n";
    489             #MAYBETODO: export this error somehow? silent it?
    490             # exporting it may be useful - specially while previewing
    491             # the result.
    492              
    493 1         57 return $params;
    494             }
    495              
    496             1;
    497             __END__