csv-parse
Advanced tools
Comparing version 3.2.0 to 4.0.0
# Changelog | ||
## Version 4.0.0 | ||
This is a complete rewrite based with a Buffer implementation. There are no major breaking changes but it introduces multiple minor breaking changes: | ||
* option `rowDelimiter` is now `record_delimiter` | ||
* option `max_limit_on_data_read` is now `max_record_size` | ||
* drop the record event | ||
* normalise error message as `{error type}: {error description}` | ||
* state values are now isolated into the `info` object | ||
* `count` is now `info.records` | ||
* `lines` is now `info.lines` | ||
* `empty_line_count` is now `info.empty_lines` | ||
* `skipped_line_count` is now `info.invalid_field_length` | ||
* `context.count` is cast function is now `context.records` | ||
* drop support for deprecated options `auto_parse` and `auto_parse_date` | ||
* drop emission of the `record` event | ||
* in `raw` option, the `row` property is renamed `record` | ||
* default value of `max_record_size` is now `0` (unlimited) | ||
* remove the `record` event, use the `readable` event and `this.read()` instead | ||
New features: | ||
* new options `info`, `from_line` and `to_line` | ||
* trim: respect `ltrim` and `rtrim` when defined | ||
* delimiter: may be a Buffer | ||
* delimiter: handle multiple bytes/characters | ||
* callback: export info object as third argument | ||
* cast: catch error in user functions | ||
* ts: mark info as readonly with required properties | ||
* comment_lines: count the number of commented lines with no records | ||
* callback: pass undefined instead of null | ||
API management | ||
* Multiple tests have been rewritten with easier data sample | ||
* Source code is now written in ES6 instead of CoffeeScript | ||
* package: switch to MIT license | ||
## Version 3.2.0 | ||
@@ -5,0 +43,0 @@ |
1405
lib/es5/index.js
@@ -21,821 +21,978 @@ "use strict"; | ||
// Generated by CoffeeScript 2.3.2 | ||
// # CSV Parser | ||
// This module provides a CSV parser tested and used against large datasets. Over the year, it has been enhance and is now full of useful options. | ||
// Please look at the [project website](https://csv.js.org/parse/) for additional information. | ||
var Parser, StringDecoder, default_options, isObjLiteral, stream, util; | ||
stream = require('stream'); | ||
util = require('util'); | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
var _require = require('string_decoder'); | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
StringDecoder = _require.StringDecoder; | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
// ## Usage | ||
// Callback approach, for ease of use: | ||
// `parse(data, [options], callback)` | ||
// [Node.js Stream API](http://nodejs.org/api/stream.html), for maximum of power: | ||
// `parse([options], [callback])` | ||
module.exports = function () { | ||
var callback, called, chunks, data, err, options, parser; | ||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } | ||
if (arguments.length === 3) { | ||
data = arguments[0]; | ||
options = arguments[1]; | ||
callback = arguments[2]; | ||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } | ||
if (typeof callback !== 'function') { | ||
throw Error("Invalid callback argument: ".concat(JSON.stringify(callback))); | ||
} | ||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } | ||
if (!(typeof data === 'string' || Buffer.isBuffer(arguments[0]))) { | ||
return callback(Error("Invalid data argument: ".concat(JSON.stringify(data)))); | ||
} | ||
} else if (arguments.length === 2) { | ||
// 1st arg is data:string or options:object | ||
if (typeof arguments[0] === 'string' || Buffer.isBuffer(arguments[0])) { | ||
data = arguments[0]; | ||
} else if (isObjLiteral(arguments[0])) { | ||
options = arguments[0]; | ||
} else { | ||
err = "Invalid first argument: ".concat(JSON.stringify(arguments[0])); | ||
} // 2nd arg is options:object or callback:function | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } | ||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } | ||
if (typeof arguments[1] === 'function') { | ||
callback = arguments[1]; | ||
} else if (isObjLiteral(arguments[1])) { | ||
if (options) { | ||
err = 'Invalid arguments: got options twice as first and second arguments'; | ||
} else { | ||
options = arguments[1]; | ||
} | ||
} else { | ||
err = "Invalid first argument: ".concat(JSON.stringify(arguments[1])); | ||
} | ||
var _require = require('stream'), | ||
Transform = _require.Transform; | ||
if (err) { | ||
if (!callback) { | ||
throw Error(err); | ||
} else { | ||
return callback(Error(err)); | ||
} | ||
} | ||
} else if (arguments.length === 1) { | ||
if (typeof arguments[0] === 'function') { | ||
callback = arguments[0]; | ||
} else { | ||
options = arguments[0]; | ||
} | ||
} | ||
var ResizeableBuffer = require('./ResizeableBuffer'); | ||
if (options == null) { | ||
options = {}; | ||
} | ||
parser = new Parser(options); | ||
if (data != null) { | ||
process.nextTick(function () { | ||
parser.write(data); | ||
return parser.end(); | ||
}); | ||
} | ||
if (callback) { | ||
called = false; | ||
chunks = options.objname ? {} : []; | ||
parser.on('readable', function () { | ||
var chunk, results; | ||
results = []; | ||
while (chunk = parser.read()) { | ||
if (options.objname) { | ||
results.push(chunks[chunk[0]] = chunk[1]); | ||
} else { | ||
results.push(chunks.push(chunk)); | ||
} | ||
} | ||
return results; | ||
}); | ||
parser.on('error', function (err) { | ||
called = true; | ||
return callback(err); | ||
}); | ||
parser.on('end', function () { | ||
if (!called) { | ||
return callback(null, chunks); | ||
} | ||
}); | ||
} | ||
return parser; | ||
}; // ## `Parser([options])` | ||
// Options are documented [here](http://csv.js.org/parse/options/). | ||
default_options = { | ||
rowDelimiter: null, | ||
delimiter: ',', | ||
quote: '"', | ||
escape: '"', | ||
var default_options = { | ||
// cast: false, | ||
// cast_date: false, | ||
columns: null, | ||
comment: '', | ||
objname: false, | ||
trim: false, | ||
ltrim: false, | ||
rtrim: false, | ||
cast: false, | ||
cast_date: false, | ||
delimiter: Buffer.from(','), | ||
escape: Buffer.from('"'), | ||
from: 1, | ||
from_line: 1, | ||
objname: undefined, | ||
// ltrim: false, | ||
// quote: Buffer.from('"'), | ||
// TODO create a max_comment_size | ||
max_record_size: 0, | ||
relax: false, | ||
relax_column_count: false, | ||
// rtrim: false, | ||
skip_empty_lines: false, | ||
max_limit_on_data_read: 128000, | ||
skip_lines_with_empty_values: false, | ||
skip_lines_with_error: false | ||
skip_lines_with_error: false, | ||
to_line: -1, | ||
to: -1, | ||
trim: false | ||
}; | ||
var cr = 13; | ||
var nl = 10; | ||
var space = 32; | ||
Parser = function Parser() { | ||
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var k, options, v; | ||
opts.objectMode = true; | ||
stream.Transform.call(this, opts); | ||
options = {}; // Clone options | ||
var Parser = | ||
/*#__PURE__*/ | ||
function (_Transform) { | ||
_inherits(Parser, _Transform); | ||
for (k in opts) { | ||
v = opts[k]; | ||
options[k] = v; | ||
} // Default values | ||
function Parser() { | ||
var _this; | ||
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
for (k in default_options) { | ||
v = default_options[k]; | ||
_classCallCheck(this, Parser); | ||
if (options[k] === void 0) { | ||
options[k] = default_options[k]; | ||
var options = {}; | ||
for (var i in opts) { | ||
options[i] = opts[i]; | ||
} | ||
} | ||
this.options = options; | ||
options.readableObjectMode = true; | ||
_this = _possibleConstructorReturn(this, _getPrototypeOf(Parser).call(this, options)); // Import default options | ||
if (typeof options.rowDelimiter === 'string') { | ||
options.rowDelimiter = [options.rowDelimiter]; | ||
} | ||
for (var k in default_options) { | ||
if (options[k] === undefined) { | ||
options[k] = default_options[k]; | ||
} | ||
} // Normalize option `cast` | ||
if (options.quote !== void 0 && !options.quote) { | ||
options.quote = ''; | ||
} | ||
if (options.auto_parse != null) { | ||
options.cast = options.auto_parse; | ||
} | ||
var fnCastField = null; | ||
if (options.auto_parse_date != null) { | ||
options.cast_date = options.auto_parse_date; | ||
} | ||
if (options.cast === undefined || options.cast === null || options.cast === false || options.cast === '') { | ||
options.cast = undefined; | ||
} else if (typeof options.cast === 'function') { | ||
fnCastField = options.cast; | ||
options.cast = true; | ||
} else if (options.cast !== true) { | ||
throw new Error('Invalid Option: cast must be true or a function'); | ||
} // Normize option `cast_date` | ||
if (options.cast_date === true) { | ||
options.cast_date = function (value) { | ||
var m; | ||
m = Date.parse(value); | ||
if (!isNaN(m)) { | ||
value = new Date(m); | ||
if (options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === '') { | ||
options.cast_date = false; | ||
} else if (options.cast_date === true) { | ||
options.cast_date = function (value) { | ||
var date = Date.parse(value); | ||
return !isNaN(date) ? new Date(date) : value; | ||
}; | ||
} else if (typeof options.cast_date !== 'function') { | ||
throw new Error('Invalid Option: cast_date must be true or a function'); | ||
} // Normalize option `comment` | ||
if (options.comment === undefined || options.comment === null || options.comment === false || options.comment === '') { | ||
options.comment = null; | ||
} else { | ||
if (typeof options.comment === 'string') { | ||
options.comment = Buffer.from(options.comment); | ||
} | ||
return value; | ||
}; | ||
} // Counters | ||
// lines = count + skipped_line_count + empty_line_count | ||
if (!Buffer.isBuffer(options.comment)) { | ||
throw new Error("Invalid Option: comment must be a buffer or a string, got ".concat(JSON.stringify(options.comment))); | ||
} | ||
} // Normalize option `delimiter` | ||
this.lines = 0; // Number of lines encountered in the source dataset | ||
if (typeof options.delimiter === 'string') { | ||
options.delimiter = Buffer.from(options.delimiter); | ||
} // Normalize option `columns` | ||
this.count = 0; // Number of records being processed | ||
this.skipped_line_count = 0; // Number of records skipped due to errors | ||
var fnFirstLineToHeaders = null; | ||
this.empty_line_count = 0; // Number of empty lines | ||
// Constants | ||
if (options.columns === true) { | ||
fnFirstLineToHeaders = firstLineToHeadersDefault; | ||
} else if (typeof options.columns === 'function') { | ||
fnFirstLineToHeaders = options.columns; | ||
options.columns = true; | ||
} else if (Array.isArray(options.columns)) { | ||
normalizeColumnsArray(options.columns); | ||
} else if (options.columns === undefined || options.columns === null || options.columns === false) { | ||
options.columns = false; | ||
} else { | ||
throw new Error("Invalid Option columns: expect an object or true, got ".concat(JSON.stringify(options.columns))); | ||
} // Normalize option `escape` | ||
this.is_int = /^(\-|\+)?([1-9]+[0-9]*)$/; // @is_float = /^(\-|\+)?([0-9]+(\.[0-9]+)([eE][0-9]+)?|Infinity)$/ | ||
// @is_float = /^(\-|\+)?((([0-9])|([1-9]+[0-9]*))(\.[0-9]+)([eE][0-9]+)?|Infinity)$/ | ||
this.is_float = function (value) { | ||
return value - parseFloat(value) + 1 >= 0; // Borrowed from jquery | ||
}; // Internal private state | ||
if (typeof options.escape === 'string') { | ||
options.escape = Buffer.from(options.escape); | ||
} | ||
if (!Buffer.isBuffer(options.escape)) { | ||
throw new Error("Invalid Option: escape must be a buffer or a string, got ".concat(JSON.stringify(options.escape))); | ||
} else if (options.escape.length !== 1) { | ||
throw new Error("Invalid Option Length: escape must be one character, got ".concat(options.escape.length)); | ||
} else { | ||
options.escape = options.escape[0]; | ||
} // Normalize option `info` | ||
this._ = { | ||
decoder: new StringDecoder(), | ||
quoting: false, | ||
commenting: false, | ||
field: null, | ||
nextChar: null, | ||
closingQuote: 0, | ||
line: [], | ||
chunks: [], | ||
rawBuf: '', | ||
buf: '', | ||
rowDelimiterMaxLength: this.options.rowDelimiter ? Math.max.apply(Math, _toConsumableArray(this.options.rowDelimiter.map(function (v) { | ||
return v.length; | ||
}))) : void 0, | ||
quotedRowDelimiterMaxLength: this.options.rowDelimiter ? options.quote.length + Math.max.apply(Math, _toConsumableArray(this.options.rowDelimiter.map(function (v) { | ||
return v.length; | ||
}))) : void 0, | ||
lineHasError: false, | ||
isEnded: false | ||
}; | ||
return this; | ||
}; // ## Internal API | ||
// The Parser implement a [`stream.Transform` class](https://nodejs.org/api/stream.html#stream_class_stream_transform). | ||
// ### Events | ||
// The library extends Node [EventEmitter][event] class and emit all the events of the Writable and Readable [Stream API](http://nodejs.org/api/stream.html). | ||
if (options.info === undefined || options.info === null || options.info === false) { | ||
options.info = false; | ||
} else if (options.info !== true) { | ||
throw new Error("Invalid Option: info must be true, got ".concat(JSON.stringify(options.info))); | ||
} // Normalize option `quote` | ||
util.inherits(Parser, stream.Transform); // For extra flexibility, you can get access to the original Parser class: `require('csv-parse').Parser`. | ||
module.exports.Parser = Parser; // ### `_transform(chunk, encoding, callback)` | ||
// * `chunk` Buffer | String | ||
// The chunk to be transformed. Will always be a buffer unless the decodeStrings option was set to false. | ||
// * `encoding` String | ||
// If the chunk is a string, then this is the encoding type. (Ignore if decodeStrings chunk is a buffer.) | ||
// * `callback` Function | ||
// Call this function (optionally with an error argument) when you are done processing the supplied chunk. | ||
// Implementation of the [`stream.Transform` API](https://nodejs.org/api/stream.html#stream_class_stream_transform) | ||
if (options.quote === null || options.quote === false || options.quote === '') { | ||
options.quote = null; | ||
} else { | ||
if (options.quote === undefined || options.quote === true) { | ||
options.quote = Buffer.from('"'); | ||
} else if (typeof options.quote === 'string') { | ||
options.quote = Buffer.from(options.quote); | ||
} | ||
Parser.prototype._transform = function (chunk, encoding, callback) { | ||
var _this = this; | ||
if (!Buffer.isBuffer(options.quote)) { | ||
throw new Error("Invalid Option: quote must be a buffer or a string, got ".concat(JSON.stringify(options.quote))); | ||
} else if (options.quote.length !== 1) { | ||
throw new Error("Invalid Option Length: quote must be one character, got ".concat(options.quote.length)); | ||
} else { | ||
options.quote = options.quote[0]; | ||
} | ||
} // Normalize options `raw` | ||
return setImmediate(function () { | ||
var err; | ||
if (chunk instanceof Buffer) { | ||
chunk = _this._.decoder.write(chunk); | ||
} | ||
if (options.raw === undefined || options.raw === null || options.raw === false) { | ||
options.raw = false; | ||
} else if (options.raw !== true) { | ||
throw new Error("Invalid Option: raw must be true, got ".concat(JSON.stringify(options.raw))); | ||
} // Normalize option `record_delimiter` | ||
err = _this.__write(chunk, false); | ||
if (err) { | ||
return _this.emit('error', err); | ||
if (!options.record_delimiter) { | ||
options.record_delimiter = []; | ||
} else if (!Array.isArray(options.record_delimiter)) { | ||
options.record_delimiter = [options.record_delimiter]; | ||
} | ||
return callback(); | ||
}); | ||
}; | ||
options.record_delimiter = options.record_delimiter.map(function (rd) { | ||
if (typeof rd === 'string') { | ||
rd = Buffer.from(rd); | ||
} | ||
Parser.prototype._flush = function (callback) { | ||
return callback(this.__flush()); | ||
}; | ||
return rd; | ||
}); // Normalize options `trim`, `ltrim` and `rtrim` | ||
Parser.prototype.__flush = function () { | ||
var err; | ||
err = this.__write(this._.decoder.end(), true); | ||
if (options.trim === true && options.ltrim !== false) { | ||
options.ltrim = true; | ||
} else if (options.ltrim !== true) { | ||
options.ltrim = false; | ||
} | ||
if (err) { | ||
return err; | ||
} | ||
if (options.trim === true && options.rtrim !== false) { | ||
options.rtrim = true; | ||
} else if (options.rtrim !== true) { | ||
options.rtrim = false; | ||
} | ||
if (this._.quoting) { | ||
err = this.error("Quoted field not terminated at line ".concat(this.lines + 1)); | ||
return err; | ||
_this.info = { | ||
comment_lines: 0, | ||
empty_lines: 0, | ||
invalid_field_length: 0, | ||
lines: 1, | ||
records: 0 | ||
}; | ||
_this.options = options; | ||
_this.state = { | ||
castField: fnCastField, | ||
commenting: false, | ||
enabled: options.from_line === 1, | ||
escaping: false, | ||
escapeIsQuote: options.escape === options.quote, | ||
expectedRecordLength: options.columns === null ? 0 : options.columns.length, | ||
field: new ResizeableBuffer(20), | ||
firstLineToHeaders: fnFirstLineToHeaders, | ||
info: Object.assign({}, _this.info), | ||
previousBuf: undefined, | ||
quoting: false, | ||
stop: false, | ||
rawBuffer: new ResizeableBuffer(100), | ||
record: [], | ||
recordHasError: false, | ||
record_length: 0, | ||
recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max.apply(Math, _toConsumableArray(options.record_delimiter.map(function (v) { | ||
return v.length; | ||
}))), | ||
trimChars: [Buffer.from(' ')[0], Buffer.from('\t')[0]], | ||
wasQuoting: false, | ||
wasRowDelimiter: false | ||
}; | ||
return _this; | ||
} | ||
if (this._.line.length > 0) { | ||
return this.__push(this._.line); | ||
} | ||
}; | ||
_createClass(Parser, [{ | ||
key: "_transform", | ||
value: function _transform(buf, encoding, callback) { | ||
if (this.state.stop === true) { | ||
return; | ||
} | ||
Parser.prototype.__push = function (line) { | ||
var call_column_udf, columnName, columns, err, field, i, j, len, lineAsColumns, record; | ||
var err = this.__parse(buf, false); | ||
if (this._.isEnded) { | ||
return; | ||
} | ||
if (err !== undefined) { | ||
this.state.stop = true; | ||
} | ||
if (this.options.skip_lines_with_empty_values && line.join('').trim() === '') { | ||
return; | ||
} | ||
callback(err); | ||
} | ||
}, { | ||
key: "_flush", | ||
value: function _flush(callback) { | ||
if (this.state.stop === true) { | ||
return; | ||
} | ||
record = null; | ||
var err = this.__parse(undefined, true); | ||
if (this.options.columns === true) { | ||
this.options.columns = line; | ||
return; | ||
} else if (typeof this.options.columns === 'function') { | ||
call_column_udf = function call_column_udf(fn, line) { | ||
var columns, err; | ||
callback(err); | ||
} | ||
}, { | ||
key: "__parse", | ||
value: function __parse(nextBuf, end) { | ||
var _this$options = this.options, | ||
comment = _this$options.comment, | ||
escape = _this$options.escape, | ||
from = _this$options.from, | ||
from_line = _this$options.from_line, | ||
info = _this$options.info, | ||
ltrim = _this$options.ltrim, | ||
max_record_size = _this$options.max_record_size, | ||
quote = _this$options.quote, | ||
raw = _this$options.raw, | ||
relax = _this$options.relax, | ||
rtrim = _this$options.rtrim, | ||
skip_empty_lines = _this$options.skip_empty_lines, | ||
to = _this$options.to, | ||
to_line = _this$options.to_line; | ||
var record_delimiter = this.options.record_delimiter; | ||
var _this$state = this.state, | ||
previousBuf = _this$state.previousBuf, | ||
rawBuffer = _this$state.rawBuffer, | ||
escapeIsQuote = _this$state.escapeIsQuote, | ||
trimChars = _this$state.trimChars; | ||
var buf; | ||
try { | ||
columns = fn.call(null, line); | ||
return [null, columns]; | ||
} catch (error) { | ||
err = error; | ||
return [err]; | ||
if (previousBuf === undefined && nextBuf !== undefined) { | ||
buf = nextBuf; | ||
} else if (previousBuf !== undefined && nextBuf === undefined) { | ||
buf = previousBuf; | ||
} else { | ||
buf = Buffer.concat([previousBuf, nextBuf]); | ||
} | ||
}; | ||
var _call_column_udf = call_column_udf(this.options.columns, line); | ||
var bufLen = buf.length; | ||
var pos; // let escaping = this. | ||
var _call_column_udf2 = _slicedToArray(_call_column_udf, 2); | ||
for (pos = 0; pos < bufLen; pos++) { | ||
// Ensure we get enough space to look ahead | ||
// There should be a way to move this out of the loop | ||
if (this.__needMoreData(pos, bufLen, end)) { | ||
break; | ||
} | ||
err = _call_column_udf2[0]; | ||
columns = _call_column_udf2[1]; | ||
if (this.state.wasRowDelimiter === true) { | ||
this.info.lines++; | ||
if (err) { | ||
return err; | ||
} | ||
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.options.columns = columns; | ||
return; | ||
} | ||
this.state.wasRowDelimiter = false; | ||
} | ||
if (!this._.line_length && line.length > 0) { | ||
this._.line_length = this.options.columns ? this.options.columns.length : line.length; | ||
} // Dont check column count on empty lines | ||
if (to_line !== -1 && this.info.lines > to_line) { | ||
this.state.stop = true; | ||
this.push(null); | ||
return; | ||
} // Auto discovery of record_delimiter, unix, mac and windows supported | ||
if (line.length === 1 && line[0] === '') { | ||
this.empty_line_count++; | ||
} else if (line.length !== this._.line_length) { | ||
// Dont check column count with relax_column_count | ||
if (this.options.relax_column_count) { | ||
this.count++; | ||
this.skipped_line_count++; | ||
} else if (this.options.columns != null) { | ||
// Suggest: Inconsistent header and column numbers: header is 1 and number of columns is 1 on line 1 | ||
err = this.error("Number of columns on line ".concat(this.lines, " does not match header")); | ||
return err; | ||
} else { | ||
err = this.error("Number of columns is inconsistent on line ".concat(this.lines)); | ||
return err; | ||
} | ||
} else { | ||
this.count++; | ||
} | ||
if (this.state.quoting === false && record_delimiter.length === 0) { | ||
var recordDelimiterCount = this.__autoDiscoverRowDelimiter(buf, pos); | ||
if (this.options.columns != null) { | ||
lineAsColumns = {}; | ||
if (recordDelimiterCount) { | ||
record_delimiter = this.options.record_delimiter; | ||
} | ||
} | ||
for (i = j = 0, len = line.length; j < len; i = ++j) { | ||
field = line[i]; | ||
columnName = this.options.columns[i]; | ||
var chr = buf[pos]; | ||
if (columnName === void 0 || columnName === null || columnName === false) { | ||
continue; | ||
} | ||
if (raw === true) { | ||
rawBuffer.append(chr); | ||
} | ||
if (typeof columnName !== 'string') { | ||
throw Error("Invalid column name ".concat(JSON.stringify(columnName))); | ||
} | ||
if ((chr === cr || chr === nl) && this.state.wasRowDelimiter === false) { | ||
this.state.wasRowDelimiter = true; | ||
} // Previous char was a valid escape char | ||
// treat the current char as a regular char | ||
lineAsColumns[columnName] = field; | ||
} | ||
if (this.options.objname) { | ||
record = [lineAsColumns[this.options.objname], lineAsColumns]; | ||
} else { | ||
record = lineAsColumns; | ||
} | ||
} else { | ||
record = line; | ||
} | ||
if (this.state.escaping === true) { | ||
this.state.escaping = false; | ||
} else { | ||
// Escape is only active inside quoted fields | ||
if (this.state.quoting === true && chr === escape && pos + 1 < bufLen) { | ||
// We are quoting, the char is an escape chr and there is a chr to escape | ||
if (escapeIsQuote) { | ||
if (buf[pos + 1] === quote) { | ||
this.state.escaping = true; | ||
continue; | ||
} | ||
} else { | ||
this.state.escaping = true; | ||
continue; | ||
} | ||
} // Not currently escaping and chr is a quote | ||
// TODO: need to compare bytes instead of single char | ||
if (this.count < this.options.from) { | ||
return; | ||
} | ||
if (this.options.raw) { | ||
this.push({ | ||
raw: this._.rawBuf, | ||
row: record | ||
}); | ||
this._.rawBuf = ''; | ||
} else { | ||
this.push(record); | ||
} | ||
if (this.state.commenting === false && chr === quote) { | ||
if (this.state.quoting === true) { | ||
var nextChr = buf[pos + 1]; | ||
if (this.listenerCount('record')) { | ||
this.emit('record', record); | ||
} // When to is reached set ignore any future calls | ||
var isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr); // const isNextChrComment = nextChr === comment | ||
if (this.count >= this.options.to) { | ||
this._.isEnded = true; | ||
return this.push(null); | ||
} | ||
var isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos + 1, nextChr); | ||
return null; | ||
}; | ||
var isNextChrDelimiter = this.__isDelimiter(nextChr, buf, pos + 1); | ||
Parser.prototype.__write = function (chars, end) { | ||
var _this2 = this; | ||
var isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos + 1) : this.__isRecordDelimiter(nextChr, buf, pos + 1); // Escape a quote | ||
// Treat next char as a regular character | ||
// TODO: need to compare bytes instead of single char | ||
var areNextCharsDelimiter, areNextCharsRowDelimiters, cast, char, err, escapeIsQuote, i, isDelimiter, isEscape, isNextCharAComment, isNextCharTrimable, isQuote, isRowDelimiter, isRowDelimiterLength, is_float, is_int, l, ltrim, nextCharPos, numOfCharLeft, ref, ref1, ref2, ref3, ref4, ref5, ref6, remainingBuffer, rowDelimiter, rtrim, wasCommenting; | ||
if (chr === escape && nextChr === quote) { | ||
pos++; | ||
} else if (!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable) { | ||
this.state.quoting = false; | ||
this.state.wasQuoting = true; | ||
continue; | ||
} else if (relax === false) { | ||
var err = this.__error("Invalid Closing Quote: got \"".concat(String.fromCharCode(nextChr), "\" at line ").concat(this.info.lines, " instead of delimiter, row delimiter, trimable character (if activated) or comment")); | ||
is_int = function is_int(value) { | ||
if (typeof _this2.is_int === 'function') { | ||
return _this2.is_int(value); | ||
} else { | ||
return _this2.is_int.test(value); | ||
} | ||
}; | ||
if (err !== undefined) return err; | ||
} else { | ||
this.state.quoting = false; | ||
this.state.wasQuoting = true; // continue | ||
is_float = function is_float(value) { | ||
if (typeof _this2.is_float === 'function') { | ||
return _this2.is_float(value); | ||
} else { | ||
return _this2.is_float.test(value); | ||
} | ||
}; | ||
this.state.field.prepend(quote); | ||
} | ||
} else { | ||
if (this.state.field.length !== 0) { | ||
// In relax mode, treat opening quote preceded by chrs as regular | ||
if (relax === false) { | ||
var _err = this.__error("Invalid opening quote at line ".concat(this.info.lines)); | ||
cast = function cast(value) { | ||
var context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
if (_err !== undefined) return _err; | ||
} | ||
} else { | ||
this.state.quoting = true; | ||
continue; | ||
} | ||
} | ||
} | ||
if (!_this2.options.cast) { | ||
return value; | ||
} | ||
if (this.state.quoting === false) { | ||
var recordDelimiterLength = this.__isRecordDelimiter(chr, buf, pos); | ||
if (context.quoting == null) { | ||
context.quoting = !!_this2._.closingQuote; | ||
} | ||
if (recordDelimiterLength !== 0) { | ||
// Do not emit comments which take a full line | ||
var skipCommentLine = this.state.commenting && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0; | ||
if (context.lines == null) { | ||
context.lines = _this2.lines; | ||
} | ||
if (skipCommentLine) { | ||
this.info.comment_lines++; // Skip full comment line | ||
} else { | ||
// Skip if line is empty and skip_empty_lines activated | ||
if (skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0) { | ||
this.info.empty_lines++; | ||
continue; | ||
} // Activate records emition if above from_line | ||
if (context.count == null) { | ||
context.count = _this2.count; | ||
} | ||
if (context.index == null) { | ||
context.index = _this2._.line.length; | ||
} // context.header ?= if @options.column and @lines is 1 and @count is 0 then true else false | ||
if (this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1 : 0) >= from_line) { | ||
this.state.enabled = true; | ||
this.__resetField(); | ||
if (context.header == null) { | ||
context.header = _this2.options.columns === true; | ||
} | ||
this.__resetRow(); | ||
if (context.column == null) { | ||
context.column = Array.isArray(_this2.options.columns) ? _this2.options.columns[context.index] : context.index; | ||
} | ||
continue; | ||
} else { | ||
var errField = this.__onField(); | ||
if (typeof _this2.options.cast === 'function') { | ||
return _this2.options.cast(value, context); | ||
} | ||
if (errField !== undefined) return errField; | ||
if (is_int(value)) { | ||
value = parseInt(value); | ||
} else if (is_float(value)) { | ||
value = parseFloat(value); | ||
} else if (_this2.options.cast_date) { | ||
value = _this2.options.cast_date(value, context); | ||
} | ||
var errRecord = this.__onRow(); | ||
return value; | ||
}; | ||
if (errRecord !== undefined) return errRecord; | ||
} | ||
ltrim = this.options.trim || this.options.ltrim; | ||
rtrim = this.options.trim || this.options.rtrim; | ||
escapeIsQuote = this.options.escape === this.options.quote; | ||
chars = this._.buf + chars; | ||
l = chars.length; | ||
i = 0; | ||
if (to !== -1 && this.info.records >= to) { | ||
this.state.stop = true; | ||
this.push(null); | ||
return; | ||
} | ||
} | ||
if (this.lines === 0 && 0xFEFF === chars.charCodeAt(0)) { | ||
// Strip BOM header | ||
i++; | ||
} | ||
this.state.commenting = false; | ||
pos += recordDelimiterLength - 1; | ||
continue; | ||
} | ||
while (i < l) { | ||
// Ensure we get enough space to look ahead | ||
if (!end) { | ||
numOfCharLeft = l - i; | ||
remainingBuffer = chars.substr(i, numOfCharLeft); // Skip if row delimiter larger than auto discovered values | ||
// Skip if the remaining buffer smaller than comment | ||
// Skip if the remaining buffer smaller than row delimiter | ||
// Skip if the remaining buffer can be row delimiter following the closing quote | ||
// Skip if the remaining buffer can be delimiter | ||
// Skip if the remaining buffer can be escape sequence | ||
if (this.state.commenting) { | ||
continue; | ||
} | ||
if (!this.options.rowDelimiter && i + 3 > l || !this._.commenting && numOfCharLeft < this.options.comment.length || this.options.rowDelimiter && numOfCharLeft < this._.rowDelimiterMaxLength || this._.quoting && this.options.rowDelimiter && numOfCharLeft < this._.quotedRowDelimiterMaxLength || numOfCharLeft <= this.options.delimiter.length && this.options.delimiter.substr(0, numOfCharLeft) === remainingBuffer || numOfCharLeft <= this.options.escape.length && this.options.escape.substr(0, numOfCharLeft) === remainingBuffer) { | ||
break; | ||
} | ||
} | ||
var commentCount = comment === null ? 0 : this.__compareBytes(comment, buf, pos, chr); | ||
char = this._.nextChar ? this._.nextChar : chars.charAt(i); | ||
this._.nextChar = l > i + 1 ? chars.charAt(i + 1) : null; | ||
if (commentCount !== 0) { | ||
this.state.commenting = true; | ||
continue; | ||
} | ||
if (this.options.raw) { | ||
this._.rawBuf += char; | ||
} // Auto discovery of rowDelimiter, unix, mac and windows supported | ||
var delimiterLength = this.__isDelimiter(chr, buf, pos); | ||
if (delimiterLength !== 0) { | ||
var _errField = this.__onField(); | ||
if (this.options.rowDelimiter == null) { | ||
nextCharPos = i; | ||
rowDelimiter = null; // First empty line | ||
if (_errField !== undefined) return _errField; | ||
pos += delimiterLength - 1; | ||
continue; | ||
} | ||
} | ||
} | ||
if (!this._.quoting && (char === '\n' || char === '\r')) { | ||
rowDelimiter = char; | ||
nextCharPos += 1; | ||
} else if (this._.quoting && char === this.options.quote && ((ref = this._.nextChar) === '\n' || ref === '\r')) { | ||
rowDelimiter = this._.nextChar; | ||
nextCharPos += 2; | ||
} | ||
if (this.state.commenting === false) { | ||
if (max_record_size !== 0 && this.state.record_length + this.state.field.length > max_record_size) { | ||
var _err2 = this.__error("Max Record Size: record exceed the maximum number of tolerated bytes of ".concat(max_record_size, " on line ").concat(this.info.lines)); | ||
if (rowDelimiter) { | ||
if (rowDelimiter === '\r' && chars.charAt(nextCharPos) === '\n') { | ||
rowDelimiter += '\n'; | ||
if (_err2 !== undefined) return _err2; | ||
} | ||
} | ||
this.options.rowDelimiter = [rowDelimiter]; | ||
this._.rowDelimiterMaxLength = rowDelimiter.length; | ||
var lappend = ltrim === false || this.state.quoting === true || this.state.field.length !== 0 || !this.__isCharTrimable(chr); // rtrim in non quoting is handle in __onField | ||
var rappend = rtrim === false || this.state.wasQuoting === false; | ||
if (lappend === true && rappend === true) { | ||
this.state.field.append(chr); | ||
} else if (rtrim === true && !this.__isCharTrimable(chr)) { | ||
var _err3 = this.__error("Invalid Closing Quote: found non trimable byte after quote at line ".concat(this.info.lines)); | ||
if (_err3 !== undefined) return _err3; | ||
} | ||
} | ||
} // Parse that damn char | ||
// Note, shouldn't we have sth like chars.substr(i, @options.escape.length) | ||
if (end === true) { | ||
if (this.state.quoting === true) { | ||
var _err4 = this.__error("Invalid Closing Quote: quote is not closed at line ".concat(this.info.lines)); | ||
if (!this._.commenting && char === this.options.escape) { | ||
// Make sure the escape is really here for escaping: | ||
// If escape is same as quote, and escape is first char of a field | ||
// and it's not quoted, then it is a quote | ||
// Next char should be an escape or a quote | ||
isEscape = this._.nextChar === this.options.escape; | ||
isQuote = this._.nextChar === this.options.quote; | ||
if (_err4 !== undefined) return _err4; | ||
} else { | ||
// Skip last line if it has no characters | ||
if (this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0) { | ||
var _errField2 = this.__onField(); | ||
if (!(escapeIsQuote && !this._.field && !this._.quoting) && (isEscape || isQuote)) { | ||
i++; | ||
char = this._.nextChar; | ||
this._.nextChar = chars.charAt(i + 1); | ||
if (_errField2 !== undefined) return _errField2; | ||
if (this._.field == null) { | ||
this._.field = ''; | ||
} | ||
var _errRecord = this.__onRow(); | ||
this._.field += char; // Since we're skipping the next one, better add it now if in raw mode. | ||
if (this.options.raw) { | ||
this._.rawBuf += char; | ||
if (_errRecord !== undefined) return _errRecord; | ||
} else if (this.state.wasRowDelimiter === true) { | ||
this.info.empty_lines++; | ||
} else if (this.state.commenting === true) { | ||
this.info.comment_lines++; | ||
} | ||
} | ||
} else { | ||
this.state.previousBuf = buf.slice(pos); | ||
} | ||
i++; | ||
continue; | ||
if (this.state.wasRowDelimiter === true) { | ||
this.info.lines++; | ||
this.state.wasRowDelimiter = false; | ||
} | ||
} // Char match quote | ||
} | ||
}, { | ||
key: "__isCharTrimable", | ||
value: function __isCharTrimable(chr) { | ||
return chr === space || chr === cr || chr === nl; | ||
} | ||
}, { | ||
key: "__onRow", | ||
value: function __onRow() { | ||
var _this$options2 = this.options, | ||
columns = _this$options2.columns, | ||
info = _this$options2.info, | ||
from = _this$options2.from, | ||
relax_column_count = _this$options2.relax_column_count, | ||
raw = _this$options2.raw, | ||
skip_lines_with_empty_values = _this$options2.skip_lines_with_empty_values; | ||
var _this$state2 = this.state, | ||
enabled = _this$state2.enabled, | ||
record = _this$state2.record; // Validate column length | ||
if (!this._.commenting && char === this.options.quote) { | ||
if (this._.acceptOnlyEmptyChars && char !== ' ' && char !== '\t') { | ||
return this.error('Only trimable characters are accepted after quotes'); | ||
if (columns === true && this.state.firstLineToHeaders) { | ||
return this.__firstLineToColumns(record); | ||
} | ||
if (this._.quoting) { | ||
// Make sure a closing quote is followed by a delimiter | ||
// If we have a next character and | ||
// it isnt a rowDelimiter and | ||
// it isnt an column delimiter and | ||
// it isnt the begining of a comment | ||
// Otherwise, if this is not "relax" mode, throw an error | ||
isNextCharTrimable = rtrim && ((ref1 = this._.nextChar) === ' ' || ref1 === '\t'); | ||
areNextCharsRowDelimiters = this.options.rowDelimiter && this.options.rowDelimiter.some(function (rd) { | ||
return chars.substr(i + 1, rd.length) === rd; | ||
}); | ||
areNextCharsDelimiter = chars.substr(i + 1, this.options.delimiter.length) === this.options.delimiter; | ||
isNextCharAComment = this._.nextChar === this.options.comment; | ||
var recordLength = record.length; | ||
if (this._.nextChar != null && !isNextCharTrimable && !areNextCharsRowDelimiters && !areNextCharsDelimiter && !isNextCharAComment) { | ||
if (this.options.relax) { | ||
this._.quoting = false; | ||
if (columns === false && this.info.records === 0) { | ||
this.state.expectedRecordLength = recordLength; | ||
} else if (enabled === true) { | ||
if (recordLength !== this.state.expectedRecordLength) { | ||
if (relax_column_count === true) { | ||
this.info.invalid_field_length++; | ||
} else { | ||
if (columns === false) { | ||
var err = this.__error("Invalid Record Length: expect ".concat(this.state.expectedRecordLength, ", got ").concat(recordLength, " on line ").concat(this.info.lines)); | ||
if (this._.field) { | ||
this._.field = "".concat(this.options.quote).concat(this._.field); | ||
if (err !== undefined) return err; | ||
} else { | ||
var _err5 = this.__error("Invalid Record Length: header length is ".concat(columns.length, ", got ").concat(recordLength, " on line ").concat(this.info.lines)); | ||
if (_err5 !== undefined) return _err5; | ||
} | ||
} else { | ||
if (err = this.error("Invalid closing quote at line ".concat(this.lines + 1, "; found ").concat(JSON.stringify(this._.nextChar), " instead of delimiter ").concat(JSON.stringify(this.options.delimiter)))) { | ||
return err; | ||
} | ||
} | ||
} else if (this._.nextChar != null && isNextCharTrimable) { | ||
i++; | ||
this._.quoting = false; | ||
this._.closingQuote = 1; | ||
this._.acceptOnlyEmptyChars = true; | ||
continue; | ||
} else { | ||
i++; | ||
this._.quoting = false; | ||
this._.closingQuote = 1; | ||
} | ||
} | ||
if (end && i === l) { | ||
this._.line.push(cast(this._.field || '')); | ||
if (enabled === false) { | ||
return this.__resetRow(); | ||
} | ||
this._.field = null; | ||
} | ||
if (skip_lines_with_empty_values === true) { | ||
if (record.map(function (field) { | ||
return field.trim(); | ||
}).join('') === '') { | ||
this.__resetRow(); | ||
continue; | ||
return; | ||
} | ||
} else if (!this._.field) { | ||
this._.quoting = true; | ||
i++; | ||
continue; | ||
} else if (this._.field != null && !this.options.relax) { | ||
if (err = this.error("Invalid opening quote at line ".concat(this.lines + 1))) { | ||
return err; | ||
} | ||
} | ||
} // Otherwise, treat quote as a regular character | ||
if (this.state.recordHasError === true) { | ||
this.__resetRow(); | ||
isRowDelimiter = this.options.rowDelimiter && this.options.rowDelimiter.some(function (rd) { | ||
return chars.substr(i, rd.length) === rd; | ||
}); | ||
this.state.recordHasError = false; | ||
return; | ||
} | ||
if (isRowDelimiter || end && i === l - 1) { | ||
this.lines++; | ||
} // Set the commenting flag | ||
this.info.records++; | ||
if (from === 1 || this.info.records >= from) { | ||
if (columns !== false) { | ||
var obj = {}; // Transform record array to an object | ||
wasCommenting = false; | ||
for (var i in record) { | ||
if (columns[i].disabled) continue; | ||
obj[columns[i].name] = record[i]; | ||
} | ||
if (!this._.commenting && !this._.quoting && this.options.comment && chars.substr(i, this.options.comment.length) === this.options.comment) { | ||
this._.commenting = true; | ||
} else if (this._.commenting && isRowDelimiter) { | ||
wasCommenting = true; | ||
this._.commenting = false; | ||
} | ||
var objname = this.options.objname; | ||
isDelimiter = chars.substr(i, this.options.delimiter.length) === this.options.delimiter; | ||
if (this._.acceptOnlyEmptyChars) { | ||
if (isDelimiter || isRowDelimiter) { | ||
this._.acceptOnlyEmptyChars = false; | ||
} else { | ||
if (char === ' ' || char === '\t') { | ||
i++; | ||
continue; | ||
if (objname === undefined) { | ||
if (raw === true || info === true) { | ||
this.push(Object.assign({ | ||
record: obj | ||
}, raw === true ? { | ||
raw: this.state.rawBuffer.toString() | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
} : {})); | ||
} else { | ||
this.push(obj); | ||
} | ||
} else { | ||
if (raw === true || info === true) { | ||
this.push(Object.assign({ | ||
record: [obj[objname], obj] | ||
}, raw === true ? { | ||
raw: this.state.rawBuffer.toString() | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
} : {})); | ||
} else { | ||
this.push([obj[objname], obj]); | ||
} | ||
} | ||
} else { | ||
return this.error('Only trimable characters are accepted after quotes'); | ||
if (raw === true || info === true) { | ||
this.push(Object.assign({ | ||
record: record | ||
}, raw === true ? { | ||
raw: this.state.rawBuffer.toString() | ||
} : {}, info === true ? { | ||
info: this.state.info | ||
} : {})); | ||
} else { | ||
this.push(record); | ||
} | ||
} | ||
} | ||
this.__resetRow(); | ||
} | ||
}, { | ||
key: "__firstLineToColumns", | ||
value: function __firstLineToColumns(record) { | ||
try { | ||
var headers = this.state.firstLineToHeaders.call(null, record); | ||
if (!this._.commenting && !this._.quoting && (isDelimiter || isRowDelimiter)) { | ||
if (isRowDelimiter) { | ||
isRowDelimiterLength = this.options.rowDelimiter.filter(function (rd) { | ||
return chars.substr(i, rd.length) === rd; | ||
})[0].length; | ||
} // Empty lines | ||
if (!Array.isArray(headers)) { | ||
return this.__error("Invalid Header Mapping: expect an array, got ".concat(JSON.stringify(headers))); | ||
} | ||
normalizeColumnsArray(headers); | ||
this.state.expectedRecordLength = headers.length; | ||
this.options.columns = headers; | ||
if (isRowDelimiter && this._.line.length === 0 && this._.field == null) { | ||
if (wasCommenting || this.options.skip_empty_lines) { | ||
i += isRowDelimiterLength; | ||
this._.nextChar = chars.charAt(i); | ||
continue; | ||
} | ||
this.__resetRow(); | ||
return; | ||
} catch (err) { | ||
return err; | ||
} | ||
} | ||
}, { | ||
key: "__resetRow", | ||
value: function __resetRow() { | ||
var info = this.options.info; | ||
if (rtrim) { | ||
if (!this._.closingQuote) { | ||
this._.field = (ref2 = this._.field) != null ? ref2.trimRight() : void 0; | ||
} | ||
if (this.options.raw === true) { | ||
this.state.rawBuffer.reset(); | ||
} | ||
this._.line.push(cast(this._.field || '')); | ||
this.state.record = []; | ||
this.state.record_length = 0; | ||
} | ||
}, { | ||
key: "__onField", | ||
value: function __onField() { | ||
var _this$options3 = this.options, | ||
cast = _this$options3.cast, | ||
rtrim = _this$options3.rtrim; | ||
var _this$state3 = this.state, | ||
enabled = _this$state3.enabled, | ||
wasQuoting = _this$state3.wasQuoting; // Deal with from_to options | ||
this._.closingQuote = 0; | ||
this._.field = null; // End of field | ||
// Ensure that the delimiter doesnt match as well the rowDelimiter | ||
if (this.options.columns !== true && enabled === false) { | ||
return this.__resetField(); | ||
} | ||
if (isDelimiter && !isRowDelimiter) { | ||
i += this.options.delimiter.length; | ||
this._.nextChar = chars.charAt(i); | ||
var field = this.state.field.toString(); | ||
if (end && !this._.nextChar) { | ||
isRowDelimiter = true; | ||
this._.line.push(''); | ||
} | ||
if (rtrim === true && wasQuoting === false) { | ||
field = field.trimRight(); | ||
} | ||
if (isRowDelimiter) { | ||
// End of record | ||
if (!this._.lineHasError) { | ||
err = this.__push(this._.line); | ||
if (cast === true) { | ||
var _this$__cast = this.__cast(field), | ||
_this$__cast2 = _slicedToArray(_this$__cast, 2), | ||
err = _this$__cast2[0], | ||
f = _this$__cast2[1]; | ||
if (err) { | ||
return err; | ||
} | ||
} | ||
if (err !== undefined) return err; | ||
field = f; | ||
} | ||
if (this._.lineHasError) { | ||
this._.lineHasError = false; | ||
} // Some cleanup for the next record | ||
this.state.record.push(field); | ||
this.state.record_length += field.length; | ||
this.__resetField(); | ||
} | ||
}, { | ||
key: "__resetField", | ||
value: function __resetField() { | ||
this.state.field.reset(); | ||
this.state.wasQuoting = false; | ||
} | ||
}, { | ||
key: "__cast", | ||
value: function __cast(field) { | ||
var context = { | ||
column: Array.isArray(this.options.columns) === true ? this.options.columns[this.state.record.length] : this.state.record.length, | ||
empty_lines: this.info.empty_lines, | ||
header: this.options.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 | ||
}; | ||
this._.line = []; | ||
i += isRowDelimiterLength; | ||
this._.nextChar = chars.charAt(i); | ||
continue; | ||
if (this.state.castField !== null) { | ||
try { | ||
return [undefined, this.state.castField.call(null, field, context)]; | ||
} catch (err) { | ||
return [err]; | ||
} | ||
} | ||
} else if (!this._.commenting && !this._.quoting && (char === ' ' || char === '\t')) { | ||
if (this._.field == null) { | ||
// Left trim unless we are quoting or field already filled | ||
this._.field = ''; | ||
} | ||
if (!(ltrim && !this._.field)) { | ||
this._.field += char; | ||
if (this.__isInt(field) === true) { | ||
return [undefined, parseInt(field)]; | ||
} else if (this.__isFloat(field)) { | ||
return [undefined, parseFloat(field)]; | ||
} else if (this.options.cast_date !== false) { | ||
return [undefined, this.options.cast_date.call(null, field, context)]; | ||
} | ||
i++; | ||
} else if (!this._.commenting) { | ||
if (this._.field == null) { | ||
this._.field = ''; | ||
return [undefined, field]; | ||
} | ||
}, { | ||
key: "__isInt", | ||
value: function __isInt(value) { | ||
return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value); | ||
} | ||
}, { | ||
key: "__isFloat", | ||
value: function __isFloat(value) { | ||
return value - parseFloat(value) + 1 >= 0; // Borrowed from jquery | ||
} | ||
}, { | ||
key: "__compareBytes", | ||
value: function __compareBytes(sourceBuf, targetBuf, pos, firtByte) { | ||
if (sourceBuf[0] !== firtByte) return 0; | ||
var sourceLength = sourceBuf.length; | ||
for (var i = 1; i < sourceLength; i++) { | ||
if (sourceBuf[i] !== targetBuf[pos + i]) return 0; | ||
} | ||
this._.field += char; | ||
i++; | ||
} else { | ||
i++; | ||
return sourceLength; | ||
} | ||
}, { | ||
key: "__needMoreData", | ||
value: function __needMoreData(i, bufLen, end) { | ||
if (end) { | ||
return false; | ||
} | ||
if (!this._.commenting && ((ref3 = this._.field) != null ? ref3.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error("Field exceeds max_limit_on_data_read: maximum value is ".concat(this.options.max_limit_on_data_read)); | ||
var _this$options4 = this.options, | ||
comment = _this$options4.comment, | ||
delimiter = _this$options4.delimiter, | ||
escape = _this$options4.escape; | ||
var _this$state4 = this.state, | ||
quoting = _this$state4.quoting, | ||
recordDelimiterMaxLength = _this$state4.recordDelimiterMaxLength; | ||
var numOfCharLeft = bufLen - i - 1; | ||
var requiredLength = Math.max( // Skip if the remaining buffer smaller than comment | ||
comment ? comment.length : 0, // Skip if the remaining buffer smaller than row delimiter | ||
recordDelimiterMaxLength, // Skip if the remaining buffer can be row delimiter following the closing quote | ||
// 1 is for quote.length | ||
quoting ? 1 + recordDelimiterMaxLength : 0, // Skip if the remaining buffer can be delimiter | ||
delimiter.length, // Skip if the remaining buffer can be escape sequence | ||
// 1 is for escape.length | ||
1); | ||
return numOfCharLeft < requiredLength; | ||
} | ||
}, { | ||
key: "__isDelimiter", | ||
value: function __isDelimiter(chr, buf, pos) { | ||
var delimiter = this.options.delimiter; | ||
var delLength = delimiter.length; | ||
if (delimiter[0] !== chr) return 0; | ||
if (!this._.commenting && ((ref4 = this._.line) != null ? ref4.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error("Row delimiter not found in the file ".concat(JSON.stringify(this.options.rowDelimiter))); | ||
for (var i = 1; i < delLength; i++) { | ||
if (delimiter[i] !== buf[pos + i]) return 0; | ||
} | ||
return delimiter.length; | ||
} | ||
} // Flush remaining fields and lines | ||
}, { | ||
key: "__isRecordDelimiter", | ||
value: function __isRecordDelimiter(chr, buf, pos) { | ||
var record_delimiter = this.options.record_delimiter; | ||
var recordDelimiterLength = record_delimiter.length; | ||
loop1: for (var i = 0; i < recordDelimiterLength; i++) { | ||
var rd = record_delimiter[i]; | ||
var rdLength = rd.length; | ||
if (end) { | ||
if (l === 0) { | ||
this.lines++; | ||
} | ||
if (rd[0] !== chr) { | ||
continue; | ||
} | ||
if (this._.field != null) { | ||
if (rtrim) { | ||
if (!this._.closingQuote) { | ||
this._.field = (ref5 = this._.field) != null ? ref5.trimRight() : void 0; | ||
for (var j = 1; j < rdLength; j++) { | ||
if (rd[j] !== buf[pos + j]) { | ||
continue loop1; | ||
} | ||
} | ||
return rd.length; | ||
} | ||
this._.line.push(cast(this._.field || '')); | ||
this._.field = null; | ||
return 0; | ||
} | ||
}, { | ||
key: "__autoDiscoverRowDelimiter", | ||
value: function __autoDiscoverRowDelimiter(buf, pos) { | ||
var chr = buf[pos]; | ||
if (((ref6 = this._.field) != null ? ref6.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error("Delimiter not found in the file ".concat(JSON.stringify(this.options.delimiter))); | ||
if (chr === cr) { | ||
if (buf[pos + 1] === nl) { | ||
this.options.record_delimiter.push(Buffer.from('\r\n')); | ||
this.state.recordDelimiterMaxLength = 2; | ||
return 2; | ||
} else { | ||
this.options.record_delimiter.push(Buffer.from('\r')); | ||
this.state.recordDelimiterMaxLength = 1; | ||
return 1; | ||
} | ||
} else if (chr === nl) { | ||
this.options.record_delimiter.push(Buffer.from('\n')); | ||
this.state.recordDelimiterMaxLength = 1; | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
}, { | ||
key: "__error", | ||
value: function __error(msg) { | ||
var skip_lines_with_error = this.options.skip_lines_with_error; | ||
var err = new Error(msg); | ||
if (this._.line.length > this.options.max_limit_on_data_read) { | ||
return Error("Row delimiter not found in the file ".concat(JSON.stringify(this.options.rowDelimiter))); | ||
if (skip_lines_with_error) { | ||
this.state.recordHasError = true; | ||
this.emit('skip', err); | ||
return undefined; | ||
} else { | ||
return err; | ||
} | ||
} | ||
} // Store un-parsed chars for next call | ||
}]); | ||
return Parser; | ||
}(Transform); | ||
this._.buf = chars.substr(i); | ||
return null; | ||
}; | ||
var parse = function parse() { | ||
var data, options, callback; | ||
Parser.prototype.error = function (msg) { | ||
var err; | ||
err = Error(msg); | ||
for (var i in arguments) { | ||
var argument = arguments[i]; | ||
if (!this.options.skip_lines_with_error) { | ||
return err; | ||
} else { | ||
if (!this._.lineHasError) { | ||
this._.lineHasError = true; | ||
this.emit('skip', err); | ||
var type = _typeof(argument); | ||
if (data === undefined && (typeof argument === 'string' || Buffer.isBuffer(argument))) { | ||
data = argument; | ||
} else if (options === undefined && isObject(argument)) { | ||
options = argument; | ||
} else if (callback === undefined && type === 'function') { | ||
callback = argument; | ||
} else { | ||
throw new Error("Invalid argument: got ".concat(JSON.stringify(argument), " at index ").concat(i)); | ||
} | ||
} | ||
return null; | ||
}; // ## Utils | ||
var parser = new Parser(options); | ||
if (callback) { | ||
var records = options === undefined || options.objname === undefined ? [] : {}; | ||
parser.on('readable', function () { | ||
var record; | ||
isObjLiteral = function isObjLiteral(_obj) { | ||
var _test; | ||
while (record = this.read()) { | ||
if (options === undefined || options.objname === undefined) { | ||
records.push(record); | ||
} else { | ||
records[record[0]] = record[1]; | ||
} | ||
} | ||
}); | ||
parser.on('error', function (err) { | ||
callback(err, undefined, parser.info); | ||
}); | ||
parser.on('end', function () { | ||
callback(undefined, records, parser.info); | ||
}); | ||
} | ||
_test = _obj; | ||
if (data !== undefined) { | ||
parser.write(data); | ||
parser.end(); | ||
} | ||
if (_typeof(_obj) !== 'object' || _obj === null || Array.isArray(_obj)) { | ||
return false; | ||
} else { | ||
return function () { | ||
while (!false) { | ||
if (Object.getPrototypeOf(_test = Object.getPrototypeOf(_test)) === null) { | ||
break; | ||
} | ||
return parser; | ||
}; | ||
parse.Parser = Parser; | ||
module.exports = parse; | ||
var isObject = function isObject(obj) { | ||
return _typeof(obj) === 'object' && obj !== null && !Array.isArray(obj); | ||
}; | ||
var firstLineToHeadersDefault = function firstLineToHeadersDefault(record) { | ||
return record.map(function (field) { | ||
return { | ||
header: field, | ||
name: field | ||
}; | ||
}); | ||
}; | ||
var normalizeColumnsArray = function normalizeColumnsArray(columns) { | ||
for (var i = 0; i < columns.length; i++) { | ||
var column = columns[i]; | ||
if (column === undefined || column === null || column === false) { | ||
columns[i] = { | ||
disabled: true | ||
}; | ||
} else if (typeof column === 'string') { | ||
columns[i] = { | ||
name: column | ||
}; | ||
} else if (isObject(column)) { | ||
if (typeof column.name !== 'string') { | ||
throw new Error("Invalid Option columns: property \"name\" is required at position ".concat(i)); | ||
} | ||
return Object.getPrototypeOf(_obj === _test); | ||
}(); | ||
columns[i] = column; | ||
} else { | ||
throw new Error("Invalid Option columns: expect a string or an object, got ".concat(JSON.stringify(column), " at position ").concat(i)); | ||
} | ||
} | ||
}; |
"use strict"; | ||
// Generated by CoffeeScript 2.3.2 | ||
// # CSV Parse Sync | ||
// Provides a synchronous alternative to the CSV parser. | ||
// ## Usage | ||
// `const records = parse(data, [options]` | ||
var StringDecoder, parse; | ||
var parse = require('.'); | ||
var _require = require('string_decoder'); | ||
StringDecoder = _require.StringDecoder; | ||
parse = require('./index'); | ||
module.exports = function (data) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var chunks, decoder, err, parser; | ||
chunks = options.objname ? {} : []; | ||
if (data instanceof Buffer) { | ||
decoder = new StringDecoder(); | ||
data = decoder.write(data); | ||
if (typeof data === 'string') { | ||
data = Buffer.from(data); | ||
} | ||
parser = new parse.Parser(options); | ||
var records = options && options.objname ? {} : []; | ||
var parser = new parse.Parser(options); | ||
parser.push = function (chunk) { | ||
if (options.objname) { | ||
return chunks[chunk[0]] = chunk[1]; | ||
} else { | ||
return chunks.push(chunk); | ||
parser.push = function (record) { | ||
if (options.objname === undefined) records.push(record);else { | ||
records[record[0]] = record[1]; | ||
} | ||
}; | ||
err = parser.__write(data, false); | ||
var err1 = parser.__parse(data, false); | ||
if (err) { | ||
throw err; | ||
} | ||
if (err1 !== undefined) throw err1; | ||
if (data instanceof Buffer) { | ||
err = parser.__write(data.end(), true); | ||
var err2 = parser.__parse(undefined, true); | ||
if (err) { | ||
throw err; | ||
} | ||
} | ||
err = parser.__flush(); | ||
if (err) { | ||
throw err; | ||
} | ||
return chunks; | ||
if (err2 !== undefined) throw err2; | ||
return records; | ||
}; |
@@ -14,4 +14,5 @@ // Original definitions in https://github.com/DefinitelyTyped/DefinitelyTyped by: David Muller <https://github.com/davidm77> | ||
declare namespace parse { | ||
type Callback = (err: any | Error, output: any) => void; | ||
type Callback = (err: Error | undefined, records: any | undefined, info: Info) => void; | ||
type MatcherFunc = (value: any) => boolean; | ||
@@ -23,45 +24,21 @@ | ||
constructor(options: Options); | ||
__push(line: any): any; | ||
__write(chars: any, end: any, callback: any): any; | ||
/** | ||
* Internal counter of records being processed. | ||
*/ | ||
readonly count: number; | ||
/** | ||
* Internal counter of empty lines | ||
*/ | ||
readonly empty_line_count: number; | ||
/** | ||
* Number of non uniform lines skipped when relax_column_count is true. | ||
*/ | ||
readonly skipped_line_count: number; | ||
/** | ||
* The number of lines encountered in the source dataset, start at 1 for the first line. | ||
*/ | ||
readonly lines: number; | ||
/** | ||
* The regular expression or function used to determine if a value should be cast to an integer. | ||
*/ | ||
readonly is_int: RegExp | MatcherFunc; | ||
/** | ||
* The regular expression or function used to determine if a value should be cast to a float. | ||
*/ | ||
readonly is_float: RegExp | MatcherFunc | ||
readonly options: Options; | ||
readonly info: Info; | ||
} | ||
interface CastingContext { | ||
column?: string; | ||
count: number; | ||
index: number; | ||
header: boolean; | ||
quoting: boolean; | ||
lines: number; | ||
readonly column?: number | string; | ||
readonly empty_lines: number; | ||
readonly header: boolean; | ||
readonly index: number; | ||
readonly quoting: boolean; | ||
readonly lines: number; | ||
readonly records: number; | ||
readonly invalid_field_length: number; | ||
} | ||
@@ -104,3 +81,3 @@ | ||
*/ | ||
columns?: any[] | boolean | ((line1: any) => boolean | string[]); | ||
columns?: any[] | boolean | ((record: any) => boolean | string[]); | ||
@@ -115,3 +92,3 @@ /** | ||
*/ | ||
delimiter?: string; | ||
delimiter?: string | Buffer; | ||
@@ -121,8 +98,18 @@ /** | ||
*/ | ||
escape?: string; | ||
escape?: string | Buffer; | ||
/** | ||
* Start returning records from a particular line. | ||
* Start handling records from the requested number of records. | ||
*/ | ||
from?: number; | ||
/** | ||
* Start handling records from the requested line number. | ||
*/ | ||
from_line?: number; | ||
/** | ||
* Generate two properties `info` and `record` where `info` is a snapshot of the info object at the time the record was created and `record` is the parsed array or object. | ||
*/ | ||
info?: boolean; | ||
@@ -137,6 +124,6 @@ /** | ||
* Maximum numer of characters to be contained in the field and line buffers before an exception is raised, | ||
* used to guard against a wrong delimiter or rowDelimiter, | ||
* used to guard against a wrong delimiter or record_delimiter, | ||
* default to 128000 characters. | ||
*/ | ||
max_limit_on_data_read?: number; | ||
max_record_size?: number; | ||
@@ -151,5 +138,10 @@ /** | ||
*/ | ||
quote?: string | boolean; | ||
quote?: string | boolean | Buffer; | ||
/** | ||
* Generate two properties raw and row where raw is the original CSV row content and row is the parsed array or object. | ||
*/ | ||
raw?: boolean; | ||
/** | ||
* Preserve quotes inside unquoted field. | ||
@@ -165,11 +157,6 @@ */ | ||
/** | ||
* Generate two properties raw and row where raw is the original CSV row content and row is the parsed array or object. | ||
*/ | ||
raw?: boolean; | ||
/** | ||
* One or multiple characters used to delimit record rows; defaults to auto discovery if not provided. | ||
* Supported auto discovery method are Linux ("\n"), Apple ("\r") and Windows ("\r\n") row delimiters. | ||
*/ | ||
rowDelimiter?: string | string[]; | ||
record_delimiter?: string | string[] | Buffer | Buffer[]; | ||
@@ -199,3 +186,3 @@ /** | ||
/** | ||
* Stop returning records after a particular line. | ||
* Stop handling records after the requested number of records. | ||
*/ | ||
@@ -205,2 +192,7 @@ to?: number; | ||
/** | ||
* Stop handling records after the requested line number. | ||
*/ | ||
to_line?: number; | ||
/** | ||
* If true, ignore whitespace immediately around the delimiter, defaults to false. | ||
@@ -211,2 +203,25 @@ * Does not remove whitespace in a quoted field. | ||
} | ||
interface Info { | ||
/** | ||
* Count the number of lines being fully commented. | ||
*/ | ||
readonly comment_lines: number; | ||
/** | ||
* Count the number of processed empty lines. | ||
*/ | ||
readonly empty_lines: number; | ||
/** | ||
* The number of lines encountered in the source dataset, start at 1 for the first line. | ||
*/ | ||
readonly lines: number; | ||
/** | ||
* Count the number of processed records. | ||
*/ | ||
readonly records: number; | ||
/** | ||
* Number of non uniform records when `relax_column_count` is true. | ||
*/ | ||
readonly invalid_field_length: number; | ||
} | ||
} |
1288
lib/index.js
@@ -1,699 +0,741 @@ | ||
// Generated by CoffeeScript 2.3.2 | ||
// # CSV Parser | ||
// This module provides a CSV parser tested and used against large datasets. Over the year, it has been enhance and is now full of useful options. | ||
const { Transform } = require('stream') | ||
const ResizeableBuffer = require('./ResizeableBuffer') | ||
// Please look at the [project website](https://csv.js.org/parse/) for additional information. | ||
var Parser, StringDecoder, default_options, isObjLiteral, stream, util; | ||
stream = require('stream'); | ||
util = require('util'); | ||
({StringDecoder} = require('string_decoder')); | ||
// ## Usage | ||
// Callback approach, for ease of use: | ||
// `parse(data, [options], callback)` | ||
// [Node.js Stream API](http://nodejs.org/api/stream.html), for maximum of power: | ||
// `parse([options], [callback])` | ||
module.exports = function() { | ||
var callback, called, chunks, data, err, options, parser; | ||
if (arguments.length === 3) { | ||
data = arguments[0]; | ||
options = arguments[1]; | ||
callback = arguments[2]; | ||
if (typeof callback !== 'function') { | ||
throw Error(`Invalid callback argument: ${JSON.stringify(callback)}`); | ||
} | ||
if (!(typeof data === 'string' || Buffer.isBuffer(arguments[0]))) { | ||
return callback(Error(`Invalid data argument: ${JSON.stringify(data)}`)); | ||
} | ||
} else if (arguments.length === 2) { | ||
// 1st arg is data:string or options:object | ||
if (typeof arguments[0] === 'string' || Buffer.isBuffer(arguments[0])) { | ||
data = arguments[0]; | ||
} else if (isObjLiteral(arguments[0])) { | ||
options = arguments[0]; | ||
} else { | ||
err = `Invalid first argument: ${JSON.stringify(arguments[0])}`; | ||
} | ||
// 2nd arg is options:object or callback:function | ||
if (typeof arguments[1] === 'function') { | ||
callback = arguments[1]; | ||
} else if (isObjLiteral(arguments[1])) { | ||
if (options) { | ||
err = 'Invalid arguments: got options twice as first and second arguments'; | ||
} else { | ||
options = arguments[1]; | ||
} | ||
} else { | ||
err = `Invalid first argument: ${JSON.stringify(arguments[1])}`; | ||
} | ||
if (err) { | ||
if (!callback) { | ||
throw Error(err); | ||
} else { | ||
return callback(Error(err)); | ||
} | ||
} | ||
} else if (arguments.length === 1) { | ||
if (typeof arguments[0] === 'function') { | ||
callback = arguments[0]; | ||
} else { | ||
options = arguments[0]; | ||
} | ||
} | ||
if (options == null) { | ||
options = {}; | ||
} | ||
parser = new Parser(options); | ||
if (data != null) { | ||
process.nextTick(function() { | ||
parser.write(data); | ||
return parser.end(); | ||
}); | ||
} | ||
if (callback) { | ||
called = false; | ||
chunks = options.objname ? {} : []; | ||
parser.on('readable', function() { | ||
var chunk, results; | ||
results = []; | ||
while (chunk = parser.read()) { | ||
if (options.objname) { | ||
results.push(chunks[chunk[0]] = chunk[1]); | ||
} else { | ||
results.push(chunks.push(chunk)); | ||
} | ||
} | ||
return results; | ||
}); | ||
parser.on('error', function(err) { | ||
called = true; | ||
return callback(err); | ||
}); | ||
parser.on('end', function() { | ||
if (!called) { | ||
return callback(null, chunks); | ||
} | ||
}); | ||
} | ||
return parser; | ||
}; | ||
// ## `Parser([options])` | ||
// Options are documented [here](http://csv.js.org/parse/options/). | ||
default_options = { | ||
rowDelimiter: null, | ||
delimiter: ',', | ||
quote: '"', | ||
escape: '"', | ||
const default_options = { | ||
// cast: false, | ||
// cast_date: false, | ||
columns: null, | ||
comment: '', | ||
objname: false, | ||
trim: false, | ||
ltrim: false, | ||
rtrim: false, | ||
cast: false, | ||
cast_date: false, | ||
delimiter: Buffer.from(','), | ||
escape: Buffer.from('"'), | ||
from: 1, | ||
from_line: 1, | ||
objname: undefined, | ||
// ltrim: false, | ||
// quote: Buffer.from('"'), | ||
// TODO create a max_comment_size | ||
max_record_size: 0, | ||
relax: false, | ||
relax_column_count: false, | ||
// rtrim: false, | ||
skip_empty_lines: false, | ||
max_limit_on_data_read: 128000, | ||
skip_lines_with_empty_values: false, | ||
skip_lines_with_error: false | ||
}; | ||
skip_lines_with_error: false, | ||
to_line: -1, | ||
to: -1, | ||
trim: false | ||
} | ||
Parser = function(opts = {}) { | ||
var k, options, v; | ||
opts.objectMode = true; | ||
stream.Transform.call(this, opts); | ||
options = {}; | ||
// Clone options | ||
for (k in opts) { | ||
v = opts[k]; | ||
options[k] = v; | ||
} | ||
// Default values | ||
for (k in default_options) { | ||
v = default_options[k]; | ||
if (options[k] === void 0) { | ||
options[k] = default_options[k]; | ||
const cr = 13 | ||
const nl = 10 | ||
const space = 32 | ||
class Parser extends Transform { | ||
constructor(opts = {}){ | ||
const options = {} | ||
for(let i in opts){ | ||
options[i] = opts[i] | ||
} | ||
} | ||
this.options = options; | ||
if (typeof options.rowDelimiter === 'string') { | ||
options.rowDelimiter = [options.rowDelimiter]; | ||
} | ||
if (options.quote !== void 0 && !options.quote) { | ||
options.quote = ''; | ||
} | ||
if (options.auto_parse != null) { | ||
options.cast = options.auto_parse; | ||
} | ||
if (options.auto_parse_date != null) { | ||
options.cast_date = options.auto_parse_date; | ||
} | ||
if (options.cast_date === true) { | ||
options.cast_date = function(value) { | ||
var m; | ||
m = Date.parse(value); | ||
if (!isNaN(m)) { | ||
value = new Date(m); | ||
options.readableObjectMode = true | ||
super(options) | ||
// Import default options | ||
for(let k in default_options){ | ||
if(options[k] === undefined){ | ||
options[k] = default_options[k] | ||
} | ||
return value; | ||
}; | ||
} | ||
// Counters | ||
// lines = count + skipped_line_count + empty_line_count | ||
this.lines = 0; // Number of lines encountered in the source dataset | ||
this.count = 0; // Number of records being processed | ||
this.skipped_line_count = 0; // Number of records skipped due to errors | ||
this.empty_line_count = 0; // Number of empty lines | ||
// Constants | ||
this.is_int = /^(\-|\+)?([1-9]+[0-9]*)$/; | ||
// @is_float = /^(\-|\+)?([0-9]+(\.[0-9]+)([eE][0-9]+)?|Infinity)$/ | ||
// @is_float = /^(\-|\+)?((([0-9])|([1-9]+[0-9]*))(\.[0-9]+)([eE][0-9]+)?|Infinity)$/ | ||
this.is_float = function(value) { | ||
return (value - parseFloat(value) + 1) >= 0; // Borrowed from jquery | ||
}; | ||
// Internal private state | ||
this._ = { | ||
decoder: new StringDecoder(), | ||
quoting: false, | ||
commenting: false, | ||
field: null, | ||
nextChar: null, | ||
closingQuote: 0, | ||
line: [], | ||
chunks: [], | ||
rawBuf: '', | ||
buf: '', | ||
rowDelimiterMaxLength: this.options.rowDelimiter ? Math.max(...this.options.rowDelimiter.map(function(v) { | ||
return v.length; | ||
})) : void 0, | ||
quotedRowDelimiterMaxLength: this.options.rowDelimiter ? options.quote.length + Math.max(...this.options.rowDelimiter.map(function(v) { | ||
return v.length; | ||
})) : void 0, | ||
lineHasError: false, | ||
isEnded: false | ||
}; | ||
return this; | ||
}; | ||
// ## Internal API | ||
// The Parser implement a [`stream.Transform` class](https://nodejs.org/api/stream.html#stream_class_stream_transform). | ||
// ### Events | ||
// The library extends Node [EventEmitter][event] class and emit all the events of the Writable and Readable [Stream API](http://nodejs.org/api/stream.html). | ||
util.inherits(Parser, stream.Transform); | ||
// For extra flexibility, you can get access to the original Parser class: `require('csv-parse').Parser`. | ||
module.exports.Parser = Parser; | ||
// ### `_transform(chunk, encoding, callback)` | ||
// * `chunk` Buffer | String | ||
// The chunk to be transformed. Will always be a buffer unless the decodeStrings option was set to false. | ||
// * `encoding` String | ||
// If the chunk is a string, then this is the encoding type. (Ignore if decodeStrings chunk is a buffer.) | ||
// * `callback` Function | ||
// Call this function (optionally with an error argument) when you are done processing the supplied chunk. | ||
// Implementation of the [`stream.Transform` API](https://nodejs.org/api/stream.html#stream_class_stream_transform) | ||
Parser.prototype._transform = function(chunk, encoding, callback) { | ||
return setImmediate(() => { | ||
var err; | ||
if (chunk instanceof Buffer) { | ||
chunk = this._.decoder.write(chunk); | ||
} | ||
err = this.__write(chunk, false); | ||
if (err) { | ||
return this.emit('error', err); | ||
// Normalize option `cast` | ||
let fnCastField = null | ||
if(options.cast === undefined || options.cast === null || options.cast === false || options.cast === ''){ | ||
options.cast = undefined | ||
}else if(typeof options.cast === 'function'){ | ||
fnCastField = options.cast | ||
options.cast = true | ||
}else if(options.cast !== true){ | ||
throw new Error('Invalid Option: cast must be true or a function') | ||
} | ||
return callback(); | ||
}); | ||
}; | ||
Parser.prototype._flush = function(callback) { | ||
return callback(this.__flush()); | ||
}; | ||
Parser.prototype.__flush = function() { | ||
var err; | ||
err = this.__write(this._.decoder.end(), true); | ||
if (err) { | ||
return err; | ||
} | ||
if (this._.quoting) { | ||
err = this.error(`Quoted field not terminated at line ${this.lines + 1}`); | ||
return err; | ||
} | ||
if (this._.line.length > 0) { | ||
return this.__push(this._.line); | ||
} | ||
}; | ||
Parser.prototype.__push = function(line) { | ||
var call_column_udf, columnName, columns, err, field, i, j, len, lineAsColumns, record; | ||
if (this._.isEnded) { | ||
return; | ||
} | ||
if (this.options.skip_lines_with_empty_values && line.join('').trim() === '') { | ||
return; | ||
} | ||
record = null; | ||
if (this.options.columns === true) { | ||
this.options.columns = line; | ||
return; | ||
} else if (typeof this.options.columns === 'function') { | ||
call_column_udf = function(fn, line) { | ||
var columns, err; | ||
try { | ||
columns = fn.call(null, line); | ||
return [null, columns]; | ||
} catch (error) { | ||
err = error; | ||
return [err]; | ||
// Normize option `cast_date` | ||
if(options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === ''){ | ||
options.cast_date = false | ||
}else if(options.cast_date === true){ | ||
options.cast_date = function(value){ | ||
const date = Date.parse(value) | ||
return !isNaN(date) ? new Date(date) : value | ||
} | ||
}; | ||
[err, columns] = call_column_udf(this.options.columns, line); | ||
if (err) { | ||
return err; | ||
}else if(typeof options.cast_date !== 'function'){ | ||
throw new Error('Invalid Option: cast_date must be true or a function') | ||
} | ||
this.options.columns = columns; | ||
return; | ||
} | ||
if (!this._.line_length && line.length > 0) { | ||
this._.line_length = this.options.columns ? this.options.columns.length : line.length; | ||
} | ||
// Dont check column count on empty lines | ||
if (line.length === 1 && line[0] === '') { | ||
this.empty_line_count++; | ||
} else if (line.length !== this._.line_length) { | ||
// Dont check column count with relax_column_count | ||
if (this.options.relax_column_count) { | ||
this.count++; | ||
this.skipped_line_count++; | ||
} else if (this.options.columns != null) { | ||
// Suggest: Inconsistent header and column numbers: header is 1 and number of columns is 1 on line 1 | ||
err = this.error(`Number of columns on line ${this.lines} does not match header`); | ||
return err; | ||
} else { | ||
err = this.error(`Number of columns is inconsistent on line ${this.lines}`); | ||
return err; | ||
} | ||
} else { | ||
this.count++; | ||
} | ||
if (this.options.columns != null) { | ||
lineAsColumns = {}; | ||
for (i = j = 0, len = line.length; j < len; i = ++j) { | ||
field = line[i]; | ||
columnName = this.options.columns[i]; | ||
if (columnName === void 0 || columnName === null || columnName === false) { | ||
continue; | ||
// Normalize option `comment` | ||
if(options.comment === undefined || options.comment === null || options.comment === false || options.comment === ''){ | ||
options.comment = null | ||
}else{ | ||
if(typeof options.comment === 'string'){ | ||
options.comment = Buffer.from(options.comment) | ||
} | ||
if (typeof columnName !== 'string') { | ||
throw Error(`Invalid column name ${JSON.stringify(columnName)}`); | ||
if(!Buffer.isBuffer(options.comment)){ | ||
throw new Error(`Invalid Option: comment must be a buffer or a string, got ${JSON.stringify(options.comment)}`) | ||
} | ||
lineAsColumns[columnName] = field; | ||
} | ||
if (this.options.objname) { | ||
record = [lineAsColumns[this.options.objname], lineAsColumns]; | ||
} else { | ||
record = lineAsColumns; | ||
// Normalize option `delimiter` | ||
if(typeof options.delimiter === 'string'){ | ||
options.delimiter = Buffer.from(options.delimiter) | ||
} | ||
} else { | ||
record = line; | ||
} | ||
if (this.count < this.options.from) { | ||
return; | ||
} | ||
if (this.options.raw) { | ||
this.push({ | ||
raw: this._.rawBuf, | ||
row: record | ||
}); | ||
this._.rawBuf = ''; | ||
} else { | ||
this.push(record); | ||
} | ||
if (this.listenerCount('record')) { | ||
this.emit('record', record); | ||
} | ||
// When to is reached set ignore any future calls | ||
if (this.count >= this.options.to) { | ||
this._.isEnded = true; | ||
return this.push(null); | ||
} | ||
return null; | ||
}; | ||
Parser.prototype.__write = function(chars, end) { | ||
var areNextCharsDelimiter, areNextCharsRowDelimiters, cast, char, err, escapeIsQuote, i, isDelimiter, isEscape, isNextCharAComment, isNextCharTrimable, isQuote, isRowDelimiter, isRowDelimiterLength, is_float, is_int, l, ltrim, nextCharPos, numOfCharLeft, ref, ref1, ref2, ref3, ref4, ref5, ref6, remainingBuffer, rowDelimiter, rtrim, wasCommenting; | ||
is_int = (value) => { | ||
if (typeof this.is_int === 'function') { | ||
return this.is_int(value); | ||
} else { | ||
return this.is_int.test(value); | ||
// Normalize option `columns` | ||
let fnFirstLineToHeaders = null | ||
if(options.columns === true){ | ||
fnFirstLineToHeaders = firstLineToHeadersDefault | ||
}else if(typeof options.columns === 'function'){ | ||
fnFirstLineToHeaders = options.columns | ||
options.columns = true | ||
}else if(Array.isArray(options.columns)){ | ||
normalizeColumnsArray(options.columns) | ||
}else if(options.columns === undefined || options.columns === null || options.columns === false){ | ||
options.columns = false | ||
}else{ | ||
throw new Error(`Invalid Option columns: expect an object or true, got ${JSON.stringify(options.columns)}`) | ||
} | ||
}; | ||
is_float = (value) => { | ||
if (typeof this.is_float === 'function') { | ||
return this.is_float(value); | ||
} else { | ||
return this.is_float.test(value); | ||
// Normalize option `escape` | ||
if(typeof options.escape === 'string'){ | ||
options.escape = Buffer.from(options.escape) | ||
} | ||
}; | ||
cast = (value, context = {}) => { | ||
if (!this.options.cast) { | ||
return value; | ||
if(!Buffer.isBuffer(options.escape)){ | ||
throw new Error(`Invalid Option: escape must be a buffer or a string, got ${JSON.stringify(options.escape)}`) | ||
}else if(options.escape.length !== 1){ | ||
throw new Error(`Invalid Option Length: escape must be one character, got ${options.escape.length}`) | ||
}else{ | ||
options.escape = options.escape[0] | ||
} | ||
if (context.quoting == null) { | ||
context.quoting = !!this._.closingQuote; | ||
// Normalize option `info` | ||
if(options.info === undefined || options.info === null || options.info === false){ | ||
options.info = false | ||
}else if(options.info !== true){ | ||
throw new Error(`Invalid Option: info must be true, got ${JSON.stringify(options.info)}`) | ||
} | ||
if (context.lines == null) { | ||
context.lines = this.lines; | ||
// Normalize option `quote` | ||
if(options.quote === null || options.quote === false || options.quote === ''){ | ||
options.quote = null | ||
}else{ | ||
if(options.quote === undefined || options.quote === true){ | ||
options.quote = Buffer.from('"') | ||
}else if(typeof options.quote === 'string'){ | ||
options.quote = Buffer.from(options.quote) | ||
} | ||
if(!Buffer.isBuffer(options.quote)){ | ||
throw new Error(`Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`) | ||
}else if(options.quote.length !== 1){ | ||
throw new Error(`Invalid Option Length: quote must be one character, got ${options.quote.length}`) | ||
}else{ | ||
options.quote = options.quote[0] | ||
} | ||
} | ||
if (context.count == null) { | ||
context.count = this.count; | ||
// Normalize options `raw` | ||
if(options.raw === undefined || options.raw === null || options.raw === false){ | ||
options.raw = false | ||
}else if(options.raw !== true){ | ||
throw new Error(`Invalid Option: raw must be true, got ${JSON.stringify(options.raw)}`) | ||
} | ||
if (context.index == null) { | ||
context.index = this._.line.length; | ||
// Normalize option `record_delimiter` | ||
if(!options.record_delimiter){ | ||
options.record_delimiter = [] | ||
}else if(!Array.isArray(options.record_delimiter)){ | ||
options.record_delimiter = [options.record_delimiter] | ||
} | ||
// context.header ?= if @options.column and @lines is 1 and @count is 0 then true else false | ||
if (context.header == null) { | ||
context.header = this.options.columns === true; | ||
options.record_delimiter = options.record_delimiter.map( function(rd){ | ||
if(typeof rd === 'string'){ | ||
rd = Buffer.from(rd) | ||
} | ||
return rd | ||
}) | ||
// Normalize options `trim`, `ltrim` and `rtrim` | ||
if(options.trim === true && options.ltrim !== false){ | ||
options.ltrim = true | ||
}else if(options.ltrim !== true){ | ||
options.ltrim = false | ||
} | ||
if (context.column == null) { | ||
context.column = Array.isArray(this.options.columns) ? this.options.columns[context.index] : context.index; | ||
if(options.trim === true && options.rtrim !== false){ | ||
options.rtrim = true | ||
}else if(options.rtrim !== true){ | ||
options.rtrim = false | ||
} | ||
if (typeof this.options.cast === 'function') { | ||
return this.options.cast(value, context); | ||
this.info = { | ||
comment_lines: 0, | ||
empty_lines: 0, | ||
invalid_field_length: 0, | ||
lines: 1, | ||
records: 0 | ||
} | ||
if (is_int(value)) { | ||
value = parseInt(value); | ||
} else if (is_float(value)) { | ||
value = parseFloat(value); | ||
} else if (this.options.cast_date) { | ||
value = this.options.cast_date(value, context); | ||
this.options = options | ||
this.state = { | ||
castField: fnCastField, | ||
commenting: false, | ||
enabled: options.from_line === 1, | ||
escaping: false, | ||
escapeIsQuote: options.escape === options.quote, | ||
expectedRecordLength: options.columns === null ? 0 : options.columns.length, | ||
field: new ResizeableBuffer(20), | ||
firstLineToHeaders: fnFirstLineToHeaders, | ||
info: Object.assign({}, this.info), | ||
previousBuf: undefined, | ||
quoting: false, | ||
stop: false, | ||
rawBuffer: new ResizeableBuffer(100), | ||
record: [], | ||
recordHasError: false, | ||
record_length: 0, | ||
recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max(...options.record_delimiter.map( (v) => v.length)), | ||
trimChars: [Buffer.from(' ')[0], Buffer.from('\t')[0]], | ||
wasQuoting: false, | ||
wasRowDelimiter: false | ||
} | ||
return value; | ||
}; | ||
ltrim = this.options.trim || this.options.ltrim; | ||
rtrim = this.options.trim || this.options.rtrim; | ||
escapeIsQuote = this.options.escape === this.options.quote; | ||
chars = this._.buf + chars; | ||
l = chars.length; | ||
i = 0; | ||
if (this.lines === 0 && 0xFEFF === chars.charCodeAt(0)) { | ||
// Strip BOM header | ||
i++; | ||
} | ||
while (i < l) { | ||
// Ensure we get enough space to look ahead | ||
if (!end) { | ||
numOfCharLeft = l - i; | ||
remainingBuffer = chars.substr(i, numOfCharLeft); | ||
// Skip if row delimiter larger than auto discovered values | ||
// Skip if the remaining buffer smaller than comment | ||
// Skip if the remaining buffer smaller than row delimiter | ||
// Skip if the remaining buffer can be row delimiter following the closing quote | ||
// Skip if the remaining buffer can be delimiter | ||
// Skip if the remaining buffer can be escape sequence | ||
if ((!this.options.rowDelimiter && i + 3 > l) || (!this._.commenting && numOfCharLeft < this.options.comment.length) || (this.options.rowDelimiter && numOfCharLeft < this._.rowDelimiterMaxLength) || (this._.quoting && this.options.rowDelimiter && numOfCharLeft < this._.quotedRowDelimiterMaxLength) || (numOfCharLeft <= this.options.delimiter.length && this.options.delimiter.substr(0, numOfCharLeft) === remainingBuffer) || (numOfCharLeft <= this.options.escape.length && this.options.escape.substr(0, numOfCharLeft) === remainingBuffer)) { | ||
break; | ||
} | ||
_transform(buf, encoding, callback){ | ||
if(this.state.stop === true){ | ||
return | ||
} | ||
char = this._.nextChar ? this._.nextChar : chars.charAt(i); | ||
this._.nextChar = l > i + 1 ? chars.charAt(i + 1) : null; | ||
if (this.options.raw) { | ||
this._.rawBuf += char; | ||
const err = this.__parse(buf, false) | ||
if(err !== undefined){ | ||
this.state.stop = true | ||
} | ||
// Auto discovery of rowDelimiter, unix, mac and windows supported | ||
if (this.options.rowDelimiter == null) { | ||
nextCharPos = i; | ||
rowDelimiter = null; | ||
// First empty line | ||
if (!this._.quoting && (char === '\n' || char === '\r')) { | ||
rowDelimiter = char; | ||
nextCharPos += 1; | ||
} else if (this._.quoting && char === this.options.quote && ((ref = this._.nextChar) === '\n' || ref === '\r')) { | ||
rowDelimiter = this._.nextChar; | ||
nextCharPos += 2; | ||
callback(err) | ||
} | ||
_flush(callback){ | ||
if(this.state.stop === true){ | ||
return | ||
} | ||
const err = this.__parse(undefined, true) | ||
callback(err) | ||
} | ||
__parse(nextBuf, end){ | ||
const {comment, escape, from, from_line, info, ltrim, max_record_size, quote, raw, relax, rtrim, skip_empty_lines, to, to_line} = this.options | ||
let {record_delimiter} = this.options | ||
const {previousBuf, rawBuffer, escapeIsQuote, trimChars} = this.state | ||
let buf | ||
if(previousBuf === undefined && nextBuf !== undefined){ | ||
buf = nextBuf | ||
}else if(previousBuf !== undefined && nextBuf === undefined){ | ||
buf = previousBuf | ||
}else{ | ||
buf = Buffer.concat([previousBuf, nextBuf]) | ||
} | ||
const bufLen = buf.length | ||
let pos | ||
// let escaping = this. | ||
for(pos = 0; pos < bufLen; pos++){ | ||
// Ensure we get enough space to look ahead | ||
// There should be a way to move this out of the loop | ||
if(this.__needMoreData(pos, bufLen, end)){ | ||
break | ||
} | ||
if (rowDelimiter) { | ||
if (rowDelimiter === '\r' && chars.charAt(nextCharPos) === '\n') { | ||
rowDelimiter += '\n'; | ||
if(this.state.wasRowDelimiter === true){ | ||
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.options.rowDelimiter = [rowDelimiter]; | ||
this._.rowDelimiterMaxLength = rowDelimiter.length; | ||
this.state.wasRowDelimiter = false | ||
} | ||
} | ||
// Parse that damn char | ||
// Note, shouldn't we have sth like chars.substr(i, @options.escape.length) | ||
if (!this._.commenting && char === this.options.escape) { | ||
// Make sure the escape is really here for escaping: | ||
// If escape is same as quote, and escape is first char of a field | ||
// and it's not quoted, then it is a quote | ||
// Next char should be an escape or a quote | ||
isEscape = this._.nextChar === this.options.escape; | ||
isQuote = this._.nextChar === this.options.quote; | ||
if (!(escapeIsQuote && !this._.field && !this._.quoting) && (isEscape || isQuote)) { | ||
i++; | ||
char = this._.nextChar; | ||
this._.nextChar = chars.charAt(i + 1); | ||
if (this._.field == null) { | ||
this._.field = ''; | ||
if(to_line !== -1 && this.info.lines > to_line){ | ||
this.state.stop = true | ||
this.push(null) | ||
return | ||
} | ||
// Auto discovery of record_delimiter, unix, mac and windows supported | ||
if(this.state.quoting === false && record_delimiter.length === 0){ | ||
const recordDelimiterCount = this.__autoDiscoverRowDelimiter(buf, pos) | ||
if(recordDelimiterCount){ | ||
record_delimiter = this.options.record_delimiter | ||
} | ||
this._.field += char; | ||
// Since we're skipping the next one, better add it now if in raw mode. | ||
if (this.options.raw) { | ||
this._.rawBuf += char; | ||
} | ||
i++; | ||
continue; | ||
} | ||
} | ||
// Char match quote | ||
if (!this._.commenting && char === this.options.quote) { | ||
if (this._.acceptOnlyEmptyChars && (char !== ' ' && char !== '\t')) { | ||
return this.error('Only trimable characters are accepted after quotes'); | ||
const chr = buf[pos] | ||
if(raw === true){ | ||
rawBuffer.append(chr) | ||
} | ||
if (this._.quoting) { | ||
// Make sure a closing quote is followed by a delimiter | ||
// If we have a next character and | ||
// it isnt a rowDelimiter and | ||
// it isnt an column delimiter and | ||
// it isnt the begining of a comment | ||
// Otherwise, if this is not "relax" mode, throw an error | ||
isNextCharTrimable = rtrim && ((ref1 = this._.nextChar) === ' ' || ref1 === '\t'); | ||
areNextCharsRowDelimiters = this.options.rowDelimiter && this.options.rowDelimiter.some(function(rd) { | ||
return chars.substr(i + 1, rd.length) === rd; | ||
}); | ||
areNextCharsDelimiter = chars.substr(i + 1, this.options.delimiter.length) === this.options.delimiter; | ||
isNextCharAComment = this._.nextChar === this.options.comment; | ||
if ((this._.nextChar != null) && !isNextCharTrimable && !areNextCharsRowDelimiters && !areNextCharsDelimiter && !isNextCharAComment) { | ||
if (this.options.relax) { | ||
this._.quoting = false; | ||
if (this._.field) { | ||
this._.field = `${this.options.quote}${this._.field}`; | ||
if((chr === cr || chr === nl) && this.state.wasRowDelimiter === false ){ | ||
this.state.wasRowDelimiter = true | ||
} | ||
// Previous char was a valid escape char | ||
// treat the current char as a regular char | ||
if(this.state.escaping === true){ | ||
this.state.escaping = false | ||
}else{ | ||
// Escape is only active inside quoted fields | ||
if(this.state.quoting === true && chr === escape && pos + 1 < bufLen){ | ||
// We are quoting, the char is an escape chr and there is a chr to escape | ||
if(escapeIsQuote){ | ||
if(buf[pos+1] === quote){ | ||
this.state.escaping = true | ||
continue | ||
} | ||
} else { | ||
if (err = this.error(`Invalid closing quote at line ${this.lines + 1}; found ${JSON.stringify(this._.nextChar)} instead of delimiter ${JSON.stringify(this.options.delimiter)}`)) { | ||
return err; | ||
}else{ | ||
this.state.escaping = true | ||
continue | ||
} | ||
} | ||
// Not currently escaping and chr is a quote | ||
// TODO: need to compare bytes instead of single char | ||
if(this.state.commenting === false && chr === quote){ | ||
if(this.state.quoting === true){ | ||
const nextChr = buf[pos+1] | ||
const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr) | ||
// const isNextChrComment = nextChr === comment | ||
const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+1, nextChr) | ||
const isNextChrDelimiter = this.__isDelimiter(nextChr, buf, pos+1) | ||
const isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos+1) : this.__isRecordDelimiter(nextChr, buf, pos+1) | ||
// Escape a quote | ||
// Treat next char as a regular character | ||
// TODO: need to compare bytes instead of single char | ||
if(chr === escape && nextChr === quote){ | ||
pos++ | ||
}else if(!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable){ | ||
this.state.quoting = false | ||
this.state.wasQuoting = true | ||
continue | ||
}else if(relax === false){ | ||
const err = this.__error(`Invalid Closing Quote: got "${String.fromCharCode(nextChr)}" at line ${this.info.lines} instead of delimiter, row delimiter, trimable character (if activated) or comment`) | ||
if(err !== undefined) return err | ||
}else{ | ||
this.state.quoting = false | ||
this.state.wasQuoting = true | ||
// continue | ||
this.state.field.prepend(quote) | ||
} | ||
}else{ | ||
if(this.state.field.length !== 0){ | ||
// In relax mode, treat opening quote preceded by chrs as regular | ||
if( relax === false ){ | ||
const err = this.__error(`Invalid opening quote at line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
} | ||
}else{ | ||
this.state.quoting = true | ||
continue | ||
} | ||
} | ||
} else if ((this._.nextChar != null) && isNextCharTrimable) { | ||
i++; | ||
this._.quoting = false; | ||
this._.closingQuote = 1; | ||
this._.acceptOnlyEmptyChars = true; | ||
continue; | ||
} else { | ||
i++; | ||
this._.quoting = false; | ||
this._.closingQuote = 1; | ||
if (end && i === l) { | ||
this._.line.push(cast(this._.field || '')); | ||
this._.field = null; | ||
} | ||
if(this.state.quoting === false){ | ||
let recordDelimiterLength = this.__isRecordDelimiter(chr, buf, pos) | ||
if(recordDelimiterLength !== 0){ | ||
// Do not emit comments which take a full line | ||
const skipCommentLine = this.state.commenting && (this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0) | ||
if(skipCommentLine){ | ||
this.info.comment_lines++ | ||
// Skip full comment line | ||
}else{ | ||
// Skip if line is empty and skip_empty_lines activated | ||
if(skip_empty_lines === true && this.state.wasQuoting === false && this.state.record.length === 0 && this.state.field.length === 0){ | ||
this.info.empty_lines++ | ||
continue | ||
} | ||
// Activate records emition if above from_line | ||
if(this.state.enabled === false && this.info.lines + (this.state.wasRowDelimiter === true ? 1: 0 ) >= from_line){ | ||
this.state.enabled = true | ||
this.__resetField() | ||
this.__resetRow() | ||
continue | ||
}else{ | ||
const errField = this.__onField() | ||
if(errField !== undefined) return errField | ||
const errRecord = this.__onRow() | ||
if(errRecord !== undefined) return errRecord | ||
} | ||
if(to !== -1 && this.info.records >= to){ | ||
this.state.stop = true | ||
this.push(null) | ||
return | ||
} | ||
} | ||
this.state.commenting = false | ||
pos += recordDelimiterLength - 1 | ||
continue | ||
} | ||
continue; | ||
if(this.state.commenting){ | ||
continue | ||
} | ||
const commentCount = comment === null ? 0 : this.__compareBytes(comment, buf, pos, chr) | ||
if(commentCount !== 0){ | ||
this.state.commenting = true | ||
continue | ||
} | ||
let delimiterLength = this.__isDelimiter(chr, buf, pos) | ||
if(delimiterLength !== 0){ | ||
const errField = this.__onField() | ||
if(errField !== undefined) return errField | ||
pos += delimiterLength - 1 | ||
continue | ||
} | ||
} | ||
} else if (!this._.field) { | ||
this._.quoting = true; | ||
i++; | ||
continue; | ||
} else if ((this._.field != null) && !this.options.relax) { | ||
if (err = this.error(`Invalid opening quote at line ${this.lines + 1}`)) { | ||
return err; | ||
} | ||
if(this.state.commenting === false){ | ||
if(max_record_size !== 0 && this.state.record_length + this.state.field.length > max_record_size){ | ||
const err = this.__error(`Max Record Size: record exceed the maximum number of tolerated bytes of ${max_record_size} on line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
} | ||
} | ||
const lappend = ltrim === false || this.state.quoting === true || this.state.field.length !== 0 || !this.__isCharTrimable(chr) | ||
// rtrim in non quoting is handle in __onField | ||
const rappend = rtrim === false || this.state.wasQuoting === false | ||
if( lappend === true && rappend === true ){ | ||
this.state.field.append(chr) | ||
}else if(rtrim === true && !this.__isCharTrimable(chr)){ | ||
const err = this.__error(`Invalid Closing Quote: found non trimable byte after quote at line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
} | ||
} | ||
// Otherwise, treat quote as a regular character | ||
isRowDelimiter = this.options.rowDelimiter && this.options.rowDelimiter.some(function(rd) { | ||
return chars.substr(i, rd.length) === rd; | ||
}); | ||
if (isRowDelimiter || (end && i === l - 1)) { | ||
this.lines++; | ||
if(end === true){ | ||
if(this.state.quoting === true){ | ||
const err = this.__error(`Invalid Closing Quote: quote is not closed at line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
}else{ | ||
// Skip last line if it has no characters | ||
if(this.state.wasQuoting === true || this.state.record.length !== 0 || this.state.field.length !== 0){ | ||
const errField = this.__onField() | ||
if(errField !== undefined) return errField | ||
const errRecord = this.__onRow() | ||
if(errRecord !== undefined) return errRecord | ||
}else if(this.state.wasRowDelimiter === true){ | ||
this.info.empty_lines++ | ||
}else if(this.state.commenting === true){ | ||
this.info.comment_lines++ | ||
} | ||
} | ||
}else{ | ||
this.state.previousBuf = buf.slice(pos) | ||
} | ||
// Set the commenting flag | ||
wasCommenting = false; | ||
if (!this._.commenting && !this._.quoting && this.options.comment && chars.substr(i, this.options.comment.length) === this.options.comment) { | ||
this._.commenting = true; | ||
} else if (this._.commenting && isRowDelimiter) { | ||
wasCommenting = true; | ||
this._.commenting = false; | ||
if(this.state.wasRowDelimiter === true){ | ||
this.info.lines++ | ||
this.state.wasRowDelimiter = false | ||
} | ||
isDelimiter = chars.substr(i, this.options.delimiter.length) === this.options.delimiter; | ||
if (this._.acceptOnlyEmptyChars) { | ||
if (isDelimiter || isRowDelimiter) { | ||
this._.acceptOnlyEmptyChars = false; | ||
} else { | ||
if (char === ' ' || char === '\t') { | ||
i++; | ||
continue; | ||
} else { | ||
return this.error('Only trimable characters are accepted after quotes'); | ||
} | ||
__isCharTrimable(chr){ | ||
return chr === space || chr === cr || chr === nl | ||
} | ||
__onRow(){ | ||
const {columns, info, from, relax_column_count, raw, skip_lines_with_empty_values} = this.options | ||
const {enabled, record} = this.state | ||
// Validate column length | ||
if(columns === true && this.state.firstLineToHeaders){ | ||
return this.__firstLineToColumns(record) | ||
} | ||
const recordLength = record.length | ||
if(columns === false && this.info.records === 0){ | ||
this.state.expectedRecordLength = recordLength | ||
}else if(enabled === true){ | ||
if(recordLength !== this.state.expectedRecordLength){ | ||
if(relax_column_count === true){ | ||
this.info.invalid_field_length++ | ||
}else{ | ||
if(columns === false){ | ||
const err = this.__error(`Invalid Record Length: expect ${this.state.expectedRecordLength}, got ${recordLength} on line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
}else{ | ||
const err = this.__error(`Invalid Record Length: header length is ${columns.length}, got ${recordLength} on line ${this.info.lines}`) | ||
if(err !== undefined) return err | ||
} | ||
} | ||
} | ||
} | ||
if (!this._.commenting && !this._.quoting && (isDelimiter || isRowDelimiter)) { | ||
if (isRowDelimiter) { | ||
isRowDelimiterLength = this.options.rowDelimiter.filter(function(rd) { | ||
return chars.substr(i, rd.length) === rd; | ||
})[0].length; | ||
if(enabled === false){ | ||
return this.__resetRow() | ||
} | ||
if( skip_lines_with_empty_values === true){ | ||
if(record.map( (field) => field.trim() ).join('') === ''){ | ||
this.__resetRow() | ||
return | ||
} | ||
// Empty lines | ||
if (isRowDelimiter && this._.line.length === 0 && (this._.field == null)) { | ||
if (wasCommenting || this.options.skip_empty_lines) { | ||
i += isRowDelimiterLength; | ||
this._.nextChar = chars.charAt(i); | ||
continue; | ||
} | ||
if(this.state.recordHasError === true){ | ||
this.__resetRow() | ||
this.state.recordHasError = false | ||
return | ||
} | ||
this.info.records++ | ||
if(from === 1 || this.info.records >= from){ | ||
if(columns !== false){ | ||
const obj = {} | ||
// Transform record array to an object | ||
for(let i in record){ | ||
if(columns[i].disabled) continue | ||
obj[columns[i].name] = record[i] | ||
} | ||
} | ||
if (rtrim) { | ||
if (!this._.closingQuote) { | ||
this._.field = (ref2 = this._.field) != null ? ref2.trimRight() : void 0; | ||
} | ||
} | ||
this._.line.push(cast(this._.field || '')); | ||
this._.closingQuote = 0; | ||
this._.field = null; | ||
// End of field | ||
// Ensure that the delimiter doesnt match as well the rowDelimiter | ||
if (isDelimiter && !isRowDelimiter) { | ||
i += this.options.delimiter.length; | ||
this._.nextChar = chars.charAt(i); | ||
if (end && !this._.nextChar) { | ||
isRowDelimiter = true; | ||
this._.line.push(''); | ||
} | ||
} | ||
if (isRowDelimiter) { // End of record | ||
if (!this._.lineHasError) { | ||
err = this.__push(this._.line); | ||
if (err) { | ||
return err; | ||
const {objname} = this.options | ||
if(objname === undefined){ | ||
if(raw === true || info === true){ | ||
this.push(Object.assign( | ||
{record: obj}, | ||
(raw === true ? {raw: this.state.rawBuffer.toString()}: {}), | ||
(info === true ? {info: this.state.info}: {}) | ||
)) | ||
}else{ | ||
this.push(obj) | ||
} | ||
}else{ | ||
if(raw === true || info === true){ | ||
this.push(Object.assign( | ||
{record: [obj[objname], obj]}, | ||
raw === true ? {raw: this.state.rawBuffer.toString()}: {}, | ||
info === true ? {info: this.state.info}: {} | ||
)) | ||
}else{ | ||
this.push([obj[objname], obj]) | ||
} | ||
} | ||
if (this._.lineHasError) { | ||
this._.lineHasError = false; | ||
}else{ | ||
if(raw === true || info === true){ | ||
this.push(Object.assign( | ||
{record: record}, | ||
raw === true ? {raw: this.state.rawBuffer.toString()}: {}, | ||
info === true ? {info: this.state.info}: {} | ||
)) | ||
}else{ | ||
this.push(record) | ||
} | ||
// Some cleanup for the next record | ||
this._.line = []; | ||
i += isRowDelimiterLength; | ||
this._.nextChar = chars.charAt(i); | ||
continue; | ||
} | ||
} else if (!this._.commenting && !this._.quoting && (char === ' ' || char === '\t')) { | ||
if (this._.field == null) { | ||
// Left trim unless we are quoting or field already filled | ||
this._.field = ''; | ||
} | ||
this.__resetRow() | ||
} | ||
__firstLineToColumns(record){ | ||
try{ | ||
const headers = this.state.firstLineToHeaders.call(null, record) | ||
if(!Array.isArray(headers)){ | ||
return this.__error(`Invalid Header Mapping: expect an array, got ${JSON.stringify(headers)}`) | ||
} | ||
if (!(ltrim && !this._.field)) { | ||
this._.field += char; | ||
normalizeColumnsArray(headers) | ||
this.state.expectedRecordLength = headers.length | ||
this.options.columns = headers | ||
this.__resetRow() | ||
return | ||
}catch(err){ | ||
return err | ||
} | ||
} | ||
__resetRow(){ | ||
const {info} = this.options | ||
if(this.options.raw === true){ | ||
this.state.rawBuffer.reset() | ||
} | ||
this.state.record = [] | ||
this.state.record_length = 0 | ||
} | ||
__onField(){ | ||
const {cast, rtrim} = this.options | ||
const {enabled, wasQuoting} = this.state | ||
// Deal with from_to options | ||
if(this.options.columns !== true && enabled === false){ | ||
return this.__resetField() | ||
} | ||
let field = this.state.field.toString() | ||
if(rtrim === true && wasQuoting === false){ | ||
field = field.trimRight() | ||
} | ||
if(cast === true){ | ||
const [err, f] = this.__cast(field) | ||
if(err !== undefined) return err | ||
field = f | ||
} | ||
this.state.record.push(field) | ||
this.state.record_length += field.length | ||
this.__resetField() | ||
} | ||
__resetField(){ | ||
this.state.field.reset() | ||
this.state.wasQuoting = false | ||
} | ||
__cast(field){ | ||
const context = { | ||
column: Array.isArray(this.options.columns) === true ? this.options.columns[this.state.record.length] : this.state.record.length, | ||
empty_lines: this.info.empty_lines, | ||
header: this.options.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 | ||
} | ||
if(this.state.castField !== null){ | ||
try{ | ||
return [undefined, this.state.castField.call(null, field, context)] | ||
}catch(err){ | ||
return [err] | ||
} | ||
i++; | ||
} else if (!this._.commenting) { | ||
if (this._.field == null) { | ||
this._.field = ''; | ||
} | ||
this._.field += char; | ||
i++; | ||
} else { | ||
i++; | ||
} | ||
if (!this._.commenting && ((ref3 = this._.field) != null ? ref3.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error(`Field exceeds max_limit_on_data_read: maximum value is ${this.options.max_limit_on_data_read}`); | ||
if(this.__isInt(field) === true){ | ||
return [undefined, parseInt(field)] | ||
}else if(this.__isFloat(field)){ | ||
return [undefined, parseFloat(field)] | ||
}else if(this.options.cast_date !== false){ | ||
return [undefined, this.options.cast_date.call(null, field, context)] | ||
} | ||
if (!this._.commenting && ((ref4 = this._.line) != null ? ref4.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error(`Row delimiter not found in the file ${JSON.stringify(this.options.rowDelimiter)}`); | ||
return [undefined, field] | ||
} | ||
__isInt(value){ | ||
return /^(\-|\+)?([1-9]+[0-9]*)$/.test(value) | ||
} | ||
__isFloat(value){ | ||
return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery | ||
} | ||
__compareBytes(sourceBuf, targetBuf, pos, firtByte){ | ||
if(sourceBuf[0] !== firtByte) return 0 | ||
const sourceLength = sourceBuf.length | ||
for(let i = 1; i < sourceLength; i++){ | ||
if(sourceBuf[i] !== targetBuf[pos+i]) return 0 | ||
} | ||
return sourceLength | ||
} | ||
// Flush remaining fields and lines | ||
if (end) { | ||
if (l === 0) { | ||
this.lines++; | ||
__needMoreData(i, bufLen, end){ | ||
if(end){ | ||
return false | ||
} | ||
if (this._.field != null) { | ||
if (rtrim) { | ||
if (!this._.closingQuote) { | ||
this._.field = (ref5 = this._.field) != null ? ref5.trimRight() : void 0; | ||
const {comment, delimiter, escape} = this.options | ||
const {quoting, recordDelimiterMaxLength} = this.state | ||
const numOfCharLeft = bufLen - i - 1 | ||
const requiredLength = Math.max( | ||
// Skip if the remaining buffer smaller than comment | ||
comment ? comment.length : 0, | ||
// Skip if the remaining buffer smaller than row delimiter | ||
recordDelimiterMaxLength, | ||
// Skip if the remaining buffer can be row delimiter following the closing quote | ||
// 1 is for quote.length | ||
quoting ? (1 + recordDelimiterMaxLength) : 0, | ||
// Skip if the remaining buffer can be delimiter | ||
delimiter.length, | ||
// Skip if the remaining buffer can be escape sequence | ||
// 1 is for escape.length | ||
1 | ||
) | ||
return numOfCharLeft < requiredLength | ||
} | ||
__isDelimiter(chr, buf, pos){ | ||
const {delimiter} = this.options | ||
const delLength = delimiter.length | ||
if(delimiter[0] !== chr) return 0 | ||
for(let i = 1; i < delLength; i++){ | ||
if(delimiter[i] !== buf[pos+i]) return 0 | ||
} | ||
return delimiter.length | ||
} | ||
__isRecordDelimiter(chr, buf, pos){ | ||
const {record_delimiter} = this.options | ||
const recordDelimiterLength = record_delimiter.length | ||
loop1: for(let i = 0; i < recordDelimiterLength; i++){ | ||
const rd = record_delimiter[i] | ||
const rdLength = rd.length | ||
if(rd[0] !== chr){ | ||
continue | ||
} | ||
for(let j = 1; j < rdLength; j++){ | ||
if(rd[j] !== buf[pos+j]){ | ||
continue loop1 | ||
} | ||
} | ||
this._.line.push(cast(this._.field || '')); | ||
this._.field = null; | ||
return rd.length | ||
} | ||
if (((ref6 = this._.field) != null ? ref6.length : void 0) > this.options.max_limit_on_data_read) { | ||
return Error(`Delimiter not found in the file ${JSON.stringify(this.options.delimiter)}`); | ||
return 0 | ||
} | ||
__autoDiscoverRowDelimiter(buf, pos){ | ||
const chr = buf[pos] | ||
if(chr === cr){ | ||
if(buf[pos+1] === nl){ | ||
this.options.record_delimiter.push(Buffer.from('\r\n')) | ||
this.state.recordDelimiterMaxLength = 2 | ||
return 2 | ||
}else{ | ||
this.options.record_delimiter.push(Buffer.from('\r')) | ||
this.state.recordDelimiterMaxLength = 1 | ||
return 1 | ||
} | ||
}else if(chr === nl){ | ||
this.options.record_delimiter.push(Buffer.from('\n')) | ||
this.state.recordDelimiterMaxLength = 1 | ||
return 1 | ||
} | ||
if (this._.line.length > this.options.max_limit_on_data_read) { | ||
return Error(`Row delimiter not found in the file ${JSON.stringify(this.options.rowDelimiter)}`); | ||
return 0 | ||
} | ||
__error(msg){ | ||
const {skip_lines_with_error} = this.options | ||
const err = new Error(msg) | ||
if(skip_lines_with_error){ | ||
this.state.recordHasError = true | ||
this.emit('skip', err) | ||
return undefined | ||
}else{ | ||
return err | ||
} | ||
} | ||
// Store un-parsed chars for next call | ||
this._.buf = chars.substr(i); | ||
return null; | ||
}; | ||
} | ||
Parser.prototype.error = function(msg) { | ||
var err; | ||
err = Error(msg); | ||
if (!this.options.skip_lines_with_error) { | ||
return err; | ||
} else { | ||
if (!this._.lineHasError) { | ||
this._.lineHasError = true; | ||
this.emit('skip', err); | ||
const parse = function(){ | ||
let data, options, callback | ||
for(let i in arguments){ | ||
const argument = arguments[i] | ||
const type = typeof argument | ||
if(data === undefined && (typeof argument === 'string' || Buffer.isBuffer(argument))){ | ||
data = argument | ||
}else if(options === undefined && isObject(argument)){ | ||
options = argument | ||
}else if(callback === undefined && type === 'function'){ | ||
callback = argument | ||
}else{ | ||
throw new Error(`Invalid argument: got ${JSON.stringify(argument)} at index ${i}`) | ||
} | ||
} | ||
return null; | ||
}; | ||
// ## Utils | ||
isObjLiteral = function(_obj) { | ||
var _test; | ||
_test = _obj; | ||
if (typeof _obj !== 'object' || _obj === null || Array.isArray(_obj)) { | ||
return false; | ||
} else { | ||
return (function() { | ||
while (!false) { | ||
if (Object.getPrototypeOf(_test = Object.getPrototypeOf(_test)) === null) { | ||
break; | ||
const parser = new Parser(options) | ||
if(callback){ | ||
const records = options === undefined || options.objname === undefined ? [] : {} | ||
parser.on('readable', function(){ | ||
let record | ||
while(record = this.read()){ | ||
if(options === undefined || options.objname === undefined){ | ||
records.push(record) | ||
}else{ | ||
records[record[0]] = record[1] | ||
} | ||
} | ||
return Object.getPrototypeOf(_obj === _test); | ||
})(); | ||
}) | ||
parser.on('error', function(err){ | ||
callback(err, undefined, parser.info) | ||
}) | ||
parser.on('end', function(){ | ||
callback(undefined, records, parser.info) | ||
}) | ||
} | ||
}; | ||
if(data !== undefined){ | ||
parser.write(data) | ||
parser.end() | ||
} | ||
return parser | ||
} | ||
parse.Parser = Parser | ||
module.exports = parse | ||
const isObject = function(obj){ | ||
return (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) | ||
} | ||
const firstLineToHeadersDefault = function(record){ | ||
return record.map(function(field){ | ||
return { | ||
header: field, | ||
name: field | ||
} | ||
}) | ||
} | ||
const normalizeColumnsArray = function(columns){ | ||
for(let i=0; i< columns.length; i++){ | ||
const column = columns[i] | ||
if(column === undefined || column === null || column === false){ | ||
columns[i] = { disabled: true } | ||
}else if(typeof column === 'string'){ | ||
columns[i] = { name: column } | ||
}else if(isObject(column)){ | ||
if(typeof column.name !== 'string'){ | ||
throw new Error(`Invalid Option columns: property "name" is required at position ${i}`) | ||
} | ||
columns[i] = column | ||
}else{ | ||
throw new Error(`Invalid Option columns: expect a string or an object, got ${JSON.stringify(column)} at position ${i}`) | ||
} | ||
} | ||
} |
@@ -1,45 +0,22 @@ | ||
// Generated by CoffeeScript 2.3.2 | ||
// # CSV Parse Sync | ||
// Provides a synchronous alternative to the CSV parser. | ||
const parse = require('.') | ||
// ## Usage | ||
// `const records = parse(data, [options]` | ||
var StringDecoder, parse; | ||
({StringDecoder} = require('string_decoder')); | ||
parse = require('./index'); | ||
module.exports = function(data, options = {}) { | ||
var chunks, decoder, err, parser; | ||
chunks = options.objname ? {} : []; | ||
if (data instanceof Buffer) { | ||
decoder = new StringDecoder(); | ||
data = decoder.write(data); | ||
module.exports = function(data, options={}){ | ||
if(typeof data === 'string'){ | ||
data = Buffer.from(data) | ||
} | ||
parser = new parse.Parser(options); | ||
parser.push = function(chunk) { | ||
if (options.objname) { | ||
return chunks[chunk[0]] = chunk[1]; | ||
} else { | ||
return chunks.push(chunk); | ||
const records = options && options.objname ? {} : [] | ||
const parser = new parse.Parser(options) | ||
parser.push = function(record){ | ||
if(options.objname === undefined) | ||
records.push(record) | ||
else{ | ||
records[record[0]] = record[1] | ||
} | ||
}; | ||
err = parser.__write(data, false); | ||
if (err) { | ||
throw err; | ||
} | ||
if (data instanceof Buffer) { | ||
err = parser.__write(data.end(), true); | ||
if (err) { | ||
throw err; | ||
} | ||
} | ||
err = parser.__flush(); | ||
if (err) { | ||
throw err; | ||
} | ||
return chunks; | ||
}; | ||
const err1 = parser.__parse(data, false) | ||
if(err1 !== undefined) throw err1 | ||
const err2 = parser.__parse(undefined, true) | ||
if(err2 !== undefined) throw err2 | ||
return records | ||
} |
{ | ||
"version": "3.2.0", | ||
"version": "4.0.0", | ||
"name": "csv-parse", | ||
@@ -41,9 +41,14 @@ "description": "CSV parsing implementing the Node.js `stream.Transform` API", | ||
"@babel/preset-env": "^7.1.0", | ||
"@types/mocha": "^5.2.5", | ||
"@types/node": "^10.12.3", | ||
"@types/should": "^13.0.0", | ||
"coffeescript": "^2.3.2", | ||
"csv-generate": "^2.2.2", | ||
"stream-transform": "^1.0.7", | ||
"csv-spectrum": "^1.0.0", | ||
"each": "^1.2.1", | ||
"mocha": "^5.2.0", | ||
"should": "^13.2.3" | ||
"should": "^13.2.3", | ||
"stream-transform": "^1.0.7", | ||
"ts-node": "^7.0.1", | ||
"typescript": "^3.1.6" | ||
}, | ||
@@ -53,3 +58,3 @@ "optionalDependencies": {}, | ||
"scripts": { | ||
"preversion": "rm -rf lib/*.js && npm test && grep '## Trunk' CHANGELOG.md", | ||
"preversion": "npm test && grep '## Trunk' CHANGELOG.md", | ||
"version": "version=`grep '^ \"version\": ' package.json | sed 's/.*\"\\([0-9\\.]*\\)\".*/\\1/'` && sed -i \"s/## Trunk/## Version $version/\" CHANGELOG.md && git add CHANGELOG.md", | ||
@@ -60,7 +65,6 @@ "postversion": "git push && git push --tags && npm publish", | ||
"major": "npm version major -m 'Bump to version %s'", | ||
"coffee": "coffee -b -o lib src && cd lib && babel *.js -d es5 && cd ..", | ||
"pretest": "coffee -b -o lib src && cd lib && babel *.js -d es5 && cd ..", | ||
"test": "mocha test/**/*.coffee" | ||
"pretest": "cd lib && babel *.js -d es5 && cd ..", | ||
"test": "mocha test/**/*.{coffee,ts}" | ||
}, | ||
"types": "./lib/index.d.ts" | ||
} |
@@ -10,3 +10,3 @@ [![Build Status](https://api.travis-ci.org/adaltas/node-csv-parse.svg)](https://travis-ci.org/#!/adaltas/node-csv-parse) | ||
* [Options](http://csv.js.org/parse/options/) | ||
* [State properties](http://csv.js.org/parse/state/) | ||
* [Info properties](http://csv.js.org/parse/info/) | ||
* [Common errors](http://csv.js.org/parse/errors/) | ||
@@ -23,4 +23,4 @@ * [Examples](http://csv.js.org/parse/examples/) | ||
* Complete test coverage and samples for inspiration | ||
* no external dependencies | ||
* to be used conjointly with `csv-generate`, `stream-transform` and `csv-stringify` | ||
* BSD License | ||
* No external dependencies | ||
* Work nicely with the [csv-generate](https://csv.js.org/generate/), [stream-transform](https://csv.js.org/transform/) and [csv-stringify](https://csv.js.org/stringify/) packages | ||
* MIT License |
@@ -21,5 +21,9 @@ | ||
[ '2000-01-01T05:00:00.000Z', { | ||
quoting: false, lines: 1, count: 0, index: 1, header: false, column: 1 } ], | ||
column: 1, empty_lines: 0, header: false, index: 1, | ||
invalid_field_length: 0, quoting: false, lines: 1, records: 0 | ||
} ], | ||
[ '2050-11-27T05:00:00.000Z', { | ||
quoting: false, lines: 2, count: 1, index: 1, header: false, column: 1 } ] | ||
column: 1, empty_lines: 0, header: false, index: 1, | ||
invalid_field_length: 0, quoting: false, lines: 2, records: 1 | ||
} ] | ||
]) |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
90518
28
2083
15
1
1