0 && ($raw = fgets($fh)) !== false) { $normalized = $this->normalizeLine($raw); if (trim($normalized) !== '') { $line = $normalized; break; } } fclose($fh); return $line; } /** * Detect delimiter and return columns for first row. * If $hasHeader is false, returns positional indices instead of header names. * Returns [delimiter, columns]. */ public function detectColumnsFromCsv(string $path, bool $hasHeader): array { // Use actual tab character for TSV; keep other common delimiters $delims = [',', ';', '|', "\t"]; $bestDelim = ','; $bestCols = []; $firstLine = $this->readFirstMeaningfulLine($path); if ($firstLine === null) { return [$bestDelim, []]; } // Already normalized by readFirstMeaningfulLine $maxCount = 0; foreach ($delims as $d) { $row = str_getcsv($firstLine, $d); $count = is_array($row) ? count($row) : 0; if ($count > $maxCount) { $maxCount = $count; $bestDelim = $d; $bestCols = $row; } } // Fallback: if str_getcsv failed to split but we clearly see delimiters, do a simple explode if ($maxCount <= 1) { foreach (["\t", ';', ',', '|'] as $d) { if (substr_count($firstLine, $d) >= 1) { $parts = explode($d, $firstLine); if (count($parts) > $maxCount) { $bestDelim = $d; $bestCols = $parts; $maxCount = count($parts); } } } } if (! $hasHeader) { // return positional indices 0..N-1 $cols = []; for ($i = 0; $i < $maxCount; $i++) { $cols[] = (string) $i; } return [$bestDelim, $cols]; } // Clean header names $clean = array_map(function ($v) { $v = trim((string) $v); $v = preg_replace('/\s+/', ' ', $v); return $v; }, $bestCols); return [$bestDelim, $clean]; } /** * Parse columns from CSV using a specific delimiter. If $hasHeader is false, * returns positional indices instead of header names. */ public function parseColumnsFromCsv(string $path, string $delimiter, bool $hasHeader): array { $firstLine = $this->readFirstMeaningfulLine($path); if ($firstLine === null) { return []; } // Already normalized by readFirstMeaningfulLine $row = str_getcsv($firstLine, $delimiter); $count = is_array($row) ? count($row) : 0; // Fallback explode if str_getcsv failed to split if ($count <= 1 && substr_count($firstLine, $delimiter) >= 1) { $row = explode($delimiter, $firstLine); $count = count($row); } if ($hasHeader) { return array_map(function ($v) { $v = trim((string) $v); $v = preg_replace('/\s+/', ' ', $v); return $v; }, $row ?: []); } $cols = []; for ($i = 0; $i < $count; $i++) { $cols[] = (string) $i; } return $cols; } }