diff --git a/.eslintrc.js b/.eslintrc.js index c3ed7c56..97a67265 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,8 +1,9 @@ module.exports = { "parserOptions": { - "ecmaVersion": 5 + "ecmaVersion": 6 }, "env": { + "es6": true, "browser": true, "worker": true, "node": true diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 3072b48a..ed0b6d40 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x, 15.x] + node-version: [20.x, 22.x, 24.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: @@ -26,4 +26,4 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm install - - run: npm test + - run: npm run lint && npm run test-node && npm run build diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f00e0fbb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -language: node_js -node_js: - - "8" - - "9" - - "10" - - "11" diff --git a/README.md b/README.md index 45313c3b..28ab0f8f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ Parse CSV with JavaScript ======================================== -Papa Parse is the [fastest](http://jsperf.com/javascript-csv-parsers/4) in-browser CSV (or delimited text) parser for JavaScript. It is reliable and correct according to [RFC 4180](https://tools.ietf.org/html/rfc4180), and it comes with these features: +Papa Parse is the fastest in-browser CSV (or delimited text) parser for JavaScript. It is reliable and correct according to [RFC 4180](https://tools.ietf.org/html/rfc4180), and it comes with these features: - Easy to use - Parse CSV files directly (local or over the network) -- Fast mode ([is really fast](http://jsperf.com/javascript-csv-parsers/3)) +- Fast mode - Stream large files (even via HTTP) - Reverse parsing (converts JSON to CSV) - Auto-detect delimiter @@ -23,21 +23,31 @@ Install papaparse is available on [npm](https://www.npmjs.com/package/papaparse). It can be installed with the following command: - - npm install papaparse +```shell +npm install papaparse +``` If you don't want to use npm, [papaparse.min.js](https://unpkg.com/papaparse@latest/papaparse.min.js) can be downloaded to your project source. +Usage +----- +```js +import Papa from 'papaparse'; + +Papa.parse(file, config); + +const csv = Papa.unparse(data[, config]); +``` Homepage & Demo ---------------- -- [Homepage](http://papaparse.com) -- [Demo](http://papaparse.com/demo) +- [Homepage](https://www.papaparse.com) +- [Demo](https://www.papaparse.com/demo) To learn how to use Papa Parse: -- [Documentation](http://papaparse.com/docs) +- [Documentation](https://www.papaparse.com/docs) The website is hosted on [Github Pages](https://pages.github.com/). Its content is also included in the docs folder of this repository. If you want to contribute on it just clone the master of this repository and open a pull request. @@ -52,7 +62,7 @@ Papa Parse can also parse in a node streaming style which makes `.pipe` availabl Get Started ----------- -For usage instructions, see the [homepage](http://papaparse.com) and, for more detail, the [documentation](http://papaparse.com/docs). +For usage instructions, see the [homepage](https://www.papaparse.com) and, for more detail, the [documentation](https://www.papaparse.com/docs). Tests ----- diff --git a/docs/docs.html b/docs/docs.html index da3c8b62..ac39363f 100644 --- a/docs/docs.html +++ b/docs/docs.html @@ -303,7 +303,7 @@
delimiter
escapeFormulae
true, field values that begin with =, +, -, or @, will be prepended with a ' to defend against injection attacks, because Excel and LibreOffice will automatically parse such cells as formulae.
+ If true, field values that begin with =, +, -, @, \t, or \r, will be prepended with a ' to defend against injection attacks, because Excel and LibreOffice will automatically parse such cells as formulae. You can override those values by setting this option to a regular expression
delimiter
delimitersToGuess. It can be a string or a function. If string, it must be one of length 1. If a function, it must accept the input as first parameter and it must return a string which will be used as delimiter. In both cases it cannot be found in Papa.BAD_DELIMITERS.
+ The delimiting character. Leave blank to auto-detect from a list of most common delimiters, or any values passed in through delimitersToGuess. It can be a string or a function. If a string, it can be of any length (so multi-character delimiters are supported). If a function, it must accept the input as first parameter and it must return a string which will be used as delimiter. In both cases it cannot be found in Papa.BAD_DELIMITERS.
header
transformHeader) are stored in ParseResult.meta.renamedHeaders
dynamicTyping
2^53 or less than -2^53 will not be converted to numbers to preserve precision. European-formatted numbers must have commas and dots swapped. If also accepts an object or a function. If object it's values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
+ If true, numeric and boolean data will be converted to their type instead of remaining strings. Numeric data must conform to the definition of a decimal literal. Numerical values greater than 2^53 or less than -2^53 will not be converted to numbers to preserve precision. European-formatted numbers must have commas and dots swapped. It also accepts an object or a function. If it's an object, its values should be a boolean to indicate if dynamic typing should be applied for each column number (or header name if using headers). If it's a function, it should return a boolean value for each field number (or name if using headers) which will be passed as first argument.
delimiter option is not set.
skipFirstNLines
+ Papa.BAD_DELIMITERS\r, \n, ", \ufeff).
+ Papa.BYTE_ORDER_MARK\ufeff).
Yes, Paparse supports Node. See our READMEfor further details. +
Yes, Paparse supports Node. See our README for further details.
diff --git a/docs/resources/js/lovers.js b/docs/resources/js/lovers.js
index 530a1644..1c939510 100644
--- a/docs/resources/js/lovers.js
+++ b/docs/resources/js/lovers.js
@@ -26,6 +26,18 @@
**/
var peopleLovePapa = [
+ {
+ link: "http://doctempleapp.com",
+ name: "DocTemple",
+ description: "helps you effortlessly populate .docx documents with .csv data using scalable templating solution.",
+ quote: "Papa Parse made it extremely easy to load user CSV files in the browser. Implementing it was a breeze and it works perfect!"
+ },
+ {
+ link: "https://www.vertex.io",
+ name: "The no-code database of the future.",
+ description: "No-code, Postgres-powered admin, internal tools, and backend suite.",
+ quote: "Vertex uses Papa to power all CSV related features!"
+ },
{
link: "https://www.circlehd.com",
name: "CircleHD Enterprise Video Platform",
@@ -55,24 +67,6 @@ var peopleLovePapa = [
name: "Wikipedia",
description: "uses Papa Parse in VisualEditor to help article editors effortlessly build data tables from text files."
},
- {
- link: "https://www.webucator.com/webdesign/javascript.cfm",
- name: "Webucator",
- description: "created a video showing how to use Papa Parse and FileDrop.js to create a drag-and-drop CSV-JSON converter.",
- quote: "It's often easy to convert data to CSV. With Papa, it's easy to turn that CSV into JSON."
- },
- {
- link: "http://www.yolpo.com/social/gist.github?1dbd4556e748bdb830b3&autoplay=1&interimresults=0&failfast=1",
- name: "Yolpo",
- description: "created a simple regression test for Papa Parse.",
- quote: "Papa's API is so intuitive, it took me no time to get it to work."
- },
- {
- link: "https://www.appstax.com",
- name: "Appstax",
- description: "uses Papa Parse to import and export CSV data in their visual databrowser.",
- quote: "Papa is a great for parsing CSV. And what a great tone of voice - love it!"
- },
{
link: "https://github.com/Nanofus/novel.js",
name: "Novel.js",
@@ -97,12 +91,6 @@ var peopleLovePapa = [
description: "is a brand-new messaging app made specifically for busy families. Automatically align all family members when sending text messages to parents in the kindergarten or school or when planning your kids birthday parties.",
quote: "With Papa it was a joy to implement our tool for importing messages and places from external systems."
},
- {
- link: "https://explore.hua-gallery.com",
- name: "Hua Explore",
- description: "The premier destination for information on Contemporary Chinese Art.",
- quote: "Papa makes processing data that galleries send us totally seamless."
- },
{
link: "https://monei.net",
name: "MONEI",
@@ -126,5 +114,17 @@ var peopleLovePapa = [
name: "Visa SOP Sample",
description: "Providing free guide to international students.",
quote: "Use Papa Parse for many of side projects. Super fast and works all the time. Love it!"
+ },
+ {
+ link: "https://retool.com/",
+ name: "Retool",
+ description: "A remarkably fast way to build internal tools.",
+ quote: "Papa makes it easy for our users to customize CSV parsing to match their business logic."
+ },
+ {
+ link: "https://www.hellodata.ai/",
+ name: "HelloData",
+ description: "Automatic rent surveys with real-time data on over 25M multifamily units nationwide, direct from property websites.",
+ quote: "Papa Parse makes bulk data imports a breeze! It's helped us easily onboard our largest customers. It's robust and a true game-changer."
}
];
diff --git a/docs/resources/js/papaparse.js b/docs/resources/js/papaparse.js
index ac02f6d6..9dd7fa27 100644
--- a/docs/resources/js/papaparse.js
+++ b/docs/resources/js/papaparse.js
@@ -491,6 +491,16 @@ License: MIT
this.parseChunk = function(chunk, isFakeChunk)
{
// First chunk pre-processing
+ const skipFirstNLines = parseInt(this._config.skipFirstNLines) || 0;
+ if (this.isFirstChunk && skipFirstNLines > 0) {
+ let _newline = this._config.newline;
+ if (!_newline) {
+ const quoteChar = this._config.quoteChar || '"';
+ _newline = this._handle.guessLineEndings(chunk, quoteChar);
+ }
+ const splitChunk = chunk.split(_newline);
+ chunk = [...splitChunk.slice(skipFirstNLines)].join(_newline);
+ }
if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk))
{
var modifiedChunk = this._config.beforeFirstChunk(chunk);
@@ -503,7 +513,6 @@ License: MIT
// Rejoin the line we likely just split in two by chunking the file
var aggregate = this._partialLine + chunk;
this._partialLine = '';
-
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);
if (this._handle.paused() || this._handle.aborted()) {
@@ -1048,7 +1057,7 @@ License: MIT
{
var quoteChar = _config.quoteChar || '"';
if (!_config.newline)
- _config.newline = guessLineEndings(input, quoteChar);
+ _config.newline = this.guessLineEndings(input, quoteChar);
_delimiterError = false;
if (!_config.delimiter)
@@ -1119,6 +1128,32 @@ License: MIT
_input = '';
};
+ this.guessLineEndings = function(input, quoteChar)
+ {
+ input = input.substr(0, 1024 * 1024); // max length 1 MB
+ // Replace all the text inside quotes
+ var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
+ input = input.replace(re, '');
+
+ var r = input.split('\r');
+
+ var n = input.split('\n');
+
+ var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);
+
+ if (r.length === 1 || nAppearsFirst)
+ return '\n';
+
+ var numWithN = 0;
+ for (var i = 0; i < r.length; i++)
+ {
+ if (r[i][0] === '\n')
+ numWithN++;
+ }
+
+ return numWithN >= r.length / 2 ? '\r\n' : '\r';
+ };
+
function testEmptyLine(s) {
return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0;
}
@@ -1321,32 +1356,6 @@ License: MIT
};
}
- function guessLineEndings(input, quoteChar)
- {
- input = input.substr(0, 1024 * 1024); // max length 1 MB
- // Replace all the text inside quotes
- var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
- input = input.replace(re, '');
-
- var r = input.split('\r');
-
- var n = input.split('\n');
-
- var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);
-
- if (r.length === 1 || nAppearsFirst)
- return '\n';
-
- var numWithN = 0;
- for (var i = 0; i < r.length; i++)
- {
- if (r[i][0] === '\n')
- numWithN++;
- }
-
- return numWithN >= r.length / 2 ? '\r\n' : '\r';
- }
-
function addError(type, code, msg, row)
{
_results.errors.push({
diff --git a/package.json b/package.json
index 6404a35e..013a4b0c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "papaparse",
- "version": "5.3.0",
+ "version": "5.5.3",
"description": "Fast and powerful CSV parser for the browser that supports web workers and streaming large files. Converts CSV to JSON and JSON to CSV.",
"keywords": [
"csv",
@@ -24,10 +24,10 @@
"multi-threaded",
"jquery-plugin"
],
- "homepage": "http://papaparse.com",
+ "homepage": "https://www.papaparse.com/",
"repository": {
"type": "git",
- "url": "https://github.com/mholt/PapaParse.git"
+ "url": "git+https://github.com/mholt/PapaParse.git"
},
"author": {
"name": "Matthew Holt",
@@ -40,15 +40,16 @@
"chai": "^4.2.0",
"connect": "^3.3.3",
"eslint": "^4.19.1",
- "grunt": "^1.0.2",
- "grunt-contrib-uglify": "^3.3.0",
+ "grunt": "^1.5.2",
+ "grunt-contrib-uglify": "^5.2.0",
"mocha": "^5.2.0",
- "mocha-headless-chrome": "^2.0.1",
+ "mocha-headless-chrome": "^4.0.0",
"open": "7.0.0",
"serve-static": "^1.7.1"
},
"scripts": {
"lint": "eslint --no-ignore papaparse.js Gruntfile.js .eslintrc.js 'tests/**/*.js'",
+ "build": "grunt build",
"test-browser": "node tests/test.js",
"test-mocha-headless-chrome": "node tests/test.js --mocha-headless-chrome",
"test-node": "mocha tests/node-tests.js tests/test-cases.js",
diff --git a/papaparse.js b/papaparse.js
index 26db15cf..2c7fd178 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -1,6 +1,6 @@
/* @license
Papa Parse
-v5.3.0
+v5.5.3
https://github.com/mholt/PapaParse
License: MIT
*/
@@ -49,11 +49,12 @@ License: MIT
function getWorkerBlob() {
var URL = global.URL || global.webkitURL || null;
var code = moduleFactory.toString();
- return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(['(', code, ')();'], {type: 'text/javascript'})));
+ return Papa.BLOB_URL || (Papa.BLOB_URL = URL.createObjectURL(new Blob(["var global = (function() { if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } if (typeof global !== 'undefined') { return global; } return {}; })(); global.IS_PAPA_WORKER=true; ", '(', code, ')();'], {type: 'text/javascript'})));
}
var IS_WORKER = !global.document && !!global.postMessage,
- IS_PAPA_WORKER = IS_WORKER && /blob:/i.test((global.location || {}).protocol);
+ IS_PAPA_WORKER = global.IS_PAPA_WORKER || false;
+
var workers = {}, workerIdCounter = 0;
var Papa = {};
@@ -184,8 +185,13 @@ License: MIT
global.onmessage = workerThreadReceivedMessage;
}
-
-
+ // Strip character from UTF-8 BOM encoded files that cause issue parsing the file
+ function stripBom(string) {
+ if (string.charCodeAt(0) === 0xfeff) {
+ return string.slice(1);
+ }
+ return string;
+ }
function CsvToJson(_input, _config)
{
@@ -234,6 +240,7 @@ License: MIT
}
else if (typeof _input === 'string')
{
+ _input = stripBom(_input);
if (_config.download)
streamer = new NetworkStreamer(_config);
else
@@ -307,7 +314,7 @@ License: MIT
if (Array.isArray(_input.data))
{
if (!_input.fields)
- _input.fields = _input.meta && _input.meta.fields;
+ _input.fields = _input.meta && _input.meta.fields || _columns;
if (!_input.fields)
_input.fields = Array.isArray(_input.data[0])
@@ -367,11 +374,13 @@ License: MIT
_escapedQuote = _config.escapeChar + _quoteChar;
}
- if (typeof _config.escapeFormulae === 'boolean')
+ if (_config.escapeFormulae instanceof RegExp) {
_escapeFormulae = _config.escapeFormulae;
+ } else if (typeof _config.escapeFormulae === 'boolean' && _config.escapeFormulae) {
+ _escapeFormulae = /^[=+\-@\t\r].*$/;
+ }
}
-
/** The double for loop that iterates the data and writes out a CSV string including header row */
function serialize(fields, data, skipEmptyLines)
{
@@ -444,13 +453,17 @@ License: MIT
if (str.constructor === Date)
return JSON.stringify(str).slice(1, 25);
- if (_escapeFormulae === true && typeof str === "string" && (str.match(/^[=+\-@].*$/) !== null)) {
+ var needsQuotes = false;
+
+ if (_escapeFormulae && typeof str === "string" && _escapeFormulae.test(str)) {
str = "'" + str;
+ needsQuotes = true;
}
var escapedQuoteStr = str.toString().replace(quoteCharRegex, _escapedQuote);
- var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
+ needsQuotes = needsQuotes
+ || _quotes === true
|| (typeof _quotes === 'function' && _quotes(str, col))
|| (Array.isArray(_quotes) && _quotes[col])
|| hasAny(escapedQuoteStr, Papa.BAD_DELIMITERS)
@@ -470,6 +483,7 @@ License: MIT
}
}
+
/** ChunkStreamer is the base prototype for various streamer implementations. */
function ChunkStreamer(config)
{
@@ -494,6 +508,16 @@ License: MIT
this.parseChunk = function(chunk, isFakeChunk)
{
// First chunk pre-processing
+ const skipFirstNLines = parseInt(this._config.skipFirstNLines) || 0;
+ if (this.isFirstChunk && skipFirstNLines > 0) {
+ let _newline = this._config.newline;
+ if (!_newline) {
+ const quoteChar = this._config.quoteChar || '"';
+ _newline = this._handle.guessLineEndings(chunk, quoteChar);
+ }
+ const splitChunk = chunk.split(_newline);
+ chunk = [...splitChunk.slice(skipFirstNLines)].join(_newline);
+ }
if (this.isFirstChunk && isFunction(this._config.beforeFirstChunk))
{
var modifiedChunk = this._config.beforeFirstChunk(chunk);
@@ -506,7 +530,6 @@ License: MIT
// Rejoin the line we likely just split in two by chunking the file
var aggregate = this._partialLine + chunk;
this._partialLine = '';
-
var results = this._handle.parse(aggregate, this._baseIndex, !this._finished);
if (this._handle.paused() || this._handle.aborted()) {
@@ -1008,7 +1031,7 @@ License: MIT
var MAX_FLOAT = Math.pow(2, 53);
var MIN_FLOAT = -MAX_FLOAT;
var FLOAT = /^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/;
- var ISO_DATE = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
+ var ISO_DATE = /^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;
var self = this;
var _stepCounter = 0; // Number of times step was called (number of rows parsed)
var _rowCounter = 0; // Number of rows that have been parsed so far
@@ -1061,7 +1084,7 @@ License: MIT
{
var quoteChar = _config.quoteChar || '"';
if (!_config.newline)
- _config.newline = guessLineEndings(input, quoteChar);
+ _config.newline = this.guessLineEndings(input, quoteChar);
_delimiterError = false;
if (!_config.delimiter)
@@ -1135,6 +1158,32 @@ License: MIT
_input = '';
};
+ this.guessLineEndings = function(input, quoteChar)
+ {
+ input = input.substring(0, 1024 * 1024); // max length 1 MB
+ // Replace all the text inside quotes
+ var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
+ input = input.replace(re, '');
+
+ var r = input.split('\r');
+
+ var n = input.split('\n');
+
+ var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);
+
+ if (r.length === 1 || nAppearsFirst)
+ return '\n';
+
+ var numWithN = 0;
+ for (var i = 0; i < r.length; i++)
+ {
+ if (r[i][0] === '\n')
+ numWithN++;
+ }
+
+ return numWithN >= r.length / 2 ? '\r\n' : '\r';
+ };
+
function testEmptyLine(s) {
return _config.skipEmptyLines === 'greedy' ? s.join('').trim() === '' : s.length === 1 && s[0].length === 0;
}
@@ -1159,9 +1208,9 @@ License: MIT
if (_config.skipEmptyLines)
{
- for (var i = 0; i < _results.data.length; i++)
- if (testEmptyLine(_results.data[i]))
- _results.data.splice(i--, 1);
+ _results.data = _results.data.filter(function(d) {
+ return !testEmptyLine(d);
+ });
}
if (needsHeaderRow())
@@ -1182,6 +1231,7 @@ License: MIT
function addHeader(header, i)
{
+ header = stripBom(header);
if (isFunction(_config.transformHeader))
header = _config.transformHeader(header, i);
@@ -1341,32 +1391,6 @@ License: MIT
};
}
- function guessLineEndings(input, quoteChar)
- {
- input = input.substring(0, 1024 * 1024); // max length 1 MB
- // Replace all the text inside quotes
- var re = new RegExp(escapeRegExp(quoteChar) + '([^]*?)' + escapeRegExp(quoteChar), 'gm');
- input = input.replace(re, '');
-
- var r = input.split('\r');
-
- var n = input.split('\n');
-
- var nAppearsFirst = (n.length > 1 && n[0].length < r[0].length);
-
- if (r.length === 1 || nAppearsFirst)
- return '\n';
-
- var numWithN = 0;
- for (var i = 0; i < r.length; i++)
- {
- if (r[i][0] === '\n')
- numWithN++;
- }
-
- return numWithN >= r.length / 2 ? '\r\n' : '\r';
- }
-
function addError(type, code, msg, row)
{
var error = {
@@ -1399,8 +1423,10 @@ License: MIT
var preview = config.preview;
var fastMode = config.fastMode;
var quoteChar;
- /** Allows for no quoteChar by setting quoteChar to undefined in config */
- if (config.quoteChar === undefined) {
+ var renamedHeaders = null;
+ var headerParsed = false;
+
+ if (config.quoteChar === undefined || config.quoteChar === null) {
quoteChar = '"';
} else {
quoteChar = config.quoteChar;
@@ -1460,6 +1486,7 @@ License: MIT
{
row = rows[i];
cursor += row.length;
+
if (i !== rows.length - 1)
cursor += newline.length;
else if (ignoreLastRow)
@@ -1555,7 +1582,7 @@ License: MIT
var spacesBetweenQuoteAndDelimiter = extraSpaces(checkUpTo);
// Closing quote followed by delimiter or 'unnecessary spaces + delimiter'
- if (input[quoteSearch + 1 + spacesBetweenQuoteAndDelimiter] === delim)
+ if (input.substr(quoteSearch + 1 + spacesBetweenQuoteAndDelimiter, delimLen) === delim)
{
row.push(input.substring(cursor, quoteSearch).replace(quoteCharRegex, quoteChar));
cursor = quoteSearch + 1 + spacesBetweenQuoteAndDelimiter + delimLen;
@@ -1654,7 +1681,6 @@ License: MIT
break;
}
-
return finish();
@@ -1714,6 +1740,48 @@ License: MIT
/** Returns an object with the results, errors, and meta. */
function returnable(stopped)
{
+ if (config.header && !baseIndex && data.length && !headerParsed)
+ {
+ const result = data[0];
+ const headerCount = Object.create(null); // To track the count of each base header
+ const usedHeaders = new Set(result); // To track used headers and avoid duplicates
+ let duplicateHeaders = false;
+
+ for (let i = 0; i < result.length; i++) {
+ let header = stripBom(result[i]);
+ if (isFunction(config.transformHeader))
+ header = config.transformHeader(header, i);
+
+ if (!headerCount[header]) {
+ headerCount[header] = 1;
+ result[i] = header;
+ } else {
+ let newHeader;
+ let suffixCount = headerCount[header];
+
+ // Find a unique new header
+ do {
+ newHeader = `${header}_${suffixCount}`;
+ suffixCount++;
+ } while (usedHeaders.has(newHeader));
+
+ usedHeaders.add(newHeader); // Mark this new Header as used
+ result[i] = newHeader;
+ headerCount[header]++;
+ duplicateHeaders = true;
+ if (renamedHeaders === null) {
+ renamedHeaders = {};
+ }
+ renamedHeaders[newHeader] = header;
+ }
+
+ usedHeaders.add(header); // Ensure the original header is marked as used
+ }
+ if (duplicateHeaders) {
+ console.warn('Duplicate headers found and renamed.');
+ }
+ headerParsed = true;
+ }
return {
data: data,
errors: errors,
@@ -1722,7 +1790,8 @@ License: MIT
linebreak: newline,
aborted: aborted,
truncated: !!stopped,
- cursor: lastCursor + (baseIndex || 0)
+ cursor: lastCursor + (baseIndex || 0),
+ renamedHeaders: renamedHeaders
}
};
}
@@ -1865,7 +1934,6 @@ License: MIT
{
return function() { f.apply(self, arguments); };
}
-
function isFunction(func)
{
return typeof func === 'function';
diff --git a/papaparse.min.js b/papaparse.min.js
index a92afc17..f3141104 100644
--- a/papaparse.min.js
+++ b/papaparse.min.js
@@ -1,7 +1,7 @@
/* @license
Papa Parse
-v5.3.0
+v5.5.3
https://github.com/mholt/PapaParse
License: MIT
*/
-!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof module&&"undefined"!=typeof exports?module.exports=t():e.Papa=t()}(this,function s(){"use strict";var f="undefined"!=typeof self?self:"undefined"!=typeof window?window:void 0!==f?f:{};var n=!f.document&&!!f.postMessage,o=n&&/blob:/i.test((f.location||{}).protocol),a={},h=0,b={parse:function(e,t){var i=(t=t||{}).dynamicTyping||!1;U(i)&&(t.dynamicTypingFunction=i,i={});if(t.dynamicTyping=i,t.transform=!!U(t.transform)&&t.transform,t.worker&&b.WORKERS_SUPPORTED){var r=function(){if(!b.WORKERS_SUPPORTED)return!1;var e=(i=f.URL||f.webkitURL||null,r=s.toString(),b.BLOB_URL||(b.BLOB_URL=i.createObjectURL(new Blob(["(",r,")();"],{type:"text/javascript"})))),t=new f.Worker(e);var i,r;return t.onmessage=m,t.id=h++,a[t.id]=t}();return r.userStep=t.step,r.userChunk=t.chunk,r.userComplete=t.complete,r.userError=t.error,t.step=U(t.step),t.chunk=U(t.chunk),t.complete=U(t.complete),t.error=U(t.error),delete t.worker,void r.postMessage({input:e,config:t,workerId:r.id})}var n=null;b.NODE_STREAM_INPUT,"string"==typeof e?n=t.download?new l(t):new p(t):!0===e.readable&&U(e.read)&&U(e.on)?n=new g(t):(f.File&&e instanceof File||e instanceof Object)&&(n=new c(t));return n.stream(e)},unparse:function(e,t){var n=!1,m=!0,_=",",v="\r\n",s='"',a=s+s,i=!1,r=null,o=!1;!function(){if("object"!=typeof t)return;"string"!=typeof t.delimiter||b.BAD_DELIMITERS.filter(function(e){return-1!==t.delimiter.indexOf(e)}).length||(_=t.delimiter);("boolean"==typeof t.quotes||"function"==typeof t.quotes||Array.isArray(t.quotes))&&(n=t.quotes);"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(i=t.skipEmptyLines);"string"==typeof t.newline&&(v=t.newline);"string"==typeof t.quoteChar&&(s=t.quoteChar);"boolean"==typeof t.header&&(m=t.header);if(Array.isArray(t.columns)){if(0===t.columns.length)throw new Error("Option columns is empty");r=t.columns}void 0!==t.escapeChar&&(a=t.escapeChar+s);"boolean"==typeof t.escapeFormulae&&(o=t.escapeFormulae)}();var h=new RegExp(q(s),"g");"string"==typeof e&&(e=JSON.parse(e));if(Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return f(null,e,i);if("object"==typeof e[0])return f(r||u(e[0]),e,i)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:u(e.data[0])),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),f(e.fields||[],e.data||[],i);throw new Error("Unable to serialize unrecognized input");function u(e){if("object"!=typeof e)return[];var t=[];for(var i in e)t.push(i);return t}function f(e,t,i){var r="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=Array.isArray(e)&&0