File Coverage

blib/lib/Text/YAWikiFormater.pm
Criterion Covered Total %
statement 203 259 78.3
branch 76 118 64.4
condition 18 54 33.3
subroutine 16 20 80.0
pod 7 7 100.0
total 320 458 69.8


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