This commit is contained in:
seb
2026-08-04 01:24:34 +02:00
parent aa543f8631
commit dc956c4e60
4 changed files with 169 additions and 2 deletions

41
src/utils/csv.js Normal file
View File

@@ -0,0 +1,41 @@
/** Escape a value for a CSV field ("..." double-quoting per RFC 4180). */
function csvField(value) {
const text = value == null ? '' : String(value);
return /[";\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
}
/**
* Build a CSV string from an array of row objects (keys become the header).
* Uses `;` as the field separator with a `sep=;` hint line so Excel opens it
* correctly regardless of the OS list-separator locale.
*/
export function toCsv(rows) {
if (!rows.length) return '';
const headers = Object.keys(rows[0]);
const lines = [
'sep=;',
headers.map(csvField).join(';'),
...rows.map((row) => headers.map((h) => csvField(row[h])).join(';')),
];
return lines.join('\r\n');
}
/** Trigger a browser download of the given string, encoded as Windows-1252. */
export function downloadCsv(filename, csv) {
const bytes = new Uint8Array(csv.length);
for (let i = 0; i < csv.length; i++) {
const code = csv.charCodeAt(i);
// Windows-1252 covers the single-byte Latin-1 range + punctuation (e.g. „ " –).
// Category names are German/single-byte, so chars are well inside this range.
bytes[i] = code <= 0xff ? code : 0x3f; // '?' for any out-of-range char
}
const blob = new Blob([bytes], { type: 'text/csv;charset=windows-1252;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}