Cold Fusion 2018: how to save .xlsx file as .csv - coldfusion

CF2018 on Win2019
Trying to save user-uploaded .xlsx file as .csv file to be processed into Oracle database.
Running this code, which completes without errors, but the result is not legible:
<cfspreadsheet action = "read"
format="csv"
src="c:\bin\Nov_sales.xlsx"
name="foo"
>
<cfspreadsheet action="write"
filename='c:\bin\Nov_sales.csv'
format="csv"
name="foo"
overwrite=true>
The result in .csv file looks like this:
504b 0304 1400 0808 0800 d260 8f51 0000
0000 0000 0000 0000 0000 0b00 0000 5f72
656c 732f 2e72 656c 73ad 92c1 4a03 3110
865f 25cc bd9b 6d05 1169 da8b 08bd 89d4
.....
What am I missing???

"The cfspreadsheet tag always writes spreadsheet data as an XLS file. To write HTML variables or CSV variables as HTML or CSV files, use the cffile tag."
https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-tags/tags-r-s/cfspreadsheet.html
Or in other words, when using cfspreadsheet action="write", the format attribute does not specify the output format, it is specifying the input format - because it could be any one of a CF spreadsheet object, a query, a CSV string, or HTML string.
You're seeing raw bytes in your .csv file, because it's really an Excel file regardless of the .csv filename extension.

Presuming your excel file has a header record, what you should do is first read the excel file into a query object. From there, you can then export the query object into a csv by writing the file using a <cfoutput> loop.
<cfspreadsheet action="read" src="c:\bin\Nov_sales.xlsx" query="qryExcel">
<cfoutput query="qryExcel">
<cfset line = "#col1#,#col2#,#col3#,#col4#,#col5#">
<cffile action="append" file="c:\bin\Nov_sales.csv" output="#line#">
</cfoutput>

After reading:
"The cfspreadsheet tag always writes spreadsheet data as an XLS file. To write HTML variables or CSV variables as HTML or CSV files, use the cffile tag."
I got it to work, something like this:
<cfquery name="ExportData" datasource="yourdatasource">
/* Only specify the columns you need to display in the query */
SELECT *
FROM instructor_student_session
</cfquery>
<cfset doc_nm = "instructor_student">
<cfscript>
//Use an absolute path for the files.
theDir="c:\bin\";
theXLSXFile="#doc_nm#.xlsx";
//Create two empty ColdFusion spreadsheet objects.
theSheet = SpreadsheetNew(true); // creates as xlsx
//Populate each object with a query.
//SpreadsheetAddRows(theSheet,ExportData); //no headers
SpreadsheetAddrows(theSheet,ExportData,1,1,true,[""],true); // add headers
</cfscript>
<cfset theCSVFile = "#doc_nm#.csv">
<cfspreadsheet action="write" filename="#theDir##theXLSXFile#" name="theSheet" sheetname="Students" overwrite="true">
<cfspreadsheet action = "read" src="#theDir##theXLSXFile#" format="csv" name="csvdata" sheet="1">
<cffile action="write" file="#theDir##theCSVFile#" output="#csvdata#">
<cfheader name="Content-disposition" value="attachment;filename=#theCSVFile#">
<cfcontent type="application/vnd.ms-excel" file="#theDir##theCSVFile#" deletefile="yes">

Related

Is validate delimiters with ColdFusion possible?

My CF application provide three selections (semicolon, comma or tab) for users to choose to match the delimiters they have in their file. I want to validate what users selected with what delimiter they have in their file. Is there a way to do this?
So if user is using tab delimiters for his text file but he accidentally selected a comma then I will get this error:
Invalid list index 2.
In function ListGetAt(list, index [, delimiters]), the value of index, 2, is not a valid as the first argument (this list has 1 elements). Valid indexes are in the range 1 through the number of elements in the list.
I think the only way to avoid this type of error is if I can validate user's delimiters being used in their file but I could not find any example when I searched the web.
You didn't specify what kind of data is delimited in the file, so here's just a very simple guessing method:
<!--- read file into memory --->
<cfset fileContent = fileRead( expandPath("yourfile.csv") )>
<!--- declare delimiting characters to check, NOTE: due to using "listLen" you may only specify single characters --->
<cfset possibleDelimiters = [ ";", ",", chr(9) ]> <!--- chr(9) = tab --->
<!--- count number of records found for each delimiter --->
<cfset countResults = {}>
<cfloop array="#possibleDelimiters#" index="delimiter">
<cfset countResults[delimiter] = listLen(fileContent, delimiter)>
</cfloop>
<!--- determine delimiter with the highest count --->
<cfset sortedDelimiters = structSort(countResults, "NUMERIC", "DESC")>
<cfset mostFrequentDelimiter = sortedDelimiters[1]>
<cfoutput>
Is <code>#encodeForHtml(mostFrequentDelimiter)# (#asc(mostFrequentDelimiter)#)</code> the delimiter?
</cfoutput>
However, this will guess terribly if you have text paragraphs in your file due to the frequency of commas in most written languages, so take it with a grain of salt.

