|
|
@ -1,19 +1,10 @@ |
|
|
|
/* @license |
|
|
|
/* @license |
|
|
|
Papa Parse |
|
|
|
Papa Parse |
|
|
|
v4.6.1 |
|
|
|
v5.0.0 |
|
|
|
https://github.com/mholt/PapaParse
|
|
|
|
https://github.com/mholt/PapaParse
|
|
|
|
License: MIT |
|
|
|
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) |
|
|
|
(function(root, factory) |
|
|
|
{ |
|
|
|
{ |
|
|
|
/* globals define */ |
|
|
|
/* globals define */ |
|
|
@ -34,7 +25,10 @@ if (!Array.isArray) |
|
|
|
// Browser globals (root is window)
|
|
|
|
// Browser globals (root is window)
|
|
|
|
root.Papa = factory(); |
|
|
|
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'; |
|
|
|
'use strict'; |
|
|
|
|
|
|
|
|
|
|
@ -51,9 +45,15 @@ if (!Array.isArray) |
|
|
|
return {}; |
|
|
|
return {}; |
|
|
|
})(); |
|
|
|
})(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
|
var IS_WORKER = !global.document && !!global.postMessage, |
|
|
|
IS_PAPA_WORKER = IS_WORKER && /(\?|&)papaworker(=|&|$)/.test(global.location.search), |
|
|
|
IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol); |
|
|
|
LOADED_SYNC = false, AUTO_SCRIPT_PATH; |
|
|
|
|
|
|
|
var workers = {}, workerIdCounter = 0; |
|
|
|
var workers = {}, workerIdCounter = 0; |
|
|
|
|
|
|
|
|
|
|
|
var Papa = {}; |
|
|
|
var Papa = {}; |
|
|
@ -66,7 +66,6 @@ if (!Array.isArray) |
|
|
|
Papa.BYTE_ORDER_MARK = '\ufeff'; |
|
|
|
Papa.BYTE_ORDER_MARK = '\ufeff'; |
|
|
|
Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; |
|
|
|
Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK]; |
|
|
|
Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker; |
|
|
|
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; |
|
|
|
Papa.NODE_STREAM_INPUT = 1; |
|
|
|
|
|
|
|
|
|
|
|
// Configurable chunk sizes for local and remote files, respectively
|
|
|
|
// Configurable chunk sizes for local and remote files, respectively
|
|
|
@ -81,7 +80,9 @@ if (!Array.isArray) |
|
|
|
Papa.FileStreamer = FileStreamer; |
|
|
|
Papa.FileStreamer = FileStreamer; |
|
|
|
Papa.StringStreamer = StringStreamer; |
|
|
|
Papa.StringStreamer = StringStreamer; |
|
|
|
Papa.ReadableStreamStreamer = ReadableStreamStreamer; |
|
|
|
Papa.ReadableStreamStreamer = ReadableStreamStreamer; |
|
|
|
Papa.DuplexStreamStreamer = DuplexStreamStreamer; |
|
|
|
if (typeof PAPA_BROWSER_CONTEXT === 'undefined') { |
|
|
|
|
|
|
|
Papa.DuplexStreamStreamer = DuplexStreamStreamer; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (global.jQuery) |
|
|
|
if (global.jQuery) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -182,23 +183,6 @@ if (!Array.isArray) |
|
|
|
{ |
|
|
|
{ |
|
|
|
global.onmessage = workerThreadReceivedMessage; |
|
|
|
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); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -241,7 +225,7 @@ if (!Array.isArray) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var streamer = null; |
|
|
|
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
|
|
|
|
// create a node Duplex stream for use
|
|
|
|
// with .pipe
|
|
|
|
// with .pipe
|
|
|
@ -289,12 +273,18 @@ if (!Array.isArray) |
|
|
|
/** quote character */ |
|
|
|
/** quote character */ |
|
|
|
var _quoteChar = '"'; |
|
|
|
var _quoteChar = '"'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** escaped quote character, either "" or <config.escapeChar>" */ |
|
|
|
|
|
|
|
var _escapedQuote = _quoteChar + _quoteChar; |
|
|
|
|
|
|
|
|
|
|
|
/** whether to skip empty lines */ |
|
|
|
/** whether to skip empty lines */ |
|
|
|
var _skipEmptyLines = false; |
|
|
|
var _skipEmptyLines = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** the columns (keys) we expect when we unparse objects */ |
|
|
|
|
|
|
|
var _columns = null; |
|
|
|
|
|
|
|
|
|
|
|
unpackConfig(); |
|
|
|
unpackConfig(); |
|
|
|
|
|
|
|
|
|
|
|
var quoteCharRegex = new RegExp(_quoteChar, 'g'); |
|
|
|
var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g'); |
|
|
|
|
|
|
|
|
|
|
|
if (typeof _input === 'string') |
|
|
|
if (typeof _input === 'string') |
|
|
|
_input = JSON.parse(_input); |
|
|
|
_input = JSON.parse(_input); |
|
|
@ -304,7 +294,7 @@ if (!Array.isArray) |
|
|
|
if (!_input.length || Array.isArray(_input[0])) |
|
|
|
if (!_input.length || Array.isArray(_input[0])) |
|
|
|
return serialize(null, _input, _skipEmptyLines); |
|
|
|
return serialize(null, _input, _skipEmptyLines); |
|
|
|
else if (typeof _input[0] === 'object') |
|
|
|
else if (typeof _input[0] === 'object') |
|
|
|
return serialize(objectKeys(_input[0]), _input, _skipEmptyLines); |
|
|
|
return serialize(_columns || objectKeys(_input[0]), _input, _skipEmptyLines); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (typeof _input === 'object') |
|
|
|
else if (typeof _input === 'object') |
|
|
|
{ |
|
|
|
{ |
|
|
@ -329,7 +319,7 @@ if (!Array.isArray) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Default (any valid paths should return before this)
|
|
|
|
// 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() |
|
|
|
function unpackConfig() |
|
|
@ -359,6 +349,17 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
if (typeof _config.header === 'boolean') |
|
|
|
if (typeof _config.header === 'boolean') |
|
|
|
_writeHeader = _config.header; |
|
|
|
_writeHeader = _config.header; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(_config.columns)) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (_config.columns.length === 0) throw new Error('Option columns is empty'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_columns = _config.columns; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (_config.escapeChar !== undefined) { |
|
|
|
|
|
|
|
_escapedQuote = _config.escapeChar + _quoteChar; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -403,19 +404,34 @@ if (!Array.isArray) |
|
|
|
for (var row = 0; row < data.length; row++) |
|
|
|
for (var row = 0; row < data.length; row++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var maxCol = hasHeader ? fields.length : data[row].length; |
|
|
|
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++) |
|
|
|
for (var col = 0; col < maxCol; col++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (col > 0) |
|
|
|
if (col > 0 && !nullLine) |
|
|
|
csv += _delimiter; |
|
|
|
csv += _delimiter; |
|
|
|
var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; |
|
|
|
var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; |
|
|
|
csv += safe(data[row][colIdx], 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; |
|
|
|
csv += _newline; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return csv; |
|
|
|
return csv; |
|
|
@ -430,7 +446,7 @@ if (!Array.isArray) |
|
|
|
if (str.constructor === Date) |
|
|
|
if (str.constructor === Date) |
|
|
|
return JSON.stringify(str).slice(1, 25); |
|
|
|
return JSON.stringify(str).slice(1, 25); |
|
|
|
|
|
|
|
|
|
|
|
str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar); |
|
|
|
str = str.toString().replace(quoteCharRegex, _escapedQuote); |
|
|
|
|
|
|
|
|
|
|
|
var needsQuotes = (typeof _quotes === 'boolean' && _quotes) |
|
|
|
var needsQuotes = (typeof _quotes === 'boolean' && _quotes) |
|
|
|
|| (Array.isArray(_quotes) && _quotes[col]) |
|
|
|
|| (Array.isArray(_quotes) && _quotes[col]) |
|
|
@ -457,6 +473,7 @@ if (!Array.isArray) |
|
|
|
this._handle = null; |
|
|
|
this._handle = null; |
|
|
|
this._finished = false; |
|
|
|
this._finished = false; |
|
|
|
this._completed = false; |
|
|
|
this._completed = false; |
|
|
|
|
|
|
|
this._halted = false; |
|
|
|
this._input = null; |
|
|
|
this._input = null; |
|
|
|
this._baseIndex = 0; |
|
|
|
this._baseIndex = 0; |
|
|
|
this._partialLine = ''; |
|
|
|
this._partialLine = ''; |
|
|
@ -481,6 +498,7 @@ if (!Array.isArray) |
|
|
|
chunk = modifiedChunk; |
|
|
|
chunk = modifiedChunk; |
|
|
|
} |
|
|
|
} |
|
|
|
this.isFirstChunk = false; |
|
|
|
this.isFirstChunk = false; |
|
|
|
|
|
|
|
this._halted = false; |
|
|
|
|
|
|
|
|
|
|
|
// Rejoin the line we likely just split in two by chunking the file
|
|
|
|
// Rejoin the line we likely just split in two by chunking the file
|
|
|
|
var aggregate = this._partialLine + chunk; |
|
|
|
var aggregate = this._partialLine + chunk; |
|
|
@ -488,8 +506,10 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished); |
|
|
|
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished); |
|
|
|
|
|
|
|
|
|
|
|
if (this._handle.paused() || this._handle.aborted()) |
|
|
|
if (this._handle.paused() || this._handle.aborted()) { |
|
|
|
|
|
|
|
this._halted = true; |
|
|
|
return; |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var lastIndex = results.meta.cursor; |
|
|
|
var lastIndex = results.meta.cursor; |
|
|
|
|
|
|
|
|
|
|
@ -515,8 +535,10 @@ if (!Array.isArray) |
|
|
|
else if (isFunction(this._config.chunk) && !isFakeChunk) |
|
|
|
else if (isFunction(this._config.chunk) && !isFakeChunk) |
|
|
|
{ |
|
|
|
{ |
|
|
|
this._config.chunk(results, this._handle); |
|
|
|
this._config.chunk(results, this._handle); |
|
|
|
if (this._handle.paused() || this._handle.aborted()) |
|
|
|
if (this._handle.paused() || this._handle.aborted()) { |
|
|
|
|
|
|
|
this._halted = true; |
|
|
|
return; |
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
results = undefined; |
|
|
|
results = undefined; |
|
|
|
this._completeResults = undefined; |
|
|
|
this._completeResults = undefined; |
|
|
|
} |
|
|
|
} |
|
|
@ -634,7 +656,6 @@ if (!Array.isArray) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive
|
|
|
|
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
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
@ -881,13 +902,11 @@ if (!Array.isArray) |
|
|
|
this._onCsvData = function(results) |
|
|
|
this._onCsvData = function(results) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var data = results.data; |
|
|
|
var data = results.data; |
|
|
|
for (var i = 0; i < data.length; i++) { |
|
|
|
if (!stream.push(data) && !this._handle.paused()) { |
|
|
|
if (!stream.push(data[i]) && !this._handle.paused()) { |
|
|
|
// the writeable consumer buffer has filled up
|
|
|
|
// the writeable consumer buffer has filled up
|
|
|
|
// so we need to pause until more items
|
|
|
|
// so we need to pause until more items
|
|
|
|
// can be processed
|
|
|
|
// can be processed
|
|
|
|
this._handle.pause(); |
|
|
|
this._handle.pause(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -967,8 +986,10 @@ if (!Array.isArray) |
|
|
|
}); |
|
|
|
}); |
|
|
|
stream.once('finish', bindFunction(this._onWriteComplete, this)); |
|
|
|
stream.once('finish', bindFunction(this._onWriteComplete, this)); |
|
|
|
} |
|
|
|
} |
|
|
|
DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); |
|
|
|
if (typeof PAPA_BROWSER_CONTEXT === 'undefined') { |
|
|
|
DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer; |
|
|
|
DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype); |
|
|
|
|
|
|
|
DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Use one ParserHandle per entire CSV file or string
|
|
|
|
// Use one ParserHandle per entire CSV file or string
|
|
|
@ -977,7 +998,6 @@ if (!Array.isArray) |
|
|
|
// One goal is to minimize the use of regular expressions...
|
|
|
|
// One goal is to minimize the use of regular expressions...
|
|
|
|
var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i; |
|
|
|
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 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 self = this; |
|
|
|
var _stepCounter = 0; // Number of times step was called (number of rows parsed)
|
|
|
|
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 _rowCounter = 0; // Number of rows that have been parsed so far
|
|
|
@ -1033,7 +1053,7 @@ if (!Array.isArray) |
|
|
|
_delimiterError = false; |
|
|
|
_delimiterError = false; |
|
|
|
if (!_config.delimiter) |
|
|
|
if (!_config.delimiter) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments); |
|
|
|
var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments, _config.delimitersToGuess); |
|
|
|
if (delimGuess.successful) |
|
|
|
if (delimGuess.successful) |
|
|
|
_config.delimiter = delimGuess.bestDelimiter; |
|
|
|
_config.delimiter = delimGuess.bestDelimiter; |
|
|
|
else |
|
|
|
else |
|
|
@ -1074,8 +1094,14 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
this.resume = function() |
|
|
|
this.resume = function() |
|
|
|
{ |
|
|
|
{ |
|
|
|
_paused = false; |
|
|
|
if(self.streamer._halted) { |
|
|
|
self.streamer.parseChunk(_input, true); |
|
|
|
_paused = false; |
|
|
|
|
|
|
|
self.streamer.parseChunk(_input, true); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Bugfix: #636 In case the processing hasn't halted yet
|
|
|
|
|
|
|
|
// wait for it to halt in order to resume
|
|
|
|
|
|
|
|
setTimeout(this.resume, 3); |
|
|
|
|
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
this.aborted = function() |
|
|
|
this.aborted = function() |
|
|
@ -1127,18 +1153,25 @@ if (!Array.isArray) |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!_results) |
|
|
|
if (!_results) |
|
|
|
return; |
|
|
|
return; |
|
|
|
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) |
|
|
|
|
|
|
|
for (var j = 0; j < _results.data[i].length; j++) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
var header = _results.data[i][j]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (_config.trimHeaders) { |
|
|
|
function addHeder(header) |
|
|
|
header = header.trim(); |
|
|
|
{ |
|
|
|
} |
|
|
|
if (isFunction(_config.transformHeader)) |
|
|
|
|
|
|
|
header = _config.transformHeader(header); |
|
|
|
|
|
|
|
|
|
|
|
_fields.push(header); |
|
|
|
_fields.push(header); |
|
|
|
} |
|
|
|
} |
|
|
|
_results.data.splice(0, 1); |
|
|
|
|
|
|
|
|
|
|
|
if (Array.isArray(_results.data[0])) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
for (var i = 0; needsHeaderRow() && i < _results.data.length; i++) |
|
|
|
|
|
|
|
_results.data[i].forEach(addHeder); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_results.data.splice(0, 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// if _results.data[0] is not an array, we are in a step where _results.data is the row.
|
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
_results.data.forEach(addHeder); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function shouldApplyDynamicTyping(field) { |
|
|
|
function shouldApplyDynamicTyping(field) { |
|
|
@ -1172,15 +1205,15 @@ if (!Array.isArray) |
|
|
|
if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform)) |
|
|
|
if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform)) |
|
|
|
return _results; |
|
|
|
return _results; |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < _results.data.length; i++) |
|
|
|
function processRow(rowSource, i) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var row = _config.header ? {} : []; |
|
|
|
var row = _config.header ? {} : []; |
|
|
|
|
|
|
|
|
|
|
|
var j; |
|
|
|
var j; |
|
|
|
for (j = 0; j < _results.data[i].length; j++) |
|
|
|
for (j = 0; j < rowSource.length; j++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var field = j; |
|
|
|
var field = j; |
|
|
|
var value = _results.data[i][j]; |
|
|
|
var value = rowSource[j]; |
|
|
|
|
|
|
|
|
|
|
|
if (_config.header) |
|
|
|
if (_config.header) |
|
|
|
field = j >= _fields.length ? '__parsed_extra' : _fields[j]; |
|
|
|
field = j >= _fields.length ? '__parsed_extra' : _fields[j]; |
|
|
@ -1199,7 +1232,6 @@ if (!Array.isArray) |
|
|
|
row[field] = value; |
|
|
|
row[field] = value; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
_results.data[i] = row; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (_config.header) |
|
|
|
if (_config.header) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -1208,23 +1240,36 @@ if (!Array.isArray) |
|
|
|
else if (j < _fields.length) |
|
|
|
else if (j < _fields.length) |
|
|
|
addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i); |
|
|
|
addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return row; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var incrementBy = 1; |
|
|
|
|
|
|
|
if (!_results.data[0] || Array.isArray(_results.data[0])) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
_results.data = _results.data.map(processRow); |
|
|
|
|
|
|
|
incrementBy = _results.data.length; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
else |
|
|
|
|
|
|
|
_results.data = processRow(_results.data, 0); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (_config.header && _results.meta) |
|
|
|
if (_config.header && _results.meta) |
|
|
|
_results.meta.fields = _fields; |
|
|
|
_results.meta.fields = _fields; |
|
|
|
|
|
|
|
|
|
|
|
_rowCounter += _results.data.length; |
|
|
|
_rowCounter += incrementBy; |
|
|
|
return _results; |
|
|
|
return _results; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function guessDelimiter(input, newline, skipEmptyLines, comments) |
|
|
|
function guessDelimiter(input, newline, skipEmptyLines, comments, delimitersToGuess) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var delimChoices = [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]; |
|
|
|
|
|
|
|
var bestDelim, bestDelta, fieldCountPrevRow; |
|
|
|
var bestDelim, bestDelta, fieldCountPrevRow; |
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < delimChoices.length; i++) |
|
|
|
delimitersToGuess = delimitersToGuess || [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < delimitersToGuess.length; i++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var delim = delimChoices[i]; |
|
|
|
var delim = delimitersToGuess[i]; |
|
|
|
var delta = 0, avgFieldCount = 0, emptyLinesCount = 0; |
|
|
|
var delta = 0, avgFieldCount = 0, emptyLinesCount = 0; |
|
|
|
fieldCountPrevRow = undefined; |
|
|
|
fieldCountPrevRow = undefined; |
|
|
|
|
|
|
|
|
|
|
@ -1247,7 +1292,7 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
if (typeof fieldCountPrevRow === 'undefined') |
|
|
|
if (typeof fieldCountPrevRow === 'undefined') |
|
|
|
{ |
|
|
|
{ |
|
|
|
fieldCountPrevRow = fieldCount; |
|
|
|
fieldCountPrevRow = 0; |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (fieldCount > 1) |
|
|
|
else if (fieldCount > 1) |
|
|
@ -1260,7 +1305,7 @@ if (!Array.isArray) |
|
|
|
if (preview.data.length > 0) |
|
|
|
if (preview.data.length > 0) |
|
|
|
avgFieldCount /= (preview.data.length - emptyLinesCount); |
|
|
|
avgFieldCount /= (preview.data.length - emptyLinesCount); |
|
|
|
|
|
|
|
|
|
|
|
if ((typeof bestDelta === 'undefined' || delta < bestDelta) |
|
|
|
if ((typeof bestDelta === 'undefined' || delta > bestDelta) |
|
|
|
&& avgFieldCount > 1.99) |
|
|
|
&& avgFieldCount > 1.99) |
|
|
|
{ |
|
|
|
{ |
|
|
|
bestDelta = delta; |
|
|
|
bestDelta = delta; |
|
|
@ -1349,7 +1394,7 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
// Comment character must be valid
|
|
|
|
// Comment character must be valid
|
|
|
|
if (comments === delim) |
|
|
|
if (comments === delim) |
|
|
|
throw 'Comment character same as delimiter'; |
|
|
|
throw new Error('Comment character same as delimiter'); |
|
|
|
else if (comments === true) |
|
|
|
else if (comments === true) |
|
|
|
comments = '#'; |
|
|
|
comments = '#'; |
|
|
|
else if (typeof comments !== 'string' |
|
|
|
else if (typeof comments !== 'string' |
|
|
@ -1368,7 +1413,7 @@ if (!Array.isArray) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// For some reason, in Chrome, this speeds things up (!?)
|
|
|
|
// For some reason, in Chrome, this speeds things up (!?)
|
|
|
|
if (typeof input !== 'string') |
|
|
|
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,
|
|
|
|
// 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
|
|
|
|
// but having them in a more local scope seems to perform better
|
|
|
@ -1419,8 +1464,8 @@ if (!Array.isArray) |
|
|
|
|
|
|
|
|
|
|
|
var nextDelim = input.indexOf(delim, cursor); |
|
|
|
var nextDelim = input.indexOf(delim, cursor); |
|
|
|
var nextNewline = input.indexOf(newline, 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; |
|
|
|
var quoteSearch = input.indexOf(quoteChar, cursor); |
|
|
|
|
|
|
|
|
|
|
|
// Parser loop
|
|
|
|
// Parser loop
|
|
|
|
for (;;) |
|
|
|
for (;;) |
|
|
@ -1485,6 +1530,12 @@ if (!Array.isArray) |
|
|
|
{ |
|
|
|
{ |
|
|
|
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); |
|
|
|
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); |
|
|
|
cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen; |
|
|
|
cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If char after following delimiter is not quoteChar, we find next quote char position
|
|
|
|
|
|
|
|
if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen] !== quoteChar) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
quoteSearch = input.indexOf(quoteChar, cursor); |
|
|
|
|
|
|
|
} |
|
|
|
nextDelim = input.indexOf(delim, cursor); |
|
|
|
nextDelim = input.indexOf(delim, cursor); |
|
|
|
nextNewline = input.indexOf(newline, cursor); |
|
|
|
nextNewline = input.indexOf(newline, cursor); |
|
|
|
break; |
|
|
|
break; |
|
|
@ -1498,6 +1549,7 @@ if (!Array.isArray) |
|
|
|
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); |
|
|
|
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar)); |
|
|
|
saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen); |
|
|
|
saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen); |
|
|
|
nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field
|
|
|
|
nextDelim = input.indexOf(delim, cursor); // because we may have skipped the nextDelim in the quoted field
|
|
|
|
|
|
|
|
quoteSearch = input.indexOf(quoteChar, cursor); // we search for first quote in next line
|
|
|
|
|
|
|
|
|
|
|
|
if (stepIsFunction) |
|
|
|
if (stepIsFunction) |
|
|
|
{ |
|
|
|
{ |
|
|
@ -1544,10 +1596,27 @@ if (!Array.isArray) |
|
|
|
// Next delimiter comes before next newline, so we've reached end of field
|
|
|
|
// Next delimiter comes before next newline, so we've reached end of field
|
|
|
|
if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1)) |
|
|
|
if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1)) |
|
|
|
{ |
|
|
|
{ |
|
|
|
row.push(input.substring(cursor, nextDelim)); |
|
|
|
// we check, if we have quotes, because delimiter char may be part of field enclosed in quotes
|
|
|
|
cursor = nextDelim + delimLen; |
|
|
|
if (quoteSearch !== -1) { |
|
|
|
nextDelim = input.indexOf(delim, cursor); |
|
|
|
// we have quotes, so we try to find the next delimiter not enclosed in quotes and also next starting quote char
|
|
|
|
continue; |
|
|
|
var nextDelimObj = getNextUnqotedDelimiter(nextDelim, quoteSearch, nextNewline); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if we have next delimiter char which is not enclosed in quotes
|
|
|
|
|
|
|
|
if (nextDelimObj && nextDelimObj.nextDelim) { |
|
|
|
|
|
|
|
nextDelim = nextDelimObj.nextDelim; |
|
|
|
|
|
|
|
quoteSearch = nextDelimObj.quoteSearch; |
|
|
|
|
|
|
|
row.push(input.substring(cursor, nextDelim)); |
|
|
|
|
|
|
|
cursor = nextDelim + delimLen; |
|
|
|
|
|
|
|
// we look for next delimiter char
|
|
|
|
|
|
|
|
nextDelim = input.indexOf(delim, cursor); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
row.push(input.substring(cursor, nextDelim)); |
|
|
|
|
|
|
|
cursor = nextDelim + delimLen; |
|
|
|
|
|
|
|
nextDelim = input.indexOf(delim, cursor); |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// End of row
|
|
|
|
// End of row
|
|
|
@ -1630,10 +1699,11 @@ if (!Array.isArray) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Returns an object with the results, errors, and meta. */ |
|
|
|
/** Returns an object with the results, errors, and meta. */ |
|
|
|
function returnable(stopped) |
|
|
|
function returnable(stopped, step) |
|
|
|
{ |
|
|
|
{ |
|
|
|
|
|
|
|
var isStep = step || false; |
|
|
|
return { |
|
|
|
return { |
|
|
|
data: data, |
|
|
|
data: isStep ? data[0] : data, |
|
|
|
errors: errors, |
|
|
|
errors: errors, |
|
|
|
meta: { |
|
|
|
meta: { |
|
|
|
delimiter: delim, |
|
|
|
delimiter: delim, |
|
|
@ -1648,10 +1718,44 @@ if (!Array.isArray) |
|
|
|
/** Executes the user's step function and resets data & errors. */ |
|
|
|
/** Executes the user's step function and resets data & errors. */ |
|
|
|
function doStep() |
|
|
|
function doStep() |
|
|
|
{ |
|
|
|
{ |
|
|
|
step(returnable()); |
|
|
|
step(returnable(undefined, true)); |
|
|
|
data = []; |
|
|
|
data = []; |
|
|
|
errors = []; |
|
|
|
errors = []; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Gets the delimiter character, which is not inside the quoted field */ |
|
|
|
|
|
|
|
function getNextUnqotedDelimiter(nextDelim, quoteSearch, newLine) { |
|
|
|
|
|
|
|
var result = { |
|
|
|
|
|
|
|
nextDelim: undefined, |
|
|
|
|
|
|
|
quoteSearch: undefined |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
// get the next closing quote character
|
|
|
|
|
|
|
|
var nextQuoteSearch = input.indexOf(quoteChar, quoteSearch + 1); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if next delimiter is part of a field enclosed in quotes
|
|
|
|
|
|
|
|
if (nextDelim > quoteSearch && nextDelim < nextQuoteSearch && (nextQuoteSearch < newLine || newLine === -1)) { |
|
|
|
|
|
|
|
// get the next delimiter character after this one
|
|
|
|
|
|
|
|
var nextNextDelim = input.indexOf(delim, nextQuoteSearch); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// if there is no next delimiter, return default result
|
|
|
|
|
|
|
|
if (nextNextDelim === -1) { |
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// find the next opening quote char position
|
|
|
|
|
|
|
|
if (nextNextDelim > nextQuoteSearch) { |
|
|
|
|
|
|
|
nextQuoteSearch = input.indexOf(quoteChar, nextQuoteSearch + 1); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// try to get the next delimiter position
|
|
|
|
|
|
|
|
result = getNextUnqotedDelimiter(nextNextDelim, nextQuoteSearch, newLine); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
result = { |
|
|
|
|
|
|
|
nextDelim: nextDelim, |
|
|
|
|
|
|
|
quoteSearch: quoteSearch |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** Sets the abort flag */ |
|
|
|
/** Sets the abort flag */ |
|
|
@ -1668,26 +1772,12 @@ if (!Array.isArray) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 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() |
|
|
|
function newWorker() |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (!Papa.WORKERS_SUPPORTED) |
|
|
|
if (!Papa.WORKERS_SUPPORTED) |
|
|
|
return false; |
|
|
|
return false; |
|
|
|
if (!LOADED_SYNC && Papa.SCRIPT_PATH === null) |
|
|
|
|
|
|
|
throw new Error( |
|
|
|
var workerUrl = getWorkerBlob(); |
|
|
|
'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 w = new global.Worker(workerUrl); |
|
|
|
var w = new global.Worker(workerUrl); |
|
|
|
w.onmessage = mainThreadReceivedMessage; |
|
|
|
w.onmessage = mainThreadReceivedMessage; |
|
|
|
w.id = workerIdCounter++; |
|
|
|
w.id = workerIdCounter++; |
|
|
@ -1722,7 +1812,7 @@ if (!Array.isArray) |
|
|
|
for (var i = 0; i < msg.results.data.length; i++) |
|
|
|
for (var i = 0; i < msg.results.data.length; i++) |
|
|
|
{ |
|
|
|
{ |
|
|
|
worker.userStep({ |
|
|
|
worker.userStep({ |
|
|
|
data: [msg.results.data[i]], |
|
|
|
data: msg.results.data[i], |
|
|
|
errors: msg.results.errors, |
|
|
|
errors: msg.results.errors, |
|
|
|
meta: msg.results.meta |
|
|
|
meta: msg.results.meta |
|
|
|
}, handle); |
|
|
|
}, handle); |
|
|
@ -1751,7 +1841,7 @@ if (!Array.isArray) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function notImplemented() { |
|
|
|
function notImplemented() { |
|
|
|
throw 'Not implemented.'; |
|
|
|
throw new Error('Not implemented.'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** Callback when worker thread receives a message */ |
|
|
|
/** Callback when worker thread receives a message */ |
|
|
|