diff --git a/src/58json.js b/src/58json.js index 0308999ed3..8bc6039ec8 100755 --- a/src/58json.js +++ b/src/58json.js @@ -46,7 +46,7 @@ const JSONtoString = (alasql.utils.JSONtoString = function (obj) { function JSONtoJS(obj, context, tableid, defcols) { var s = ''; - if (typeof obj == 'string') s = '"' + obj + '"'; + if (typeof obj == 'string') s = '"' + escapeq(obj) + '"'; else if (typeof obj == 'number') s = '(' + obj + ')'; else if (typeof obj == 'boolean') s = obj; else if (typeof obj === 'bigint') s = obj.toString() + 'n'; @@ -56,7 +56,7 @@ function JSONtoJS(obj, context, tableid, defcols) { } else if (!obj.toJS || obj instanceof yy.Json) { let ss = []; for (const k in obj) { - let keyStr = typeof k === 'string' ? `"${k}"` : k.toString(); + let keyStr = typeof k === 'string' ? `"${escapeq(k)}"` : k.toString(); let valueStr = JSONtoJS(obj[k], context, tableid, defcols); ss.push(`${keyStr}:${valueStr}`); } diff --git a/src/alasqlparser.jison b/src/alasqlparser.jison index 40093f13cc..f99d970f11 100755 --- a/src/alasqlparser.jison +++ b/src/alasqlparser.jison @@ -1460,9 +1460,37 @@ LogicValue StringValue : STRING - { $$ = new yy.StringValue({value: $1.substr(1,$1.length-2).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); } + { + var str = $1.substr(1,$1.length-2).replace(/(\'\')/g,"'"); + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + } + }); + $$ = new yy.StringValue({value: str}); + } | NSTRING - { $$ = new yy.StringValue({value: $1.substr(2,$1.length-3).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); } + { + var str = $1.substr(2,$1.length-3).replace(/(\'\')/g,"'"); + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + } + }); + $$ = new yy.StringValue({value: str}); + } ; NullValue diff --git a/src/alasqlparser.js b/src/alasqlparser.js index 4bcacc5f94..c9b2c0b369 100755 --- a/src/alasqlparser.js +++ b/src/alasqlparser.js @@ -904,10 +904,38 @@ case 378: this.$ = new yy.LogicValue({value:false}); break; case 379: - this.$ = new yy.StringValue({value: $$[$0].substr(1,$$[$0].length-2).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); + + var str = $$[$0].substr(1,$$[$0].length-2).replace(/(\'\')/g,"'"); + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + } + }); + this.$ = new yy.StringValue({value: str}); + break; case 380: - this.$ = new yy.StringValue({value: $$[$0].substr(2,$$[$0].length-3).replace(/(\\\')/g,"'").replace(/(\'\')/g,"'")}); + + var str = $$[$0].substr(2,$$[$0].length-3).replace(/(\'\')/g,"'"); + // Process escape sequences: \n \t \r \\ \' \" + str = str.replace(/\\(n|t|r|\\|'|")/g, function(match, char) { + switch(char) { + case 'n': return '\n'; + case 't': return '\t'; + case 'r': return '\r'; + case '\\': return '\\'; + case "'": return "'"; + case '"': return '"'; + } + }); + this.$ = new yy.StringValue({value: str}); + break; case 381: this.$ = new yy.NullValue({value:undefined}); diff --git a/test/test125.js b/test/test125.js index 60b37f1044..b021adfc4e 100644 --- a/test/test125.js +++ b/test/test125.js @@ -34,11 +34,12 @@ describe('Test 125 - remove comments', function () { var res = alasql("select 'Cote d''Ivoir'"); assert.deepEqual(res, [{"'Cote d'Ivoir'": "Cote d'Ivoir"}]); var res = alasql('select "Cote d\\"Ivoir"'); - assert.deepEqual(res, [{"'Cote d\\\"Ivoir'": 'Cote d\\"Ivoir'}]); - var res = alasql('select "\\r"'); - assert.deepEqual(res, [{"'\\r'": '\\r'}]); - var res = alasql('select "\\n"'); - assert.deepEqual(res, [{"'\\n'": '\\n'}]); + assert.deepEqual(res, [{"'Cote d\"Ivoir'": 'Cote d"Ivoir'}]); + // Escape sequences now work as of issue #134 + var res = alasql('select "\\r" as col'); + assert.deepEqual(res, [{col: '\r'}]); + var res = alasql('select "\\n" as col'); + assert.deepEqual(res, [{col: '\n'}]); alasql('drop database test125'); done(); diff --git a/test/test134-B.js b/test/test134-B.js new file mode 100644 index 0000000000..6b32e6c8a8 --- /dev/null +++ b/test/test134-B.js @@ -0,0 +1,64 @@ +if (typeof exports === 'object') { + var assert = require('assert'); + var alasql = require('..'); +} + +describe('Test 134-B - Escape sequences in strings', function () { + const test = '134B'; + + before(function () { + alasql('create database test' + test); + alasql('use test' + test); + }); + + after(function () { + alasql('drop database test' + test); + }); + + it('A) Tab escape sequence', function () { + var res = alasql("SELECT 'hello\\tworld' AS result"); + assert.deepEqual(res, [{result: 'hello\tworld'}]); + }); + + it('B) Newline escape sequence', function () { + var res = alasql("SELECT 'line1\\nline2' AS result"); + assert.deepEqual(res, [{result: 'line1\nline2'}]); + }); + + it('C) Carriage return escape sequence', function () { + var res = alasql("SELECT 'text\\rmore' AS result"); + assert.deepEqual(res, [{result: 'text\rmore'}]); + }); + + it('D) Backslash escape sequence', function () { + var res = alasql("SELECT 'back\\\\slash' AS result"); + assert.deepEqual(res, [{result: 'back\\slash'}]); + }); + + it('E) Double quote escape sequence', function () { + var res = alasql('SELECT "quote\\"here" AS result'); + assert.deepEqual(res, [{result: 'quote"here'}]); + }); + + it('F) Mixed escape sequences', function () { + var res = alasql("SELECT 'Line 1\\nLine 2\\tTabbed\\rCarriage' AS result"); + assert.deepEqual(res, [{result: 'Line 1\nLine 2\tTabbed\rCarriage'}]); + }); + + it('G) Escape sequence in WHERE clause', function () { + alasql('CREATE TABLE test_escapes (id INT, text STRING)'); + alasql("INSERT INTO test_escapes VALUES (1, 'hello\\tworld')"); + alasql("INSERT INTO test_escapes VALUES (2, 'no tabs here')"); + + var res = alasql("SELECT * FROM test_escapes WHERE text = 'hello\\tworld'"); + assert.deepEqual(res, [{id: 1, text: 'hello\tworld'}]); + }); + + it('H) Single quote still works', function () { + var res = alasql("SELECT 'Cote d\\'Ivoir' AS result"); + assert.deepEqual(res, [{result: "Cote d'Ivoir"}]); + + var res2 = alasql("SELECT 'Cote d''Ivoir' AS result"); + assert.deepEqual(res2, [{result: "Cote d'Ivoir"}]); + }); +});