1#============================================================= -*-Perl-*-
2#
3# Template::Plugin::String
4#
5# DESCRIPTION
6#   Template Toolkit plugin to implement a basic String object.
7#
8# AUTHOR
9#   Andy Wardley   <abw@wardley.org>
10#
11# COPYRIGHT
12#   Copyright (C) 2001-2007 Andy Wardley.  All Rights Reserved.
13#
14#   This module is free software; you can redistribute it and/or
15#   modify it under the same terms as Perl itself.
16#
17#============================================================================
18
19package Template::Plugin::String;
20
21use strict;
22use warnings;
23use base 'Template::Plugin';
24use Template::Exception;
25
26use overload q|""| => "text",
27             fallback => 1;
28
29our $VERSION = 2.40;
30our $ERROR   = '';
31
32*centre  = \*center;
33*append  = \*push;
34*prepend = \*unshift;
35
36#------------------------------------------------------------------------
37
38sub new {
39    my ($class, @args) = @_;
40    my $context = ref $class ? undef : shift(@args);
41    my $config = @args && ref $args[-1] eq 'HASH' ? pop(@args) : { };
42
43    $class = ref($class) || $class;
44
45    my $text = defined $config->{ text }
46        ? $config->{ text }
47        : (@args ? shift(@args) : '');
48
49#    print STDERR "text: [$text]\n";
50#    print STDERR "class: [$class]\n";
51
52    my $self = bless {
53        text     => $text,
54        filters  => [ ],
55        _CONTEXT => $context,
56    }, $class;
57
58    my $filter = $config->{ filter } || $config->{ filters };
59
60    # install any output filters specified as 'filter' or 'filters' option
61    $self->output_filter($filter)
62        if $filter;
63
64    return $self;
65}
66
67
68sub text {
69    my $self = shift;
70    return $self->{ text } unless @{ $self->{ filters } };
71
72    my $text = $self->{ text };
73    my $context = $self->{ _CONTEXT };
74
75    foreach my $dispatch (@{ $self->{ filters } }) {
76        my ($name, $args) = @$dispatch;
77        my $code = $context->filter($name, $args)
78            || $self->throw($context->error());
79        $text = &$code($text);
80    }
81    return $text;
82}
83
84
85sub copy {
86    my $self = shift;
87    $self->new($self->{ text });
88}
89
90
91sub throw {
92    my $self = shift;
93
94    die (Template::Exception->new('String', join('', @_)));
95}
96
97
98#------------------------------------------------------------------------
99# output_filter($filter)
100#
101# Install automatic output filter(s) for the string.  $filter can a list:
102# [ 'name1', 'name2' => [ ..args.. ], name4 => { ..args.. } ] or a hash
103# { name1 => '', name2 => [ args ], name3 => { args } }
104#------------------------------------------------------------------------
105
106sub output_filter {
107    my ($self, $filter) = @_;
108    my ($name, $args, $dispatch);
109    my $filters = $self->{ filters };
110    my $count = 0;
111
112    if (ref $filter eq 'HASH') {
113        $filter = [ %$filter ];
114    }
115    elsif (ref $filter ne 'ARRAY') {
116        $filter = [ split(/\s*\W+\s*/, $filter) ];
117    }
118
119    while (@$filter) {
120        $name = shift @$filter;
121
122        # args may follow as a reference (or empty string, e.g. { foo => '' }
123        if (@$filter && (ref($filter->[0]) || ! length $filter->[0])) {
124            $args = shift @$filter;
125            if ($args) {
126                $args = [ $args ] unless ref $args eq 'ARRAY';
127            }
128            else {
129                $args = [ ];
130            }
131        }
132        else {
133            $args = [ ];
134        }
135
136#       $self->DEBUG("adding output filter $name(@$args)\n");
137
138        push(@$filters, [ $name, $args ]);
139        $count++;
140    }
141
142    return '';
143}
144
145
146#------------------------------------------------------------------------
147
148sub push {
149    my $self = shift;
150    $self->{ text } .= join('', @_);
151    return $self;
152}
153
154
155sub unshift {
156    my $self = shift;
157    $self->{ text } = join('', @_) . $self->{ text };
158    return $self;
159}
160
161
162sub pop {
163    my $self = shift;
164    my $strip = shift || return $self;
165    $self->{ text } =~ s/$strip$//;
166    return $self;
167}
168
169
170sub shift {
171    my $self = shift;
172    my $strip = shift || return $self;
173    $self->{ text } =~ s/^$strip//;
174    return $self;
175}
176
177#------------------------------------------------------------------------
178
179sub center {
180    my ($self, $width) = @_;
181    my $text = $self->{ text };
182    my $len = length $text;
183    $width ||= 0;
184
185    if ($len < $width) {
186        my $lpad = int(($width - $len) / 2);
187        my $rpad = $width - $len - $lpad;
188        $self->{ text } = (' ' x $lpad) . $self->{ text } . (' ' x $rpad);
189    }
190
191    return $self;
192}
193
194
195sub left {
196    my ($self, $width) = @_;
197    my $len = length $self->{ text };
198    $width ||= 0;
199
200    $self->{ text } .= (' ' x ($width - $len))
201        if $width > $len;
202
203    return $self;
204}
205
206
207sub right {
208    my ($self, $width) = @_;
209    my $len = length $self->{ text };
210    $width ||= 0;
211
212    $self->{ text } = (' ' x ($width - $len)) . $self->{ text }
213        if $width > $len;
214
215    return $self;
216}
217
218
219sub format {
220    my ($self, $format) = @_;
221    $format = '%s' unless defined $format;
222    $self->{ text } = sprintf($format, $self->{ text });
223    return $self;
224}
225
226
227sub filter {
228    my ($self, $name, @args) = @_;
229
230    my $context = $self->{ _CONTEXT };
231
232    my $code = $context->filter($name, \@args)
233        || $self->throw($context->error());
234    return &$code($self->{ text });
235}
236
237
238#------------------------------------------------------------------------
239
240sub upper {
241    my $self = CORE::shift;
242    $self->{ text } = uc $self->{ text };
243    return $self;
244}
245
246
247sub lower {
248    my $self = CORE::shift;
249    $self->{ text } = lc $self->{ text };
250    return $self;
251}
252
253
254sub capital {
255    my $self = CORE::shift;
256    $self->{ text } =~ s/^(.)/\U$1/;
257    return $self;
258}
259
260#------------------------------------------------------------------------
261
262sub chop {
263    my $self = CORE::shift;
264    chop $self->{ text };
265    return $self;
266}
267
268
269sub chomp {
270    my $self = CORE::shift;
271    chomp $self->{ text };
272    return $self;
273}
274
275
276sub trim {
277    my $self = CORE::shift;
278    for ($self->{ text }) {
279        s/^\s+//;
280        s/\s+$//;
281    }
282    return $self;
283}
284
285
286sub collapse {
287    my $self = CORE::shift;
288    for ($self->{ text }) {
289        s/^\s+//;
290        s/\s+$//;
291        s/\s+/ /g
292    }
293    return $self;
294
295}
296
297#------------------------------------------------------------------------
298
299sub length {
300    my $self = CORE::shift;
301    return length $self->{ text };
302}
303
304
305sub truncate {
306    my ($self, $length, $suffix) = @_;
307    return $self unless defined $length;
308    $suffix ||= '';
309    return $self if CORE::length $self->{ text } <= $length;
310    $self->{ text } = CORE::substr($self->{ text }, 0,
311                             $length - CORE::length($suffix)) . $suffix;
312    return $self;
313}
314
315
316sub substr {
317    my ($self, $offset, $length, $replacement) = @_;
318    $offset ||= 0;
319
320    if(defined $length) {
321        if (defined $replacement) {
322            my $removed = CORE::substr( $self->{text}, $offset, $length );
323            CORE::substr( $self->{text}, $offset, $length ) = $replacement;
324            return $removed;
325        }
326        else {
327            return CORE::substr( $self->{text}, $offset, $length );
328        }
329    }
330    else {
331        return CORE::substr( $self->{text}, $offset );
332    }
333}
334
335
336sub repeat {
337    my ($self, $n) = @_;
338    return $self unless defined $n;
339    $self->{ text } = $self->{ text } x $n;
340    return $self;
341}
342
343
344sub replace {
345    my ($self, $search, $replace) = @_;
346    return $self unless defined $search;
347    $replace = '' unless defined $replace;
348    $self->{ text } =~ s/$search/$replace/g;
349    return $self;
350}
351
352
353sub remove {
354    my ($self, $search) = @_;
355    $search = '' unless defined $search;
356    $self->{ text } =~ s/$search//g;
357    return $self;
358}
359
360
361sub split {
362    my $self  = CORE::shift;
363    my $split = CORE::shift;
364    my $limit = CORE::shift || 0;
365    $split = '\s+' unless defined $split;
366    return [ split($split, $self->{ text }, $limit) ];
367}
368
369
370sub search {
371    my ($self, $pattern) = @_;
372    return $self->{ text } =~ /$pattern/;
373}
374
375
376sub equals {
377    my ($self, $comparison) = @_;
378    return $self->{ text } eq $comparison;
379}
380
381
3821;
383
384__END__
385
386=head1 NAME
387
388Template::Plugin::String - Object oriented interface for string manipulation
389
390=head1 SYNOPSIS
391
392    # create String objects via USE directive
393    [% USE String %]
394    [% USE String 'initial text' %]
395    [% USE String text => 'initial text' %]
396
397    # or from an existing String via new()
398    [% newstring = String.new %]
399    [% newstring = String.new('newstring text') %]
400    [% newstring = String.new( text => 'newstring text' ) %]
401
402    # or from an existing String via copy()
403    [% newstring = String.copy %]
404
405    # append text to string
406    [% String.append('text to append') %]
407
408    # format left, right or center/centre padded
409    [% String.left(20) %]
410    [% String.right(20) %]
411    [% String.center(20) %]   # American spelling
412    [% String.centre(20) %]   # European spelling
413
414    # and various other methods...
415
416=head1 DESCRIPTION
417
418This module implements a C<String> class for doing stringy things to
419text in an object-oriented way.
420
421You can create a C<String> object via the C<USE> directive, adding any
422initial text value as an argument or as the named parameter C<text>.
423
424    [% USE String %]
425    [% USE String 'initial text' %]
426    [% USE String text='initial text' %]
427
428The object created will be referenced as C<String> by default, but you
429can provide a different variable name for the object to be assigned
430to:
431
432    [% USE greeting = String 'Hello World' %]
433
434Once you've got a C<String> object, you can use it as a prototype to
435create other C<String> objects with the C<new()> method.
436
437    [% USE String %]
438    [% greeting = String.new('Hello World') %]
439
440The C<new()> method also accepts an initial text string as an argument
441or the named parameter C<text>.
442
443    [% greeting = String.new( text => 'Hello World' ) %]
444
445You can also call C<copy()> to create a new C<String> as a copy of the
446original.
447
448    [% greet2 = greeting.copy %]
449
450The C<String> object has a C<text()> method to return the content of the
451string.
452
453    [% greeting.text %]
454
455However, it is sufficient to simply print the string and let the
456overloaded stringification operator call the C<text()> method
457automatically for you.
458
459    [% greeting %]
460
461Thus, you can treat C<String> objects pretty much like any regular piece
462of text, interpolating it into other strings, for example:
463
464    [% msg = "It printed '$greeting' and then dumped core\n" %]
465
466You also have the benefit of numerous other methods for manipulating
467the string.
468
469    [% msg.append("PS  Don't eat the yellow snow") %]
470
471Note that all methods operate on and mutate the contents of the string
472itself.  If you want to operate on a copy of the string then simply
473take a copy first:
474
475    [% msg.copy.append("PS  Don't eat the yellow snow") %]
476
477These methods return a reference to the C<String> object itself.  This
478allows you to chain multiple methods together.
479
480    [% msg.copy.append('foo').right(72) %]
481
482It also means that in the above examples, the C<String> is returned which
483causes the C<text()> method to be called, which results in the new value of
484the string being printed.  To suppress printing of the string, you can
485use the C<CALL> directive.
486
487    [% foo = String.new('foo') %]
488
489    [% foo.append('bar') %]         # prints "foobar"
490
491    [% CALL foo.append('bar') %]    # nothing
492
493=head1 CONSTRUCTOR METHODS
494
495These methods are used to create new C<String> objects.
496
497=head2 new()
498
499Creates a new string using an initial value passed as a positional
500argument or the named parameter C<text>.
501
502    [% USE String %]
503    [% msg = String.new('Hello World') %]
504    [% msg = String.new( text => 'Hello World' ) %]
505
506=head2 copy()
507
508Creates a new C<String> object which contains a copy of the original string.
509
510    [% msg2 = msg.copy %]
511
512=head1 INSPECTOR METHODS
513
514These methods are used to examine the string.
515
516=head2 text()
517
518Returns the internal text value of the string.  The stringification
519operator is overloaded to call this method.  Thus the following are
520equivalent:
521
522    [% msg.text %]
523    [% msg %]
524
525=head2 length()
526
527Returns the length of the string.
528
529    [% USE String("foo") %]
530    [% String.length %]   # => 3
531
532=head2 search($pattern)
533
534Searches the string for the regular expression specified in C<$pattern>
535returning true if found or false otherwise.
536
537    [% item = String.new('foo bar baz wiz waz woz') %]
538    [% item.search('wiz') ? 'WIZZY! :-)' : 'not wizzy :-(' %]
539
540=head2 split($pattern, $limit)
541
542Splits the string based on the delimiter C<$pattern> and optional C<$limit>.
543Delegates to Perl's internal C<split()> so the parameters are exactly the same.
544
545    [% FOREACH item.split %]
546         ...
547    [% END %]
548
549    [% FOREACH item.split('baz|waz') %]
550         ...
551    [% END %]
552
553=head1 MUTATOR METHODS
554
555These methods modify the internal value of the string.  For example:
556
557    [% USE str=String('foobar') %]
558    [% str.append('.html') %]   # str => 'foobar.html'
559
560The value of C<str> is now 'C<foobar.html>'.  If you don't want
561to modify the string then simply take a copy first.
562
563    [% str.copy.append('.html') %]
564
565These methods all return a reference to the C<String> object itself.  This
566has two important benefits.  The first is that when used as above, the
567C<String> object 'C<str>' returned by the C<append()> method will be stringified
568with a call to its C<text()> method.  This will return the newly modified
569string content.  In other words, a directive like:
570
571    [% str.append('.html') %]
572
573will update the string and also print the new value.  If you just want
574to update the string but not print the new value then use C<CALL>.
575
576    [% CALL str.append('.html') %]
577
578The other benefit of these methods returning a reference to the C<String>
579is that you can chain as many different method calls together as you
580like.  For example:
581
582    [% String.append('.html').trim.format(href) %]
583
584Here are the methods:
585
586=head2 push($suffix, ...) / append($suffix, ...)
587
588Appends all arguments to the end of the string.  The
589C<append()> method is provided as an alias for C<push()>.
590
591    [% msg.push('foo', 'bar') %]
592    [% msg.append('foo', 'bar') %]
593
594=head2 pop($suffix)
595
596Removes the suffix passed as an argument from the end of the String.
597
598    [% USE String 'foo bar' %]
599    [% String.pop(' bar')   %]   # => 'foo'
600
601=head2 unshift($prefix, ...) / prepend($prefix, ...)
602
603Prepends all arguments to the beginning of the string.  The
604C<prepend()> method is provided as an alias for C<unshift()>.
605
606    [% msg.unshift('foo ', 'bar ') %]
607    [% msg.prepend('foo ', 'bar ') %]
608
609=head2 shift($prefix)
610
611Removes the prefix passed as an argument from the start of the String.
612
613    [% USE String 'foo bar' %]
614    [% String.shift('foo ') %]   # => 'bar'
615
616=head2 left($pad)
617
618If the length of the string is less than C<$pad> then the string is left
619formatted and padded with spaces to C<$pad> length.
620
621    [% msg.left(20) %]
622
623=head2 right($pad)
624
625As per L<left()> but right padding the C<String> to a length of C<$pad>.
626
627    [% msg.right(20) %]
628
629=head2 center($pad) / centre($pad)
630
631As per L<left()> and L<right()> but formatting the C<String> to be centered within
632a space padded string of length C<$pad>.  The C<centre()> method is provided as
633an alias for C<center()>.
634
635    [% msg.center(20) %]    # American spelling
636    [% msg.centre(20) %]    # European spelling
637
638=head2 format($format)
639
640Apply a format in the style of C<sprintf()> to the string.
641
642    [% USE String("world") %]
643    [% String.format("Hello %s\n") %]  # => "Hello World\n"
644
645=head2 upper()
646
647Converts the string to upper case.
648
649    [% USE String("foo") %]
650    [% String.upper %]  # => 'FOO'
651
652=head2 lower()
653
654Converts the string to lower case
655
656    [% USE String("FOO") %]
657    [% String.lower %]  # => 'foo'
658
659=head2 capital()
660
661Converts the first character of the string to upper case.
662
663    [% USE String("foo") %]
664    [% String.capital %]  # => 'Foo'
665
666The remainder of the string is left untouched.  To force the string to
667be all lower case with only the first letter capitalised, you can do
668something like this:
669
670    [% USE String("FOO") %]
671    [% String.lower.capital %]  # => 'Foo'
672
673=head2 chop()
674
675Removes the last character from the string.
676
677    [% USE String("foop") %]
678    [% String.chop %]   # => 'foo'
679
680=head2 chomp()
681
682Removes the trailing newline from the string.
683
684    [% USE String("foo\n") %]
685    [% String.chomp %]  # => 'foo'
686
687=head2 trim()
688
689Removes all leading and trailing whitespace from the string
690
691    [% USE String("   foo   \n\n ") %]
692    [% String.trim %]   # => 'foo'
693
694=head2 collapse()
695
696Removes all leading and trailing whitespace and collapses any sequences
697of multiple whitespace to a single space.
698
699    [% USE String(" \n\r  \t  foo   \n \n bar  \n") %]
700    [% String.collapse %]   # => "foo bar"
701
702=head2 truncate($length, $suffix)
703
704Truncates the string to C<$length> characters.
705
706    [% USE String('long string') %]
707    [% String.truncate(4) %]  # => 'long'
708
709If C<$suffix> is specified then it will be appended to the truncated
710string.  In this case, the string will be further shortened by the
711length of the suffix to ensure that the newly constructed string
712complete with suffix is exactly C<$length> characters long.
713
714    [% USE msg = String('Hello World') %]
715    [% msg.truncate(8, '...') %]   # => 'Hello...'
716
717=head2 replace($search, $replace)
718
719Replaces all occurrences of C<$search> in the string with C<$replace>.
720
721    [% USE String('foo bar foo baz') %]
722    [% String.replace('foo', 'wiz')  %]  # => 'wiz bar wiz baz'
723
724=head2 remove($search)
725
726Remove all occurrences of C<$search> in the string.
727
728    [% USE String('foo bar foo baz') %]
729    [% String.remove('foo ')  %]  # => 'bar baz'
730
731=head2 repeat($count)
732
733Repeats the string C<$count> times.
734
735    [% USE String('foo ') %]
736    [% String.repeat(3)  %]  # => 'foo foo foo '
737
738=head1 AUTHOR
739
740Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
741
742=head1 COPYRIGHT
743
744Copyright (C) 1996-2007 Andy Wardley.  All Rights Reserved.
745
746This module is free software; you can redistribute it and/or
747modify it under the same terms as Perl itself.
748
749=head1 SEE ALSO
750
751L<Template::Plugin>
752
753=cut
754
755# Local Variables:
756# mode: perl
757# perl-indent-level: 4
758# indent-tabs-mode: nil
759# End:
760#
761# vim: expandtab shiftwidth=4:
762