readXlsx($filePath); } else { $lines = $this->readCsv($filePath, $format['delimiter'], $format['enclosure']); } // Übersprungene Zeilen und optionale Headerzeile entfernen $skipRows = (int)$format['skip_rows']; $lines = array_slice($lines, $skipRows); if (!empty($format['has_header_row'])) { $lines = array_slice($lines, 1); } $colBuchungstag = (int)$format['col_buchungstag']; $colWertstellung = (int)$format['col_wertstellung']; $colVorgang = (int)$format['col_vorgang']; $colBuchungstext = (int)$format['col_buchungstext']; $colWert = (int)$format['col_wert']; $result = []; foreach ($lines as $i => $cells) { // Leerzeilen überspringen $joined = implode('', $cells); if (trim($joined) === '') { continue; } $buchungstag = $this->parseDate($cells[$colBuchungstag] ?? '', $format['date_format']); $wertstellung = $this->parseDate($cells[$colWertstellung] ?? '', $format['date_format']); $wert = $this->parseDecimal( $cells[$colWert] ?? '0', $format['decimal_separator'], $format['thousands_separator'] ); if ($buchungstag === null || $wertstellung === null) { continue; } $result[] = [ 'buchungstag' => $buchungstag, 'wertstellung' => $wertstellung, 'vorgang' => trim($cells[$colVorgang] ?? ''), 'buchungstext' => trim($cells[$colBuchungstext] ?? ''), 'wert' => $wert, 'raw_line' => implode($format['delimiter'], $cells), ]; } return $result; } private function readCsv(string $filePath, string $delimiter, string $enclosure): array { $content = file_get_contents($filePath); if ($content === false) { throw new \RuntimeException('Datei konnte nicht gelesen werden.'); } // BOM entfernen $content = preg_replace('/^\xEF\xBB\xBF/', '', $content); // Verschiedene Zeilenenden normalisieren $content = str_replace(["\r\n", "\r"], "\n", $content); $lines = explode("\n", $content); $result = []; foreach ($lines as $line) { if (trim($line) === '') { $result[] = []; continue; } $result[] = $this->parseCsvLine($line, $delimiter, $enclosure); } return $result; } private function parseCsvLine(string $line, string $delimiter, string $enclosure): array { $cells = []; $len = strlen($line); $i = 0; while ($i < $len) { if ($line[$i] === $enclosure) { // Quoted cell $i++; $cell = ''; while ($i < $len) { if ($line[$i] === $enclosure) { if ($i + 1 < $len && $line[$i + 1] === $enclosure) { $cell .= $enclosure; $i += 2; } else { $i++; break; } } else { $cell .= $line[$i++]; } } $cells[] = $cell; // Delimiter überspringen if ($i < $len && $line[$i] === $delimiter) { $i++; } } else { // Unquoted cell $end = strpos($line, $delimiter, $i); if ($end === false) { $cells[] = substr($line, $i); break; } $cells[] = substr($line, $i, $end - $i); $i = $end + 1; } } return $cells; } private function readXlsx(string $filePath): array { if (!class_exists('ZipArchive')) { throw new \RuntimeException('ZipArchive nicht verfügbar – XLSX kann nicht gelesen werden.'); } $zip = new \ZipArchive(); if ($zip->open($filePath) !== true) { throw new \RuntimeException('XLSX-Datei konnte nicht geöffnet werden.'); } // Shared Strings laden (optional) $sharedStrings = []; $ssXml = $zip->getFromName('xl/sharedStrings.xml'); if ($ssXml !== false) { $ss = simplexml_load_string($ssXml); foreach ($ss->si ?? [] as $si) { $text = ''; if (isset($si->t)) { $text = (string)$si->t; } else { foreach ($si->r ?? [] as $r) { $text .= (string)($r->t ?? ''); } } $sharedStrings[] = $text; } } // Erstes Worksheet lesen $sheetXml = $zip->getFromName('xl/worksheets/sheet1.xml'); $zip->close(); if ($sheetXml === false) { throw new \RuntimeException('Kein Arbeitsblatt in der XLSX-Datei gefunden.'); } $sheet = simplexml_load_string($sheetXml); $rows = []; $maxCol = 0; foreach ($sheet->sheetData->row ?? [] as $row) { $rowArr = []; foreach ($row->c as $cell) { [$colIdx] = $this->xlsxCellRef((string)$cell['r']); if ($colIdx > $maxCol) $maxCol = $colIdx; $type = (string)($cell['t'] ?? ''); $value = (string)($cell->v ?? ''); if ($type === 's') { $value = $sharedStrings[(int)$value] ?? ''; } elseif ($type === 'inlineStr') { $value = (string)($cell->is->t ?? ''); } $rowArr[$colIdx] = $value; } // Lücken auffüllen $filled = []; for ($c = 0; $c <= $maxCol; $c++) { $filled[] = $rowArr[$c] ?? ''; } $rows[] = $filled; } return $rows; } private function xlsxCellRef(string $ref): array { preg_match('/^([A-Z]+)(\d+)$/', $ref, $m); $col = 0; foreach (str_split($m[1] ?? 'A') as $char) { $col = $col * 26 + (ord($char) - ord('A') + 1); } return [$col - 1, (int)($m[2] ?? 1) - 1]; } private function parseDate(string $value, string $format): ?string { $value = trim($value); if ($value === '') return null; $d = \DateTime::createFromFormat($format, $value); if (!$d) return null; return $d->format('Y-m-d'); } private function parseDecimal(string $value, string $decimal, string $thousands): float { $value = trim($value); // Tausendertrennzeichen entfernen, Dezimaltrennzeichen → Punkt $value = str_replace($thousands, '', $value); $value = str_replace($decimal, '.', $value); $value = preg_replace('/[^\d.\-]/', '', $value); return (float)$value; } }