fputcsv and newline codes - php

I'm using fputcsv in PHP to output a comma-delimited file of a database query. When opening the file in gedit in Ubuntu, it looks correct - each record has a line break (no visible line break characters, but you can tell each record is separated,and opening it in OpenOffice spreadsheet allows me to view the file correctly.)
However, we're sending these files on to a client on Windows, and on their systems, the file comes in as one big, long line. Opening it in Excel, it doesn't recognize multiple lines at all.
I've read several questions on here that are pretty similar, including this one, which includes a link to the really informative Great Newline Schism explanation.
Unfortunately, we can't just tell our clients to open the files in a "smarter" editor. They need to be able to open them in Excel. Is there any programmatic way to ensure that the correct newline characters are added so the file can be opened in a spreadsheet program on any OS?
I'm already using a custom function to force quotes around all values, since fputcsv is selective about it. I've tried doing something like this:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure."\r\n");
}
But when the file is opened in a Windows text editor, it still shows up as a single long line.

// Writes an array to an open CSV file with a custom end of line.
//
// $fp: a seekable file pointer. Most file pointers are seekable,
// but some are not. example: fopen('php://output', 'w') is not seekable.
// $eol: probably one of "\r\n", "\n", or for super old macs: "\r"
function fputcsv_eol($fp, $array, $eol) {
fputcsv($fp, $array);
if("\n" != $eol && 0 === fseek($fp, -1, SEEK_CUR)) {
fwrite($fp, $eol);
}
}

This is an improved version of #John Douthat's great answer, preserving the possibility of using custom delimiters and enclosures and returning fputcsv's original output:
function fputcsv_eol($handle, $array, $delimiter = ',', $enclosure = '"', $eol = "\n") {
$return = fputcsv($handle, $array, $delimiter, $enclosure);
if($return !== FALSE && "\n" != $eol && 0 === fseek($handle, -1, SEEK_CUR)) {
fwrite($handle, $eol);
}
return $return;
}

Using the php function fputcsv writes only \n and cannot be customized. This makes the function worthless for microsoft environment although some packages will detect the linux newline also.
Still the benefits of fputcsv kept me digging into a solution to replace the newline character just before sending to the file. This can be done by streaming the fputcsv to the build in php temp stream first. Then adapt the newline character(s) to whatever you want and then save to file. Like this:
function getcsvline($list, $seperator, $enclosure, $newline = "" ){
$fp = fopen('php://temp', 'r+');
fputcsv($fp, $list, $seperator, $enclosure );
rewind($fp);
$line = fgets($fp);
if( $newline and $newline != "\n" ) {
if( $line[strlen($line)-2] != "\r" and $line[strlen($line)-1] == "\n") {
$line = substr_replace($line,"",-1) . $newline;
} else {
// return the line as is (literal string)
//die( 'original csv line is already \r\n style' );
}
}
return $line;
}
/* to call the function with the array $row and save to file with filehandle $fp */
$line = getcsvline( $row, ",", "\"", "\r\n" );
fwrite( $fp, $line);

