The code relies heavily on regular expressions, but is compact enough that you can port it to your favorite language (Perl, Java, Python, whatever) in short order.
Before looking at the code itself, here is a short demo to you can try. You can paste your own test strings into the text box below for testing.
Paste the CSV text below, and click
Here is the code. For simplicity, I have assumed that fields are separated by commas and that the quoting character is the double quote, but those choices are easy to configure. The function uses two regular expressions to compute the end points of the CSV fields, and repeatedly matches these expressions against the string.
I would be delighted to hear your comments.
function parseCSV (csvString) {
var fieldEndMarker = /([,\015\012] *)/g; /* Comma is assumed as field separator */
var qFieldEndMarker = /("")*"([,\015\012] *)/g; /* Double quotes are assumed as the quote character */
var startIndex = 0;
var records = [], currentRecord = [];
do {
// If the to-be-matched substring starts with a double-quote, use the qFieldMarker regex, otherwise use fieldMarker.
var endMarkerRE = (csvString.charAt (startIndex) == '"') ? qFieldEndMarker : fieldEndMarker;
endMarkerRE.lastIndex = startIndex;
var matchArray = endMarkerRE.exec (csvString);
if (!matchArray || !matchArray.length) {
break;
}
var endIndex = endMarkerRE.lastIndex - matchArray[matchArray.length-1].length;
var match = csvString.substring (startIndex, endIndex);
if (match.charAt(0) == '"') { // The matching field starts with a quoting character, so remove the quotes
match = match.substring (1, match.length-1).replace (/""/g, '"');
}
currentRecord.push (match);
var marker = matchArray[0];
if (marker.indexOf (',') < 0) { // Field ends with newline, not comma
records.push (currentRecord);
currentRecord = [];
}
startIndex = endMarkerRE.lastIndex;
} while (true);
if (startIndex < csvString.length) { // Maybe something left over?
var remaining = csvString.substring (startIndex).trim();
if (remaining) currentRecord.push (remaining);
}
if (currentRecord.length > 0) { // Account for the last record
records.push (currentRecord);
}
return records;
};