File Coverage

blib/lib/dTemplate.pm
Criterion Covered Total %
statement 87 154 56.4
branch 16 46 34.7
condition 9 26 34.6
subroutine 17 33 51.5
pod 1 6 16.6
total 130 265 49.0


line stmt bran cond sub pod time code
1             #
2             # $Id: dTemplate.pm 132 2006-10-21 15:52:44Z dlux $
3             #
4             # $URL: http://svn.dlux.hu/public/dTemplate/trunk/dTemplate.pm $
5             #
6              
7             package dTemplate;
8 1     1   17666 use strict;
  1         2  
  1         42  
9 1     1   6 use DynaLoader;
  1         2  
  1         45  
10 1         4488 use vars qw($VERSION @ISA %ENCODERS $ENCODERS %parse
11             $START_DELIMITER $END_DELIMITER $ENCODER_SEP $PRINTF_SEP $VAR_PATH_SEP
12 1     1   6 $ENCODER_PARAM_START $ENCODER_PARAM_END);
  1         2  
13              
14             @ISA = qw(DynaLoader);
15              
16             $VERSION = '2.5';
17             $START_DELIMITER = '\$';
18             $END_DELIMITER = '\$';
19             $VAR_PATH_SEP = '\.';
20             $ENCODER_SEP = '\*+';
21             $ENCODER_PARAM_START = '\/';
22             $ENCODER_PARAM_END = '';
23             $PRINTF_SEP = '%+';
24              
25             dTemplate->bootstrap($VERSION);
26              
27             # Constructors ...
28              
29             sub new {
30 14     14 0 406 my $obj = shift;
31 14 50       37 my $type = shift or die "Invalid constructor call. Use: new dTemplate type => ...";
32 14 50 0     32 return ((ref $obj || $obj)."::Template")->new(@_) if $type eq "file";
33 14 50 0     69 return ((ref $obj || $obj)."::Choose")->new(@_) if $type eq "choose" || $type eq "select";
      33        
34 14 50 33     98 return ((ref $obj || $obj)."::Template")->new_raw(@_) if $type eq "text";
35 0         0 die "Invalid type in dTemplate constructor: $type";
36             }
37              
38 0     0 0 0 sub define { shift->new(file => @_) }
39 0     0 0 0 sub choose { shift->new(choose => @_) }
40 0     0 0 0 sub select { shift->new(choose => @_) }
41 9     9 1 589 sub text { shift->new(text => @_) }
42              
43             sub encode {
44 0     0 0 0 my $encoder = shift;
45 0         0 return $ENCODERS{$encoder}->(shift());
46             };
47              
48             $ENCODERS{''} = sub { shift() };
49              
50             $ENCODERS{u} = sub {
51             require URI::Escape; # autoload URI::Escape module
52             $ENCODERS{u} = sub {
53             URI::Escape::uri_escape(defined $_[0] ? $_[0] : "","^a-zA-Z0-9_.!~*'()");
54             };
55             $ENCODERS{u}->(shift);
56             };
57              
58             $ENCODERS{h} = sub {
59             require HTML::Entities; # autoload HTML::Entities module
60             $ENCODERS{h}=sub {
61             HTML::Entities::encode(defined $_[0] ? $_[0] : "","^\n\t !\#\$%-;=?-~") ;
62             };
63             $ENCODERS{h}->(shift);
64             };
65              
66             $ENCODERS{uc} = sub { uc($_[0]) };
67              
68             $ENCODERS{lc} = sub { lc($_[0]) };
69              
70             $ENCODERS{ha} = sub { # Advanced html encoding: \n =>
, tabs => spaces
71             my $e=$ENCODERS->{'h'}->($_[0]);
72             $e =~ s/\n/
/g;
73             $e =~ s/\t/   /go;
74             $e;
75             };
76              
77             $ENCODERS{eq} = sub { # equality check
78             return $_[0] eq $_[1];
79             };
80              
81             $ENCODERS{if} = sub { # returns the second parameter if the first is true
82             return $_[1] if $_[0];
83             };
84              
85             $ENCODERS{printf} = sub { # returns the printf-formatted output
86             return sprintf("%".$_[1], $_[0]);
87             };
88              
89             $ENCODERS=\%ENCODERS; # for compatibility of older versions
90              
91             package dTemplate::Template;
92 1     1   13 use strict;
  1         2  
  1         140  
