Best String Methods
I've been wondering about your use of String.remove in your above code. This method is described as follows. Thus if you have identical lines in your CSV this will for sure not be what your expecting, which i assume was just to remove the last line read.
Removes all occurrences of the specified substring and returns the String result.
You should consider using String.substring to chop your way along the string (though reviewing the ever growing array of String methods, suggests maybe String.substringAfter might also be worth a try).
Example Line Reader
I recently used the following Iterator implementation (useful for Batch Apex integration) to parse some Apex code this should work for your use case. I've modified the LINE_SEPARATOR variable to based on a CSV file I've used in the past (from Excel), though there is no standard specification on this you will have to stipulate and/or make it a configuration option.
LineReader lineReader = new LineReader(csvFile);
List<String> newLines = new List<String>();
while(lineReader.hasNext())
{
// Read line
String line = lineReader.next();
}
/**
* Utility class to iterate over lines
**/
public class LineReader
implements Iterator<string>, Iterable<string>
{
private String LINE_SEPARATOR = '\r\n';
private String textData;
public LineReader(String textData)
{
this.textData = textData;
}
public Boolean hasNext()
{
return textData.length() > 0 ? true : false;
}
public String next()
{
String row = null;
Integer endPos = textData.indexOf(LINE_SEPARATOR);
if(endPos == -1)
{
row = textData;
textData = '';
}
else
{
row = textData.subString(0, endPos);
textData = textData.subString(endPos + LINE_SEPARATOR.length(), textData.length());
}
return row;
}
public Iterator<String> Iterator()
{
return this;
}
}
Batch Apex Consideration
Quite a large CSV file to parse, in the statement governor free world, this type of processing stands a better chance. Though depending on what your doing with the parsed data you may still run into issues. There is an approach described in more detail here which uses Batch Apex you might want to look at (not the global keyword is not needed these days).
i am trying to create a csv and succeeded in that except with a new
empty row has been added on the top of the csv. The following is the
visualforce page code for the generation.
You need to make sure there's no intervening space between the opening page tag and the next tag:
<apex:page ...>{!output}</apex:page>
Also, i need to use the same page to create csv's for 2 to 3 custom
list which are more or less have similar structure. What i used is
nested if's to identify which content i need to output as csv and also
to create headers. Is that a right approach using nested if's to
handle different csv header structures in a single page?
You're doing this in Visualforce expressions, which can be problematic, as larger files will take forever to export and may run into Visualforce runtime exceptions. I would recommend simply emitting a string directly, as in the prior example above:
public String getOutput() {
String result = '';
// Build csv headers and values
return result;
}
Also, I've noticed you're probably not escaping the fields correctly (quotes within a field need to be double-quoted, as in """Bob's"" Bar-B-Que"
for "Bob's" Bar-B-Que
).
You might be able to use a polymorphic method to generate your code to make the code easier to follow:
abstract class CSVBuilder {
abstract String buildCSV(SObject[] records);
}
class OSOCSVBuilder extends CSVBuilder {
override String buildCSV(SObject[] records) {
// implementation 1;
}
}
class ODCSVBuilder extends CSVBuilder {
override String buildCSV(SObject[] records) {
// implementation 2;
}
}
Then, you can change your "getOutput" method:
public String getOutput() {
SObject[] data = loadData(); // Some function that gets the data to output
Map<String, Type> dispatch = new Map<String, Type> {
'ODO' => ODOCSVBuilder.class,
'OD' => ODCSVBuilder.class
};
return ((CSVBuilder)dispatch.get(CSVTab).newInstance()).buildCSV(data);
}
You can add new implementations by adding a mapping to the dispatch table, and implementing the subclass, as desired. You can source from some other data point if you like; it doesn't have to be a list of SObject records.
The base abstract class can also provide some utility functions, such as the proper escaping of field values, etc; you can use those those functions common to both classes directly using that manner.
Best Answer
Creating a CSV file blob in Apex
My recommendations:
Lists
as stateful variables usingDatabase.Stateful
, and then creating a CSV row out of each SObject during the Batch Apexexecute
method. In thefinish
method of your Batch Apex, send an email containing the generated CSV. Using Batch Apex to do the CSV row generation will avoid theToo Many Script Statements
error, since Batch Apex limits are reset for each Batch Apexexecute
method invocation. The resultant CSV file can be stored as a String instance variable, and converted to a Blob in thefinish
method, then emailed to the user requesting the export.Example of Batch Apex
Here is some sample code for the JavaScript method:
To create CSV fields in JavaSCript, here's some a method that will work:
Parsing CSV files in Apex
Number One: Do not roll your own CSV Parser: use someone else's! There are a lot of complexities to parsing IETF RFC 4180 - compliant CSV files that you will uncover, one after another, if you try to do roll your own parser. Trust me, not worth it.
And guess what? You don't have to!
Marty Chang has done the Salesforce/Apex developer commmunity a huge service by writing a rock-solid, IETF RFC 4180 compliant CSV Parser FOR APEX!!!
Here is the link to download the 2 files he wrote for this purpose, CSVParser and CSVReader. I've used it before, trust me, it handles everything, even newlines embedded in your CSV fields (try writing that yourself --- NOT straightforward.)
Here's how to use it:
Yeah, it's that easy!
His solution uses some good RegEx to bypass the "too many statements" limit.
Creating SObject records from CSV Rows in Apex
As far as creating records from these CSV rows, i've done this before using Batch Apex. Convert your CSV files into rows during the Batch Apex job and create rows one-by-one in the execute method. Also, shameless plug, there's actually an IETF RFC 4180-compliant CSV Import Wizard built-in to Skuid, so if you are open to using an external library, why roll your own parser?
Parsing CSV Rows in Apex
Winter 13 (API v26) has some awesome methods for escaping/unescaping CSV fields:
String.escapeCsv(String textToMakeIntoACSVField)
String.unescapeCsv(String csvField)