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 );