enclosure parameter in fputcsv not behaving as expected - php

This I believe has a simple explanation, I just cannot see it. I want to generate a pretty simple csv delimetered file with double quotes around field values.
As per the manual page on fputcsv(), I try to pass on the delimiter and enclosure argument explicitly to have double-quoted field values required for data import in third-party application as in function call below:
// relevant line of codes ...
while($row = mysql_fetch_array($result)){
$name_pieces = "";
$name_pieces = explode(" ", $row['name']);
$numberOfPieces = count($name_pieces);
$lastNameIndex = $numberOfPieces - 1;
$lastname = $name_pieces[$lastNameIndex];
$firstname = $name_pieces[0];
} // Line array becomes the first, second, third fields in resulting .csv output.
$line = array( $row['username'],
$row['name'],
$firstname,
$lastname,
$row['description'] );
$data[] = $line;
}
foreach($data as $dataLine){
if(fputcsv($fh, $dataLine, ',', '"') === false){
die("Unable to write to csv file.");
}
}
Following is a sample of the script's .csv output, notice that only second and last fields are quoted.
AG,"Alan Gaulois",Alan,Gaulois,"(AG) Alan Gaulois"
COMMIS,commis,commis,commis,"(COMMIS) commis"
DU,"Denis Fargo",Denis,Fargo,"(DF) Denis Fargo"
Anyone have an idea why the fields are not all quoted? I could well have missed something on doc page, any insight appreciated!

To my knowledge, yes, delimiters are only needed if there are spaces, if you need something else, consider using array_map.
Something like:
function surroundWithQuotes ($input)
{
$input = str_replace('"', '""', $input); //escaping in csv files is done by doing the same quote twice, odd
return '"' . $input . '"';
}
foreach($data as $dataLine){
if(fputcsv($fh, array_map("surroundWithQuotes" $dataLine),$dataLine, ',', '') === false){
die("Unable to write to csv file.");
}
}

the function just sets quotes, if necessary. If there is no space, special char or quote inside the string you want to write, no enclosing quotes are needed.
The $enclosure argument doesn't tell the parser "you have to quote everything", but it tells the parser, which char to use to quote (by default it is ", so you don't have to write it).

There's something missing in the above mentioned answers, the predicate or call back function would return "" in case of an empty string.
Ideally it should be.
function encloseWithQuotes($value)
{
if (empty($value)) {
return "";
}
$value = str_replace('"', '""', $value);
return '"'.$value.'"';
}

Related

Remove quotes when converting array to txt file using fputcsv in PHP [duplicate]