ColdFusion Reg expression

I have a csv file composed from a header line (field list) and several detalis lines (details for each Customer).
This file contain a lot of unused fileds so I tried to rebuild a clean file in which I put only fields I need.
for that I loop over the header line, I get the index (position) of the field I need and stored it in a variable:
<cfset FirstName_Pos = listfind(header,'FirstName',';')>
<cfset LastName_Pos = listfind(header,'LastName',';')>
The separated header fields is the ';' character.
After that I retreive the positions of all needed fields, I create a new file to put in the desired info of each line
<cffile action="rename" source="#LocalPath#/#FileName#" destination="#LocalPath#/Source_#FileName#">
<cfset NewFile = FileOpen('#LocalPath#/#FileName#','Append')>
<cfset Newheader = 'FirstName,LastName'>
<cfset fileWriteLine(NewFile, Newheader)>
<cfloop file="#LocalPath#\Source_#FileName#" index="line">
<cfset count = count + 1>
<cfif count GTE 2>
<cfset FirstName= listgetat(line,FirstName_Pos,';',1)>
<cfset LastName= listgetat(line,LastName_Pos,';',1)>
<cfset detail = '#FirstName#,#LastName#'>
<cfset fileWriteLine(NewFile, detail)>
</cfif>
The problem is that in the details lines of the original file there are some fields written as follow :
"#08/04/14 23:00;08/05/14 23:00#"
i.e the field contains the ';' character which is my separated fields character I used in the listgetat function
Therefore, I get non desired value in the variable FirstName and LastName.
Considering that the original file contain the following info:
USERID;Post;FirstName;Date1;Mail;Date2;LastName;Telephone
123;Engineer;Alan;"#08/04/14 23:00;08/05/14 23:00#";alan#yahoo.fr;"#10/04/14 11:00;10/05/14 11:00#";Jones;0624262589
I get :
FirstName;LastName
Alan;"#10/04/14 11:00
instead of
FirstName;LastName
Alan;Jones
I get the idea to loop over all details line of the original file and replace the ';' charcater with a space or blank character using regular expression only on fields having the same format "#08/04/14 23:00;08/05/14 23:00#".
(The date change of course from one field to another and from one raw to another)
<cfloop file="#LocalPath#\Source_#FileName#" index="line">
<cfset newline = rereplace(line,'"##[^\w.];[^\w.]##"','"##[^\w.] [^\w.]##"','all')>
<cfset count = count + 1>
<cfif count GTE 2>
<cfset FirstName= listgetat(newline,FirstName_Pos,';',1)>
<cfset LastName= listgetat(newline,LastName_Pos,';',1)>
<cfset detail = '#FirstName#,#LastName#'>
<cfset fileWriteLine(NewFile, detail)>
</cfif>
</cfloop>
It doesn't work because it seems that the regular expression I used is completely wrong. And also maybe because I duplicate the # sign to deal with coldfusion syntax error
Can anyone has an idea about the regular expression I have to used to deal with this situation?
Thanks in advance
this is an example of an original file
USERID;Post;FirstName;Date1;Mail;Date2;LastName;Telephone
123;Engineer;Alan;"#08/04/14 23:00;08/05/14 23:00#";alan#yahoo.fr;"#10/04/14 11:00;10/05/14 11:00#";Jones;0624262589
parse the original file and replace all occurrences of 0;0 (number;number) with 0,0 (number,number). Then your original solution should work fine.
regex = "/d;/d" to track them down I believe.

can I limit records retrieved in ColdFusion cffile action = read?

I have a file which I want to retrieve with a cffile action = read command. However, I would like to give the user the option of only retrieving a small number of lines. The file could be quite large, and might take a very long time to load. Meanwhile the user may only need to know whether he wants to delete it, and a page or two may be quite enough. Does ColdFusion offer any way to limit the output of this command? Or can anyone suggest another approach to this problem?
Sounds like it's a text file? If so, use the <cfloop file="" and line=""> like so and read line-by-line in a loop and stop after some number of lines.
<cfset lineCount = 0>
<cfloop file="c:\temp\simplefile.txt" index="line">
<cfoutput>#line#</cfoutput><br>
<cfset lineCount++>
<cfif lineCount EQ 10>
<cfbreak>
</cfif>
</cfloop>
OR:
To read a specified number of characters from a text file during each
iteration of the loop, use the tag as follows:
<cfloop file="c:\temp\simplefile.txt" index="chars" characters="12">
<cfoutput>#chars#</cfoutput><br>
</cfloop>
Quote from: http://help.adobe.com/en_US/ColdFusion/9.0/CFMLRef/WSc3ff6d0ea77859461172e0811cbec22c24-71a7.html

