kyle has asked for the wisdom of the Perl Monks concerning the following question:
I was looking through Class::Phrasebook recently. Among its features is a miniature template system. You can give it data like this:
$phrase = 'Hello $dolly!'; $variables = { dolly => 'Nurse' };
...and that will be turned into "Hello Nurse!" The code to do this job looks like this:
$phrase =~ s/\$([a-zA-Z0-9_]+)/$variables->{$1}/g; # also process variables in $(var_name) format. $phrase =~ s/\$\(([a-zA-Z0-9_]+)\)/$variables->{$1}/g;
That's fine until someone does something like:
$phrase = '$foo $bar'; $variables = { foo => '$(bar)', bar => '$(foo)', };
When that happens, the first replacement above changes the phrase to "$(bar) $(foo)" (the correct result), and then the second replacement turns it into "$(foo) $(bar)" (wrong).
To be fair, this is a contrived example, and real world examples of this problem are few and far between. Nevertheless, when it does happen, it may be a real pain to debug.
It got me thinking about a bulletproof way to do this kind of interpolation, and I eventually came up with this:
my @phrase_parts = split /(\$(?:\(\w+\)|\w+))/, $phrase; foreach my $part ( @phrase_parts ) { $part =~ s{ \$ (\w+) }{$variables->{$1}}xms || $part =~ s{ \$ \( (\w+) \) }{$variables->{$1}}xms; } $phrase = join '', @phrase_parts;
I'm using the fact that split will include its delimiters in its result when the pattern you give it is wrapped in capturing parentheses. Everything that looks like a variable is an isolated element in @phrase_parts. Each one is subjected to replacement only once, so their replacements can't interfere with each other.
Now I'm wondering if there's an even better way. The only other thought I had was to use Template Toolkit, but that seemed like a much larger hammer than necessary. I'd be interested to hear thoughts about this from the monks.
|
---|