tags:

views:

454

answers:

3

My client has a database table of email bodies that get sent at certain times to customers. The text for the emails contains ColdFusion expressions like Dear #firstName# and so on. These emails are HTML - they also contain all sorts of HTML mark-up. What I'd like to do is read that text from the database into a string and then have ColdFusion Evaluate() that string to resolve the variables. When I do that, Evaluate() throws an exception because it doesn't like the HTML markup in there (I also tried filtering the string through HTMLEditFormat() as an intermediate step for grins but it didn't like the entities in there).

My predecessor solved this problem by writing the email text out to a file and then cfincluding that. It works. It's seems really hacky though. Is there a more elegant way to handle this using something like Evaluate that I'm not seeing?

+2  A: 

CF 7+: You may use regular expression, REReplace()?

CF 9: use Virtual File System

Henry
Henry - Thanks, I forgot to mention that I'm on CF7. I don't see how I'd use REReplace to do variable substitution. Is there something I don't see in the doc?
DaveBurns
you can do almost anything with regular expression! :) Well, just fish out the variable name inside #varName#, and then call evalulate(varName), and replace the "#varName#" with evaluated result?
Henry
or... loop through every character in the string, like an input stream, when u encounter the # sign, stop outputting to the output stream, until u hit another # sign, evaluate the recorded string, and add that to your output stream. :)
Henry
I think ReReplace is a good solution. You could be more pragmatic if there are just three variables to replace and use ReplaceNoCase().
SWD
@Henry: +1, that's also what I would do. Unwritten SO rule, proven again: Telling the concept is not very fruitful, telling the concept and adding a code sample = up-votes. ;)
Tomalak
+8  A: 

Not sure you need rereplace, you could brute force it with a simple replace if you don't have too many fields to merge

How about something like this (not tested)

<cfset var BaseTemplate = "... lots of html with embedded tokens">

<cfloop (on whatever)>

   <cfset LoopTemplate = replace(BaseTemplate, "#firstName#", myvarforFirstName, "All">
   <cfset LoopTemplate = replace(LoopTemplate, "#lastName#",  myvarforLastName, "All">
   <cfset LoopTemplate = replace(LoopTemplate, "#address#",   myvarforAddress, "All">

</cfloop>

Just treat the html block as a simple string.

kevink
Yup. I typically populate email templates with dynamic variables this way, just keywords enclosed in other %characters%.
Sergii
I've done similar, but avoided hash marks to delineate variables specifically because it plays havoc when reading the file in. I generally like to enclose my variables with [[brackets]].
Al Everett
Sounded like he was processing legacy code with the #fieldname# syntax already embedded in his "templates". If you were designing it from scratch I agree the "#" character would be a terrible choice.
kevink
Yes, the previous programmer used #'s on purpose. He'd read in the customer's name from the db into a variable then cfinclude the email template and CF would resolve those variables for him. The person creating the email templates needed to know what the names of the CF variables were. I appreciate the simplicity and compactness of the code but it creates a system that is very tightly bound and inflexible. Searching and replacing variables/keywords myself is more code but I like the idea since it makes it a bit more self-documenting.
DaveBurns
BTW, while I agree doing the replacing myself is probably better design, the best quick-fix from my customer's limited budget PoV is still some way to trick Evaluate() into replacing the variables for me while smoothly ignoring the HTML. I guess from the responses that there's no way to do that?
DaveBurns
I like the solution.Using the code in this answer above, you'd probably run a query or lookup function above to get the mailing list right?<!--- function that returns a query of users ---><cfset myQuery = getMailingList()><cfloop query="myQuery">...
Dan Sorensen
Have you tried global replacing > with something like |gt| and < with something like |lt| then running evaluate? If it works then you just swap them back as the last pass
kevink
@kevink - Evaluate wants the entire thing I pass it to be an expression so it will choke. That's the difference with the cfinclude approach: it allows for CFML to be in there. Hmm, I wonder what would happen though if I put double-quotes at the beginning and end of the string before calling evaluate? I'd also have to escape any double-quotes inside it. Then after the eval, change the escaped quotes back.
DaveBurns
I use replace extensively to send out emails where the template is stored in the email with dynamic content in them. Works great. Use a different character than # and you're laughing.
Jas Panesar
+3  A: 

What other languages often do that seems to work very well is just have some kind of token within your template that can be easily replaced by a regular expression. So you might have a template like:

Dear {{name}}, Thanks for trying {{product_name}}.  Etc...

And then you can simply:

<cfset str = ReplaceNoCase(str, "{{name}}", name, "ALL") />

And when you want to get fancier you could just write a method to wrap this:

<cffunction name="fillInTemplate" access="public" returntype="string" output="false">
    <cfargument name="map" type="struct" required="true" />
    <cfargument name="template" type="string" required="true" />

    <cfset var str = arguments.template />
    <cfset var k = "" />

    <cfloop list="#StructKeyList(arguments.map)#" index="k">
        <cfset str = ReplaceNoCase(str, "{{#k#}}", arguments.map[k], "ALL") />
    </cfloop>

    <cfreturn str />
</cffunction>

And use it like so:

<cfset map = { name : "John", product : "SpecialWidget" } />
<cfset filledInTemplate = fillInTemplate(map, someTemplate) />
Bialecki
This gets the nod for complete sample code although instead of Replace, I had to use REReplaceNoCase since Replace is case-sensitive and CF converted all the struct member keys to upper-case.
DaveBurns
Makes sense. I updated to use ReplaceNoCase instead of REReplaceNoCase because you shouldn't need regular expressions, but I suppose you could use them if you wanted.
Bialecki
Interesting. I'm using CF7 and I looked for ReplaceNoCase but it's not listed in my CF7 WACK book and it doesn't have an online help entry at Adobe unless you look at CF8 and above. I assumed it was added in CF8 but I do see it used in sample code for CF6. So, not sure what to make of that.
DaveBurns
Bialecki