Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a filter for emitting JS data #358

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 81 additions & 2 deletions mustache.js
Expand Up @@ -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);
Expand All @@ -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`
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions test/_files/js_escape.js
@@ -0,0 +1,6 @@
({ string: "This is my\u2028string\"'."
, breaker: "This is my string with a </script> tag."
, array: ["abc\u2028", 1.1, [2, 3, true], "z"]
, object: {"key\u2028": false, "otherkey": true, "finalkey": 3}
, nil: null
})
8 changes: 8 additions & 0 deletions test/_files/js_escape.mustache
@@ -0,0 +1,8 @@
<script>
var s = {{$string}},
b = {{$breaker}},
a = {{$array}},
o = {{$object}},
n = {{$nil}},
u = {{$nonexistent}};
</script>
8 changes: 8 additions & 0 deletions test/_files/js_escape.txt
@@ -0,0 +1,8 @@
<script>
var s = 'This is my\u2028string"\'.',
b = 'This is my string with a \x3c\x2fscript> tag.',
a = ['abc\u2028',1.1,[2,3,true],'z'],
o = {'key\u2028':false,'otherkey':true,'finalkey':3},
n = null,
u = undefined;
</script>