note 111665 added to function.str-getcsv
| From: | normadize-a-gmail-d-com | Date: | Wed, 13 Mar 2013 23:19:06 +0000 |
| Subject: | note 111665 added to function.str-getcsv | ||
| Groups: | php.notes | ||
| Request: | Send a blank email to php-notes+get-194065@lists.php.net to get a copy of this message | ||
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).
Here is the more human readable version of the function:
<?php
// returns a two-dimensional array or rows and fields
function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields
= true)
{
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string);
$enc = preg_replace_callback(
'/"(.*?)"/s',
function ($field) {
return urlencode(utf8_encode($field[1]));
},
$enc
);
$lines = preg_split($skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' :
'/\R+/s') : '/\R/s', $enc);
return array_map(
function ($line) use ($delimiter, $trim_fields) {
$fields = $trim_fields ? array_map('trim', explode($delimiter, $line)) :
explode($delimiter, $line);
return array_map(
function ($field) {
return str_replace('!!Q!!', '"',
utf8_decode(urldecode($field)));
},
$fields
);
},
$lines
);
}
?>
Since this is not using any loops, you can actually write it as a one-line statement (one-liner).
Here's the function using just one line of code for the function body, formatted nicely though:
<?php
// returns the same two-dimensional array as above, but with a one-liner code
function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields
= true)
{
return array_map(
function ($line) use ($delimiter, $trim_fields) {
return array_map(
function ($field) {
return str_replace('!!Q!!', '"',
utf8_decode(urldecode($field)));
},
$trim_fields ? array_map('trim', explode($delimiter, $line)) :
explode($delimiter, $line)
);
},
preg_split(
$skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') :
'/\R/s',
preg_replace_callback(
'/"(.*?)"/s',
function ($field) {
return urlencode(utf8_encode($field[1]));
},
$enc = preg_replace('/(?<!")""/', '!!Q!!',
$csv_string)
)
)
);
}
?>
Replace !!Q!! with another placeholder if you wish.
Have fun.
----
Server IP: 69.147.83.199
Probable Submitter: 131.111.185.47
----
Manual Page -- http://php.net/manual/en/function.str-getcsv.php
Edit -- https://master.php.net/note/edit/111665
Del: integrated -- https://master.php.net/note/delete/111665/integrated
Del: useless -- https://master.php.net/note/delete/111665/useless
Del: bad code -- https://master.php.net/note/delete/111665/bad+code
Del: spam -- https://master.php.net/note/delete/111665/spam
Del: non-english -- https://master.php.net/note/delete/111665/non-english
Del: in docs -- https://master.php.net/note/delete/111665/in+docs
Del: other reasons-- https://master.php.net/note/delete/111665
Reject -- https://master.php.net/note/reject/111665
Search -- https://master.php.net/manage/user-notes.php