Resolving variables inside a Coldfusion string - coldfusion
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?
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) />
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.
CF 7+: You may use regular expression, REReplace()?
CF 9: use Virtual File System
If the variable is in a structure from, something like a form post, then you can use "StructFind". It does exactly as you request. I ran into this issue when processing a form with dynamic inputs.
Ex.
StructFind(FORM, 'WhatYouNeed')
Related
ColdFusion Application.cfm cfinclude not working
I have a function called "conv" which is needed in several programs within my application. So rather than include it in each program, I put it in the Application.cfm, which looked like this: <cfapplication name = "Moxware" sessionmanagement = "Yes"> <cfset lang = "LU"> <cfset x127 = Chr(127)> <cfset mmox = 'Moxware'> <cfinclude template="conv.cfc"> When I ran one of the programs using the function "conv" (which is in conv.cfc) I got an error that the function conv could not be found. After I hunted around the internet for ideas I tried this: <cfapplication name = "Moxware" sessionmanagement = "Yes"> <cfset lang = "LU"> <cfset x127 = Chr(127)> <cfset mmox = 'Moxware'> <cffunction name="onRequestStart" output="true" returntype="void"> <cfinclude template="conv.cfc"> </cffunction> That gave me the same error message as before. Can someone explain to me how to do this? Note that the function conv was tested and works just fine.
Instead of using an include, like this... <cfinclude template="conv.cfc"> Try creating an object, like this... <cfscript> MyObject = createObject("component", "conv"); </cfscript> When you want to access a function within that object, try this... <cfscript> SomeValue = MyObject.MyFunction(); </cfscript>
You can include CFM pages into the CFC, but I don't think you can include CFC code into a CFC. So this is possible... <cffunction name="OnRequest" access="public" returntype="void" output="true" hint="Fires after pre page processing is complete."> <cfargument name="TargetPage" type="string" required="true" /> <cfinclude template = "/myMapping/onRequestStart_include.cfm" /> <cfinclude template = "/myMapping/onRequest_include.cfm" /> <cfinclude template = "/myMapping/onRequestEnd_include.cfm" /> </cffunction> Note the use of a mapping (in our case /myMapping), which can help if your doing this in CFC's. If no mapping is needed, just drop that. But probably the best option is to instantiate your CFC from within the Application.cfc and use it. <cfset myConv = createObject("component", "myMapping.conv").init() /> Again using a mapping to get to the CFC. The .init() is not always needed, depends how your CFC is setup. Then presumably conv has methods you want to use (You talk about it as a single function? A cfc is essentially an object, so you create it as an object first and then use it's methods), so then you'd invoke then by using... myConv.functionName() Put another way - Application.cfc is no different from anywhere else in your code. How ever it's invoked and used elsewhere is how you should do it here. The only trick might be pathing to that CFC, which you can do by creating a custom mapping. Also consider going old school (if it is just a function), and using custom-tags. Good luck.
Convert Special Characters to HTML - ColdFusion
I need to convert a lot of special characters to their html format and I am trying to do this with a function that is using ReplaceList but something is wrong with the function or the values I am passing to it. This is the function <cffunction name="HtmlUnEditFormat" access="public" returntype="string" output="no" displayname="HtmlUnEditFormat" hint="Undo escaped characters"> <cfargument name="str" type="string" required="Yes" /> <cfscript> var lEntities = "&##xE7;,&##xF4;,&##xE2;,Î,Ç,È,Ó,Ê,&OElig,Â,«,»,À,É,≤,ý,χ,∑,′,ÿ,∼,β,⌈,ñ,ß,„,´,·,–,ς,®,†,⊕,õ,η,⌉,ó,,>,φ,∠,,α,∩,↓,υ,ℑ,³,ρ,é,¹,<,¢,¸,π,⊃,÷,ƒ,¿,ê, ,∅,∀, ,γ,¡,ø,¬,à,ð,ℵ,º,ψ,⊗,δ,ö,°,≅,ª,‹,♣,â,ò,ï,♦,æ,∧,◊,è,¾,&,⊄,ν,“,∈,ç,ˆ,©,á,§,—,ë,κ,∉,⌊,≥,ì,↔,∗,ô,∞,¦,∫,¯,½,¤,≈,λ,⁄,‘,…,œ,£,♥,−,ã,ε,∇,∃,ä,μ,¼, ,≡,•,←,«,‾,∨,€,µ,≠,∪,å,ι,í,⊥,¶,→,»,û,ο,‚,ϑ,∋,∂,”,℘,‰,²,σ,⋅,š,¥,ξ,±,ℜ,þ,〉,ù,√,,∴,↑,×, ,θ,⌋,⊂,⊇,ü,’,ζ,™,î,ϖ,,〈,˜,ú,¨,∝,ϒ,ω,↵,τ,⊆,›,∏,",,♠"; var lEntitiesChars = "ç,ô,â,Î,Ç,È,Ó,Ê,Œ,Â,«,»,À,É,?,ý,?,?,?,Ÿ,?,?,?,ñ,ß,„,´,·,–,?,®,‡,?,õ,?,?,ó,,>,?,?,?,?,?,?,?,?,³,?,é,¹,<,¢,¸,?,?,÷,ƒ,¿,ê,?,?,?,?,?,¡,ø,¬,à,ð,?,º,?,?,?,ö,°,?,ª,‹,?,â,ò,ï,?,æ,?,?,è,¾,&,?,?,“,?,ç,ˆ,©,á,§,—,ë,?,?,?,?,ì,?,?,ô,?,¦,?,¯,½,¤,?,?,?,‘,…,œ,£,?,?,ã,?,?,?,ä,?,¼, ,?,•,?,«,?,?,€,µ,?,?,å,?,í,?,¶,?,»,û,?,‚,?,?,?,”,?,‰,²,?,?,š,¥,?,±,?,þ,?,ù,?,?,?,?,×,?,?,?,?,?,ü,’,?,™,î,?,?,?,˜,ú,¨,?,?,?,?,?,?,›,?,"",?,?"; </cfscript> <cfreturn ReplaceList(arguments.str, lEntities, lEntitiesChars) /> </cffunction> This is how I am calling it: <cfoutput> <cfloop query="local.q" startrow="2"> #HtmlUnEditFormat(consultServiceType)# <br /> </cfloop> </cfoutput> These are the strings I am passing to it: Security? Security Guard® Alarm System© Private Investigator; I am not getting any errors back (I had a cftry in the function before) and the strings come back the same EDIT: I've tried using #FindNoCase('©',consultServiceType)# and is returning 0 so I guess something is wrong with the string I am passing in?
You're using CF11, did you try EncodeForHTML() ?
The accepted answer is the better approach (don't reinvent the wheel), but your function isn't working because you have lEntities and lEntitiesChars mixed up. <cffunction name="HtmlUnEditFormat" access="public" returntype="string" output="no" displayname="HtmlUnEditFormat" hint="Undo escaped characters"> <cfargument name="str" type="string" required="Yes" /> <cfscript> var lEntities = "&##xE7;,&##xF4;,&##xE2;,Î,Ç,È,Ó,Ê,&OElig,Â,«,»,À,É,≤,ý,χ,∑,′,ÿ,∼,β,⌈,ñ,ß,„,´,·,–,ς,®,†,⊕,õ,η,⌉,ó,,>,φ,∠,,α,∩,↓,υ,ℑ,³,ρ,é,¹,<,¢,¸,π,⊃,÷,ƒ,¿,ê, ,∅,∀, ,γ,¡,ø,¬,à,ð,ℵ,º,ψ,⊗,δ,ö,°,≅,ª,‹,♣,â,ò,ï,♦,æ,∧,◊,è,¾,&,⊄,ν,“,∈,ç,ˆ,©,á,§,—,ë,κ,∉,⌊,≥,ì,↔,∗,ô,∞,¦,∫,¯,½,¤,≈,λ,⁄,‘,…,œ,£,♥,−,ã,ε,∇,∃,ä,μ,¼, ,≡,•,←,«,‾,∨,€,µ,≠,∪,å,ι,í,⊥,¶,→,»,û,ο,‚,ϑ,∋,∂,”,℘,‰,²,σ,⋅,š,¥,ξ,±,ℜ,þ,〉,ù,√,,∴,↑,×, ,θ,⌋,⊂,⊇,ü,’,ζ,™,î,ϖ,,〈,˜,ú,¨,∝,ϒ,ω,↵,τ,⊆,›,∏,",,♠"; var lEntitiesChars = "ç,ô,â,Î,Ç,È,Ó,Ê,Œ,Â,«,»,À,É,?,ý,?,?,?,Ÿ,?,?,?,ñ,ß,„,´,·,–,?,®,‡,?,õ,?,?,ó,,>,?,?,?,?,?,?,?,?,³,?,é,¹,<,¢,¸,?,?,÷,ƒ,¿,ê,?,?,?,?,?,¡,ø,¬,à,ð,?,º,?,?,?,ö,°,?,ª,‹,?,â,ò,ï,?,æ,?,?,è,¾,&,?,?,“,?,ç,ˆ,©,á,§,—,ë,?,?,?,?,ì,?,?,ô,?,¦,?,¯,½,¤,?,?,?,‘,…,œ,£,?,?,ã,?,?,?,ä,?,¼, ,?,•,?,«,?,?,€,µ,?,?,å,?,í,?,¶,?,»,û,?,‚,?,?,?,”,?,‰,²,?,?,š,¥,?,±,?,þ,?,ù,?,?,?,?,×,?,?,?,?,?,ü,’,?,™,î,?,?,?,˜,ú,¨,?,?,?,?,?,?,›,?,"",?,?"; </cfscript> <cfreturn ReplaceList(arguments.str, lEntitiesChars, lEntities) /> </cffunction> <cfoutput>#htmluneditformat("Company?")#</cfoutput> Further, #ReplaceList()# in both ACF and Railo/Lucee recurse through the list, which means the order of the lists matter. With the fix I suggest, ? becomes ≤. A fix to this would be to move & and the code for it to the beginning of each list. Consider this simple piece of code <cfoutput>#replacelist("abc","a,b","b,c")#</cfoutput> You would probably expect the output to be "bcc", but that's not how ReplaceList works, it works something more like this <cfset sx = "abc"> <cfset listf = "a,b"> <cfset listr = "b,c"> <cfloop from="1" to="#listlen(listf)#" index="i"> <cfset sx = replace(sx,listgetat(listf,i),listgetat(listr,i),"ALL")> <!--- iteration one replaces a with b to make bbc ---> <!--- iteration two replaces b with c to make ccc ---> </cfloop> I'm not suggesting that someone use this code when CF has the built in functionality, I'm merely explaining why it doesn't work and a pitfall of ReplaceList().
How can I strip this URL of everything before "http://"?
I'm doing some web scraping with ColdFusion and mostly everything is working fine. The only other issues I'm getting is that some URL's come through with text behind them that is now causing errors. Not sure what's causing it, but it's probably my regex. Anyhow, there's a distinct pattern where text appears before the "http://". I'd like to simply remove everything before it. Any chance you could help? Take this string for example: "I'M OBSESSED WITH MY BEAUTIFUL FRIEND" src="http://scs.viceland.com/feed/images/uk_970014338_300.jpg I'd much appreciate your help as regex isn't something I've managed to make time for - hopefully I will some day! Thanks. EDIT: Hi, I thought it might be helpful to post my entire function, since it could be my initial REGEX that is causing the issue. Basically, the funcion takes one argument. In this case, it's the contents of a HTML file (via CFHTTP). In some cases, every URL looks and works fine. If I try digg.com for example it works...but it dies on something like youtube.com. I guess this would be down to their specific HTML formatting. Either way, all I ever need is the value of the SRC attribute on image tags. Here's what I have so far: <cffunction name="extractImages" returntype="array" output="false" access="public" displayname="extractImages"> <cfargument name="fileContent" type="string" /> <cfset var local = {} /> <cfset local.images = [] /> <cfset local.imagePaths = [] /> <cfset local.temp = [] /> <cfset local.images = reMatchNoCase("<img([^>]*[^/]?)>", arguments.fileContent) /> <cfloop array="#local.images#" index="local.i"> <cfset local.temp = reMatchNoCase("(""|')(.*)(gif|jpg|jpeg|png)", local.i) /> <cfset local.path = local.temp /> <cfif not arrayIsEmpty(local.path)> <cfset local.path = trim(replace(local.temp[1],"""","","all")) /> <cfset arrayAppend(local.imagePaths, local.path) /> </cfif> <cfif isValid("url", local.path)> <cftry> <cfif fileExists(local.path)> <cfset arrayAppend(local.imagePaths, local.path) /> </cfif> <cfcatch type="any"> <cfset application.messagesObject.addMessage("error","We were not able to obtain all available images on this page.") /> </cfcatch> </cftry> </cfif> </cfloop> <cfset local.imagePaths = application.udfObject.removeArrayDuplicates(local.imagePaths) /> <cfreturn local.imagePaths /> </cffunction> This function WORKS. However, on some sites, not so. It looks a bit over the top but much of it is just certain safeguards to make sure I get valid image paths. Hope you can help. Many thanks again. Michael
Take a look at ReFind() or REFindNoCase() - http://cfquickdocs.com/cf9/#refindnocase Here is a regex that will work. <cfset string = 'IM OBSESSED WITH MY BEAUTIFUL FRIEND" src="http://scs.viceland.com/feed/images/uk_970014338_300.jpg' /> <cfdump var="#refindNoCase('https?://[-\w.]+(:\d+)?(/([\w/_.]*)?)?',string, 1, true)#"> You will see a structure returned with a POS and LEN keys. Use the first element in the POS array to see where the match starts, and the first element in the LEN array to see how long it is. You can then use these values in the Mid() function to grab just that matching URL.
I'm not familiar with ColdFusion, but it seems to me that you just need a regex that looks for http://, then any number of characters, then the end of the string.
How can I retrieve a (quasi) Array from a URL string?
I would like to achieve something I can easily do in .net. What I would like to do is pass multiple URL parameters of the same name to build an array of those values. In other words, I would like to take a URL string like so: http://www.example.com/Test.cfc?method=myArrayTest&foo=1&foo=2&foo=3 And build an array from the URL parameter "foo". In .net / C# I can do something like this: [WebMethod] myArrayTest(string[] foo) And that will build a string array from the variable "foo". What I have done so far is something like this: <cffunction name="myArrayTest" access="remote" returntype="string"> <cfargument name="foo" type="string" required="yes"> This would output: 1,2,3 I'm not thrilled with that because it's just a comma separated string and I'm afraid that there may be commas passed in the URL (encoded of course) and then if I try to loop over the commas it may be misinterpreted as a separate param. So, I'm stumped on how to achieve this. Any ideas?? Thanks in advance!!
Edit: Sergii's method is more versatile. But if you are parsing the current url, and do not need to modify the resulting array, another option is using getPageContext() to extract the parameter from the underlying request. Just be aware of the two quirks noted below. <!--- note: duplicate forces the map to be case-INsensitive ---> <cfset params = duplicate(getPageContext().getRequest().getParameterMap())> <cfset quasiArray = []> <cfif structKeyExists(params, "foo")> <!--- note: this is not a *true* CF array ---> <!--- you can do most things with it, but you cannot append data to it ---> <cfset quasiArray = params["foo"]> </cfif> <cfdump var="#quasiArray#">
Well, if you're OK with parsing the URL, following "raw" method may work for you: <cffunction name="myArrayTest" access="remote" output="false"> <cfset var local = {} /> <!--- parse raw query ---> <cfset local.args = ListToArray(cgi.QUERY_STRING, "&") /> <!--- grab only foo's values ---> <cfset local.foo = [] /> <cfloop array="#local.args#" index="local.a"> <cfif Left(local.a, 3) EQ "foo"> <cfset ArrayAppend(local.foo, ListLast(local.a, "=")) /> </cfif> </cfloop> <cfreturn SerializeJSON(local.foo) /> </cffunction> I've tested it with this query: ?method=myArrayTest&foo=1&foo=2&foo=3,3, looks to work as expected. Bonus. Railo's top tip: if you format the query as follows, this array will be created automatically in URL scope ?method=myArrayTest&foo[]=1&foo[]=2&foo[]=3,3.
listToArray( arguments.foo ) should give you what you want.
Can a ColdFusion cfc method determine its own name?
I am creating an API, and within each method I make a call to a logging method for auditing and troubleshooting. Something like: <cffunction name="isUsernameAvailable"> <cfset logAccess(request.userid,"isUsernameAvailable")> ...... </cffunction> I'd like to avoid manually repeating the method name. Is there a way to programatically determine it? I've looked at GetMetaData() but it only returns info about the component (including all the methods) but nothing about which method is currently being called.
So now 3 ways. If you are using ColdFusion 9.0 or higher there is now a function named GetFunctionCalledName(). It will return what you are looking for. http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WS7cc222be8a31a47d-6e8b7083122cebfc8f2-8000.html OR Use ColdSpring and Aspect Oriented Programming (http://www.coldspringframework.org/coldspring/examples/quickstart/index.cfm?page=aop) to handle this for you. OR Use a cfthrow to generate a stack trace that has the information for you: <cffunction name="determineFunction" output="FALSE" access="public" returntype="string" hint="" > <cfset var functionName ="" /> <cfset var i = 0 /> <cfset var stackTraceArray = "" /> <cftry> <cfthrow /> <cfcatch type="any"> <cfset stacktraceArray = ListToArray(Replace(cfcatch.stacktrace, "at ", " | ", "All"), "|") /> <!---Rip the right rows out of the stacktrace ---> <cfloop index ="i" to="1" from="#ArrayLen(stackTraceArray)#" step="-1"> <cfif not findNoCase("runFunction", stackTraceArray[i]) or FindNoCase("determineFunction", stackTraceArray[i])> <cfset arrayDeleteAt(stackTraceArray, i) /> </cfif> </cfloop> <!---Whittle down the string to the func name ---> <cfset functionName =GetToken(stacktraceArray[1], 1, ".") /> <cfset functionName =GetToken(functionName, 2, "$")/> <cfset functionName =ReplaceNoCase(functionName, "func", "", "once")/> <cfreturn functionName /> </cfcatch> </cftry></cffunction> My recommendation would be use getFunctionCalledName, or if not on CF 9 ColdSpring, as it will probably buy you some other things.
I agree w/ tpryan. ColdSpring makes this very easy. However, here is another alternative. Instead of parsing the stack trace, you can parse the CFC file itself. <cffunction name="foo" displayname="foo" hint="this is just a test function" access="public" returntype="string"> <cfset var test = getFunctionName(getMetaData().path, getPageContext().getCurrentLineNo()) /> <cfreturn test /> </cffunction> <cffunction name="getFunctionName" hint="returns the function name based on the line number" access="public" returntype="string"> <cfargument name="filepath" type="string" required="true" /> <cfargument name="linenum" type="any" required="true" /> <cfset var line = "" /> <cfset var functionName = "" /> <cfset var i = 1 /> <!---- loop over CFC by line ----> <cfloop file="#ARGUMENTS.filepath#" index="line"> <cfif findNoCase('cffunction', line, 1)> <cfset functionName = line /> </cfif> <cfif i EQ ARGUMENTS.linenum><cfbreak /></cfif> <cfset i++ /> </cfloop> <!---- parse function name ----> <cfset functionName = REMatchNoCase("(\bname=[""|'])+[a-z]*[""|']", functionName) /> <cfset functionName = REMatchNoCase("[""']+[a-z]*[""']", functionName[1]) /> <cfset functionName = ReReplaceNoCase(functionName[1], "[""']", "", "all") /> <!---- return success ----> <cfreturn functionName /> </cffunction> The above is written for ColdFusion 8. CFLOOP added support for looping over files line by line (and doesn't read the entire file into memory). I did a few tests comparing the stack trace method vs. file parsing. Both performed equally well on a small CFC being called directly from a single CFM template. Obviously if you have very large CFCs the parsing method might be a bit slower. On the other hand, if you have a large stack trace (like if you are using any of the popular frameworks) then file parsing may be faster. -= Viva ColdFusion =-
Well you might try this: <cffunction name="getFunctionName" returntype="any"> <cfset meta =getMetaData(this)> <cfreturn meta.functions[numberOfFunction].name> </cffunction> I've tried various things, and this is not accurate as the functions seem to be added to the array of functions in reverse alphabetical order. This seems limiting (and not solving the problem). I would imagine some native java code could be invoked, but i'm going to need to look into that. This and This look like interesting reading on related internal functions. Re: The other answer on coldspring. I found this in depth article on function metadata with coldspring. Related question : How to get the name of the component that’s extending mine in ColdFusion?
I thought of another way that could work. Setup an OnMissingMethod something like this: <cffunction name="onMissingMethod"> <cfargument name="missingMethodName" type="string"> <cfargument name="missingMethodNameArguments" type="struct"> <cfset var tmpReturn = ""> <cfset var functionToCallName = "Hidden" & Arguments.missingMethodName> <cfset arguments.missingMethodArguments.calledMethodName = Arguments.missingMethodName> <cfinvoke method="#functionToCallName#" argumentcollection="#Arguments.missingMethodArguments#" returnvariable="tmpReturn" /> <cfreturn tmpReturn> </cffunction> Then name each of the regular methods with a prefix ("Hidden" in this example), and mark them as private. So my initial example would become: <cffunction name="HiddenisUsernameAvailable" access="private"> <cfset logAccess(request.userid,Arguments.calledMethodName)> ...... </cffunction> Now all the calls will be intercepted by onMissingMethod, which will add the method name to the arguments that get passed to the real method. The downsides I see to this are that introspection no longer works properly, and you must be using named arguments to call all your functions. If you are not using named arguments, the args will randomly change order in the missingMethodNameArguments structure.
getFunctionCalledName() gives you the name of the active method.