/* =====================================================================
EIN ZIP- UND XLSX-SCHREIBER FUER PRUEFUNGEN (14.09.2026)
Er steht hier und nicht in einer der Pruefdateien, weil ihn seit
heute ZWEI brauchen: pruef-xlsx (der Leser) und pruef-backstage-import
(der Weg durch die Oberflaeche). Zwei Abschriften waeren zwei
Gelegenheiten, dass die eine ein Feld anders schreibt als die andere
-- und dann misst man zwei verschiedene Dinge und nennt beide "eine
Excel-Datei".
Gebaut wird nach der echten Backstage-Form: alles in der
Zeichenkettentabelle (`t="s"`), ein Blatt, keine Formate.
`crc32` gibt es in node:zlib seit Node 22. Fehlt es, sagen die
Pruefungen das -- der dritte Ausgang, kein stiller Aussetzer.
===================================================================== */
import { deflateRawSync, crc32 } from "node:zlib";
export const kannXlsxBauen = typeof crc32 === "function";
export function zipBauen(eintraege) {
const lokale = [];
const zentrale = [];
let stelle = 0;
for (const [name, inhalt] of eintraege) {
const roh = Buffer.from(inhalt, "utf8");
const gepackt = deflateRawSync(roh);
const pruef = crc32(roh) >>> 0;
const nameB = Buffer.from(name, "utf8");
const lokal = Buffer.alloc(30);
lokal.writeUInt32LE(0x04034b50, 0);
lokal.writeUInt16LE(20, 4); // Version
lokal.writeUInt16LE(0, 6); // Merker
lokal.writeUInt16LE(8, 8); // Deflate
lokal.writeUInt32LE(pruef, 14);
lokal.writeUInt32LE(gepackt.length, 18);
lokal.writeUInt32LE(roh.length, 22);
lokal.writeUInt16LE(nameB.length, 26);
lokale.push(lokal, nameB, gepackt);
const zentral = Buffer.alloc(46);
zentral.writeUInt32LE(0x02014b50, 0);
zentral.writeUInt16LE(20, 4);
zentral.writeUInt16LE(20, 6);
zentral.writeUInt16LE(8, 10);
zentral.writeUInt32LE(pruef, 16);
zentral.writeUInt32LE(gepackt.length, 20);
zentral.writeUInt32LE(roh.length, 24);
zentral.writeUInt16LE(nameB.length, 28);
zentral.writeUInt32LE(stelle, 42);
zentrale.push(zentral, nameB);
stelle += lokal.length + nameB.length + gepackt.length;
}
const koerper = Buffer.concat(lokale);
const verzeichnis = Buffer.concat(zentrale);
const ende = Buffer.alloc(22);
ende.writeUInt32LE(0x06054b50, 0);
ende.writeUInt16LE(eintraege.length, 8);
ende.writeUInt16LE(eintraege.length, 10);
ende.writeUInt32LE(verzeichnis.length, 12);
ende.writeUInt32LE(koerper.length, 16);
return Buffer.concat([koerper, verzeichnis, ende]);
}
export const RELS = `
`;
/** Baut eine .xlsx aus Zeilen. Alles wandert in die
* Zeichenkettentabelle – genau wie Backstage es tut. */
export function xlsxBauen(zeilen, { blattName = "Sheet1", extra = "" } = {}) {
const texte = [];
const nummer = (w) => {
const i = texte.indexOf(w);
if (i >= 0) return i;
texte.push(w);
return texte.length - 1;
};
const buchstabe = (n) => {
let s = "";
n += 1;
while (n > 0) { const r = (n - 1) % 26; s = String.fromCharCode(65 + r) + s; n = (n - r - 1) / 26; }
return s;
};
const zeilenXml = zeilen.map((z, r) => {
const zellen = z.map((w, c) => {
if (w === null || w === undefined || w === "") return "";
return `${nummer(String(w))}`;
}).join("");
return `${zellen}
`;
}).join("");
const blatt = `
${zeilenXml}`;
const strings = `
`
+ texte.map((t) => `${t.replace(/&/g, "&").replace(/`).join("")
+ "";
const mappe = `
`;
return zipBauen([
["xl/workbook.xml", mappe],
["xl/_rels/workbook.xml.rels", RELS],
["xl/sharedStrings.xml", strings],
["xl/worksheets/sheet1.xml", blatt],
]);
}