Перейти к содержанию

Как превратить таблицу из Excel в массив (PHP)

Задача: форма с полем textarea, куда пользователь вставляет ячейки из Excel (Ctrl+C, Ctrl+V). Нужно превратить их в двумерный массив $arr[номер_строки][номер_ячейки].

В 2021 году я решил это делением по переводам строк и табуляциям, потом подпёр костылём, когда пользователи начали ставить переносы прямо в ячейках. Правильное решение оказалось короче обоих: в PHP уже есть функция, которая понимает этот формат.

Если массив нужен один раз, а не в форме на сайте, — вставь ячейки в конвертер и выбери «Массив PHP». Он же выдаст JSON, CSV или список Python.

Правильный способ: fgetcsv

Excel кладёт ячейки в буфер как TSV: табуляция между ячейками, перевод строки между строками. Ячейку, внутри которой есть перевод строки, табуляция или кавычка, он берёт в двойные кавычки, а кавычки внутри удваивает. Ровно по этим правилам работает CSV, только разделитель другой. Поэтому разбирать вставку надо CSV-парсером с разделителем "\t":

function tableToArray(string $text): array {
	$f = fopen('php://memory', 'r+');
	fwrite($f, $text);
	rewind($f);
	$rows = [];
	while (($row = fgetcsv($f, 0, "\t", '"', '')) !== false) {
		if ($row !== [null]) {   // пустая строка в конце вставки
			$rows[] = $row;
		}
	}
	fclose($f);
	return $rows;
}

Почему не str_getcsv: она разбирает одну строку, а текст сначала пришлось бы разделить по переводам строк — и ячейка с переносом снова развалится. fgetcsv читает поток и сама склеивает такие ячейки. Поток в памяти (php://memory) избавляет от временного файла.

Последний параметр '' отключает символ экранирования. По умолчанию это \, и обратный слэш в конце ячейки ломал бы разбор. С PHP 8.4 параметр стоит указывать явно, иначе будет предупреждение об устаревании.

Проверено на PHP 8.5: ячейка "Чёрный↵пакетики, ""25 шт""" приходит одной ячейкой Чёрный↵пакетики, "25 шт".

Как я делал в 2021-м

Первая версия — делим по переводам строк, потом по табуляциям:

function tableToArray($textarea) {
	$arrtr = explode("\n", $textarea);
	foreach ($arrtr as $k => $v) {
		$arr[$k] = explode("\t", $v);
	}
	return $arr;
}

Ничто не предвещало беды, но вмешался человеческий фактор. Кое-кто повадился ставить переводы строк прямо в ячейках, и всё пошло по… лесу: одна ячейка превращалась в две строки массива.

Тогда я усложнил алгоритм. Считал число колонок по первой строке (шапку никто не правит, переносов в ней нет), делил весь текст по табуляциям, а последнюю ячейку каждой строки, слипшуюся с первой ячейкой следующей, разрезал по переводу строки. Заодно вычищал из ячеек переносы и спецсимволы:

function tableToArray($textarea) {
	$prearrtr = explode("\n", $textarea);
	$tdcount = count(explode("\t", $prearrtr[0])) - 1;
	$tabattr = explode("\t", $textarea);
	$forbiddenCharacters = "\/*?<>|+%!\n\r";
	$arr = [];
	$trnum = 0;
	$tdnum = 0;
	foreach ($tabattr as $v) {
		if ($tdnum < $tdcount) {
			$arr[$trnum][$tdnum] = preg_replace("/[${forbiddenCharacters}]/", '', $v);
			$tdnum++;
		} else {
			$finalv = array_values(array_diff(explode("\n", $v), array('')));
			$arr[$trnum][$tdnum] = preg_replace("/[${forbiddenCharacters}]/", '', $finalv[0]);
			$trnum++;
			$tdnum = 0;
			$arr[$trnum][$tdnum] = preg_replace("/[${forbiddenCharacters}]/", '', $finalv[1]);
			$tdnum++;
		}
	}
	return $arr;
}

Работало, но на честном слове. Перенос в последней ячейке строки или в шапке, кавычки Excel в данных, строка с другим числом ячеек — и массив съезжал. А синтаксис ${…} внутри строки с PHP 8.2 считается устаревшим.

Урок: прежде чем писать свой разбор формата, стоит проверить, нет ли уже готового парсера. Почти всегда есть, и он уже прошёл через все грабли, на которые ты только собираешься наступить.

Написать комментарий

Адрес почты не публикуется. Обязательные поля отмечены звёздочкой.

Возможно, вы не читали

Блог или сайт? Что выбрать?

12 октября 2011
Формально блог - это тоже сайт. Да что там блог! Даже страничка слепленная в Ворде, сохраненная в формате html и присобаченная на…