File Coverage

blib/lib/CAM/Template.pm
Criterion Covered Total %
statement 171 214 79.9
branch 53 88 60.2
condition 22 41 53.6
subroutine 18 21 85.7
pod 15 15 100.0
total 279 379 73.6


line stmt bran cond sub pod time code
1             package CAM::Template;
2              
3             =head1 NAME
4              
5             CAM::Template - Clotho-style search/replace HTML templates
6              
7             =head1 LICENSE
8              
9             Copyright 2005 Clotho Advanced Media, Inc.,
10              
11             This library is free software; you can redistribute it and/or modify it
12             under the same terms as Perl itself.
13              
14             =head1 SYNOPSIS
15              
16             use CAM::Template;
17             my $tmpl = new CAM::Template($tmpldir . "/main_tmpl.html");
18             $tmpl->addParams(url => "http://foo.com/",
19             date => localtime(),
20             name => "Carol");
21             $tmpl->addParams(\%more_params);
22             $tmpl->setLoop("birthdaylist", name => "Eileen", date => "Sep 12");
23             $tmpl->addLoop("birthdaylist", name => "Chris", date => "Oct 13");
24             $tmpl->addLoop("birthdaylist", [{name => "Dan", date => "Feb 12"},
25             {name => "Scott", date => "Sep 24"}]);
26             print "Content-Type: text/html\n\n";
27             $tmpl->print();
28              
29             =head1 DESCRIPTION
30              
31             This package is intended to replace Clotho's traditional ::PARAM::
32             syntax with an object-oriented API. This syntax is overrideable by
33             subclasses. See the last section of this documentation for an
34             explanation of the default syntax.
35              
36             We recommend that you DO NOT use this module unless you have a good
37             reason. The other CPAN templating modules, like HTML::Template and
38             Template::Toolkit) are better maintained than this one. See
39             L
40             for an excellent discussion of the various templating approaches.
41              
42             So why does this module exist? Legacy, mostly. A ton of HTML was
43             written in this templating language. So, we keep this module in good
44             condition. Additionally, we believe it's unique in the Perl community
45             in that it has a reconfigurable template syntax. That's worth a
46             little, we think.
47              
48             =cut
49              
50             #==============================
51              
52             require 5.005_62;
53 1     1   24427 use strict;
  1         3  
  1         38  
54 1     1   6 use warnings;
  1         2  
  1         29  
55 1     1   5 use Carp;
  1         3  
  1         3051  