As webbiedave pointed out (thx!) probably the cleanest way is to use a stream filter.
It is a bit more complex than other solutions, but even works on streams that are not editable after writing to them (like a download using $handle = fopen('php://output', 'w'); )
Here is my approach:
class StreamFilterNewlines extends php_user_filter {
function filter($in, $out, &$consumed, $closing) {
while ( $bucket = stream_bucket_make_writeable($in) ) {
$bucket->data = preg_replace('/([^\r])\n/', "$1\r\n", $bucket->data);
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
}
stream_filter_register("newlines", "StreamFilterNewlines");
stream_filter_append($handle, "newlines");
fputcsv($handle, $list, $seperator, $enclosure);
...

alternatively, you can output in native unix format (\n only) then run unix2dos on the resulting file to convert to \r\n in the appropriate places. Just be careful that your data contains no \n's . Also, I see you are using a default separator of ~ . try a default separator of \t .

I've been dealing with a similiar situation. Here's a solution I've found that outputs CSV files with windows friendly line-endings.
http://www.php.net/manual/en/function.fputcsv.php#90883
I wasn't able to use the since I'm trying to stream a file to the client and can't use the fseeks.

windows needs \r\n as the linebreak/carriage return combo in order to show separate lines.

I did eventually get an answer over at experts-exchange; here's what worked:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure.PHP_EOL);
}
to be used in place of standard fputcsv.

Related

Writing file from array is generating extra blank lines

Here is my function:
function writeFile($array, $package_date) {
$filename = "loadpkg-" . $package_date . ".txt";
$package = fopen($filename, "w");
$header = "File\n" .
"Batch Comment: Load - " . $package_date .
"\nRECORDS\n";
fwrite($package, $header);
foreach ($array as $id) {
fwrite($package, $id);
}
fclose($package);
}
When the output file is opened in Notepad it seems to ignore the newline characters in my $header variable. When the output file is opened in Sublime, the $header variably displays as expected but there are blank lines between each written array element.
It might also be helpful to note that the text file I am originally reading from to create the array that gets written also contains blank lines when opened in Sublime but not Notepad. I would like the format for my output file to be standard throughout, without any empty blank lines.
Here's an example of how the original array is created:
if (substr($line, 0, 2) == "1,") {
$line = str_replace("1,", "TEST", $line);
array_push($array, $line);
}

Alternative to fputcsv

I have an array that I want to export to a CSV file, now I know that there is a fputcsv function but I am using version 5.0.4 of PHP so this isn't an option for me.
Is there an alternative method I can use?
You can use a polyfill for this. write your code as if you where on a system that supports fputcsv From comments within the php block (with some slight framing code) but include this
(copied and slightly modified from http://www.php.net/manual/en/function.fputcsv.php#56827)
<?php
if (!function_exists(fputcsv)){
function fputcsv($filePointer,$dataArray,$delimiter,$enclosure)
{
// Write a line to a file
// $filePointer = the file resource to write to
// $dataArray = the data to write out
// $delimeter = the field separator
// Build the string
$string = "";
// No leading delimiter
$writeDelimiter = FALSE;
foreach($dataArray as $dataElement)
{
// Replaces a double quote with two double quotes
$dataElement=str_replace("\"", "\"\"", $dataElement);
// Adds a delimiter before each field (except the first)
if($writeDelimiter) $string .= $delimiter;
// Encloses each field with $enclosure and adds it to the string
$string .= $enclosure . $dataElement . $enclosure;
// Delimiters are used every time except the first.
$writeDelimiter = TRUE;
} // end foreach($dataArray as $dataElement)
// Append new line
$string .= "\n";
// Write the string to the file
fwrite($filePointer,$string);
}
}
?>
Assuming you have a $Data array, which contains individual arrays for each registry (or line), you may try this:
$Delimiter = '"';
$Separator = ','
foreach($Data as $Line)
{
fwrite($File, $Delimiter.
implode($Delimiter.$Separator.$Delimiter, $Line).$Delimiter."\n");
}
Where $File is the handle for your file. Put in $Delimiter, the character you want to put around each field, and in $Separator, the character to use between fields.
I took the solution from #Orangepill and refactored / simplified it in a couple of ways. This may also become handy if you want every field to be enclosed which is not the case in the default php implementation.
function fputcsv_custom($handle, $fields, $delimiter = ",", $enclosure = '"', $escape_char = "\\") {
$field_arr = [];
foreach($fields as $field) {
$field_arr[] = $enclosure . str_replace($enclosure, $escape_char . $enclosure, $field) . $enclosure;
}
fwrite($handle, implode($delimiter, $field_arr) . "\n");
}

write the out put on file

I am very new to php, and i have search and put together this script to convert text to csv and write the out put on the file.
$File = "/var/apache2/htdocs/loginS/host.txt";
$Handle = fopen($File,"r");
$Content = fread ($Handle,filesize ($File));
fclose($File);
fclose($Handle);
$Content = explode("\t", $Content);
foreach($Content as $Value) {
//echo $Value."|"; // till this line working
fwrite($save, $Value);
fclose($save);
}
the problem is when I try to write on the file. I got only one line.what is my error.
You are calling fclose() on the file in your loop that is writing records. fclose() closes the file handle so it is no longer valid and cannot be written to.
Move fclose($save); after the } that ends the foreach() with your content.
Also, you could simplify things a bit by calling $Content = file_get_contents($File); since that is what you are doing in effect with fread(). Also, since $File is just a string variable, calling fclose() on it is unnecessary and doesn't do anything. You were correctly closing it by calling fclose() on $Handle. But using file_get_contents() will eliminate the need for both. The only file you would need is the one you were writing to.
Here is an example using the file() function which reads each line of a file into an array.
$file = '/var/apache2/htdocs/loginS/host.txt';
$content = file($file);
$save = fopen('./out.csv', 'w+');
foreach($content as $line) {
$line = rtrim($line, "\r\n"); // remove the newline from $line
$parts = explode("\t", $line);
$lineCsv = implode('|', $parts); // or ',' ?
fwrite($save, $lineCsv . "\n"); // write output to file
}
fclose($save);

Is my array to csv function correct?

PHP has a function for converting CSV strings to PHP arrays, but not a function for vice-versa, so I wrote one:
function echocsv(array $arr, $quo = '"', $sep = ',') {
$escape = $quo . $quo;
foreach($arr as &$val) {
if(strpos($val, $quo) !== false) {
$val = $quo . str_replace($quo, $escape, $val) . $quo;
}
}
echo implode($sep, $arr) . PHP_EOL;
}
Is there anything I'm overlooking? From wikipedia it basically says that quotes should be escaped with another quote, and that's pretty much all there is to it. The .CSV file will need to be openable in MS Excel.
My primitive tests seem to suggest it's working.
(I'm echoing it rather than returning a string because I'm going to stream it right to the browser)
$stdout = fopen('php://output','w'); // 'stdout' is for CLI, 'output' is for Browser
fputcsv($stdout, array('val,ue1','val"ue2','value3','etc'));
fflush($stdout); // flush for fun :)
fclose($stdout);
^ this outputs CSV to the Browser.
PHP does indeed contain the function you need: fputcsv()
To stream to the browser, use stdout as your "file":
$stdout = fopen('php://stdout','w');
fputcsv($stdout, array('val,ue1','val"ue2','value3','etc'));
fclose($stdout);
You also need to check if $val contains $sep (i.e. quote the string if it contains a comma):
if (strpos($val, $quo) !== false || strpos($val, $sep) !== false) {
...
}
Otherwise, fputcsv() will do the job (but only to a file/stream).

Any function to take an array of strings and return a CSV line?

I have an array of strings and want a way to create a CSV line from them. Something like:
$CSV_line = implode(',',$pieces);
Will not work as the pieces may contain comma and double quotes.
Is there a PHP built in function that takes the pieces and return a well formatted CSV line?
Thanks,
Roge
If you want to write that line to a file, you can use fputcsv
Using the streams functionnality of PHP, it should be possible to write to a variable -- indeed, there's one guy in the comments of str_getcsv who posted his implementation of str_putcsv :
<?php
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 = 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" );
}
?>
Note : this code is not mine -- it's a copy-paste of the one posted by Ulf on php.net.
You could even write a Class to do this
class CSV_ARRAY {
static public function arr_to_csv_line($arr) {
$line = array();
foreach ($arr as $v) {
$line[] = is_array($v) ? self::arr_to_csv_line($v) : '"' . str_replace('"', '""', $v) . '"';
}
return implode(",", $line);
}
static public function arr_to_csv($arr) {
$lines = array();
foreach ($arr as $v) {
$lines[] = self::arr_to_csv_line($v);
}
return implode("\n", $lines);
}
}
you could useit like this
$csvlines = CSV_ARRAY::arr_to_csv_line($myarray);
file_put_contents($csvlines,"mycsv.csv");
thats it ;-)
As far as I know there is no such built in function. The one I'm aware of is fputcsv that does exactly what you want
but it does not return the CSV line, but writes it to a file.
<?php
$pieces = array (
'a,b,c,d', // contains comma.
'"1","2"' // contains double quotes.
);
$fp = fopen('file.csv', 'w');
fputcsv($fp, $pieces); // "a,b,c,d","""1"",""2""" written
fclose($fp);
?>
Most of the times you want create the line of CSV and write it to the file. So the above function should suffice.
I'd written one such function for PHP4 as it does not support fputcsv. I'll share it with you:
// If a value contains a comma, a quote, a space, a tab, a newline, or a linefeed,
// then surround it with quotes and replace any quotes inside it with two quotes
function make_csv_line($values)
{
// iterate through the array ele by ele.
foreach($values as $key => $value)
{
// check for presence of special char.
if ( (strpos($value, ',') !== false) || (strpos($value, '"') !== false) ||
(strpos($value, ' ') !== false) || (strpos($value, "\t") !== false) ||
(strpos($value, "\n") !== false) || (strpos($value, "\r") !== false))
{
// if present, surround the ele in quotes..also replace
// already existing " with ""
$values[$key] = '"' . str_replace('"', '""', $value) . '"';
}
}
// now create the CSV line by joining with a comma, also put a \n at the end.
return implode(',', $values) . "\n";
}
Here is a simpler fputcsv implementation:
$stdout = fopen('php://output','w'); // 'stdout' is for CLI, 'output' is for Browser
fputcsv($stdout, array('val,ue1','val"ue2','value3','etc'));
fflush($stdout); // flush for fun :)
fclose($stdout);
I snarfed this elswhere on SO and posted here for faster reference, please bump/credit the original posting:
Is my array to csv function correct?
Well, the thing is, there is no well formatted CSV. For instance, Open Office uses the semicolon (;) as default delimiter, while Excel expects it to be a comma (,). But fact is, no standard exists.
Assuming the Array looks something like this:
Array
0 => Some string
1 => Some other string, with comma
2 => And a third one with "double quotes"
);
You could either use implode with a different delimiter, e.g.
$delimiter = chr(9); // Tab
$delimiter = ';' // Semicolon. Excel's default
$delimiter = '|' // Pipe
$delimiter = "','"; // Comma plus Single Quote
If that is not working, run the String array through array_filter first and change the double quotes and comma via callback to whatever you think they should be, before exploding with just a comma delimiter:
$csv = implode(',' array_filter($array, 'my_csv_prepare'));
But keep in mind, this does not guarantee your consuming application to be able to read the CSV. It really depends on how they implemented CSV parsing. Like I said: no standard.

Categories