Like some other users here noted, str_getcsv() cannot be used if you want to comply with either the RFC or with most spreadsheet tools like Excel or Google Docs.

These tools do not escape commas or new lines, but instead place double-quotes (") around the field. If there are any double-quotes in the field, these are escaped with another double-quote (" becomes ""). All this may look odd, but it is what the RFC and most tools do ...

For instance, try exporting as .csv a Google Docs spreadsheet (File > Download as > .csv) which has new lines and commas as part of the field values and see how the .csv content looks, then try to parse it using str_getcsv() ... it will spectacularly regardless of the arguments you pass to it.

Here is a function that can handle everything correctly, and more:

- doesn't use any for or while loops,- it allows for any separator (any string of any length),- option to skip empty lines,- option to trim fields,- can handle UTF8 data too (although .csv files are likely non-unicode).

<?phpif(!function_exists('str_putcsv')) { function str_putcsv($input, $delimiter = ',', $enclosure = '"') {// Open a memory "file" for read/write...$fp = fopen('php://temp', 'r+');// ... write the $input array to the "file" using fputcsv()...fputcsv($fp, $input, $delimiter, $enclosure);// ... rewind the "file" so we can read what we just wrote...rewind($fp);// ... read the entire line into a variable...$data = fgets($fp);// ... close the "file"...fclose($fp);// ... and return the $data to the caller, with the trailing newline from fgets() removed.return rtrim( $data, "\n" ); }}?>

@normadize - that is a nice start, but it fails on situations where a field is empty but quoted (returning a string with one double quote instead of an empty string) and cases like """""foo""""" that should result in ""foo"" but instead return "foo". I also get a row with 1 empty field at the end because of the final CRLF in the CSV. Plus, I don't really like the !!Q!! magic or urlencoding to get around things. Also, \R doesn't work in pcre on any of my php installations.

Here is my take on this, without anonymous functions (so it works on PHP < 5.3), and without your options (because I believe the only correct way to parse according to the RFC would be $skip_empty_lines = false and $trim_fields = false).

//parse a CSV file into a two-dimensional array//this seems as simple as splitting a string by lines and commas, but this only works if tricks are performed//to ensure that you do NOT split on lines and commas that are inside of double quotes.function parse_csv($str){ //match all the non-quoted text and one series of quoted text (or the end of the string) //each group of matches will be parsed with the callback, with $matches[1] containing all the non-quoted text, //and $matches[3] containing everything inside the quotes $str = preg_replace_callback('/([^"]*)("((""|[^"])*)"|$)/s', 'parse_csv_quotes', $str);

//remove the very last newline to prevent a 0-field array for the last line $str = preg_replace('/\n$/', '', $str);

//split on LF and parse each line with a callback return array_map('parse_csv_line', explode("\n", $str));}

//replace all the csv-special characters inside double quotes with markers using an escape sequencefunction parse_csv_quotes($matches){ //anything inside the quotes that might be used to split the string into lines and fields later, //needs to be quoted. The only character we can guarantee as safe to use, because it will never appear in the unquoted text, is a CR //So we're going to use CR as a marker to make escape sequences for CR, LF, Quotes, and Commas. $str = str_replace("\r", "\rR", $matches[3]); $str = str_replace("\n", "\rN", $str); $str = str_replace('""', "\rQ", $str); $str = str_replace(',', "\rC", $str);

//The unquoted text is where commas and newlines are allowed, and where the splits will happen //We're going to remove all CRs from the unquoted text, by normalizing all line endings to just LF //This ensures us that the only place CR is used, is as the escape sequences for quoted text return preg_replace('/\r\n?/', "\n", $matches[1]) . $str;}

//split on comma and parse each field with a callbackfunction parse_csv_line($line){ return array_map('parse_csv_field', explode(',', $line));}

After using several methods in the past to create CSV strings without using files (disk IO sucks), I finally decided it's time to write a function to handle it all. This function could use some cleanup, and the variable type test might be overkill for what is needed, I haven't thought about it too much.

Also, I took the liberty of replacing fields with certain data types with strings which I find much easier to work with. Some of you may not agree with those. Also, please note that the type "double" or float has been coded specifically for two digit precision because if I am using a float, it's most likely for currency.

str_getcsv can be really fussy about trailing spaces - it will not necessarily recognise a final element in a quote delimited set of strings with a space following the final string for example. Using trim() before str_getcsv() quickly fixes this.

RFC 4180 which deals with CSVs states the escape character is supposed to be a double quotation mark: (page 2) 7. If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote. For example:

$longest is a number that represents the longest line in the csv file as required by fgetcsv(). The page for fgetcsv() said that the longest line could be set to 0 or left out, but I couldn't get it to work without. I just made it extra large when I had to use it.

[EDIT BY danbrown AT php DOT net: Contains a bugfix provided by (depely AT IAMNOTABOT prestaconcept.net) on 04-MAR-2011 with the following note: "The previous anonymous function only read the first line".]

Just to clarify, my str_putcsv() function was only ever designed to complement the functionality of the str_getcsv() built-in function, which can only handle converting one line of input into a single level array. For example, this code:

/** * This is the simplest way to do a csv line getter i could imagine. * It's not perfect at all, but will do the job. * There are indeed some error checks for very badly formated csv lines. * It will not, for example, handle the last field if it's empty, * and it will omit repeated enclosures inside quoted fields if * they are not escaped, but I tried to do it very clearly * so everyone could change it for your own necessities. * @param string $str String * @param string $delimiter String * @param string $enclosure String * @param string $escape String * @return array */function user_str_getcsv($str, $delimiter=',', $enclosure='"', $escape='\\') {$return = array();$fields = 0;$inside = false;$quoted = false;$char = '';

if (!$inside) { //Check if we are not inside a fieldif ($char == $delimiter) { //Check if the current char is the delimiter //Tells the function that we are not inside a field anymore$inside = false;$quoted = false;

//Jumps to the next field$fields++;

} elseif($char == $escape) { //Check if the current char is the escape //Error, because it isn't inside a field and there is a escape herereturn false;

} elseif($char != ' ') { //Check if the current char isn't a blank space //Tells the function that a field starts$inside = true;

//Check if the current char is the enclosure, indicating that this field is quotedif ($char == $enclosure) {$quoted= true; } else {$return[$fields] .= $char; } } } else { //Here we are inside a field //Check if the current char is the escapeif ($char == $escape) {//Check if the string has one more char beyond the current oneif (mb_strlen($str)>$i+1) {//Tells the function we will treat the next char$i++;$char = mb_substr($str, $i, 1, 'UTF-8');

//Check if our new char is the enclosureif ($char == $enclosure) {//Check if the field is a quoted oneif ($quoted) {$return[$fields] .= $enclosure; } else {//Error, because we have an escape and then we have an enclosure and we are not inside a quoted fieldreturn false; } } elseif ($char == $escape) {$return[$fields] .= $char; } else { eval("\$return[\$fields] .= \"\\".$char."\";"); }

} else {//Error, because there is an escape and nothing more thenreturn false; } } elseif ($char == $enclosure) { //Check if the current char is the enclosure //Check if we are in a quoted fieldif ($quoted) {//Tells the function that we are not inside a field anymore$inside = false;$quoted = false; } else {//Error, because there is an enclosure inside a non quoted fieldreturn false; } } elseif ($char == $delimiter) { //Check if it is the delimiter //Check if we are inside a quoted fieldif ($quoted) {$return[$fields] .= $char; } else {//Tells the function that we are not inside a field anymore$inside = false;$quoted = false;

As Dave's function also had the problem with only one line being returned here's a slightly changed version:

<?phpfunction str_putcsv($input, $delimiter = ',', $enclosure = '"') {// Open a memory "file" for read/write...$fp = fopen('php://temp', 'r+');// ... write the $input array to the "file" using fputcsv()...fputcsv($fp, $input, $delimiter, $enclosure);// ... rewind the "file" so we can read what we just wrote...rewind($fp);// ... read the entire line into a variable...$data = fread($fp, 1048576); // [changed] // ... close the "file"...fclose($fp);// ... and return the $data to the caller, with the trailing newline from fgets() removed.return rtrim( $data, "\n" );}?>It assumes that one line won't exceed 1Mb of data. That should be more than enough.