Comma is invalid when using dataformat

Here is my code to output a query to a spreadsheet.
<cfscript>
//Use an absolute path for the files. --->
theDir=GetDirectoryFromPath(GetCurrentTemplatePath());
theFile=theDir & "getTestInv.xls";
//Create an empty ColdFusion spreadsheet object. --->
theSheet = SpreadsheetNew("invoicesData");
//Populate the object with a query. --->
SpreadsheetAddRows(theSheet,getTestInv);
</cfscript>
<cfset format = StructNew()>
<cfset format.dataformat = "#,###0.00">
<cfset SpreadsheetFormatColumn(theSheet,format,10)
<cfspreadsheet action="write" filename="#theFile#" name="theSheet" sheetname="getTestInv" overwrite=true>
The error I am getting is:
Invalid CFML construct found on line 125 at column 32.
ColdFusion was looking at the following text:
,
The CFML compiler was processing:
An expression beginning with /", on line 125, column 30.This message is usually caused by a problem in the expressions structure.
A cfset tag beginning on line 125, column 4.
A cfset tag beginning on line 125, column 4.
125: <cfset format.dataformat = "#,###0.00">
For some reason, it doesn't like the comma, even though it is valid according to the documentation. If I take the comma out, it works, but I need it for the thousands grouping.
Anyone encountered this?
In ColdFusion, the # is a reserved character. To escape it, you'll have to double them up to escape them:
<cfset format.dataformat = "##,######0.00">
Silly that they didn't account for this either in the documentation or followed ColdFusion's formatting rules using 9s instead of #s.
Here is my full working standalone test code:
<cfset myQuery = QueryNew('number')>
<cfset newRow = QueryAddRow(MyQuery, 2)>
<cfset temp = QuerySetCell(myQuery, "number", "349348394", 1)>
<cfset temp = QuerySetCell(myQuery, "number", "10000000", 2)>
<cfscript>
//Use an absolute path for the files. --->
theDir=GetDirectoryFromPath(GetCurrentTemplatePath());
theFile=theDir & "getTestInv.xls";
//Create an empty ColdFusion spreadsheet object. --->
theSheet = SpreadsheetNew("invoicesData");
//Populate the object with a query. --->
SpreadsheetAddRows(theSheet,myQuery,1,1);
</cfscript>
<cfset format = StructNew()>
<cfset format.dataformat = "##,######0.00">
<cfset SpreadsheetFormatColumn(theSheet,format,1)>
<cfspreadsheet action="write" filename="#theFile#" name="theSheet" sheetname="theSheet" overwrite=true>
it should be like that
<cfset format = StructNew()>
<cfset format.dataformat = "##,####0.00">

How to create a cfspreadsheet with protected cells

I am creating a spreadsheet object using cfspreadsheet. Would like to make some of the individual cells as protected (read-only). Please let me know if anybody has tried this before.
I did try putting cell format as locked but it did not seems to work. Here is the sample code:
<cfset a = spreadsheetnew()>
<cfset format1 = structNew()>
<cfset format1.locked=true>
<cfset SpreadsheetFormatCell(a,format1,1,1)>
<cfspreadsheet action="write" filename="#expandpath('.')#/test.xls" name="a" overwrite="true">
Thanks.
Locking a cell does nothing unless the sheet is protected ie using cfspreadsheet's password attribute. But doing so has some negative side effects ...
Protecting the sheet locks all cells. That means you essentially have to "unlock" everything else by applying a format. In theory you could just unlock the entire sheet:
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, maxRow, maxCol)>
However, that has the nasty effect of populating every single cell in the sheet. So if you read the file into a query, the query would contain ~65,536 rows and 256 columns. Even if you only populated a few cells explicitly.
The lock feature is better suited to cases where you want everything to be locked except a few cells (not the reverse). Unless that is what you are doing, I probably would not bother with it, given all the negative side effects.
Side effect example
<cfset testFile = "c:/test.xls">
<cfset sheet = spreadsheetNew()>
<!--- only unlocking 100 rows to demonstrate --->
<cfset SpreadsheetFormatCellRange (sheet, {locked=false}, 1, 1, 100, 10)>
<!--- populate two cells --->
<cfset SpreadsheetSetCellValue(sheet,"LOCKED",1,1)>
<cfset SpreadsheetSetCellValue(sheet,"UNLOCKED",2,1)>
<!--- make one cell locked --->
<cfset SpreadsheetFormatCell(sheet, {locked=true}, 1, 1)>
<cfspreadsheet action="write"
name="sheet"
fileName="#testFile#"
password=""
overwrite="true" >
<!--- now see it is filled with empty cells --->
<cfspreadsheet action="read"
query="sheetData"
src="#testFile#" >
<cfdump var="#sheetData#" label="Lots of empty cells" />