Repository: mholt/PapaParse
Pull request #631 (2019-02-08 18:14:48)

add ability to support escapeChar on unparse
This was requested/referenced in #620 (docs only) - this PR adds support for `escapeChar` to be used in `unparse` 


Patch:
diff --git a/docs/docs.html b/docs/docs.html
index dd37e50..df87a72 100644
--- a/docs/docs.html
+++ b/docs/docs.html
@@ -258,10 +258,12 @@
 {
 	quotes: false, //or array of booleans
 	quoteChar: '"',
+	escapeChar: '"',
 	delimiter: ",",
 	header: true,
 	newline: "\r\n",
-	skipEmptyLines: false, //or 'greedy'
+	skipEmptyLines: false, //or 'greedy',
+	columns: null //or array of strings
 }
 					</code></pre>
 				</div>
@@ -292,6 +294,12 @@
 									The character used to quote fields.
 								</td>
 							</tr>
+							<tr>
+								<td><code>escapeChar</code></td>
+								<td>
+									The character used to escape <code>quoteChar</code> inside field values.
+								</td>
+							</tr>
 							<tr>
 								<td>
 									<code>delimiter</code>
@@ -324,6 +332,14 @@
 									If <code>true</code>, lines that are completely empty (those which evaluate to an empty string) will be skipped. If set to <code>'greedy'</code>, lines that don't have any content (those which have only whitespace after parsing) will also be skipped.
 								</td>
 							</tr>
+							<tr>
+								<td>
+									<code>columns</code>
+								</td>
+								<td>
+									If <code>data</code> is an array of objects this option can be used to manually specify the keys (columns) you expect in the objects. If not set the keys of the first objects are used as column.
+								</td>
+							</tr>
 						</table>
 					</div>
 					<div class="clear"></div>
diff --git a/papaparse.js b/papaparse.js
index 5ed703e..70c4edc 100755
--- a/papaparse.js
+++ b/papaparse.js
@@ -273,9 +273,15 @@ License: MIT
 		/** quote character */
 		var _quoteChar = '"';
 
+		/** escaped quote character, either "" or <config.escapeChar>" */
+		var _escapedQuote = _quoteChar + _quoteChar;
+
 		/** whether to skip empty lines */
 		var _skipEmptyLines = false;
 
+		/** the columns (keys) we expect when we unparse objects */
+		var _columns = null;
+
 		unpackConfig();
 
 		var quoteCharRegex = new RegExp(escapeRegExp(_quoteChar), 'g');
@@ -288,7 +294,7 @@ License: MIT
 			if (!_input.length || Array.isArray(_input[0]))
 				return serialize(null, _input, _skipEmptyLines);
 			else if (typeof _input[0] === 'object')
-				return serialize(objectKeys(_input[0]), _input, _skipEmptyLines);
+				return serialize(_columns || objectKeys(_input[0]), _input, _skipEmptyLines);
 		}
 		else if (typeof _input === 'object')
 		{
@@ -343,6 +349,17 @@ License: MIT
 
 			if (typeof _config.header === 'boolean')
 				_writeHeader = _config.header;
+
+			if (Array.isArray(_config.columns)) {
+
+				if (_config.columns.length === 0) throw new Error('Option columns is empty');
+
+				_columns = _config.columns;
+			}
+
+			if (_config.escapeChar !== undefined) {
+				_escapedQuote = _config.escapeChar + _quoteChar;
+			}
 		}
 
 
@@ -429,7 +446,7 @@ License: MIT
 			if (str.constructor === Date)
 				return JSON.stringify(str).slice(1, 25);
 
-			str = str.toString().replace(quoteCharRegex, _quoteChar + _quoteChar);
+			str = str.toString().replace(quoteCharRegex, _escapedQuote);
 
 			var needsQuotes = (typeof _quotes === 'boolean' && _quotes)
 							|| (Array.isArray(_quotes) && _quotes[col])


Test patch:
diff --git a/tests/test-cases.js b/tests/test-cases.js
index b0610dd..07b8a53 100644
--- a/tests/test-cases.js
+++ b/tests/test-cases.js
@@ -1732,6 +1732,25 @@ var UNPARSE_TESTS = [
 		input: [{a: null, b: ' '}, {}, {a: '1', b: '2'}],
 		config: {skipEmptyLines: 'greedy', header: true},
 		expected: 'a,b\r\n1,2'
+	},
+	{
+		description: "Column option used to manually specify keys",
+		notes: "Should not throw any error when attempting to serialize key not present in object. Columns are different than keys of the first object. When an object is missing a key then the serialized value should be an empty string.",
+		input: [{a: 1, b: '2'}, {}, {a: 3, d: 'd', c: 4,}],
+		config: {columns: ['a', 'b', 'c']},
+		expected: 'a,b,c\r\n1,2,\r\n\r\n3,,4'
+	},
+	{
+		description: "Use different escapeChar",
+		input: [{a: 'foo', b: '"quoted"'}],
+		config: {header: false, escapeChar: '\\'},
+		expected: 'foo,"\\"quoted\\""'
+	},
+	{
+		description: "test defeault escapeChar",
+		input: [{a: 'foo', b: '"quoted"'}],
+		config: {header: false},
+		expected: 'foo,"""quoted"""'
 	}
 ];