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 как комбинирование перевода строки / возврата каретки, чтобы показать отдельные строки.

Другие вопросы по тегам