csv-parse
Advanced tools
Comparing version
@@ -6,4 +6,6 @@ | ||
Please join and contribute: | ||
Below is the list of upgrades we are considering for the next major release. The majority of them are just simple refactoring. They will however introduce backward incompatibilities. | ||
We invite you to join and contribute but create an issue before engaging any work. Some tasks are scheduled for another time or might depends on another one. | ||
* `skip_lines_with_empty_values`: rename to skip_records_with_empty_values (easy) | ||
@@ -16,7 +18,16 @@ * `skip_lines_with_error`: rename to skip_records_with_error (easy) | ||
* encoding: new encoding_input and encoding_output options (medium) | ||
* context: isolate info properties at context root (easy) | ||
* context: merge record, raw, context, info, error into a single object (medium) | ||
* relax_column_count: rename INCONSISTENT_RECORD_LENGTH to RECORD_INCONSISTENT_FIELDS_LENGTH (easy) | ||
* relax_column_count: rename RECORD_DONT_MATCH_COLUMNS_LENGTH to RECORD_INCONSISTENT_COLUMNS (easy) | ||
* `columns_duplicates_to_array`: this is just too long but I don't have much insipiration for a better name | ||
* `relax_column_count`: rename INCONSISTENT_RECORD_LENGTH to RECORD_INCONSISTENT_FIELDS_LENGTH (easy) | ||
* `relax_column_count`: rename RECORD_DONT_MATCH_COLUMNS_LENGTH to RECORD_INCONSISTENT_COLUMNS (easy) | ||
* `info`: remove the `parser.info` object and move its properties to `state` | ||
* `info`: rename the `info` related properties and functions to `context` | ||
## Version 4.16.0 | ||
* fix: info print the number of encountered line when emited | ||
* feat: cast expose context.empty_lines | ||
* fix: handle empty column names properly | ||
* feat: enforce usage of columns with columns_duplicates_to_array | ||
* fix: update error message with invalid column type | ||
## Version 4.15.4 | ||
@@ -23,0 +34,0 @@ |
@@ -180,3 +180,3 @@ "use strict"; | ||
} else { | ||
throw new CsvError('CSV_INVALID_OPTION_COLUMNS', ['Invalid option columns:', 'expect an object, a function or true,', "got ".concat(JSON.stringify(options.columns))], options); | ||
throw new CsvError('CSV_INVALID_OPTION_COLUMNS', ['Invalid option columns:', 'expect an array, a function or true,', "got ".concat(JSON.stringify(options.columns))], options); | ||
} // Normalize option `columns_duplicates_to_array` | ||
@@ -189,2 +189,4 @@ | ||
throw new CsvError('CSV_INVALID_OPTION_COLUMNS_DUPLICATES_TO_ARRAY', ['Invalid option columns_duplicates_to_array:', 'expect an boolean,', "got ".concat(JSON.stringify(options.columns_duplicates_to_array))], options); | ||
} else if (options.columns === false) { | ||
throw new CsvError('CSV_INVALID_OPTION_COLUMNS_DUPLICATES_TO_ARRAY', ['Invalid option columns_duplicates_to_array:', 'the `columns` mode must be activated.'], options); | ||
} // Normalize option `comment` | ||
@@ -515,6 +517,6 @@ | ||
escapeIsQuote: Buffer.isBuffer(options.escape) && Buffer.isBuffer(options.quote) && Buffer.compare(options.escape, options.quote) === 0, | ||
expectedRecordLength: options.columns === null ? 0 : options.columns.length, | ||
// columns can be `false`, `true`, `Array` | ||
expectedRecordLength: Array.isArray(options.columns) ? options.columns.length : undefined, | ||
field: new ResizeableBuffer(20), | ||
firstLineToHeaders: fnFirstLineToHeaders, | ||
info: Object.assign({}, this.info), | ||
needMoreDataSize: Math.max.apply(Math, [// Skip if the remaining buffer smaller than comment | ||
@@ -577,3 +579,2 @@ options.comment !== null ? options.comment.length : 0].concat(_toConsumableArray(options.delimiter.map(function (delimiter) { | ||
from_line = _this$options.from_line, | ||
info = _this$options.info, | ||
ltrim = _this$options.ltrim, | ||
@@ -651,7 +652,2 @@ max_record_size = _this$options.max_record_size, | ||
this.info.lines++; | ||
if (info === true && this.state.record.length === 0 && this.state.field.length === 0 && this.state.wasQuoting === false) { | ||
this.state.info = Object.assign({}, this.info); | ||
} | ||
this.state.wasRowDelimiter = false; | ||
@@ -730,3 +726,3 @@ } | ||
} else if (relax === false) { | ||
var err = this.__error(new CsvError('CSV_INVALID_CLOSING_QUOTE', ['Invalid Closing Quote:', "got \"".concat(String.fromCharCode(nextChr), "\""), "at line ".concat(this.info.lines), 'instead of delimiter, record delimiter, trimable character', '(if activated) or comment'], this.options, this.__context())); | ||
var err = this.__error(new CsvError('CSV_INVALID_CLOSING_QUOTE', ['Invalid Closing Quote:', "got \"".concat(String.fromCharCode(nextChr), "\""), "at line ".concat(this.info.lines), 'instead of delimiter, record delimiter, trimable character', '(if activated) or comment'], this.options, this.__infoField())); | ||
@@ -744,3 +740,3 @@ if (err !== undefined) return err; | ||
if (relax === false) { | ||
var _err = this.__error(new CsvError('INVALID_OPENING_QUOTE', ['Invalid Opening Quote:', "a quote is found inside a field at line ".concat(this.info.lines)], this.options, this.__context(), { | ||
var _err = this.__error(new CsvError('INVALID_OPENING_QUOTE', ['Invalid Opening Quote:', "a quote is found inside a field at line ".concat(this.info.lines)], this.options, this.__infoField(), { | ||
field: this.state.field | ||
@@ -833,3 +829,3 @@ })); | ||
if (max_record_size !== 0 && this.state.record_length + this.state.field.length > max_record_size) { | ||
var _err2 = this.__error(new CsvError('CSV_MAX_RECORD_SIZE', ['Max Record Size:', 'record exceed the maximum number of tolerated bytes', "of ".concat(max_record_size), "at line ".concat(this.info.lines)], this.options, this.__context())); | ||
var _err2 = this.__error(new CsvError('CSV_MAX_RECORD_SIZE', ['Max Record Size:', 'record exceed the maximum number of tolerated bytes', "of ".concat(max_record_size), "at line ".concat(this.info.lines)], this.options, this.__infoField())); | ||
@@ -847,3 +843,3 @@ if (_err2 !== undefined) return _err2; | ||
} else if (rtrim === true && !this.__isCharTrimable(chr)) { | ||
var _err3 = this.__error(new CsvError('CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE', ['Invalid Closing Quote:', 'found non trimable byte after quote', "at line ".concat(this.info.lines)], this.options, this.__context())); | ||
var _err3 = this.__error(new CsvError('CSV_NON_TRIMABLE_CHAR_AFTER_CLOSING_QUOTE', ['Invalid Closing Quote:', 'found non trimable byte after quote', "at line ".concat(this.info.lines)], this.options, this.__infoField())); | ||
@@ -857,3 +853,3 @@ if (_err3 !== undefined) return _err3; | ||
if (this.state.quoting === true) { | ||
var _err4 = this.__error(new CsvError('CSV_QUOTE_NOT_CLOSED', ['Quote Not Closed:', "the parsing is finished with an opening quote at line ".concat(this.info.lines)], this.options, this.__context())); | ||
var _err4 = this.__error(new CsvError('CSV_QUOTE_NOT_CLOSED', ['Quote Not Closed:', "the parsing is finished with an opening quote at line ".concat(this.info.lines)], this.options, this.__infoField())); | ||
@@ -912,3 +908,3 @@ if (_err4 !== undefined) return _err4; | ||
if (columns === true) { | ||
if (isRecordEmpty(record)) { | ||
if (skip_lines_with_empty_values === true && isRecordEmpty(record)) { | ||
this.__resetRecord(); | ||
@@ -929,3 +925,3 @@ | ||
// CSV_RECORD_INCONSISTENT_FIELDS_LENGTH | ||
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', ['Invalid Record Length:', "expect ".concat(this.state.expectedRecordLength, ","), "got ".concat(recordLength, " on line ").concat(this.info.lines)], this.options, this.__context(), { | ||
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', ['Invalid Record Length:', "expect ".concat(this.state.expectedRecordLength, ","), "got ".concat(recordLength, " on line ").concat(this.info.lines)], this.options, this.__infoField(), { | ||
record: record | ||
@@ -935,3 +931,3 @@ }) : // Todo: rename CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH to | ||
new CsvError('CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH', ['Invalid Record Length:', "columns length is ".concat(columns.length, ","), // rename columns | ||
"got ".concat(recordLength, " on line ").concat(this.info.lines)], this.options, this.__context(), { | ||
"got ".concat(recordLength, " on line ").concat(this.info.lines)], this.options, this.__infoField(), { | ||
record: record | ||
@@ -950,8 +946,6 @@ }); | ||
if (skip_lines_with_empty_values === true) { | ||
if (isRecordEmpty(record)) { | ||
this.__resetRecord(); | ||
if (skip_lines_with_empty_values === true && isRecordEmpty(record)) { | ||
this.__resetRecord(); | ||
return; | ||
} | ||
return; | ||
} | ||
@@ -969,2 +963,3 @@ | ||
if (from === 1 || this.info.records >= from) { | ||
// With columns, records are object | ||
if (columns !== false) { | ||
@@ -987,3 +982,3 @@ var obj = {}; // Transform record array to an object | ||
var objname = this.options.objname; | ||
var objname = this.options.objname; // Without objname (default) | ||
@@ -997,3 +992,3 @@ if (objname === undefined) { | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
info: this.__infoRecord() | ||
} : {})); | ||
@@ -1010,3 +1005,4 @@ | ||
} | ||
} | ||
} // With objname (default) | ||
} else { | ||
@@ -1019,3 +1015,3 @@ if (raw === true || info === true) { | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
info: this.__infoRecord() | ||
} : {})); | ||
@@ -1033,3 +1029,4 @@ | ||
} | ||
} | ||
} // Without columns, records are array | ||
} else { | ||
@@ -1042,3 +1039,3 @@ if (raw === true || info === true) { | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
info: this.__infoRecord() | ||
} : {})); | ||
@@ -1070,3 +1067,3 @@ | ||
if (!Array.isArray(headers)) { | ||
return this.__error(new CsvError('CSV_INVALID_COLUMN_MAPPING', ['Invalid Column Mapping:', 'expect an array from column function,', "got ".concat(JSON.stringify(headers))], this.options, this.__context(), { | ||
return this.__error(new CsvError('CSV_INVALID_COLUMN_MAPPING', ['Invalid Column Mapping:', 'expect an array from column function,', "got ".concat(JSON.stringify(headers))], this.options, this.__infoField(), { | ||
headers: headers | ||
@@ -1111,3 +1108,2 @@ })); | ||
if (enabled === false) { | ||
/* this.options.columns !== true && */ | ||
return this.__resetField(); | ||
@@ -1152,6 +1148,6 @@ } | ||
if (on_record !== undefined) { | ||
var context = this.__context(); | ||
var info = this.__infoRecord(); | ||
try { | ||
record = on_record.call(null, record, context); | ||
record = on_record.call(null, record, info); | ||
} catch (err) { | ||
@@ -1183,7 +1179,7 @@ return err; | ||
var context = this.__context(); | ||
if (this.state.castField !== null) { | ||
try { | ||
return [undefined, this.state.castField.call(null, field, context)]; | ||
var info = this.__infoField(); | ||
return [undefined, this.state.castField.call(null, field, info)]; | ||
} catch (err) { | ||
@@ -1197,3 +1193,5 @@ return [err]; | ||
} else if (this.options.cast_date !== false) { | ||
return [undefined, this.options.cast_date.call(null, field, context)]; | ||
var _info = this.__infoField(); | ||
return [undefined, this.options.cast_date.call(null, field, _info)]; | ||
} | ||
@@ -1373,18 +1371,28 @@ | ||
}, { | ||
key: "__context", | ||
value: function __context() { | ||
key: "__infoDataSet", | ||
value: function __infoDataSet() { | ||
return _objectSpread(_objectSpread({}, this.info), {}, { | ||
columns: this.options.columns | ||
}); | ||
} | ||
}, { | ||
key: "__infoRecord", | ||
value: function __infoRecord() { | ||
var columns = this.options.columns; | ||
var isColumns = Array.isArray(columns); | ||
return { | ||
column: isColumns === true ? columns.length > this.state.record.length ? columns[this.state.record.length].name : null : this.state.record.length, | ||
empty_lines: this.info.empty_lines, | ||
return _objectSpread(_objectSpread({}, this.__infoDataSet()), {}, { | ||
error: this.state.error, | ||
header: columns === true, | ||
index: this.state.record.length, | ||
invalid_field_length: this.info.invalid_field_length, | ||
quoting: this.state.wasQuoting, | ||
lines: this.info.lines, | ||
records: this.info.records | ||
}; | ||
index: this.state.record.length | ||
}); | ||
} | ||
}, { | ||
key: "__infoField", | ||
value: function __infoField() { | ||
var columns = this.options.columns; | ||
var isColumns = Array.isArray(columns); | ||
return _objectSpread(_objectSpread({}, this.__infoRecord()), {}, { | ||
column: isColumns === true ? columns.length > this.state.record.length ? columns[this.state.record.length].name : null : this.state.record.length, | ||
quoting: this.state.wasQuoting | ||
}); | ||
} | ||
}]); | ||
@@ -1430,6 +1438,6 @@ | ||
parser.on('error', function (err) { | ||
callback(err, undefined, parser.info); | ||
callback(err, undefined, parser.__infoDataSet()); | ||
}); | ||
parser.on('end', function () { | ||
callback(undefined, records, parser.info); | ||
callback(undefined, records, parser.__infoDataSet()); | ||
}); | ||
@@ -1436,0 +1444,0 @@ } |
@@ -110,3 +110,3 @@ | ||
'Invalid option columns:', | ||
'expect an object, a function or true,', | ||
'expect an array, a function or true,', | ||
`got ${JSON.stringify(options.columns)}` | ||
@@ -124,2 +124,7 @@ ], options) | ||
], options) | ||
}else if(options.columns === false){ | ||
throw new CsvError('CSV_INVALID_OPTION_COLUMNS_DUPLICATES_TO_ARRAY', [ | ||
'Invalid option columns_duplicates_to_array:', | ||
'the `columns` mode must be activated.' | ||
], options) | ||
} | ||
@@ -439,6 +444,6 @@ // Normalize option `comment` | ||
escapeIsQuote: Buffer.isBuffer(options.escape) && Buffer.isBuffer(options.quote) && Buffer.compare(options.escape, options.quote) === 0, | ||
expectedRecordLength: options.columns === null ? 0 : options.columns.length, | ||
// columns can be `false`, `true`, `Array` | ||
expectedRecordLength: Array.isArray(options.columns) ? options.columns.length : undefined, | ||
field: new ResizeableBuffer(20), | ||
firstLineToHeaders: fnFirstLineToHeaders, | ||
info: Object.assign({}, this.info), | ||
needMoreDataSize: Math.max( | ||
@@ -486,3 +491,3 @@ // Skip if the remaining buffer smaller than comment | ||
__parse(nextBuf, end){ | ||
const {bom, comment, escape, from_line, info, ltrim, max_record_size, quote, raw, relax, rtrim, skip_empty_lines, to, to_line} = this.options | ||
const {bom, comment, escape, from_line, ltrim, max_record_size, quote, raw, relax, rtrim, skip_empty_lines, to, to_line} = this.options | ||
let {record_delimiter} = this.options | ||
@@ -538,5 +543,2 @@ const {bomSkipped, previousBuf, rawBuffer, escapeIsQuote} = this.state | ||
this.info.lines++ | ||
if(info === true && this.state.record.length === 0 && this.state.field.length === 0 && this.state.wasQuoting === false){ | ||
this.state.info = Object.assign({}, this.info) | ||
} | ||
this.state.wasRowDelimiter = false | ||
@@ -610,3 +612,3 @@ } | ||
'(if activated) or comment', | ||
], this.options, this.__context()) | ||
], this.options, this.__infoField()) | ||
) | ||
@@ -628,3 +630,3 @@ if(err !== undefined) return err | ||
`a quote is found inside a field at line ${this.info.lines}`, | ||
], this.options, this.__context(), { | ||
], this.options, this.__infoField(), { | ||
field: this.state.field, | ||
@@ -704,3 +706,3 @@ }) | ||
`at line ${this.info.lines}`, | ||
], this.options, this.__context()) | ||
], this.options, this.__infoField()) | ||
) | ||
@@ -710,3 +712,2 @@ if(err !== undefined) return err | ||
} | ||
const lappend = ltrim === false || this.state.quoting === true || this.state.field.length !== 0 || !this.__isCharTrimable(chr) | ||
@@ -723,3 +724,3 @@ // rtrim in non quoting is handle in __onField | ||
`at line ${this.info.lines}`, | ||
], this.options, this.__context()) | ||
], this.options, this.__infoField()) | ||
) | ||
@@ -736,3 +737,3 @@ if(err !== undefined) return err | ||
`the parsing is finished with an opening quote at line ${this.info.lines}`, | ||
], this.options, this.__context()) | ||
], this.options, this.__infoField()) | ||
) | ||
@@ -770,3 +771,3 @@ if(err !== undefined) return err | ||
if(columns === true){ | ||
if(isRecordEmpty(record)){ | ||
if(skip_lines_with_empty_values === true && isRecordEmpty(record)){ | ||
this.__resetRecord() | ||
@@ -788,3 +789,3 @@ return | ||
`got ${recordLength} on line ${this.info.lines}`, | ||
], this.options, this.__context(), { | ||
], this.options, this.__infoField(), { | ||
record: record, | ||
@@ -799,3 +800,3 @@ }) | ||
`got ${recordLength} on line ${this.info.lines}`, | ||
], this.options, this.__context(), { | ||
], this.options, this.__infoField(), { | ||
record: record, | ||
@@ -814,7 +815,5 @@ }) | ||
} | ||
if(skip_lines_with_empty_values === true){ | ||
if(isRecordEmpty(record)){ | ||
this.__resetRecord() | ||
return | ||
} | ||
if(skip_lines_with_empty_values === true && isRecordEmpty(record)){ | ||
this.__resetRecord() | ||
return | ||
} | ||
@@ -828,2 +827,3 @@ if(this.state.recordHasError === true){ | ||
if(from === 1 || this.info.records >= from){ | ||
// With columns, records are object | ||
if(columns !== false){ | ||
@@ -846,2 +846,3 @@ const obj = {} | ||
const {objname} = this.options | ||
// Without objname (default) | ||
if(objname === undefined){ | ||
@@ -852,3 +853,3 @@ if(raw === true || info === true){ | ||
(raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {}), | ||
(info === true ? {info: this.state.info}: {}) | ||
(info === true ? {info: this.__infoRecord()}: {}) | ||
)) | ||
@@ -864,2 +865,3 @@ if(err){ | ||
} | ||
// With objname (default) | ||
}else{ | ||
@@ -870,3 +872,3 @@ if(raw === true || info === true){ | ||
raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {}, | ||
info === true ? {info: this.state.info}: {} | ||
info === true ? {info: this.__infoRecord()}: {} | ||
)) | ||
@@ -883,2 +885,3 @@ if(err){ | ||
} | ||
// Without columns, records are array | ||
}else{ | ||
@@ -889,3 +892,3 @@ if(raw === true || info === true){ | ||
raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {}, | ||
info === true ? {info: this.state.info}: {} | ||
info === true ? {info: this.__infoRecord()}: {} | ||
)) | ||
@@ -915,3 +918,3 @@ if(err){ | ||
`got ${JSON.stringify(headers)}` | ||
], this.options, this.__context(), { | ||
], this.options, this.__infoField(), { | ||
headers: headers, | ||
@@ -942,3 +945,3 @@ }) | ||
// Short circuit for the from_line options | ||
if(enabled === false){ /* this.options.columns !== true && */ | ||
if(enabled === false){ | ||
return this.__resetField() | ||
@@ -969,5 +972,5 @@ } | ||
if(on_record !== undefined){ | ||
const context = this.__context() | ||
const info = this.__infoRecord() | ||
try{ | ||
record = on_record.call(null, record, context) | ||
record = on_record.call(null, record, info) | ||
}catch(err){ | ||
@@ -990,6 +993,6 @@ return err | ||
} | ||
const context = this.__context() | ||
if(this.state.castField !== null){ | ||
try{ | ||
return [undefined, this.state.castField.call(null, field, context)] | ||
const info = this.__infoField() | ||
return [undefined, this.state.castField.call(null, field, info)] | ||
}catch(err){ | ||
@@ -1002,3 +1005,4 @@ return [err] | ||
}else if(this.options.cast_date !== false){ | ||
return [undefined, this.options.cast_date.call(null, field, context)] | ||
const info = this.__infoField() | ||
return [undefined, this.options.cast_date.call(null, field, info)] | ||
} | ||
@@ -1135,6 +1139,22 @@ return [undefined, field] | ||
} | ||
__context(){ | ||
__infoDataSet(){ | ||
return { | ||
...this.info, | ||
columns: this.options.columns | ||
} | ||
} | ||
__infoRecord(){ | ||
const {columns} = this.options | ||
return { | ||
...this.__infoDataSet(), | ||
error: this.state.error, | ||
header: columns === true, | ||
index: this.state.record.length, | ||
} | ||
} | ||
__infoField(){ | ||
const {columns} = this.options | ||
const isColumns = Array.isArray(columns) | ||
return { | ||
...this.__infoRecord(), | ||
column: isColumns === true ? | ||
@@ -1146,10 +1166,3 @@ ( columns.length > this.state.record.length ? | ||
this.state.record.length, | ||
empty_lines: this.info.empty_lines, | ||
error: this.state.error, | ||
header: columns === true, | ||
index: this.state.record.length, | ||
invalid_field_length: this.info.invalid_field_length, | ||
quoting: this.state.wasQuoting, | ||
lines: this.info.lines, | ||
records: this.info.records | ||
} | ||
@@ -1191,6 +1204,6 @@ } | ||
parser.on('error', function(err){ | ||
callback(err, undefined, parser.info) | ||
callback(err, undefined, parser.__infoDataSet()) | ||
}) | ||
parser.on('end', function(){ | ||
callback(undefined, records, parser.info) | ||
callback(undefined, records, parser.__infoDataSet()) | ||
}) | ||
@@ -1197,0 +1210,0 @@ } |
{ | ||
"version": "4.15.4", | ||
"version": "4.16.0", | ||
"name": "csv-parse", | ||
@@ -4,0 +4,0 @@ "description": "CSV parsing implementing the Node.js `stream.Transform` API", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
680948
0.36%16656
0.21%