Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | 'use strict'
/**
* Minimal, deterministic RFC-4180-style CSV parser.
*
* Handles: quoted fields, commas inside quotes, escaped quotes (""),
* embedded newlines inside quotes, and CRLF/CR/LF line endings.
*
* Returns rows as plain string arrays; header mapping is done in columns.js.
*/
function parseCsv(text) {
const rows = []
let field = ''
let row = []
let inQuotes = false
let i = 0
const len = text.length
const pushField = () => {
row.push(field)
field = ''
}
const pushRow = () => {
pushField()
rows.push(row)
row = []
}
while (i < len) {
const char = text[i]
if (inQuotes) {
if (char === '"') {
if (text[i + 1] === '"') {
field += '"'
i += 2
continue
}
inQuotes = false
i += 1
continue
}
field += char
i += 1
continue
}
if (char === '"') {
inQuotes = true
i += 1
continue
}
if (char === ',') {
pushField()
i += 1
continue
}
if (char === '\r') {
// swallow a following \n (CRLF)
if (text[i + 1] === '\n') i += 1
pushRow()
i += 1
continue
}
if (char === '\n') {
pushRow()
i += 1
continue
}
field += char
i += 1
}
// flush trailing field/row if the file does not end with a newline
if (field.length > 0 || row.length > 0) {
pushRow()
}
// drop fully-empty trailing rows
while (rows.length > 0 && rows[rows.length - 1].every(cell => cell.trim() === '')) {
rows.pop()
}
return rows
}
/**
* Serialize an array-of-objects back to CSV using an explicit ordered header
* list. Values are quoted when they contain a comma, quote, or newline.
*/
function toCsv(headers, records) {
const escape = value => {
const str = value == null ? '' : String(value)
if (/[",\r\n]/.test(str)) {
return '"' + str.replace(/"/g, '""') + '"'
}
return str
}
const lines = [headers.map(escape).join(',')]
for (const record of records) {
lines.push(headers.map(h => escape(record[h])).join(','))
}
return lines.join('\n') + '\n'
}
module.exports = { parseCsv, toCsv }
|