Dear [% salutation %] [% last_name %],
You may have won 1 million dollars.
Thanks,
grep
One dead unjugged rabbit fish later |
| [reply] [d/l] |
Why not turn it around just a little and use a regex to substitute replacement text for key text like this:
use strict;
use warnings;
my @plates = (
{GIVENNAME => 'Fred', SIRNAME => 'Fredson', AMOUNT => '$10.24'},
{GIVENNAME => 'Joe', SIRNAME => 'Joeson', AMOUNT => '$20.48'},
);
my $text = do {local $/; <DATA>};
for my $plate (@plates) {
my $copy = $text;
$copy =~ s/$_/$plate->{$_}/g for keys %$plate;
print "$copy\n\n";
}
__DATA__
Dear GIVENNAME SIRNAME,
thanks for your recent contribution if AMOUNT to PerlMonks.
Prints:
Dear Fred Fredson,
thanks for your recent contribution if $10.24 to PerlMonks.
Dear Joe Joeson,
thanks for your recent contribution if $20.48 to PerlMonks.
DWIM is Perl's answer to Gödel
| [reply] [d/l] [select] |
If I understand you correctly, (see how (not) to ask a question) You want to eval $body as a string, but since you're using the eval { BLOCK } syntax it isn't.
print eval $body;
But you can much more easily (and definitely more safely) use variable interpolation:
print "Foo$foo";
See also perlop on Quote operators
| [reply] [d/l] [select] |
Here's what I want to do more clearly. Given a file:
"Dear $row->{name}; thanks for giving us money."
I want to slurp it in:
undef($/);
$block = <FILE>;
Then substitue in a loop:
$sql = "select email,name from database";
while( $row=getrow($sql) ) {
print "Working on $row->{name}\n";
eval{$block);
send_email($row->{email},$block);
}
The idea is to use perl syntax and the perl parser for the substitution, rather than a made up syntax (e.g. %NAME% and s/%NAME%/$name/). Is this crazy? | [reply] [d/l] [select] |
my $string = 'some $stuff I want to $interpolate';
You can now eval $string to interpolate $stuff and $interpolate, but... You need to add quotes:
my $value = eval "'$string'";
update there's a bug here. read ikegami's reply.
The problem with this, is that you can't know if there are any quotes in $string, so you need to escape the quotes in $string before eval()ing. Also, it will interpolate anything that looks like a variable. Even if the variable in question shouldn't be interpolated at all.
In general, if you want to give your users the maximum amount of control, use eval. In any other case, you're better off using a templating language. You might even use something like Text::Template, which is fast and works more or less like perl. If you want to use something simple that's more strict in its input, use something like GrandFather's post.
| [reply] [d/l] [select] |
I would recommend avoiding eval for security reasons. It sounds like a simple substitution will do the trick. For example:
my $first_name = "foo";
my $last_name = "bar";
my $string = 'Dear $first_name, $last_name; Blah.';
$string =~ s/\$first_name/$first_name/g;
$string =~ s/\$last_name/$last_name/g;
#or
#$string =~ s/(\$\w+)/$1/eeg;
#if you are sure all variables in the text will exist.
print $string;
-driver8 | [reply] [d/l] |
The idea is to use perl syntax and the perl parser for the substitution, rather than a made up syntax (e.g. %NAME% and s/%NAME%/$name/). Is this crazy?
Yes, that's crazy, because:
- Neither string eval nor block eval will really work the way you think you want it to here.
- Even if eval did work this way (which it doesn't) this approach would require that you maintain a tricky and easy-to-break dependency between the template text and the perl script: the author of the text needs to know the names and the nature of specific variables (and hash keys) in the perl script, and typos will create unpredictable results that you really don't want in a form letter.
- As others have mentioned, taking a string from a file (or from a database) and passing it to eval poses some risk: it's not just that things might go horribly wrong, it's also an issue that getting things to come out right may be ridiculously hard. (update: note that eval will treat "-" as the subtraction operator, "." as the concatenation operator, etc, so simple punctuation becomes a hazard)
You'll be better off with an approach that generalizes more easily. In what you have so far, the form letter text and the per-recipient data fields come from external sources (a file and a database, respectively) rather than being hard-coded in the script, and that's good.
The next step is to work out a way to externalize the relation between the form and the field data, so that this doesn't depend on how the perl script is written. If the template uses some easily recognized pattern for the field data (like "Dear %NAME%", etc), the perl script can pull these out automatically without needing to worry about how they are spelled; if these names happen to match the column names in the database, things get even easier for the script:
{ # limit the scope of $/ being undef
local $/;
$block = <FILE>
}
my %template_flds = ( EMAIL => undef ); # just in case %EMAIL% is not
+in the template
while ( $block =~ /\%(\w+)\%/g ) {
$template_flds{$1} = undef;
}
my @query_flds = sort keys %template_flds;
$sql = "select ".join(',',@query_flds)." from database";
while( $row = getrow( $sql )) {
my $mesg = $block;
for ( @query_flds ) {
$mesg =~ s/\%$_\%/$row->{$_}/g;
}
print "Sending to $row->{EMAIL}\n";
send_email( $row->{EMAIL}, $mesg );
}
Here, only three dependencies are hard-coded: the name of the database table that holds the field data, the general form of the query string (simple select of columns from a single table with no joins or "where" conditions), and the fact that the database table needs to have a column called EMAIL.
When I wrote my own form-letter app like this, I chose to externalize the query as well. My script used two inputs: the template file, and a tab-delimited flat file (the recipient list) whose first column -- potentially the only column -- was always the email address. If there were more columns in the list, the first row/line in the list had to be a header that assigned a name to each column, and each distinct "%\w+%" string in the template had to exist in the header row, or the script would die with an error message.
Doing it that way means that the query for form data can be arbitrarily complex, so long as it delivers a simple tab-delimited report with one recipient per row; and it's entirely up to the user (not the programmer) to make sure that the template content and per-recipient data are compatible with one another. | [reply] [d/l] |