ListRemoveDuplicates result has a comma at the end - list
When using listRemoveDuplicates in Lucee, it removes the duplicate values but still leaves the delimiter in end of the value.
Ex:
<cfset myUsers = 'sathish,sathish'>
<cfset removeDups = listRemoveDuplicates(myUsers)>
<cfdump var="#removeDups#" />
This produces the output:
sathish,
However, when checking this same function with Adobe ColdFusion, it produces the correct value (no comma at the end):
sathish
How can I omit the comma delimiter in Lucee?
Surfing the Lucee documentation, I found the ListCompact() function which returns the correct value.
<cfset myUsers = 'sathish,sathish'>
<cfset removeDups = listCompact(listRemoveDuplicates(myUsers))>
<cfdump var="#removeDups#" />
You can also create a custom function like this:
<cffunction name="UDF_listRemoveDuplicates" access="public">
<cfargument name="list" type="string" required="yes">
<!--- Hash Map to maintain the order --->
<cfset local.hashMap = createObject("java", "java.util.LinkedHashMap").init()>
<!--- Add list values as key to Hash Map --->
<cfloop list="#arguments.list#" index="local.value" delimiters=",">
<cfset local.hashMap[local.value] = 1>
</cfloop>
<!--- Return list of Keys --->
<cfreturn structKeyList(local.hashMap, ",")>
</cffunction>
<cfoutput>#UDF_listRemoveDuplicates("sathish,sathish")#</cfoutput>
This should work on both. (Not tested on Lucee)
Related
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().
Coldfusion 10 - Element [n] is undefined in a Java object of type class coldfusion.runtime.Array
I recently upgraded a system from CF8 to CF10 and have one bug that I'm having problems tracking down. It has to do with a remote API call that gets a JSON string back and that string then gets converted to a query object. That's where I'm coming across the error: Element [n] is undefined in a Java object of type class coldfusion.runtime.Array. The problem is in the function that converts the string to a query. <cffunction name="CFjsonToQuery" access="public" returntype="query" output="no"> <cfargument name="cfData" required="yes" type="struct"/> <cfset var LOCAL = {}/> <cfset LOCAL.tmpQry = QueryNew( ArrayToList(ARGUMENTS.cfData.Data.COLUMNS) ) /> <cfloop index = "i" from = "1" to = "#ArrayLen(ARGUMENTS.cfData.Data.DATA)#"> <cfset LOCAL.Row = QueryAddRow(LOCAL.tmpQry) /> <cfloop index="k" from="1" to="#ArrayLen(ARGUMENTS.cfData.Data.DATA[i])#"> <cfset LOCAL.colName = ARGUMENTS.cfData.Data.COLUMNS[K]/> <cfset QuerySetCell(LOCAL.tmpQry,LOCAL.colName,ARGUMENTS.cfData.Data.DATA[i][k],LOCAL.Row)/> </cfloop> </cfloop> <cfreturn LOCAL.tmpQry/> </cffunction> Anywhere the JSON returns 'null' (i.e. "...","19107-3609",null,null,null,"...") the error is thrown. I've tried using isNull to check if it's null in the cfloop: <cfif isNull(ARGUMENTS.cfData.Data.DATA[i][k])> <cfset ARGUMENTS.cfData.Data.DATA[i][k] = 'I AM NULL'/> </cfif> EDIT - here's a simplified example - the issue is the way the newer deserializeJson() works I believe: <cfset jstr = '{"SUCCESS":true,"ERRORS":[],"DATA":{"COLUMNS":["ID","FNAME","LNAME"],"DATA":[[390132,"steve",null]]}}'/> <cfset cfData = deserializeJson(jstr) /> <cfloop index = "i" from = "1" to = "#ArrayLen(cfData.Data.DATA)#"> <cfset Row = QueryAddRow(tmpQry) /> <cfloop index="k" from="1" to="#ArrayLen(cfData.Data.DATA[i])#"> <cfset colName = cfData.Data.COLUMNS[K]/> <cfset QuerySetCell(tmpQry,colName,cfData.Data.DATA[i][k],Row)/> </cfloop> </cfloop> I've tried all sorts of tests for empty string, isNull etc. and I'm still not sure how to get the query object built if deserializejson returns: [undefined array element] Element 3 is undefined in a Java object of type class coldfusion.runtime.Array. This does seem to work: <cfset cfData = deserializeJson(returnData,'FALSE') /> <cfset qryData = cfData.data /> This lets me then use qryData as if it were a normal cfquery.
You can do a check if the element is undefined using the CF Function ArrayIsDefined(array, elementIndex)
What I've done for now is add 'FALSE' to the deserializeJSON strictMapping flag and that seems to automatically create a query object? I'll admit though this is getting into the underpinnings of CF10 and I could be wrong on that. I'll update my code above for visual clarity.
Consuming a webservice code simplification
UPDATED CODE TO LATEST ITERATION The following function consumes a webservice that returns address details based on zip code (CEP). I'm using this function to parse the xml and populate an empty query with the address details. I would like to know if there is a more elegant way to achieve the same result. It seems to be a waste to create an empty query and populate it... Any ideas could my method be modified or the code factored/simplified? <!--- ****** ACTION: getAddress (consumes web-service to retrieve address details) ---> <cffunction name="getAddress" access="remote" returntype="any" output="false"> <!--- Defaults: strcep (cep (Brazilian zip-code) string webservice would look for), search result returned from webservice ---> <cfargument name="cep" type="string" default="00000000"> <cfset var searchResult = ""> <cfset var nodes = ""> <cfset var cfhttp = ""> <cfset var stateid = 0> <cfset var tmp = structNew()> <!--- Validate cep string ---> <cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8> <cftry> <!--- Consume webservice ---> <cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#"></cfhttp> <cfset searchResult = xmlparse(cfhttp.FileContent)> <cfset nodes = xmlSearch(searchResult, "//tbCEP")> <!--- If result insert address data into session struct ---> <cfif arrayLen(nodes)> <cfset tmp.streetType = nodes[1].logradouro.XmlText> <cfset tmp.streetName = nodes[1].nome.XmlText> <cfset tmp.area = nodes[1].bairro.XmlText> <cfset tmp.city = nodes[1].cidade.XmlText> <cfset tmp.state = nodes[1].uf.XmlText> <cfset tmp.cep = arguments.cep> <!--- Get state id and add to struct ---> <cfset stateid = model("state").findOneByStateInitials(tmp.state)> <cfset tmp.stateid = stateid.id> <cfreturn tmp> </cfif> <!--- Display error if any ---> <cfcatch type="any"> <cfoutput> <h3>Sorry, but there was an error.</h3> <p>#cfcatch.message#</p> </cfoutput> </cfcatch> </cftry> </cfif> </cffunction> <!--- ****** END ACTION getAddress ---> The calling code: <!--- Get address data based on CEP ---> <cfset session.addressData = getAddress(cep=params.newMember.cep)>
I can't test this because I don't have an example XML file / CEP to test with, but here is a minor rewrite that addresses four things: Instead of using cfparam and some strange "params" structure, you should pass the CEP into the function as an argument. The function shouldn't directly modify session data. Instead, you should return the result and let the calling code assign it to the session (or wherever else it might be needed). I'll show this in a 2nd code example. Cache the xml result per CEP -- assuming this doesn't change often. (You'll have to improve it further if you want time-based manual cache invalidation, but I can help add that if necessary) Don't use StructInsert. It's not necessary and you're just writing it the long way for the sake of writing it the long way. There is no benefit. Again, this isn't tested, but hopefully it's helpful: <cffunction name="getAddress" access="remote" returntype="any" output="false"> <cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) ---> <cfset var searchResult = ""> <cfset var nodes = ""> <cfset var cfhttp = ""> <cfset var stateid = 0 /> <cfset var tmp = structNew()> <!--- Validate cep string ---> <cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8> <cfif not structKeyExists(application.cepCache, arguments.cep)><!--- or cache is expired: you'd have to figure this part out ---> <!--- Consume webservice ---> <cftry> <cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" /> <cfset searchResult = xmlparse(cfhttp.FileContent)> <cfset nodes = xmlSearch(searchResult, "//tbCEP")> <!--- If result insert address data into session struct ---> <cfif arrayLen(nodes)> <cfset tmp.streetType = nodes[1].logradouro.XmlText /> <cfset tmp.streetName = nodes[1].nome.XmlText /> <cfset tmp.area = nodes[1].bairro.XmlText /> <cfset tmp.city = nodes[1].cidade.XmlText /> <cfset tmp.state = nodes[1].uf.XmlText /> <cfset tmp.cep = arguments.cep /> <!--- Get state id and add to struct ---> <cfset stateid = model("state").findOneByStateInitials(session.addressData.state)> <cfset tmp.stateid = stateid.id /> </cfif> <cfreturn duplicate(tmp) /> <!--- Display error if any ---> <cfcatch type="any"> <h3>Sorry, but there was an error.</h3> <p>#cfcatch.message#</p> </cfcatch> </cftry> <cfelse> <!--- cache exists and is not expired, so use it ---> <cfreturn duplicate(application.cepCache[arguments.cep]) /> </cfif> </cfif> <!--- <!--- Redirect to page two of the sign up process ---> <cfset redirectTo(controller="assine", action="perfil")> ---> </cffunction> Notice that I commented out the redirect you had at the end. That's because with my function, you'll be returning a value, and the redirect should be done after that, like so: <cfset session.addressData = getAddress("some-CEP-value") /> <cfset redirectTo(controller="assine", action="perfil")> If you're going to leave out the caching (As you say in a comment you will), then here is a version that makes no attempt at caching: <cffunction name="getAddress" access="remote" returntype="any" output="false"> <cfargument name="cep" type="string" default="00000000" /><!--- (cep (Brazilian zip-code) string webservice would look for) ---> <cfset var searchResult = ""> <cfset var nodes = ""> <cfset var cfhttp = ""> <cfset var stateid = 0 /> <cfset var tmp = structNew()> <!--- Validate cep string ---> <cfif IsNumeric(arguments.cep) AND Len(arguments.cep) EQ 8> <!--- Consume webservice ---> <cftry> <cfhttp method="get" url="http://www.bronzebusiness.com.br/webservices/wscep.asmx/cep?strcep=#arguments.cep#" /> <cfset searchResult = xmlparse(cfhttp.FileContent)> <cfset nodes = xmlSearch(searchResult, "//tbCEP")> <!--- If result insert address data into session struct ---> <cfif arrayLen(nodes)> <cfset tmp.streetType = nodes[1].logradouro.XmlText /> <cfset tmp.streetName = nodes[1].nome.XmlText /> <cfset tmp.area = nodes[1].bairro.XmlText /> <cfset tmp.city = nodes[1].cidade.XmlText /> <cfset tmp.state = nodes[1].uf.XmlText /> <cfset tmp.cep = arguments.cep /> <!--- Get state id and add to struct ---> <cfset stateid = model("state").findOneByStateInitials(session.addressData.state)> <cfset tmp.stateid = stateid.id /> </cfif> <cfreturn duplicate(tmp) /> <!--- Display error if any ---> <cfcatch type="any"> <h3>Sorry, but there was an error.</h3> <p>#cfcatch.message#</p> </cfcatch> </cftry> </cfif> <!--- <!--- Redirect to page two of the sign up process ---> <cfset redirectTo(controller="assine", action="perfil")> ---> </cffunction> Note that I did leave in the use of duplicate(). What this does is return a duplicate of the object (in this case, the struct). This is much more important when you start to work on applications where you're passing complex values into and out of functions over and over again. Using duplicate() causes things to be passed by value instead of by reference. It may not bite you in this case, but it's a good habit to get into. I would also still use the function argument and return a value -- but it's arguable that this is my personal preference. In a way it is. I believe that a function should be fully encapsulated; a total "black box". You give it some input and it gives you back some output. It should not modify anything outside of itself. (Again, just my opinion.) So assuming you're using this function as part of a larger multi-step process, you should still use it the same way I've described above. The only difference is that you're setting the session variable outside of the function body. Just as previously: <cfset session.addressData = getAddress("some-CEP-value") /> <cfset redirectTo(controller="assine", action="perfil")>
That looks pretty straightforward. CF doesn't (yet?) have any magical XML-to-Query functions, but that would be pretty cool. If you wanted, you could probably write up an XSL transform to go from XML to WDDX so that you could use the cfwddx tag ... but that's probably putting the cart before the horse. You need to move your arrayLen() if block into the try block. As it stands, if the cfhttp tag throws an error, the nodes variable will be a string and not an array, thus causing the arrayLen() to throw another error. Minor nitpick: I wouldn't add a row to the query until inside the arrayLen() block. That way, the calling code can check recordCount to see if the result was a success. Beyond that ... that's pretty much how it's done.
Slurp an INI file in CFML
Could anyone find a way of improving this code a bit? I want to read in an INI file in one felt swoop and create a corresponding data structure. <cfset INIfile = expandPath(".") & "\jobs.ini"> <cfset profile = GetProfileSections(INIfile)> <cfloop collection="#profile#" item="section"> <cfloop list="#profile[section]#" index="entry"> <cfset app.workflow[section][entry]=GetProfileString(INIfile, section, entry) > </cfloop> </cfloop>
I don't believe you can improve this using CFML power. Do you need to parse huge ini files? If not, why would you like to improve your code, it looks pretty straightforward for me. Other possible (though common for CF) solution is to try pure Java. See this SO thread for pure Java examples. P.S. BTW, in case of special performance needs you should consider using another storage for configuration. Simple SELECT query to the old good MySQL can be much faster for large datasets.
To expand on ryber's comment, you might consider using this approach instead. I'm assuming you're using CF8.01 or later, as I make use of nested implicit structure notation. This could easily be converted to CF7/6/etc syntax, but wouldn't be as clean or concise. Again, this only applies if your ini file isn't used by any other applications or people, and doesn't need to be in ini format. settings.cfm: <cfset variables.settings = { fooSection = { fooKey = 'fooVal', fooNumber = 2, }, fooSection2 = { //... }, fooSection3 = { //... } } /> Application.cfc: (only the onApplicationStart method) <cffunction name="onApplicationStart"> <cfinclude template="settings.cfm" /> <cfset application.workflow = variables.settings /> <cfreturn true /> </cffunction> In addition, I've use the CFEncode application to encrypt the contents of settings.cfm. It won't protect you from someone who gets a copy of the file and wants to see what its encrypted contents are (the encryption isn't that strong, and there are ways to see the contents without decrypting it), but if you just want to keep some nosy people out, it adds a little extra barrier-to-entry that might deter some people. Update: Since you just left a comment that says you are on CF7, here's native CF7 syntax: settings.cfm: <cfset variables.settings = StructNew() /> <cfset variables.settings.fooSection = StructNew() /> <cfset variables.settings.fooSection.fooKey = 'fooVal' /> <cfset variables.settings.fooSection.fooNumber = 2 /> <!--- ... ---> Alternatively, you could use JSONUtil and CFSaveContent to continue to use a JSON-looking approach (similar to my original syntax), but on CF7: <cfsavecontent variable="variables.jsonSettings"> { fooSection = { fooKey = 'fooVal', fooNumber = 2, }, fooSection2 = { //... }, fooSection3 = { //... } }; </cfsavecontent> <cfset variables.settings = jsonUtil.deserializeFromJSON(variables.jsonSettings) />
I created a CFC that I use in a bunch of apps. You give it an ini filepath when you init it and it creates a structure based on the ini file. It also optionally keeps the structure flat or creates sub-structures based on the [Sections] in the ini file. You can then either use its getSetting() method to get individual methods or getAllSettings() to return the entire structure. You may find it helpful. <cfcomponent hint="converts INI file to a structure"> <cfset variables.settings=structNew() /> <cffunction name="init" access="public" output="false" returntype="any"> <cfargument name="configurationFile" type="string" required="yes" /> <cfargument name="useSections" default="false" type="boolean" /> <cfset var local=structNew() /> <cfif fileExists(arguments.configurationFile)> <!--- Get the [sections] in the .INI file ---> <cfset local.sectionStruct=getProfileSections(arguments.configurationFile) /> <!--- Loop over each of these sections in turn ---> <cfloop collection="#local.sectionStruct#" item="local.item"> <cfset local.workingStruct=structNew() /> <cfloop list="#local.sectionStruct[local.item]#" index="local.key"> <!--- Loop over the keys in the current section and add the key/value to a temporary structure ---> <cfset local.workingStruct[local.key]=getProfileString(arguments.configurationFile,local.item,local.key) /> </cfloop> <cfif arguments.useSections> <!--- Copy the temporary structure to a key in the setting structure for the current section ---> <cfset variables.settings[local.item]=duplicate(local.workingStruct) /> <cfelse> <!--- Append the temporary structure to the setting structure ---> <cfset structAppend(variables.settings,local.workingStruct,"yes") /> </cfif> </cfloop> <cfelse> <cfthrow message="Configuration file not found. Must use fully-qualified path." extendedinfo="#arguments.configurationFile#" /> </cfif> <cfreturn this> </cffunction> <cffunction name="getAllSettings" access="public" output="false" returntype="struct"> <cfreturn variables.settings> </cffunction> <cffunction name="getSetting" access="public" output="false" returntype="string"> <cfargument name="settingName" required="yes" type="string" /> <cfset var returnValue="" /> <cfif structKeyExists(variables.settings,arguments.settingName)> <cfset returnValue=variables.settings[arguments.settingName] /> <cfelse> <cfthrow message="No such setting '#arguments.settingName#'." /> </cfif> <cfreturn returnValue> </cffunction> </cfcomponent>
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.