File Coverage

blib/lib/Text/Haml.pm
Criterion Covered Total %
statement 750 772 97.1
branch 305 360 84.7
condition 98 124 79.0
subroutine 98 98 100.0
pod 27 29 93.1
total 1278 1383 92.4


line stmt bran cond sub pod time code
1             package Text::Haml;
2              
3 30     30   338457 use strict;
  30         67  
  30         1245  
4 30     30   155 use warnings;
  30         53  
  30         1951  
5              
6 30     30   26448 use IO::File;
  30         375620  
  30         3964  
7 30     30   537 use Scalar::Util qw/weaken/;
  30         64  
  30         3272  
8 30     30   35615 use Encode qw/decode/;
  30         338654  
  30         2411  
9 30     30   236 use Carp ();
  30         63  
  30         626  
10 30     30   165 use File::Spec;
  30         69  
  30         727  
11 30     30   161 use File::Basename ();
  30         75  
  30         1033  
12 29     29   21009 use URI::Escape ();
  29         35588  
  29         783  
13 29     29   491 use Digest::MD5;
  29         55  
  29         2802  
14              
15             our $VERSION = '0.990116';
16              
17 29     29   155 use constant CHUNK_SIZE => 4096;
  29         46  
  29         4616  
18              
19             BEGIN {
20 29     29   84 my $cache_dir = '.text_haml_cache';
21 29         3272 for my $dir ($ENV{HOME}, File::Spec->tmpdir) {
22 29 50 33     1127 if (defined($dir) && -d $dir && -w _) {
      33        
23 29         234 $cache_dir = File::Spec->catdir($dir, '.text_haml_cache');
24 29         65 last;
25             }
26             }
27 29         249853 *_DEFAULT_CACHE_DIR = sub () { $cache_dir };
  7         33  
28             }
29              
30             my $ESCAPE = {
31             '\"' => "\x22",
32             "\'" => "\x27",
33             '\\' => "\x5c",
34             '\/' => "\x2f",
35             '\b' => "\x8",
36             '\f' => "\xC",
37             '\n' => "\xA",
38             '\r' => "\xD",
39             '\t' => "\x9",
40             '\\\\' => "\x5c\x5c"
41             };
42              
43             my $UNESCAPE_RE = qr/
44             \\[\"\'\/\\bfnrt]
45             /x;
46              
47             my $STRING_DOUBLE_QUOTES_RE = qr/
48             \"
49             (?:
50             $UNESCAPE_RE
51             |
52             [\x20-\x21\x23-\x5b\x5b-\x{10ffff}]
53             )*
54             \"
55             /x;
56              
57             my $STRING_SINGLE_QUOTES_RE = qr/
58             \'
59             (?:
60             $UNESCAPE_RE
61             |
62             [\x20-\x26\x28-\x5b\x5b-\x{10ffff}]
63             )*
64             \'
65             /x;
66              
67             my $STRING_RE = qr/
68             $STRING_SINGLE_QUOTES_RE
69             |
70             $STRING_DOUBLE_QUOTES_RE
71             /x;
72              
73             sub new {
74 29     29 1 17976 my $class = shift;
75              
76             # Default attributes
77 29         199 my $attrs = {};
78 29         192 $attrs->{vars_as_subs} = 0;
79 29         114 $attrs->{tape} = [];
80 29         195 $attrs->{encoding} = 'utf-8';
81 29         82 $attrs->{escape_html} = 1;
82 29         84 $attrs->{helpers} = {};
83 29         512 $attrs->{format} = 'xhtml';
84 28         92 $attrs->{prepend} = '';
85 28         262 $attrs->{append} = '';
86 28         1269 $attrs->{namespace} = '';
87 28         735 $attrs->{path} = ['.'];
88 28         78 $attrs->{cache} = 1; # 0: not cached, 1: checks mtime, 2: always cached
89 28         2889 $attrs->{cache_dir} = _DEFAULT_CACHE_DIR;
90              
91 28         78 $attrs->{escape} = <<'EOF';
92             my $s = shift;
93             return unless defined $s;
94             $s =~ s/&/&/g;
95             $s =~ s/
96             $s =~ s/>/>/g;
97             $s =~ s/"/"/g;
98             $s =~ s/'/'/g;
99             return $s;
100             EOF
101              
102             $attrs->{filters} = {
103 4     5   93 plain => sub { $_[0] =~ s/\n*$//; $_[0] },
  4         21  
104 2     3   3 escaped => sub { $_[0] },
105 3     4   98 preserve => sub { $_[0] =~ s/\n/ /g; $_[0] },
  3         13  
106             javascript => sub {
107 6     6   29 "";
112             },
113             css => sub {
114 3     3   62 "";
119             },
120 28         558 };
121              
122 28         306 my $self = {%$attrs, @_};
123 28         111 bless $self, $class;
124              
125             # Convert to template fullpath
126 28 100       1410 $self->path([
127 26         78 map { ref($_) ? $_ : File::Spec->rel2abs($_) }
128 28 100       223 ref($self->path) eq 'ARRAY' ? @{$self->path} : $self->path
129             ]);
130              
131 28   33     286 $self->{helpers_arg} ||= $self;
132 28         201 weaken $self->{helpers_arg};
133              
134 28         179 return $self;
135             }
136              
137             # Yes, i know!
138 21 50   21 1 559 sub vars_as_subs { @_ > 1 ? $_[0]->{vars_as_subs} = $_[1] : $_[0]->{vars_as_subs}; }
139 46 100   46 1 4329 sub format { @_ > 1 ? $_[0]->{format} = $_[1] : $_[0]->{format} }
140 16 50   16 1 94 sub encoding { @_ > 1 ? $_[0]->{encoding} = $_[1] : $_[0]->{encoding} }
141 525 100   525 1 7222 sub escape_html { @_ > 1 ? $_[0]->{escape_html} = $_[1] : $_[0]->{escape_html}; }
142 242 100   242 1 1044 sub code { @_ > 1 ? $_[0]->{code} = $_[1] : $_[0]->{code} }
143 238 100   238 1 965 sub compiled { @_ > 1 ? $_[0]->{compiled} = $_[1] : $_[0]->{compiled} }
144 125 50   125 1 1198 sub helpers { @_ > 1 ? $_[0]->{helpers} = $_[1] : $_[0]->{helpers} }
145 13 50   13 1 66 sub filters { @_ > 1 ? $_[0]->{filters} = $_[1] : $_[0]->{filters} }
146 120 50   120 1 469 sub prepend { @_ > 1 ? $_[0]->{prepend} = $_[1] : $_[0]->{prepend} }
147 120 50   120 1 484 sub append { @_ > 1 ? $_[0]->{append} = $_[1] : $_[0]->{append} }
148 120 50   120 1 538 sub escape { @_ > 1 ? $_[0]->{escape} = $_[1] : $_[0]->{escape} }
149 1725 100   1725 1 6022 sub tape { @_ > 1 ? $_[0]->{tape} = $_[1] : $_[0]->{tape} }
150 87 100   87 1 656 sub path { @_ > 1 ? $_[0]->{path} = $_[1] : $_[0]->{path} }
151 20 50   20 1 123 sub cache { @_ > 1 ? $_[0]->{cache} = $_[1] : $_[0]->{cache} }
152 45 100   45 0 522 sub fullpath { @_ > 1 ? $_[0]->{fullpath} = $_[1] : $_[0]->{fullpath}; }
153 8 50   8 1 116 sub cache_dir { @_ > 1 ? $_[0]->{cache_dir} = $_[1] : $_[0]->{cache_dir}; }
154 21 100   21 0 919 sub cache_path { @_ > 1 ? $_[0]->{cache_path} = $_[1] : $_[0]->{cache_path}; }
155 120 50   120 1 911 sub namespace { @_ > 1 ? $_[0]->{namespace} = $_[1] : $_[0]->{namespace}; }
156 9 100   9 1 2490 sub error { @_ > 1 ? $_[0]->{error} = $_[1] : $_[0]->{error} }
157              
158             sub helpers_arg {
159 4 50   4 1 18 if (@_ > 1) {
160 2         3 $_[0]->{helpers_arg} = $_[1];
161 2         61 weaken $_[0]->{helpers_arg};
162             }
163             else {
164 4         17 return $_[0]->{helpers_arg};
165             }
166             }
167              
168              
169             our @AUTOCLOSE = (qw/meta img link br hr input area param col base/);
170              
171             sub add_helper {
172 3     3 1 16 my $self = shift;
173 3         313 my ($name, $code) = @_;
174              
175 3         19 $self->helpers->{$name} = $code;
176             }
177              
178             sub add_filter {
179 3     3 1 441 my $self = shift;
180 3         99 my ($name, $code) = @_;
181              
182 3         14 $self->filters->{$name} = $code;
183             }
184              
185             sub parse {
186 139     137 1 243 my $self = shift;
187 139         555 my $tmpl = shift;
188              
189 139 100       408 $tmpl = '' unless defined $tmpl;
190              
191 139         514 $self->tape([]);
192              
193 139         902 my $level_token = quotemeta ' ';
194 139         277 my $escape_token = quotemeta '&';
195 139         260 my $unescape_token = quotemeta '!';
196 139         288 my $expr_token = quotemeta '=';
197 139         208 my $tag_start = quotemeta '%';
198 139         211 my $class_start = quotemeta '.';
199 139         237 my $id_start = quotemeta '#';
200              
201 139         202 my $attributes_start = quotemeta '{';
202 139         172 my $attributes_end = quotemeta '}';
203 139         235 my $attribute_arrow = quotemeta '=>';
204 139         171 my $attributes_sep = quotemeta ',';
205 139         189 my $attribute_prefix = quotemeta ':';
206 139         3281 my $attribute_name = qr/(?:$STRING_RE|.*?(?= |$attribute_arrow))/;
207 139         2408 my $attribute_value =
208             qr/(?:$STRING_RE|[^ $attributes_sep$attributes_end]+)/x;
209              
210 139         289 my $attributes_start2 = quotemeta '(';
211 139         416 my $attributes_end2 = quotemeta ')';
212 137         191 my $attribute_arrow2 = quotemeta '=';
213 137         200 my $attributes_sep2 = ' ';
214 137         2306 my $attribute_name2 = qr/(?:$STRING_RE|.*?(?= |$attribute_arrow2))/;
215 137         2143 my $attribute_value2 =
216             qr/(?:$STRING_RE|[^ $attributes_sep2$attributes_end2]+)/;
217              
218 137         275 my $filter_token = quotemeta ':';
219 137         197 my $quote = "'";
220 137         235 my $comment_token = quotemeta '-#';
221 137         182 my $trim_in = quotemeta '<';
222 137         176 my $trim_out = quotemeta '>';
223 137         172 my $autoclose_token = quotemeta '/';
224 137         172 my $multiline_token = quotemeta '|';
225              
226 137         2672 my $tag_name = qr/([^
227             $level_token
228             $attributes_start
229             $attributes_start2
230             $class_start
231             $id_start
232             $trim_in
233             $trim_out
234             $unescape_token
235             $escape_token
236             $expr_token
237             $autoclose_token]+)/;
238              
239 137         351 my $tape = $self->tape;
240              
241 137         221 my $level;
242             my @multiline_el_queue;
243 137         1110 my @lines = split /\n/, $tmpl;
244 137 100       808 push @lines, '' if $tmpl =~ m/\n$/;
245 137 100       381 @lines = ('') if $tmpl eq "\n";
246 137         431 for (my $i = 0; $i < @lines; $i++) {
247 633         1043 my $line = $lines[$i];
248              
249 633 100       2867 if ($line =~ s/^($level_token+)//) {
250 271         571 $level = length $1;
251             }
252             else {
253 362         504 $level = 0;
254             }
255              
256 633         2692 my $el = {level => $level, type => 'text', line => $line, lineno => $i+1};
257              
258             # Haml comment
259 633 100       2490 if ($line =~ m/^$comment_token(?: (.*))?/) {
260 10         89 $el->{type} = 'comment';
261 10 100       40 $el->{text} = $1 if $1;
262 10         18 push @$tape, $el;
263 10         62 next;
264             }
265              
266             # Inside a filter
267 623         997 my $prev = $tape->[-1];
268 623 100 100     2633 if ($prev && $prev->{type} eq 'filter') {
269 34 100 100     158 if ($prev->{level} < $el->{level}
      66        
270             || ($i + 1 < @lines && $line eq ''))
271             {
272 22 100       131 $prev->{text} .= "\n" if $prev->{text};
273 22         46 $prev->{text} .= $line;
274 22         79 $prev->{line} .= "\n" . (' ' x $el->{level}) . $el->{line};
275 22         52 _update_lineno($prev, $i);
276 22         96 next;
277             }
278             }
279              
280             # Filter
281 601 100       1274 if ($line =~ m/^:(\w+)/) {
282 12         27 $el->{type} = 'filter';
283 12         42 $el->{name} = $1;
284 12         30 $el->{text} = '';
285 12         21 push @$tape, $el;
286 12         51 next;
287             }
288              
289             # Doctype
290 589 100       1192 if ($line =~ m/^!!!(?: ([^ ]+)(?: (.*))?)?$/) {
291 16         29 $el->{type} = 'text';
292 16         25 $el->{escape} = 0;
293 16         63 $el->{text} = $self->_doctype($1, $2);
294 16         37 push @$tape, $el;
295 16         57 next;
296             }
297              
298             # HTML comment
299 573 100       1169 if ($line =~ m/^\/(?:\[if (.*)?\])?(?: *(.*))?/) {
300 8         17 $el->{type} = 'html_comment';
301 8 100       26 $el->{if} = $1 if $1;
302 8 100       31 $el->{text} = $2 if $2;
303 8         12 push @$tape, $el;
304 8         30 next;
305             }
306              
307             # Escaping, everything after is a text
308 565 100       1223 if ($line =~ s/^\\//) {
309 2         8 $el->{type} = 'text', $el->{text} = $line;
310 2         6 push @$tape, $el;
311 2         8 next;
312             }
313              
314             # Block
315 563 100       1188 if ($line =~ s/^- \s*(.*)//) {
316 36         66 $el->{type} = 'block';
317 36         107 $el->{text} = $1;
318 36         64 push @$tape, $el;
319 36         130 next;
320             }
321              
322             # Preserve whitespace
323 527 100       1124 if ($line =~ s/^~ \s*(.*)//) {
324 1         4 $el->{type} = 'text';
325 1         5 $el->{text} = $1;
326 1         4 $el->{expr} = 1;
327 1         2 $el->{preserve_whitespace} = 1;
328 1         3 push @$tape, $el;
329 1         4 next;
330             }
331              
332             # Tag
333 526 100       4140 if ($line =~ m/^(?:$tag_start
334             |$class_start
335             |$id_start
336             |$attributes_start[^$attributes_start]
337             |$attributes_start2
338             )/x
339             )
340             {
341 232         418 $el->{type} = 'tag';
342 232         411 $el->{name} = '';
343              
344 232 100       3196 if ($line =~ s/^$tag_start$tag_name//) {
345 210         605 $el->{name} = $1;
346             }
347              
348 232         328 while (1) {
349 276 100       4493 if ($line =~ s/^$class_start$tag_name//) {
    100          
350 31         108 my $class = join(' ', split(/\./, $1));
351              
352 31   100     132 $el->{name} ||= 'div';
353 31   100     149 $el->{class} ||= [];
354 31         39 push @{$el->{class}}, $class;
  31         100  
355             }
356             elsif ($line =~ s/^$id_start$tag_name//) {
357 13         32 my $id = $1;
358              
359 13   100     54 $el->{name} ||= 'div';
360 13         46 $el->{id} = $id;
361             }
362             else {
363 232         486 last;
364             }
365             }
366              
367 232 100       7160 if ($line =~ m/^
368             (?:
369             $attributes_start\s*
370             $attribute_prefix?
371             $attribute_name\s*
372             $attribute_arrow\s*
373             $attribute_value
374             |
375             $attributes_start2\s*
376             $attribute_name2\s*
377             $attribute_arrow2\s*
378             $attribute_value2
379             )
380             /x
381             )
382             {
383 40         78 my $attrs = [];
384              
385 40         65 my $type = 'html';
386 40 100       280 if ($line =~ s/^$attributes_start//) {
387 30         64 $type = 'perl';
388             }
389             else {
390 10         78 $line =~ s/^$attributes_start2//;
391             }
392              
393 40         59 while (1) {
394 104 100 100     942 if (!$line) {
    100 100        
    100          
395 3   50     14 $line = $lines[++$i] || last;
396 3         9 $el->{line} .= "\n$line";
397 3         14 _update_lineno($el, $i);
398             }
399             elsif ($type eq 'perl' && $line =~ s/^$attributes_end//) {
400 30         60 last;
401             }
402             elsif ($type eq 'html' && $line =~ s/^$attributes_end2//)
403             {
404 10         20 last;
405             }
406             else {
407 61         79 my ($name, $value);
408              
409 61 100       2824 if ($line =~ s/^\s*$attribute_prefix?
    50          
410             ($attribute_name)\s*
411             $attribute_arrow\s*
412             ($attribute_value)\s*
413             (?:$attributes_sep\s*)?//x
414             )
415             {
416 44         90 $name = $1;
417 44         95 $value = $2;
418             }
419             elsif (
420             $line =~ s/^\s*
421             ($attribute_name2)\s*
422             $attribute_arrow2\s*
423             ($attribute_value2)\s*
424             (?:$attributes_sep2\s*)?//x
425             )
426             {
427 17         40 $name = $1;
428 17         50 $value = $2;
429             }
430             else {
431 0         0 $self->error('Tag attributes parsing error');
432 0         0 return;
433             }
434              
435 61 100       257 if ($name =~ s/^(?:'|")//) {
436 3         10 $name =~ s/(?:'|")$//;
437 3         41 $name =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
438             }
439              
440 61 100 100     439 if ($value =~ s/^(?:'|")//) {
    100          
441 47         155 $value =~ s/(?:'|")$//;
442 47         332 $value =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
443 47         264 push @$attrs,
444             $name => {type => 'text', text => $value};
445             }
446             elsif ($value eq 'true' || $value eq 'false') {
447 7 100       42 push @$attrs, $name => {
448             type => 'boolean',
449             text => $value eq 'true' ? 1 : 0
450             };
451             }
452             else {
453 7         40 push @$attrs,
454             $name => {type => 'expr', text => $value};
455             }
456             }
457             }
458              
459 40         82 $el->{type} = 'tag';
460 40 50       191 $el->{attrs} = $attrs if @$attrs;
461             }
462              
463 232 100       1110 if ($line =~ s/^$trim_out ?//) {
464 1         3 $el->{trim_out} = 1;
465             }
466              
467 232 100       1115 if ($line =~ s/^$trim_in ?//) {
468 1         5 $el->{trim_in} = 1;
469             }
470             }
471              
472 526 100       2721 if ($line =~ s/^($escape_token|$unescape_token)?$expr_token //) {
473 55         136 $el->{expr} = 1;
474 55 100       188 if ($1) {
475 8 100       48 $el->{escape} = quotemeta($1) eq $escape_token ? 1 : 0;
476             }
477             }
478              
479 526 100 100     2269 if ($el->{type} eq 'tag'
      66        
480             && ($line =~ s/$autoclose_token$//
481             || grep { $el->{name} eq $_ } @AUTOCLOSE)
482             )
483             {
484 14         31 $el->{autoclose} = 1;
485             }
486              
487 526 100       1296 $line =~ s/^ // if $line;
488              
489             # Multiline
490 526 100 100     2465 if ($line && $line =~ s/(\s*)$multiline_token$//) {
491              
492             # For the first time
493 20 100 100     120 if (!$tape->[-1] || ref $tape->[-1]->{text} ne 'ARRAY') {
494 6         21 $el->{text} = [$line];
495 6   33     19 $el->{line} ||= $line . "$1|"; # XXX: is this really necessary?
496              
497 6         11 push @$tape, $el;
498 6         39 push @multiline_el_queue, $el;
499             }
500              
501             # Continue concatenation
502             else {
503 14         26 my $prev_stack_el = $tape->[-1];
504 14         17 push @{$prev_stack_el->{text}}, $line;
  14         32  
505 14         55 $prev_stack_el->{line} .= "\n" . $line . "$1|";
506 14         45 _update_lineno($prev_stack_el, $i);
507             }
508             }
509              
510             # Normal text
511             else {
512 506 100       1277 $el->{text} = $line if $line;
513              
514 506         2123 push @$tape, $el;
515             }
516             }
517              
518             # Finalize multilines
519 137         1047 for my $el (@multiline_el_queue) {
520 6         9 $el->{text} = join(" ", @{$el->{text}});
  6         74  
521             }
522             }
523              
524             # Updates lineno entry on the tape element
525             # for itens spanning more than one line
526             sub _update_lineno {
527 39     39   67 my ($el, $lineno) = @_;
528 39         47 $lineno++; # report line numbers starting at 1 instead of 0
529 39         284 $el->{lineno} =~ s/^(\d+)(?:-\d+)?/$1-$lineno/;
530 39         131 return;
531             }
532              
533             sub build {
534 118     118 1 194 my $self = shift;
535 118         273 my %vars = @_;
536              
537 118         153 my $code;
538              
539 118         409 my $ESCAPE = $self->escape;
540 118         345 $ESCAPE = <<"EOF";
541             no strict 'refs'; no warnings 'redefine';
542             sub escape;
543             *escape = sub {
544             $ESCAPE
545             };
546             use strict; use warnings;
547             EOF
548              
549 118         1039 $ESCAPE =~ s/\n//g;
550              
551 118   33     378 my $namespace = $self->namespace || ref($self) . '::template';
552 118         286 $code .= qq/package $namespace;/;
553              
554 118         316 $code .= qq/sub { my \$_H = ''; $ESCAPE;/;
555              
556 118         202 $code .= qq/my \$self = shift;/;
557              
558 118         157 $code .= qq/my \%____vars = \@_;/;
559              
560 118         171 $code .= qq/no strict 'refs'; no warnings 'redefine';/;
561              
562             # Install helpers
563 118         153 for my $name (sort keys %{$self->helpers}) {
  118         329  
564 2 50       9 next unless $name =~ m/^\w+$/;
565              
566 2         4 $code .= "sub $name;";
567 2         5 $code .= " *$name = sub { \$self";
568 2         5 $code .= "->helpers->{'$name'}->(\$self->helpers_arg, \@_) };";
569             }
570              
571             # Install variables
572 118         372 foreach my $var (sort keys %vars) {
573 20 100       108 next unless $var =~ m/^\w+$/;
574 19 100       60 if ($self->vars_as_subs) {
575 2 50       4 next if $self->helpers->{$var};
576 2         9 $code
577             .= qq/sub $var() : lvalue; *$var = sub () : lvalue {\$____vars{'$var'}};/;
578             }
579             else {
580 17         83 $code .= qq/my \$$var = \$____vars{'$var'};/;
581             }
582             }
583              
584 118         260 $code .= qq/use strict; use warnings;/;
585              
586 118         426 $code .= $self->prepend;
587              
588 118         245 my $stack = [];
589              
590 118         240 my $output = '';
591 118         139 my @lines;
592 118         187 my $count = 0;
593 118         155 my $in_block = 0;
594 118         275 ELEM:
595 118         213 for my $el (@{$self->tape}) {
596 538         829 my $level = $el->{level};
597 538 100       1112 $level -= 2 * $in_block if $in_block;
598              
599 538         659 my $offset = '';
600 538         816 $offset .= ' ' x $level;
601              
602 538         615 my $escape = '';
603 538 100 100     6752 if ( (!exists $el->{escape} && $self->escape_html)
      100        
      66        
604             || (exists $el->{escape} && $el->{escape} == 1))
605             {
606 492         745 $escape = 'escape';
607             }
608              
609 538         1124 my $prev_el = $self->tape->[$count - 1];
610 538         1188 my $next_el = $self->tape->[$count + 1];
611              
612 538         933 my $prev_stack_el = $stack->[-1];
613              
614 538 100 100     2073 if ($prev_stack_el && $prev_stack_el->{type} eq 'comment') {
615 25 100 100     115 if ( $el->{line}
616             && $prev_stack_el->{level} >= $el->{level})
617             {
618 8         15 pop @$stack;
619 8         17 undef $prev_stack_el;
620             }
621             else {
622 17         38 next ELEM;
623             }
624             }
625              
626 521 100 100     2729 if ( $el->{line}
      100        
627             && $prev_stack_el
628             && $prev_stack_el->{level} >= $el->{level})
629             {
630             STACKEDBLK:
631 64         181 while (my $poped = pop @$stack) {
632 80         126 my $level = $poped->{level};
633 80 100       158 $level -= 2 * $in_block if $in_block;
634 80         136 my $poped_offset = ' ' x $level;
635              
636 80         99 my $ending = '';
637 80 100       216 if ($poped->{type} eq 'tag') {
    50          
638 46         104 $ending .= "{name}>";
639             }
640             elsif ($poped->{type} eq 'html_comment') {
641 0 0       0 $ending .= "{if};
642 0         0 $ending .= "-->";
643             }
644              
645 80 100       212 if ($poped->{type} ne 'block') {
646 46         128 push @lines, qq|\$_H .= "$poped_offset$ending\n";|;
647             }
648              
649 80 100       312 last STACKEDBLK if $poped->{level} == $el->{level};
650             }
651             }
652              
653              
654             SWITCH: {
655              
656 521 100       622 if ($el->{type} eq 'tag') {
  521         1157  
657 208 100 100     642 my $ending =
658             $el->{autoclose} && $self->format eq 'xhtml' ? ' /' : '';
659              
660 208         268 my $attrs = '';
661 208 100       433 if ($el->{attrs}) {
662 65         216 ATTR:
663 27         45 for (my $i = 0; $i < @{$el->{attrs}}; $i += 2) {
664 38         81 my $name = $el->{attrs}->[$i];
665 38         73 my $value = $el->{attrs}->[$i + 1];
666 38         70 my $text = $value->{text};
667              
668 38 100       128 if ($name eq 'class') {
    100          
669 6   100     32 $el->{class} ||= [];
670 6 100       27 if ($value->{type} eq 'text') {
671 4         7 push @{$el->{class}}, $self->_parse_text($text);
  4         16  
672             }
673             else {
674 2         4 push @{$el->{class}}, qq/" . $text . "/;
  2         8  
675             }
676 6         18 next ATTR;
677             }
678             elsif ($name eq 'id') {
679 3   100     17 $el->{id} ||= '';
680 3 100       13 $el->{id} = $el->{id} . '_' if $el->{id};
681 3         10 $el->{id} .= $self->_parse_text($value->{text});
682 3         10 next ATTR;
683             }
684              
685 29 100 100     149 if ( $value->{type} eq 'text'
    100 66        
686             || $value->{type} eq 'expr')
687             {
688 26         49 $attrs .= ' ';
689 26         41 $attrs .= $name;
690 26         36 $attrs .= '=';
691              
692 26 100       90 if ($value->{type} eq 'text') {
693 25         75 $attrs
694             .= "'" . $self->_parse_text($text) . "'";
695             }
696             else {
697 1         5 $attrs .= qq/'" . $text . "'/;
698             }
699             }
700             elsif ($value->{type} eq 'boolean' && $value->{text})
701             {
702 2         4 $attrs .= ' ';
703 2         3 $attrs .= $name;
704 2 100       5 if ($self->format eq 'xhtml') {
705 1         2 $attrs .= '=';
706 1         4 $attrs .= qq/'$name'/;
707             }
708             }
709             } #end:for ATTR
710             }
711              
712 208         298 my $tail = '';
713 208 100       506 if ($el->{class}) {
714 28         48 $tail .= qq/ class='"./;
715 28         38 $tail .= qq/join(' ', sort(/;
716 28         46 $tail .= join(',', map {"\"$_\""} @{$el->{class}});
  35         106  
  28         59  
717 28         48 $tail .= qq/))/;
718 28         43 $tail .= qq/."'/;
719             }
720              
721 208 100       451 if ($el->{id}) {
722 14         39 $tail .= qq/ id='$el->{id}'/;
723             }
724              
725 208         584 $output .= qq|"$offset<$el->{name}$tail$attrs$ending>"|;
726              
727 208 100 100     1864 if ($el->{text} && $el->{expr}) {
    100 66        
    100 33        
    50          
728 17 100       59 if ($escape eq 'escape') {
729 13         48 $output .= '. ( do { my $ret = ' . qq/ $escape( do { $el->{text} } )/ . '; defined($ret) ? $ret : "" } )';
730 13         34 $output .= qq| . "{name}>"|;
731             } else {
732 4         12 $output .= '. ( do {' . $el->{text} . '} || "")';
733 4         11 $output .= qq| . "{name}>"|;
734             }
735             }
736             elsif ($el->{text}) {
737 56         243 $output .= qq/. $escape(/ . '"'
738             . $self->_parse_text($el->{text}) . '");';
739 56 50       251 $output .= qq|\$_H .= "{name}>"|
740             unless $el->{autoclose};
741             }
742             elsif (
743             !$next_el
744             || ( $next_el
745             && $next_el->{level} <= $el->{level})
746             )
747             {
748 26 100       89 $output .= qq|. "{name}>"| unless $el->{autoclose};
749             }
750             elsif (!$el->{autoclose}) {
751 109         178 push @$stack, $el;
752             }
753              
754 208         286 $output .= qq|. "\n"|;
755 208         237 $output .= qq|;|;
756 208         496 last SWITCH;
757             }
758              
759 313 100 100     1374 if ($el->{line} && $el->{type} eq 'text') {
760 139         268 $output = qq/"$offset"/;
761              
762 139 50       335 $el->{text} = '' unless defined $el->{text};
763              
764 139 100       395 if ($el->{expr}) {
    50          
765 31         149 $output .= '. ( do { my $ret = ' . qq/ $escape( do { $el->{text} } )/ . '; defined($ret) ? $ret : "" } )';
766 31         112 $output .= qq/;\$_H .= "\n"/;
767             }
768             elsif ($el->{text}) {
769 108         367 $output
770             .= '.'
771             . qq/$escape / . '"'
772             . $self->_parse_text($el->{text}) . '"';
773 108         169 $output .= qq/. "\n"/;
774             }
775              
776 139         174 $output .= qq/;/;
777 139         292 last SWITCH;
778             }
779              
780 174 100       461 if ($el->{type} eq 'block') {
781 35         73 push @lines, $el->{text};
782 35         53 push @$stack, $el;
783              
784 35 100 66     198 if ($prev_el && $prev_el->{level} > $el->{level}) {
785 7         8 $in_block--;
786             }
787              
788 35 100 66     169 if ($next_el && $next_el->{level} > $el->{level}) {
789 7         8 $in_block++;
790             }
791 35         83 last SWITCH;
792             }
793              
794 139 100       396 if ($el->{type} eq 'html_comment') {
795 6         12 $output = qq/"$offset"/;
796              
797 6         8 $output .= qq/ . "\n"/;
803             }
804             else {
805 2         3 $output .= qq/. "\n"/;
806 2         5 push @$stack, $el;
807             }
808              
809 6         7 $output .= qq/;/;
810 6         15 last SWITCH;
811             }
812              
813 133 100       352 if ($el->{type} eq 'comment') {
814 9         21 push @$stack, $el;
815 9         20 last SWITCH;
816             }
817              
818 124 100       298 if ($el->{type} eq 'filter') {
819 10         35 my $filter = $self->filters->{$el->{name}};
820 10 50       30 die "unknown filter: $el->{name}" unless $filter;
821              
822 10 100       24 if ($el->{name} eq 'escaped') {
823 1         5 $output =
824             qq/escape "/
825             . $self->_parse_text($el->{text})
826             . qq/\n";/;
827             }
828             else {
829 9         40 $el->{text} = $filter->($el->{text});
830              
831 9         42 my $text = $self->_parse_text($el->{text});
832 9         47 $text =~ s/\\\n/\\n/g;
833 9         41 $output = qq/"/ . $text . qq/\n";/;
834             }
835 10         27 last SWITCH;
836             }
837              
838 114 50       309 unless ($el->{text}) {
839 114         236 last SWITCH;
840             }
841              
842 0         0 die "unknown type=" . $el->{type};
843              
844             } #end:SWITCH
845             } #end:ELEM
846             continue {
847 538 100       1541 push @lines, '$_H .= ' . $output if $output;
848 538         685 $output = '';
849 538         931 $count++;
850             } #ELEM
851              
852 118         245 my $last_empty_line = 0;
853 118 100 66     265 $last_empty_line = 1
854             if $self->tape->[-1] && $self->tape->[-1]->{line} eq '';
855              
856             # Close remaining content blocks, last-seen first
857 118         309 foreach my $el (reverse @$stack) {
858 67         151 my $offset = ' ' x $el->{level};
859 67         122 my $ending = '';
860 67 100       196 if ($el->{type} eq 'tag') {
    100          
861 63         152 $ending = "{name}>";
862             }
863             elsif ($el->{type} eq 'html_comment') {
864 2 100       6 $ending .= '{if};
865 2         4 $ending .= "-->";
866             }
867              
868 67 100       299 push @lines, qq|\$_H .= "$offset$ending\n";| if $ending;
869             }
870              
871 118 50       312 if ($lines[-1]) {
872 118 100       452 $lines[-1] =~ s/\n";$/";/ unless $last_empty_line;
873             }
874              
875 118         425 $code .= join("\n", @lines);
876              
877 118         420 $code .= $self->append;
878              
879 118         228 $code .= q/return $_H; };/;
880              
881 118         380 $self->code($code);
882              
883 118         722 return $self;
884             }
885              
886             sub _parse_text {
887 206     206   298 my $self = shift;
888 206         288 my $text = shift;
889              
890 206         245 my $expr = 0;
891 206 50 33     646 if ($text =~ m/^\"/ && $text =~ m/\"$/) {
892 0         0 $text =~ s/^"//;
893 0         0 $text =~ s/"$//;
894 0         0 $expr = 1;
895             }
896              
897 206         1081 $text =~ s/($UNESCAPE_RE)/$ESCAPE->{$1}/g;
898              
899 206         281 my $output = '';
900 206         223 while (1) {
901 229         259 my $t;
902 229         274 my $escape = 0;
903 229         257 my $found = 0;
904 229         240 my $variable;
905              
906 229         240 our $curly_brace_n;
907 229         1411 $curly_brace_n = qr/ (?> [^{}]+ | \{ (??{ $curly_brace_n }) \} )* /x;
908              
909 229 100       15505 if ($text =~ s/^(.*?)?(?
    100          
910 21         37 $found = 1;
911 21         47 $t = $1;
912 21         42 $variable = $2;
913             }
914             elsif ($text =~ s/^(.*?)?\\\\(\#\{$curly_brace_n\})//xms) {
915 2         3 $found = 1;
916 2         4 $t = $1;
917 2         27 $variable = $2;
918 2         4 $escape = 1;
919             }
920              
921 229 100       742 if ($t) {
922 16         30 $t =~ s/\\\#/\#/g;
923 16 50       53 $output .= $expr ? $t : quotemeta($t);
924             }
925              
926 229 100       417 if ($found) {
927 23         121 $variable =~ s/\#\{(.*)\}/$1/;
928              
929 23 100       62 my $prefix = $escape ? quotemeta("\\") : '';
930 23         90 $output .= qq/$prefix".do { $variable }."/;
931             }
932             else {
933 206         508 $text = $self->_parse_interpolation($text);
934 206         436 $output .= $text;
935 206         435 last;
936             }
937             }
938              
939 206 50       715 return $expr ? qq/$output/ : $output;
940             }
941              
942             sub _parse_interpolation {
943 206     206   358 my $self = shift;
944 206         329 my ($text) = @_;
945              
946 206         223 my @parts;
947              
948 206         623 my $start_tag = qr{(?
949 206         588 my $end_tag = qr{\}};
950              
951 206         824 pos $text = 0;
952 206         686 while (pos $text < length $text) {
953 202 50       3083 if ($text =~ m/\G $start_tag (.*?) $end_tag/xgcms) {
    50          
954 0         0 push @parts, 'do {' . $1 . '}';
955             }
956             elsif ($text =~ m/\G (.*?) (?=$start_tag)/xgcms) {
957 0         0 push @parts, 'qq{' . quotemeta($1) . '}';
958             }
959             else {
960 202         475 my $leftover = substr($text, pos($text));
961 202         626 push @parts, 'qq{' . quotemeta($leftover) . '}';
962 202         399 last;
963             }
964             }
965              
966 206 100       498 return '' unless @parts;
967              
968 202         352 return '" . ' . join('.', map {s/\\\\#\\{/#\\{/; $_} @parts) . '."';
  202         337  
  202         1172  
969             }
970              
971             sub compile {
972 118     118 1 177 my $self = shift;
973              
974 118         235 my $code = $self->code;
975 118 50       300 return unless $code;
976              
977 28 50   28   182 my $compiled = eval $code;
  28     28   252  
  28     28   945  
  28     28   126  
  28     27   230  
  28     27   4888  
  28     27   119  
  28     27   692  
  27     24   907  
  27     24   129  
  27     24   251  
  27     24   1191  
  27     24   116  
  27     24   1161  
  27     24   609  
  27     24   115  
  27     21   211  
  27     21   856  
  27     21   109  
  27     21   332  
  27     20   606  
  27     20   111  
  27     20   222  
  27     20   3812  
  24     16   141  
  24     16   204  
  24     16   664  
  24     16   102  
  24     14   242  
  24     14   3357  
  24     14   121  
  24     14   599  
  24     12   724  
  24     12   109  
  24     12   239  
  24     12   1221  
  24     12   125  
  24     12   1097  
  24     12   524  
  24     12   105  
  24     38   243  
  24         721  
  24         116  
  24         336  
  24         676  
  24         109  
  24         218  
  24         2707  
  21         100  
  21         244  
  21         591  
  21         89  
  21         204  
  21         3106  
  21         95  
  21         588  
  20         589  
  20         91  
  20         179  
  20         740  
  20         88  
  20         1013  
  20         459  
  20         82  
  20         173  
  20         519  
  20         144  
  20         287  
  20         435  
  20         83  
  20         181  
  20         1439  
  16         79  
  16         169  
  16         465  
  16         65  
  16         187  
  16         1943  
  16         71  
  16         519  
  14         348  
  14         58  
  14         134  
  14         576  
  14         61  
  14         669  
  14         449  
  14         147  
  14         139  
  14         402  
  14         61  
  14         188  
  14         322  
  14         59  
  14         118  
  14         1145  
  12         71  
  12         197  
  12         359  
  12         54  
  12         100  
  12         1630  
  12         54  
  12         568  
  12         301  
  12         46  
  12         137  
  12         445  
  12         49  
  12         487  
  12         265  
  12         44  
  12         126  
  12         291  
  12         70  
  12         163  
  12         281  
  12         50  
  12         114  
  12         801  
  118         15427  
  38         52  
  41         167  
  41         68  
  41         54  
  41         51  
  41         50  
  41         51  
  41         90  
978              
979 118 100       471 if ($@) {
980 2         8 $self->error($@);
981 2         10 return undef;
982             }
983              
984 116         527 $self->compiled($compiled);
985              
986 116         396 return $self;
987             }
988              
989             sub interpret {
990 123     118 1 191 my $self = shift;
991              
992 120         286 my $compiled = $self->compiled;
993              
994 118         176 my $output = eval { $compiled->($self, @_) };
  118         3507  
995              
996 118 100       776 if ($@) {
997 1         3 $self->error($@);
998 1         4 return undef;
999             }
1000              
1001 117         450 return $output;
1002             }
1003              
1004             sub render {
1005 118     118 1 67948 my $self = shift;
1006 118         217 my $tmpl = shift;
1007              
1008             # Parse
1009 118         401 $self->parse($tmpl);
1010              
1011             # Build
1012 118 50       474 return unless defined $self->build(@_);
1013              
1014             # Compile
1015 118 100       354 $self->compile || return undef;
1016              
1017             # Interpret
1018 116         376 return $self->interpret(@_);
1019             }
1020              
1021             # For templates in __DATA__ section
1022             sub _eq_checksum {
1023 1     1   1 my $self = shift;
1024              
1025             # Exit if not virtual path
1026 1 50       3 return 0 unless ref $self->fullpath eq 'SCALAR';
1027              
1028 0 0       0 return 1 if $self->cache == 2;
1029 0 0       0 return 0 if $self->cache == 0;
1030              
1031 0         0 my $fullpath = $self->fullpath;
1032 0         0 $fullpath = $$fullpath;
1033              
1034 0         0 my $file = IO::File->new;
1035 0 0       0 $file->open($self->cache_path, 'r') or return;
1036 0         0 $file->sysread(my $cache_md5_checksum, 33); # 33 = # + hashsum
1037 0         0 $file->close;
1038              
1039 0         0 my $orig_md5_checksum = '#'.$self->_digest($fullpath);
1040              
1041 0         0 return $cache_md5_checksum eq $orig_md5_checksum;
1042             }
1043              
1044             sub _digest {
1045 1     1   3 my ($self, $content) = @_;
1046              
1047 1         12 my $md5 = Digest::MD5->new();
1048 1 50       4 $content = decode($self->encoding, $content) if $self->encoding;
1049 1         63 $md5->add($content);
1050 1         12 return $md5->hexdigest();
1051             }
1052              
1053             sub render_file {
1054 8     8 1 3419 my $self = shift;
1055 8         34 my $path = shift;
1056              
1057             # Set file fullpath
1058 8         55 $self->_fullpath($path);
1059              
1060 8 100       22 if ($self->cache >= 1) {
1061             # Make cache directory
1062 6         16 my $cache_dir = $self->_cache_dir;
1063             # Set cache path
1064 6         21 $self->_cache_path($path, $cache_dir);
1065              
1066             # Exists same cache file?
1067 6 100 66     31 if (-e $self->cache_path && ($self->_eq_mtime || $self->_eq_checksum)) {
      66        
1068 2         8 return $self->_interpret_cached(@_);
1069             }
1070             }
1071              
1072 6         10 my $content = '';
1073 6         43 my $file = IO::File->new;
1074 6 100       213 if (ref $self->fullpath eq 'SCALAR') { # virtual path
1075 1         4 $content = $self->fullpath;
1076 1         3 $content = $$content;
1077             } else {
1078             # Open file
1079 5 50       12 $file->open($self->fullpath, 'r') or die "Can't open template '$path': $!";
1080              
1081             # Slurp file
1082 5         367 while ($file->sysread(my $buffer, CHUNK_SIZE, 0)) {
1083 5         109 $content .= $buffer;
1084             }
1085 5         54 $file->close;
1086             }
1087              
1088 6         79 $content =~ s/\r//g;
1089              
1090             # Encoding
1091 6 50       25 $content = decode($self->encoding, $content) if $self->encoding;
1092              
1093             # Render
1094 6         670 my $output;
1095 6 50       24 if ($output = $self->render($content, @_)) {
1096 6 100       20 if ($self->cache >= 1) {
1097             # Create cache
1098 4 50       13 if ($file->open($self->cache_path, 'w')) {
1099 4         652 binmode $file, ':utf8';
1100              
1101 4 100       27 if (ref $self->fullpath eq 'SCALAR') {
1102 1         5 my $md5_checksum = $self->_digest($content);
1103 1         7 print $file '#'.$md5_checksum."\n".$self->code; # Write with file checksum (virtual path)
1104             } else {
1105 3         7 my $mtime = (stat($self->fullpath))[9];
1106 3         21 print $file '#'.$mtime."\n".$self->code; # Write with file mtime
1107             }
1108              
1109 4         25 $file->close;
1110             }
1111             }
1112             }
1113              
1114 6         293 return $output;
1115             }
1116              
1117             sub _fullpath {
1118 8     8   10 my $self = shift;
1119 8         16 my $path = shift;
1120              
1121 8 100 66     115 if (File::Spec->file_name_is_absolute($path) and -r $path) {
1122 1         4 $self->fullpath($path);
1123 1         2 return;
1124             }
1125              
1126 7         11 for my $p (@{$self->path}) {
  7         30  
1127 7 100       18 if (ref $p eq 'HASH') { # virtual path
1128 1 50       4 if (defined(my $content = $p->{$path})) {
1129 1         4 $self->fullpath(\$content);
1130 1         2 return;
1131             }
1132             } else {
1133 6         66 my $fullpath = File::Spec->catfile($p, $path);
1134 6 50       232 if (-r $fullpath) { # is readable ?
1135 6         20 $self->fullpath($fullpath);
1136 6         12 return;
1137             }
1138             }
1139             }
1140              
1141 0         0 Carp::croak("Can't find template '$path'");
1142             }
1143              
1144             sub _cache_dir {
1145 6     6   8 my $self = shift;
1146              
1147 6 100       12 my $cache_prefix = (ref $self->fullpath eq 'SCALAR')
1148             ? 'HASH'
1149             : URI::Escape::uri_escape(
1150             File::Basename::dirname($self->fullpath)
1151             );
1152              
1153 6         154 my $cache_dir = File::Spec->catdir(
1154             $self->cache_dir,
1155             $cache_prefix,
1156             );
1157              
1158 6 100       150 if (not -e $cache_dir) {
1159 3         23 require File::Path;
1160 3         5 eval { File::Path::mkpath($cache_dir) };
  3         569  
1161 3 50       10 Carp::carp("Can't mkpath '$cache_dir': $@") if $@;
1162             }
1163              
1164 6         16 return $cache_dir;
1165             }
1166              
1167             sub _cache_path {
1168 6     6   9 my $self = shift;
1169 6         28 my $path = shift;
1170 6         24 my $cache_dir = shift;
1171              
1172 6         261 $self->cache_path(File::Spec->catfile(
1173             $cache_dir,
1174             File::Basename::basename($path).'.pl',
1175             ));
1176             }
1177              
1178             sub _eq_mtime {
1179 3     3   6 my $self = shift;
1180              
1181             # Exit if virtual path
1182 3 50       9 return 0 if ref $self->fullpath eq 'SCALAR';
1183              
1184 3 100       16 return 1 if $self->cache == 2;
1185 1 50       4 return 0 if $self->cache == 0;
1186              
1187 1         10 my $file = IO::File->new;
1188 1 50       36 $file->open($self->cache_path, 'r') or return;
1189 1         76 $file->sysread(my $cache_mtime, length('#xxxxxxxxxx'));
1190 1         12 $file->close;
1191 1         14 my $orig_mtime = '#'.(stat($self->fullpath))[9];
1192              
1193 1         9 return $cache_mtime eq $orig_mtime;
1194             }
1195              
1196             sub _interpret_cached {
1197 2     2   4 my $self = shift;
1198              
1199 2         5 my $compiled = do $self->cache_path;
1200 2         11 $self->compiled($compiled);
1201 2         6 return $self->interpret(@_);
1202             }
1203              
1204             sub _doctype {
1205 16     16   28 my $self = shift;
1206 16         42 my ($type, $encoding) = @_;
1207              
1208 16   100     67 $type ||= '';
1209 16   100     62 $encoding ||= 'utf-8';
1210              
1211 16         25 $type = lc $type;
1212              
1213 16 100       39 if ($type eq 'xml') {
1214 2 50       6 return '' if $self->format eq 'html5';
1215 2 50       4 return '' if $self->format eq 'html4';
1216              
1217 2         8 return qq||;
1218             }
1219              
1220 14 100       37 if ($self->format eq 'xhtml') {
    100          
    50          
1221 10 100       73 if ($type eq 'strict') {
    100          
    100          
    100          
    100          
    100          
1222             return
1223 1         3 q||;
1224             }
1225             elsif ($type eq 'frameset') {
1226             return
1227 1         4 q||;
1228             }
1229             elsif ($type eq '5') {
1230 1         3 return '';
1231             }
1232             elsif ($type eq '1.1') {
1233             return
1234 1         3 q||;
1235             }
1236             elsif ($type eq 'basic') {
1237             return
1238 1         4 q||;
1239             }
1240             elsif ($type eq 'mobile') {
1241             return
1242 1         4 q||;
1243             }
1244             else {
1245             return
1246 4         15 q||;
1247             }
1248             }
1249             elsif ($self->format eq 'html4') {
1250 3 100       9 if ($type eq 'strict') {
    100          
1251             return
1252 1         5 q||;
1253             }
1254             elsif ($type eq 'frameset') {
1255             return
1256 1         4 q||;
1257             }
1258             else {
1259             return
1260 1         4 q||;
1261             }
1262             }
1263             elsif ($self->format eq 'html5') {
1264 1         6 return '';
1265             }
1266              
1267 0           return '';
1268             }
1269              
1270             1;
1271             __END__