← back to Rentv Adintel
src/export/csv.js
82 lines
'use strict';
/**
* Pure-Node CSV writer — RFC 4180 compliant quoting/escaping.
*
* - All fields containing commas, double-quotes, or newlines are quoted.
* - Double-quotes inside a quoted field are escaped as "".
* - Null/undefined rendered as empty string.
* - Numbers and booleans serialized as strings.
* - CRLF line endings (RFC 4180).
*
* @module src/export/csv
*/
/**
* Serialize one field value to RFC 4180.
* @param {*} value
* @returns {string}
*/
function escapeField(value) {
if (value === null || value === undefined) return '';
let str = String(value);
// Must quote if contains comma, double-quote, CR, LF, or leading/trailing space
if (/[",\r\n]/.test(str) || str !== str.trim()) {
str = '"' + str.replace(/"/g, '""') + '"';
}
return str;
}
/**
* Serialize one row (array of values) to a CSV line with CRLF.
* @param {Array} row
* @returns {string}
*/
function rowToLine(row) {
return row.map(escapeField).join(',') + '\r\n';
}
/**
* Convert an array of objects to a CSV string.
* Column order is determined by the keys of the first row (or the explicit
* `columns` parameter).
*
* @param {Object[]} rows
* @param {Object} [opts]
* @param {string[]} [opts.columns] - explicit column order; defaults to Object.keys(rows[0])
* @param {boolean} [opts.header] - include header row (default true)
* @returns {string} UTF-8 CSV string (BOM prefix for Excel compatibility)
*/
function toCsv(rows, opts = {}) {
const { columns, header = true } = opts;
if (!rows || rows.length === 0) {
const cols = columns || [];
const headerLine = header && cols.length ? rowToLine(cols) : '';
return '' + headerLine; // BOM for Excel
}
const cols = columns || Object.keys(rows[0]);
const lines = [];
if (header) lines.push(rowToLine(cols));
for (const row of rows) {
lines.push(rowToLine(cols.map((c) => row[c])));
}
// BOM (U+FEFF) so Excel on Windows recognizes UTF-8 correctly
return '' + lines.join('');
}
/**
* Convert an array of objects to a Buffer (UTF-8 with BOM).
* @param {Object[]} rows
* @param {Object} [opts]
* @returns {Buffer}
*/
function toCsvBuffer(rows, opts = {}) {
return Buffer.from(toCsv(rows, opts), 'utf8');
}
module.exports = { toCsv, toCsvBuffer, escapeField, rowToLine };