fputcsv и коды новой строки
Я использую fputcsv в PHP для вывода файла запроса базы данных, разделенного запятыми. При открытии файла в gedit в Ubuntu это выглядит правильно - каждая запись имеет разрыв строки (нет видимых символов разрыва строки, но вы можете сказать, что каждая запись отделена, а открытие ее в электронной таблице OpenOffice позволяет мне правильно просматривать файл.)
Однако мы отправляем эти файлы клиенту в Windows, и в их системах этот файл представляет собой одну большую длинную строку. Открыв его в Excel, он вообще не распознает несколько строк.
Здесь я прочитал несколько похожих вопросов, в том числе и этот, который включает в себя ссылку на действительно информативное объяснение Великого раскола Новой Линии.
К сожалению, мы не можем просто сказать нашим клиентам открывать файлы в "умном" редакторе. Они должны иметь возможность открывать их в Excel. Есть ли программный способ обеспечить добавление правильных символов новой строки, чтобы файл можно было открыть в программе для работы с электронными таблицами в любой ОС?
Я уже использую пользовательскую функцию для наложения кавычек вокруг всех значений, так как fputcsv избирательно относится к этому. Я пытался сделать что-то вроде этого:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure."\r\n");
}
Но когда файл открывается в текстовом редакторе Windows, он все равно отображается в виде одной длинной строки.
9 ответов
В конце концов я получил ответ на обмен экспертами; вот что сработало:
function my_fputcsv($handle, $fieldsarray, $delimiter = "~", $enclosure ='"'){
$glue = $enclosure . $delimiter . $enclosure;
return fwrite($handle, $enclosure . implode($glue,$fieldsarray) . $enclosure.PHP_EOL);
}
использоваться вместо стандартного fputcsv.
// 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);
}
}
Это улучшенная версия замечательного ответа @John Douthat, сохраняющая возможность использования пользовательских разделителей и вложений и возвращающая исходный вывод fputcsv:
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;
}
Используя функцию php, fputcsv пишет только \n и не может быть настроен. Это делает функцию бесполезной для среды Microsoft, хотя некоторые пакеты также обнаруживают новую строку linux.
Тем не менее, преимущества fputcsv заставили меня покопаться в решении заменить символ новой строки непосредственно перед отправкой в файл. Это можно сделать путем потоковой передачи fputcsv в сборку во временном потоке php. Затем адаптируйте символы новой строки к тому, что вы хотите, а затем сохраните в файл. Как это:
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);
Как указывал webbiedave (спасибо!), Возможно, самый чистый способ - использовать потоковый фильтр.
Это немного сложнее, чем другие решения, но даже работает с потоками, которые не редактируются после записи в них (например, загрузка с использованием $handle = fopen('php://output', 'w');
)
Вот мой подход:
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);
...
В качестве альтернативы, вы можете вывести в собственном формате Unix (только \ n), а затем запустить unix2dos для полученного файла, чтобы преобразовать в \ r \ n в соответствующих местах. Просто будьте осторожны, чтобы ваши данные не содержали \ n. Кроме того, я вижу, что вы используете разделитель по умолчанию ~ . попробуйте разделитель по умолчанию \ t.
В PHP 8.1.0 появилась новая опция для eol, см. официальный документ.
Я имел дело с похожей ситуацией. Вот решение, которое я нашел, которое выводит файлы CSV с оконными дружественными окончаниями строк.
http://www.php.net/manual/en/function.fputcsv.php
Я не смог использовать, так как я пытаюсь передать файл клиенту и не могу использовать fseeks.
Потребности в окнах \r\n
как комбинирование перевода строки / возврата каретки, чтобы показать отдельные строки.