File Coverage

blib/lib/Dotiac/DTL/Addon/html_template/Convert.pm
Criterion Covered Total %
statement 253 328 77.1
branch 137 216 63.4
condition 31 55 56.3
subroutine 12 18 66.6
pod 1 6 16.6
total 434 623 69.6


line stmt bran cond sub pod time code
1             ###############################################################################
2             #Convert.pm
3             #Last Change: 2009-01-21
4             #Copyright (c) 2009 Marc-Seabstian "Maluku" Lucksch
5             #Version 0.4
6             ####################
7             #This file is an addon to the Dotiac::DTL project.
8             #http://search.cpan.org/perldoc?Dotiac::DTL
9             #
10             #Convert.pm is published under the terms of the MIT license, which
11             #basically means "Do with it whatever you want". For more information, see the
12             #license.txt file that should be enclosed with libsofu distributions. A copy of
13             #the license is (at the time of writing) also available at
14             #http://www.opensource.org/licenses/mit-license.php .
15             ###############################################################################
16            
17             package Dotiac::DTL::Addon::html_template::Convert;
18 2     2   51084 use warnings;
  2         5  
  2         55  
19 2     2   9 use strict;
  2         4  
  2         62  
20             require Dotiac::DTL;
21            
22 2     2   11 use Carp;
  2         4  
  2         204  
23 2     2   10 use File::Spec;
  2         5  
  2         56  
24 2     2   11 use Scalar::Util qw/blessed reftype/;
  2         3  
  2         448  
