Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 44 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
}
}
}

Expand All @@ -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))
}

Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"ava": {
"files": [
"!**/fixtures/**",
"!**/helpers/**"
"!**/helpers/**",
"test/*.test.js"
]
},
"husky": {
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/multi-separator.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nombre#|#edad#|#ciudad
Juan#|#25#|#Madrid
Ivan#|#30#|#Barcelona
Luis#|#28#|#Valencia
24 changes: 24 additions & 0 deletions test/multicharSeparator.test.js
Original file line number Diff line number Diff line change
@@ -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()
})
})
})