Is there a native function or solid class/library for writing an array as a line in a CSV file without enclosures? fputcsv will default to " if nothing is passed in for the enclosure param. Google is failing me (returning results for a whole bunch of pages about fputcsv), and PEAR's libraries do more or less the same things as fputcsv.
Something that works exactly like fputcsv, but will allow the fields to remain unquoted.
currently: "field 1","field 2",field3hasNoSpaces
desired: field 1,field 2,field3hasNoSpaces
The warnings about foregoing enclosures are valid, but you've said they don't apply to your use-case.
I'm wondering why you can't just use something like this?
<?php
$fields = array(
"field 1","field 2","field3hasNoSpaces"
);
fputs(STDOUT, implode(',', $fields)."\n");
works with chr() function:
fputcsv($f,$array,',',chr(0));
fputcsv($file, $data, ';', chr(127));
Well car(0) didn't work out as the NULL value will most likely choke most csv parsers.
I ended using fputcsv() to build the initial file, then went through and removed all quotes. Elegant? Maybe not, but it got the job done :).
<?php
$filename = "sample.csv";
$handle = fopen($filename, 'w+');
fputcsv($handle, ['column 1','column 2']);
$data = ['sample','data'];
fputs($handle, implode(',', $data)."\n");
// or
fwrite($handle, implode(',', $data)."\n");
fclose($handle);
$headers = array(
'Content-Type' => 'text/csv',
);
Doesn't this work?
fputcsv($fp, split(',', $line),',',' ');
This is what I use to put standard CSV into an array...
function csv_explode($delim=',', $str, $enclose='"', $preserve=false){
$resArr = array();
$n = 0;
$expEncArr = explode($enclose, $str);
foreach($expEncArr as $EncItem){
if($n++%2){
array_push($resArr, array_pop($resArr) . ($preserve?$enclose:'') . $EncItem.($preserve?$enclose:''));
}else{
$expDelArr = explode($delim, $EncItem);
array_push($resArr, array_pop($resArr) . array_shift($expDelArr));
$resArr = array_merge($resArr, $expDelArr);
}
}
return $resArr;
}
You can then output whatever you want in a foreach loop.
The downside with a CSV file with no enclosures means an errant comma in user input will munge the row. So you'll need to remove commas before writing a CSV row.
The tricky part with handling CSV is parsing enclosures, which makes the PHP & PEAR CSV functions valuable. Essentially you're looking for a file that is comma-delimited for columns and newline-delimited for rows. Here's a simple starting point:
<?php
$col_separator= ',';
$row_separator= "\n";
$a= array(
array('my', 'values', 'are', 'awes,breakit,ome'),
array('these', 'values', 'also', "rock\nAND\nROLL")
);
function encodeRow(array $a) {
global $col_separator;
global $row_separator;
// Can't have the separators in the column data!
$a2= array();
foreach ($a as $v) {
$a2[]= str_replace(array($col_separator, $row_separator), '', $v);
}
return implode($col_separator, $a2);
}
$output= array();
foreach ($a as $row) {
$output[]= encodeRow($row);
}
echo(implode($row_separator, $output));
?>
I use tricky way to remove double quote, but only in Linux
....
fputcsv($fp, $product_data,"\t");
....
shell_exec('sed -i \'s/"//g\' /path/to/your-file.txt ');
Whats wrong with good old fwrite()?
function encloseString($field){
return ($field) ? '"' . $field . '"' : null;
}
$delimiter = ';';
$data = ["data_field_1", "data_field_2", "data_field_3"];
$fp = fopen("some-file.csv", 'w');
for($i = 0;$i<100000;$i++) {
fwrite($fp, implode($delimiter, array_map('encloseString', $data) . "\n");
}
fclose($fp);
(Obviously you need to make sure the data in $data is escaped first)
Chose the solution depends on your application. For some case own code for csv is needed. In case 1 (See result chr0), case 2 (chr127) and case 3 (chr127) the data will be modified(bad thing). Instead use something like this, thanks #oops:
<?php
$fields = array("field 1","field 2","field3hasNoSpaces");
fputs(STDOUT, implode(',', $fields)."\n");
case 1. fputcsv($f, $array, $delimiter, car(0)) See result chr0
case 2. fputcsv($f, $array, $delimiter, car(127))
chr127
case 3. fputcsv($f, $array, $delimiter, ' ')
onespace
case 4. Own code (s.above inspired by
oops or
Ansyori or
zeros-and-ones ) produces
better results.
This is an old question but as I was struggling with this as well, I thought it would be good to let anyone who is looking for something like this know than you can just pass empty string in the enclosure param of fputcsv and it will not use any enclosure at all.
i.e.
fputcsv($file, $array, ',', '');
chr(0) also worked for me:
fputcsv($fp, $aLine, $sDelimiter, chr(0));
Figured it out. By passing in the ascii code for Null to the car() function it seems to work just fine.
fputcsv($f, $array, $delimiter, car(0))
Thanks for the answers everyone!!!

How to properly escape double quotes in a malformed json input before php json_encode

I have a large json file that I'm importing before treatment. I do not control the content of this file, and the json is improperly escaped, leading to some cases of double quotes inside double quotes enclosed strings.
for example :
/...../
"productdiploma" : [],
"productcursus" : ["Méthodes"],
"productpublishedonsite" : false,
"productpublishedonkne" : false,
"productypologyparentality" : [ {
"productmediaformat" : "01- Livre",
"producttechformat" : "50- Grand format - "Autre"",
"productparent" : ""
}],
"productparentwork" : [ {
"productparentworkid" : "1000248048",
"productparentworktitle" : "TRAVAILLER EN FRANCAIS "EN ENTREPRISE" 2007"
/...../
In my import, the file is treated as a very large string with file_get_contents(). I probably need a preg_replace() or a preg_filter() there, but I can't quite figure what patterns I'd need to find and escape those double quotes with a \". Any Help/ideas out there ?
(and see comment below in anwser to shibon to see why it's not a duplicate)
I'd suggest you need a different approach here. Loading the file as one large string means it's nearly impossible to know which double quotes are wanted and which ones are not. I'd go with an approach that allows you to read the fine line by line instead;
<?php
$file = fopen("test.txt","r");
while(! feof($file))
{
$line = fgets($file);
}
fclose($file);
?>
This will allow you to test just the right hand side of each :, like this;
$partToTest = explode($line, ':')[1];
Knowing that for items that have quotes at all (i.e, not the arrays), they should be the first and last character on each line. So you could do something along the lines of;
If the part has quotes, remove the first and last
if($partToTest.substr($line, 0, -1) === "\""))
$partToTest = substr($line, 0, -1); # removes the last quote
$partToTest = substr($line, 1, 0); # removes the first quote
Replace any remaining quotes with \"
$partToTest = str_replace("\"", "\\\""); # escape remaining quotes
Append and prepend new quotes to replace the ones we removed
$partToTest = "\"" . $partToTest . "\"";
Put the line back together.
$line = explode($line, ':')[0] + $partToTest;
}
Admittedly, my php skills aren't the best so there may be a much simpler way of doing this, but the principle should work.
I managed to work out this code, based on #lewis' idea :
$content = '';
while(! feof($infile) )
{
// reset line values at each iteration.
$final_line = '';
$test = array();
// get the next line
$line = trim(fgets($infile));
// test if the line is splitable, else, white as is ({, [ etc...])
if(strpos($line,'" : "')) {
// split the line at quote+space+':'.space+quote to avoid splitting strings containing just ' : ' (not fool proof as a string might still contain '" : "' and split, but works in my case)
$test = explode('" : "',$line) ;
// add the final quote that we just stripped in the splitting to the key
$key = $test[0].'"';
// test if the line ends with a comma or not to decide at which position to remove the last quote
if( strpos($test[1], '",') == (strlen($test[1])-2) ){
$val = substr($test[1],0,-2);
$comma = ','; // store a comma for latter use
} else {
$val = substr($test[1],0,-1);
$comma = '';
}
// no need to remove remove the fist quote it's been taken care of at splitting
// replace the double quotes inside the trimmed string
$val = str_replace('"','\"', trim($val));
// reassemble the corrected line
$final_line = $key . ' : "' . $val . '"'. $comma ."\n";
} else {
$final_line = $line ."\n";
}
//store the line for later treatment
$content .= utf8_encode($final_line);
}
That does the job, though it's significantly slower, and there is still room for errors in the splitting if the line contains the '" : "' string inside the part I want to test, but that's a fix anyway :)

concatenate multiline string into one line string php

I am trying to format my mysql data to csv but for some reason when the code gets to physical address the text inside which is multi-lined brakes the output in the csv thus making it non usable.
i've tried trim(), strip_tags() on top of the cleanData() function;
function cleanData(&$str)
{
// escape tab characters
$str = preg_replace("/\t/", "\\t", $str);
// escape new lines
$str = preg_replace("/\r?\n/", "\\n", $str);
// convert 't' and 'f' to boolean values
if($str == 't') $str = 'TRUE';
if($str == 'f') $str = 'FALSE';
// force certain number/date formats to be imported as strings
if(preg_match("/^0/", $str) || preg_match("/^\+?\d{8,}$/", $str) || preg_match("/^\d{4}.\d{1,2}.\d{1,2}/", $str)) {
$str = "'$str";
}
// escape fields that include double quotes
if(strstr($str, '"')) $str = '"' . str_replace('"', '""', $str) . '"';
}
which is echo'd for download with this code;
array_walk($rows,'cleanData');
$trows = implode(",", $rows)."\n";
echo $trows;
Is there a way to concatenate/combine a multi-lined string into a single line string without jumping through hoops?
The data is being retrieved from mysql table - which i loop through and add to the array:
foreach($row as $data)
{
if($data != '')
//echo $data.'<br>';
$tdata = strip_tags(trim($data));
array_push($rows, $tdata);
//echo $tdata.'<br>';
}
When it hits the physical address field, i am trying to combine/concatenate the string which is normally entered over a few lines, eg:
The Campus
Cnr Main & Sloane Street
Bryanston, Johannesburg
02021 South Africa
I want it as "The Campus Cnr Main & Sloane Street Bryanston Johannesburg 02021 South Africa"
or
"The Campus \n Cnr Main & Sloane Street\n Bryanston, Johannesburg\n 02021 South Africa"
I'm sorry, but your question incomplete, the code snippets are in a muddle and your variable names are inconsistent. For instance, you don't implode(',', $rows) for CSV. You implode(',', $cols) or implode(',', $row), and then implode("\n", $rows) (the latter only if you want the data to be in one single string).
I don't know whether your cleanData() function only fails to replace the newlines (the preg_replace looks ok) or if it's not called at all.
Your code
foreach($row as $data)
{
if($data != '')
//echo $data.'<br>';
$tdata = strip_tags(trim($data));
array_push($rows, $tdata);
//echo $tdata.'<br>';
}
is weird since you commented out a statement after an if clause without {}. I don't see any cleanData() calls here. Try to write an SSCCE and I'm quite sure you'll find the problem yourself while doing that.
Oh, to provide another approach for your question. You can do some replaces in SQL already:
select REPLACE(col, "\n", "\\n") from table
I a found solution that fixed my problem from this post;
How to remove line breaks (no characters!) from the string?
Fox's answer fixed it =D

Wrap CSV values generated by PHP fputcsv() with " "

So, my code generates a CSV file using PHP's built-in fputcsv function.
For the delimiter, I use ',' (a comma).
For the enclosure, I use '"' (a double-quote).
However, when I try something like
fputcsv($file,array('a','b',"long string, with commas",NULL,''),',','"');
it outputs
a,b,"long string, with commas",,
but I would like it to output
"a","b","long string, with commas","",""
Is there an easy way to deal with this, or would I have to write a replacement for fputcsv?
This is not usually a problem for CSV files.
fputcsv puts quotes around the value if it would be ambiguous. For example,
a,b,"long string, with commas",,
is not ambiguous, but,
a,b,long string, with commas,,
is, and will in most (read: all) cases be interpreted by the CSV reader as having more than 5 fields.
CSV parsers will accept string literals even without quotes around them.
If you want quotes around the values anyway, the following snippet would do that. It doesn't escape quotes inside the string - that exercise is left to the reader:
$row = '"' . implode('", "', $rowitems) . '"';
You would want to put this in a loop for all your rows.
I worked around this by inserting some bogus string characters, with a space, ## ##, and then removing them. Here's a sample implementation:
//$exported is our array of data to export
$filename = 'myfile.csv';
$fp = fopen($filename, 'w');
foreach ($exported as $line => $row) {
if ($line > 0) {
foreach ($row as $key => $value) {
$row[$key] = $value."## ##";
}
}
fputcsv($fp, $row);
}
fclose($fp);
$contents = file_get_contents($filename);
$contents = str_replace("## ##", "", $contents);
file_put_contents($filename, $contents);
This encloses all fields in double quotes, including empty ones
I think solution will be like this,
$order_header_arr = array("Item1", "Item2","This is Item3");
fputcsv($fp, $order_header_arr,',',' ');
remember " "[Space] Between third parameter of fputcsv
Any reason you can't str_replace(',,',',"",',$output); ? You'd also have to see if the last or first character is a comma and if so, replace the comma with ,""
fputcsv will not enclose all array variables in quotes. Having a numeric array value without quotes may be correct but presents a problem when a label or address program encounters a numeric defined US zip code because it will strip the leading zeros when printing. Thus 05123-0019 becomes 5123-19.
To enclose all values, whether they exist or not, in quotes I read the input file with fgetsrc and write a corrected version using fwrite. fgetsrc reads the record into array variables. Since fwrite writes a variable, you must string the array variables, enclose them in quotes and separate the array variable with a comma. Then add the record separator.
<?php
// fgetcsv - read array with fgetcsv and string into output variable
// then write with fwrite
// $ar is the array populated from fgetcsv and $arc is the variable strung
// with array variables enclosed in quotes and written using fwrite.
$file_in = fopen("reinOCp.csv","r") or die("Unable to open input file
reinOCp.csv!");
$file_out = fopen("printLABEL.csv", "w") or die("Unable to open output file
prtLABEL!");
while (!feof($file_in)) { //loop through each record of the input file
$ar=fgetcsv($file_in); //read the record into array $ar
if (is_array($ar)){ //this loop will string all the array values plus
// the end of record into variable $arc and then write the variable
$arc = ""; //clear variable $arc
foreach ($ar as $value) {
$arc .= '"' . $value . '",'; // add the array values, enclose in
// quotes with comma separator and store in variable $arc
}
$arc .= "\n"; //add end of record to variable $arc
fwrite($file_out, $arc) or die ("ERROR: Cannot write the file");
//write the record using variable $arc
}
}
echo "end of job";
fclose($file_in);
fclose($file_out);
?>

php fputcsv and enclosing fields

I was just about to ask the same questions as the question aksed here.... Forcing fputcsv to Use Enclosure For *all* Fields
The question was
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?
The answer was:
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.
I need to create a CSV file will every field enclosed... What would be the best solution?
Thanks in advance...
Roll your own function - its not hard:
function dumbcsv($file_handle, $data_array, $enclosure, $field_sep, $record_sep)
{
dumbescape(false, $enclosure);
$data_array=array_map('dumbescape',$data_array);
return fputs($file_handle,
$enclosure
. implode($enclosure . $field_sep . $enclosure, $data_array)
. $enclosure . $record_sep);
}
function dumbescape($in, $enclosure=false)
{
static $enc;
if ($enclosure===false) {
return str_replace($enc, '\\' . $enc, $in);
}
$enc=$enclosure;
}
(above is using unix style escaping)
C.
A workaround: Supposing you have your data in a 2-dimensional array, you can append a string that will force quoting and you are sure is not contained in your data ("## ##" here) and then remove it:
$fp = fopen($filename, 'w');
foreach ($data as $line => $row) {
foreach ($row as $key => $value) {
$row[$key] = $value."## ##";
}
fputcsv($fp, $row);
}
fclose($fp);
$contents = file_get_contents($filename);
$contents = str_replace("## ##", "", $contents);
file_put_contents($filename, $contents);
I have encountered the same problem, and I have solved it as follows.
I have inserted single quotes to each array value.
This way when you open the csv file with excel, the scientific notation E + 15 will no longer be displayed.
Here I share you as I did.
This worked for me, I hope you do too.
Regards
// this function add a single quote for each member of array
function insertquote($value) {
return "'$value'";
}
# here i send each value of array
# to a insertquote function, and returns an array with new values,
# with the single quote.
foreach ($list as $ferow) {
fputcsv($fp, array_map(insertquote, $ferrow), ';');
}

Categories