25             require File::Basename;
26            
27             our $VERSION = 0.4;
28            
29             our $COMBINE=0;
30            
31             sub import {
32 2     2   22 my $class=shift;
33 2 50 33     18 if (@_ and (lc($_[0]) eq "combine" or lc($_[0]) eq ":combine")) {
      66        
34 1         1 $COMBINE=1;
35             }
36             {
37 2         3 require Dotiac::DTL::Template;
  2         11  
38 2     2   12 no warnings qw/redefine/;
  2         4  
  2         8921  
39 2         8 *HTML::Template::new=\&new;
40 2         4 *HTML::Template::new_file=\&new_file;
41 2         4 *HTML::Template::new_filehandle=\&new_filehandle;
42 2         4 *HTML::Template::new_array_ref=\&new_array_ref;
43 2         5 *HTML::Template::new_scalar_ref=\&new_scalar_ref;
44 2         6502 *Dotiac::DTL::Template::query=\&query;
45             }
46             }
47            
48             my %cache;
49            
50            
51             #package HTML::Template;
52            
53             #our $VERSION=2.9;
54            
55             sub query {
56 0     0 0 0 my $self=shift;
57 0         0 my @a=$self->param();
58 0 0 0     0 if (@_ and $_ eq "name") {
59 0         0 my $l=$_[0];
60 0 0       0 $l=$l->[-1] if ref $l;
61 0         0 $l=lc($l);
62 0 0       0 return "VAR" if grep { lc($_) eq $l } @a;
  0         0  
63             }
64 0 0 0     0 if (@_ and $_ eq "loop") {
65 0         0 my $l=$_[0];
66 0 0       0 $l=$l->[-1] if ref $l;
67 0         0 $l=lc($l);
68 0 0       0 return () if grep { lc($_) eq $l } @a;
  0         0  
69             }
70 0         0 return $self->param();
71             }
72            
73             sub _filter {
74 2     2   4 my $data=shift;
75 2         3 my $filter=shift;
76 2 100       10 $filter=[$filter] unless ref $filter eq 'ARRAY';
77 2         3 foreach my $f (@{$filter}) {
  2         4  
78 2 100 66     27 if (ref ($f) eq "HASH" and $f->{"sub"} and ref $f->{"sub"} eq "CODE") {
    50 66        
79 1 50 33     12 if ($f->{format} and not ref $f->{format} and lc($f->{format}) eq "array") {
      33        
80 0         0 my @data=split /\n/,$data;
81 0         0 $f->{'sub'}->(\@data);
82 0         0 $data=join("\n",@data);
83             }
84             else {
85 1         5 $f->{'sub'}->(\$data);
86             }
87             }
88             elsif (ref ($f) eq "CODE") {
89 1         4 $f->(\$data);
90             }
91             }
92 2         45 return $data;
93             }
94            
95             sub _associate {
96 0     0   0 my $template=shift;
97 0         0 my $a=shift;
98 0         0 my @a=();
99 0 0       0 if (Scalar::Util::blessed($a)) {
100 0         0 @a=($a)
101             }
102             else {
103 0 0       0 @a=@{$a} if ref $a eq "ARRAY";
  0         0  
104             }
105 0         0 foreach my $obj (@a) {
106 0 0       0 next unless Scalar::Util::blessed($obj);
107 0 0       0 next unless $obj->can("param");
108 0         0 my @params=$obj->param();
109 0         0 foreach my $p (@params) {
110 0         0 $template->param($p,$obj->param($p));
111             }
112             }
113             }
114            
115             our %include;
116            
117             my %escapeflags = (
118             url=>"u",
119             js=>"j"
120             );
121            
122             sub new_file {
123 0     0 0 0 my $class = shift;
124 0         0 return $class->new('filename', @_);
125             }
126             sub new_filehandle {
127 0     0 0 0 my $class = shift;
128 0         0 return $class->new('filehandle', @_);
129             }
130             sub new_array_ref {
131 0     0 0 0 my $class = shift;
132 0         0 return $class->new('arrayref', @_);
133             }
134             sub new_scalar_ref {
135 0     0 0 0 my $class = shift;
136 0         0 return $class->new('scalarref', @_);
137             }
138            
139             sub new {
140 46     46 1 1046588 %Dotiac::DTL::Addon::html_template::Convert::include=();
141 46         84 my $class=shift;
142 46         139 my %opts=@_;
143 46         60 my $flags="";
144 46 100       123 $flags.=($Dotiac::DTL::Addon::html_template::Convert::COMBINE?"+":"-");
145 46 100       107 $flags.=($opts{global_vars}?"g":"n");
146 46 100       108 $flags.=($opts{case_sensitive}?"s":"i");
147 46 100       84 $flags.=($opts{loop_context_vars}?"l":"c");
148 46 100 100     110 $flags.=($opts{default_escape}?($escapeflags{lc($opts{default_escape})}||"h"):"o");
149 46         56 my $r = eval {
150 46 100       109 if ($opts{filename}) {
151 10         62 my @compile=();
152 10 100       36 push @compile,$opts{compile} if exists ($opts{compile});
153 10         36 my $file=_find_file(\%opts);
154 10 50       26 croak "Can't find file: $opts{filename}" unless $file;
155 10         28 $Dotiac::DTL::Addon::html_template::Convert::include{$file}++;
156             #die $flags;
157 10 100       129 if (-e "$file$flags.html") {
158 5 50       146 if ((stat("$file$flags.html"))[9] >= (stat("$file"))[9]) {
159             #if (-M "$file$flags.html" < -M $file) {
160 5         51 my $template=Dotiac::DTL->new("$file$flags.html",@compile);
161 5 50       5188 Dotiac::DTL::Addon::html_template::Convert::_associate($template,$opts{associate}) if $opts{associate};
162 5         17 return $template;
163             }
164             }
165 5 50       291 open my $fh, "<",$file or croak "Can't open $file: $!";
166 5         5 my $data=do {local $/;<$fh>};
  5         19  
  5         131  
167 5         55 close $fh;
168 5 100       19 $data=Dotiac::DTL::Addon::html_template::Convert::_filter($data,$opts{filter}) if $opts{filter};
169 5         109 my @f = File::Basename::fileparse($file);
170 5         16 $data=Dotiac::DTL::Addon::html_template::Convert::_convert($data,\%opts,$f[1]);
171 5         5 my $template;
172 5 50       342 if (open my $fh,">","$file$flags.html") {
173 5         23 print $fh $data;
174 5         171 close $fh;
175 5         35 $template=Dotiac::DTL->new("$file$flags.html")
176             }
177             else {
178 0 0       0 if (@compile) {
179 0         0 carp "Can't compile template $file, even though it was requested. Can't create \"$file$flags.html\": $!";
180 0         0 delete $opts{compile};
181 0         0 @compile=();
182             }
183 0         0 $Dotiac::DTL::CURRENTDIR=$f[1]; # Works only with Dotiac::DTL >= 0.8
184 0         0 $template=Dotiac::DTL->new(\$data);
185             }
186 5 50       3619 Dotiac::DTL::Addon::html_template::Convert::_associate($template,$opts{associate}) if $opts{associate};
187 5         24 return $template;
188             }
189 36         46 my $data;
190 36 50       103 if ($opts{filehandle}) {
    50          
    0          
191 0         0 my $fh=$opts{filehandle};
192 0         0 $data=do {local $/;<$fh>};
  0         0  
  0         0  
193             }
194             elsif ($opts{scalarref}) {
195 36         39 $data=${$opts{scalarref}}; #Have to deref here for conversion
  36         68  
196             }
197             elsif ($opts{arrayref}) {
198 0         0 $data=join("",@{$opts{arrayref}});
  0         0  
199             }
200 36 100       92 $data=Dotiac::DTL::Addon::html_template::Convert::_filter($data,$opts{filter}) if $opts{filter};
201 36 100       117 if ($cache{$data.$flags}) {
202 1         4 $data=$cache{$data.$flags};
203             }
204             else {
205 35         47 my $odata=$data;
206 35         79 $data=Dotiac::DTL::Addon::html_template::Convert::_convert($data,\%opts);
207 35         125 $cache{$odata.$flags}=$data;
208             }
209 36         138 my $template=Dotiac::DTL->new(\$data);
210 36 50       204840 Dotiac::DTL::Addon::html_template::Convert::_associate($template,$opts{associate}) if $opts{associate};
211 36         75 return $template;
212             };
213 46 50       224 return $r if $r;
214 0         0 croak "Something went wrong while generating the template: $@";
215             }
216            
217            
218             my %filter=(
219             url=>"urlencode",
220             js=>"escapejs",
221             html=>"escape"
222             );
223            
224             sub _convert_tag {
225 109     109   189 my $start=shift;
226 109         191 $start="{% templatetag openbrace %}"x length($start);
227 109         155 my $end=shift;
228 109         190 my $tag=lc(shift(@_));
229 109         154 my $options=shift;
230 109         140 my @opts=();
231 109 100       582 @opts=split /\s*((?:(?:[Dd][Ee][Ff][Aa][Uu][Ll][Tt])|(?:[Ee][Ss][Cc][Aa][Pp][Ee])|(?:[Nn][Aa][Mm][Ee]))\s*=)\s*/,$options if $options;
232 109         164 my %opts=();
233             #die shift(@opts);
234             #Convert options, quick and dirty
235 109         260 while (defined(my $o=shift(@opts))) {
236             #if (my $o=0) {
237 172 100       353 next unless $o;
238 122 100       245 if (substr($o,-1,1) ne "=") {
239 32         32 push @{$opts{"name"}},$o;
  32         144  
240             }
241             else {
242 90         316 $o=~s/\s*=$//;
243 90         145 $o=lc $o;
244 90         90 push @{$opts{$o}},shift(@opts);
  90         375  
245             }
246            
247             }
248 109 100       222 if ($opts{default}) {
249 11         12 foreach my $value (@{$opts{default}}) {
  11         21  
250 11         17 my $f=substr $value,0,1;
251 11         14 my $e=substr $value,-1,1;
252 11 100 100     98 if ($f eq $e and $f eq '"' or $f eq "'") {
      100        
253 9         25 $value=~s/\\/\\\\/g;
254             }
255             else {
256 2         11 $value=~s/\s*$//g;
257 2         5 $value=~s/\\/\\\\/g;
258 2         3 $value=~s/\"/\\\"/g;
259 2         8 $value='"'.$value.'"';
260             }
261             }
262             }
263 109 100       218 if ($opts{name}) {
264 80         79 foreach my $value (@{$opts{name}}) {
  80         153  
265 80         306 $value=~s/["'\\\s}{]//g;
266             }
267             }
268 109 100       262 if ($opts{escape}) {
269 30         31 foreach my $value (@{$opts{escape}}) {
  30         49  
270 31         84 $value=~s/\W//g;
271 31         68 $value=~tr/A-Z/a-z/;
272             }
273             }
274             #use Data::Dumper;
275             #warn Data::Dumper->Dump([$tag,\%opts]);
276 109         154 my $cv=shift;
277 109         117 my $global=shift;
278 109         122 my $default=shift;
279 109         111 my $opts=shift;
280 109 100       281 if ($tag eq "var") {
    100          
    100          
    100          
    100          
    50          
281 57         60 my @filter;
282 57         59 my $d=$default;
283 57 100       184 if ($opts{escape}) {
284 30         31 foreach my $f (@{$opts{escape}}) {
  30         46  
285 31 50       64 next if $f eq "";
286 31         34 $d="";
287 31 100       58 next unless $f;
288 26 50       47 next if $f eq "off";
289 26 50       49 next if $f eq "none";
290 26 100 100     86 if ($f eq "js" or $f eq "url") {
291 15         43 push @filter,$filter{$f}
292             }
293             else {
294 11 100       38 push @filter,"escape" unless $default eq "html";
295 11 100       31 $d=$default if $default eq "html";
296             }
297             }
298             }
299 57 100       160 if ($d) {
    100          
300 8 50       26 push @filter,$filter{$d} if $filter{$d};
301             #push @filter,"safe" if $default eq "html";
302             }
303             elsif ($default eq "html") {
304 9         14 push @filter,"safe";
305             }
306 57 100       115 if ($opts{default}) {
307 11         17 my $def=shift @{$opts{default}};
  11         26  
308 11 50       31 push @filter,"default:$def" if $def;
309             }
310 57         59 my $name;
311 57 100       106 if ($opts{name}) {
312 55   66     118 $name=shift @{$opts{name}} while not $name and @{$opts{name}};
  55         212  
  55         181  
313             }
314 57 100       111 $name='""' unless $name;
315             #warn "{{ ".join("|",$name,@filter)." }}";
316 57         601 return "$start\{\{ ".join("|",$name,@filter)." }}";
317             }
318             elsif ($tag eq "else") {
319 4         32 return "$start\{\% else \%}";
320             }
321             elsif ($tag eq "if") {
322 29 100       123 return "$start\{\% endif \%}" if ($end);
323 14         17 my $name;
324 14 50       31 if ($opts{name}) {
325 14   66     42 $name=shift @{$opts{name}} while not $name and @{$opts{name}};
  14         51  
  14         49  
326             }
327 14 50       32 $name='""' unless $name;
328 14         169 return "$start\{\% if $name \%}";
329             }
330             elsif ($tag eq "unless") {
331 9 100       32 return "$start\{\% endif \%}" if ($end);
332 5         6 my $name;
333 5 50       14 if ($opts{name}) {
334 5   66     13 $name=shift @{$opts{name}} while not $name and @{$opts{name}};
  5         18  
  5         17  
335             }
336 5 50       11 $name='""' unless $name;
337 5         51 return "$start\{\% if not $name \%}";
338             }
339             elsif ($tag eq "loop") {
340 8 100       313 return "$start\{\% endimportloop \%}" if ($end);
341 4         4 my $name;
342 4 50       10 if ($opts{name}) {
343 4   66     11 $name=shift @{$opts{name}} while not $name and @{$opts{name}};
  4         14  
  4         15  
344             }
345 4 50       15 $name='""' unless $name;
346 4         43 return "$start\{\% importloop ${name}${cv}$global \%}";
347             }
348             elsif ($tag eq "include") {
349 2         4 my $name;
350 2 50       9 if ($opts{name}) {
351 2   66     14 $name=shift @{$opts{name}} while not $name and @{$opts{name}};
  2         14  
  2         9  
352             }
353 2 50       7 return "$start" unless $name;
354 2         5 my $me="";
355 2 100       7 $me = $opts->{filename} if $opts->{filename};
356 2         4 $opts->{filename}=$name;
357 2         5 my $file=_find_file($opts,@_);
358 2 50       8 unless ($file) {
359 0         0 carp "Can't find included file: $opts->{filename}";
360 0         0 return "$start";
361             }
362 2 50       7 if ($include{$file}) {
363 0         0 carp "Can't cyclic include $file, Include skipped";
364 0         0 return "$start";
365             }
366 2         7 $include{$file}++;
367 2         4 my $flags="";
368 2 50       7 $flags.=($COMBINE?"+":"-");
369 2 50       6 $flags.=($opts{global_vars}?"g":"n");
370 2 50       6 $flags.=($opts{case_sensitive}?"s":"i");
371 2 50       7 $flags.=($opts{loop_context_vars}?"l":"c");
372 2 50 0     7 $flags.=($opts{default_escape}?($escapeflags{lc($opts{default_escape})}||"h"):"o");
373 2         4 my $relfile=$file;
374 2 100       6 if ($me) {
375 1         47 my @mypath=File::Basename::fileparse(File::Spec->rel2abs($me));
376 1         264 $relfile=File::Spec->abs2rel(File::Spec->rel2abs($file),$mypath[1]);
377             }
378 2 50       35 if (-e "$file$flags.html") {
379 0 0       0 if ((stat("$file$flags.html"))[9] >= (stat("$file"))[9]) {
380 0         0 $include{$file}--;
381 0         0 return die "$start\{\% include \"$relfile$flags.html\" \%}";
382             }
383             }
384 2         24 my $pathsep=quotemeta(File::Spec->catdir('',''));
385 2         51 $relfile=~s/$pathsep/\//g; #Works almost everywhere, Dotiac takes care of that
386 2         5 $relfile=~s/\\/\\\\/g;
387 2 50       94 open my $fh, "<",$file or croak "Can't open $file: $!";
388 2         4 my $data=do {local $/;<$fh>};
  2         10  
  2         60  
389 2         24 close $fh;
390 2 50       9 $data=Dotiac::DTL::Addon::html_template::Replace::_filter($data,$opts{filter}) if $opts{filter};
391 2         41 my @f = File::Basename::fileparse($file);
392 2         10 $data=_convert($data,\%opts,$f[1],@_);
393 2 50       184 if (open my $fh,">","$file$flags.html") {
394 2         27 print $fh $data;
395 2         99 close $fh;
396 2         4 $include{$file}--;
397 2         25 return "$start\{\% include \"$relfile$flags.html\" \%}";
398             }
399             else {
400 0         0 carp "Can't write into $file$flags.html: $!, Include of $file skipped";
401 0         0 return "$start"
402             }
403            
404             }
405 0         0 return "$start";
406             }
407            
408             my %tag=reverse (
409             openblock=>"{%",
410             closeblock=>"%}",
411             openvariable=>"{{",
412             closevariable=>"}}",
413             openbrace=>"{",
414             closebrace=>"}",
415             opencomment=>"{#",
416             closecomment=>"#}"
417             );
418            
419             sub _convert {
420 42     42   89 my $data=shift;
421 42         85 my $ret="";
422 42         51 my %opts=%{shift(@_)};
  42         129  
423 42         61 my $global="";
424 42 100       99 $global=" merge" if $opts{global_vars};
425 42         52 my $cv="";
426 42 100       90 $cv=" contextvars" if $opts{loop_context_vars};
427 42         48 my $default="";
428 42 100       72 if ($opts{default_escape}) {
429 5         9 $default=lc($opts{default_escape});
430             }
431 42 100       78 if ($opts{case_sensitive}) {
432 8         13 $ret="{% load importloop %}";
433             }
434             else {
435 34         48 $ret="{% load case-insensitive importloop %}";
436             }
437 42 100       108 $ret.="{% autoescape off %}" unless $default eq "html";
438 42 100       187 $data=~s/((?:\{\{)|(?:\}\})|(?:\%\})|(?:\{\%)|(?:\{#)|(?:#\})|(?:\{)|(?:\}))/{% templatetag $tag{$1} %}/g unless $Dotiac::DTL::Addon::html_template::Convert::COMBINE;
439 42         474 $data=~s/(\{*)
440             <(?:!--\s*)?
441             ([\/]?)\s*
442             [Tt][Mm][Pp][Ll]_((?:[Vv][Aa][Rr])|(?:[Ii][Ff])|(?:[Ee][Ll][Ss][Ee])|(?:[Uu][Nn][Ll][Ee][Ss][Ss])|(?:[Ll][Oo][Oo][Pp])|(?:[Ii][Nn][Cc][Ll][Uu][Dd][Ee]))
443             \s*(
444             (?:
445             (?:
446             (?:(?:[Dd][Ee][Ff][Aa][Uu][Ll][Tt])|(?:[Ee][Ss][Cc][Aa][Pp][Ee])|(?:[Nn][Aa][Mm][Ee]))
447             \s*=\s*
448             )?
449             (?!-->)(?:(?:"[^">]*")|(?:'[^'>]*')|(?:[^\s=>]*))\s*
450             )*
451             )
452             (?:--)?>
453 109         448 /_convert_tag($1,$2,$3,$4,$cv,$global,$default,{%opts},@_)/xeg;
454             #carp $ret.$data." ";
455 42 100       286 return $ret.$data."{% endautoescape %}" unless $default eq "html";
456 3         12 return $ret.$data;
457             }
458            
459             sub _find_file { #like HTML::Template
460 12     12   21 my $o=shift;
461 12         27 my $file=$o->{filename};
462 12 50 33     133 return File::Spec->canonpath($file) if (File::Spec->file_name_is_absolute($file) and (-e $file));
463 12         36 foreach my $p (@_) {
464 1         21 my $path = File::Spec->catfile($p, $file);
465 1 50       25 return File::Spec->canonpath($path) if -e $path;
466             }
467 11 50       42 if (defined($ENV{HTML_TEMPLATE_ROOT})) {
468 0         0 my $path = File::Spec->catfile($ENV{HTML_TEMPLATE_ROOT}, $file);
469 0 0       0 return File::Spec->canonpath($path) if -e $path;
470             }
471 11 50       31 if ($o->{path}) {
472 0         0 foreach my $path (@{$o->{path}}) {
  0         0  
473 0         0 $path = File::Spec->catfile($path, $file);
474 0 0       0 return File::Spec->canonpath($path) if -e $path;
475             }
476             }
477 11 50       267 return File::Spec->canonpath($file) if -e $file;
478 0 0         if ($o->{path}) {
479 0 0         if (defined($ENV{HTML_TEMPLATE_ROOT})) {
480 0           foreach my $path (@{$o->{path}}) {
  0            
481 0           $path = File::Spec->catfile($ENV{HTML_TEMPLATE_ROOT},$path, $file);
482 0 0         return File::Spec->canonpath($path) if -e $path;
483             }
484             }
485             }
486 0           return undef;
487             }
488             1;
489            
490             __END__