diff --git a/index.js b/index.js index c0b9e45..4cf8e8e 100644 --- a/index.js +++ b/index.js @@ -28,12 +28,16 @@ class CsvParser extends Transform { options.customNewline = options.newline !== defaults.newline - for (const key of ['newline', 'quote', 'separator']) { + for (const key of ['newline', 'quote']) { if (typeof options[key] !== 'undefined') { ([options[key]] = Buffer.from(options[key])) } } + if (typeof options.separator !== 'string') { + throw new TypeError(`Separator must be a String. Received: ${typeof options.separator}`) + } + // if escape is not defined on the passed options, use the end value of quote options.escape = (opts || {}).escape ? Buffer.from(options.escape)[0] : options.quote @@ -89,14 +93,22 @@ class CsvParser extends Transform { } parseLine (buffer, start, end) { - const { customNewline, escape, mapHeaders, mapValues, quote, separator, skipComments, skipLines } = this.options - - end-- // trim newline + const { + customNewline, + escape, + mapHeaders, + mapValues, + quote, + separator, + skipComments, + skipLines + } = this.options + + end-- // Trim newline if (!customNewline && buffer.length && buffer[end - 1] === cr) { end-- } - const comma = separator const cells = [] let isQuoted = false let offset = start @@ -108,6 +120,9 @@ class CsvParser extends Transform { } } + const separatorBuffer = Buffer.from(separator) + const separatorLength = separatorBuffer.length + const mapValue = (value) => { if (this.state.first) { return value @@ -121,8 +136,17 @@ class CsvParser extends Transform { for (let i = start; i < end; i++) { const isStartingQuote = !isQuoted && buffer[i] === quote - const isEndingQuote = isQuoted && buffer[i] === quote && i + 1 <= end && buffer[i + 1] === comma - const isEscape = isQuoted && buffer[i] === escape && i + 1 < end && buffer[i + 1] === quote + const isEndingQuote = + isQuoted && + buffer[i] === quote && + i + 1 <= end && + buffer[i + 1] === separatorBuffer[0] + + const isEscape = + isQuoted && + buffer[i] === escape && + i + 1 < end && + buffer[i + 1] === quote if (isStartingQuote || isEndingQuote) { isQuoted = !isQuoted @@ -132,11 +156,15 @@ class CsvParser extends Transform { continue } - if (buffer[i] === comma && !isQuoted) { - let value = this.parseCell(buffer, offset, i) - value = mapValue(value) - cells.push(value) - offset = i + 1 + if (!isQuoted && i + separatorLength <= end) { + const segment = buffer.slice(i, i + separatorLength) + if (segment.equals(separatorBuffer)) { + let value = this.parseCell(buffer, offset, i) + value = mapValue(value) + cells.push(value) + offset = i + separatorLength + i += separatorLength - 1 + } } } @@ -146,7 +174,7 @@ class CsvParser extends Transform { cells.push(value) } - if (buffer[end - 1] === comma) { + if (buffer.slice(end - separatorLength, end).equals(separatorBuffer)) { cells.push(mapValue(this.state.empty)) } @@ -155,7 +183,9 @@ class CsvParser extends Transform { if (this.state.first && !skip) { this.state.first = false - this.headers = cells.map((header, index) => mapHeaders({ header, index })) + this.headers = cells.map((header, index) => + mapHeaders({ header, index }) + ) this.emit('headers', this.headers) return diff --git a/package.json b/package.json index de49c9a..a59780b 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,8 @@ "ava": { "files": [ "!**/fixtures/**", - "!**/helpers/**" + "!**/helpers/**", + "test/*.test.js" ] }, "husky": { diff --git a/test/fixtures/multi-separator.csv b/test/fixtures/multi-separator.csv new file mode 100644 index 0000000..28c0b4b --- /dev/null +++ b/test/fixtures/multi-separator.csv @@ -0,0 +1,4 @@ +nombre#|#edad#|#ciudad +Juan#|#25#|#Madrid +Ivan#|#30#|#Barcelona +Luis#|#28#|#Valencia diff --git a/test/multicharSeparator.test.js b/test/multicharSeparator.test.js new file mode 100644 index 0000000..d3d9a7d --- /dev/null +++ b/test/multicharSeparator.test.js @@ -0,0 +1,24 @@ +const test = require('ava') +const fs = require('fs') +const path = require('path') +const csv = require('../') + +test('parse CSV with multi-character separator', async (t) => { + const filePath = path.join(__dirname, 'fixtures', 'multi-separator.csv') + const input = fs.createReadStream(filePath) + + const results = [] + return new Promise((resolve) => { + input + .pipe(csv({ separator: '#|#' })) + .on('data', (data) => results.push(data)) + .on('end', () => { + t.deepEqual(results, [ + { nombre: 'Juan', edad: '25', ciudad: 'Madrid' }, + { nombre: 'Ivan', edad: '30', ciudad: 'Barcelona' }, + { nombre: 'Luis', edad: '28', ciudad: 'Valencia' } + ]) + resolve() + }) + }) +})