diff --git a/lib/normal-template.js b/lib/normal-template.js index a8b38db..81df00f 100644 --- a/lib/normal-template.js +++ b/lib/normal-template.js @@ -42,9 +42,10 @@ exports.compile = function(src, options) { // v = curent value, d = cursor, a = reduced array, df = default filter, res = result var code = ['var v,a,d = data,res = [];'], stack = ["data"], + nesting = [], tokens = src.split(TOKEN_RE); - var filters; + var filters, tag; if (options && options.filters) { filters = {}; @@ -72,12 +73,14 @@ exports.compile = function(src, options) { switch (cmd) { case "if": + nesting.push("if"); val = xpath(arg); code.push('if (' + val + ' != undefined) {'); continue; case "select": case "s": + nesting.push("select"); val = xpath(arg); code.push('d = ' + val + ';if (d != undefined) {'); stack.unshift(val.replace(/^d\./, stack[0] + ".")); @@ -85,6 +88,7 @@ exports.compile = function(src, options) { case "reduce": case "r": + nesting.push("reduce"); val = xpath(arg); var depth = stack.length; code.push('var a' + depth + ' = ' + val + ';if ((a' + depth + ' != undefined) && (a' + depth + '.length > 0)) '); @@ -94,16 +98,20 @@ exports.compile = function(src, options) { case "else": case "e": - code.push('} else {'); + tag = nesting.pop(); + if (tag) { + code.push('} else {'); + nesting.push(tag); + } else { + throw new Error("Unbalanced 'else' tag"); + } continue; case "lb": // output left curly bracket '{' - case "lcb": code.push('res.push("{");'); continue; case "rb": // output right curly bracket '}' - case "rcb": code.push('res.push("}");'); continue; @@ -113,23 +121,37 @@ exports.compile = function(src, options) { } else if (token[1] == "/") { // close tag if (token[2] == ":") { var cmd = token.substring(3, token.length-1).split(" ")[0]; - + switch (cmd) { case "if": - code.push('};'); + tag = nesting.pop(); + if (tag == "if") { + code.push('};'); + } else { + throw new Error("Unbalanced 'if' close tag" + (tag ? ", expecting '" + tag + "' close tag" : "")); + } continue; case "select": case "s": - stack.shift(); - code.push('};d = ' + stack[0] + ';'); + tag = nesting.pop(); + if (tag == "select") { + stack.shift(); + code.push('};d = ' + stack[0] + ';'); + } else { + throw new Error("Unbalanced 'select' close tag" + (tag ? ", expecting '" + tag + "' close tag" : "")); + } continue; case "reduce": case "r": - var depth = stack.length; - stack.shift(); - code.push('};d = ' + stack[0] + ';'); + tag = nesting.pop(); + if (tag == "reduce") { + stack.shift(); + code.push('};d = ' + stack[0] + ';'); + } else { + throw new Error("Unbalanced 'reduce' close tag" + (tag ? ", expecting '" + tag + "' close tag" : "")); + } continue; } } @@ -153,6 +175,11 @@ exports.compile = function(src, options) { code.push('res.push("' + token.replace(/\n/g, "\\n").replace(/"/g, '\\"') + '");'); } + tag = nesting.pop(); + if (tag) { + throw new Error("Unbalanced '" + tag + "' tag, is not closed"); + } + code.push('return res.join("");'); var func = new Function("data", "filters", code.join("")); diff --git a/tests/all-tests.js b/tests/all-tests.js index c3e3965..685b7b7 100644 --- a/tests/all-tests.js +++ b/tests/all-tests.js @@ -1,5 +1,5 @@ exports.testNormalTemplate = require("./normal-template-tests"); exports.testNormalTPP = require("./normal-template/tpp-tests"); -if (require.main === module.id) +if (require.main == module.id) require("os").exit(require("test/runner").run(exports)); diff --git a/tests/normal-template-tests.js b/tests/normal-template-tests.js index e003061..7ac8601 100644 --- a/tests/normal-template-tests.js +++ b/tests/normal-template-tests.js @@ -15,7 +15,7 @@ exports.testComments = function() { assert.isEqual("Hello Stella", t(data)); } -exports.testWith = function() { +exports.testSelect = function() { var t = compile("Hello {:select user}{=name}, {=age}{/:select}"); var data = {user: {name: "George", age: "34"}}; assert.isEqual("Hello George, 34", t(data)); @@ -57,7 +57,7 @@ exports.testIfElse = function() { assert.isEqual("cool this is outer", t(data)); } -exports.testWithOr = function() { +exports.testSelectElse = function() { var t = compile("{:s cool}cool{:e}not cool{/:s}"); var data = {}; assert.isEqual("not cool", t(data)); @@ -76,13 +76,13 @@ exports.testInterpolateNone = function() { } exports.testDot = function() { - var t = compile("{:s cool}{=.}{:s}not cool{/:s}"); + var t = compile("{:s cool}{=.}{:e}not cool{/:s}"); var data = {cool: 34}; assert.isEqual("34", t(data)); } -exports.testReduceOr = function() { - var t = compile('{:r articles}
  • {=title}: {=content}
  • {:or}no articles{/:r}'); +exports.testReduceElse = function() { + var t = compile('{:r articles}
  • {=title}: {=content}
  • {:e}no articles{/:r}'); var data = {articles: [ {title: "Hello1", content: "World1"}, {title: "Hello2", content: "World2"}, @@ -98,7 +98,7 @@ exports.testReduceOr = function() { } // stupid, but lets test this. -exports.testWithReduceOr = function() { +exports.testSelectReduceElse = function() { var t = compile("{:s articles}{:r .}
  • {=title}: {=content}
  • {/:r}{:e}no articles{/:s}"); var data = {articles: [ {title: "Hello1", content: "World1"}, @@ -153,7 +153,27 @@ exports.testQuotesEscaping = function() { } exports.testCurlyBrackets = function() { - var t = compile('enclose in {:lcb}brackets{:rcb}'); + var t = compile('enclose in {:lb}brackets{:rb}'); var data = {}; assert.isEqual('enclose in {brackets}', t(data)); } + +exports.testSyntaxErrors = function() { + try { + compile("{/:s articles}articles") + } catch (e) { + assert.isEqual("Error: Unbalanced 'select' close tag", e.toString()); + } + + try { + compile("{:if user}{:s articles}articles{/:if}") + } catch (e) { + assert.isEqual("Error: Unbalanced 'if' close tag, expecting 'select' close tag", e.toString()); + } + + try { + compile("{:if user}{:s articles}articles") + } catch (e) { + assert.isEqual("Error: Unbalanced 'select' tag, is not closed", e.toString()); + } +}