I am trying to create an array of structures, in my Application.cfm file, which can then be appended to in further pages. I am following the EasyCFM tutorial #173 by Charlie. I am using it this way:
<cfset session.box_status = arrayNew(1) />
<cfset session.box_status[1] = structNew() />
<cfset session.box_status[1].partner_id = '0' />
<cfset session.box_status[1].partner_username = '' />
<cfset session.box_status[1].status = '0' />
In my page, I am appending to the structure like so:
<cfloop from="1" to="#arrayLen(session.box_status)#" index="i">
<cfset session.box_status[i].partner_id = ArrayAppend(i,FORM.partner_id) />
<cfset session.box_status[i].partner_username = ArrayAppend(i,FORM.partner_username) />
<cfset session.box_status[i].status = ArrayAppend(i,FORM.box_status) />
</cfloop>
But am getting an error:
The web site you are accessing has experienced an unexpected error.
Please contact the website administrator.
The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request
Object of type class java.lang.Double cannot be used as an array
In addition to Scott's comments, you need to clarify what you are actually trying to achieve. The question asks about appending a new item, yet it looks as if parts of your code attempt to overwrite the existing structure values in position session.box_status[1].
If you really want to append a new structure to the array, there is no reason to loop. Simply create an empty structure:
<cfset newItem = structNew() />
... populate it with some values:
<cfset newItem.partner_id = FORM.partner_id>
... etcetera
Then append the new structure to the array. Notice, the code below does not care about the result of ArrayAppend. That is because the function modifies the array in place, and only returns true/false depending on whether the action was successful.
<cfset ArrayAppend(session.box_status, newItem)>
Update:
That said, the tutorial you are using was obviously written for an older version of CF. As #cfqueryparam pointed out, later versions support a shorthand for creating arrays and structures. Instead of using structNew(), you could simply do this:
<cfset newItem = { partner_id = FORM.partner_id, ... etectera }>
The first argument in arrayAppend() needs to be the array to which you are appending something, in your example, you are using i - which is the counter of your loop - which is a number, not an array.
Note that a common error is to pass the array name, but forget to put the pound symbols. For me, when I make the error of saying
<cfloop array="myAry" index="aryElement">
instead of the proper expression
<cfloop array="#myAry#" index="aryElement">
then the debug message java.lang.string cannot be used as an array is issued.
Related
I used CFBuilder "create CFC" plugin to create a services for a table, so i could play around with OOD. Now I am struggling to use the "update" function generated in a dynamic manner.
I call a cfc, to get the structure of the account, passing an ID.
<cfinvoke component="cfc.Account.accountService" method="getAccount" returnvariable="Account" AccountID="#session.auth.AccountID#">
I could call the update function using a manual bit of code.
<cfset Account.setAccountFirstname('#form.AccountFirstname#')>
That works fine, but I want to dynamically update the structure based on data from a form. So I figured loop the fields in the form and produce the following
<!--- Dynanic call of submitted fields --->
<cfloop list="#form.FieldNames#" index="i">
<cfset Account.set[i]('#Evaluate('#i#')#')>
</cfloop>
Now of course that does not work! any ideas what would work? Or a better way to handle it?
What you are trying to do with invoke wont work, this is because you are passing the attribute as a standalone component argument (I.e the class path) you need to pass in the object instance instead.
Edit to add:
<cfset account = new Account()/>
<cfset data = {
accountId = session.auth.AccountID
}/>
<cfset fieldNames = listToArray(form.fieldNames)/>
<cfif ! arrayIsEmpty(fieldNames)>
<cfloop array="#fieldNames#" index="fieldName">
<cfinvoke
component="#account#"
method="set#FieldName#"
returnVariable="methodResult"
argumentCollection="#data#"
/>
</cfloop>
</cfif>
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.
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.
Let's say i've just parsed someone else's XML document which is a response to an API request. I want to know if a value nested deep inside exists. If my API request worked, it will be in the same place every time. If my API request fails, the root of the XML is very different.
If I try <cfif structKeyExists(myStruct.level1.level2.level3, 'myTarget')> on a failed api request, I get the fatal error: Element LEVEL1.LEVEL2 is undefined in MYSTRUCT.
Of course, I could try to depend on the root level of the XML telling me of success or failure, and not looking for the result if it failed, but... barring that solution, what should i do?
Do i need to check for the existence of each level of the struct? As in:
<cfif structKeyExists(myStruct, 'level1')
and structKeyExists(myStruct.level1, 'level2')
and structKeyExists(myStruct.level1.level2, 'level3')
and structKeyExists(myStruct.level1.level2.level3, 'myTarget')>
<!--- ... --->
</cfif>
This is not a real-world problem, this is just something i've faced too many times. Please don't tell me solutions that involve changing the API or solutions like those in the third paragraph.
Thanks!
edit: i should have mentioned why i can't use isDefined() - some of the keys do not have syntactically valid names, so isDefined() throws an error, eg myStruct.level1[42].level3
XMLSearch
I would use the parsed XML document (i.e. xmlDoc) and XMLSearch:
<cfset xmlDoc = xmlParse(responseData)>
<cfset nodes = XmlSearch(xmlDoc, '/level1/level2/level3/myTarget')>
<cfif arrayLen(nodes)>
<!--- do something, you have the "nodes" array to work with too --->
</cfif>
xpath for XMLSearch() assumes the structure keys are nodes. You would need to modify accordingly if, for instance, 'myTarget' is an attribute of a node.
StructFindKey
Another way of doing this would be StructFindKey.
<cfset result = structFindKey(myStruct, "myTarget")>
<cfif arrayLen(result) AND result.path EQ "level1.level2.level3">
<!--- do something --->
</cfif>
Conclusion
Haven't tested, but I believe either will be faster than using IsDefined() or a try-catch block. Has the advantage over XMLValidate() of not needing a DTD. And, even with a DTD, the node you want may be defined as optional, so it could still validate.
You could validate the XML against a DTD to make sure the document was in the right format. XmlParse() and XmlValidate() both take a DTD as a parameter.
<cfset validateResult = XmlValidate(myXmlDocument, myDTD)>
<cfif validateResult.status>
<!--- xml is valid continue processing --->
<cfelse>
<!--- xml did not validate handle the error --->
</cfif>
Personally I wouldn't go crazy checking for every level of a 'deep' structure like this. I would presume that if the top level exists the rest of the document will be as you expect, and I'd just address the document from there.
If you wanted you could perhaps try to address the value in your struct and wrap it in a try/catch. That way you can handle any errors at any 'level' in the same way.
<cftry>
<cfset myVar = myStruct.level1.level2.level3 />
<cfcatch type="any">
<!--- Handle error --->
</cfcatch>
</cftry>
Hope that helps some.
I know I'm going to get booed off the stage here, but this is where isDefined() can save you a lot of typing:
<cfif isDefined(structKeyExists(myStruct.level1.level2.level3)>
<!--- do something --->
</cfif>
I know this is a year old, but I'm going to put in an answer here. I struggled for a good long time with this one, till I found a simple solution. If I know the structure of the XML already, a simple IsDefined works to test if the node or node attribute exists. I don't think most people know you can do this, or have tried and failed because they didn't include single quotes in the IsDefined function.
So say I grab some user xml from a web service somewhere and want to display the user's ID.
<cfhttp url="https://mycompany.com/mywebservices/getusers" username="me" password="mysecret">
<cfset userXMLDoc = XMLParse(ToString(cfhttp.FileContent).trim())>
<cfif IsDefined('userXMLDoc.Data.Record.User.XmlAttributes.id')>
<cfdump var="#userXMLDoc.Data.Record.User.XmlAttributes.id#">
<cfelse>
<cfoutput>Failed: No User ID found</cfoutput>
</cfif>
How Can I get the URL parameter & Value in Coldfusion?
for Ex:-
my URL is
test.cfm?par1=val1&par2=val2&par3=val3
Is it possible to get the second parameter and its value directly?
with <cfset param='#url.par2#'> I can get value of par2,
But my parameters are dynamicically generated from other page and passed to here (par2 may be next time abc2,xyz2 etc..)
So I think better way is to get the parameter and Value in 2nd Possition(Possition dont change always).
Any Idea How can I get it ?
Thanks in advance
You can also access the url scope as a struct, so you could get:
<cfset param2 = url['param2'] />
This is useful if you might have a naming convention for a bunch of fields. Say you're collecting names and emails like so:
email1=foo#bar.com&name1=Fred&email2=xxx#yyy.com&name2=Sally
You could write something like:
<cfloop condition="someCondition">
<cfset email = url['email' & i] />
<cfset name = url['name' & i] />
<!--- Do something --->
<cfset i++ />
</cfloop>
<cfset Param2 = ListGetAt(CGI.QUERY_STRING,2,"&")>
Order of query string variables is not relevant, or your app shouldnt expect it to be relevant. I think your best bet is to have another variable which is a list of the variables in the order. Like so:
test.cfm?par1=val1&par2=val2&par3=val3&list=var1,var2,var3
Notice the presence of the new variable "list".
So you first grab the value of "list" and then takes it 2nd entry "var2" and reference that in the URL scope. You could easily abstract all of this so the names of the variables themselves dont matter. Good error handling will be necessary to guard against missing expectations.
to get the list of params you can use structKeyList(url) or structKeyArray(url) then access those parameters through the url scope like #url['par1']#
<cfset params = structKeyList(url) />
<cfdump label="parameters" var="#params#" />
<cfloop index="ix" list="#params#">
<cfoutput><div>#ix# = #url[ix]#</div></cfoutput>
</cfloop>
as others have mentioned, you really shouldn't rely on the order of parameters
<cfscript>
par2=getToken(cgi.query_string,2,"&"); // contains "par2=val2"
par2name=getToken(par2,1,"="); // contains "par2"
par2value=urlDecode(getToken(par2,2,"=")); // contains "val2"
</cfscript>
You could also use the listGetAt function, which is basically equivalent to getToken, with slightly different syntax.