UNPKG

@pirxpilot/csv-parse

Version:

Minimal CSV parser implemented using web `TransformStream`

145 lines (131 loc) 2.81 kB
export class CsvLineStream extends TransformStream { constructor(options) { super(parser(options)); } } const QUOTE = '"'; const FS = ','; const LS = '\n'; const ESC = '\\'; function parser({ trim = false } = {}) { const states = Object.create(null); const field = []; let line = []; states.startField = { on(c, controller) { switch (c) { case FS: emitField(); return states.startField; case LS: emitLine(controller); return states.startField; case QUOTE: return states.quoting; default: field.push(c); return states.inField; } }, flush() {} }; states.inField = { on(c, controller) { switch (c) { case FS: emitField(); return states.startField; case LS: emitField(); emitLine(controller); return states.startField; default: field.push(c); return states.inField; } }, flush(controller) { emitField(); emitLine(controller); } }; states.quoting = { on(c) { switch (c) { case QUOTE: return states.postQuoting; case ESC: return states.escaping; default: field.push(c); return states.quoting; } }, flush() {} }; states.postQuoting = { on(c, controller) { switch (c) { case FS: emitField(); return states.startField; case LS: emitField(); emitLine(controller); return states.startField; default: // ignore anything else, or maybe raise error return states.postQuoting; } }, flush(controller) { emitField(); emitLine(controller); } }; states.escaping = { on(c) { if (c === QUOTE) { field.push(QUOTE); } else { field.push(ESC, c); } return states.quoting; }, flush(controller) { field.push(ESC); states.quoting.flush(controller); } }; let state = states.startField; return { transform, flush }; function emitField() { let f = field.join(''); if (trim) { f = f.trim(); } line.push(f); field.length = 0; } function emitLine(controller) { controller.enqueue(line); line = []; } /** * Process next `codePoint` in the stream by feeding the state machine. Emit a new line if it is ready. * * @param {String} str * @param {TransformStreamDefaultController} controller */ function transform(str, controller) { for (const cp of str) { state = state.on(cp, controller); } } function flush(controller) { state.flush(controller); } }