File Coverage

blib/lib/Template/Direct/Conditional.pm
Criterion Covered Total %
statement 162 171 94.7
branch 86 114 75.4
condition 20 33 60.6
subroutine 26 32 81.2
pod 7 7 100.0
total 301 357 84.3


line stmt bran cond sub pod time code
1             package Template::Direct::Conditional;
2              
3 2     2   11 use base Template::Direct::Base;
  2         4  
  2         196  
4              
5 2     2   12 use strict;
  2         4  
  2         75  
6 2     2   10 use warnings;
  2         3  
  2         58  
7              
8             =head1 NAME
9              
10             Template::Direct::Conditional - Handle a conditonal in a template
11              
12             =head1 DESCRIPTION
13              
14             Provide support for conditionals in templates
15              
16             =cut
17              
18 2     2   11 use Carp;
  2         12  
  2         2480  
19              
20             =head2 I<$class>->new( $index, $line )
21              
22             Create a new instance object.
23              
24             =cut
25             sub new {
26 16     16 1 37 my ($class, $index, $data) = @_;
27 16         61 my $self = $class->SUPER::new();
28 16         45 $self->{'startTag'} = $index;
29 16         34 $self->{'conditional'} = $data;
30 16         40 return $self;
31             }
32              
33             =head2 I<$if>->tagName( )
34              
35             Returns 'if'
36              
37             =cut
38 16     16 1 51 sub tagName { 'if' }
39              
40             =head2 I<$if>->subTags( )
41              
42             Returns a list of expected sub tags: [else, elif]
43              
44             =cut
45             sub subTags {
46             {
47 20     20 1 156 'else' => 1,
48             'elif' => 1,
49             }
50             }
51              
52             =head2 I<$if>->conditional( )
53              
54             Returns the conditional statement fromt he template.
55              
56             =cut
57 38     38 1 101 sub conditional { $_[0]->{'conditional'} }
58              
59             =head2 I<$if>->compile( )
60              
61             Modifies a template with the data listed correctly.
62              
63             =cut
64             sub compile {
65 56     56 1 106 my ($self, $data, $template, %p) = @_;
66 56 50       139 return if ref($template) ne 'SCALAR';
67              
68             # Do conditional here
69 56         144 my $section = $self->getFullSection( $template );
70              
71             #print "Found Section '$section'\n";
72 56 100       200 if($section) {
73 38         84 my $cnd = $self->conditional();
74 38         51 my $result = $section;
75              
76             # Make sure we always deal with an else
77 38 100       121 if(not $self->hasSubTag('else')) {
78 5         21 $self->addSubTag('else', 'FAKEELSE', '');
79 5         9 $result .= '{{TAGFAKEELSE}}';
80             }
81              
82             # Conditional has content
83 38         52 foreach (@{$self->allSubTags()}) {
  38         95  
84 46         53 my ($name, $index, $newcond) = @{$_};
  46         104  
85 46         374 my ($prime, $second) = split(/\{\{TAG$index\}\}/, $result);
86             #print "LOOKING AT $cnd with $prime or $second\n";
87 46         112 $self->{'condForWarn'} = $cnd;
88 46         102 my $cond = $self->parseConditional($cnd, $data);
89             #warn "Full Conditional: '$cnd' returns '$cond'\n";
90 46 100       167 if($cond) {
91 18         24 $result = $prime;
92 18         34 last;
93             } else {
94 28         34 $result = $second;
95 28         81 $cnd = $newcond;
96             }
97             }
98 38         71 $section = $result;
99             }
100              
101 56         166 $self->setSection($template, $section);
102              
103             # Prcoess any children (and only children)
104 56         245 $self->SUPER::compileChildren( $data, $template, %p );
105             }
106              
107              
108             =head2 I<$if>->parseConditional($tokenString, $dataStructure)
109              
110             Reduce a string conditional into a boolean
111              
112             =cut
113             sub parseConditional
114             {
115 46     46 1 75 my ($self, $string, $data) = @_;
116             #Special dispensation for clean else
117 46 50       96 return 1 if $string eq 'else';
118              
119             #Split into raw tokens
120 46         229 my @raws = split(/(?
121              
122             #Record all stages
123 46         62 my @depths;
124             my @tokens;
125 46         131 my $current = Template::Direct::Conditional::Tokens->new(\@tokens);
126              
127 46         103 foreach my $raw (@raws) {
128 118 100       416 if($raw =~ s/^\(//) {
    100          
129             # New level
130 2         7 $current->append(Template::Direct::Conditional::Tokens->new());
131 2 50       6 push @depths, $current if $current;
132 2         7 $current = $current->lastItem();
133             } elsif($raw =~ s/^\{//) {
134             # Static Array
135 1         10 my $array = Template::Direct::Conditional::Array->new();
136 1         4 $current->append($array);
137 1 50       3 push @depths, $current if $current;
138 1         2 $current = $array;
139             }
140              
141 118 100       235 if(ref($current) eq 'Template::Direct::Conditional::Array') {
142 2 100       8 my $end = 1 if $raw =~ s/\}$//;
143 2         4 push @{$current}, $raw;
  2         47  
144 2 100 66     12 $current = pop @depths if $end and @depths;
145             } else {
146 116 100 100     432 if($raw eq 'and' or $raw eq 'or') {
147             # Logical Statements are treated later.
148 11         28 $current->append(\$raw);
149             } else {
150             # Add sane tokens only, remove all unexpected charicters.
151 105         132 my $sane = $raw;
152 105         215 $sane =~ s/[^\w\$_\{\}\<\>\|\&\=\!\@]//g;
153            
154             # Get datum if required, replace this token with real value
155 105 100       319 if($sane =~ /^\$(.+)$/) {
156 57         160 $sane = $data->getDatum($1);
157             }
158 105 100 66     439 if(UNIVERSAL::isa($sane, 'ARRAY') and not UNIVERSAL::isa($sane, 'HASH')) {
159 3         8 $sane = Template::Direct::Conditional::Array->new( $sane );
160             }
161              
162             # Push this token onto the current stack.
163 105 100 66     537 $current->append($sane) if defined($sane) and scalar($sane.'') ne '';
164              
165 105 100       347 if($raw =~ /\)$/) {
166 2 50       11 $current = pop @depths if @depths;
167             }
168             }
169             }
170             }
171              
172 46         114 return $self->parseLogical(\@tokens);
173             }
174              
175              
176             =head2 I<$if>->parseLogical( $tokens )
177              
178             Take tokens and group logical statements by and/or
179              
180             =cut
181             sub parseLogical
182             {
183 48     48 1 79 my ($self, $tokens) = @_;
184 48         54 my @tokens;
185              
186             my @stack;
187 48         93 for my $token ($tokens->iterator()) {
188 102 100       240 if(ref($token) eq 'SCALAR') {
    100          
189 11 50       27 warn "Variable or operand to logically compare: ".$self->{'condForWarn'} if @stack == 0;
190 11         42 push @tokens, Template::Direct::Conditional::Tokens->new( [ @stack ] ), ${$token};
  11         23  
191 11         27 @stack = ();
192             } elsif(ref($token) eq 'Template::Direct::Conditional::Tokens') {
193             # Processes and logicals in brackets
194 2         7 push @stack, $self->parseLogical( $token );
195             } else {
196             # Push each static variable or operand to the stack.
197 89         153 push @stack, $token;
198             }
199             }
200              
201 48 50 66     183 warn "Expected variables or operands in conditional: ".$self->{'condForWarn'} if @stack == 0 and @tokens != 0;
202 48 100       128 push @tokens, ((@tokens == 0) ? @stack : Template::Direct::Conditional::Tokens->new( \@stack ));
203              
204 48         122 return Template::Direct::Conditional::Tokens->new(\@tokens)->execute($self->{'startTag'});
205             }
206              
207              
208             package Template::Direct::Conditional::Tokens;
209              
210 2     2   21 use strict;
  2         9  
  2         86  
211 2     2   10 use Carp;
  2         4  
  2         1457  
212              
213             =head1 NAME
214              
215             Template::Direct::Conditional::Tokens - Handle a list of conditional tokens
216              
217             =head1 METHODS
218              
219             =head2 I<$class>->new( $list )
220              
221             Return a list of tokens object.
222              
223             =cut
224             sub new {
225 118     118   153 my ($class, $list) = @_;
226 118 100       216 $list = [] if not defined $list;
227 118         408 return bless $list, $class;
228             }
229              
230             =head2 I<$tokens>->executeConditional( $conditional )
231              
232             suck in triples and output booleans
233              
234             =cut
235             sub execute
236             {
237 70     70   95 my ($self, $cond) = @_;
238 70         123 my @t = $self->iterator();
239              
240             #warn "Tokens: ".join(', ', @t)."\n";
241              
242             # Single comparisons
243 70 100       200 return undef if @t == 0;
244 53 100       142 return $t[0] if @t == 1;
245            
246 36         49 my $true = 1;
247 36         44 my $false = 0;
248              
249 36 100       78 if($t[0] eq 'not') {
250 3 50       16 return not $t[1] ? $true : $false if @t == 2;
    100          
251 1         2 shift @t; # Remove not token
252 1         2 $true = 0;
253 1         2 $false = 1;
254             }
255              
256             # And / Or comparisons
257 34         50 my $a = shift @t;
258 34 50 33     144 $a = not shift @t if $a and $a eq 'not';
259 34         49 my $o = shift @t;
260 34 50       62 warn "Operator not found in conditional: ".join(' ', @{$self})."\n" if not $o;
  0         0  
261 34         43 my $b = shift @t;
262 34 50 33     134 $b = not shift(@t) if $b and $b eq 'not';
263              
264 34 100       85 $a = $a->execute($cond) if ref($a) eq 'Template::Direct::Conditional::Tokens';
265 34 100       80 $b = $b->execute($cond) if ref($b) eq 'Template::Direct::Conditional::Tokens';
266              
267             #print $cond." Found: $a $o $b\n"; # if $o eq 'in';
268              
269             # Arathmetic Logic
270 34 50       58 unshift @t, ($a > $b) if $o eq '>';
271 34 50       62 unshift @t, ($a < $b) if $o eq '<';
272 34 50       68 unshift @t, ($a >= $b) if $o eq '>=';
273 34 50       61 unshift @t, ($a <= $b) if $o eq '<=';
274              
275             # Bitwise Logic
276 34 50       57 unshift @t, ($a | $b) if $o eq '|';
277 34 50       52 unshift @t, ($a & $b) if $o eq '&';
278              
279             # Logical Conditionals
280 34 100 100     87 unshift @t, ($a and $b) ? $true : $false if $o eq 'and';
    100          
281 34 50 33     63 unshift @t, ($a or $b) ? $true : $false if $o eq 'or';
    100          
282 34 100 66     157 unshift @t, ($a eq $b) ? $true : $false if $o eq '=' or $o eq 'eq';
    100          
283 34 0 33     129 unshift @t, ($a ne $b) ? $true : $false if $o eq '!=' or $o eq 'ne';
    50          
284              
285 34 100       67 if($o eq "in") {
286 3 50       7 if(ref($b) eq "Template::Direct::Conditional::Array") {
287             # Array Conditional (python kidnaped!)
288 3 100       11 unshift @t, $b->in($a) ? $true : $false;
289             } else {
290 0         0 croak "Invalid array used in conditional $a in $b";
291             }
292             }
293              
294             # Order of magnatude
295 34 0       63 unshift @t, (($a % $b) == 0) ? $true : $false if $o eq '@';
    50          
296            
297 34 50       64 if(@t == 1) {
298 34         134 return $t[0];
299             } else {
300 0 0       0 Template::Direct::Conditional::Tokens->new(\@t)->execute() ? $true : $false;
301             }
302             }
303              
304             =head2 I<$tokens>->append( $item )
305              
306             Add a token to this token list.
307              
308             =cut
309             sub append {
310 102     102   142 my ($self, $item) = @_;
311 102         98 push @{$self}, $item;
  102         238  
312             }
313              
314             =head2 I<$tokens>->lastItem( )
315              
316             Return the last item from this token list.
317              
318             =cut
319             sub lastItem {
320 2     2   3 my ($self) = @_;
321 2         3 return $self->[$#{$self}];
  2         8  
322             }
323              
324             =head2 I<$tokens>->iterator( )
325              
326             Return the token list as an array.
327              
328             =cut
329 118     118   123 sub iterator { return @{$_[0]}; }
  118         330  
330              
331              
332              
333             package Template::Direct::Conditional::Array;
334              
335 2     2   12 use strict;
  2         3  
  2         71  
336 2     2   15 use Carp;
  2         10  
  2         532  
337              
338             =head1 NAME
339              
340             Template::Direct::Conditional::Array - Handle arrays in conditionals
341              
342             =head1 METHODS
343              
344             =cut
345              
346             use overload
347 0     0   0 "''" => sub { shift->count() },
348 3     3   7 "eq" => sub { shift->count() eq shift },
349 0     0   0 "ne" => sub { shift->count() ne shift },
350 0     0   0 ">" => sub { shift->count() > shift },
351 0     0   0 "<" => sub { shift->count() < shift },
352 0     0   0 "<=" => sub { shift->count() <= shift },
353 0     0   0 ">=" => sub { shift->count() >= shift },
354 2     2   4033 'bool' => sub { shift->count() > 0 },;
  2     11   2385  
  2         36  
  11         23  
355              
356             =head2 I<$class>->new( $list )
357              
358             Return an array object.
359              
360             =cut
361             sub new {
362 4     4   8 my ($class, $list) = @_;
363 4 100       10 $list = [] if not defined $list;
364 4         6 return bless \@{$list}, $class;
  4         18  
365             }
366              
367             =head2 I<$array>->in( $var )
368              
369             Return true if var is in this array.
370              
371             =cut
372             sub in {
373 3     3   4 my ($self, $var) = @_;
374 3         5 for my $i (@{$self}) {
  3         6  
375 5 100       18 return 1 if $i eq $var;
376             }
377 1         3 return 0;
378             }
379              
380             =head1 OVERLOADED
381              
382             All the kinds of overloading this object has on it.
383              
384             =cut
385 14     14   15 sub count { scalar(@{ $_[0] }) }
  14         256  
386              
387             =head1 AUTHOR
388              
389             Martin Owens - Copyright 2007, AGPL
390              
391             =cut
392             1;