Populate object with multidimensional menu - coldfusion

I'm wondering if there is an effective way to put a menu into an array or any other data type.
With php I would do something like this:
$menu[1] = "home";
$menu[2] = "news";
$menu[3]["item"] = "products";
$menu[3]["subMenu"][1] = "jackets";
$menu[3]["subMenu"][2] = "T-shirts";
$menu[4] = "contact";
However I have no clue how one would do this in coldfusion.
I want to grab this data from the DB and push it into an object, this will allow me to generate the html from the array.

To take Ciaran's answer a step farther, you can do it completely with object literals in CF 9:
<cfset menu = ["home",
"news",
{"item"="products",
"subMenu"= ["jackets",
"T-shirts"]},
"contact"]>
<cfdump var="#menu#" /> <!--- Output --->

It's actually very similar. This presumes ColdFusion 8 (or higher) for array ([]) and struct ({}) literals:
<cfset menu = [] /> <!--- Create initial array --->
<cfset menu[1] = "home" />
<cfset menu[2] = "news" />
<cfset menu[3] = {} /> <!--- Create structure --->
<cfset menu[3]["item"] = "products" /> <!--- Address structure by key --->
<cfset menu[3]["subMenu"] = [] />
<cfset menu[3]["subMenu"][1] = "jackets" />
<cfset menu[3]["subMenu"][2] = "T-shirts" />
<cfset menu[4] = "contact" />
<cfdump var="#menu#" /> <!--- Output --->
Hope that helps!

Related

How to replace < with < in ColdFusion

