diff --git a/.eslintrc.js b/.eslintrc.js index f63bc09..c3ed7c5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -178,7 +178,7 @@ module.exports = { "no-tabs": "off", "no-template-curly-in-string": "error", "no-ternary": "off", - "no-throw-literal": "off", + "no-throw-literal": "error", "no-trailing-spaces": "error", "no-undef-init": "error", "no-undefined": "off", diff --git a/.travis.yml b/.travis.yml index a4ef1c4..f00e0fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: node_js node_js: - - "6" - "8" - "9" - "10" - + - "11" diff --git a/Gruntfile.js b/Gruntfile.js index e645b52..93b1256 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,6 +2,12 @@ module.exports = function(grunt) { grunt.initConfig({ uglify: { options: { + compress: { + global_defs: { + 'PAPA_BROWSER_CONTEXT': true + }, + dead_code: true + }, output: { comments: 'some', }, diff --git a/docs/docs.html b/docs/docs.html index 68f1132..1da2126 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -340,7 +340,7 @@ var csv = Papa.unparse({ quoteChar: '"', escapeChar: '"', header: false, - trimHeaders: false, + transformHeader: undefined, dynamicTyping: false, preview: 0, encoding: "", @@ -412,10 +412,10 @@ var csv = Papa.unparse({ - trimHeaders + transformHeader - If true leading/trailing spaces will be trimed from headers. + A function to apply on each header. Requires header to be true. The function receives the header as its first argument. @@ -447,7 +447,7 @@ var csv = Papa.unparse({ worker - Whether or not to use a worker thread. Using a worker will keep your page reactive, but may be slightly slower. Web Workers also load the entire Javascript file, so be careful when combining other libraries in the same file as Papa Parse. Note that worker option is only available when parsing files and not when converting from JSON to CSV. + Whether or not to use a worker thread. Using a worker will keep your page reactive, but may be slightly slower. @@ -545,7 +545,7 @@ var csv = Papa.unparse({ transform - A function to apply on each value. The function receives the value as its first argument and the column number as its second argument. The return value of the function will replace the value it received. The transform function is applied before dynamicTyping. + A function to apply on each value. The function receives the value as its first argument and the column number or header name when enabled as its second argument. The return value of the function will replace the value it received. The transform function is applied before dynamicTyping. @@ -749,12 +749,6 @@ var csv = Papa.unparse({ Whether or not the browser supports HTML5 Web Workers. If false, worker: true will have no effect. - - Papa.SCRIPT_PATH - - The relative path to Papa Parse. This is automatically detected when Papa Parse is loaded synchronously. However, if you load Papa Parse asynchronously (e.g. with RequireJS), you need to set this variable manually in order to use Web Workers. (In those cases, this variable is not read-only and you should set it!) - - diff --git a/docs/faq.html b/docs/faq.html index 9af0863..edced65 100644 --- a/docs/faq.html +++ b/docs/faq.html @@ -84,7 +84,7 @@
Can I put other libraries in the same file as Papa Parse?

- Yes, but then don't use the Web Worker feature unless your other dependencies are battle-hardened for worker threads. A worker thread loads an entire file, not just a function, so all those dependencies would be executed in an environment without a DOM and other window features. If any of those dependencies crash (Cannot read property "defaultView" of undefined is common), the whole worker thread will crash and parsing will not succeed. + Yes.

@@ -96,7 +96,7 @@
Can Papa Parse be loaded asynchronously (after the page loads)?

- Yes. But if you want to use Web Workers, you'll need to specify the relative path to Papa Parse. To do this, set Papa.SCRIPT_PATH to the relative path of the Papa Parse file. In synchronous loading, this is automatically detected. + Yes.

@@ -209,7 +209,7 @@
Can I use a worker if I combine/concatenate my Javascript files?

- Probably not. It's safest to concatenate the rest of your dependencies and include Papa Parse in a seperate file. Any library that expects to have access to the window or DOM will crash when executed in a worker thread. Only put other libraries in the same file if they are ready to be used in worker threads. + Yes.

When should I use a worker?
diff --git a/docs/resources/js/papaparse.js b/docs/resources/js/papaparse.js index 5bef3cc..94c81b6 100644 --- a/docs/resources/js/papaparse.js +++ b/docs/resources/js/papaparse.js @@ -1,11 +1,22 @@ -/*! - Papa Parse - v4.3.7 - https://github.com/mholt/PapaParse - License: MIT +/* @license +Papa Parse +v4.6.1 +https://github.com/mholt/PapaParse +License: MIT */ + +// Polyfills +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray#Polyfill +if (!Array.isArray) +{ + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; +} + (function(root, factory) { + /* globals define */ if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. @@ -27,7 +38,7 @@ { 'use strict'; - var global = (function () { + var global = (function() { // alternative method, similar to `Function('return this')()` // but without using `eval` (which is disabled when // using Content Security Policy). @@ -40,7 +51,6 @@ return {}; })(); - var IS_WORKER = !global.document && !!global.postMessage, IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search), LOADED_SYNC = false, AUTO_SCRIPT_PATH; @@ -57,6 +67,7 @@ Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker; Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously + Papa.NODE_STREAM_INPUT = 1; // Configurable chunk sizes for local and remote files, respectively Papa.LocalChunkSize = 1024 * 1024 * 10; // 10 MB @@ -70,6 +81,7 @@ Papa.FileStreamer = FileStreamer; Papa.StringStreamer = StringStreamer; Papa.ReadableStreamStreamer = ReadableStreamStreamer; + Papa.DuplexStreamStreamer = DuplexStreamStreamer; if (global.jQuery) { @@ -162,7 +174,7 @@ queue.splice(0, 1); parseNextFile(); } - } + }; } @@ -182,7 +194,7 @@ } else { - document.addEventListener('DOMContentLoaded', function () { + document.addEventListener('DOMContentLoaded', function() { LOADED_SYNC = true; }, true); } @@ -202,6 +214,8 @@ } _config.dynamicTyping = dynamicTyping; + _config.transform = isFunction(_config.transform) ? _config.transform : false; + if (_config.worker && Papa.WORKERS_SUPPORTED) { var w = newWorker(); @@ -227,7 +241,14 @@ } var streamer = null; - if (typeof _input === 'string') + if (_input === Papa.NODE_STREAM_INPUT) + { + // create a node Duplex stream for use + // with .pipe + streamer = new DuplexStreamStreamer(_config); + return streamer.getStream(); + } + else if (typeof _input === 'string') { if (_config.download) streamer = new NetworkStreamer(_config); @@ -251,9 +272,6 @@ function JsonToCsv(_input, _config) { - var _output = ''; - var _fields = []; - // Default configuration /** whether to surround every datum with quotes */ @@ -262,7 +280,7 @@ /** whether to write headers */ var _writeHeader = true; - /** delimiting character */ + /** delimiting character(s) */ var _delimiter = ','; /** newline character(s) */ @@ -271,6 +289,9 @@ /** quote character */ var _quoteChar = '"'; + /** whether to skip empty lines */ + var _skipEmptyLines = false; + unpackConfig(); var quoteCharRegex = new RegExp(_quoteChar, 'g'); @@ -278,33 +299,33 @@ if (typeof _input === 'string') _input = JSON.parse(_input); - if (_input instanceof Array) + if (Array.isArray(_input)) { - if (!_input.length || _input[0] instanceof Array) - return serialize(null, _input); + if (!_input.length || Array.isArray(_input[0])) + return serialize(null, _input, _skipEmptyLines); else if (typeof _input[0] === 'object') - return serialize(objectKeys(_input[0]), _input); + return serialize(objectKeys(_input[0]), _input, _skipEmptyLines); } else if (typeof _input === 'object') { if (typeof _input.data === 'string') _input.data = JSON.parse(_input.data); - if (_input.data instanceof Array) + if (Array.isArray(_input.data)) { if (!_input.fields) _input.fields = _input.meta && _input.meta.fields; if (!_input.fields) - _input.fields = _input.data[0] instanceof Array - ? _input.fields - : objectKeys(_input.data[0]); + _input.fields = Array.isArray(_input.data[0]) + ? _input.fields + : objectKeys(_input.data[0]); - if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object') + if (!(Array.isArray(_input.data[0])) && typeof _input.data[0] !== 'object') _input.data = [_input.data]; // handles input like [1,2,3] or ['asdf'] } - return serialize(_input.fields || [], _input.data || []); + return serialize(_input.fields || [], _input.data || [], _skipEmptyLines); } // Default (any valid paths should return before this) @@ -317,16 +338,19 @@ return; if (typeof _config.delimiter === 'string' - && _config.delimiter.length === 1 - && Papa.BAD_DELIMITERS.indexOf(_config.delimiter) === -1) + && !Papa.BAD_DELIMITERS.filter(function(value) { return _config.delimiter.indexOf(value) !== -1; }).length) { _delimiter = _config.delimiter; } if (typeof _config.quotes === 'boolean' - || _config.quotes instanceof Array) + || Array.isArray(_config.quotes)) _quotes = _config.quotes; + if (typeof _config.skipEmptyLines === 'boolean' + || typeof _config.skipEmptyLines === 'string') + _skipEmptyLines = _config.skipEmptyLines; + if (typeof _config.newline === 'string') _newline = _config.newline; @@ -350,7 +374,7 @@ } /** The double for loop that iterates the data and writes out a CSV string including header row */ - function serialize(fields, data) + function serialize(fields, data, skipEmptyLines) { var csv = ''; @@ -359,8 +383,8 @@ if (typeof data === 'string') data = JSON.parse(data); - var hasHeader = fields instanceof Array && fields.length > 0; - var dataKeyedByField = !(data[0] instanceof Array); + var hasHeader = Array.isArray(fields) && fields.length > 0; + var dataKeyedByField = !(Array.isArray(data[0])); // If there a header row, write it first if (hasHeader && _writeHeader) @@ -379,19 +403,21 @@ for (var row = 0; row < data.length; row++) { var maxCol = hasHeader ? fields.length : data[row].length; + var r = hasHeader ? fields : data[row]; - for (var col = 0; col < maxCol; col++) + if (skipEmptyLines !== 'greedy' || r.join('').trim() !== '') { - if (col > 0) - csv += _delimiter; - var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; - csv += safe(data[row][colIdx], col); + for (var col = 0; col < maxCol; col++) + { + if (col > 0) + csv += _delimiter; + var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; + csv += safe(data[row][colIdx], col); + } + if (row < data.length - 1 && (!skipEmptyLines || maxCol > 0)) + csv += _newline; } - - if (row < data.length - 1) - csv += _newline; } - return csv; } @@ -401,10 +427,13 @@ if (typeof str === 'undefined' || str === null) return ''; - str = str.toString().replace(quoteCharRegex, _quoteChar+_quoteChar); + if (str.constructor === Date) + return JSON.stringify(str).slice(1, 25); + + str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar); var needsQuotes = (typeof _quotes === 'boolean' && _quotes) - || (_quotes instanceof Array && _quotes[col]) + || (Array.isArray(_quotes) && _quotes[col]) || hasAny(str, Papa.BAD_DELIMITERS) || str.indexOf(_delimiter) > -1 || str.charAt(0) === ' ' @@ -426,8 +455,8 @@ function ChunkStreamer(config) { this._handle = null; - this._paused = false; this._finished = false; + this._completed = false; this._input = null; this._baseIndex = 0; this._partialLine = ''; @@ -442,7 +471,7 @@ }; replaceConfig.call(this, config); - this.parseChunk = function(chunk) + this.parseChunk = function(chunk, isFakeChunk) { // First chunk pre-processing if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk)) @@ -483,10 +512,10 @@ finished: finishedIncludingPreview }); } - else if (isFunction(this._config.chunk)) + else if (isFunction(this._config.chunk) && !isFakeChunk) { this._config.chunk(results, this._handle); - if (this._paused) + if (this._handle.paused() || this._handle.aborted()) return; results = undefined; this._completeResults = undefined; @@ -498,8 +527,10 @@ this._completeResults.meta = results.meta; } - if (finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) + if (!this._completed && finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) { this._config.complete(this._completeResults, this._input); + this._completed = true; + } if (!finishedIncludingPreview && (!results || !results.meta.paused)) this._nextChunk(); @@ -602,7 +633,7 @@ if (this._config.chunkSize) { var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive - xhr.setRequestHeader('Range', 'bytes='+this._start+'-'+end); + xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end); xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672 } @@ -617,11 +648,11 @@ this._chunkError(); else this._start += this._config.chunkSize; - } + }; this._chunkLoaded = function() { - if (xhr.readyState != 4) + if (xhr.readyState !== 4) return; if (xhr.status < 200 || xhr.status >= 400) @@ -632,20 +663,20 @@ this._finished = !this._config.chunkSize || this._start > getFileSize(xhr); this.parseChunk(xhr.responseText); - } + }; this._chunkError = function(errorMessage) { var errorText = xhr.statusText || errorMessage; - this._sendError(errorText); - } + this._sendError(new Error(errorText)); + }; function getFileSize(xhr) { var contentRange = xhr.getResponseHeader('Content-Range'); if (contentRange === null) { // no content range, then finish! - return -1; - } + return -1; + } return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1)); } } @@ -687,7 +718,7 @@ { if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview)) this._readChunk(); - } + }; this._readChunk = function() { @@ -700,7 +731,7 @@ var txt = reader.readAsText(input, this._config.encoding); if (!usingAsyncReader) this._chunkLoaded({ target: { result: txt } }); // mimic the async signature - } + }; this._chunkLoaded = function(event) { @@ -708,12 +739,12 @@ this._start += this._config.chunkSize; this._finished = !this._config.chunkSize || this._start >= this._input.size; this.parseChunk(event.target.result); - } + }; this._chunkError = function() { - this._sendError(reader.error.message); - } + this._sendError(reader.error); + }; } FileStreamer.prototype = Object.create(ChunkStreamer.prototype); @@ -725,14 +756,12 @@ config = config || {}; ChunkStreamer.call(this, config); - var string; var remaining; this.stream = function(s) { - string = s; remaining = s; return this._nextChunk(); - } + }; this._nextChunk = function() { if (this._finished) return; @@ -741,7 +770,7 @@ remaining = size ? remaining.substr(size) : ''; this._finished = !remaining; return this.parseChunk(chunk); - } + }; } StringStreamer.prototype = Object.create(StringStreamer.prototype); StringStreamer.prototype.constructor = StringStreamer; @@ -755,6 +784,19 @@ var queue = []; var parseOnData = true; + var streamHasEnded = false; + + this.pause = function() + { + ChunkStreamer.prototype.pause.apply(this, arguments); + this._input.pause(); + }; + + this.resume = function() + { + ChunkStreamer.prototype.resume.apply(this, arguments); + this._input.resume(); + }; this.stream = function(stream) { @@ -763,10 +805,18 @@ this._input.on('data', this._streamData); this._input.on('end', this._streamEnd); this._input.on('error', this._streamError); - } + }; + + this._checkIsFinished = function() + { + if (streamHasEnded && queue.length === 1) { + this._finished = true; + } + }; this._nextChunk = function() { + this._checkIsFinished(); if (queue.length) { this.parseChunk(queue.shift()); @@ -775,7 +825,7 @@ { parseOnData = true; } - } + }; this._streamData = bindFunction(function(chunk) { @@ -786,6 +836,7 @@ if (parseOnData) { parseOnData = false; + this._checkIsFinished(); this.parseChunk(queue.shift()); } } @@ -798,13 +849,13 @@ this._streamError = bindFunction(function(error) { this._streamCleanUp(); - this._sendError(error.message); + this._sendError(error); }, this); this._streamEnd = bindFunction(function() { this._streamCleanUp(); - this._finished = true; + streamHasEnded = true; this._streamData(''); }, this); @@ -819,14 +870,117 @@ ReadableStreamStreamer.prototype.constructor = ReadableStreamStreamer; + function DuplexStreamStreamer(_config) { + var Duplex = require('stream').Duplex; + var config = copy(_config); + var parseOnWrite = true; + var writeStreamHasFinished = false; + var parseCallbackQueue = []; + var stream = null; + + this._onCsvData = function(results) + { + var data = results.data; + for (var i = 0; i < data.length; i++) { + if (!stream.push(data[i]) && !this._handle.paused()) { + // the writeable consumer buffer has filled up + // so we need to pause until more items + // can be processed + this._handle.pause(); + } + } + }; + + this._onCsvComplete = function() + { + // node will finish the read stream when + // null is pushed + stream.push(null); + }; + + config.step = bindFunction(this._onCsvData, this); + config.complete = bindFunction(this._onCsvComplete, this); + ChunkStreamer.call(this, config); + + this._nextChunk = function() + { + if (writeStreamHasFinished && parseCallbackQueue.length === 1) { + this._finished = true; + } + if (parseCallbackQueue.length) { + parseCallbackQueue.shift()(); + } else { + parseOnWrite = true; + } + }; + + this._addToParseQueue = function(chunk, callback) + { + // add to queue so that we can indicate + // completion via callback + // node will automatically pause the incoming stream + // when too many items have been added without their + // callback being invoked + parseCallbackQueue.push(bindFunction(function() { + this.parseChunk(typeof chunk === 'string' ? chunk : chunk.toString(config.encoding)); + if (isFunction(callback)) { + return callback(); + } + }, this)); + if (parseOnWrite) { + parseOnWrite = false; + this._nextChunk(); + } + }; + + this._onRead = function() + { + if (this._handle.paused()) { + // the writeable consumer can handle more data + // so resume the chunk parsing + this._handle.resume(); + } + }; + + this._onWrite = function(chunk, encoding, callback) + { + this._addToParseQueue(chunk, callback); + }; + + this._onWriteComplete = function() + { + writeStreamHasFinished = true; + // have to write empty string + // so parser knows its done + this._addToParseQueue(''); + }; + + this.getStream = function() + { + return stream; + }; + stream = new Duplex({ + readableObjectMode: true, + decodeStrings: false, + read: bindFunction(this._onRead, this), + write: bindFunction(this._onWrite, this) + }); + stream.once('finish', bindFunction(this._onWriteComplete, this)); + } + DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); + DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer; + + // Use one ParserHandle per entire CSV file or string function ParserHandle(_config) { // One goal is to minimize the use of regular expressions... var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; + var ISO_DATE = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/; var self = this; var _stepCounter = 0; // Number of times step was called (number of rows parsed) + var _rowCounter = 0; // Number of rows that have been parsed so far var _input; // The input being parsed var _parser; // The core parser being used var _paused = false; // Whether we are paused or not @@ -872,13 +1026,14 @@ */ this.parse = function(input, baseIndex, ignoreLastRow) { + var quoteChar = _config.quoteChar || '"'; if (!_config.newline) - _config.newline = guessLineEndings(input); + _config.newline = guessLineEndings(input, quoteChar); _delimiterError = false; if (!_config.delimiter) { - var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines); + var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments); if (delimGuess.successful) _config.delimiter = delimGuess.bestDelimiter; else @@ -920,10 +1075,10 @@ this.resume = function() { _paused = false; - self.streamer.parseChunk(_input); + self.streamer.parseChunk(_input, true); }; - this.aborted = function () + this.aborted = function() { return _aborted; }; @@ -938,25 +1093,29 @@ _input = ''; }; + function testEmptyLine(s) { + return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0; + } + function processResults() { if (_results && _delimiterError) { - addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \''+Papa.DefaultDelimiter+'\''); + addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \'' + Papa.DefaultDelimiter + '\''); _delimiterError = false; } if (_config.skipEmptyLines) { for (var i = 0; i < _results.data.length; i++) - if (_results.data[i].length === 1 && _results.data[i][0] === '') + if (testEmptyLine(_results.data[i])) _results.data.splice(i--, 1); } if (needsHeaderRow()) fillHeaderFields(); - return applyHeaderAndDynamicTyping(); + return applyHeaderAndDynamicTypingAndTransformation(); } function needsHeaderRow() @@ -970,7 +1129,15 @@ return; for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) for (var j = 0; j < _results.data[i].length; j++) - _fields.push(_results.data[i][j]); + { + var header = _results.data[i][j]; + + if (_config.trimHeaders) { + header = header.trim(); + } + + _fields.push(header); + } _results.data.splice(0, 1); } @@ -979,7 +1146,7 @@ if (_config.dynamicTypingFunction && _config.dynamicTyping[field] === undefined) { _config.dynamicTyping[field] = _config.dynamicTypingFunction(field); } - return (_config.dynamicTyping[field] || _config.dynamicTyping) === true + return (_config.dynamicTyping[field] || _config.dynamicTyping) === true; } function parseDynamic(field, value) @@ -990,22 +1157,27 @@ return true; else if (value === 'false' || value === 'FALSE') return false; + else if (FLOAT.test(value)) + return parseFloat(value); + else if (ISO_DATE.test(value)) + return new Date(value); else - return tryParseFloat(value); + return (value === '' ? null : value); } return value; } - function applyHeaderAndDynamicTyping() + function applyHeaderAndDynamicTypingAndTransformation() { - if (!_results || (!_config.header && !_config.dynamicTyping)) + if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform)) return _results; for (var i = 0; i < _results.data.length; i++) { var row = _config.header ? {} : []; - for (var j = 0; j < _results.data[i].length; j++) + var j; + for (j = 0; j < _results.data[i].length; j++) { var field = j; var value = _results.data[i][j]; @@ -1013,6 +1185,9 @@ if (_config.header) field = j >= _fields.length ? '__parsed_extra' : _fields[j]; + if (_config.transform) + value = _config.transform(value,field); + value = parseDynamic(field, value); if (field === '__parsed_extra') @@ -1029,18 +1204,20 @@ if (_config.header) { if (j > _fields.length) - addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, i); + addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i); else if (j < _fields.length) - addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, i); + addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i); } } if (_config.header && _results.meta) _results.meta.fields = _fields; + + _rowCounter += _results.data.length; return _results; } - function guessDelimiter(input, newline, skipEmptyLines) + function guessDelimiter(input, newline, skipEmptyLines, comments) { var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]; var bestDelim, bestDelta, fieldCountPrevRow; @@ -1052,6 +1229,7 @@ fieldCountPrevRow = undefined; var preview = new Parser({ + comments: comments, delimiter: delim, newline: newline, preview: 10 @@ -1059,9 +1237,10 @@ for (var j = 0; j < preview.data.length; j++) { - if (skipEmptyLines && preview.data[j].length === 1 && preview.data[j][0].length === 0) { - emptyLinesCount++ - continue + if (skipEmptyLines && testEmptyLine(preview.data[j])) + { + emptyLinesCount++; + continue; } var fieldCount = preview.data[j].length; avgFieldCount += fieldCount; @@ -1094,12 +1273,15 @@ return { successful: !!bestDelim, bestDelimiter: bestDelim - } + }; } - function guessLineEndings(input) + function guessLineEndings(input, quoteChar) { - input = input.substr(0, 1024*1024); // max length 1 MB + input = input.substr(0, 1024 * 1024); // max length 1 MB + // Replace all the text inside quotes + var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm'); + input = input.replace(re, ''); var r = input.split('\r'); @@ -1120,12 +1302,6 @@ return numWithN >= r.length / 2 ? '\r\n' : '\r'; } - function tryParseFloat(val) - { - var isNumber = FLOAT.test(val); - return isNumber ? parseFloat(val) : val; - } - function addError(type, code, msg, row) { _results.errors.push({ @@ -1137,9 +1313,11 @@ } } - - - + /** https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions */ + function escapeRegExp(string) + { + return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string + } /** The core parser implements speedy and correct CSV parsing */ function Parser(config) @@ -1152,11 +1330,16 @@ var step = config.step; var preview = config.preview; var fastMode = config.fastMode; + var quoteChar; /** Allows for no quoteChar by setting quoteChar to undefined in config */ - if (config.quoteChar === undefined){ - var quoteChar = '"'; + if (config.quoteChar === undefined) { + quoteChar = '"'; } else { - var quoteChar = config.quoteChar; + quoteChar = config.quoteChar; + } + var escapeChar = quoteChar; + if (config.escapeChar !== undefined) { + escapeChar = config.escapeChar; } // Delimiter must be valid @@ -1174,7 +1357,7 @@ comments = false; // Newline must be valid: \r, \n, or \r\n - if (newline != '\n' && newline != '\r' && newline != '\r\n') + if (newline !== '\n' && newline !== '\r' && newline !== '\r\n') newline = '\n'; // We're gonna need these at the Parser scope @@ -1207,7 +1390,7 @@ var rows = input.split(newline); for (var i = 0; i < rows.length; i++) { - var row = rows[i]; + row = rows[i]; cursor += row.length; if (i !== rows.length - 1) cursor += newline.length; @@ -1236,7 +1419,8 @@ var nextDelim = input.indexOf(delim, cursor); var nextNewline = input.indexOf(newline, cursor); - var quoteCharRegex = new RegExp(quoteChar+quoteChar, 'g'); + var quoteCharRegex = new RegExp(escapeChar.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') + quoteChar, 'g'); + var quoteSearch; // Parser loop for (;;) @@ -1245,7 +1429,7 @@ if (input[cursor] === quoteChar) { // Start our search for the closing quote where the cursor is - var quoteSearch = cursor; + quoteSearch = cursor; // Skip the opening quote cursor++; @@ -1253,7 +1437,7 @@ for (;;) { // Find closing quote - var quoteSearch = input.indexOf(quoteChar, quoteSearch+1); + quoteSearch = input.indexOf(quoteChar, quoteSearch + 1); //No other quotes are found - no other delimiters if (quoteSearch === -1) @@ -1272,34 +1456,47 @@ } // Closing quote at EOF - if (quoteSearch === inputLen-1) + if (quoteSearch === inputLen - 1) { var value = input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar); return finish(value); } // If this quote is escaped, it's part of the data; skip it - if (input[quoteSearch+1] === quoteChar) + // If the quote character is the escape character, then check if the next character is the escape character + if (quoteChar === escapeChar && input[quoteSearch + 1] === escapeChar) { quoteSearch++; continue; } - // Closing quote followed by delimiter - if (input[quoteSearch+1] === delim) + // If the quote character is not the escape character, then check if the previous character was the escape character + if (quoteChar !== escapeChar && quoteSearch !== 0 && input[quoteSearch - 1] === escapeChar) + { + continue; + } + + // Check up to nextDelim or nextNewline, whichever is closest + var checkUpTo = nextNewline === -1 ? nextDelim : Math.min(nextDelim, nextNewline); + var spacesBetweenQuoteAndDelimiter = extraSpaces(checkUpTo); + + // Closing quote followed by delimiter or 'unnecessary spaces + delimiter' + if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter] === delim) { row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); - cursor = quoteSearch + 1 + delimLen; + cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen; nextDelim = input.indexOf(delim, cursor); nextNewline = input.indexOf(newline, cursor); break; } - // Closing quote followed by newline - if (input.substr(quoteSearch+1, newlineLen) === newline) + var spacesBetweenQuoteAndNewLine = extraSpaces(nextNewline); + + // Closing quote followed by newline or 'unnecessary spaces + newLine' + if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndNewLine, newlineLen) === newline) { row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); - saveRow(quoteSearch + 1 + newlineLen); + saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen); nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field if (stepIsFunction) @@ -1385,6 +1582,21 @@ lastCursor = cursor; } + /** + * checks if there are extra spaces after closing quote and given index without any text + * if Yes, returns the number of spaces + */ + function extraSpaces(index) { + var spaceLength = 0; + if (index !== -1) { + var textBetweenClosingQuoteAndIndex = input.substring(quoteSearch + 1, index); + if (textBetweenClosingQuoteAndIndex && textBetweenClosingQuoteAndIndex.trim() === '') { + spaceLength = textBetweenClosingQuoteAndIndex.length; + } + } + return spaceLength; + } + /** * Appends the remaining input from cursor to the end into * row, saves the row, calls step, and returns the results. @@ -1437,7 +1649,8 @@ function doStep() { step(returnable()); - data = [], errors = []; + data = []; + errors = []; } }; @@ -1572,9 +1785,9 @@ /** Makes a deep copy of an array or object (mostly) */ function copy(obj) { - if (typeof obj !== 'object') + if (typeof obj !== 'object' || obj === null) return obj; - var cpy = obj instanceof Array ? [] : {}; + var cpy = Array.isArray(obj) ? [] : {}; for (var key in obj) cpy[key] = copy(obj[key]); return cpy; diff --git a/package.json b/package.json index 2adb9e1..d83361f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "papaparse", - "version": "4.6.0", + "version": "5.0.0-beta.0", "description": "Fast and powerful CSV parser for the browser that supports web workers and streaming large files. Converts CSV to JSON and JSON to CSV.", "keywords": [ "csv", @@ -35,23 +35,23 @@ }, "license": "MIT", "main": "papaparse.js", + "browser": "papaparse.min.js", "devDependencies": { "chai": "^4.2.0", "connect": "^3.3.3", "eslint": "^4.19.1", "grunt": "^1.0.2", "grunt-contrib-uglify": "^3.3.0", - "mocha": "^3.5.0", - "mocha-phantomjs": "^4.1.0", + "mocha": "^5.2.0", + "mocha-headless-chrome": "^2.0.1", "open": "0.0.5", - "phantomjs-prebuilt": "^2.1.16", "serve-static": "^1.7.1" }, "scripts": { "lint": "eslint --no-ignore papaparse.js Gruntfile.js .eslintrc.js 'tests/**/*.js'", "test-browser": "node tests/test.js", - "test-phantomjs": "node tests/test.js --phantomjs", + "test-mocha-headless-chrome": "node tests/test.js --mocha-headless-chrome", "test-node": "mocha tests/node-tests.js tests/test-cases.js", - "test": "npm run lint && npm run test-node && npm run test-phantomjs" + "test": "npm run lint && npm run test-node && npm run test-mocha-headless-chrome" } } diff --git a/papaparse.js b/papaparse.js index 059ccbb..7febbaf 100755 --- a/papaparse.js +++ b/papaparse.js @@ -1,9 +1,10 @@ -/*@license - Papa Parse - v4.6.0 - https://github.com/mholt/PapaParse - License: MIT +/* @license +Papa Parse +v5.0.0-beta.0 +https://github.com/mholt/PapaParse +License: MIT */ + (function(root, factory) { /* globals define */ @@ -24,7 +25,10 @@ // Browser globals (root is window) root.Papa = factory(); } -}(this, function() + // in strict mode we cannot access arguments.callee, so we need a named reference to + // stringify the factory method for the blob worker + // eslint-disable-next-line func-name +}(this, function moduleFactory() { 'use strict'; @@ -42,9 +46,14 @@ })(); + function getWorkerBlob() { + var URL = global.URL || global.webkitURL || null; + var code = moduleFactory.toString(); + return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(['(', code, ')();'], {type: 'text/javascript'}))); + } + var IS_WORKER = !global.document && !!global.postMessage, - IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search), - LOADED_SYNC = false, AUTO_SCRIPT_PATH; + IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol); var workers = {}, workerIdCounter = 0; var Papa = {}; @@ -57,7 +66,6 @@ Papa.BYTE_ORDER_MARK = '\ufeff'; Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker; - Papa.SCRIPT_PATH = null; // Must be set by your code if you use workers and this lib is loaded asynchronously Papa.NODE_STREAM_INPUT = 1; // Configurable chunk sizes for local and remote files, respectively @@ -72,7 +80,9 @@ Papa.FileStreamer = FileStreamer; Papa.StringStreamer = StringStreamer; Papa.ReadableStreamStreamer = ReadableStreamStreamer; - Papa.DuplexStreamStreamer = DuplexStreamStreamer; + if (typeof PAPA_BROWSER_CONTEXT === 'undefined') { + Papa.DuplexStreamStreamer = DuplexStreamStreamer; + } if (global.jQuery) { @@ -173,23 +183,6 @@ { global.onmessage = workerThreadReceivedMessage; } - else if (Papa.WORKERS_SUPPORTED) - { - AUTO_SCRIPT_PATH = getScriptPath(); - - // Check if the script was loaded synchronously - if (!document.body) - { - // Body doesn't exist yet, must be synchronous - LOADED_SYNC = true; - } - else - { - document.addEventListener('DOMContentLoaded', function() { - LOADED_SYNC = true; - }, true); - } - } @@ -232,7 +225,7 @@ } var streamer = null; - if (_input === Papa.NODE_STREAM_INPUT) + if (_input === Papa.NODE_STREAM_INPUT && typeof PAPA_BROWSER_CONTEXT === 'undefined') { // create a node Duplex stream for use // with .pipe @@ -285,14 +278,14 @@ unpackConfig(); - var quoteCharRegex = new RegExp(_quoteChar, 'g'); + var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g'); if (typeof _input === 'string') _input = JSON.parse(_input); - if (_input instanceof Array) + if (Array.isArray(_input)) { - if (!_input.length || _input[0] instanceof Array) + if (!_input.length || Array.isArray(_input[0])) return serialize(null, _input, _skipEmptyLines); else if (typeof _input[0] === 'object') return serialize(objectKeys(_input[0]), _input, _skipEmptyLines); @@ -302,17 +295,17 @@ if (typeof _input.data === 'string') _input.data = JSON.parse(_input.data); - if (_input.data instanceof Array) + if (Array.isArray(_input.data)) { if (!_input.fields) _input.fields = _input.meta && _input.meta.fields; if (!_input.fields) - _input.fields = _input.data[0] instanceof Array + _input.fields = Array.isArray(_input.data[0]) ? _input.fields : objectKeys(_input.data[0]); - if (!(_input.data[0] instanceof Array) && typeof _input.data[0] !== 'object') + if (!(Array.isArray(_input.data[0])) && typeof _input.data[0] !== 'object') _input.data = [_input.data]; // handles input like [1,2,3] or ['asdf'] } @@ -320,7 +313,7 @@ } // Default (any valid paths should return before this) - throw 'exception: Unable to serialize unrecognized input'; + throw new Error('Unable to serialize unrecognized input'); function unpackConfig() @@ -335,7 +328,7 @@ } if (typeof _config.quotes === 'boolean' - || _config.quotes instanceof Array) + || Array.isArray(_config.quotes)) _quotes = _config.quotes; if (typeof _config.skipEmptyLines === 'boolean' @@ -374,8 +367,8 @@ if (typeof data === 'string') data = JSON.parse(data); - var hasHeader = fields instanceof Array && fields.length > 0; - var dataKeyedByField = !(data[0] instanceof Array); + var hasHeader = Array.isArray(fields) && fields.length > 0; + var dataKeyedByField = !(Array.isArray(data[0])); // If there a header row, write it first if (hasHeader && _writeHeader) @@ -394,19 +387,34 @@ for (var row = 0; row < data.length; row++) { var maxCol = hasHeader ? fields.length : data[row].length; - var r = hasHeader ? fields : data[row]; - if (skipEmptyLines !== 'greedy' || r.join('').trim() !== '') + var emptyLine = false; + var nullLine = hasHeader ? Object.keys(data[row]).length === 0 : data[row].length === 0; + if (skipEmptyLines && !hasHeader) + { + emptyLine = skipEmptyLines === 'greedy' ? data[row].join('').trim() === '' : data[row].length === 1 && data[row][0].length === 0; + } + if (skipEmptyLines === 'greedy' && hasHeader) { + var line = []; + for (var c = 0; c < maxCol; c++) { + var cx = dataKeyedByField ? fields[c] : c; + line.push(data[row][cx]); + } + emptyLine = line.join('').trim() === ''; + } + if (!emptyLine) { for (var col = 0; col < maxCol; col++) { - if (col > 0) + if (col > 0 && !nullLine) csv += _delimiter; var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; csv += safe(data[row][colIdx], col); } - if (row < data.length - 1 && (!skipEmptyLines || maxCol > 0)) + if (row < data.length - 1 && (!skipEmptyLines || (maxCol > 0 && !nullLine))) + { csv += _newline; + } } } return csv; @@ -424,7 +432,7 @@ str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar); var needsQuotes = (typeof _quotes === 'boolean' && _quotes) - || (_quotes instanceof Array && _quotes[col]) + || (Array.isArray(_quotes) && _quotes[col]) || hasAny(str, Papa.BAD_DELIMITERS) || str.indexOf(_delimiter) > -1 || str.charAt(0) === ' ' @@ -625,7 +633,6 @@ { var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end); - xhr.setRequestHeader('If-None-Match', 'webkit-no-cache'); // https://bugs.webkit.org/show_bug.cgi?id=82672 } try { @@ -872,13 +879,11 @@ this._onCsvData = function(results) { var data = results.data; - for (var i = 0; i < data.length; i++) { - if (!stream.push(data[i]) && !this._handle.paused()) { - // the writeable consumer buffer has filled up - // so we need to pause until more items - // can be processed - this._handle.pause(); - } + if (!stream.push(data) && !this._handle.paused()) { + // the writeable consumer buffer has filled up + // so we need to pause until more items + // can be processed + this._handle.pause(); } }; @@ -958,8 +963,10 @@ }); stream.once('finish', bindFunction(this._onWriteComplete, this)); } - DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); - DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer; + if (typeof PAPA_BROWSER_CONTEXT === 'undefined') { + DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); + DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer; + } // Use one ParserHandle per entire CSV file or string @@ -1123,8 +1130,8 @@ { var header = _results.data[i][j]; - if (_config.trimHeaders) { - header = header.trim(); + if (isFunction(_config.transformHeader)) { + header = _config.transformHeader(header); } _fields.push(header); @@ -1340,7 +1347,7 @@ // Comment character must be valid if (comments === delim) - throw 'Comment character same as delimiter'; + throw new Error('Comment character same as delimiter'); else if (comments === true) comments = '#'; else if (typeof comments !== 'string' @@ -1359,7 +1366,7 @@ { // For some reason, in Chrome, this speeds things up (!?) if (typeof input !== 'string') - throw 'Input must be a string'; + throw new Error('Input must be a string'); // We don't need to compute some of these every time parse() is called, // but having them in a more local scope seems to perform better @@ -1410,7 +1417,7 @@ var nextDelim = input.indexOf(delim, cursor); var nextNewline = input.indexOf(newline, cursor); - var quoteCharRegex = new RegExp(escapeChar.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&') + quoteChar, 'g'); + var quoteCharRegex = new RegExp(escapeRegExp(escapeChar) + escapeRegExp(quoteChar), 'g'); var quoteSearch; // Parser loop @@ -1621,10 +1628,11 @@ } /** Returns an object with the results, errors, and meta. */ - function returnable(stopped) + function returnable(stopped, step) { + var isStep = step || false; return { - data: data, + data: isStep ? data[0] : data, errors: errors, meta: { delimiter: delim, @@ -1639,7 +1647,7 @@ /** Executes the user's step function and resets data & errors. */ function doStep() { - step(returnable()); + step(returnable(undefined, true)); data = []; errors = []; } @@ -1659,26 +1667,12 @@ } - // If you need to load Papa Parse asynchronously and you also need worker threads, hard-code - // the script path here. See: https://github.com/mholt/PapaParse/issues/87#issuecomment-57885358 - function getScriptPath() - { - var scripts = document.getElementsByTagName('script'); - return scripts.length ? scripts[scripts.length - 1].src : ''; - } - function newWorker() { if (!Papa.WORKERS_SUPPORTED) return false; - if (!LOADED_SYNC && Papa.SCRIPT_PATH === null) - throw new Error( - 'Script path cannot be determined automatically when Papa Parse is loaded asynchronously. ' + - 'You need to set Papa.SCRIPT_PATH manually.' - ); - var workerUrl = Papa.SCRIPT_PATH || AUTO_SCRIPT_PATH; - // Append 'papaworker' to the search string to tell papaparse that this is our worker. - workerUrl += (workerUrl.indexOf('?') !== -1 ? '&' : '?') + 'papaworker'; + + var workerUrl = getWorkerBlob(); var w = new global.Worker(workerUrl); w.onmessage = mainThreadReceivedMessage; w.id = workerIdCounter++; @@ -1742,7 +1736,7 @@ } function notImplemented() { - throw 'Not implemented.'; + throw new Error('Not implemented.'); } /** Callback when worker thread receives a message */ @@ -1778,7 +1772,7 @@ { if (typeof obj !== 'object' || obj === null) return obj; - var cpy = obj instanceof Array ? [] : {}; + var cpy = Array.isArray(obj) ? [] : {}; for (var key in obj) cpy[key] = copy(obj[key]); return cpy; diff --git a/papaparse.min.js b/papaparse.min.js index 68fb77d..c960d68 100644 --- a/papaparse.min.js +++ b/papaparse.min.js @@ -1,7 +1,7 @@ -/*@license - Papa Parse - v4.6.0 - https://github.com/mholt/PapaParse - License: MIT +/* @license +Papa Parse +v5.0.0-beta.0 +https://github.com/mholt/PapaParse +License: MIT */ -!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof module&&"undefined"!=typeof exports?module.exports=t():e.Papa=t()}(this,function(){"use strict";var s,e,f="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==f?f:{},r=!f.document&&!!f.postMessage,o=r&&/(\?|&)papaworker(=|&|$)/.test(f.location.search),a=!1,h={},u=0,k={parse:function(e,t){var i=(t=t||{}).dynamicTyping||!1;M(i)&&(t.dynamicTypingFunction=i,i={});if(t.dynamicTyping=i,t.transform=!!M(t.transform)&&t.transform,t.worker&&k.WORKERS_SUPPORTED){var n=function(){if(!k.WORKERS_SUPPORTED)return!1;if(!a&&null===k.SCRIPT_PATH)throw new Error("Script path cannot be determined automatically when Papa Parse is loaded asynchronously. You need to set Papa.SCRIPT_PATH manually.");var e=k.SCRIPT_PATH||s;e+=(-1!==e.indexOf("?")?"&":"?")+"papaworker";var t=new f.Worker(e);return t.onmessage=v,t.id=u++,h[t.id]=t}();return n.userStep=t.step,n.userChunk=t.chunk,n.userComplete=t.complete,n.userError=t.error,t.step=M(t.step),t.chunk=M(t.chunk),t.complete=M(t.complete),t.error=M(t.error),delete t.worker,void n.postMessage({input:e,config:t,workerId:n.id})}var r=null;{if(e===k.NODE_STREAM_INPUT)return(r=new g(t)).getStream();"string"==typeof e?r=t.download?new l(t):new _(t):!0===e.readable&&M(e.read)&&M(e.on)?r=new m(t):(f.File&&e instanceof File||e instanceof Object)&&(r=new p(t))}return r.stream(e)},unparse:function(e,t){var n=!1,f=!0,d=",",c="\r\n",r='"';!function(){if("object"!=typeof t)return;"string"!=typeof t.delimiter||k.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(d=t.delimiter);("boolean"==typeof t.quotes||t.quotes instanceof Array)&&(n=t.quotes);"string"==typeof t.newline&&(c=t.newline);"string"==typeof t.quoteChar&&(r=t.quoteChar);"boolean"==typeof t.header&&(f=t.header)}();var s=new RegExp(r,"g");"string"==typeof e&&(e=JSON.parse(e));if(e instanceof Array){if(!e.length||e[0]instanceof Array)return a(null,e);if("object"==typeof e[0])return a(i(e[0]),e)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),e.data instanceof Array&&(e.fields||(e.fields=e.meta&&e.meta.fields),e.fields||(e.fields=e.data[0]instanceof Array?e.fields:i(e.data[0])),e.data[0]instanceof Array||"object"==typeof e.data[0]||(e.data=[e.data])),a(e.fields||[],e.data||[]);throw"exception: Unable to serialize unrecognized input";function i(e){if("object"!=typeof e)return[];var t=[];for(var i in e)t.push(i);return t}function a(e,t){var i="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=e instanceof Array&&0=this._config.preview;if(o)f.postMessage({results:r,workerId:k.WORKER_ID,finished:a});else if(M(this._config.chunk)&&!t){if(this._config.chunk(r,this._handle),this._handle.paused()||this._handle.aborted())return;r=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(r.data),this._completeResults.errors=this._completeResults.errors.concat(r.errors),this._completeResults.meta=r.meta),this._completed||!a||!M(this._config.complete)||r&&r.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||r&&r.meta.paused||this._nextChunk(),r}},this._sendError=function(e){M(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:k.WORKER_ID,error:e,finished:!1})}}function l(e){var n;(e=e||{}).chunkSize||(e.chunkSize=k.RemoteChunkSize),c.call(this,e),this._nextChunk=r?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(n=new XMLHttpRequest,this._config.withCredentials&&(n.withCredentials=this._config.withCredentials),r||(n.onload=R(this._chunkLoaded,this),n.onerror=R(this._chunkError,this)),n.open("GET",this._input,!r),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)n.setRequestHeader(t,e[t])}if(this._config.chunkSize){var i=this._start+this._config.chunkSize-1;n.setRequestHeader("Range","bytes="+this._start+"-"+i),n.setRequestHeader("If-None-Match","webkit-no-cache")}try{n.send()}catch(e){this._chunkError(e.message)}r&&0===n.status?this._chunkError():this._start+=this._config.chunkSize}},this._chunkLoaded=function(){4===n.readyState&&(n.status<200||400<=n.status?this._chunkError():(this._finished=!this._config.chunkSize||this._start>function(e){var t=e.getResponseHeader("Content-Range");if(null===t)return-1;return parseInt(t.substr(t.lastIndexOf("/")+1))}(n),this.parseChunk(n.responseText)))},this._chunkError=function(e){var t=n.statusText||e;this._sendError(new Error(t))}}function p(e){var n,r;(e=e||{}).chunkSize||(e.chunkSize=k.LocalChunkSize),c.call(this,e);var s="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,r=e.slice||e.webkitSlice||e.mozSlice,s?((n=new FileReader).onload=R(this._chunkLoaded,this),n.onerror=R(this._chunkError,this)):n=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(n.error)}}function _(e){var i;c.call(this,e=e||{}),this.stream=function(e){return i=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?i.substr(0,e):i;return i=e?i.substr(e):"",this._finished=!i,this.parseChunk(t)}}}function m(e){c.call(this,e=e||{});var t=[],i=!0,n=!1;this.pause=function(){c.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){c.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){n&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):i=!0},this._streamData=R(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),i&&(i=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=R(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=R(function(){this._streamCleanUp(),n=!0,this._streamData("")},this),this._streamCleanUp=R(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function g(e){var t=require("stream").Duplex,i=E(e),n=!0,r=!1,s=[],a=null;this._onCsvData=function(e){for(var t=e.data,i=0;im.preview?o.abort():c(d,t)}}}function g(e){return"greedy"===m.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function l(){if(d&&h&&(v("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+k.DefaultDelimiter+"'"),h=!1),m.skipEmptyLines)for(var e=0;e=f.length?"__parsed_extra":f[t]),m.transform&&(r=m.transform(r,n)),r=_(n,r),"__parsed_extra"===n?(i[n]=i[n]||[],i[n].push(r)):i[n]=r}d.data[e]=i,m.header&&(t>f.length?v("FieldMismatch","TooManyFields","Too many fields: expected "+f.length+" fields but parsed "+t,s+e):t=n.length/2?"\r\n":"\r"}(e,n)),h=!1,m.delimiter)M(m.delimiter)&&(m.delimiter=m.delimiter(e),d.meta.delimiter=m.delimiter);else{var r=function(e,t,i,n){for(var r,s,a,o=[",","\t","|",";",k.RECORD_SEP,k.UNIT_SEP],h=0;h=D)return E(!0)}else for(p=A,A++;;){if(-1===(p=n.indexOf(S,p+1)))return i||u.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:h.length,index:A}),b();if(p===r-1)return b(n.substring(A,p).replace(g,S));if(S!==L||n[p+1]!==L){if(S===L||0===p||n[p-1]!==L){var v=C(-1===m?_:Math.min(_,m));if(n[p+1+v]===x){f.push(n.substring(A,p).replace(g,S)),A=p+1+v+e,_=n.indexOf(x,A),m=n.indexOf(T,A);break}var k=C(m);if(n.substr(p+1+k,s)===T){if(f.push(n.substring(A,p).replace(g,S)),w(p+1+k+s),_=n.indexOf(x,A),o&&(R(),F))return E();if(D&&h.length>=D)return E(!0);break}u.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:h.length,index:A}),p++}}else p++}return b();function y(e){h.push(e),d=A}function C(e){var t=0;if(-1!==e){var i=n.substring(p+1,e);i&&""===i.trim()&&(t=i.length)}return t}function b(e){return i||(void 0===e&&(e=n.substr(A)),f.push(e),A=r,y(f),o&&R()),E()}function w(e){A=e,y(f),f=[],m=n.indexOf(T,A)}function E(e){return{data:h,errors:u,meta:{delimiter:x,linebreak:T,aborted:F,truncated:!!e,cursor:d+(t||0)}}}function R(){I(E()),h=[],u=[]}},this.abort=function(){F=!0},this.getCharIndex=function(){return A}}function v(e){var t=e.data,i=h[t.workerId],n=!1;if(t.error)i.userError(t.error,t.file);else if(t.results&&t.results.data){var r={abort:function(){n=!0,b(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},pause:w,resume:w};if(M(i.userStep)){for(var s=0;s=this._config.preview;if(o)f.postMessage({results:n,workerId:k.WORKER_ID,finished:a});else if(M(this._config.chunk)&&!t){if(this._config.chunk(n,this._handle),this._handle.paused()||this._handle.aborted())return;n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),this._completed||!a||!M(this._config.complete)||n&&n.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),a||n&&n.meta.paused||this._nextChunk(),n}},this._sendError=function(e){M(this._config.error)?this._config.error(e):o&&this._config.error&&f.postMessage({workerId:k.WORKER_ID,error:e,finished:!1})}}function l(e){var i;(e=e||{}).chunkSize||(e.chunkSize=k.RemoteChunkSize),u.call(this,e),this._nextChunk=n?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(i=new XMLHttpRequest,this._config.withCredentials&&(i.withCredentials=this._config.withCredentials),n||(i.onload=w(this._chunkLoaded,this),i.onerror=w(this._chunkError,this)),i.open("GET",this._input,!n),this._config.downloadRequestHeaders){var e=this._config.downloadRequestHeaders;for(var t in e)i.setRequestHeader(t,e[t])}if(this._config.chunkSize){var r=this._start+this._config.chunkSize-1;i.setRequestHeader("Range","bytes="+this._start+"-"+r)}try{i.send()}catch(e){this._chunkError(e.message)}n&&0===i.status?this._chunkError():this._start+=this._config.chunkSize}},this._chunkLoaded=function(){4===i.readyState&&(i.status<200||400<=i.status?this._chunkError():(this._finished=!this._config.chunkSize||this._start>function(e){var t=e.getResponseHeader("Content-Range");if(null===t)return-1;return parseInt(t.substr(t.lastIndexOf("/")+1))}(i),this.parseChunk(i.responseText)))},this._chunkError=function(e){var t=i.statusText||e;this._sendError(new Error(t))}}function c(e){var i,n;(e=e||{}).chunkSize||(e.chunkSize=k.LocalChunkSize),u.call(this,e);var s="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,s?((i=new FileReader).onload=w(this._chunkLoaded,this),i.onerror=w(this._chunkError,this)):i=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(i.error)}}function p(e){var r;u.call(this,e=e||{}),this.stream=function(e){return r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function _(e){u.call(this,e=e||{});var t=[],r=!0,i=!1;this.pause=function(){u.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){u.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):r=!0},this._streamData=w(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),r&&(r=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=w(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=w(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=w(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function r(g){var a,o,h,i=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,n=/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/,t=this,r=0,s=0,u=!1,e=!1,f=[],d={data:[],errors:[],meta:{}};if(M(g.step)){var l=g.step;g.step=function(e){if(d=e,p())c();else{if(c(),0===d.data.length)return;r+=e.data.length,g.preview&&r>g.preview?o.abort():l(d,t)}}}function m(e){return"greedy"===g.skipEmptyLines?""===e.join("").trim():1===e.length&&0===e[0].length}function c(){if(d&&h&&(v("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+k.DefaultDelimiter+"'"),h=!1),g.skipEmptyLines)for(var e=0;e=f.length?"__parsed_extra":f[t]),g.transform&&(n=g.transform(n,i)),n=_(i,n),"__parsed_extra"===i?(r[i]=r[i]||[],r[i].push(n)):r[i]=n}d.data[e]=r,g.header&&(t>f.length?v("FieldMismatch","TooManyFields","Too many fields: expected "+f.length+" fields but parsed "+t,s+e):t=i.length/2?"\r\n":"\r"}(e,i)),h=!1,g.delimiter)M(g.delimiter)&&(g.delimiter=g.delimiter(e),d.meta.delimiter=g.delimiter);else{var n=function(e,t,r,i){for(var n,s,a,o=[",","\t","|",";",k.RECORD_SEP,k.UNIT_SEP],h=0;h=T)return C(!0)}else for(p=F,F++;;){if(-1===(p=i.indexOf(S,p+1)))return t||u.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:h.length,index:F}),E();if(p===n-1)return E(i.substring(F,p).replace(m,S));if(S!==A||i[p+1]!==A){if(S===A||0===p||i[p-1]!==A){var v=b(-1===g?_:Math.min(_,g));if(i[p+1+v]===O){f.push(i.substring(F,p).replace(m,S)),F=p+1+v+e,_=i.indexOf(O,F),g=i.indexOf(x,F);break}var y=b(g);if(i.substr(p+1+y,s)===x){if(f.push(i.substring(F,p).replace(m,S)),w(p+1+y+s),_=i.indexOf(O,F),o&&(R(),z))return C();if(T&&h.length>=T)return C(!0);break}u.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:h.length,index:F}),p++}}else p++}return E();function k(e){h.push(e),d=F}function b(e){var t=0;if(-1!==e){var r=i.substring(p+1,e);r&&""===r.trim()&&(t=r.length)}return t}function E(e){return t||(void 0===e&&(e=i.substr(F)),f.push(e),F=n,k(f),o&&R()),C()}function w(e){F=e,k(f),f=[],g=i.indexOf(x,F)}function C(e,t){return{data:t||!1?h[0]:h,errors:u,meta:{delimiter:O,linebreak:x,aborted:z,truncated:!!e,cursor:d+(r||0)}}}function R(){D(C(void 0,!0)),h=[],u=[]}},this.abort=function(){z=!0},this.getCharIndex=function(){return F}}function g(e){var t=e.data,r=a[t.workerId],i=!1;if(t.error)r.userError(t.error,t.file);else if(t.results&&t.results.data){var n={abort:function(){i=!0,m(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},pause:v,resume:v};if(M(r.userStep)){for(var s=0;s - +