93 1     1   7 use vars qw(%ENCODERS $ENCODERS);
  1         1  
  1         64  
94 1     1   1138 use locale;
  1         263  
  1         6  
95              
96             sub spf {
97 2     2   88 my $format = shift;
98 2         19 return sprintf $format,@_;
99             }
100              
101             *ENCODERS = *dTemplate::ENCODERS;
102              
103 0     0   0 sub FILENAME { 0 };
104 42     42   621 sub TEXT { 1 };
105 43     43   111 sub COMPILED { 2 };
106 8     8   43 sub PARSEHASH { 3 };
107              
108             # use this constant to determinene the last field for subclassing dTemplate
109 0     0   0 sub LAST_FIELD { PARSEHASH };
110              
111 0     0   0 sub new { my ($class,$filename)=@_;
112 0 0       0 return undef if ! -r $filename;
113 0         0 my $s=[$filename];
114 0         0 bless ($s,$class);
115             };
116              
117 14     14   17 sub new_raw { my $class=shift;
118 14         25 my $txt=shift;
119 14 50       41 my $s=[undef, (ref($txt) ? $txt : \$txt)];
120 14         66 bless ($s,$class);
121             };
122              
123 0     0   0 sub style { return undef };
124              
125 14     14   178 sub compile { my $s=shift;
126 14 50       33 return if $s->[COMPILED];
127 14         30 $s->load_file;
128              
129             # template parsing
130              
131 14         16 my %varhash;
132 14         27 my @comp=({});
133 14         18 ${ $s->[TEXT] } =~ s{ (.*?) (
  14         23  
134             (?:$dTemplate::START_DELIMITER) ( (?:\w|(?:$dTemplate::VAR_PATH_SEP))* )
135             ( (?:$dTemplate::PRINTF_SEP) (.*?[\w]) )?
136             ( (?:$dTemplate::ENCODER_SEP) (.*?) )?
137             (?:$dTemplate::END_DELIMITER) | $
138             ) }{
139 36         204 my ($pre,$full_matched,$varname,$full_format,$format,
140             $full_encoding,$encoding) = ($1,$2,$3,$4,$5,$6,$7);
141 36   50     79 my $clast = $comp[-1] ||= {};
142 36 50       63 if ($full_matched eq '$$') { # $$ sign
143 0         0 $clast->{text} .= $pre.'$';
144             } else {
145 36         77 $clast->{text} .= $pre;
146 36 100       70 if ($varname) {
147 17         37 $clast->{full_matched} = $full_matched;
148 17         78 my (@varp) = split (/$dTemplate::VAR_PATH_SEP/,
149             $varname);
150 17         31 my $varn = $varp[0];
151 17         30 $clast->{varn} = $varn;
152 17         34 $varhash{$varn}++;
153 17         29 $clast->{varp} = \@varp;
154 17 100       42 $clast->{format} = defined $format ? "%".$format : "";
155 17         32 $clast->{encoding}=$encoding;
156 17         35 push @comp,{};
157             };
158             };
159 36         263 "";
160             }gxse;
161              
162             # assigning ID-s for variables
163              
164 1 0       8 my @variables = sort {
165 14         52 $varhash{$b} <=> $varhash{$a} || length($a) <=> length($b)
166             } keys %varhash;
167 14         23 my %varids;
168 14         34 for (my $i=0; $i<@variables; $i++) {
169 15         50 $varids{$variables[$i]} = $i;
170             }
171              
172             # settings up the compiled scalar:
173             # variable parameter hash + inverted index
174              
175 14         21 my ($var_list, $var_index) = ("","");
176 14         19 foreach my $varname (@variables) {
177 15         19 my $varlen = length($varname);
178 15 100       37 my $addspc = $varlen >= 4 ? 0 : 4 - $varlen;
179 15         32 my $var_list_add = " ".$varname.(" " x $addspc);
180 15         18 $var_list .= $var_list_add;
181 15         24 my $var_index_add = "\0" x length($var_list_add);
182 15         40 substr($var_index_add,0,4) = pack("l", $varids{$varname});
183 15         40 $var_index .= $var_index_add;
184             }
185 14         38 my $compiled = pack("l",scalar(@variables)). $var_list. " \0". $var_index."";
186              
187             # chunks
188              
189 14         23 foreach my $chunk (@comp) {
190 31         74 $compiled .= pack("l", length($chunk->{text})).$chunk->{text};
191 31 100       59 if ($chunk->{full_matched}) {
192 26         167 $compiled .= $chunk->{full_matched}."\0". # full matched string
193             pack("l",$varids{ $chunk->{varn} }). # variable ID
194 17         34 join("",map { $_."\0" } @{ $chunk->{varp}})."\0".
  32         98  
195             # variable path in hash
196 16         197 join("",map { $_."\0" } # encoding:
197 17   100     109 map { /^(.*?) # encoder name
198             (?:
199             (?:$dTemplate::ENCODER_PARAM_START)
200             (.*) # encoder parameter
201             (?:$dTemplate::ENCODER_PARAM_END)
202             )?$/x }
203             (split(/$dTemplate::ENCODER_SEP/,
204             $chunk->{encoding} || ""))
205             )."\0". # encoding
206             $chunk->{format}."\0"
207             } else {
208 14         33 $compiled .= "\0";
209             }
210             }
211              
212 14         28 $s->[COMPILED] = $compiled;
213 14         22 $s->[TEXT]=undef; # free up some memory
214             };
215              
216 14     14   15 sub load_file { my $s=shift;
217 14 50 33     47 return if $s->[COMPILED] || defined $s->[TEXT] || !defined $s->[FILENAME];
      33        
218 0 0       0 if (!open(FILE,$s->[FILENAME])) {
219 0         0 warn "Cannot load template file: ".$s->[FILENAME];
220 0         0 $s->[TEXT]=\"";
221 0         0 close (FILE);
222 0         0 return;
223             };
224 0         0 local $/=undef;
225 0         0 my $text=;
226 0         0 $s->[TEXT]=\$text;
227 0         0 close (FILE);
228             };
229              
230 8   100 8   230 sub parsehash: lvalue { shift->[PARSEHASH] ||= {} }
231              
232             package dTemplate::Choose;
233 1     1   4192 use strict;
  1         5  
  1         916  
