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 @@
+