CSV файл на PHP — создание и чтение
CSV очень удобный формат с точки зрения генерации, поскольку он очень просто устроен. В этой заметке разберемся как устроены файлы с расширением .csv, как их создавать и разбирать (парсить) в PHP. Делается это очень просто.
Ниже приведены простые PHP функции для создания и парсинга csv файлов. Никаких библиотек - для CSV это лишнее!
Формат CSV
Чтобы понимать суть вещей, нужно разобраться в спецификации CSV файлов, как устроен формат. Давайте коротко...
CSV (Comma-Separated Values — значения, разделённые запятыми) — текстовый формат, предназначенный для представления табличных данных.
-
Каждая строка файла — это одна строка таблицы.
-
Разделителем значений колонок является символ:
,(запятая). Для русского языка используется;(точка с запятой), потому что в русском запятая используется в дробных числах. -
Значения, содержащие зарезервированные символы:
" , ; \r\n или \n или \r(двойная кавычка, запятая, точка с запятой, новая строка) обрамляются двойными кавычками". -
Если в значении встречаются двойные кавычки
", то они должны выглядеть как двое кавычек подряд"". - Строка файла может разделяться символами:
\r\nили\n.
Это все что нужно знать, чтобы работать с CSV!
Пример для рус. языка:
1965;Пиксель;E240 – формальдегид (опасный консервант)!;"красный, зелёный, битый";3000,00 1965;Мышка;"А правильней использовать ""Ёлочки""";;4900,00 "Н/д";Кнопка;Сочетания клавиш;"MUST USE! Ctrl, Alt, Shift";4799,00
Пример для англ. языка:
1997,Ford,E350,"ac, abs, moon",3000.00 1999,Chevy,"Venture «Extended Edition»","",4900.00 1996,Jeep,Grand Cherokee,"MUST SELL! air, moon roof, loaded",4799.00
Wiki-справка
Большинство программ под CSV понимают более общий формат DSV (delimiter-separated values — значения разделённые разделителем), допускающий использование иных символов в качестве разделителя. В частности, в русской и других локалях запятая по умолчанию зарезервирована под десятичный разделитель. Поэтому как разделитель используется точка с запятой или табуляция (формат TSV).
Сегодня под CSV понимают набор значений, разделенных какими угодно разделителями, в какой угодно кодировке с какими угодно окончаниями строк. Это значительно затрудняет перенос данных из одних программ в другие, несмотря на всю простоту формата.
Создание CSV файла в PHP
Для создания CSV файла, достаточно создать текстовый файл, используя в нужных местах необходимые разделители столбцов и строк.
Важным моментом, является кодировка файла. Для корректного отображения кириллицы следует использовать кодировку cp1251 (windows-1251).
Разделитель колонок
Для русского языка символом-разделителем является ; (точка с запятой). Для англ. , (запятая).
Строки содержащие спец символы: " , ; \r\n или \n или \r должны быть в двойных кавычках "строка".
Двойные кавычки внутри строки, нужно «очистить» поставив перед кавычкой еще одну такую же кавычку: строка "csv" превратиться в "строка ""csv""". Обрамление в кавычки нужно, чтобы можно было внутри значений колонок использовать разделители ;, , и не переживать что что-то сломается при чтении файла.
Разделитель строк
Для разделения строк в csv файлах можно использовать \r\n (возврат каретки и перенос строки, CR LF). В этом случае, если нужен перенос строки внутри значения колонки, то там используется просто \n.
Также, для разделения строки, может использоваться просто \n (перенос строки, LF). В этом случае, перенос строки внутри значения колонки должен обозначаться как \r (возврат каретки CR).
Функция для создания CSV файла
/**
* Создает CSV файл из переданных в массиве данных.
*
* @param array $create_data Массив данных из которых нужно созать CSV файл.
* @param string $file Путь до файла 'path/to/test.csv'. Если не указать, то просто вернет результат.
* @param string $col_delimiter Разделитель колонок. Default: `;`.
* @param string $row_delimiter Разделитель рядов. Default: `\r\n`.
*
* @return false|string CSV строку или false, если не удалось создать файл.
*
* @version 2
*/
function kama_create_csv_file( $create_data, $file = null, $col_delimiter = ';', $row_delimiter = "\r\n" ){
if( ! is_array( $create_data ) ){
return false;
}
if( $file && ! is_dir( dirname( $file ) ) ){
return false;
}
// строка, которая будет записана в csv файл
$CSV_str = '';
// перебираем все данные
foreach( $create_data as $row ){
$cols = array();
foreach( $row as $col_val ){
// строки должны быть в кавычках ""
// кавычки " внутри строк нужно предварить такой же кавычкой "
if( $col_val && preg_match('/[",;\r\n]/', $col_val) ){
// поправим перенос строки
if( $row_delimiter === "\r\n" ){
$col_val = str_replace( [ "\r\n", "\r" ], [ '\n', '' ], $col_val );
}
elseif( $row_delimiter === "\n" ){
$col_val = str_replace( [ "\n", "\r\r" ], '\r', $col_val );
}
$col_val = str_replace( '"', '""', $col_val ); // предваряем "
$col_val = '"'. $col_val .'"'; // обрамляем в "
}
$cols[] = $col_val; // добавляем колонку в данные
}
$CSV_str .= implode( $col_delimiter, $cols ) . $row_delimiter; // добавляем строку в данные
}
$CSV_str = rtrim( $CSV_str, $row_delimiter );
// задаем кодировку windows-1251 для строки
if( $file ){
$CSV_str = iconv( "UTF-8", "cp1251", $CSV_str );
// создаем csv файл и записываем в него строку
$done = file_put_contents( $file, $CSV_str );
return $done ? $CSV_str : false;
}
return $CSV_str;
}
Теперь, чтобы сгенерировать CSV файл нужно использовать эту функцию так:
$create_data = array( array( 'Заголовок 1', 'Заголовок 2', 'Заголовок 3', ), array( 'строка 2 "столбец 1"', '4799,01', 'строка 2 "столбец 3"', ), array( '"Ёлочки"', 4900.01, 'красный, зелёный', ) ); echo kama_create_csv_file( $create_data, THEME_PATH .'csv_file.csv' ); /* Получим Заголовок 1;Заголовок 2;Заголовок 3 "строка 2 ""столбец 1""";"4799,00";"строка 2 ""столбец 3""" """Ёлочки""";4900.01;"красный, зелёный" */
Чтение CSV файла в PHP
Когда нужно получить данные из CSV файла, т.е. разобрать его и получить данные в переменную, можно использовать встороенную в PHP функцию str_getcsv().
Есть еще функция fgetcsv(), но она оказалась капризной и не всегда работает как нужно (может перепутать переносы строк)...
Вариант на базе функции str_getcsv():
/**
* Читает CSV файл и возвращает данные в виде массива.
*
* @param string $file_path Путь до csv файла.
* @param array $file_encodings
* @param string $col_delimiter Разделитель колонки (по умолчанию автоопределение)
* @param string $row_delimiter Разделитель строки (по умолчанию автоопределение)
*
* @version 6
*/
function kama_parse_csv_file( $file_path, $file_encodings = ['cp1251','UTF-8'], $col_delimiter = '', $row_delimiter = '' ){
if( ! file_exists( $file_path ) ){
return false;
}
$cont = trim( file_get_contents( $file_path ) );
$encoded_cont = mb_convert_encoding( $cont, 'UTF-8', mb_detect_encoding( $cont, $file_encodings ) );
unset( $cont );
// определим разделитель
if( ! $row_delimiter ){
$row_delimiter = "\r\n";
if( false === strpos($encoded_cont, "\r\n") )
$row_delimiter = "\n";
}
$lines = explode( $row_delimiter, trim($encoded_cont) );
$lines = array_filter( $lines );
$lines = array_map( 'trim', $lines );
// авто-определим разделитель из двух возможных: ';' или ','.
// для расчета берем не больше 30 строк
if( ! $col_delimiter ){
$lines10 = array_slice( $lines, 0, 30 );
// если в строке нет одного из разделителей, то значит другой точно он...
foreach( $lines10 as $line ){
if( ! strpos( $line, ',') ) $col_delimiter = ';';
if( ! strpos( $line, ';') ) $col_delimiter = ',';
if( $col_delimiter ) break;
}
// если первый способ не дал результатов, то погружаемся в задачу и считаем кол разделителей в каждой строке.
// где больше одинаковых количеств найденного разделителя, тот и разделитель...
if( ! $col_delimiter ){
$delim_counts = array( ';'=>array(), ','=>array() );
foreach( $lines10 as $line ){
$delim_counts[','][] = substr_count( $line, ',' );
$delim_counts[';'][] = substr_count( $line, ';' );
}
$delim_counts = array_map( 'array_filter', $delim_counts ); // уберем нули
// кол-во одинаковых значений массива - это потенциальный разделитель
$delim_counts = array_map( 'array_count_values', $delim_counts );
$delim_counts = array_map( 'max', $delim_counts ); // берем только макс. значения вхождений
if( $delim_counts[';'] === $delim_counts[','] )
return array('Не удалось определить разделитель колонок.');
$col_delimiter = array_search( max($delim_counts), $delim_counts );
}
}
$data = [];
foreach( $lines as $key => $line ){
$data[] = str_getcsv( $line, $col_delimiter ); // linedata
unset( $lines[$key] );
}
return $data;
}
Использование:
$data = kama_parse_csv_file( '/path/to/file.csv' ); print_r( $data );
Конвертация .lsx, .xlsx файла в .csv
Чтобы перевести Excel файл в CSV, нужно открыть его в Excel и сохранить в формате .csv:
Если такую конвертацию нужно сделать программно, смотрите в сторону онлайн конвертеров с API или готовых библиотек.
-
Сталкивались с такой задачей и знаете более универсальный способ? Прошу поделиться в комментариях.
Я столкнулся и потратил несколько часов перебирая всякий не универсальный «мусор» из гугла - то одно не работает, то другое, то библиотеку нужно ставить «хитро-мудрую»... Так и появилась эта заметка...
Как отдать сгенерированный CSV файл для загрузки
Для этого нужно установить заголовки:
<?php header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="file.csv"' ); $create_data = [ [ 'Заголовок 1', 'Заголовок 2', 'Заголовок 3', ], ]; echo kama_create_csv_file( $create_data );
