From ab3bf8ad3ce64cff7d6149e1b70f92b71bebf6c7 Mon Sep 17 00:00:00 2001 From: Juanjo Diaz Date: Thu, 28 Jan 2021 01:23:15 +0200 Subject: [PATCH] fix: Escape quotes excel (#512) * fix: properly escape quotes when using excel mode * refactor: fix typo in comments * perf: do quote replacement in a single pass --- lib/JSON2CSVBase.js | 16 +++++++------ test/CLI.js | 13 ++++++++++- test/JSON2CSVAsyncParser.js | 18 ++++++++++++++- test/JSON2CSVParser.js | 14 ++++++++++- test/JSON2CSVTransform.js | 23 ++++++++++++++++++- .../csv/excelStringsWithEscapedQuoted.csv | 3 +++ 6 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 test/fixtures/csv/excelStringsWithEscapedQuoted.csv diff --git a/lib/JSON2CSVBase.js b/lib/JSON2CSVBase.js index 708054ef..6e45da23 100644 --- a/lib/JSON2CSVBase.js +++ b/lib/JSON2CSVBase.js @@ -168,14 +168,16 @@ class JSON2CSVBase { } if (typeof value === 'string') { - if(value.includes(this.opts.quote)) { - value = value.replace(new RegExp(this.opts.quote, 'g'), this.opts.escapedQuote); - } - - value = `${this.opts.quote}${value}${this.opts.quote}`; - if (this.opts.excelStrings) { - value = `"="${value}""`; + if(value.includes(this.opts.quote)) { + value = value.replace(new RegExp(this.opts.quote, 'g'), `${this.opts.escapedQuote}${this.opts.escapedQuote}`); + } + value = `"=""${value}"""`; + } else { + if(value.includes(this.opts.quote)) { + value = value.replace(new RegExp(this.opts.quote, 'g'), this.opts.escapedQuote); + } + value = `${this.opts.quote}${value}${this.opts.quote}`; } } diff --git a/test/CLI.js b/test/CLI.js index 0db7fe17..4408612f 100644 --- a/test/CLI.js +++ b/test/CLI.js @@ -430,7 +430,7 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { }); }); - // Excell + // Excel testRunner.add('should format strings to force excel to view the values as strings', (t) => { const opts = '--fields carModel,price,color --excel-strings'; @@ -443,6 +443,17 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { }); }); + testRunner.add('should format strings to force excel to view the values as strings with escaped quotes', (t) => { + const opts = '--excel-strings'; + + exec(`${cli} -i "${getFixturePath('/json/quotes.json')}" ${opts}`, (err, stdout, stderr) => { + t.notOk(stderr); + const csv = stdout; + t.equal(csv, csvFixtures.excelStringsWithEscapedQuoted); + t.end(); + }); + }); + // Escaping and preserving values testRunner.add('should parse JSON values with trailing backslashes', (t) => { diff --git a/test/JSON2CSVAsyncParser.js b/test/JSON2CSVAsyncParser.js index 8b004676..9885a54a 100644 --- a/test/JSON2CSVAsyncParser.js +++ b/test/JSON2CSVAsyncParser.js @@ -768,7 +768,7 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) = t.end(); }); - // Excell + // Excel testRunner.add('should format strings to force excel to view the values as strings', async (t) => { const opts = { @@ -787,6 +787,22 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) = t.end(); }); + testRunner.add('should format strings to force excel to view the values as strings with escaped quotes', async (t) => { + const opts = { + excelStrings:true + }; + const parser = new AsyncParser(opts); + + try { + const csv = await parser.fromInput(jsonFixtures.quotes()).promise(); + t.equal(csv, csvFixtures.excelStringsWithEscapedQuoted); + } catch(err) { + t.fail(err.message); + } + + t.end(); + }); + // Escaping and preserving values testRunner.add('should parse JSON values with trailing backslashes', async (t) => { diff --git a/test/JSON2CSVParser.js b/test/JSON2CSVParser.js index 542c1613..90d2af30 100644 --- a/test/JSON2CSVParser.js +++ b/test/JSON2CSVParser.js @@ -515,7 +515,7 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { t.end(); }); - // Excell + // Excel testRunner.add('should format strings to force excel to view the values as strings', (t) => { const opts = { @@ -530,6 +530,18 @@ module.exports = (testRunner, jsonFixtures, csvFixtures) => { t.end(); }); + testRunner.add('should format strings to force excel to view the values as strings with escaped quotes', (t) => { + const opts = { + excelStrings:true + }; + + const parser = new Json2csvParser(opts); + const csv = parser.parse(jsonFixtures.quotes); + + t.equal(csv, csvFixtures.excelStringsWithEscapedQuoted); + t.end(); + }); + // Escaping and preserving values testRunner.add('should parse JSON values with trailing backslashes', (t) => { diff --git a/test/JSON2CSVTransform.js b/test/JSON2CSVTransform.js index b9771a73..612f33b9 100644 --- a/test/JSON2CSVTransform.js +++ b/test/JSON2CSVTransform.js @@ -842,7 +842,7 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) = }); }); - // Excell + // Excel testRunner.add('should format strings to force excel to view the values as strings', (t) => { const opts = { @@ -866,6 +866,27 @@ module.exports = (testRunner, jsonFixtures, csvFixtures, inMemoryJsonFixtures) = }); }); + testRunner.add('should format strings to force excel to view the values as strings with escaped quotes', (t) => { + const opts = { + excelStrings:true + }; + + const transform = new Json2csvTransform(opts); + const processor = jsonFixtures.quotes().pipe(transform); + + let csv = ''; + processor + .on('data', chunk => (csv += chunk.toString())) + .on('end', () => { + t.equal(csv, csvFixtures.excelStringsWithEscapedQuoted); + t.end(); + }) + .on('error', err => { + t.fail(err.message); + t.end(); + }); + }); + // Escaping and preserving values testRunner.add('should parse JSON values with trailing backslashes', (t) => { diff --git a/test/fixtures/csv/excelStringsWithEscapedQuoted.csv b/test/fixtures/csv/excelStringsWithEscapedQuoted.csv new file mode 100644 index 00000000..e8045c12 --- /dev/null +++ b/test/fixtures/csv/excelStringsWithEscapedQuoted.csv @@ -0,0 +1,3 @@ +"=""a string""" +"=""with a description""" +"=""with a description and """"quotes""""""" \ No newline at end of file