Currently I am working on a PHP script to output a CSV file from entries in a MySQL database. My problem lies in how to correctly output the values. Many of the entries in the MySQL database will contain commas and quotes, which destroy the format of the CSV file if I just plainly print them out to the file.
I'm aware that I can surround the text in quotes, but the entries that contain quotes would mess up the format of the file.
My question is, what can I do to keep this from happening?
Also, do new lines affect the interpretation of the file?
In addition, I'd rather not use the fputcsv function in PHP. I'm attempting to make the PHP script output the contents of the file (with appropriate headers) rather than write to a new file.
Thanks in advance!
Regards,
celestialorb
I think your dismissal of fputcsv might have been premature. In the comments of the fputcsv manual there's an example where they use fputcsv to output to the browser instead of a file.
http://php.net/manual/en/function.fputcsv.php
Here is that code, plus some headers to show that it does indeed prompt the user to download a csv file.
$mydata = array(
array('data11', 'data12', 'data13'),
array('data21', 'data22', 'data23'),
array('data31', 'data32', 'data23'));
header("Content-type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"my-data.csv\"");
outputCSV($mydata);
function outputCSV($data) {
$outstream = fopen("php://output", 'w');
function __outputCSV(&$vals, $key, $filehandler) {
fputcsv($filehandler, $vals, ';', '"');
}
array_walk($data, '__outputCSV', $outstream);
fclose($outstream);
}
The process is called escaping, and most parsers (included PHP's) use the backslash to escape characters:
"This string contains literal \"quotes\" denoted by backslashes"
You can escape characters in a string with addcslashes:
// escape double-quotes
$string = addcslashes('this string contains "quotes"', '"');
echo $string; // 'this string contains \"quotes\"'
Given an array of data you want to separate by commas, you can do the following:
// Escape all double-quotes
foreach ($data as $key => $value)
$data[$key] = addcslashes($value, '"');
// Wrap each element in double quotes
echo '"' . implode('", "', $data), '"';
I have found tab separated value files to be helpful in these situations. TSV is less susceptible to the issues with commas etc in your data.
Related
I've got a form which submits data to a csv file.
When a user inputs a comma to a field, it destroys my csv structure.
I want to convert inputted commas so that they can get displayed as a character.
I tried this:
$_POST["field"] = str_replace(",", "','", $_POST["field"]);
Use html encoding for instant relief , but still my recommendation to use phpExcel
$comma=",";
$_POST["field"] = str_replace(",", $comma, $_POST["field"]);
You can use fputcsv() to write, and fgetcsv() to read the file, it automatically converts your string.
A simple example for writing the data:
$csv = fopen('file.csv', 'w');
$array = array($csv);
fputcsv($csv, $array);
And reading the data:
$csv = fopen('file.csv','r');
print_r(fgetcsv($csv));
Probably not the best answer, but it does work.
You could replace the comma with a random string when inputting to the CSV as below:
$commastring = str_replace(",", "/zwdz/", $tempstring);
and then when you need to output the comma somewhere on your website (if a database website) you can do the opposite str_replace
You can escape coma like this:
$_POST["field"] = str_replace(",", "\,", $_POST["field"]);
Or you can put string in quotes
$_POST["field"] = "'".$_POST["field"]."'";
I have a link on a page that, when clicked, exports an array of data to csv using fputcsv. When Excel displays the data, there is a column that looks like an integer, but it's not, and Excel is converting it to scientific notation. How do I export the data so that this column is displayed as characters (not a scientific number) ?
The code I'm using for export is from Alain Tiemblo's answer here:
Link to Code
function array2csv(array &$array)
{
if (count($array) == 0) {
return null;
}
ob_start();
$df = fopen("php://output", 'w');
fputcsv($df, array_keys(reset($array)));
foreach ($array as $row) {
fputcsv($df, $row);
}
fclose($df);
return ob_get_clean();
}
Not sure about Excel, but LibreOffice and OpenOffice will import fields as strings if the CSV field is quoted. For example, you want your CSV to be something like:
foo,bar,"12345",baz
(You may also have to check "Quoted field as text" option in the file open dialog.)
Edit: PHP's fputcsv() function will only use quote wrappers if it needs to, so you'll likely have to manually force quotes around the actual field value yourself:
$field = 12345;
$quoted_field = '"' . $field . '"';
Edit 2: If you don't need to worry about escaping, this might work for you instead of fputcsv():
fwrite($fp, '"' . implode($fields, '","') . '"' . "\n");
Try to force your int into a string before your fputcsv.
For example
$foo = "$foo";
http://php.net/manual/en/language.types.type-juggling.php
But then again Excel might make up its own mind when converting your CSV to an Excel format...
Also this question might help: Excel CSV - Number cell format
You could cast the value to string using strval.
converting integer to string using :-
string, strval or enclosing value in double/single quotes , or even concat space with the variable does not work because CSV doesn't hold field type information.
The only way I found is to add some character or symbol to forcefully make it string but that will show in output too.
I have try to write data to csv using the php function.
But the 0's are truncate while write in to an csv.
$data = array ( 'aaa,bbb,ccc,dddd', '000123,456,789','"aaa","bbb"');
$fp = fopen('data.csv', 'w');
foreach($data as $line)
{
$val = explode(",",$line);
fputcsv($fp, $val);
}
fclose($fp);
if you are trying to open csv in excel or open office, it will truncate leading zeros.
when u construct the string with "\t" before zero to avoid 0 truncation
I think Excel has treated it as a number and omitted the 0.
You may try to do this:
fputcsv ($fp, "='".$val."'");
See if it works
You could find that it's Excel, etc that's eating the 0 characters (you can test this out by opening the csv file in notepad (or whatever your favourite text editor is) and seeing if they're there.
If that's not the case then try using the following line:
fputcsv($fp, (string) $val);
Just in case the variable is somehow being cast to an integer somewhere.
I need to store a string in a MySQL database. The values will later be used in a CSV. How do I escape the string so that it is CSV-safe? I assume I need to escape the following: comma, single quote, double quote.
PHP's addslashes function does:
single quote ('), double quote ("), backslash () and NUL (the NULL byte).
So that won't work. Suggestions? I'd rather not try to create some sort of regex solution.
Also, I need to be able to unescape.
Use fputcsv() to write, and fgetcsv() to read.
fputcsv() is not always necessary especially if you don't need to write any file but you want to return the CSV as an HTTP response.
All you need to do is to double quote each value and to escape double quote characters repeating a double quote each time you find one.
Here a few examples:
hello -> "hello"
this is my "quote" -> "this is my ""quote"""
catch 'em all -> "catch 'em all"
As you can see the single quote character doesn't need any escaping.
Follows a full working example:
<?php
$arrayToCsvLine = function(array $values) {
$line = '';
$values = array_map(function ($v) {
return '"' . str_replace('"', '""', $v) . '"';
}, $values);
$line .= implode(',', $values);
return $line;
};
$csv = [];
$csv[] = $arrayToCsvLine(["hello", 'this is my "quote"', "catch 'em all"]);
$csv[] = $arrayToCsvLine(["hello", 'this is my "quote"', "catch 'em all"]);
$csv[] = $arrayToCsvLine(["hello", 'this is my "quote"', "catch 'em all"]);
$csv = implode("\r\n", $csv);
If you get an error is just because you're using an old version of PHP. Fix it by declaring the arrays with their old syntax and replacing the lambda function with a classic one.
For those of you trying to sanitise data using PHP and output as a CSV this can be done using PHP's fputcsv() function without having to write to a file as such:
<?php
// An example PHP array holding data to be put into CSV format
$data = [];
$data[] = ['row1_val1', 'row1_val2', 'row1_val3'];
$data[] = ['row2_val1', 'row2_val2', 'row2_val3'];
// Write to memory (unless buffer exceeds 2mb when it will write to /tmp)
$fp = fopen('php://temp', 'w+');
foreach ($data as $fields) {
// Add row to CSV buffer
fputcsv($fp, $fields);
}
rewind($fp); // Set the pointer back to the start
$csv_contents = stream_get_contents($fp); // Fetch the contents of our CSV
fclose($fp); // Close our pointer and free up memory and /tmp space
// Handle/Output your final sanitised CSV contents
echo $csv_contents;
Don't store the data CSV escaped in the database. Escape it when you export to CSV using fputcsv. If you're storing it CSV escaped you're essentially storing garbage for all purposes other than CSV exporting.
When I use fputcsv to write out a line to an open file handle, PHP will add an enclosing character to any column that it believes needs it, but will leave other columns without the enclosures.
For example, you might end up with a line like this
11,"Bob ",Jenkins,"200 main st. USA ",etc
Short of appending a bogus space to the end of every field, is there any way to force fputcsv to always enclose columns with the enclosure (defaults to a ") character?
No, fputcsv() only encloses the field under the following conditions
/* enclose a field that contains a delimiter, an enclosure character, or a newline */
if (FPUTCSV_FLD_CHK(delimiter) ||
FPUTCSV_FLD_CHK(enclosure) ||
FPUTCSV_FLD_CHK(escape_char) ||
FPUTCSV_FLD_CHK('\n') ||
FPUTCSV_FLD_CHK('\r') ||
FPUTCSV_FLD_CHK('\t') ||
FPUTCSV_FLD_CHK(' ')
)
There is no "always enclose" option.
Not happy with this solution but it is what I did and worked. The idea is to set an empty char as enclosure character on fputcsv and add some quotes on every element of your array.
function encodeFunc($value) {
return "\"$value\"";
}
fputcsv($handler, array_map(encodeFunc, $array), ',', chr(0));
Building on Martin's answer, if you want to avoid inserting any characters that don't stem from the source array (Chr(127), Chr(0), etc), you can replace the fputcsv() line with the following instead:
fputs($fp, implode(",", array_map("encodeFunc", $row))."\r\n");
Granted, fputs() is slower than fputcsv(), but it's a cleaner output. The complete code is thus:
/***
* #param $value array
* #return string array values enclosed in quotes every time.
*/
function encodeFunc($value) {
///remove any ESCAPED double quotes within string.
$value = str_replace('\\"','"',$value);
//then force escape these same double quotes And Any UNESCAPED Ones.
$value = str_replace('"','\"',$value);
//force wrap value in quotes and return
return '"'.$value.'"';
}
$fp = fopen("filename.csv", 'w');
foreach($table as $row){
fputs($fp, implode(",", array_map("encodeFunc", $row))."\r\n");
}
fclose($fp);
After a lot of scrafffing around and some somewhat tedious character checking, I have a version of the above referenced codes by Diego and Mahn that will correctly strip out encasings and replace with double quotes on all fields in fputcsv. and then output the file to the browser to download.
I also had a secondary issue of not being able to be sure that double quotes were always / never escaped.
Specifically for when outputting directly to browser using the php://input stream as referenced by Diego. Chr(127) is a space character so the CSV file has a few more spaces than otherwise but I believe this sidesteps the issue of chr(0) NULL characters in UTF-8.
/***
* #param $value array
* #return string array values enclosed in quotes every time.
*/
function encodeFunc($value) {
///remove any ESCAPED double quotes within string.
$value = str_replace('\\"','"',$value);
//then force escape these same double quotes And Any UNESCAPED Ones.
$value = str_replace('"','\"',$value);
//force wrap value in quotes and return
return '"'.$value.'"';
}
$result = $array_Set_Of_DataBase_Results;
$fp = fopen('php://output', 'w');
if ($fp && $result) {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="export-'.date("d-m-Y").'.csv"');
foreach($result as $row) {
fputcsv($fp, array_map("encodeFunc", $row), ',', chr(127));
}
unset($result,$row);
die;
}
I hope this is useful for some one.
A "quick and dirty" solution is to add ' ' at the end of all of your fields, if it's acceptable for you:
function addspace($v) {
return $v.' ';
}
fputcsv($handle, array_map('addspace', $fields));
PS: why it's working? see Volkerk answer ;)