> Yes, it's a tricky one.  There's a couple of items in the TODO list 
> which read:
> 
> * Richard Tietjen's patch for stash replace.  Allows back references
>   (e.g. $1) but it would be nice to find a rock-solid way to implement
>   it without relying on unusual ^A delimiter character.
> 
> * Further to the above, Craig Barratt has this solution which will be
>   going into the next verion (2.05b) unless anyone has any further
>   suggestions to make before then.
> 
>     It would be great if replace handled backreferences.  I don't like
>     the ^A solution since the string could contain ^A, plus it is a
>     security hole.  The attempt I posted only works for up to 9
>     backreferences and doesn't handle an escaped '\$' and uses nested
>     evals:
> 
>         $str =~ s{$search}{
>                 my $r = $replace;
>                 my @d = (0, $1, $2, $3, $4, $5, $6, $7, $8, $9);
>                 $r =~ s/\$(\d+)/$d[$1]/eg;
>                 $r;
>               }eg;
> 
>     I wish there was a perl predefined variable array containing all
>     the backreferences (is there one?).  You can avoid the hard-coded
>     limit of 9 with extra evals, and a bit of work on the re could
>     handle the escaped '\$' case, so maybe that would be good enough.
> 
> I obviously didn't put it into 2.05b like I said I would (mea culpa)
> but I'm happy for it to go in now.

Even with Randal's reminder about m/// returning the backrefs, I
couldn't substantially improve my earlier suggestion for replace.

Here's an improved form of the replace sub that handles arbitrary
numbers of backrefs, correctly handles backslash escapes on '$'
(so that '\$1', '\\\$1', etc are not replaced, but '\\$1', '\\\\$1'
are), and supports options (i,g,s,m,x) through an extra optional
argument (default is "g" to remain backward compatible).

sub replaceBackrefs
{
    my($replace) = @_;
    my(@d);
    # save array of backrefs, because regexp below destroys them
    for ( my $i = 1 ; defined(my $v = eval("\$$i")) ; $i++ ) {
        push(@d, $v);
    }
    # replace each $\d+ with backref, provided it is preceded by an
    # even number # of backslashes, including 0
    $replace =~ s{(\\*)\$(\d+)}{length($1) % 2 ? "$1\$$2" : "$1$d[$2-1]"}eg;
    return $replace;
}

$Template::Stash::SCALAR_OPS->{replace} = sub replace {
    my ($str, $search, $replace, $opts) = @_;
    my ($global);
    $replace = '' unless defined $replace;
    return $str unless defined $str and defined $search;
    $opts ||= 'g';
    $global = 1 if ( $opts =~ s/g//g );
    $opts = "(?$opts)" if ( $opts ne "" );
    if ( $replace !~ /\$/ ) {
        # do simple replace if there are no backrefs in $replace
        if ( $global ) {
            $str =~ s{$opts$search}{$replace}g;
        } else {
            $str =~ s{$opts$search}{$replace};
        }
    } else {
        # do more expensive eval replacement for backrefs
        if ( $global ) {
            $str =~ s{$opts$search}{replaceBackrefs($replace)}eg;
        } else {
            $str =~ s{$opts$search}{replaceBackrefs($replace)}e;
        }
    }
    return $str;
};  

Please send suggestions for something cleaner or more compact.  If there
are no objections or improvements I can try comitting it (Andy gasps).

Craig


Reply via email to