export function dataToCSV>( data: T[], accessors: Record any | string>, ): string { const csv: string[] = []; const keys = Object.keys(accessors); const columnNames: string[] = []; for (const key of keys) { let value = key.trim(); value = preventCSVInjection(value); value = wrapFieldIfNecessary(value); columnNames.push(value); } csv.push(columnNames.join(",")); for (const row of data) { const csvRow: string[] = []; for (const key of keys) { const accessor = accessors[key]; let value = typeof accessor === "function" ? (accessor?.(row) ?? "") : accessor; if (typeof value === "string" && value) { value = value.trim(); } else { value = String(value); } value = preventCSVInjection(value); value = wrapFieldIfNecessary(value); csvRow.push(value); } csv.push(csvRow.join(",")); } return csv.join("\n"); } function preventCSVInjection(value: string): string { if (isNaN(Number(value))) { value = value.replace(/^[=+\-@\t\r]+/g, ""); } return value; } function wrapFieldIfNecessary(value: string): string { if (value.includes('"')) { return value.replace(/"/g, '""'); } if ( value.includes(",") || value.includes("\n") || value.includes("\r") || value.includes('"') ) { return `"${value}"`; } return value; }