Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)

<!---
	Save CSV data values. Here, we are creating a CSV data
	value that has both qualified and non-qualified field
	values, populated and empty field values, and embedded
	field qualifiers and field/line delimiters.
--->
<cfsavecontent variable="strCSV">
"Name","Nick Name","Age","Hair Color"
Kim,"Kim ""Hot Legs"" Smith",24,"Brunette"
"Sarah Vivenz, II","Stubs",27,"Brunette"
"Kit Williams",Kitty,34,Blonde,,,
"Even
Values With
Embedded Line Breaks"
</cfsavecontent>
 
 
<!--- Trim data values. --->
<cfset strCSV = Trim( strCSV ) />
 
 
<!---
	Get the regular expression to match the tokens. I have
	put the field value on the first line and delimiters on
	the second line for easier reading.
--->
<cfset strRegEx = (
	"(""(?:[^""]|"""")*""|[^"",\r\n]*)" &
	"(,|\r\n?|\n)?"
	)/>
 
 
<!---
	Create a compiled Java regular expression pattern object
	based on the pattern above.
--->
<cfset objPattern = CreateObject(
	"java",
	"java.util.regex.Pattern"
	).Compile(
		JavaCast( "string", strRegEx )
		)
	/>
 
<!---
	Get the pattern matcher for our target text (the CSV data).
	This will allows us to iterate over all the data fields.
--->
<cfset objMatcher = objPattern.Matcher(
	JavaCast( "string", strCSV )
	) />
 
 
<!---
	Create an array to hold the CSV data. We are going
	to create an array of arrays in which each nested
	array represents a row in the CSV data file.
--->
<cfset arrData = ArrayNew( 1 ) />
 
<!--- Start off with a new array for the new data. --->
<cfset ArrayAppend( arrData, ArrayNew( 1 ) ) />
 
 
<!---
	Here's where the magic is taking place; we are going
	to use the Java pattern matcher to iterate over each
	of the CSV data fields using the regular expression
	we defined above.
 
	Each match will have at least the field value and
	possibly an optional trailing delimiter.
--->
<cfloop condition="objMatcher.Find()">
 
	<!--- Get the field token value. --->
	<cfset REQUEST.Value = objMatcher.Group(
		JavaCast( "int", 1 )
		) />
 
	<!--- Remove the field qualifiers (if any). --->
	<cfset REQUEST.Value = REQUEST.Value.ReplaceAll(
		JavaCast( "string", "^""|""$" ),
		JavaCast( "string", "" )
		) />
 
	<!--- Unesacepe embedded qualifiers (if any). --->
	<cfset REQUEST.Value = REQUEST.Value.ReplaceAll(
		JavaCast( "string", "(""){2}" ),
		JavaCast( "string", "$1" )
		) />
 
	<!--- Add the field value to the row array. --->
	<cfset ArrayAppend(
		arrData[ ArrayLen( arrData ) ],
		REQUEST.Value
		) />
 
 
	<!---
		Get the delimiter. If no delimiter group was matched,
		this will destroy the variable in the REQUEST scope.
	--->
	<cfset REQUEST.Delimiter = objMatcher.Group(
		JavaCast( "int", 2 )
		) />
 
 
	<!--- Check for delimiter. --->
	<cfif StructKeyExists( REQUEST, "Delimiter" )>
 
		<!---
			Check to see if we need to start a new array to
			hold the next row of data. We need to do this if the
			delimiter we just found is NOT a field delimiter.
		--->
		<cfif (REQUEST.Delimiter NEQ ",")>
 
			<!--- Start new row data array. --->
			<cfset ArrayAppend(
				arrData,
				ArrayNew( 1 )
				) />
 
		</cfif>
 
	<cfelse>
 
		<!---
			If there is no delimiter, then we are done parsing
			the CSV file data. Break out rather than just ending
			the loop to make sure we don't get any extra data.
		--->
		<cfbreak />
 
	</cfif>
 
</cfloop>
 
 
<!--- Dump out CSV data array. --->
<cfdump
	var="#arrData#"
	label="CSV File Data"
	/>

For Cut-and-Paste