File Coverage

blib/lib/Template/Alloy/Velocity.pm
Criterion Covered Total %
statement 178 205 86.8
branch 111 160 69.3
condition 40 61 65.5
subroutine 5 6 83.3
pod 2 3 66.6
total 336 435 77.2


line stmt bran cond sub pod time code
1             package Template::Alloy::Velocity;
2              
3             =head1 NAME
4              
5             Template::Alloy::Velocity - Velocity (VTL) role
6              
7             =cut
8              
9 3     3   26 use strict;
  3         8  
  3         121  
10 3     3   27 use warnings;
  3         8  
  3         167  
11 3     3   23 use Template::Alloy;
  3         6  
  3         32  
12              
13             our $VERSION = $Template::Alloy::VERSION;
14              
15 0     0 0 0 sub new { die "This class is a role for use by packages such as Template::Alloy" }
16              
17             ###----------------------------------------------------------------###
18              
19             sub parse_tree_velocity {
20 266     266 1 424 my $self = shift;
21 266         429 my $str_ref = shift;
22 266 50 33     997 if (! $str_ref || ! defined $$str_ref) {
23 0         0 $self->throw('parse.no_string', "No string or undefined during parse", undef, 1);
24             }
25              
26 266   50     1103 local $self->{'V2EQUALS'} = $self->{'V2EQUALS'} || 0;
27 266 50       861 local $self->{'INTERPOLATE'} = defined($self->{'INTERPOLATE'}) ? $self->{'INTERPOLATE'} : 1;
28 266 50       678 local $self->{'V1DOLLAR'} = defined($self->{'V1DOLLAR'}) ? $self->{'V1DOLLAR'} : 1;
29 266 50       575 local $self->{'ANYCASE'} = defined($self->{'ANYCASE'}) ? $self->{'ANYCASE'} : 1;
30 266 100       563 local $self->{'AUTO_EVAL'} = defined($self->{'AUTO_EVAL'}) ? $self->{'AUTO_EVAL'} : 1;
31 266 50       554 local $self->{'SHOW_UNDEFINED_INTERP'} = defined($self->{'SHOW_UNDEFINED_INTERP'}) ? $self->{'SHOW_UNDEFINED_INTERP'} : 1;
32              
33 266         948 local $self->{'START_TAG'} = qr{\#};
34 266 50       1658 local $self->{'_start_tag'} = (! $self->{'INTERPOLATE'}) ? $self->{'START_TAG'} : qr{(?: $self->{'START_TAG'} | (\$))}sx;
35 266         654 local $self->{'_end_tag'}; # changes over time
36              
37 266         459 local @{ $Template::Alloy::Parse::ALIASES }{qw(PARSE INCLUDE _INCLUDE ELSEIF)}
  266         977  
38             = qw(PROCESS INSERT INCLUDE ELSIF);
39 266         440 my $dirs = $Template::Alloy::Parse::DIRECTIVES;
40 266         344 my $aliases = $Template::Alloy::Parse::ALIASES;
41 266         805 local @{ $dirs }{ keys %$aliases } = values %$aliases; # temporarily add to the table
  266         1008  
42 266         579 local @{ $self }{@Template::Alloy::CONFIG_COMPILETIME} = @{ $self }{@Template::Alloy::CONFIG_COMPILETIME};
  266         2344  
  266         1198  
43 266 50       807 delete $dirs->{'JS'} if ! $self->{'COMPILE_JS'};
44              
45 266         385 my @tree; # the parsed tree
46 266         414 my $pointer = \@tree; # pointer to current tree to handle nested blocks
47 266         388 my @state; # maintain block levels
48 266         522 local $self->{'_state'} = \@state; # allow for items to introspect (usually BLOCKS)
49 266         516 local $self->{'_no_interp'} = 0; # no interpolation in perl
50 266         616 my @in_view; # let us know if we are in a view
51             my @blocks; # storage for defined blocks
52 266         0 my @meta; # place to store any found meta information (to go into META)
53 266         393 my $post_chomp = 0; # previous post_chomp setting
54 266         329 my $continue = 0; # flag for multiple directives in the same tag
55 266         351 my $post_op = 0; # found a post-operative DIRECTIVE
56 266         934 my $capture; # flag to start capture
57             my $func;
58 266         0 my $pre_chomp;
59 266         0 my $node;
60 266         0 my $macro_block;
61 266         754 pos($$str_ref) = 0;
62              
63 266         526 while (1) {
64             ### allow for #set(foo = PROCESS foo)
65 784 100       1453 if ($capture) {
66 28 100       93 if ($macro_block) {
    50          
67 16         19 $macro_block = 0;
68 16         38 push @state, $capture;
69 16   50     60 $pointer = $capture->[4] ||= [];
70 16         32 undef $capture;
71 16         28 next;
72             } elsif ($$str_ref =~ m{ \G \s* (\w+)\b }gcx) {
73 12 50       44 $func = $self->{'ANYCASE'} ? uc($1) : $1;
74 12 50       35 $func = $aliases->{$func} if $aliases->{$func};
75             $self->throw('parse', "Found unknown DIRECTIVE ($func)", undef, pos($$str_ref) - length($func))
76 12 50       29 if ! $dirs->{$func};
77 12         39 $node = [$func, pos($$str_ref) - length($func), undef];
78             } else {
79 0         0 $self->throw('parse', "Error looking for block in capture DIRECTIVE", undef, pos($$str_ref));
80             }
81              
82 12         21 push @{ $capture->[4] }, $node;
  12         47  
83 12         25 undef $capture;
84              
85             ### handle all other
86             } else {
87             ### find the next opening tag
88 756 100       4365 $$str_ref =~ m{ \G (.*?) $self->{'_start_tag'} }gcxs
89             || last;
90 496         1748 my ($text, $dollar) = ($1, $2);
91              
92             ### found a text portion - chomp it and store it
93 496 100       1086 if (length $text) {
94 160 100       359 if (! $post_chomp) { }
    50          
    50          
    0          
95 0         0 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x }
96 16         57 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x }
97 0         0 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x }
98 160 50       432 push @$pointer, $text if length $text;
99             }
100              
101             ### handle variable interpolation ($2 eq $)
102 496 100       1008 if ($dollar) {
103             ### inspect previous text chunk for escape slashes
104 168 100       408 my $n = ($text =~ m{ (\\+) $ }x) ? length($1) : 0;
105 168 100 66     412 if ($n && ! $self->{'_no_interp'}) {
106 12         40 my $chop = int(($n + 1) / 2); # were there odd escapes
107 12 50 33     62 substr($pointer->[-1], -$chop, $chop, '') if defined($pointer->[-1]) && ! ref($pointer->[-1]);
108             }
109 168 100 66     692 if ($self->{'_no_interp'} || $n % 2) {
110 8         18 push @$pointer, $dollar;
111 8         17 next;
112             }
113              
114 160         405 my $not = $$str_ref =~ m{ \G ! }gcx;
115 160         275 my $mark = pos($$str_ref);
116 160         201 my $ref;
117 160 100       312 if ($$str_ref =~ m{ \G \{ }gcx) {
118 8         16 local $self->{'_operator_precedence'} = 0; # allow operators
119 8         32 local $self->{'_end_tag'} = qr{\}};
120 8         31 $ref = $self->parse_expr($str_ref);
121 8 50       76 $$str_ref =~ m{ \G \s* $Template::Alloy::Parse::QR_COMMENTS \} }gcxo
122             || $self->throw('parse', 'Missing close }', undef, pos($$str_ref));
123             } else {
124 152         337 local $self->{'_operator_precedence'} = 1; # no operators
125 152         491 local $Template::Alloy::Parse::QR_COMMENTS = qr{};
126 152         498 $ref = $self->parse_expr($str_ref);
127             }
128 160 50       412 $self->throw('parse', "Error while parsing for interpolated string", undef, pos($$str_ref))
129             if ! defined $ref;
130 160 50 66     599 if (! $not && $self->{'SHOW_UNDEFINED_INTERP'}) {
131 148         658 $ref = [[undef, '//', $ref, '$'.substr($$str_ref, $mark, pos($$str_ref)-$mark)], 0];
132             }
133 160         470 push @$pointer, ['GET', $mark, pos($$str_ref), $ref];
134 160         254 $post_chomp = 0; # no chomping after dollar vars
135 160         371 next;
136             }
137              
138             ### allow for escaped #
139 328 50       725 my $n = ($text =~ m{ (\\+) $ }x) ? length($1) : 0;
140 328 50       702 if ($n) {
141 0         0 my $chop = int(($n + 1) / 2); # were there odd escapes
142 0 0 0     0 substr($pointer->[-1], -$chop, $chop, '') if defined($pointer->[-1]) && ! ref($pointer->[-1]);
143 0 0       0 if ($n % 2) {
144 0         0 push @$pointer, '#';
145 0         0 next;
146             }
147             }
148 328 100 100     1362 if ($$str_ref =~ m{ \G \# .*\n? }gcx # single line comment
149             || $$str_ref =~ m{ \G \* .*? \*\# }gcxs) { # multi-line comment
150 10         27 next;
151             }
152              
153 318 100 100     1201 $$str_ref =~ m{ \G (\w+) }gcx
154             || $$str_ref =~ m{ \G \{ (\w+) (\}) }gcx
155             || $self->throw('parse', 'Missing directive name', undef, pos($$str_ref));
156 316 50       1031 $func = $self->{'ANYCASE'} ? uc($1) : $1;
157              
158             ### make sure we know this directive - if we don't then allow fallback to macros (velocity allows them as directives)
159 316 100       712 $func = $aliases->{$func} if $aliases->{$func};
160 316 100       730 if (! $dirs->{$func}) {
161 12         24 my $name = $1;
162 12 50       39 my $mark = pos($$str_ref) - length($func) - ($2 ? 2 : 0);
163 12         18 my $args = 0;
164 12 50       39 if ($$str_ref =~ m{ \G \( }gcx) {
165 12         29 local $self->{'_operator_precedence'} = 0; # reset precedence
166 12         52 $args = $self->parse_args($str_ref, {is_parened => 1});
167 12 50       101 $$str_ref =~ m{ \G \s* $Template::Alloy::Parse::QR_COMMENTS \) }gcxo
168             || $self->throw('parse.missing.paren', "Missing close \) in directive args", undef, pos($$str_ref));
169             }
170 12         45 $node = ['GET', $mark, pos($$str_ref), [$name, $args]];
171 12         25 push @$pointer, $node;
172 12         32 next;
173             #$self->throw('parse', "Found unknow DIRECTIVE ($func)", undef, pos($$str_ref) - length($func));
174             }
175 304         739 $node = [$func, pos($$str_ref), undef];
176              
177 304 100       911 if ($$str_ref =~ m{ \G \( ([+=~-]?) }gcx) {
178 198         648 $self->{'_end_tag'} = qr{\s*([+=~-]?)\)};
179 198         533 $pre_chomp = $1;
180             } else {
181 106         205 $self->{'_end_tag'} = '';
182 106         222 $pre_chomp = '';
183             }
184              
185             ### take care of chomping (this is an extention to velocity
186 304   100     1193 $pre_chomp ||= $self->{'PRE_CHOMP'};
187 304 100       541 $pre_chomp =~ y/-=~+/1230/ if $pre_chomp;
188 304 100 100     653 if ($pre_chomp && $pointer->[-1] && ! ref $pointer->[-1]) {
      100        
189 14 100       50 if ($pre_chomp == 1) { $pointer->[-1] =~ s{ (?:\n|^) [^\S\n]* \z }{}x }
  2 50       11  
    50          
190 0         0 elsif ($pre_chomp == 2) { $pointer->[-1] =~ s{ (\s+) \z }{ }x }
191 12         51 elsif ($pre_chomp == 3) { $pointer->[-1] =~ s{ (\s+) \z }{}x }
192 14 50       46 splice(@$pointer, -1, 1, ()) if ! length $pointer->[-1]; # remove the node if it is zero length
193             }
194              
195 304         750 push @$pointer, $node;
196             }
197              
198 316         678 $$str_ref =~ m{ \G \s+ }gcx;
199              
200             ### parse remaining tag details
201 316 100       696 if ($func ne 'END') {
202 236         357 $node->[3] = eval { $dirs->{$func}->[0]->($self, $str_ref, $node) };
  236         897  
203 236 100       607 if (my $err = $@) {
204 2 50 33     26 $err->node($node) if UNIVERSAL::can($err, 'node') && ! $err->node;
205 2         32 die $err;
206             }
207 234         436 $node->[2] = pos $$str_ref;
208             }
209              
210             ### handle ending tags - or continuation blocks
211 314 100 100     1402 if ($func eq 'END' || $dirs->{$func}->[4]) {
    100          
    50          
212 96 50       193 if (! @state) {
213 0         0 print Data::Dumper::Dumper(\@tree);
214 0         0 $self->throw('parse', "Found an $func tag while not in a block", $node, pos($$str_ref));
215             }
216 96         159 my $parent_node = pop @state;
217              
218             ### handle continuation blocks such as elsif, else, catch etc
219 96 100       221 if ($dirs->{$func}->[4]) {
220 16         27 pop @$pointer; # we will store the node in the parent instead
221 16         32 $parent_node->[5] = $node;
222 16         29 my $parent_type = $parent_node->[0];
223 16 50       47 if (! $dirs->{$func}->[4]->{$parent_type}) {
224 0         0 $self->throw('parse', "Found unmatched nested block", $node, pos($$str_ref));
225             }
226             }
227              
228             ### restore the pointer up one level (because we hit the end of a block)
229 96 100       224 $pointer = (! @state) ? \@tree : $state[-1]->[4];
230              
231             ### normal end block
232 96 100       218 if (! $dirs->{$func}->[4]) {
233 80 100       289 if ($parent_node->[0] eq 'BLOCK') { # move BLOCKS to front
    50          
    50          
234 14 50 33     59 if (defined($parent_node->[3]) && @in_view) {
235 0         0 push @{ $in_view[-1] }, $parent_node;
  0         0  
236             } else {
237 14         26 push @blocks, $parent_node;
238             }
239 14 100 66     55 if ($pointer->[-1] && ! $pointer->[-1]->[6]) { # capturing doesn't remove the var
240 4         12 splice(@$pointer, -1, 1, ());
241             }
242             } elsif ($parent_node->[0] eq 'VIEW') {
243 0         0 my $ref = { map {($_->[3] => $_->[4])} @{ pop @in_view }};
  0         0  
  0         0  
244 0         0 unshift @{ $parent_node->[3] }, $ref;
  0         0  
245             } elsif ($dirs->{$parent_node->[0]}->[5]) { # allow no_interp to turn on and off
246 0         0 $self->{'_no_interp'}--;
247             }
248              
249              
250             ### continuation block - such as an elsif
251             } else {
252 16         29 push @state, $node;
253 16   50     63 $pointer = $node->[4] ||= [];
254             }
255 96         230 $node->[2] = pos $$str_ref;
256              
257             ### handle block directives
258             } elsif ($dirs->{$func}->[2]) {
259 64         119 push @state, $node;
260 64   50     232 $pointer = $node->[4] ||= []; # allow future parsed nodes before END tag to end up in current node
261 64 50       147 push @in_view, [] if $func eq 'VIEW';
262 64 50       207 $self->{'_no_interp'}++ if $dirs->{$node->[0]}->[5] # allow no_interp to turn on and off
263              
264             } elsif ($func eq 'META') {
265 0         0 unshift @meta, @{ $node->[3] }; # first defined win
  0         0  
266 0         0 $node->[3] = undef; # only let these be defined once - at the front of the tree
267             }
268              
269              
270             ### look for the closing tag
271 314 100       4657 if ($$str_ref =~ m{ \G $self->{'_end_tag'} }gcxs) {
    100          
272 300   100     1281 $post_chomp = $1 || $self->{'POST_CHOMP'};
273 300 100       660 $post_chomp =~ y/-=~+/1230/ if $post_chomp;
274 300         463 $continue = 0;
275 300         403 $post_op = 0;
276              
277 300 100 66     742 if ($node->[6] && $node->[0] eq 'MACRO') { # allow for MACRO's without a BLOCK
278 16         24 $capture = $node;
279 16         29 $macro_block = 1;
280             }
281 300         624 next;
282              
283             ### setup capturing
284             } elsif ($node->[6]) {
285 12         28 $capture = $node;
286 12         32 next;
287              
288             ### no closing tag
289             } else {
290 2         14 $self->throw('parse', "Not sure how to handle tag", $node, pos($$str_ref));
291             }
292             }
293              
294             ### cleanup the tree
295 260 100       646 unshift(@tree, @blocks) if @blocks;
296 260 50       514 unshift(@tree, ['META', 1, 1, \@meta]) if @meta;
297 260 50       587 $self->throw('parse', "Missing end tag", $state[-1], pos($$str_ref)) if @state > 0;
298              
299             ### pull off the last text portion - if any
300 260 100       631 if (pos($$str_ref) != length($$str_ref)) {
301 80         197 my $text = substr $$str_ref, pos($$str_ref);
302 80 100       197 if (! $post_chomp) { }
    50          
    0          
    0          
303 2         11 elsif ($post_chomp == 1) { $text =~ s{ ^ [^\S\n]* \n }{}x }
304 0         0 elsif ($post_chomp == 2) { $text =~ s{ ^ \s+ }{ }x }
305 0         0 elsif ($post_chomp == 3) { $text =~ s{ ^ \s+ }{}x }
306 80 100       234 push @$pointer, $text if length $text;
307             }
308              
309 260         3839 return \@tree;
310             }
311              
312             sub merge {
313 200     200 1 2118 my ($self, $in, $swap, $out) = @_;
314 200   50     934 local $self->{'SYNTAX'} = $self->{'SYNTAX'} || 'velocity';
315 200         588 return $self->process_simple($in, $swap, $out);
316             }
317              
318             ###----------------------------------------------------------------###
319              
320             1;
321              
322             __END__