PageSourceSearch

https://toolbox.injoyonline.com/InJoyLibrary/injoy/js/papaparse.js?update=5

js injoyonline.com collected 2026-09-30 14:34:40 UTC 50,598 bytes, 1,903 lines download raw bytes

1/* @license
2Papa Parse
3v5.0.2
4https://github.com/mholt/PapaParse
5License: MIT
6*/
7
8(function(root, factory)
9{
10	/* globals define */
11	if (typeof define === 'function' && define.amd)
12	{
13		// AMD. Register as an anonymous module.
14		define([], factory);
15	}
16	else if (typeof module === 'object' && typeof exports !== 'undefined')
17	{
18		// Node. Does not work with strict CommonJS, but
19		// only CommonJS-like environments that support module.exports,
20		// like Node.
21		module.exports = factory();
22	}
23	else
24	{
25		// Browser globals (root is window)
26		root.Papa = factory();
27	}
28	// in strict mode we cannot access arguments.callee, so we need a named reference to
29	// stringify the factory method for the blob worker
30	// eslint-disable-next-line func-name
31}(this, function moduleFactory()
32{
33	'use strict';
34
35	var global = (function() {
36		// alternative method, similar to `Function('return this')()`
37		// but without using `eval` (which is disabled when
38		// using Content Security Policy).
39
40		if (typeof self !== 'undefined') { return self; }
41		if (typeof window !== 'undefined') { return window; }
42		if (typeof global !== 'undefined') { return global; }
43
44		// When running tests none of the above have been defined
45		return {};
46	})();
47
48
49	function getWorkerBlob() {
50		var URL = global.URL || global.webkitURL || null;
51		var code = moduleFactory.toString();
52		return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(['(', code, ')();'], {type: 'text/javascript'})));
53	}
54
55	var IS_WORKER = !global.document && !!global.postMessage,
56		IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol);
57	var workers = {}, workerIdCounter = 0;
58
59	var Papa = {};
60
61	Papa.parse = CsvToJson;
62	Papa.unparse = JsonToCsv;
63
64	Papa.RECORD_SEP = String.fromCharCode(30);
65	Papa.UNIT_SEP = String.fromCharCode(31);
66	Papa.BYTE_ORDER_MARK = '\ufeff';
67	Papa.BAD_DELIMITERS = ['\r', '\n', '"', Papa.BYTE_ORDER_MARK];
68	Papa.WORKERS_SUPPORTED = !IS_WORKER && !!global.Worker;
69	Papa.NODE_STREAM_INPUT = 1;
70
71	// Configurable chunk sizes for local and remote files, respectively
72	Papa.LocalChunkSize = 1024 * 1024 * 10;	// 10 MB
73	Papa.RemoteChunkSize = 1024 * 1024 * 5;	// 5 MB
74	Papa.DefaultDelimiter = ',';			// Used if not specified and detection fails
75
76	// Exposed for testing and development only
77	Papa.Parser = Parser;
78	Papa.ParserHandle = ParserHandle;
79	Papa.NetworkStreamer = NetworkStreamer;
80	Papa.FileStreamer = FileStreamer;
81	Papa.StringStreamer = StringStreamer;
82	Papa.ReadableStreamStreamer = ReadableStreamStreamer;
83	if (typeof PAPA_BROWSER_CONTEXT === 'undefined') {
84		Papa.DuplexStreamStreamer = DuplexStreamStreamer;
85	}
86
87	if (global.jQuery)
88	{
89		var $ = global.jQuery;
90		$.fn.parse = function(options)
91		{
92			var config = options.config || {};
93			var queue = [];
94
95			this.each(function(idx)
96			{
97				var supported = $(this).prop('tagName').toUpperCase() === 'INPUT'
98								&& $(this).attr('type').toLowerCase() === 'file'
99								&& global.FileReader;
100
101				if (!supported || !this.files || this.files.length === 0)
102					return true;	// continue to next input element
103
104				for (var i = 0; i < this.files.length; i++)
105				{
106					queue.push({
107						file: this.files[i],
108						inputElem: this,
109						instanceConfig: $.extend({}, config)
110					});
111				}
112			});
113
114			parseNextFile();	// begin parsing
115			return this;		// maintains chainability
116
117
118			function parseNextFile()
119			{
120				if (queue.length === 0)
121				{
122					if (isFunction(options.complete))
123						options.complete();
124					return;
125				}
126
127				var f = queue[0];
128
129				if (isFunction(options.before))
130				{
131					var returned = options.before(f.file, f.inputElem);
132
133					if (typeof returned === 'object')
134					{
135						if (returned.action === 'abort')
136						{
137							error('AbortError', f.file, f.inputElem, returned.reason);
138							return;	// Aborts all queued files immediately
139						}
140						else if (returned.action === 'skip')
141						{
142							fileComplete();	// parse the next file in the queue, if any
143							return;
144						}
145						else if (typeof returned.config === 'object')
146							f.instanceConfig = $.extend(f.instanceConfig, returned.config);
147					}
148					else if (returned === 'skip')
149					{
150						fileComplete();	// parse the next file in the queue, if any
151						return;
152					}
153				}
154
155				// Wrap up the user's complete callback, if any, so that ours also gets executed
156				var userCompleteFunc = f.instanceConfig.complete;
157				f.instanceConfig.complete = function(results)
158				{
159					if (isFunction(userCompleteFunc))
160						userCompleteFunc(results, f.file, f.inputElem);
161					fileComplete();
162				};
163
164				Papa.parse(f.file, f.instanceConfig);
165			}
166
167			function error(name, file, elem, reason)
168			{
169				if (isFunction(options.error))
vendor: 16,419 bytes, lines 170-808
170					options.error({name: name}, file, elem, reason);
171			}
172
173			function fileComplete()
174			{
175				queue.splice(0, 1);
176				parseNextFile();
177			}
178		};
179	}
180
181
182	if (IS_PAPA_WORKER)
183	{
184		global.onmessage = workerThreadReceivedMessage;
185	}
186
187
188
189
190	function CsvToJson(_input, _config)
191	{
192		_config = _config || {};
193		var dynamicTyping = _config.dynamicTyping || false;
194		if (isFunction(dynamicTyping)) {
195			_config.dynamicTypingFunction = dynamicTyping;
196			// Will be filled on first row call
197			dynamicTyping = {};
198		}
199		_config.dynamicTyping = dynamicTyping;
200
201		_config.transform = isFunction(_config.transform) ? _config.transform : false;
202
203		if (_config.worker && Papa.WORKERS_SUPPORTED)
204		{
205			var w = newWorker();
206
207			w.userStep = _config.step;
208			w.userChunk = _config.chunk;
209			w.userComplete = _config.complete;
210			w.userError = _config.error;
211
212			_config.step = isFunction(_config.step);
213			_config.chunk = isFunction(_config.chunk);
214			_config.complete = isFunction(_config.complete);
215			_config.error = isFunction(_config.error);
216			delete _config.worker;	// prevent infinite loop
217
218			w.postMessage({
219				input: _input,
220				config: _config,
221				workerId: w.id
222			});
223
224			return;
225		}
226
227		var streamer = null;
228		if (_input === Papa.NODE_STREAM_INPUT && typeof PAPA_BROWSER_CONTEXT === 'undefined')
229		{
230			// create a node Duplex stream for use
231			// with .pipe
232			streamer = new DuplexStreamStreamer(_config);
233			return streamer.getStream();
234		}
235		else if (typeof _input === 'string')
236		{
237			if (_config.download)
238				streamer = new NetworkStreamer(_config);
239			else
240				streamer = new StringStreamer(_config);
241		}
242		else if (_input.readable === true && isFunction(_input.read) && isFunction(_input.on))
243		{
244			streamer = new ReadableStreamStreamer(_config);
245		}
246		else if ((global.File && _input instanceof File) || _input instanceof Object)	// ...Safari. (see issue #106)
247			streamer = new FileStreamer(_config);
248
249		return streamer.stream(_input);
250	}
251
252
253
254
255
256
257	function JsonToCsv(_input, _config)
258	{
259		// Default configuration
260
261		/** whether to surround every datum with quotes */
262		var _quotes = false;
263
264		/** whether to write headers */
265		var _writeHeader = true;
266
267		/** delimiting character(s) */
268		var _delimiter = ',';
269
270		/** newline character(s) */
271		var _newline = '\r\n';
272
273		/** quote character */
274		var _quoteChar = '"';
275
276		/** escaped quote character, either "" or <config.escapeChar>" */
277		var _escapedQuote = _quoteChar + _quoteChar;
278
279		/** whether to skip empty lines */
280		var _skipEmptyLines = false;
281
282		/** the columns (keys) we expect when we unparse objects */
283		var _columns = null;
284
285		unpackConfig();
286
287		var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
288
289		if (typeof _input === 'string')
290			_input = JSON.parse(_input);
291
292		if (Array.isArray(_input))
293		{
294			if (!_input.length || Array.isArray(_input[0]))
295				return serialize(null, _input, _skipEmptyLines);
296			else if (typeof _input[0] === 'object')
297				return serialize(_columns || objectKeys(_input[0]), _input, _skipEmptyLines);
298		}
299		else if (typeof _input === 'object')
300		{
301			if (typeof _input.data === 'string')
302				_input.data = JSON.parse(_input.data);
303
304			if (Array.isArray(_input.data))
305			{
306				if (!_input.fields)
307					_input.fields =  _input.meta && _input.meta.fields;
308
309				if (!_input.fields)
310					_input.fields =  Array.isArray(_input.data[0])
311						? _input.fields
312						: objectKeys(_input.data[0]);
313
314				if (!(Array.isArray(_input.data[0])) && typeof _input.data[0] !== 'object')
315					_input.data = [_input.data];	// handles input like [1,2,3] or ['asdf']
316			}
317
318			return serialize(_input.fields || [], _input.data || [], _skipEmptyLines);
319		}
320
321		// Default (any valid paths should return before this)
322		throw new Error('Unable to serialize unrecognized input');
323
324
325		function unpackConfig()
326		{
327			if (typeof _config !== 'object')
328				return;
329
330			if (typeof _config.delimiter === 'string'
331                && !Papa.BAD_DELIMITERS.filter(function(value) { return _config.delimiter.indexOf(value) !== -1; }).length)
332			{
333				_delimiter = _config.delimiter;
334			}
335
336			if (typeof _config.quotes === 'boolean'
337				|| Array.isArray(_config.quotes))
338				_quotes = _config.quotes;
339
340			if (typeof _config.skipEmptyLines === 'boolean'
341				|| typeof _config.skipEmptyLines === 'string')
342				_skipEmptyLines = _config.skipEmptyLines;
343
344			if (typeof _config.newline === 'string')
345				_newline = _config.newline;
346
347			if (typeof _config.quoteChar === 'string')
348				_quoteChar = _config.quoteChar;
349
350			if (typeof _config.header === 'boolean')
351				_writeHeader = _config.header;
352
353			if (Array.isArray(_config.columns)) {
354
355				if (_config.columns.length === 0) throw new Error('Option columns is empty');
356
357				_columns = _config.columns;
358			}
359
360			if (_config.escapeChar !== undefined) {
361				_escapedQuote = _config.escapeChar + _quoteChar;
362			}
363		}
364
365
366		/** Turns an object's keys into an array */
367		function objectKeys(obj)
368		{
369			if (typeof obj !== 'object')
370				return [];
371			var keys = [];
372			for (var key in obj)
373				keys.push(key);
374			return keys;
375		}
376
377		/** The double for loop that iterates the data and writes out a CSV string including header row */
378		function serialize(fields, data, skipEmptyLines)
379		{
380			var csv = '';
381
382			if (typeof fields === 'string')
383				fields = JSON.parse(fields);
384			if (typeof data === 'string')
385				data = JSON.parse(data);
386
387			var hasHeader = Array.isArray(fields) && fields.length > 0;
388			var dataKeyedByField = !(Array.isArray(data[0]));
389
390			// If there a header row, write it first
391			if (hasHeader && _writeHeader)
392			{
393				for (var i = 0; i < fields.length; i++)
394				{
395					if (i > 0)
396						csv += _delimiter;
397					csv += safe(fields[i], i);
398				}
399				if (data.length > 0)
400					csv += _newline;
401			}
402
403			// Then write out the data
404			for (var row = 0; row < data.length; row++)
405			{
406				var maxCol = hasHeader ? fields.length : data[row].length;
407
408				var emptyLine = false;
409				var nullLine = hasHeader ? Object.keys(data[row]).length === 0 : data[row].length === 0;
410				if (skipEmptyLines && !hasHeader)
411				{
412					emptyLine = skipEmptyLines === 'greedy' ? data[row].join('').trim() === '' : data[row].length === 1 && data[row][0].length === 0;
413				}
414				if (skipEmptyLines === 'greedy' && hasHeader) {
415					var line = [];
416					for (var c = 0; c < maxCol; c++) {
417						var cx = dataKeyedByField ? fields[c] : c;
418						line.push(data[row][cx]);
419					}
420					emptyLine = line.join('').trim() === '';
421				}
422				if (!emptyLine)
423				{
424					for (var col = 0; col < maxCol; col++)
425					{
426						if (col > 0 && !nullLine)
427							csv += _delimiter;
428						var colIdx = hasHeader && dataKeyedByField ? fields[col] : col;
429						csv += safe(data[row][colIdx], col);
430					}
431					if (row < data.length - 1 && (!skipEmptyLines || (maxCol > 0 && !nullLine)))
432					{
433						csv += _newline;
434					}
435				}
436			}
437			return csv;
438		}
439
440		/** Encloses a value around quotes if needed (makes a value safe for CSV insertion) */
441		function safe(str, col)
442		{
443			if (typeof str === 'undefined' || str === null)
444				return '';
445
446			if (str.constructor === Date)
447				return JSON.stringify(str).slice(1, 25);
448
449			str = str.toString().replace(quoteCharRegex, _escapedQuote);
450
451			var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
452							|| (Array.isArray(_quotes) && _quotes[col])
453							|| hasAny(str, Papa.BAD_DELIMITERS)
454							|| str.indexOf(_delimiter) > -1
455							|| str.charAt(0) === ' '
456							|| str.charAt(str.length - 1) === ' ';
457
458			return needsQuotes ? _quoteChar + str + _quoteChar : str;
459		}
460
461		function hasAny(str, substrings)
462		{
463			for (var i = 0; i < substrings.length; i++)
464				if (str.indexOf(substrings[i]) > -1)
465					return true;
466			return false;
467		}
468	}
469
470	/** ChunkStreamer is the base prototype for various streamer implementations. */
471	function ChunkStreamer(config)
472	{
473		this._handle = null;
474		this._finished = false;
475		this._completed = false;
476		this._halted = false;
477		this._input = null;
478		this._baseIndex = 0;
479		this._partialLine = '';
480		this._rowCount = 0;
481		this._start = 0;
482		this._nextChunk = null;
483		this.isFirstChunk = true;
484		this._completeResults = {
485			data: [],
486			errors: [],
487			meta: {}
488		};
489		replaceConfig.call(this, config);
490
491		this.parseChunk = function(chunk, isFakeChunk)
492		{
493			// First chunk pre-processing
494			if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk))
495			{
496				var modifiedChunk = this._config.beforeFirstChunk(chunk);
497				if (modifiedChunk !== undefined)
498					chunk = modifiedChunk;
499			}
500			this.isFirstChunk = false;
501			this._halted = false;
502
503			// Rejoin the line we likely just split in two by chunking the file
504			var aggregate = this._partialLine + chunk;
505			this._partialLine = '';
506
507			var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);
508
509			if (this._handle.paused() || this._handle.aborted()) {
510				this._halted = true;
511				return;
512			}
513
514			var lastIndex = results.meta.cursor;
515
516			if (!this._finished)
517			{
518				this._partialLine = aggregate.substring(lastIndex - this._baseIndex);
519				this._baseIndex = lastIndex;
520			}
521
522			if (results && results.data)
523				this._rowCount += results.data.length;
524
525			var finishedIncludingPreview = this._finished || (this._config.preview && this._rowCount >= this._config.preview);
526
527			if (IS_PAPA_WORKER)
528			{
529				global.postMessage({
530					results: results,
531					workerId: Papa.WORKER_ID,
532					finished: finishedIncludingPreview
533				});
534			}
535			else if (isFunction(this._config.chunk) && !isFakeChunk)
536			{
537				this._config.chunk(results, this._handle);
538				if (this._handle.paused() || this._handle.aborted()) {
539					this._halted = true;
540					return;
541				}
542				results = undefined;
543				this._completeResults = undefined;
544			}
545
546			if (!this._config.step && !this._config.chunk) {
547				this._completeResults.data = this._completeResults.data.concat(results.data);
548				this._completeResults.errors = this._completeResults.errors.concat(results.errors);
549				this._completeResults.meta = results.meta;
550			}
551
552			if (!this._completed && finishedIncludingPreview && isFunction(this._config.complete) && (!results || !results.meta.aborted)) {
553				this._config.complete(this._completeResults, this._input);
554				this._completed = true;
555			}
556
557			if (!finishedIncludingPreview && (!results || !results.meta.paused))
558				this._nextChunk();
559
560			return results;
561		};
562
563		this._sendError = function(error)
564		{
565			if (isFunction(this._config.error))
566				this._config.error(error);
567			else if (IS_PAPA_WORKER && this._config.error)
568			{
569				global.postMessage({
570					workerId: Papa.WORKER_ID,
571					error: error,
572					finished: false
573				});
574			}
575		};
576
577		function replaceConfig(config)
578		{
579			// Deep-copy the config so we can edit it
580			var configCopy = copy(config);
581			configCopy.chunkSize = parseInt(configCopy.chunkSize);	// parseInt VERY important so we don't concatenate strings!
582			if (!config.step && !config.chunk)
583				configCopy.chunkSize = null;  // disable Range header if not streaming; bad values break IIS - see issue #196
584			this._handle = new ParserHandle(configCopy);
585			this._handle.streamer = this;
586			this._config = configCopy;	// persist the copy to the caller
587		}
588	}
589
590
591	function NetworkStreamer(config)
592	{
593		config = config || {};
594		if (!config.chunkSize)
595			config.chunkSize = Papa.RemoteChunkSize;
596		ChunkStreamer.call(this, config);
597
598		var xhr;
599
600		if (IS_WORKER)
601		{
602			this._nextChunk = function()
603			{
604				this._readChunk();
605				this._chunkLoaded();
606			};
607		}
608		else
609		{
610			this._nextChunk = function()
611			{
612				this._readChunk();
613			};
614		}
615
616		this.stream = function(url)
617		{
618			this._input = url;
619			this._nextChunk();	// Starts streaming
620		};
621
622		this._readChunk = function()
623		{
624			if (this._finished)
625			{
626				this._chunkLoaded();
627				return;
628			}
629
630			xhr = new XMLHttpRequest();
631
632			if (this._config.withCredentials)
633			{
634				xhr.withCredentials = this._config.withCredentials;
635			}
636
637			if (!IS_WORKER)
638			{
639				xhr.onload = bindFunction(this._chunkLoaded, this);
640				xhr.onerror = bindFunction(this._chunkError, this);
641			}
642
643			xhr.open('GET', this._input, !IS_WORKER);
644			// Headers can only be set when once the request state is OPENED
645			if (this._config.downloadRequestHeaders)
646			{
647				var headers = this._config.downloadRequestHeaders;
648
649				for (var headerName in headers)
650				{
651					xhr.setRequestHeader(headerName, headers[headerName]);
652				}
653			}
654
655			if (this._config.chunkSize)
656			{
657				var end = this._start + this._config.chunkSize - 1;	// minus one because byte range is inclusive
658				xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end);
659			}
660
661			try {
662				xhr.send();
663			}
664			catch (err) {
665				this._chunkError(err.message);
666			}
667
668			if (IS_WORKER && xhr.status === 0)
669				this._chunkError();
670			else
671				this._start += this._config.chunkSize;
672		};
673
674		this._chunkLoaded = function()
675		{
676			if (xhr.readyState !== 4)
677				return;
678
679			if (xhr.status < 200 || xhr.status >= 400)
680			{
681				this._chunkError();
682				return;
683			}
684
685			this._finished = !this._config.chunkSize || this._start > getFileSize(xhr);
686			this.parseChunk(xhr.responseText);
687		};
688
689		this._chunkError = function(errorMessage)
690		{
691			var errorText = xhr.statusText || errorMessage;
692			this._sendError(new Error(errorText));
693		};
694
695		function getFileSize(xhr)
696		{
697			var contentRange = xhr.getResponseHeader('Content-Range');
698			if (contentRange === null) { // no content range, then finish!
699				return -1;
700			}
701			return parseInt(contentRange.substr(contentRange.lastIndexOf('/') + 1));
702		}
703	}
704	NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype);
705	NetworkStreamer.prototype.constructor = NetworkStreamer;
706
707
708	function FileStreamer(config)
709	{
710		config = config || {};
711		if (!config.chunkSize)
712			config.chunkSize = Papa.LocalChunkSize;
713		ChunkStreamer.call(this, config);
714
715		var reader, slice;
716
717		// FileReader is better than FileReaderSync (even in worker) - see http://stackoverflow.com/q/24708649/1048862
718		// But Firefox is a pill, too - see issue #76: https://github.com/mholt/PapaParse/issues/76
719		var usingAsyncReader = typeof FileReader !== 'undefined';	// Safari doesn't consider it a function - see issue #105
720
721		this.stream = function(file)
722		{
723			this._input = file;
724			slice = file.slice || file.webkitSlice || file.mozSlice;
725
726			if (usingAsyncReader)
727			{
728				reader = new FileReader();		// Preferred method of reading files, even in workers
729				reader.onload = bindFunction(this._chunkLoaded, this);
730				reader.onerror = bindFunction(this._chunkError, this);
731			}
732			else
733				reader = new FileReaderSync();	// Hack for running in a web worker in Firefox
734
735			this._nextChunk();	// Starts streaming
736		};
737
738		this._nextChunk = function()
739		{
740			if (!this._finished && (!this._config.preview || this._rowCount < this._config.preview))
741				this._readChunk();
742		};
743
744		this._readChunk = function()
745		{
746			var input = this._input;
747			if (this._config.chunkSize)
748			{
749				var end = Math.min(this._start + this._config.chunkSize, this._input.size);
750				input = slice.call(input, this._start, end);
751			}
752			var txt = reader.readAsText(input, this._config.encoding);
753			if (!usingAsyncReader)
754				this._chunkLoaded({ target: { result: txt } });	// mimic the async signature
755		};
756
757		this._chunkLoaded = function(event)
758		{
759			// Very important to increment start each time before handling results
760			this._start += this._config.chunkSize;
761			this._finished = !this._config.chunkSize || this._start >= this._input.size;
762			this.parseChunk(event.target.result);
763		};
764
765		this._chunkError = function()
766		{
767			this._sendError(reader.error);
768		};
769
770	}
771	FileStreamer.prototype = Object.create(ChunkStreamer.prototype);
772	FileStreamer.prototype.constructor = FileStreamer;
773
774
775	function StringStreamer(config)
776	{
777		config = config || {};
778		ChunkStreamer.call(this, config);
779
780		var remaining;
781		this.stream = function(s)
782		{
783			remaining = s;
784			return this._nextChunk();
785		};
786		this._nextChunk = function()
787		{
788			if (this._finished) return;
789			var size = this._config.chunkSize;
790			var chunk = size ? remaining.substr(0, size) : remaining;
791			remaining = size ? remaining.substr(size) : '';
792			this._finished = !remaining;
793			return this.parseChunk(chunk);
794		};
795	}
796	StringStreamer.prototype = Object.create(StringStreamer.prototype);
797	StringStreamer.prototype.constructor = StringStreamer;
798
799
800	function ReadableStreamStreamer(config)
801	{
802		config = config || {};
803
804		ChunkStreamer.call(this, config);
805
806		var queue = [];
807		var parseOnData = true;
808		var streamHasEnded = false;
vendor: 6,566 bytes, lines 809-1055
809
810		this.pause = function()
811		{
812			ChunkStreamer.prototype.pause.apply(this, arguments);
813			this._input.pause();
814		};
815
816		this.resume = function()
817		{
818			ChunkStreamer.prototype.resume.apply(this, arguments);
819			this._input.resume();
820		};
821
822		this.stream = function(stream)
823		{
824			this._input = stream;
825
826			this._input.on('data', this._streamData);
827			this._input.on('end', this._streamEnd);
828			this._input.on('error', this._streamError);
829		};
830
831		this._checkIsFinished = function()
832		{
833			if (streamHasEnded && queue.length === 1) {
834				this._finished = true;
835			}
836		};
837
838		this._nextChunk = function()
839		{
840			this._checkIsFinished();
841			if (queue.length)
842			{
843				this.parseChunk(queue.shift());
844			}
845			else
846			{
847				parseOnData = true;
848			}
849		};
850
851		this._streamData = bindFunction(function(chunk)
852		{
853			try
854			{
855				queue.push(typeof chunk === 'string' ? chunk : chunk.toString(this._config.encoding));
856
857				if (parseOnData)
858				{
859					parseOnData = false;
860					this._checkIsFinished();
861					this.parseChunk(queue.shift());
862				}
863			}
864			catch (error)
865			{
866				this._streamError(error);
867			}
868		}, this);
869
870		this._streamError = bindFunction(function(error)
871		{
872			this._streamCleanUp();
873			this._sendError(error);
874		}, this);
875
876		this._streamEnd = bindFunction(function()
877		{
878			this._streamCleanUp();
879			streamHasEnded = true;
880			this._streamData('');
881		}, this);
882
883		this._streamCleanUp = bindFunction(function()
884		{
885			this._input.removeListener('data', this._streamData);
886			this._input.removeListener('end', this._streamEnd);
887			this._input.removeListener('error', this._streamError);
888		}, this);
889	}
890	ReadableStreamStreamer.prototype = Object.create(ChunkStreamer.prototype);
891	ReadableStreamStreamer.prototype.constructor = ReadableStreamStreamer;
892
893
894	function DuplexStreamStreamer(_config) {
895		var Duplex = require('stream').Duplex;
896		var config = copy(_config);
897		var parseOnWrite = true;
898		var writeStreamHasFinished = false;
899		var parseCallbackQueue = [];
900		var stream = null;
901
902		this._onCsvData = function(results)
903		{
904			var data = results.data;
905			if (!stream.push(data) && !this._handle.paused()) {
906				// the writeable consumer buffer has filled up
907				// so we need to pause until more items
908				// can be processed
909				this._handle.pause();
910			}
911		};
912
913		this._onCsvComplete = function()
914		{
915			// node will finish the read stream when
916			// null is pushed
917			stream.push(null);
918		};
919
920		config.step = bindFunction(this._onCsvData, this);
921		config.complete = bindFunction(this._onCsvComplete, this);
922		ChunkStreamer.call(this, config);
923
924		this._nextChunk = function()
925		{
926			if (writeStreamHasFinished && parseCallbackQueue.length === 1) {
927				this._finished = true;
928			}
929			if (parseCallbackQueue.length) {
930				parseCallbackQueue.shift()();
931			} else {
932				parseOnWrite = true;
933			}
934		};
935
936		this._addToParseQueue = function(chunk, callback)
937		{
938			// add to queue so that we can indicate
939			// completion via callback
940			// node will automatically pause the incoming stream
941			// when too many items have been added without their
942			// callback being invoked
943			parseCallbackQueue.push(bindFunction(function() {
944				this.parseChunk(typeof chunk === 'string' ? chunk : chunk.toString(config.encoding));
945				if (isFunction(callback)) {
946					return callback();
947				}
948			}, this));
949			if (parseOnWrite) {
950				parseOnWrite = false;
951				this._nextChunk();
952			}
953		};
954
955		this._onRead = function()
956		{
957			if (this._handle.paused()) {
958				// the writeable consumer can handle more data
959				// so resume the chunk parsing
960				this._handle.resume();
961			}
962		};
963
964		this._onWrite = function(chunk, encoding, callback)
965		{
966			this._addToParseQueue(chunk, callback);
967		};
968
969		this._onWriteComplete = function()
970		{
971			writeStreamHasFinished = true;
972			// have to write empty string
973			// so parser knows its done
974			this._addToParseQueue('');
975		};
976
977		this.getStream = function()
978		{
979			return stream;
980		};
981		stream = new Duplex({
982			readableObjectMode: true,
983			decodeStrings: false,
984			read: bindFunction(this._onRead, this),
985			write: bindFunction(this._onWrite, this)
986		});
987		stream.once('finish', bindFunction(this._onWriteComplete, this));
988	}
989	if (typeof PAPA_BROWSER_CONTEXT === 'undefined') {
990		DuplexStreamStreamer.prototype = Object.create(ChunkStreamer.prototype);
991		DuplexStreamStreamer.prototype.constructor = DuplexStreamStreamer;
992	}
993
994
995	// Use one ParserHandle per entire CSV file or string
996	function ParserHandle(_config)
997	{
998		// One goal is to minimize the use of regular expressions...
999		var MAX_FLOAT = Math.pow(2, 53);
1000		var MIN_FLOAT = -MAX_FLOAT;
1001		var FLOAT = /^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i;
1002		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))/;
1003		var self = this;
1004		var _stepCounter = 0;	// Number of times step was called (number of rows parsed)
1005		var _rowCounter = 0;	// Number of rows that have been parsed so far
1006		var _input;				// The input being parsed
1007		var _parser;			// The core parser being used
1008		var _paused = false;	// Whether we are paused or not
1009		var _aborted = false;	// Whether the parser has aborted or not
1010		var _delimiterError;	// Temporary state between delimiter detection and processing results
1011		var _fields = [];		// Fields are from the header row of the input, if there is one
1012		var _results = {		// The last results returned from the parser
1013			data: [],
1014			errors: [],
1015			meta: {}
1016		};
1017
1018		if (isFunction(_config.step))
1019		{
1020			var userStep = _config.step;
1021			_config.step = function(results)
1022			{
1023				_results = results;
1024
1025				if (needsHeaderRow())
1026					processResults();
1027				else	// only call user's step function after header row
1028				{
1029					processResults();
1030
1031					// It's possbile that this line was empty and there's no row here after all
1032					if (_results.data.length === 0)
1033						return;
1034
1035					_stepCounter += results.data.length;
1036					if (_config.preview && _stepCounter > _config.preview)
1037						_parser.abort();
1038					else
1039						userStep(_results, self);
1040				}
1041			};
1042		}
1043
1044		/**
1045		 * Parses input. Most users won't need, and shouldn't mess with, the baseIndex
1046		 * and ignoreLastRow parameters. They are used by streamers (wrapper functions)
1047		 * when an input comes in multiple chunks, like from a file.
1048		 */
1049		this.parse = function(input, baseIndex, ignoreLastRow)
1050		{
1051			var quoteChar = _config.quoteChar || '"';
1052			if (!_config.newline)
1053				_config.newline = guessLineEndings(input, quoteChar);
1054
1055			_delimiterError = false;
vendor: 7,743 bytes, lines 1056-1355
1056			if (!_config.delimiter)
1057			{
1058				var delimGuess = guessDelimiter(input, _config.newline, _config.skipEmptyLines, _config.comments, _config.delimitersToGuess);
1059				if (delimGuess.successful)
1060					_config.delimiter = delimGuess.bestDelimiter;
1061				else
1062				{
1063					_delimiterError = true;	// add error after parsing (otherwise it would be overwritten)
1064					_config.delimiter = Papa.DefaultDelimiter;
1065				}
1066				_results.meta.delimiter = _config.delimiter;
1067			}
1068			else if(isFunction(_config.delimiter))
1069			{
1070				_config.delimiter = _config.delimiter(input);
1071				_results.meta.delimiter = _config.delimiter;
1072			}
1073
1074			var parserConfig = copy(_config);
1075			if (_config.preview && _config.header)
1076				parserConfig.preview++;	// to compensate for header row
1077
1078			_input = input;
1079			_parser = new Parser(parserConfig);
1080			_results = _parser.parse(_input, baseIndex, ignoreLastRow);
1081			processResults();
1082			return _paused ? { meta: { paused: true } } : (_results || { meta: { paused: false } });
1083		};
1084
1085		this.paused = function()
1086		{
1087			return _paused;
1088		};
1089
1090		this.pause = function()
1091		{
1092			_paused = true;
1093			_parser.abort();
1094			_input = _input.substr(_parser.getCharIndex());
1095		};
1096
1097		this.resume = function()
1098		{
1099			if(self.streamer._halted) {
1100				_paused = false;
1101				self.streamer.parseChunk(_input, true);
1102			} else {
1103				// Bugfix: #636 In case the processing hasn't halted yet
1104				// wait for it to halt in order to resume
1105				setTimeout(this.resume, 3);
1106			}
1107		};
1108
1109		this.aborted = function()
1110		{
1111			return _aborted;
1112		};
1113
1114		this.abort = function()
1115		{
1116			_aborted = true;
1117			_parser.abort();
1118			_results.meta.aborted = true;
1119			if (isFunction(_config.complete))
1120				_config.complete(_results);
1121			_input = '';
1122		};
1123
1124		function testEmptyLine(s) {
1125			return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0;
1126		}
1127
1128		function testFloat(s) {
1129			if (FLOAT.test(s)) {
1130				var floatValue = parseFloat(s);
1131				if (floatValue > MIN_FLOAT && floatValue < MAX_FLOAT) {
1132					return true;
1133				}
1134			}
1135			return false;
1136		}
1137
1138		function processResults()
1139		{
1140			if (_results && _delimiterError)
1141			{
1142				addError('Delimiter', 'UndetectableDelimiter', 'Unable to auto-detect delimiting character; defaulted to \'' + Papa.DefaultDelimiter + '\'');
1143				_delimiterError = false;
1144			}
1145
1146			if (_config.skipEmptyLines)
1147			{
1148				for (var i = 0; i < _results.data.length; i++)
1149					if (testEmptyLine(_results.data[i]))
1150						_results.data.splice(i--, 1);
1151			}
1152
1153			if (needsHeaderRow())
1154				fillHeaderFields();
1155
1156			return applyHeaderAndDynamicTypingAndTransformation();
1157		}
1158
1159		function needsHeaderRow()
1160		{
1161			return _config.header && _fields.length === 0;
1162		}
1163
1164		function fillHeaderFields()
1165		{
1166			if (!_results)
1167				return;
1168
1169			function addHeder(header)
1170			{
1171				if (isFunction(_config.transformHeader))
1172					header = _config.transformHeader(header);
1173
1174				_fields.push(header);
1175			}
1176
1177			if (Array.isArray(_results.data[0]))
1178			{
1179				for (var i = 0; needsHeaderRow() && i < _results.data.length; i++)
1180					_results.data[i].forEach(addHeder);
1181
1182				_results.data.splice(0, 1);
1183			}
1184			// if _results.data[0] is not an array, we are in a step where _results.data is the row.
1185			else
1186				_results.data.forEach(addHeder);
1187		}
1188
1189		function shouldApplyDynamicTyping(field) {
1190			// Cache function values to avoid calling it for each row
1191			if (_config.dynamicTypingFunction && _config.dynamicTyping[field] === undefined) {
1192				_config.dynamicTyping[field] = _config.dynamicTypingFunction(field);
1193			}
1194			return (_config.dynamicTyping[field] || _config.dynamicTyping) === true;
1195		}
1196
1197		function parseDynamic(field, value)
1198		{
1199			if (shouldApplyDynamicTyping(field))
1200			{
1201				if (value === 'true' || value === 'TRUE')
1202					return true;
1203				else if (value === 'false' || value === 'FALSE')
1204					return false;
1205				else if (testFloat(value))
1206					return parseFloat(value);
1207				else if (ISO_DATE.test(value))
1208					return new Date(value);
1209				else
1210					return (value === '' ? null : value);
1211			}
1212			return value;
1213		}
1214
1215		function applyHeaderAndDynamicTypingAndTransformation()
1216		{
1217			if (!_results || (!_config.header && !_config.dynamicTyping && !_config.transform))
1218				return _results;
1219
1220			function processRow(rowSource, i)
1221			{
1222				var row = _config.header ? {} : [];
1223
1224				var j;
1225				for (j = 0; j < rowSource.length; j++)
1226				{
1227					var field = j;
1228					var value = rowSource[j];
1229
1230					if (_config.header)
1231						field = j >= _fields.length ? '__parsed_extra' : _fields[j];
1232
1233					if (_config.transform)
1234						value = _config.transform(value,field);
1235
1236					value = parseDynamic(field, value);
1237
1238					if (field === '__parsed_extra')
1239					{
1240						row[field] = row[field] || [];
1241						row[field].push(value);
1242					}
1243					else
1244						row[field] = value;
1245				}
1246
1247
1248				if (_config.header)
1249				{
1250					if (j > _fields.length)
1251						addError('FieldMismatch', 'TooManyFields', 'Too many fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i);
1252					else if (j < _fields.length)
1253						addError('FieldMismatch', 'TooFewFields', 'Too few fields: expected ' + _fields.length + ' fields but parsed ' + j, _rowCounter + i);
1254				}
1255
1256				return row;
1257			}
1258
1259			var incrementBy = 1;
1260			if (!_results.data[0] || Array.isArray(_results.data[0]))
1261			{
1262				_results.data = _results.data.map(processRow);
1263				incrementBy = _results.data.length;
1264			}
1265			else
1266				_results.data = processRow(_results.data, 0);
1267
1268
1269			if (_config.header && _results.meta)
1270				_results.meta.fields = _fields;
1271
1272			_rowCounter += incrementBy;
1273			return _results;
1274		}
1275
1276		function guessDelimiter(input, newline, skipEmptyLines, comments, delimitersToGuess) {
1277			var bestDelim, bestDelta, fieldCountPrevRow, maxFieldCount;
1278
1279			delimitersToGuess = delimitersToGuess || [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP];
1280
1281			for (var i = 0; i < delimitersToGuess.length; i++) {
1282				var delim = delimitersToGuess[i];
1283				var delta = 0, avgFieldCount = 0, emptyLinesCount = 0;
1284				fieldCountPrevRow = undefined;
1285
1286				var preview = new Parser({
1287					comments: comments,
1288					delimiter: delim,
1289					newline: newline,
1290					preview: 10
1291				}).parse(input);
1292
1293				for (var j = 0; j < preview.data.length; j++) {
1294					if (skipEmptyLines && testEmptyLine(preview.data[j])) {
1295						emptyLinesCount++;
1296						continue;
1297					}
1298					var fieldCount = preview.data[j].length;
1299					avgFieldCount += fieldCount;
1300
1301					if (typeof fieldCountPrevRow === 'undefined') {
1302						fieldCountPrevRow = fieldCount;
1303						continue;
1304					}
1305					else if (fieldCount > 0) {
1306						delta += Math.abs(fieldCount - fieldCountPrevRow);
1307						fieldCountPrevRow = fieldCount;
1308					}
1309				}
1310
1311				if (preview.data.length > 0)
1312					avgFieldCount /= (preview.data.length - emptyLinesCount);
1313
1314				if ((typeof bestDelta === 'undefined' || delta <= bestDelta)
1315					&& (typeof maxFieldCount === 'undefined' || avgFieldCount > maxFieldCount) && avgFieldCount > 1.99) {
1316					bestDelta = delta;
1317					bestDelim = delim;
1318					maxFieldCount = avgFieldCount;
1319				}
1320			}
1321
1322			_config.delimiter = bestDelim;
1323
1324			return {
1325				successful: !!bestDelim,
1326				bestDelimiter: bestDelim
1327			};
1328		}
1329
1330		function guessLineEndings(input, quoteChar)
1331		{
1332			input = input.substr(0, 1024 * 1024);	// max length 1 MB
1333			// Replace all the text inside quotes
1334			var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
1335			input = input.replace(re, '');
1336
1337			var r = input.split('\r');
1338
1339			var n = input.split('\n');
1340
1341			var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);
1342
1343			if (r.length === 1 || nAppearsFirst)
1344				return '\n';
1345
1346			var numWithN = 0;
1347			for (var i = 0; i < r.length; i++)
1348			{
1349				if (r[i][0] === '\n')
1350					numWithN++;
1351			}
1352
1353			return numWithN >= r.length / 2 ? '\r\n' : '\r';
1354		}
1355
vendor: 6,843 bytes, lines 1356-1574
1356		function addError(type, code, msg, row)
1357		{
1358			_results.errors.push({
1359				type: type,
1360				code: code,
1361				message: msg,
1362				row: row
1363			});
1364		}
1365	}
1366
1367	/** https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions */
1368	function escapeRegExp(string)
1369	{
1370		return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
1371	}
1372
1373	/** The core parser implements speedy and correct CSV parsing */
1374	function Parser(config)
1375	{
1376		// Unpack the config object
1377		config = config || {};
1378		var delim = config.delimiter;
1379		var newline = config.newline;
1380		var comments = config.comments;
1381		var step = config.step;
1382		var preview = config.preview;
1383		var fastMode = config.fastMode;
1384		var quoteChar;
1385		/** Allows for no quoteChar by setting quoteChar to undefined in config */
1386		if (config.quoteChar === undefined) {
1387			quoteChar = '"';
1388		} else {
1389			quoteChar = config.quoteChar;
1390		}
1391		var escapeChar = quoteChar;
1392		if (config.escapeChar !== undefined) {
1393			escapeChar = config.escapeChar;
1394		}
1395
1396		// Delimiter must be valid
1397		if (typeof delim !== 'string'
1398			|| Papa.BAD_DELIMITERS.indexOf(delim) > -1)
1399			delim = ',';
1400
1401		// Comment character must be valid
1402		if (comments === delim)
1403			throw new Error('Comment character same as delimiter');
1404		else if (comments === true)
1405			comments = '#';
1406		else if (typeof comments !== 'string'
1407			|| Papa.BAD_DELIMITERS.indexOf(comments) > -1)
1408			comments = false;
1409
1410		// Newline must be valid: \r, \n, or \r\n
1411		if (newline !== '\n' && newline !== '\r' && newline !== '\r\n')
1412			newline = '\n';
1413
1414		// We're gonna need these at the Parser scope
1415		var cursor = 0;
1416		var aborted = false;
1417
1418		this.parse = function(input, baseIndex, ignoreLastRow)
1419		{
1420			// For some reason, in Chrome, this speeds things up (!?)
1421			if (typeof input !== 'string')
1422				throw new Error('Input must be a string');
1423
1424			// We don't need to compute some of these every time parse() is called,
1425			// but having them in a more local scope seems to perform better
1426			var inputLen = input.length,
1427				delimLen = delim.length,
1428				newlineLen = newline.length,
1429				commentsLen = comments.length;
1430			var stepIsFunction = isFunction(step);
1431
1432			// Establish starting state
1433			cursor = 0;
1434			var data = [], errors = [], row = [], lastCursor = 0;
1435
1436			if (!input)
1437				return returnable();
1438
1439			if (fastMode || (fastMode !== false && input.indexOf(quoteChar) === -1))
1440			{
1441				var rows = input.split(newline);
1442				for (var i = 0; i < rows.length; i++)
1443				{
1444					row = rows[i];
1445					cursor += row.length;
1446					if (i !== rows.length - 1)
1447						cursor += newline.length;
1448					else if (ignoreLastRow)
1449						return returnable();
1450					if (comments && row.substr(0, commentsLen) === comments)
1451						continue;
1452					if (stepIsFunction)
1453					{
1454						data = [];
1455						pushRow(row.split(delim));
1456						doStep();
1457						if (aborted)
1458							return returnable();
1459					}
1460					else
1461						pushRow(row.split(delim));
1462					if (preview && i >= preview)
1463					{
1464						data = data.slice(0, preview);
1465						return returnable(true);
1466					}
1467				}
1468				return returnable();
1469			}
1470
1471			var nextDelim = input.indexOf(delim, cursor);
1472			var nextNewline = input.indexOf(newline, cursor);
1473			var quoteCharRegex = new RegExp(escapeRegExp(escapeChar) + escapeRegExp(quoteChar), 'g');
1474			var quoteSearch = input.indexOf(quoteChar, cursor);
1475
1476			// Parser loop
1477			for (;;)
1478			{
1479				// Field has opening quote
1480				if (input[cursor] === quoteChar)
1481				{
1482					// Start our search for the closing quote where the cursor is
1483					quoteSearch = cursor;
1484
1485					// Skip the opening quote
1486					cursor++;
1487
1488					for (;;)
1489					{
1490						// Find closing quote
1491						quoteSearch = input.indexOf(quoteChar, quoteSearch + 1);
1492
1493						//No other quotes are found - no other delimiters
1494						if (quoteSearch === -1)
1495						{
1496							if (!ignoreLastRow) {
1497								// No closing quote... what a pity
1498								errors.push({
1499									type: 'Quotes',
1500									code: 'MissingQuotes',
1501									message: 'Quoted field unterminated',
1502									row: data.length,	// row has yet to be inserted
1503									index: cursor
1504								});
1505							}
1506							return finish();
1507						}
1508
1509						// Closing quote at EOF
1510						if (quoteSearch === inputLen - 1)
1511						{
1512							var value = input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar);
1513							return finish(value);
1514						}
1515
1516						// If this quote is escaped, it's part of the data; skip it
1517						// If the quote character is the escape character, then check if the next character is the escape character
1518						if (quoteChar === escapeChar &&  input[quoteSearch + 1] === escapeChar)
1519						{
1520							quoteSearch++;
1521							continue;
1522						}
1523
1524						// If the quote character is not the escape character, then check if the previous character was the escape character
1525						if (quoteChar !== escapeChar && quoteSearch !== 0 && input[quoteSearch - 1] === escapeChar)
1526						{
1527							continue;
1528						}
1529
1530						// Check up to nextDelim or nextNewline, whichever is closest
1531						var checkUpTo = nextNewline === -1 ? nextDelim : Math.min(nextDelim, nextNewline);
1532						var spacesBetweenQuoteAndDelimiter = extraSpaces(checkUpTo);
1533
1534						// Closing quote followed by delimiter or 'unnecessary spaces + delimiter'
1535						if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter] === delim)
1536						{
1537							row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
1538							cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen;
1539
1540							// If char after following delimiter is not quoteChar, we find next quote char position
1541							if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen] !== quoteChar)
1542							{
1543								quoteSearch = input.indexOf(quoteChar, cursor);
1544							}
1545							nextDelim = input.indexOf(delim, cursor);
1546							nextNewline = input.indexOf(newline, cursor);
1547							break;
1548						}
1549
1550						var spacesBetweenQuoteAndNewLine = extraSpaces(nextNewline);
1551
1552						// Closing quote followed by newline or 'unnecessary spaces + newLine'
1553						if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndNewLine, newlineLen) === newline)
1554						{
1555							row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
1556							saveRow(quoteSearch + 1 + spacesBetweenQuoteAndNewLine + newlineLen);
1557							nextDelim = input.indexOf(delim, cursor);	// because we may have skipped the nextDelim in the quoted field
1558							quoteSearch = input.indexOf(quoteChar, cursor);	// we search for first quote in next line
1559
1560							if (stepIsFunction)
1561							{
1562								doStep();
1563								if (aborted)
1564									return returnable();
1565							}
1566
1567							if (preview && data.length >= preview)
1568								return returnable(true);
1569
1570							break;
1571						}
1572
1573
1574						// Checks for valid closing quotes are complete (escaped quotes or quote followed by EOF/delimiter/newline) -- assume these quotes are part of an invali
1574d text string
1575						errors.push({
1576							type: 'Quotes',
1577							code: 'InvalidQuotes',
1578							message: 'Trailing quote on quoted field is malformed',
1579							row: data.length,	// row has yet to be inserted
1580							index: cursor
1581						});
1582
1583						quoteSearch++;
1584						continue;
1585
1586					}
1587
1588					continue;
1589				}
1590
1591				// Comment found at start of new line
1592				if (comments && row.length === 0 && input.substr(cursor, commentsLen) === comments)
1593				{
1594					if (nextNewline === -1)	// Comment ends at EOF
1595						return returnable();
1596					cursor = nextNewline + newlineLen;
1597					nextNewline = input.indexOf(newline, cursor);
1598					nextDelim = input.indexOf(delim, cursor);
1599					continue;
1600				}
1601
1602				// Next delimiter comes before next newline, so we've reached end of field
1603				if (nextDelim !== -1 && (nextDelim < nextNewline || nextNewline === -1))
1604				{
1605					// we check, if we have quotes, because delimiter char may be part of field enclosed in quotes
1606					if (quoteSearch !== -1) {
1607						// we have quotes, so we try to find the next delimiter not enclosed in quotes and also next starting quote char
1608						var nextDelimObj = getNextUnqotedDelimiter(nextDelim, quoteSearch, nextNewline);
1609
1610						// if we have next delimiter char which is not enclosed in quotes
1611						if (nextDelimObj && typeof nextDelimObj.nextDelim !== 'undefined') {
1612							nextDelim = nextDelimObj.nextDelim;
1613							quoteSearch = nextDelimObj.quoteSearch;
1614							row.push(input.substring(cursor, nextDelim));
1615							cursor = nextDelim + delimLen;
1616							// we look for next delimiter char
1617							nextDelim = input.indexOf(delim, cursor);
1618							continue;
1619						}
1620					} else {
1621						row.push(input.substring(cursor, nextDelim));
1622						cursor = nextDelim + delimLen;
1623						nextDelim = input.indexOf(delim, cursor);
1624						continue;
1625					}
1626				}
1627
1628				// End of row
1629				if (nextNewline !== -1)
1630				{
1631					row.push(input.substring(cursor, nextNewline));
1632					saveRow(nextNewline + newlineLen);
1633
1634					if (stepIsFunction)
1635					{
1636						doStep();
1637						if (aborted)
1638							return returnable();
1639					}
1640
1641					if (preview && data.length >= preview)
1642						return returnable(true);
1643
1644					continue;
1645				}
1646
1647				break;
1648			}
1649
1650
1651			return finish();
1652
1653
1654			function pushRow(row)
1655			{
1656				data.push(row);
1657				lastCursor = cursor;
1658			}
1659
1660			/**
1661             * checks if there are extra spaces after closing quote and given index without any text
1662             * if Yes, returns the number of spaces
1663             */
1664			function extraSpaces(index) {
1665				var spaceLength = 0;
1666				if (index !== -1) {
1667					var textBetweenClosingQuoteAndIndex = input.substring(quoteSearch + 1, index);
1668					if (textBetweenClosingQuoteAndIndex && textBetweenClosingQuoteAndIndex.trim() === '') {
1669						spaceLength = textBetweenClosingQuoteAndIndex.length;
1670					}
1671				}
1672				return spaceLength;
1673			}
1674
1675			/**
1676			 * Appends the remaining input from cursor to the end into
1677			 * row, saves the row, calls step, and returns the results.
1678			 */
1679			function finish(value)
1680			{
1681				if (ignoreLastRow)
1682					return returnable();
1683				if (typeof value === 'undefined')
1684					value = input.substr(cursor);
1685				row.push(value);
1686				cursor = inputLen;	// important in case parsing is paused
1687				pushRow(row);
1688				if (stepIsFunction)
1689					doStep();
1690				return returnable();
1691			}
1692
1693			/**
1694			 * Appends the current row to the results. It sets the cursor
1695			 * to newCursor and finds the nextNewline. The caller should
1696			 * take care to execute user's step function and check for
1697			 * preview and end parsing if necessary.
1698			 */
1699			function saveRow(newCursor)
1700			{
1701				cursor = newCursor;
1702				pushRow(row);
1703				row = [];
1704				nextNewline = input.indexOf(newline, cursor);
1705			}
1706
1707			/** Returns an object with the results, errors, and meta. */
1708			function returnable(stopped, step)
1709			{
1710				var isStep = step || false;
1711				return {
1712					data: isStep ? data[0]  : data,
1713					errors: errors,
1714					meta: {
1715						delimiter: delim,
1716						linebreak: newline,
1717						aborted: aborted,
1718						truncated: !!stopped,
1719						cursor: lastCursor + (baseIndex || 0)
1720					}
1721				};
1722			}
1723
1724			/** Executes the user's step function and resets data & errors. */
1725			function doStep()
1726			{
1727				step(returnable(undefined, true));
1728				data = [];
1729				errors = [];
1730			}
1731
1732			/** Gets the delimiter character, which is not inside the quoted field */
1733			function getNextUnqotedDelimiter(nextDelim, quoteSearch, newLine) {
1734				var result = {
1735					nextDelim: undefined,
1736					quoteSearch: undefined
1737				};
1738				// get the next closing quote character
1739				var nextQuoteSearch = input.indexOf(quoteChar, quoteSearch + 1);
1740
1741				// if next delimiter is part of a field enclosed in quotes
1742				if (nextDelim > quoteSearch && nextDelim < nextQuoteSearch && (nextQuoteSearch < newLine || newLine === -1)) {
1743					// get the next delimiter character after this one
1744					var nextNextDelim = input.indexOf(delim, nextQuoteSearch);
1745
1746					// if there is no next delimiter, return default result
1747					if (nextNextDelim === -1) {
1748						return result;
1749					}
1750					// find the next opening quote char position
1751					if (nextNextDelim > nextQuoteSearch) {
1752						nextQuoteSearch = input.indexOf(quoteChar, nextQuoteSearch + 1);
1753					}
1754					// try to get the next delimiter position
1755					result = getNextUnqotedDelimiter(nextNextDelim, nextQuoteSearch, newLine);
1756				} else {
1757					result = {
1758						nextDelim: nextDelim,
1759						quoteSearch: quoteSearch
1760					};
1761				}
1762
1763				return result;
1764			}
1765		};
1766
1767		/** Sets the abort flag */
1768		this.abort = function()
1769		{
1770			aborted = true;
1771		};
1772
1773		/** Gets the cursor position */
1774		this.getCharIndex = function()
1775		{
1776			return cursor;
1777		};
1778	}
1779
1780
1781	function newWorker()
1782	{
1783		if (!Papa.WORKERS_SUPPORTED)
1784			return false;
vendor: 2,648 bytes, lines 1785-1903
1785
1786		var workerUrl = getWorkerBlob();
1787		var w = new global.Worker(workerUrl);
1788		w.onmessage = mainThreadReceivedMessage;
1789		w.id = workerIdCounter++;
1790		workers[w.id] = w;
1791		return w;
1792	}
1793
1794	/** Callback when main thread receives a message */
1795	function mainThreadReceivedMessage(e)
1796	{
1797		var msg = e.data;
1798		var worker = workers[msg.workerId];
1799		var aborted = false;
1800
1801		if (msg.error)
1802			worker.userError(msg.error, msg.file);
1803		else if (msg.results && msg.results.data)
1804		{
1805			var abort = function() {
1806				aborted = true;
1807				completeWorker(msg.workerId, { data: [], errors: [], meta: { aborted: true } });
1808			};
1809
1810			var handle = {
1811				abort: abort,
1812				pause: notImplemented,
1813				resume: notImplemented
1814			};
1815
1816			if (isFunction(worker.userStep))
1817			{
1818				for (var i = 0; i < msg.results.data.length; i++)
1819				{
1820					worker.userStep({
1821						data: msg.results.data[i],
1822						errors: msg.results.errors,
1823						meta: msg.results.meta
1824					}, handle);
1825					if (aborted)
1826						break;
1827				}
1828				delete msg.results;	// free memory ASAP
1829			}
1830			else if (isFunction(worker.userChunk))
1831			{
1832				worker.userChunk(msg.results, handle, msg.file);
1833				delete msg.results;
1834			}
1835		}
1836
1837		if (msg.finished && !aborted)
1838			completeWorker(msg.workerId, msg.results);
1839	}
1840
1841	function completeWorker(workerId, results) {
1842		var worker = workers[workerId];
1843		if (isFunction(worker.userComplete))
1844			worker.userComplete(results);
1845		worker.terminate();
1846		delete workers[workerId];
1847	}
1848
1849	function notImplemented() {
1850		throw new Error('Not implemented.');
1851	}
1852
1853	/** Callback when worker thread receives a message */
1854	function workerThreadReceivedMessage(e)
1855	{
1856		var msg = e.data;
1857
1858		if (typeof Papa.WORKER_ID === 'undefined' && msg)
1859			Papa.WORKER_ID = msg.workerId;
1860
1861		if (typeof msg.input === 'string')
1862		{
1863			global.postMessage({
1864				workerId: Papa.WORKER_ID,
1865				results: Papa.parse(msg.input, msg.config),
1866				finished: true
1867			});
1868		}
1869		else if ((global.File && msg.input instanceof File) || msg.input instanceof Object)	// thank you, Safari (see issue #106)
1870		{
1871			var results = Papa.parse(msg.input, msg.config);
1872			if (results)
1873				global.postMessage({
1874					workerId: Papa.WORKER_ID,
1875					results: results,
1876					finished: true
1877				});
1878		}
1879	}
1880
1881	/** Makes a deep copy of an array or object (mostly) */
1882	function copy(obj)
1883	{
1884		if (typeof obj !== 'object' || obj === null)
1885			return obj;
1886		var cpy = Array.isArray(obj) ? [] : {};
1887		for (var key in obj)
1888			cpy[key] = copy(obj[key]);
1889		return cpy;
1890	}
1891
1892	function bindFunction(f, self)
1893	{
1894		return function() { f.apply(self, arguments); };
1895	}
1896
1897	function isFunction(func)
1898	{
1899		return typeof func === 'function';
1900	}
1901
1902	return Papa;
1903}));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.