1package Template::Plugin::Subst;
2
3# Copyright (c) 2005 Nik Clayton
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#     documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26
27use warnings;
28use strict;
29
30use Template::Plugin::Filter;
31use base qw(Template::Plugin::Filter);
32
33use Template::Stash;
34
35$Template::Stash::SCALAR_OPS->{subst} = \&subst;
36
37=head1 NAME
38
39Template::Plugin::Subst - s/// functionality for Template Toolkit templates
40
41=head1 VERSION
42
43Version 0.02
44
45=cut
46
47our $VERSION = '0.02';
48
49sub init {
50  my $self = shift;
51
52  $self->{_DYNAMIC} = 1;
53
54  return $self;
55}
56
57sub filter {
58  my($self, $text, $args, $config) = @_;
59
60  $config = $self->merge_config($config);
61
62  my $pattern     = $config->{pattern};
63  my $replacement = $config->{replacement};
64  my $global      = defined $config->{global} ? $config->{global} : 1;
65
66#  warn "pattern: $pattern, replacement: $replacement\n";
67  $text = subst($text, $pattern, $replacement, $global);
68
69  return $text;
70}
71
72sub subst {
73  my($text, $pattern, $replacement, $global) = @_;
74
75  $global = defined $global ? $global : 1;
76
77#  warn "-> subst() ('$pattern', '$replacement')\n";
78  if($text !~ m/$pattern/) {
79#    warn "text does not match '$pattern', returning";
80    return $text;
81  }
82
83  # If there are no subgroups then it's a simple search/replace
84  if($#- == 0) {
85#    warn "No subgroups found, doing simple search/replace\n";
86    if($global) {
87      $text =~ s/$pattern/$replacement/g;
88    } else {
89      $text =~ s/$pattern/$replacement/;
90    }
91    return $text;
92  }
93
94  # First, save the original text, and what was matched
95  my $saved_text = $text;
96  my $PREMATCH   = substr($saved_text, 0, $-[0]);
97  my $MATCHED    = substr($saved_text, $-[0], $+[0] - $-[0]);
98  my $POSTMATCH  = substr($saved_text, $+[0]);
99
100  # Save the positions where we matched
101  my @saved_match_start = @-;
102  my @saved_match_end   = @+;
103
104#  warn "PREMATCH : <<$PREMATCH>>";
105#  warn "MATCHED  : <<$MATCHED>>";
106#  warn "POSTMATCH: <<$POSTMATCH>>";
107
108  # Now do the s///.  This will leave placeholders (literally, '$1', '$2',
109  # etc, in the replaced text.
110#  warn "Doing s///";
111  $MATCHED =~ s/$pattern/$replacement/;
112#  warn "MATCHED:  <<$MATCHED>>";
113
114  foreach my $i (1..$#saved_match_start) {
115    my $backref = substr($saved_text,
116			 $saved_match_start[$i],
117			 $saved_match_end[$i] - $saved_match_start[$i]);
118    $MATCHED =~ s/\$$i/$backref/g;
119  }
120
121#  warn "Fixed up backrefs";
122#  warn "MATCHED:  <<$MATCHED>>";
123
124  if($global) {
125    return $PREMATCH . $MATCHED . subst($POSTMATCH, $pattern, $replacement);
126  } else {
127    return $PREMATCH . $MATCHED . $POSTMATCH;
128  }
129}
130
131=head1 SYNOPSIS
132
133=head2 As a vemthod
134
135  [% USE Subst %]
136
137  [% str = 'foobar' %]
138
139  [% str.subst('(foo)(bar)', '$2$1', 1) %]
140
141=head2 As a filter
142
143  [% USE filt = Subst
144                pattern = '(foo)(bar)'
145                replacement = '$2$1'
146                global = 1 %]
147
148Then
149
150  [% text | $filt %]
151
152or
153
154  [% FILTER $filt %]
155  foobar
156  [% END %]
157
158=head1 DESCRIPTION
159
160Template::Plugin::Subst acts as a filter and a virtual method to carry
161out regular expression substitutions with back references on text and
162variables in the Template Toolkit.
163
164That's the advantage of this approach over the built-in C<replace>
165method.  C<replace> doesn't deal with backrefs, so code like this:
166
167  [% str = 'foobar' %]
168  [% str.replace('(foo)(bar)', '$2$1') %]
169
170inserts a literal C<$2$1> in to your document.
171
172But with Template::Plugin::Subst;
173
174  [% USE Subst %]
175  [% str = 'foobar' %]
176  [% str.subst('(foo)(bar)', '$2$1') %]
177
178you get the expected C<barfoo>.
179
180It can also be used as a filter, in which case it's very useful for finding
181information in text and augmenting it in a useful fashion.
182
183For example, suppose you want all strings of the form C<rt#\d+>, which
184reference RT ticket numbers, to be converted to links to your local
185RT installation.
186
187First, instatiate the filter:
188
189  [% USE rt = Subst
190                pattern = 'rt#(\d+)'
191                replacement = '<a href="/rt.cgi?t=$1">rt#$1</a>' %]
192
193and then use it to filter arbitrary text:
194
195  [% text_variable | $rt %]
196
197=head1 OPTIONS
198
199=head2 vmethod
200
201  .subst($pattern, $replacement[, $global])
202
203As a vmethod the first two arguments are the pattern to search for and
204the string to replace it with.  These arguments are mandatory.
205
206The third argument is a boolean that specifies whether or the
207search/replace should be global, and behaves in the same way as the C<g>
208modifier on a C<s///> operation.  The default value is '1'.  Note that
209this differs from the default setting on the C<s///> operator.
210
211=head2 Filter
212
213  [% USE filt = Subst
214                  pattern = '...'
215                  replacement = '...'
216                  global = 1 %]
217
218These three named arguments have the same semantics as the arguments to
219the vmethod.  C<global> is optional, and defaults to 1.
220
221=head1 AUTHOR
222
223Nik Clayton, C<< <nik@FreeBSD.org> >>
224
225=head1 BUGS
226
227Please report any bugs or feature requests to
228C<bug-template-plugin-subst@rt.cpan.org>, or through the web interface at
229L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Plugin-Subst>.
230I will be notified, and then you'll automatically be notified of progress on
231your bug as I make changes.
232
233=head1 COPYRIGHT & LICENSE
234
235Copyright (c) 2005 Nik Clayton
236All rights reserved.
237
238Redistribution and use in source and binary forms, with or without
239modification, are permitted provided that the following conditions
240are met:
241
242 1. Redistributions of source code must retain the above copyright
243    notice, this list of conditions and the following disclaimer.
244 2. Redistributions in binary form must reproduce the above copyright
245    notice, this list of conditions and the following disclaimer in the
246    documentation and/or other materials provided with the distribution.
247
248THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
249ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
250IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
251ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
252FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
253DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
254OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
255HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
256LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
257OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
258SUCH DAMAGE.
259
260=cut
261
2621; # End of Template::Plugin::Subst
263