Repository: mholt/PapaParse
Pull request #956 (2022-10-24 12:52:38)

Added support for renaming duplicate headers
Added code to automatically rename headers to solve the issue in #129 

I tried to add a renameDuplicateHeaders config option but left it out as I am doing the processing in the Parse.parse function where I don't have the ability to read the config. I'm not sure it fits the styling of the codebase to modify the input outside of that function so have left it out.

fortydegrees: Thanks for the quick feedback & sorry for the misunderstanding re: test cases.

I've added a test case, removed the useless comments, and added a conditional based on whether the header is present. 

You were right - the renaming code could be refactored. I originally just took the code from that issue thread which worked, but rewrote it in a simpler way which produces the same result.

Patch:
diff --git a/docs/resources/js/lovers.js b/docs/resources/js/lovers.js
index 27363ca..7d9073d 100644
--- a/docs/resources/js/lovers.js
+++ b/docs/resources/js/lovers.js
@@ -26,6 +26,12 @@
 **/
 
 var peopleLovePapa = [
+	{
+		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",
diff --git a/papaparse.js b/papaparse.js
index 60ee489..6c52906 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -1013,7 +1013,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
@@ -1457,6 +1457,37 @@ License: MIT
 			if (!input)
 				return returnable();
 
+			// Rename headers if there are duplicates
+			if (config.header)
+			{
+				var firstLine = input.split(newline)[0];
+				var headers = firstLine.split(delim);
+				var separator = '_';
+				var headerMap = [];
+				var headerCount = {};
+				var duplicateHeaders = false;
+
+				for (var j in headers) {
+					var header = headers[j];
+					if (isFunction(config.transformHeader))
+						header = config.transformHeader(header, j);
+					var headerName = header;
+
+					var count = headerCount[header] || 0;
+					if (count > 0) {
+						duplicateHeaders = true;
+						headerName = header + separator + count;
+					}
+					headerCount[header] = count + 1;
+
+					headerMap.push(headerName);
+				}
+				if (duplicateHeaders) {
+					var editedInput = input.split(newline);
+					editedInput[0] = headerMap.join(delim);
+					input = editedInput.join(newline);
+				}
+			}
 			if (fastMode || (fastMode !== false && input.indexOf(quoteChar) === -1))
 			{
 				var rows = input.split(newline);


Test patch:
diff --git a/tests/test-cases.js b/tests/test-cases.js
index 75a20db..843ba33 100644
--- a/tests/test-cases.js
+++ b/tests/test-cases.js
@@ -585,7 +585,25 @@ var CORE_PARSER_TESTS = [
 			data: [['a', 'b', 'c'], ['']],
 			errors: []
 		}
-	}
+	},
+	{
+		description: "Simple duplicate header names",
+		input: 'A,A,A,A\n1,2,3,4',
+		config: { header: true },
+		expected: {
+			data: [['A', 'A_1', 'A_2', 'A_3'], ['1', '2', '3', '4']],
+			errors: []
+		}
+	},
+	{
+		description: "Duplicate header names with headerTransform",
+		input: 'A,A,A,A\n1,2,3,4',
+		config: { header: true, transformHeader: function(header) { return header.toLowerCase(); } },
+		expected: {
+			data: [['a', 'a_1', 'a_2', 'a_3'], ['1', '2', '3', '4']],
+			errors: []
+		}
+	},
 ];
 
 describe('Core Parser Tests', function() {