File Coverage

lib/Template/Filters.pm
Criterion Covered Total %
statement 163 198 82.3
branch 52 86 60.4
condition 25 37 67.5
subroutine 37 40 92.5
pod 3 22 13.6
total 280 383 73.1


line stmt bran cond sub pod time code
1             #============================================================= -*-Perl-*-
2             #
3             # Template::Filters
4             #
5             # DESCRIPTION
6             # Defines filter plugins as used by the FILTER directive.
7             #
8             # AUTHORS
9             # Andy Wardley , with a number of filters contributed
10             # by Leslie Michael Orchard
11             #
12             # COPYRIGHT
13             # Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved.
14             #
15             # This module is free software; you can redistribute it and/or
16             # modify it under the same terms as Perl itself.
17             #
18             #============================================================================
19              
20             package Template::Filters;
21              
22 80     80   2535 use strict;
  80         174  
  80         4665  
23 80     80   418 use warnings;
  80         151  
  80         2102  
24 80     80   75565 use locale;
  80         20365  
  80         437  
25 80     80   3629 use base 'Template::Base';
  80         205  
  80         8218  
26 80     80   520 use Template::Constants;
  80         171  
  80         3339  
27 80     80   629 use Scalar::Util 'blessed';
  80         142  
  80         340240  
28              
29             our $VERSION = 2.87;
30             our $AVAILABLE = { };
31             our $TRUNCATE_LENGTH = 32;
32             our $TRUNCATE_ADDON = '...';
33              
34              
35             #------------------------------------------------------------------------
36             # standard filters, defined in one of the following forms:
37             # name => \&static_filter
38             # name => [ \&subref, $is_dynamic ]
39             # If the $is_dynamic flag is set then the sub-routine reference
40             # is called to create a new filter each time it is requested; if
41             # not set, then it is a single, static sub-routine which is returned
42             # for every filter request for that name.
43             #------------------------------------------------------------------------
44              
45             our $FILTERS = {
46             # static filters
47             'html' => \&html_filter,
48             'html_para' => \&html_paragraph,
49             'html_break' => \&html_para_break,
50             'html_para_break' => \&html_para_break,
51             'html_line_break' => \&html_line_break,
52             'xml' => \&xml_filter,
53             'uri' => \&uri_filter,
54             'url' => \&url_filter,
55             'upper' => sub { uc $_[0] },
56             'lower' => sub { lc $_[0] },
57             'ucfirst' => sub { ucfirst $_[0] },
58             'lcfirst' => sub { lcfirst $_[0] },
59             'stderr' => sub { print STDERR @_; return '' },
60             'trim' => sub { for ($_[0]) { s/^\s+//; s/\s+$// }; $_[0] },
61             'null' => sub { return '' },
62             'collapse' => sub { for ($_[0]) { s/^\s+//; s/\s+$//; s/\s+/ /g };
63             $_[0] },
64              
65             # dynamic filters
66             'html_entity' => [ \&html_entity_filter_factory, 1 ],
67             'indent' => [ \&indent_filter_factory, 1 ],
68             'format' => [ \&format_filter_factory, 1 ],
69             'truncate' => [ \&truncate_filter_factory, 1 ],
70             'repeat' => [ \&repeat_filter_factory, 1 ],
71             'replace' => [ \&replace_filter_factory, 1 ],
72             'remove' => [ \&remove_filter_factory, 1 ],
73             'eval' => [ \&eval_filter_factory, 1 ],
74             'evaltt' => [ \&eval_filter_factory, 1 ], # alias
75             'perl' => [ \&perl_filter_factory, 1 ],
76             'evalperl' => [ \&perl_filter_factory, 1 ], # alias
77             'redirect' => [ \&redirect_filter_factory, 1 ],
78             'file' => [ \&redirect_filter_factory, 1 ], # alias
79             'stdout' => [ \&stdout_filter_factory, 1 ],
80             };
81              
82             # name of module implementing plugin filters
83             our $PLUGIN_FILTER = 'Template::Plugin::Filter';
84              
85              
86              
87             #========================================================================
88             # -- PUBLIC METHODS --
89             #========================================================================
90              
91             #------------------------------------------------------------------------
92             # fetch($name, \@args, $context)
93             #
94             # Attempts to instantiate or return a reference to a filter sub-routine
95             # named by the first parameter, $name, with additional constructor
96             # arguments passed by reference to a list as the second parameter,
97             # $args. A reference to the calling Template::Context object is
98             # passed as the third parameter.
99             #
100             # Returns a reference to a filter sub-routine or a pair of values
101             # (undef, STATUS_DECLINED) or ($error, STATUS_ERROR) to decline to
102             # deliver the filter or to indicate an error.
103             #------------------------------------------------------------------------
104              
105             sub fetch {
106 150     150 1 278 my ($self, $name, $args, $context) = @_;
107 150         216 my ($factory, $is_dynamic, $filter, $error);
108              
109             $self->debug("fetch($name, ",
110             defined $args ? ('[ ', join(', ', @$args), ' ]') : '', ', ',
111             defined $context ? $context : '',
112 150 0       507 ')') if $self->{ DEBUG };
    0          
    50          
113              
114             # allow $name to be specified as a reference to
115             # a plugin filter object; any other ref is
116             # assumed to be a coderef and hence already a filter;
117             # non-refs are assumed to be regular name lookups
118              
119 150 100       366 if (ref $name) {
120 4 50 66     48 if (blessed($name) && $name->isa($PLUGIN_FILTER)) {
121 0   0     0 $factory = $name->factory()
122             || return $self->error($name->error());
123             }
124             else {
125 4         15 return $name;
126             }
127             }
128             else {
129             return (undef, Template::Constants::STATUS_DECLINED)
130 146 50 66     1050 unless ($factory = $self->{ FILTERS }->{ $name }
131             || $FILTERS->{ $name });
132             }
133              
134             # factory can be an [ $code, $dynamic ] or just $code
135 146 100       514 if (ref $factory eq 'ARRAY') {
136 86         299 ($factory, $is_dynamic) = @$factory;
137             }
138             else {
139 60         93 $is_dynamic = 0;
140             }
141              
142 146 100       496 if (ref $factory eq 'CODE') {
143 144 100       269 if ($is_dynamic) {
144             # if the dynamic flag is set then the sub-routine is a
145             # factory which should be called to create the actual
146             # filter...
147 84         131 eval {
148 84 100       402 ($filter, $error) = &$factory($context, $args ? @$args : ());
149             };
150 84   100     525 $error ||= $@;
151 84 100 100     448 $error = "invalid FILTER for '$name' (not a CODE ref)"
152             unless $error || ref($filter) eq 'CODE';
153             }
154             else {
155             # ...otherwise, it's a static filter sub-routine
156 60         90 $filter = $factory;
157             }
158             }
159             else {
160 2         7 $error = "invalid FILTER entry for '$name' (not a CODE ref)";
161             }
162              
163 146 100       277 if ($error) {
164             return $self->{ TOLERANT }
165 9 50       238 ? (undef, Template::Constants::STATUS_DECLINED)
166             : ($error, Template::Constants::STATUS_ERROR) ;
167             }
168             else {
169 137         463 return $filter;
170             }
171             }
172              
173              
174             #------------------------------------------------------------------------
175             # store($name, \&filter)
176             #
177             # Stores a new filter in the internal FILTERS hash. The first parameter
178             # is the filter name, the second a reference to a subroutine or
179             # array, as per the standard $FILTERS entries.
180             #------------------------------------------------------------------------
181              
182             sub store {
183 7     7 0 15 my ($self, $name, $filter) = @_;
184              
185 7 50       26 $self->debug("store($name, $filter)") if $self->{ DEBUG };
186              
187 7         26 $self->{ FILTERS }->{ $name } = $filter;
188 7         25 return 1;
189             }
190              
191              
192             #========================================================================
193             # -- PRIVATE METHODS --
194             #========================================================================
195              
196             #------------------------------------------------------------------------
197             # _init(\%config)
198             #
199             # Private initialisation method.
200             #------------------------------------------------------------------------
201              
202             sub _init {
203 149     149   348 my ($self, $params) = @_;
204              
205 149   100     1499 $self->{ FILTERS } = $params->{ FILTERS } || { };
206 149   100     897 $self->{ TOLERANT } = $params->{ TOLERANT } || 0;
207 149   100     929 $self->{ DEBUG } = ( $params->{ DEBUG } || 0 )
208             & Template::Constants::DEBUG_FILTERS;
209              
210              
211 149         1768 return $self;
212             }
213              
214              
215              
216             #------------------------------------------------------------------------
217             # _dump()
218             #
219             # Debug method
220             #------------------------------------------------------------------------
221              
222             sub _dump {
223 0     0   0 my $self = shift;
224 0         0 my $output = "[Template::Filters] {\n";
225 0         0 my $format = " %-16s => %s\n";
226 0         0 my $key;
227              
228 0         0 foreach $key (qw( TOLERANT )) {
229 0         0 my $val = $self->{ $key };
230 0 0       0 $val = '' unless defined $val;
231 0         0 $output .= sprintf($format, $key, $val);
232             }
233              
234 0         0 my $filters = $self->{ FILTERS };
235 0         0 $filters = join('', map {
236 0         0 sprintf(" $format", $_, $filters->{ $_ });
237             } keys %$filters);
238 0         0 $filters = "{\n$filters }";
239            
240 0         0 $output .= sprintf($format, 'FILTERS (local)' => $filters);
241              
242 0         0 $filters = $FILTERS;
243 0         0 $filters = join('', map {
244 0         0 my $f = $filters->{ $_ };
245 0 0       0 my ($ref, $dynamic) = ref $f eq 'ARRAY' ? @$f : ($f, 0);
246 0 0       0 sprintf(" $format", $_, $dynamic ? 'dynamic' : 'static');
247             } sort keys %$filters);
248 0         0 $filters = "{\n$filters }";
249            
250 0         0 $output .= sprintf($format, 'FILTERS (global)' => $filters);
251              
252 0         0 $output .= '}';
253 0         0 return $output;
254             }
255              
256              
257             #========================================================================
258             # -- STATIC FILTER SUBS --
259             #========================================================================
260              
261             #------------------------------------------------------------------------
262             # uri_filter() [% FILTER uri %]
263             #
264             # URI escape a string. This code is borrowed from Gisle Aas' URI::Escape
265             # module, copyright 1995-2004. See RFC2396 for details.
266             #-----------------------------------------------------------------------
267              
268             # cache of escaped characters
269             our $URI_ESCAPES;
270              
271             sub uri_filter {
272 15     15 0 61 my $text = shift;
273              
274 512         1765 $URI_ESCAPES ||= {
275 15   100     47 map { ( chr($_), sprintf("%%%02X", $_) ) } (0..255),
276             };
277              
278 15 100 66     146 if ($] >= 5.008 && utf8::is_utf8($text)) {
279 1         6 utf8::encode($text);
280             }
281            
282 15         77 $text =~ s/([^A-Za-z0-9\-_.!~*'()])/$URI_ESCAPES->{$1}/eg;
  32         138  
283 15         76 $text;
284             }
285              
286             #------------------------------------------------------------------------
287             # url_filter() [% FILTER uri %]
288             #
289             # NOTE: the difference: url vs uri.
290             # This implements the old-style, non-strict behaviour of the uri filter
291             # which allows any valid URL characters to pass through so that
292             # http://example.com/blah.html does not get the ':' and '/' characters
293             # munged.
294             #-----------------------------------------------------------------------
295              
296             sub url_filter {
297 1     1 0 7 my $text = shift;
298              
299 0         0 $URI_ESCAPES ||= {
300 1   50     4 map { ( chr($_), sprintf("%%%02X", $_) ) } (0..255),
301             };
302              
303 1 50 33     13 if ($] >= 5.008 && utf8::is_utf8($text)) {
304 0         0 utf8::encode($text);
305             }
306            
307 1         3 $text =~ s/([^;\/?:@&=+\$,A-Za-z0-9\-_.!~*'()])/$URI_ESCAPES->{$1}/eg;
  0         0  
308 1         3 $text;
309             }
310              
311              
312             #------------------------------------------------------------------------
313             # html_filter() [% FILTER html %]
314             #
315             # Convert any '<', '>' or '&' characters to the HTML equivalents, '<',
316             # '>' and '&', respectively.
317             #------------------------------------------------------------------------
318              
319             sub html_filter {
320 14     14 0 179 my $text = shift;
321 14         39 for ($text) {
322 14         49 s/&/&/g;
323 14         49 s/
324 14         45 s/>/>/g;
325 14         47 s/"/"/g;
326             }
327 14         52 return $text;
328             }
329              
330              
331             #------------------------------------------------------------------------
332             # xml_filter() [% FILTER xml %]
333             #
334             # Same as the html filter, but adds the conversion of ' to ' which
335             # is native to XML.
336             #------------------------------------------------------------------------
337              
338             sub xml_filter {
339 2     2 0 11 my $text = shift;
340 2         8 for ($text) {
341 2         10 s/&/&/g;
342 2         8 s/
343 2         9 s/>/>/g;
344 2         10 s/"/"/g;
345 2         13 s/'/'/g;
346             }
347 2         8 return $text;
348             }
349              
350              
351             #------------------------------------------------------------------------
352             # html_paragraph() [% FILTER html_para %]
353             #
354             # Wrap each paragraph of text (delimited by two or more newlines) in the
355             #

...

HTML tags.
356             #------------------------------------------------------------------------
357              
358             sub html_paragraph {
359 1     1 0 18 my $text = shift;
360 1         16 return "

\n"

361             . join("\n

\n\n

\n", split(/(?:\r?\n){2,}/, $text))

362             . "

\n";
363             }
364              
365              
366             #------------------------------------------------------------------------
367             # html_para_break() [% FILTER html_para_break %]
368             #
369             # Join each paragraph of text (delimited by two or more newlines) with
370             #

HTML tags.
371             #------------------------------------------------------------------------
372              
373             sub html_para_break {
374 2     2 0 36 my $text = shift;
375 2         38 $text =~ s|(\r?\n){2,}|$1
$1
$1|g;
376 2         7 return $text;
377             }
378              
379             #------------------------------------------------------------------------
380             # html_line_break() [% FILTER html_line_break %]
381             #
382             # replaces any newlines with
HTML tags.
383             #------------------------------------------------------------------------
384              
385             sub html_line_break {
386 1     1 0 22 my $text = shift;
387 1         24 $text =~ s|(\r?\n)|
$1|g;
388 1         5 return $text;
389             }
390              
391             #========================================================================
392             # -- DYNAMIC FILTER FACTORIES --
393             #========================================================================
394              
395             #------------------------------------------------------------------------
396             # html_entity_filter_factory(\%options) [% FILTER html %]
397             #
398             # Dynamic version of the static html filter which attempts to locate the
399             # Apache::Util or HTML::Entities modules to perform full entity encoding
400             # of the text passed. Returns an exception if one or other of the
401             # modules can't be located.
402             #------------------------------------------------------------------------
403              
404             sub use_html_entities {
405 1     1 1 7 require HTML::Entities;
406 1         11 return ($AVAILABLE->{ HTML_ENTITY } = \&HTML::Entities::encode_entities);
407             }
408              
409             sub use_apache_util {
410 1     1 1 497 require Apache::Util;
411 0         0 Apache::Util::escape_html(''); # TODO: explain this
412 0         0 return ($AVAILABLE->{ HTML_ENTITY } = \&Apache::Util::escape_html);
413             }
414              
415             sub html_entity_filter_factory {
416 1     1 0 3 my $context = shift;
417 1         2 my $haz;
418            
419             # if Apache::Util is installed then we use escape_html
420             $haz = $AVAILABLE->{ HTML_ENTITY }
421             || eval { use_apache_util() }
422 1   50     7 || eval { use_html_entities() }
423             || -1; # we use -1 for "not available" because it's a true value
424              
425 1 50       9 return ref $haz eq 'CODE'
426             ? $haz
427             : (undef, Template::Exception->new(
428             html_entity => 'cannot locate Apache::Util or HTML::Entities' )
429             );
430             }
431              
432              
433             #------------------------------------------------------------------------
434             # indent_filter_factory($pad) [% FILTER indent(pad) %]
435             #
436             # Create a filter to indent text by a fixed pad string or when $pad is
437             # numerical, a number of space.
438             #------------------------------------------------------------------------
439              
440             sub indent_filter_factory {
441 16     16 0 24 my ($context, $pad) = @_;
442 16 100       38 $pad = 4 unless defined $pad;
443 16 100       115 $pad = ' ' x $pad if $pad =~ /^\d+$/;
444              
445             return sub {
446 16     16   89 my $text = shift;
447 16 50       38 $text = '' unless defined $text;
448 16         101 $text =~ s/^/$pad/mg;
449 16         92 return $text;
450             }
451 16         84 }
452              
453             #------------------------------------------------------------------------
454             # format_filter_factory() [% FILTER format(format) %]
455             #
456             # Create a filter to format text according to a printf()-like format
457             # string.
458             #------------------------------------------------------------------------
459              
460             sub format_filter_factory {
461 11     11 0 22 my ($context, $format) = @_;
462 11 100       27 $format = '%s' unless defined $format;
463              
464             return sub {
465 19     19   120 my $text = shift;
466 19 50       118 $text = '' unless defined $text;
467 19         54 return join("\n", map{ sprintf($format, $_) } split(/\n/, $text));
  19         132  
468             }
469 11         97 }
470              
471              
472             #------------------------------------------------------------------------
473             # repeat_filter_factory($n) [% FILTER repeat(n) %]
474             #
475             # Create a filter to repeat text n times.
476             #------------------------------------------------------------------------
477              
478             sub repeat_filter_factory {
479 3     3 0 9 my ($context, $iter) = @_;
480 3 50 33     25 $iter = 1 unless defined $iter and length $iter;
481              
482             return sub {
483 3     3   16 my $text = shift;
484 3 50       12 $text = '' unless defined $text;
485 3         29 return join('\n', $text) x $iter;
486             }
487 3         20 }
488              
489              
490             #------------------------------------------------------------------------
491             # replace_filter_factory($s, $r) [% FILTER replace(search, replace) %]
492             #
493             # Create a filter to replace 'search' text with 'replace'
494             #------------------------------------------------------------------------
495              
496             sub replace_filter_factory {
497 12     12 0 46 my ($context, $search, $replace) = @_;
498 12 50       43 $search = '' unless defined $search;
499 12 50       39 $replace = '' unless defined $replace;
500              
501             return sub {
502 13     13   83 my $text = shift;
503 13 50       40 $text = '' unless defined $text;
504 13         491 $text =~ s/$search/$replace/g;
505 13         280 return $text;
506             }
507 12         81 }
508              
509              
510             #------------------------------------------------------------------------
511             # remove_filter_factory($text) [% FILTER remove(text) %]
512             #
513             # Create a filter to remove 'search' string from the input text.
514             #------------------------------------------------------------------------
515              
516             sub remove_filter_factory {
517 6     6 0 13 my ($context, $search) = @_;
518              
519             return sub {
520 6     6   574 my $text = shift;
521 6 50       20 $text = '' unless defined $text;
522 6         84 $text =~ s/$search//g;
523 6         40 return $text;
524             }
525 6         31 }
526              
527              
528             #------------------------------------------------------------------------
529             # truncate_filter_factory($n) [% FILTER truncate(n) %]
530             #
531             # Create a filter to truncate text after n characters.
532             #------------------------------------------------------------------------
533              
534             sub truncate_filter_factory {
535 10     10 0 33 my ($context, $len, $char) = @_;
536 10 100       23 $len = $TRUNCATE_LENGTH unless defined $len;
537 10 50       26 $char = $TRUNCATE_ADDON unless defined $char;
538              
539             # Length of char is the minimum length
540 10         14 my $lchar = length $char;
541 10 100       22 if ($len < $lchar) {
542 1         3 $char = substr($char, 0, $len);
543 1         3 $lchar = $len;
544             }
545              
546             return sub {
547 10     10   85 my $text = shift;
548 10 100       35 return $text if length $text <= $len;
549 7         46 return substr($text, 0, $len - $lchar) . $char;
550              
551              
552             }
553 10         53 }
554              
555              
556             #------------------------------------------------------------------------
557             # eval_filter_factory [% FILTER eval %]
558             #
559             # Create a filter to evaluate template text.
560             #------------------------------------------------------------------------
561              
562             sub eval_filter_factory {
563 3     3 0 5 my $context = shift;
564              
565             return sub {
566 3     3   35 my $text = shift;
567 3         15 $context->process(\$text);
568             }
569 3         18 }
570              
571              
572             #------------------------------------------------------------------------
573             # perl_filter_factory [% FILTER perl %]
574             #
575             # Create a filter to process Perl text iff the context EVAL_PERL flag
576             # is set.
577             #------------------------------------------------------------------------
578              
579             sub perl_filter_factory {
580 4     4 0 10 my $context = shift;
581 4         17 my $stash = $context->stash;
582              
583 4 100       47 return (undef, Template::Exception->new('perl', 'EVAL_PERL is not set'))
584             unless $context->eval_perl();
585              
586             return sub {
587 3     3   20 my $text = shift;
588 3         8 local($Template::Perl::context) = $context;
589 3         6 local($Template::Perl::stash) = $stash;
590 3         441 my $out = eval <
591             package Template::Perl;
592             \$stash = \$context->stash();
593             $text
594             EOF
595 3 50       17 $context->throw($@) if $@;
596 3         25 return $out;
597             }
598 3         26 }
599              
600              
601             #------------------------------------------------------------------------
602             # redirect_filter_factory($context, $file) [% FILTER redirect(file) %]
603             #
604             # Create a filter to redirect the block text to a file.
605             #------------------------------------------------------------------------
606              
607             sub redirect_filter_factory {
608 2     2 0 6 my ($context, $file, $options) = @_;
609 2         28 my $outpath = $context->config->{ OUTPUT_PATH };
610              
611 2 100       16 return (undef, Template::Exception->new('redirect',
612             'OUTPUT_PATH is not set'))
613             unless $outpath;
614              
615 1 50       8 $context->throw('redirect', "relative filenames are not supported: $file")
616             if $file =~ m{(^|/)\.\./};
617              
618 1 50       7 $options = { binmode => $options } unless ref $options;
619              
620             sub {
621 1     1   16 my $text = shift;
622             my $outpath = $context->config->{ OUTPUT_PATH }
623 1   50     8 || return '';
624 1         3 $outpath .= "/$file";
625 1         7 my $error = Template::_output($outpath, \$text, $options);
626 1 50       4 die Template::Exception->new('redirect', $error)
627             if $error;
628 1         9 return '';
629             }
630 1         8 }
631              
632              
633             #------------------------------------------------------------------------
634             # stdout_filter_factory($context, $binmode) [% FILTER stdout(binmode) %]
635             #
636             # Create a filter to print a block to stdout, with an optional binmode.
637             #------------------------------------------------------------------------
638              
639             sub stdout_filter_factory {
640 0     0 0   my ($context, $options) = @_;
641              
642 0 0         $options = { binmode => $options } unless ref $options;
643              
644             sub {
645 0     0     my $text = shift;
646 0 0         binmode(STDOUT) if $options->{ binmode };
647 0           print STDOUT $text;
648 0           return '';
649             }
650 0           }
651              
652              
653             1;
654              
655             __END__