234              
235 0     0     sub style_hash { 0 };
236 0     0     sub styles { 1 };
237              
238 0     0     sub new { my $class=shift;
239 0           my $s=[shift,{}];
240 0           bless($s,$class);
241 0           $s->add(@_);
242 0           $s;
243             };
244              
245 0     0     sub add { my $s=shift;
246 0           while (@_) {
247 0           my $a=shift;
248 0           my $b=shift;
249 0 0         $s->define_style($s->[styles], ref($b) ? $b : \$b ,sort split(/\+/,$a));
250             };
251 0           $s;
252             };
253              
254 0     0     sub define_style { my ($s,$root,$template,@path)=@_;
255 0 0         if (@path) {
256 0           my $i=shift @path;
257 0   0       $root->{$i}||={};
258 0           $s->define_style($root->{$i},$template,@path);
259             } else {
260 0           $root->{''}=$template;
261             };
262             };
263              
264 0     0     sub parse { my $s=shift;
265 0           my $template=$s->get_template;
266 0 0         return defined $template ? $template->parse(@_) : undef;
267             };
268              
269 0 0   0     sub style { my $s=shift; @_ ? $s->[style_hash]=$_[0] : $s->[style_hash] };
  0            
270              
271 0     0     sub get_template { my ($s)=@_;
272 0 0         return undef if !$s->[styles];
273 0           my @walk=([ $s->[styles] ]);
274 0           my @svals = sort (grep ( { $_ } values %{ $s->[style_hash] } ));
  0            
  0            
275             # Finds the best-matching template
276 0           foreach my $i (@svals) {
277 0           for (my $depth=$#walk; $depth>=0; $depth--) {
278 0           foreach my $act (@{$walk[$depth]}) {
  0            
279 0 0         push @{ $walk[$depth+1] }, $act->{$i}
  0            
280             if exists $act->{$i};
281             };
282             };
283             };
284 0           my $retval;
285             FINDTEMPLATE:
286 0           for (my $depth=$#walk; $depth>=0; $depth--) {
287 0           foreach my $act (@{$walk[$depth]}) {
  0            
288 0 0         if (exists $act->{''}) {
289 0           $retval=$act->{''};
290 0           last FINDTEMPLATE;
291             };
292             };
293             };
294 0 0         return ref($retval) eq 'SCALAR' ? $$retval : $retval;
295             };
296              
297             1;
298