diff --git a/mustache.js b/mustache.js index a3aac8f5e..605a447ed 100644 --- a/mustache.js +++ b/mustache.js @@ -59,6 +59,80 @@ }); } + function escapeJs(o) { + var s = ''; + + if (typeof o === 'string') { + s += "'"; + + for (var i = 0; i < o.length; ++i) { + var n = o.charCodeAt(i), + c = o[i]; + + if (c == "'") { + s += "\\'"; + } else if (c == '\\') { + s += '\\\\'; + } else if (c == '<') { + s += '\\x3c'; + } else if (c == '/') { + s += '\\x2f'; + } else if (n < 32 || n > 122) { + var code = n.toString(16); + + while (code.length < 4) { + code = '0' + code; + } + + s += "\\u" + code; + } else { + s += c; + } + } + + s += "'"; + } else if (o instanceof Array) { + s += '['; + + for (var i = 0; i < o.length; ++i) { + s += escapeJs(o[i]); + + if (i < o.length - 1) { + s += ','; + } + } + + s += ']'; + } else if (typeof o === 'boolean') { + s = o ? 'true' : 'false'; + } else if (typeof o === 'number') { + s = '' + o; + } else if (typeof o === 'undefined') { + s = 'undefined'; + } else if (o === null) { + s = 'null'; + } else { + s += '{'; + + var keys = Object.keys(o); + + for (var i = 0; i < keys.length; ++i) { + var k = keys[i], + v = o[k]; + + s += escapeJs(k) + ':' + escapeJs(v); + + if (i < keys.length - 1) { + s += ','; + } + } + + s += '}'; + } + + return s; + } + function escapeTags(tags) { if (!isArray(tags) || tags.length !== 2) { throw new Error('Invalid tags: ' + tags); @@ -74,7 +148,7 @@ var spaceRe = /\s+/; var equalsRe = /\s*=/; var curlyRe = /\s*\}/; - var tagRe = /#|\^|\/|>|\{|&|=|!/; + var tagRe = /#|\^|\/|>|\{|\$|&|=|!/; /** * Breaks up the given `template` string into a tree of tokens. If the `tags` @@ -198,7 +272,7 @@ if (openSection[1] !== value) { throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); } - } else if (type === 'name' || type === '{' || type === '&') { + } else if (type === 'name' || type === '{' || type === '&' || type === '$') { nonSpace = true; } else if (type === '=') { // Set the tags for the next time around. @@ -503,6 +577,10 @@ value = context.lookup(token[1]); if (value != null) buffer += value; break; + case '$': + value = context.lookup(token[1]); + buffer += mustache.escapeJs(value); + break; case 'name': value = context.lookup(token[1]); if (value != null) buffer += mustache.escape(value); @@ -561,6 +639,7 @@ // Export the escaping function so that the user may override it. // See https://github.com/janl/mustache.js/issues/244 mustache.escape = escapeHtml; + mustache.escapeJs = escapeJs; // Export these mainly for testing, but also for advanced usage. mustache.Scanner = Scanner; diff --git a/test/_files/js_escape.js b/test/_files/js_escape.js new file mode 100644 index 000000000..02088c7ae --- /dev/null +++ b/test/_files/js_escape.js @@ -0,0 +1,6 @@ +({ string: "This is my\u2028string\"'." +, breaker: "This is my string with a tag." +, array: ["abc\u2028", 1.1, [2, 3, true], "z"] +, object: {"key\u2028": false, "otherkey": true, "finalkey": 3} +, nil: null +}) diff --git a/test/_files/js_escape.mustache b/test/_files/js_escape.mustache new file mode 100644 index 000000000..361ccf68a --- /dev/null +++ b/test/_files/js_escape.mustache @@ -0,0 +1,8 @@ + diff --git a/test/_files/js_escape.txt b/test/_files/js_escape.txt new file mode 100644 index 000000000..78e136d36 --- /dev/null +++ b/test/_files/js_escape.txt @@ -0,0 +1,8 @@ +