|
|
|
@ -1,11 +1,22 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -70,6 +81,7 @@
|
|
|
|
|
Papa.FileStreamer = FileStreamer; |
|
|
|
|
Papa.StringStreamer = StringStreamer; |
|
|
|
|
Papa.ReadableStreamStreamer = ReadableStreamStreamer; |
|
|
|
|
Papa.DuplexStreamStreamer = DuplexStreamStreamer; |
|
|
|
|
|
|
|
|
|
if (global.jQuery) |
|
|
|
|
{ |
|
|
|
@ -162,7 +174,7 @@
@@ -162,7 +174,7 @@
|
|
|
|
|
queue.splice(0, 1); |
|
|
|
|
parseNextFile(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -182,7 +194,7 @@
@@ -182,7 +194,7 @@
|
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
{ |
|
|
|
|
document.addEventListener('DOMContentLoaded', function () { |
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
|
LOADED_SYNC = true; |
|
|
|
|
}, true); |
|
|
|
|
} |
|
|
|
@ -202,6 +214,8 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 = 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 @@
@@ -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 @@
@@ -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 @@
@@ -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,7 +403,10 @@
@@ -379,7 +403,10 @@
|
|
|
|
|
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() !== '') |
|
|
|
|
{ |
|
|
|
|
for (var col = 0; col < maxCol; col++) |
|
|
|
|
{ |
|
|
|
|
if (col > 0) |
|
|
|
@ -387,11 +414,10 @@
@@ -387,11 +414,10 @@
|
|
|
|
|
var colIdx = hasHeader && dataKeyedByField ? fields[col] : col; |
|
|
|
|
csv += safe(data[row][colIdx], col); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (row < data.length - 1) |
|
|
|
|
if (row < data.length - 1 && (!skipEmptyLines || maxCol > 0)) |
|
|
|
|
csv += _newline; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
return csv; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -401,10 +427,13 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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,13 +663,13 @@
@@ -632,13 +663,13 @@
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
@ -687,7 +718,7 @@
@@ -687,7 +718,7 @@
|
|
|
|
|
{ |
|
|
|
|
if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview)) |
|
|
|
|
this._readChunk(); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this._readChunk = function() |
|
|
|
|
{ |
|
|
|
@ -700,7 +731,7 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -775,7 +825,7 @@
|
|
|
|
|
{ |
|
|
|
|
parseOnData = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this._streamData = bindFunction(function(chunk) |
|
|
|
|
{ |
|
|
|
@ -786,6 +836,7 @@
@@ -786,6 +836,7 @@
|
|
|
|
|
if (parseOnData) |
|
|
|
|
{ |
|
|
|
|
parseOnData = false; |
|
|
|
|
this._checkIsFinished(); |
|
|
|
|
this.parseChunk(queue.shift()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -798,13 +849,13 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1052,6 +1229,7 @@
|
|
|
|
|
fieldCountPrevRow = undefined; |
|
|
|
|
|
|
|
|
|
var preview = new Parser({ |
|
|
|
|
comments: comments, |
|
|
|
|
delimiter: delim, |
|
|
|
|
newline: newline, |
|
|
|
|
preview: 10 |
|
|
|
@ -1059,9 +1237,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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1437,7 +1649,8 @@
|
|
|
|
|
function doStep() |
|
|
|
|
{ |
|
|
|
|
step(returnable()); |
|
|
|
|
data = [], errors = []; |
|
|
|
|
data = []; |
|
|
|
|
errors = []; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
@ -1572,9 +1785,9 @@
@@ -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; |
|
|
|
|