56              
57             our @ISA = qw();
58             our $VERSION = '0.93';
59              
60             ## Global package settings
61              
62             our $global_include_files = 1;
63              
64             # Flags for the in-memory file cache
65             our $global_use_cache = 1;
66             our %global_filecache = ();
67              
68             #==============================
69              
70             =head1 FUNCTIONS
71              
72             =over 4
73              
74             =cut
75              
76             #==============================
77              
78             =item patterns
79              
80             This class method returns a series of regular expressions used for
81             template searching and replacing. Modules which subclass
82             CAM::Template can override this method to implement a different
83             template syntax.
84              
85             Example of the recommended way to write an override function for this
86             method in a subclass:
87              
88             sub patterns {
89             my $pkg = shift;
90             return {
91             $pkg->SUPER::patterns(),
92             # include syntax:
93             include => qr//,
94             };
95             }
96              
97             =cut
98              
99             sub patterns
100             {
101 1002     1002 1 1558 my $pkg = shift;
102              
103             return {
104             # $1 is the loop name
105 1002         31866 loopstart => qr//i,
106             loopend => qr/<\/cam_loop>/i,
107              
108             # a string that looks like one of the "vars" below for
109             # substituting the loop variable. This will be used in:
110             # $template =~ s/loop-pattern/loop_out-pattern/;
111             loop_out => '::$1::',
112              
113             # DEPRECATED
114             # $1 is the variable name, $2 is the conditional body
115             #if => qr/\?\?([\w\-]+?)\?\?(.*?)\?\?\1\?\?/s,
116             #unless => qr/\?\?!([\w\-]+?)\?\?(.*?)\?\?!\1\?\?/s,
117              
118             # $1 is a boolean flag, $2 is the variable name,
119             # $3 is the conditional body
120             ifunless => qr/\?\?(!?)([\w\-]+?)\?\?(.*?)\?\?\1\2\?\?/s,
121             ifunless_test => qr/^!$/s,
122              
123              
124             # $1 is the variable name
125             vars => [
126             qr//s,
127             qr/::([\w\-]+?)::/s,
128             qr/;;([\w\-]+?);;/s,
129             ],
130            
131             # $1 is the variable name, $2 is the value to set it to
132             staticvars => qr/::([\w\-]+)==(.{0,80}?)::/,
133              
134             # $1 is the subtemplate filename
135             include => qr//,
136             };
137             }
138             #==============================
139              
140             =item new
141              
142             =item new FILENAME
143              
144             =item new FILENAME, PARAMS
145              
146             Create a new template object. You can specify the template filename and
147             the replacement dictionary right away, or do it later via methods.
148              
149             =cut
150              
151             sub new
152             {
153 1002     1002 1 10441 my $pkg = shift;
154              
155 1002         5134 my $self = bless({
156             content => undef,
157             params => {},
158             use_cache => $global_use_cache,
159             include_files => $global_include_files,
160             patterns => $pkg->patterns(),
161             isloop => 0,
162             }, $pkg);
163              
164 1002 50 66     4419 if (@_ > 0 && !$self->setFilename(shift))
165             {
166 0         0 return undef;
167             }
168 1002 50 33     2641 if (@_ > 0 && !$self->addParams(@_))
169             {
170 0         0 return undef;
171             }
172              
173 1002         3229 return $self;
174             }
175             #==============================
176              
177             =item setFileCache 0|1
178              
179             Indicate whether the template file should be cached in memory.
180             Defaults to 1 (aka true). This can be used either on an object or
181             globally:
182              
183             my $tmpl = new CAM::Template();
184             $tmpl->setFileCache(0);
185             or
186             CAM::Template->setFileCache(0);
187              
188             The global value only affects future template objects, not existing
189             ones.
190              
191             =cut
192              
193             sub setFileCache
194             {
195 1002     1002 1 10730 my $self = shift;
196 1002         15033 my $bool = shift;
197              
198 1002 50       2535 if (ref($self))
199             {
200 1002         1903 $self->{use_cache} = $bool;
201 1002         2345 return $self;
202             }
203             else
204             {
205 0         0 $global_use_cache = $bool;
206 0         0 return 1;
207             }
208             }
209             #==============================
210              
211             =item setIncludeFiles 0|1
212              
213             Indicate whether the template file should be able to include other template files automatically via the
214              
215            
216              
217             directive. Defaults to 1 (aka true). Note that this is recursive, so
218             don't have a file include itself! This method can be used either on
219             an object or globally:
220              
221             my $tmpl = new CAM::Template();
222             $tmpl->setIncludeFiles(0);
223             or
224             CAM::Template->setIncludeFiles(0);
225              
226             The global value only affects future template objects, not existing
227             ones.
228              
229             =cut
230              
231             sub setIncludeFiles
232             {
233 0     0 1 0 my $self = shift;
234 0         0 my $bool = shift;
235              
236 0 0       0 if (ref($self))
237             {
238 0         0 $self->{include_files} = $bool;
239 0         0 return $self;
240             }
241             else
242             {
243 0         0 $global_include_files = $bool;
244 0         0 return 1;
245             }
246             }
247             #==============================
248              
249             =item setFilename FILENAME
250              
251             Specify the template file to be used. Returns false if the file does
252             not exist or the object if it does. This loads and preparses the file.
253              
254             =cut
255              
256             sub setFilename
257             {
258 1005     1005 1 13340 my $self = shift;
259 1005         1965 my $filename = shift;
260              
261             # Validate input
262 1005 50 33     33778 if ((! $filename) || (! -r $filename))
263             {
264 0         0 &carp("File '$filename' cannot be read");
265 0         0 return undef;
266             }
267 1005         4820 $self->{content} = $self->_fetchfile($filename);
268 1005         2801 $self->{filename} = $filename;
269 1005         4143 $self->_preparse();
270 1005         3861 return $self;
271             }
272             #==============================
273              
274             =item setString STRING
275              
276             Specify template content to be used. Use this instead of setFilename if
277             you already have the contents in memory. This preparses the string.
278              
279             =cut
280              
281             sub setString
282             {
283 3     3 1 3679 my $self = shift;
284 3         27 $self->{content} = {
285             string => shift,
286             studied => 0,
287             skip => {},
288             };
289 3         21 delete $self->{filename};
290 3         17 $self->_preparse();
291 3         9 return $self;
292             }
293             #==============================
294              
295             =item loopClass
296              
297             Template loops (i.e. C) usually instantiate new template
298             objects to populate the loop body. In general, we want the new
299             instance to be the same class as the main template object. However,
300             in some subclasses of CAM::Template, this is a bad thing (for example
301             PDF templates with loops in their individual pages).
302              
303             In the latter case, the subclass should override this method with
304             something like the following:
305              
306             sub loopClass { "CAM::Template" }
307              
308             =cut
309              
310             sub loopClass
311             {
312 1     1 1 2 my $pkg_or_self = shift;
313              
314 1   33     8 return ref($pkg_or_self) || $pkg_or_self;
315             }
316             #==============================
317              
318             =item addLoop LOOPNAME, HASHREF | KEY => VALUE, ...
319              
320             =item addLoop LOOPNAME, ARRAYREF
321              
322             Add to an iterating portion of the page. This extracts the
323             from the template, fills it with the specified parameters (and any
324             previously specified with setParams() or addParams()), and appends to
325             the LOOPNAME parameter in the params list.
326              
327             If the ARRAYREF form of the method is used, it behaves as if you had done:
328              
329             foreach my $row (@$ARRAYREF) {
330             $tmpl->addLoop($LOOPNAME, $row);
331             }
332              
333             so, the elements of the ARRAYREF are hashrefs representing a series of
334             rows to be added.
335              
336             =cut
337              
338             sub addLoop
339             {
340 7     7 1 29 my $self = shift;
341 7         10 my $loopname = shift;
342             # additional params are collected below
343              
344 7 50       31 return undef if (!$self->{content});
345 7 50       22 return undef if (!defined $self->{content}->{loops}->{$loopname});
346              
347 7   33     310 while (@_ > 0 && $_[0] && ref($_[0]) && ref($_[0]) eq "ARRAY")
      66        
      100        
348             {
349 1         2 my $looparray = shift;
350 1         3 foreach my $loop (@$looparray)
351             {
352 3 50       10 if (!$self->addLoop($loopname, $loop))
353             {
354 0         0 return undef;
355             }
356             }
357             # If we run out of arrayrefs, quit
358 1 50       5 if (@_ == 0)
359             {
360 1         3 return $self;
361             }
362             }
363              
364 6         14 my $looptemplate = $self->{content}->{loop_cache}->{$loopname};
365 6 100       13 if (!$looptemplate)
366             {
367 1         4 $self->{content}->{loop_cache}->{$loopname} =
368             $looptemplate = $self->loopClass()->new();
369 1         8 $looptemplate->{content} = {
370 1         2 skip => {%{$self->{content}->{skip}}},
371             string => $self->{content}->{loops}->{$loopname},
372             staticparams => $self->{content}->{staticparams},
373             };
374 1 50       4 $looptemplate->study() if ($self->{content}->{studied});
375             }
376 6         8 $looptemplate->setParams(\%{$self->{params}}, $loopname => "", @_);
  6         19  
377 6         22 $self->{params}->{$loopname} .= $looptemplate->toString();
378 6         18 return $self;
379             }
380             #==============================
381              
382             =item clearLoop LOOPNAME
383              
384             Blank the contents of the loop accumlator. This is really only useful
385             for nested loops. For example:
386              
387             foreach my $state (@states) {
388             $template->clearLoop("cities");
389             foreach my $city (@{$state->{cities}}) {
390             $template->addLoop("cities",
391             city => $city->{name},
392             pop => $city->{population});
393             }
394             $template->addLoop("state", state => $state->{name});
395             }
396              
397             =cut
398              
399             sub clearLoop
400             {
401 0     0 1 0 my $self = shift;
402 0         0 my $loopname = shift;
403              
404 0         0 $self->{params}->{$loopname} = "";
405 0         0 return $self;
406             }
407             #==============================
408              
409             =item setLoop LOOPNAME, HASHREF | KEY => VALUE, ...
410              
411             Exactly like addLoop above, except it clears the loop first. This is
412             useful for the first element of a nested loop.
413              
414             =cut
415              
416             sub setLoop
417             {
418 0     0 1 0 my $self = shift;
419 0         0 my $loopname = shift;
420              
421 0         0 $self->clearLoop($loopname);
422 0         0 return $self->addLoop($loopname, @_);
423             }
424             #==============================
425              
426             =item study
427              
428             Takes a moment to analyze the template to see if any time can be
429             gained by skipping unused portions of the replacement syntax. This is
430             obviously more useful for templates that are replaced often, like
431             loops.
432              
433             Implementation note as of v0.77: In practice this rarely helps except
434             on large, simplistic templates. Hopefully this will improve in the
435             future.
436              
437             =cut
438              
439             sub study
440             {
441 2     2 1 14 my $self = shift;
442            
443 2 50       11 return undef if (!$self->{content});
444 2 50       11 return undef if (!defined $self->{content}->{string});
445             #study $self->{content}->{string};
446 2         5 my $re_hash = $self->{patterns};
447 2         5 my $content = $self->{content};
448 2         6 foreach my $key ("if", "unless", "ifunless")
449             {
450 6 100       21 next if (!$re_hash->{$key});
451 2 50       8 next if ($content->{skip}->{$key}); # for loops
452 2 100       50 if ($content->{string} !~ /$$re_hash{$key}/)
453             {
454 1         6 $content->{skip}->{$key} = 1;
455             }
456             }
457              
458 2         5 my $i = 0;
459 2         5 foreach my $re (@{$re_hash->{vars}})
  2         8  
460             {
461 6         16 my $key = "vars".++$i;
462 6 50       18 next if ($content->{skip}->{$key}); # for loops
463 6 100       72 if ($content->{string} !~ /$re/)
464             {
465 3         14 $content->{skip}->{$key} = 1;
466             }
467             }
468              
469 2 100 33     27 $content->{skip}->{cond} = 1 if (($content->{skip}->{if} &&
      66        
470             $content->{skip}->{unless}) ||
471             $content->{skip}->{ifunless});
472 2         4 $content->{studied} = 1;
473 2         7 return $self;
474             }
475             #==============================
476              
477             =item addParams [HASHREF | KEY => VALUE], ...
478              
479             Specify the search/replace dictionary for the template. The arguments
480             can either be key value pairs, or hash references (it is permitted to
481             mix the two as of v0.71 of this library). For example:
482              
483             my %hash = (name => "chris", age => 30);
484             $tmpl1->addParams(%hash);
485            
486             my $hashref = \%hash;
487             $tmpl2->addParams($hashref);
488              
489             Returns false if the hash has an uneven number of arguments, or the
490             argument is not a hash reference. Returns the object otherwise.
491              
492             Note: this I to the parameter list. To replace the list, use
493             the setParams method instead.
494              
495             =cut
496              
497             sub addParams
498             {
499 8     8 1 15 my $self = shift;
500             # additional arguments processed below
501              
502              
503             # store everything up in a temp hash so we can detect errors and
504             # quit before applying these params to the object.
505 8         11 my %params = ();
506              
507 8         20 while (@_ > 0)
508             {
509 22 50       62 if (!defined $_[0])
    100          
    50          
510             {
511 0         0 &carp("Undefined key in the parameter list");
512 0         0 return undef;
513             }
514             elsif (ref($_[0]))
515             {
516 10         11 my $ref = shift;
517 10 50       96 if (ref($ref) =~ /^(?:SCALAR|ARRAY|CODE)$/)
518             {
519 0         0 &carp("Parameter list has a reference that is not a hash reference");
520 0         0 return undef;
521             }
522 10         74 %params = (%params, %$ref);
523             }
524             elsif (@_ == 1)
525             {
526 0         0 &carp("Uneven number of arguments in key/value pair list");
527 0         0 return undef;
528             }
529             else
530             {
531             # get a key value pair
532 12         15 my $key = shift;
533 12         38 $params{$key} = shift;
534             }
535             }
536              
537 8         22 foreach my $key (keys %params)
538             {
539 47         93 $self->{params}->{$key} = $params{$key};
540             }
541 8         29 return $self;
542             }
543             #==============================
544              
545             =item setParams HASHREF | KEY => VALUE, ...
546              
547             Exactly like addParams above, except it clears the parameter list first.
548              
549             =cut
550              
551             sub setParams
552             {
553 7     7 1 14 my $self = shift;
554            
555 7         10 $self->{params} = {};
556 7         25 return $self->addParams(@_);
557             }
558             #==============================
559              
560             # PRIVATE FUNCTION
561             sub _preparse
562             {
563 1008     1008   2002 my $self = shift;
564              
565 1008         2669 my $content = $self->{content};
566 1008 100       4501 return $self if ($content->{parsed});
567              
568 507         1284 $content->{skip} = {};
569 507         1149 $content->{studied} = 0;
570 507         1276 $content->{loops} = {};
571 507         1190 $content->{loop_cache} = {};
572 507         2509 $content->{staticparams} = {};
573              
574             # Retrieve constant parameters set in the template files
575 507         2436 my $static_re = $self->{patterns}->{staticvars};
576 507         8842 $content->{string} =~ s/$static_re/$$content{staticparams}{$1}=$2; ""/ge;
  505         2619  
  505         21753  
577              
578             # Break up all loops
579 507         1877 my $re1 = $self->{patterns}->{loopstart};
580 507         1041 my $re2 = $self->{patterns}->{loopend};
581 507         3278 my ($start,$end) = split /\$1/, $self->{patterns}->{loop_out}, 2;
582 507         41263 my @parts = split /$re1/, $content->{string};
583 507         4444 while (@parts > 2) {
584 505         1570 my $tail = pop @parts;
585 505         2923 my $name = pop @parts;
586 505 50       7988 if ($tail =~ s/^(.*?)$re2/$start$name$end/s)
587             {
588 505         4015 $content->{loops}->{$name} = $1;
589             }
590             else
591             {
592 0         0 warn "Found loop start for '$name' but no loop end";
593             }
594 505         9758 $parts[$#parts] .= $tail;
595             }
596 507         1231 $content->{string} = $parts[0];
597 507         1668 $content->{parsed} = 1;
598              
599 507         1453 return $self;
600             }
601             #==============================
602              
603             # PRIVATE FUNCTION
604             sub _fetchfile
605             {
606 1005     1005   1631 my $self = shift;
607 1005         1835 my $filename = shift;
608              
609 1005         1183 my $cache;
610 1005 100       3511 if ($self->{use_cache})
611             {
612 502         1147 my $pkg = ref($self);
613 502   100     1236 $global_filecache{$pkg} ||= {};
614 502         956 $cache = $global_filecache{$pkg};
615             }
616            
617 1005 100 100     12173 if ($self->{use_cache} && exists $cache->{$filename} &&
      66        
618             $cache->{$filename}->{time} >= (stat($filename))[9])
619             {
620 501         1571 return $cache->{$filename};
621             }
622             else
623             {
624 504         2222 my $struct = {
625             studied => 0,
626             skip => {},
627             };
628 504         1288 local *FILE;
629 504 50       35590 if (!open(FILE, $filename))
630             {
631 0         0 &carp("Failed to open file '$filename': $!");
632 0         0 return undef;
633             }
634 504         4025 local $/ = undef;
635 504         23064 $struct->{string} = ;
636 504         24507 close(FILE);
637              
638 504 50       3434 if ($self->{include_files})
639             {
640             # Recursively add included files -- must be in the same directory
641 504         878 my $dir = $filename;
642 504         18076 $dir =~ s,/[^/]+$,,; # remove filename
643 504 50       23377 $dir .= "/" if ($dir =~ /[^\/]$/);
644 504         1305 my $re = $self->{patterns}->{include};
645 504         6565 $struct->{string} =~ s/$re/ $self->_fetchfile("$dir$1")->{string} /ge;
  0         0  
646             }
647              
648 504 100       2134 if ($self->{use_cache})
649             {
650 1         20 $struct->{time} = (stat($filename))[9];
651 1         4 $cache->{$filename} = $struct;
652             }
653 504         5586 return $struct;
654             }
655             }
656              
657             #==============================
658              
659             =item toString
660              
661             Executes the search/replace and returns the content.
662              
663             =cut
664              
665             sub toString
666             {
667 3010     3010 1 17355 my $self = shift;
668              
669 3010 50       9313 return "" unless ($self->{content});
670 3010         7387 my $content = $self->{content}->{string};
671 3010 50       7303 return "" unless (defined $content);
672              
673 3010         5057 my $re_hash = $self->{patterns};
674 3010         5553 my $skip = $self->{content}->{skip};
675             {
676             # Turn off warnings, since it is likely that some parameters
677             # will be undefined
678 1     1   14 no warnings;
  1         2  
  1         532  
  3010         4131  
679              
680             # incoming params can override template params
681 3010         9252 my %params = (
682             "__filename__" => $self->{filename},
683 3010         18863 %{$self->{content}->{staticparams}},
684 3010         4838 %{$self->{params}},
685             );
686              
687 3010 100       11460 unless ($skip->{cond})
688             {
689             # Do the following multiple times to handle nested conditionals
690              
691 2510 50 33     13817 if ($re_hash->{if} && $re_hash->{unless}) # legacy subclassing
692             {
693              
694 0         0 &carp("DEPRECATED: please use 'ifunless' instead of 'if' and 'unless'\n" .
695             "in your patterns. There was a subtle bug in the old way, and\n" .
696             "the new way is too slow with 'if' and 'unless'\n");
697              
698 0         0 my $pos = 1;
699 0         0 my $neg = 1;
700 0   0     0 do {
701 0 0       0 if ($neg)
702             {
703 0 0       0 $neg = ($content =~ s/$$re_hash{unless}/(!$params{$1}) ? $2 : ''/ge);
  0         0  
704             }
705 0 0       0 if ($pos)
706             {
707 0 0       0 $pos = ($content =~ s/$$re_hash{if}/$params{$1} ? $2 : ''/ge);
  0         0  
708             }
709             } while ($neg || $pos);
710             }
711             else
712             {
713 2510         51693 do {} while ($content =~ s/$$re_hash{ifunless}/
714 13032         55905 my($bool,$var,$body)=($1,$2,$3);
715 13032 100       334546 ($bool =~ m,$$re_hash{ifunless_test}, ? !$params{$var} : $params{$var}) ? $body : ''
    100          
716             /gse);
717             }
718             }
719              
720 3010         6636 my $i = 0;
721 3010         17714 foreach my $re (@{$re_hash->{vars}})
  3010         34045  
722             {
723 9030 100       34817 next if ($skip->{"vars".++$i});
724 7530         290196 $content =~ s/$re/$params{$1}/g;
725             }
726             }
727              
728 3010         42776 return $content;
729             }
730              
731             #==============================
732              
733             =item print
734              
735             =item print FILEHANDLE
736              
737             Sends the replaced content to the currently selected output (usually
738             STDOUT) or the supplied filehandle.
739              
740             =cut
741              
742             sub print
743             {
744 1     1 1 42718 my $self = shift;
745 1         4 my $filehandle = shift;
746              
747 1         5 my $content = $self->toString();
748 1 50       8 return undef if (!defined $content);
749              
750 1 50       8 if ($filehandle)
751             {
752 1         18 print $filehandle $content;
753             }
754             else
755             {
756 0         0 print $content;
757             }
758 1         5 return $self;
759             }
760             #==============================
761              
762             1;
763             __END__