/** * CSV fuer Excel (#025). Framework-frei, damit spaetere Exporte (Protokoll, Phase 5) dieselben * Regeln nutzen. * * - Semikolon als Trenner und UTF-8-BOM: ein deutsches Excel oeffnet die Datei sonst als eine * Spalte mit kaputten Umlauten. * - Jeder Wert in Anfuehrungszeichen, damit Kommas in `authorized_keys`-Optionen und * Zeilenumbrueche nichts verschieben. * - Formel-Injektion: Excel und LibreOffice werten Zellen, die mit `= + - @` (oder Tab/CR) * beginnen, als Formel aus – ein Schluesselname wie `=HYPERLINK(…)` wuerde beim Oeffnen * ausgefuehrt. Ein vorangestelltes `'` macht daraus Text (OWASP „CSV Injection“). */ export const CSV_BOM = ""; export const CSV_TRENNER = ";"; const FORMELBEGINN = /^[=+\-@\t\r]/; export function csvEntschaerfen(wert: string): string { return FORMELBEGINN.test(wert) ? `'${wert}` : wert; } export function csvWert(wert: string | number | null | undefined): string { const text = csvEntschaerfen(wert == null ? "" : String(wert)); return `"${text.replace(/"/g, '""')}"`; } export function csvZeile(werte: readonly (string | number | null | undefined)[]): string { return werte.map(csvWert).join(CSV_TRENNER); } /** Ganze Datei: BOM, Kopfzeile, Datenzeilen, CRLF wie Excel selbst. */ export function csvDatei( kopf: readonly string[], zeilen: readonly (readonly (string | number | null | undefined)[])[], ): string { return CSV_BOM + [kopf, ...zeilen].map(csvZeile).join("\r\n") + "\r\n"; }