File Coverage

lib/Template/Filters.pm
Criterion Covered Total %
statement 171 204 83.8
branch 52 86 60.4
condition 29 45 64.4
subroutine 40 43 93.0
pod 5 25 20.0
total 297 403 73.7


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-2014 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 84     84   2330 use strict;
  84         213  
  84         1972  
23 84     84   255 use warnings;
  84         78  
  84         1588  
24 84     84   33269 use locale;
  84         37483  
  84         401  
25 84     84   2423 use base 'Template::Base';
  84         99  
  84         5421  
26 84     84   311 use Template::Constants;
  84         89  
  84         2460  
27 84     84   331 use Scalar::Util 'blessed';
  84         85  
  84         219670  
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 156     156 1 188 my ($self, $name, $args, $context) = @_;
107 156         111 my ($factory, $is_dynamic, $filter, $error);
108              
109             $self->debug("fetch($name, ",
110             defined $args ? ('[ ', join(', ', @$args), ' ]') : '', ', ',
111             defined $context ? $context : '',
112 156 0       253 ')') 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 156 100       223 if (ref $name) {
120 4 50 66     35 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         9 return $name;
126             }
127             }
128             else {
129             return (undef, Template::Constants::STATUS_DECLINED)
130             unless ($factory = $self->{ FILTERS }->{ $name }
131 152 50 66     598 || $FILTERS->{ $name });
132             }
133              
134             # factory can be an [ $code, $dynamic ] or just $code
135 152 100       246 if (ref $factory eq 'ARRAY') {
136 86         126 ($factory, $is_dynamic) = @$factory;
137             }
138             else {
139 66         67 $is_dynamic = 0;
140             }
141              
142 152 100       216 if (ref $factory eq 'CODE') {
143 150 100       192 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         87 eval {
148 84 100       242 ($filter, $error) = &$factory($context, $args ? @$args : ());
149             };
150 84   100     315 $error ||= $@;
151 84 100 100     319 $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 66         62 $filter = $factory;
157             }
158             }
159             else {
160 2         7 $error = "invalid FILTER entry for '$name' (not a CODE ref)";
161             }
162              
163 152 100       224 if ($error) {
164             return $self->{ TOLERANT }
165 9 50       50 ? (undef, Template::Constants::STATUS_DECLINED)
166             : ($error, Template::Constants::STATUS_ERROR) ;
167             }
168             else {
169 143         260 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 13 my ($self, $name, $filter) = @_;
184              
185 7 50       17 $self->debug("store($name, $filter)") if $self->{ DEBUG };
186              
187 7         19 $self->{ FILTERS }->{ $name } = $filter;
188 7         21 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 159     159   208 my ($self, $params) = @_;
204              
205 159   100     869 $self->{ FILTERS } = $params->{ FILTERS } || { };
206 159   100     547 $self->{ TOLERANT } = $params->{ TOLERANT } || 0;
207 159   100     533 $self->{ DEBUG } = ( $params->{ DEBUG } || 0 )
208             & Template::Constants::DEBUG_FILTERS;
209              
210              
211 159         986 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             $filters = join('', map {
236 0         0 sprintf(" $format", $_, $filters->{ $_ });
  0         0  
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             $filters = join('', map {
244 0         0 my $f = $filters->{ $_ };
  0         0  
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() and url_filter() below can match using either RFC3986 or
263             # RFC2732. See https://github.com/abw/Template2/issues/13
264             #-----------------------------------------------------------------------
265              
266             our $UNSAFE_SPEC = {
267             RFC2732 => q{A-Za-z0-9\-_.!~*'()},
268             RFC3986 => q{A-Za-z0-9\-\._~},
269             };
270             our $UNSAFE_CHARS = $UNSAFE_SPEC->{ RFC2732 };
271             our $URI_REGEX;
272             our $URL_REGEX;
273             our $URI_ESCAPES;
274              
275             sub use_rfc2732 {
276 1     1 1 12 $UNSAFE_CHARS = $UNSAFE_SPEC->{ RFC2732 };
277 1         6 $URI_REGEX = $URL_REGEX = undef;
278             }
279              
280             sub use_rfc3986 {
281 1     1 1 16 $UNSAFE_CHARS = $UNSAFE_SPEC->{ RFC3986 };
282 1         7 $URI_REGEX = $URL_REGEX = undef;
283             }
284              
285             sub uri_escapes {
286             return {
287 2     2 0 6 map { ( chr($_), sprintf("%%%02X", $_) ) } (0..255),
  512         1748  
288             };
289             }
290              
291             #------------------------------------------------------------------------
292             # uri_filter() [% FILTER uri %]
293             #
294             # URI escape a string. This code is borrowed from Gisle Aas' URI::Escape
295             # module, copyright 1995-2004. See RFC2396, RFC2732 and RFC3986 for
296             # details.
297             #-----------------------------------------------------------------------
298              
299             sub uri_filter {
300 18     18 0 47 my $text = shift;
301              
302 18   66     168 $URI_REGEX ||= qr/([^$UNSAFE_CHARS])/;
303 18   66     35 $URI_ESCAPES ||= uri_escapes();
304              
305 18 100 66     89 if ($] >= 5.008 && utf8::is_utf8($text)) {
306 1         3 utf8::encode($text);
307             }
308              
309 18         91 $text =~ s/$URI_REGEX/$URI_ESCAPES->{$1}/eg;
  34         79  
310 18         45 $text;
311             }
312              
313              
314              
315             #------------------------------------------------------------------------
316             # url_filter() [% FILTER uri %]
317             #
318             # NOTE: the difference: url vs uri.
319             # This implements the old-style, non-strict behaviour of the uri filter
320             # which allows any valid URL characters to pass through so that
321             # http://example.com/blah.html does not get the ':' and '/' characters
322             # munged.
323             #-----------------------------------------------------------------------
324              
325             sub url_filter {
326 4     4 0 18 my $text = shift;
327              
328 4   66     137 $URL_REGEX ||= qr/([^;\/?:@&=+\$,$UNSAFE_CHARS])/;
329 4   33     9 $URI_ESCAPES ||= uri_escapes();
330              
331 4 50 33     19 if ($] >= 5.008 && utf8::is_utf8($text)) {
332 0         0 utf8::encode($text);
333             }
334              
335 4         15 $text =~ s/$URL_REGEX/$URI_ESCAPES->{$1}/eg;
  2         7  
336 4         9 $text;
337             }
338              
339              
340             #------------------------------------------------------------------------
341             # html_filter() [% FILTER html %]
342             #
343             # Convert any '<', '>' or '&' characters to the HTML equivalents, '<',
344             # '>' and '&', respectively.
345             #------------------------------------------------------------------------
346              
347             sub html_filter {
348 14     14 0 106 my $text = shift;
349 14         30 for ($text) {
350 14         37 s/&/&/g;
351 14         38 s/
352 14         35 s/>/>/g;
353 14         29 s/"/"/g;
354             }
355 14         41 return $text;
356             }
357              
358              
359             #------------------------------------------------------------------------
360             # xml_filter() [% FILTER xml %]
361             #
362             # Same as the html filter, but adds the conversion of ' to ' which
363             # is native to XML.
364             #------------------------------------------------------------------------
365              
366             sub xml_filter {
367 2     2 0 10 my $text = shift;
368 2         6 for ($text) {
369 2         7 s/&/&/g;
370 2         5 s/
371 2         5 s/>/>/g;
372 2         10 s/"/"/g;
373 2         8 s/'/'/g;
374             }
375 2         6 return $text;
376             }
377              
378              
379             #------------------------------------------------------------------------
380             # html_paragraph() [% FILTER html_para %]
381             #
382             # Wrap each paragraph of text (delimited by two or more newlines) in the
383             #

...

HTML tags.
384             #------------------------------------------------------------------------
385              
386             sub html_paragraph {
387 1     1 0 14 my $text = shift;
388 1         15 return "

\n"

389             . join("\n

\n\n

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

390             . "

\n";
391             }
392              
393              
394             #------------------------------------------------------------------------
395             # html_para_break() [% FILTER html_para_break %]
396             #
397             # Join each paragraph of text (delimited by two or more newlines) with
398             #

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