, rows: array>} */ public function parse(string $content): array { if (str_starts_with($content, "\xEF\xBB\xBF")) { $content = substr($content, 3); } if (!mb_check_encoding($content, 'UTF-8')) { $content = mb_convert_encoding($content, 'UTF-8', 'Windows-1252'); } $firstLine = strtok($content, "\r\n") ?: ''; $delimiter = ';'; $best = 0; foreach ([';', ',', "\t"] as $candidate) { $count = substr_count($firstLine, $candidate); if ($count > $best) { $best = $count; $delimiter = $candidate; } } $stream = fopen('php://temp', 'r+'); fwrite($stream, $content); rewind($stream); $header = fgetcsv($stream, null, $delimiter, '"', ''); if (!is_array($header)) { fclose($stream); return ['columns' => [], 'rows' => []]; } $columns = []; $seen = []; foreach ($header as $i => $name) { $name = trim((string)$name); if ($name === '') { $name = 'column_' . ($i + 1); } // Duplicate headers (e.g. the keyword research CSV repeats // "Vol (Global)" for the secondary block) get a _2/_3 suffix - // otherwise the later block silently overwrites the earlier one. if (isset($seen[$name])) { $seen[$name]++; $name .= '_' . $seen[$name]; } else { $seen[$name] = 1; } $columns[$i] = $name; } $rows = []; while (($data = fgetcsv($stream, null, $delimiter, '"', '')) !== false) { if ($data === [null] || $data === ['']) { continue; // blank line } $row = []; $hasContent = false; foreach ($columns as $i => $name) { $value = trim((string)($data[$i] ?? '')); $row[$name] = $value; if ($value !== '') { $hasContent = true; } } if ($hasContent) { $rows[] = $row; } } fclose($stream); return ['columns' => array_values($columns), 'rows' => $rows]; } }