diff --git a/lib/export.ts b/lib/export.ts index 865c294..bfc9df9 100644 --- a/lib/export.ts +++ b/lib/export.ts @@ -23,9 +23,10 @@ export function sanitizeForSpreadsheetCell(text: string): string { return /^[=+\-@]/.test(text) ? `'${text}` : text; } -function csvCell(value: string | number | boolean | null, trenner: string): string { +function csvCell(value: string | number | boolean | null, trenner: string, formelschutz: boolean): string { if (value === null || value === undefined) return ""; - const text = typeof value === "boolean" ? (value ? "Ja" : "Nein") : sanitizeForSpreadsheetCell(String(value)); + const roh = typeof value === "boolean" ? (value ? "Ja" : "Nein") : String(value); + const text = formelschutz ? sanitizeForSpreadsheetCell(roh) : roh; // Das Trennzeichen gehört in die Prüfung: mit Komma als Trenner muss ein // Komma im Wert maskiert werden, sonst zerfällt die Zeile in zwei Spalten. return text.includes(trenner) || /["\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text; @@ -39,15 +40,22 @@ function csvCell(value: string | number | boolean | null, trenner: string): stri * ankommen. Ein fremdes System will oft das Gegenteil — Cornerstone etwa * erwartet Kommas, und ein BOM machte dort aus der ersten Spalte eine, die in * keiner Zuordnung vorkommt, weil das unsichtbare Zeichen im Namen steckt. + * + * `formelschutz: false` schaltet das vorangestellte Hochkomma ab (siehe + * sanitizeForSpreadsheetCell). Es gehört in eine Datei, die ein Mensch in Excel + * öffnet, und es verdirbt eine, die eine Maschine einliest: eine österreichische + * Telefonnummer beginnt mit „+", und im Zielsystem stünde danach `'+43…` als + * Nummer. Nur für Dateien setzen, die ausschliesslich ein Fremdsystem liest. */ -export type CsvForm = { trenner?: "," | ";"; bom?: boolean }; +export type CsvForm = { trenner?: "," | ";"; bom?: boolean; formelschutz?: boolean }; export function toCsv(rows: T[], columns: ExportColumn[], form: CsvForm = {}): string { const trenner = form.trenner ?? ";"; const bom = form.bom ?? true; - const lines = [columns.map((c) => csvCell(c.header, trenner)).join(trenner)]; + const schutz = form.formelschutz ?? true; + const lines = [columns.map((c) => csvCell(c.header, trenner, schutz)).join(trenner)]; for (const row of rows) { - lines.push(columns.map((c) => csvCell(c.get(row), trenner)).join(trenner)); + lines.push(columns.map((c) => csvCell(c.get(row), trenner, schutz)).join(trenner)); } return (bom ? "" : "") + lines.join("\r\n"); } diff --git a/lib/lolyo.ts b/lib/lolyo.ts index 16ac5fe..3befe3d 100644 --- a/lib/lolyo.ts +++ b/lib/lolyo.ts @@ -17,13 +17,19 @@ import type { CsvForm, ExportColumn } from "./export"; // hiesse, eine Spalte zu liefern, die LOLYO nicht zuordnet. /** - * Semikolon, kein BOM — so sieht die Vorlage aus. + * Semikolon, kein BOM, kein Formelschutz — so liest LOLYO die Datei. * - * Die Vorgabe von toCsv ist für Excel gemacht und setzt ein BOM voran; das - * hinge unsichtbar am Namen der ersten Spalte, und „Benutzer/Code" käme beim - * Einlesen in keiner Zuordnung mehr vor. + * Die Vorgabe von toCsv ist für Excel gemacht. Das BOM hinge unsichtbar am + * Namen der ersten Spalte, und „Benutzer/Code" käme beim Einlesen in keiner + * Zuordnung mehr vor. + * + * Der Formelschutz stellt jedem Wert, der mit `=`, `+`, `-` oder `@` beginnt, + * ein Hochkomma voran, damit Excel ihn nicht als Formel rechnet. Hier wäre das + * falsch: **jede** österreichische Telefonnummer beginnt mit „+", und in LOLYO + * stünde danach `'+4366…` als Nummer — in jedem Konto, und niemandem fiele es + * beim Erzeugen auf. Die Datei liest eine Maschine, kein Mensch in Excel. */ -export const LOLYO_CSV: CsvForm = { trenner: ";", bom: false }; +export const LOLYO_CSV: CsvForm = { trenner: ";", bom: false, formelschutz: false }; /** Für alle dieselbe. Wer eine andere braucht, stellt sie in LOLYO um, nicht hier. */ export const LOLYO_SPRACHE = "DE"; diff --git a/tests/unit/lolyo.test.ts b/tests/unit/lolyo.test.ts index fa159a9..e536a40 100644 --- a/tests/unit/lolyo.test.ts +++ b/tests/unit/lolyo.test.ts @@ -121,10 +121,13 @@ describe("Die Datei", () => { expect(csv.split("\r\n")[0]).toBe(VORGABE); }); - it("maskiert eine Telefonnummer, die Excel als Formel läse", () => { - // „+43…" beginnt mit einem Pluszeichen — ohne Schutz macht Excel daraus - // beim Öffnen eine Formel und zeigt einen Fehler statt der Nummer. + it("lässt die Telefonnummer unangetastet", () => { + // Der Formelschutz von toCsv stellt jedem Wert mit führendem „+" ein + // Hochkomma voran, damit Excel ihn nicht rechnet. Diese Datei liest eine + // Maschine: dort stünde danach in **jedem** Konto `'+4366…` als Nummer, + // weil jede österreichische Nummer mit „+" beginnt. const csv = toCsv([zeile()], lolyoSpalten(), LOLYO_CSV); - expect(csv).toContain("'+4366412345678"); + expect(csv).toContain(";+4366412345678;"); + expect(csv).not.toContain("'+43"); }); });