Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Form letters: eval a string to substutite perl variables

by brycen (Monk)
on Oct 27, 2006 at 22:14 UTC ( [id://581030]=perlquestion: print w/replies, xml ) Need Help??

brycen has asked for the wisdom of the Perl Monks concerning the following question:

Updated: Here's what I want to do. 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?

Replies are listed 'Best First'.
Re: Form letters: eval a string to substutite perl variables
by grep (Monsignor) on Oct 27, 2006 at 22:28 UTC
    I would highly recommend using Template Toolkit. TT makes this kind of stuff trivial, and handles layout much better than a homegrown solution.

    TT is not just for HTML.

    Template can be as easy as:

    Dear [% salutation %] [% last_name %], You may have won 1 million dollars. Thanks,


    grep
    One dead unjugged rabbit fish later
Re: Form letters: eval a string to substutite perl variables
by GrandFather (Saint) on Oct 27, 2006 at 22:28 UTC

    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
Re: Form letters: eval a string to substutite perl variables
by Joost (Canon) on Oct 27, 2006 at 22:28 UTC
      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?
        Alright, one remark up front: read up on the difference between eval {block} and eval $string. It's crucial.

        But also consider this: eval $string is for interpreting strings as code, not for variable substitution. Let's say you've got a string

        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.

Re: Form letters: eval a string to substutite perl variables
by driver8 (Scribe) on Oct 27, 2006 at 22:28 UTC
    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
Re: Form letters: eval a string to substutite perl variables
by graff (Chancellor) on Oct 28, 2006 at 11:16 UTC
    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.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://581030]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (3)
As of 2024-04-25 23:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found