I'm creating an XML document from database entries using ColdFusion and when the XML is created the < and > are in the format of < and >. So before it creates the XML is there a way to change the < to a <?
Below is the code and an example of the output:
<cfquery name="messages" datasource="showcase_Uk">
select * from t_items where pid = 2 and spid = 45
</cfquery>
<cfset myStruct = StructNew() />
<cfset mystruct.link = "http://showcase.com" />
<cfset myStruct.title = "Examples" />
<cfset mystruct.description = "Examples from UK Showcase" />
<cfset mystruct.pubDate = Now() />
<cfset mystruct.version = "rss_2.0" />
<cfset myStruct.item = ArrayNew(1) />
<cfloop query="messages">
<cfset myStruct.item[currentRow] = StructNew()>
<cfset myStruct.item[currentRow].guid = structNew()>
<cfset myStruct.item[currentRow].guid.isPermaLink="YES">
<cfset myStruct.item[currentRow].guid.value = xmlFormat(#messages.id#)>
<cfset myStruct.item[currentRow].pubDate = createDate(year(#messages.uploadDate#), month(#messages.uploadDate#), day(#messages.uploadDate#))>
<cfset myStruct.item[currentRow].title = xmlFormat(#messages.name#)>
<cfset myStruct.item[currentRow].description = StructNew() />
<cfset myStruct.item[currentRow].description.value = xmlFormat(#messages.description#)>
</cfloop>
<cffeed action="create" name="#myStruct#" overwrite="true" xmlVar="myXML">
<cffile action="write" file="e:\domains\showcase.com\wwwroot\ukshowcasefeed.xml" nameconflict="overwrite" output="#XMLFormat(myXML)#">
<cffile action="read" file="e:\domains\showcase.com\wwwroot\ukshowcasefeed.xml" variable="myfile">
<cfoutput>#myfile#</cfoutput>
Then here is a screenshot of the code it produces:
I've tried ReplaceNoCase and that doesn't change it at all. I believe it maybe something to do with Regex but I don't really know.
Simple answer:
Don't XmlFormat() when you write the file, that's what causes the double encoding you see.
Longer answer:
I recommend you use mapping to create your feed:
<cfquery name="messages" datasource="showcase_Uk">
select
id, uploadDate, name, description, 'yes' as isPermaLink
from
t_items
where
pid = 2 and spid = 45
</cfquery>
<cfset feedMeta = {
version: "rss_2.0",
title: "Examples",
link: "http://showcase.com",
publisheddate: Now(),
description: "Examples from UK Showcase"
}>
<cfset feedMap = {
title: "name",
content: "description",
publisheddate: "uploadDate",
id: "id",
idpermalink: "isPermaLink"
}>
<cffeed
action="create"
properties="#feedMeta#" columnMap="#feedMap#" query="#messages#"
xmlvar="feedXml"
>
<cffile
action="write"
file="#ExpandPath('/ukshowcasefeed.xml')#" nameconflict="overwrite" charset="utf-8"
output="#feedXml#"
>
Note the easy declarative approach to feed creation. No loops, no manual struct creation, only data-driven mapping of input to output.
Also note the charset="utf-8" on the <cffile>, that's essential if you don't want to run into encoding problems.
If you modify your query so that it has the correct column names right-away, then you don't even need the feedMap structure:
<cfquery name="messages" datasource="showcase_Uk">
select
id,
'yes' as idpermalink
uploadDate as publisheddate,
name as title,
description as content
from
t_items
where
pid = 2 and spid = 45
</cfquery>
<cffeed
action="create"
properties="#feedMeta#" query="#messages#"
xmlvar="feedXml"
>

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.

How to extract slide notes from a PowerPoint file with ColdFusion

I have a .PPT (PowerPoint, transferrable to ODP or PPTX) file with speaker notes on every slide. I want to extract the entire presentation into something dynamic so I can create a speaker cheat sheet for running on a phone or table while I talk (thumbnail of the slide with speaker notes). I do this just often enough to HATE doing it by hand.
This is almost easy enough with <cfpresentation format="html" showNotes="yes"> which splits the PPT up into HTML pages and creates an image for every slide. cfpresentation, however, does not transfer the speaker notes, they are lost in translation.
I have also tried <cfdocument> which has no options for preserving slide notes once it converts to PDF.
Is there a way to get the notes out of the PowerPoint file from within ColdFusion?
The simplest solution:
Convert the PowerPoint presentation to OpenOffice ODP format. That's a ZIP file. CFML can unzip it and inside there's a content.xml file which contains the slides and the notes, so CFML can extract the notes from that format.
Given the CFDOCUMENT functionality, perhaps ColdFusion can even convert the PPT to ODP for you?
There's no way to do this directly in CF. You can do this by dropping to the underlying Java. I stand corrected. Using the showNotes attribute on the <cfpresentation> tag, should add the notes to the HTML.
As an alternative, or if that doesn't work for some reason, you should be able to use Apache POI to do this, although you may need to use a more recent version of poi than shipped with your version of coldfusion, which may require some additional work.
public static LinkedList<String> getNotes(String filePath) {
LinkedList<String> results = new LinkedList<String>();
// read the powerpoint
FileInputStream fis = new FileInputStream(filePath);
SlideShow slideShow = new SlideShow(is);
fis.close();
// get the slides
Slide[] slides = ppt.getSlides();
// loop over the slides
for (Slide slide : slides) {
// get the notes for this slide.
Notes notes = slide.getNotesSheet();
// get the "text runs" that are part of this slide.
TextRun[] textRuns = notes.getTextRuns();
// build a string with the text from all the runs in the slide.
StringBuilder sb = new StringBuilder();
for (TextRun textRun : textRuns) {
sb.append(textRun.getRawText());
}
// add the resulting string to the results.
results.add(sb.toString());
}
return results;
}
Carrying over complex formatting may be a challenge (bulleted lists, bold, italics, links, colors, etc.), as you'll have to dig much deeper into TextRuns, and the related API's and figure how to generate HTML.
CFPRESENTATION (at least as of version 9) does have a showNotes attribute, but you'd still have to parse the output. Depending on the markup of the output, jQuery would make short work of grabbing what you want.
Felt bad that my above answer didn't work out so I dug a little bit. It's a little dated, but it works. PPTUtils, which is based on the apache library that #Antony suggested. I updated this one function to do what you want. You may have to tweak it a bit to do exactly what you want, but I like the fact that this utility returns the data to you in data format rather than in HTML which you'd have to parse.
And just in case, here is the POI API reference I used to find the "getNotes()" function.
<cffunction name="extractText" access="public" returntype="array" output="true" hint="i extract text from a PPT by means of an array of structs containing an array element for each slide in the PowerPoint">
<cfargument name="pathToPPT" required="true" hint="the full path to the powerpoint to convert" />
<cfset var hslf = instance.loader.create("org.apache.poi.hslf.HSLFSlideShow").init(arguments.pathToPPT) />
<cfset var slideshow = instance.loader.create("org.apache.poi.hslf.usermodel.SlideShow").init(hslf) />
<cfset var slides = slideshow.getSlides() />
<cfset var notes = slideshow.getNotes() />
<cfset var retArr = arrayNew(1) />
<cfset var slide = structNew() />
<cfset var i = "" />
<cfset var j = "" />
<cfset var k = "" />
<cfset var thisSlide = "" />
<cfset var thisSlideText = "" />
<cfset var thisSlideRichText = "" />
<cfset var rawText = "" />
<cfset var slideText = "" />
<cfloop from="1" to="#arrayLen(slides)#" index="i">
<cfset slide.slideText = structNew() />
<cfif arrayLen(notes)>
<cfset slide.notes = notes[i].getTextRuns()[1].getRawText() />
<cfelse>
<cfset slide.notes = "" />
</cfif>
<cfset thisSlide = slides[i] />
<cfset slide.slideTitle = thisSlide.getTitle() />
<cfset thisSlideText = thisSlide.getTextRuns() />
<cfset slideText = "" />
<cfloop from="1" to="#arrayLen(thisSlideText)#" index="j">
<cfset thisSlideRichText = thisSlideText[j].getRichTextRuns() />
<cfloop from="1" to="#arrayLen(thisSlideRichText)#" index="k">
<cfset rawText = thisSlideRichText[k].getText() />
<cfset slideText = slideText & rawText />
</cfloop>
</cfloop>
<cfset slide.slideText = duplicate(slideText) />
<cfset arrayAppend(retArr, duplicate(slide)) />
</cfloop>
<cfreturn retArr />
</cffunction>

Problems with creating an array of structs

I'm making a blog API and am having some very strange issues when trying to create an array of structs in coldfusion. The top level array will contain the post, as a struct, with a .comments that is an array of all comments under that post, also as structs.
Each of the pieces in the following code work individually. But, somehow when I put them together I end up with an infinitely nested array of structs containing an array of structs etc... all of only the very last element in the top level array of posts.
<cfset posts = VARIABLES.postDao.getBlogPosts(argumentCollection=arguments) />
<cfset result = arraynew(1) />
<cfloop index="i" from="1" to="#arrayLen(posts)#">
<cfset post = posts[i].getInstance()>
<cfset StructInsert(post, 'comments', getComments(post.postId))>
<cfset ArrayAppend(result, post)>
</cfloop>
getBlogPosts returns an array of Post beans.
bean.getInstance() returns a struct with all the data in the bean.
getComments(id) returns an array all comments(structs) for post[id].
Each of these works as intended and is used elsewhere without problems.
The structure of the infinitely nested array is as such:
Array containing Post
. Post.comments containing array of comments + Post on end
. . Post.comments containing array of comments + Post on end
. . . etc...
You didn't show the entire code.
I suspect replacing what you did show with either of these will solve the problem:
<cfset local.posts = VARIABLES.postDao.getBlogPosts(argumentCollection=arguments) />
<cfset local.result = arraynew(1) />
<cfloop index="local.i" from="1" to="#arrayLen(local.posts)#">
<cfset local.post = local.posts[local.i].getInstance()>
<cfset StructInsert(local.post, 'comments', getComments(local.post.postId))>
<cfset ArrayAppend(local.result, local.post)>
</cfloop>
Or:
<cfset var posts = VARIABLES.postDao.getBlogPosts(argumentCollection=arguments) />
<cfset var result = arraynew(1) />
<cfset var i = 0 />
<cfset var post = 0 />
<cfloop index="i" from="1" to="#arrayLen(posts)#">
<cfset post = posts[i].getInstance()>
<cfset StructInsert(post, 'comments', getComments(post.postId))>
<cfset ArrayAppend(result, post)>
</cfloop>
You should always use either var keyword or local scope for variables in a cffunction.
You can use VarScoper to check your code for other places where this needs fixing.
Please try adding some cfdumps in there and report back what you get:
<cfset posts = VARIABLES.postDao.getBlogPosts(argumentCollection=arguments) />
<cfset result = arraynew(1) />
<cfloop index="i" from="1" to="#arrayLen(posts)#">
<cfset post = posts[i].getInstance()>
<cfdump var="#post#">
<cfset StructInsert(post, 'comments', getComments(post.postId))>
<cfdump var="#post#">
<cfset ArrayAppend(result, post)>
<cfdump var="#result#"><cfabort>
</cfloop>
edit
I think the problem is a child reference to a parent value, which spawns the infinite loop when recursing through the object. Try changing to this:
<cfset posts = VARIABLES.postDao.getBlogPosts(argumentCollection=arguments) />
<cfset result = arraynew(1) />
<cfloop index="i" from="1" to="#arrayLen(posts)#">
<cfset post = posts[i].getInstance()>
<cfset StructInsert(post, 'comments', Duplicate(getComments(post.postId)))>
<cfset ArrayAppend(result, post)>
</cfloop>

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.