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.