| @@ -5,374 +5,532 @@ | |||
| */ | |||
| var Mustache = function() { | |||
| var Renderer = function() {}; | |||
| Renderer.prototype = { | |||
| otag: "{{", | |||
| ctag: "}}", | |||
| escaped_otag: "\{\{", | |||
| escaped_ctag: "\}\}", | |||
| pragmas: {}, | |||
| pragmas_implemented: { | |||
| "IMPLICIT-ITERATOR": true | |||
| }, | |||
| context: {}, | |||
| render: function(template, context, partials) { | |||
| // reset context | |||
| this.context = context; | |||
| // fail fast | |||
| if(!this.includes("", template)) { | |||
| return template; | |||
| } | |||
| template = this.render_pragmas(template); | |||
| return this.render_recursive(template, context, partials); | |||
| }, | |||
| render_recursive: function(template, context, partials) { | |||
| return this.render_delimiter(template, context, partials); | |||
| }, | |||
| /* | |||
| Looks for %PRAGMAS | |||
| */ | |||
| render_pragmas: function(template) { | |||
| // no pragmas | |||
| if(!this.includes("%", template)) { | |||
| return template; | |||
| } | |||
| var that = this; | |||
| var regex = new RegExp(this.escaped_otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + | |||
| this.escaped_ctag); | |||
| return template.replace(regex, function(match, pragma, options) { | |||
| if(!that.pragmas_implemented[pragma]) { | |||
| throw({message: | |||
| "This implementation of mustache doesn't understand the '" + | |||
| pragma + "' pragma"}); | |||
| } | |||
| that.pragmas[pragma] = {}; | |||
| if(options) { | |||
| var opts = options.split("="); | |||
| that.pragmas[pragma][opts[0]] = opts[1]; | |||
| } | |||
| return ""; | |||
| // ignore unknown pragmas silently | |||
| }); | |||
| }, | |||
| render_delimiter: function(template, context, partials) { | |||
| if(!this.includes("=", template)) { | |||
| return this.render_section(template, context, partials); | |||
| } | |||
| var regex = new RegExp("(" + this.escaped_otag + "=(?:\\S+)\\s+(?:\\S+)=" + this.escaped_ctag + "\\n*)", "mg"); | |||
| var fragments = template.split(regex); | |||
| var i, n; | |||
| var r = new RegExp(this.escaped_otag + "=(\\S+)\\s+(\\S+)=" + this.escaped_ctag); | |||
| var matches; | |||
| for (i=0, n=fragments.length; i<n; ++i) { | |||
| matches = fragments[i].match(r); | |||
| if(matches && matches.length===3) { | |||
| var old_otag = this.otag; | |||
| var old_ctag = this.ctag; | |||
| this.set_delimiters(matches[1], matches[2]); | |||
| fragments[i] = this.render_delimiter(fragments.slice(i+1).join(""), context, partials); | |||
| this.set_delimiters(old_otag, old_ctag); | |||
| fragments = fragments.slice(0,i+1); | |||
| break; | |||
| } else { | |||
| fragments[i]=this.render_section(fragments[i], context, partials); | |||
| } | |||
| } | |||
| return fragments.join(""); | |||
| }, | |||
| /* | |||
| Tries to find a partial in the current scope and render it | |||
| */ | |||
| render_partial: function(name, context, partials) { | |||
| name = this.trim(name); | |||
| if(!partials || partials[name] === undefined) { | |||
| throw({message: "unknown_partial '" + name + "'"}); | |||
| } | |||
| if(typeof(context[name]) != "object") { | |||
| return this.render_recursive(partials[name], context, partials); | |||
| } | |||
| return this.render_recursive(partials[name], context[name], partials); | |||
| }, | |||
| /* | |||
| Renders inverted (^) and normal (#) sections | |||
| */ | |||
| render_section: function(template, context, partials) { | |||
| if(!this.includes("#", template) && !this.includes("^", template)) { | |||
| return this.render_tags(template, context, partials); | |||
| } | |||
| var regex = new RegExp( | |||
| "(" + this.escaped_otag + "(?:\\^|\\#)\\s*(.+?)(?:\\(.*\\))?\\s*" + this.escaped_ctag + | |||
| "\n*[\\s\\S]+?" + this.escaped_otag + "\\/\\s*\\2\\s*" + this.escaped_ctag + "\\s*)", | |||
| "mg"); | |||
| var i, n; | |||
| var lastWasSection = false; | |||
| var fragments = template.split(regex); | |||
| for (i=0, n=fragments.length; i<n; ++i) { | |||
| if(lastWasSection) { | |||
| fragments[i] = ""; | |||
| lastWasSection = false; | |||
| continue; | |||
| } | |||
| if(fragments[i].indexOf(this.otag+"#")===0 || fragments[i].indexOf(this.otag+"^")===0) { | |||
| lastWasSection = true; | |||
| fragments[i] = this.render_section_internal(fragments[i], context, partials); | |||
| } else { | |||
| fragments[i] = this.render_tags(fragments[i], context, partials); | |||
| } | |||
| } | |||
| return fragments.join(""); | |||
| }, | |||
| render_section_internal: function(template, context, partials) { | |||
| var that = this; | |||
| // CSW - Added "+?" so it finds the tighest bound, not the widest | |||
| var regex = new RegExp(this.escaped_otag + "(\\^|\\#)\\s*((.+?)(\\(.*\\))?)\\s*" + this.escaped_ctag + | |||
| "\n*([\\s\\S]+?)" + this.escaped_otag + "\\/\\s*\\3\\s*" + this.escaped_ctag + | |||
| "\\s*", "mg"); | |||
| // for each {{#foo}}{{/foo}} section do... | |||
| var fragment = template.replace(regex, function(match, type, name, reserved1, reserved2, content) { | |||
| // the reserved variables are not being used | |||
| var value = that.find(name, context); | |||
| if(type == "^") { // inverted section | |||
| if(!value || that.is_array(value) && value.length === 0) { | |||
| // false or empty list, render it | |||
| return that.render_recursive(content, context, partials); | |||
| } else { | |||
| return ""; | |||
| } | |||
| } else if(type == "#") { // normal section | |||
| if(that.is_array(value)) { // Enumerable, Let's loop! | |||
| return that.map(value, function(row) { | |||
| return that.render_recursive(content, that.create_context(row), | |||
| partials, true); | |||
| }).join(""); | |||
| } else if(that.is_object(value)) { // Object, Use it as subcontext! | |||
| return that.render_recursive(content, that.create_context(value), | |||
| partials, true); | |||
| } else if(typeof value === "function") { | |||
| // higher order section | |||
| return value.call(context, content, function(text) { | |||
| return that.render_recursive(text, context, partials); | |||
| }); | |||
| } else if(value) { // boolean section | |||
| return that.render_recursive(content, context, partials); | |||
| } else { | |||
| return ""; | |||
| } | |||
| } | |||
| }); | |||
| return fragment; | |||
| }, | |||
| /* | |||
| Replace {{foo}} and friends with values from our view | |||
| */ | |||
| render_tags: function(template, context, partials) { | |||
| // tit for tat | |||
| var that = this; | |||
| var new_regex = function() { | |||
| return new RegExp(that.escaped_otag + "(!|>|\\{|&|%)?([^\\/#\\^=]+?)\\1?" + | |||
| that.escaped_ctag + "+", "g"); | |||
| }; | |||
| var regex = new_regex(); | |||
| var tag_replace_callback = function(match, operator, name) { | |||
| switch(operator) { | |||
| case "!": // ignore comments | |||
| return ""; | |||
| case ">": // render partial | |||
| return that.render_partial(name, context, partials); | |||
| case "{": // the triple mustache is unescaped | |||
| case "&": // the ampersand is also unescaped | |||
| return that.find(name, context); | |||
| default: // escape the value | |||
| return that.escape(that.find(name, context)); | |||
| } | |||
| }; | |||
| var lines = template.split("\n"); | |||
| var oldContent; | |||
| for(var i = 0; i < lines.length; i++) { | |||
| lines[i] = lines[i].replace(regex, tag_replace_callback, this); | |||
| } | |||
| return lines.join("\n"); | |||
| }, | |||
| set_delimiters: function(open, close) { | |||
| this.otag = open; | |||
| this.ctag = close; | |||
| this.escaped_otag = this.escape_regex(open); | |||
| this.escaped_ctag = this.escape_regex(close); | |||
| }, | |||
| escape_regex: function(text) { | |||
| // thank you Simon Willison | |||
| if(!arguments.callee.sRE) { | |||
| var specials = [ | |||
| '/', '.', '*', '+', '?', '|', | |||
| '(', ')', '[', ']', '{', '}', '\\' | |||
| ]; | |||
| arguments.callee.sRE = new RegExp( | |||
| '(\\' + specials.join('|\\') + ')', 'g' | |||
| ); | |||
| } | |||
| return text.replace(arguments.callee.sRE, '\\$1'); | |||
| }, | |||
| /* | |||
| find `name` in current `context`. That is find me a value | |||
| from the view object | |||
| */ | |||
| find: function(name, context) { | |||
| name = this.trim(name); | |||
| // Checks whether a value is truthy or false or 0 | |||
| function is_kinda_truthy(bool) { | |||
| return bool === false || bool === 0 || bool; | |||
| } | |||
| var value; | |||
| if(is_kinda_truthy(context[name])) { | |||
| value = context[name]; | |||
| } else if(is_kinda_truthy(this.context[name])) { | |||
| value = this.context[name]; | |||
| } | |||
| if(typeof value === "function") { | |||
| return value.apply(context); | |||
| } | |||
| if(value !== undefined) { | |||
| return value; | |||
| } | |||
| if (value === undefined && name.indexOf('(') != -1) { | |||
| // if value turned out to not exist on the context, then check to see if the function is parameterized | |||
| var matches = name.match(/(.+)\((.+,?)+\)/); | |||
| if (matches.length === 3) { | |||
| name = matches[1]; | |||
| value = this.context[name]; | |||
| if (typeof value === "function" && matches[2]) { | |||
| var that = this; | |||
| var args = this.map(matches[2].split(/\s*,/), | |||
| function(ele) { return that.find(ele, that.context); }); | |||
| return value.apply(context, args); | |||
| } | |||
| } | |||
| } | |||
| // silently ignore unknown variables | |||
| return ""; | |||
| }, | |||
| // Utility methods | |||
| /* includes tag */ | |||
| includes: function(needle, haystack) { | |||
| return haystack.indexOf(this.otag + needle) != -1; | |||
| }, | |||
| /* | |||
| Does away with nasty characters | |||
| */ | |||
| escape: function(s) { | |||
| s = String(s === null ? "" : s); | |||
| return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) { | |||
| switch(s) { | |||
| case "&": return "&"; | |||
| case "\\": return "\\\\"; | |||
| case '"': return '\"'; | |||
| case "<": return "<"; | |||
| case ">": return ">"; | |||
| default: return s; | |||
| } | |||
| }); | |||
| }, | |||
| // by @langalex, support for arrays of strings | |||
| create_context: function(_context) { | |||
| if(this.is_object(_context)) { | |||
| return _context; | |||
| } else { | |||
| var iterator = "."; | |||
| if(this.pragmas["IMPLICIT-ITERATOR"] && this.pragmas["IMPLICIT-ITERATOR"].iterator) { | |||
| iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; | |||
| } | |||
| var ctx = {}; | |||
| ctx[iterator] = _context; | |||
| return ctx; | |||
| } | |||
| }, | |||
| is_object: function(a) { | |||
| return a && typeof a == "object"; | |||
| }, | |||
| is_array: function(a) { | |||
| return Object.prototype.toString.call(a) === '[object Array]'; | |||
| }, | |||
| /* | |||
| Gets rid of leading and trailing whitespace | |||
| */ | |||
| trim: function(s) { | |||
| return s.replace(/^\s*|\s*$/g, ""); | |||
| }, | |||
| /* | |||
| Why, why, why? Because IE. Cry, cry cry. | |||
| */ | |||
| map: function(array, fn) { | |||
| if (typeof array.map == "function") { | |||
| return array.map(fn); | |||
| } else { | |||
| var r = []; | |||
| var l = array.length; | |||
| for(var i = 0; i < l; i++) { | |||
| r.push(fn(array[i])); | |||
| } | |||
| return r; | |||
| } | |||
| } | |||
| }; | |||
| return({ | |||
| name: "mustache.js", | |||
| version: "0.3.0-dev", | |||
| /* | |||
| Turns a template and view into HTML | |||
| */ | |||
| to_html: function(template, view, partials) { | |||
| var renderer = new Renderer(); | |||
| return renderer.render(template, view, partials); | |||
| } | |||
| }); | |||
| var ParserException = function(message) { | |||
| this.message = message; | |||
| } | |||
| var Renderer = function(send_func) { | |||
| this.send_func = send_func; | |||
| }; | |||
| Renderer.prototype = { | |||
| render: function(template, context, partials) { | |||
| var tokens = this.tokenize(template, '{{', '}}'); | |||
| this.parse(this.createParserContext(tokens, partials, '{{', '}}'), [context]); | |||
| }, | |||
| createParserContext: function(tokens, partials, openTag, closeTag) { | |||
| return { | |||
| tokens: tokens, | |||
| index: 0, | |||
| length: tokens.length, | |||
| partials: partials, | |||
| stack: [], | |||
| openTag: openTag, | |||
| closeTag: closeTag, | |||
| }; | |||
| }, | |||
| tokenize: function(template, openTag, closeTag) { | |||
| var delimiters = [ | |||
| '\\{', | |||
| '&', | |||
| '\\}', | |||
| '#', | |||
| '\\^', | |||
| '\\/', | |||
| '>', | |||
| '=', | |||
| '%', | |||
| '!', | |||
| '\\s+' | |||
| ]; | |||
| delimiters.unshift(this.escape_regex(openTag)); | |||
| delimiters.unshift(this.escape_regex(closeTag)); | |||
| var regex = new RegExp('(' + delimiters.join('|') + ')'); | |||
| var tokens = template.split(regex); | |||
| var cleaned_tokens = []; | |||
| for (var i = 0, n = tokens.length; i<n; ++i) { | |||
| if (tokens[i]!=='') { | |||
| cleaned_tokens.push(tokens[i]); | |||
| } | |||
| } | |||
| return cleaned_tokens; | |||
| }, | |||
| parse: function(parserContext, contextStack) { | |||
| var state = 'text'; | |||
| for (; parserContext.index<parserContext.length; ++parserContext.index) { | |||
| state = this.stateMachine[state].call(this, parserContext, contextStack); | |||
| } | |||
| // make sure the parser finished at an appropriate terminal state | |||
| if (state!=='text') { | |||
| this.stateMachine['endOfDoc'].call(this, parserContext, contextStack); | |||
| } | |||
| }, | |||
| escape_regex: function(text) { | |||
| // thank you Simon Willison | |||
| var specials = [ | |||
| '/', '.', '*', '+', '?', '|', | |||
| '(', ')', '[', ']', '{', '}', '\\' | |||
| ]; | |||
| var compiled_regex = new RegExp('(\\' + specials.join('|\\') + ')', 'g'); | |||
| return text.replace(compiled_regex, '\\$1'); | |||
| }, | |||
| isWhitespace: function(token) { | |||
| return token.match(/^\s+$/)!==null; | |||
| }, | |||
| stateMachine: { | |||
| text: function(parserContext, contextStack) { | |||
| switch (parserContext.tokens[parserContext.index]) { | |||
| case parserContext.openTag: | |||
| return 'openMustache'; | |||
| default: | |||
| this.send_func(parserContext.tokens[parserContext.index]); | |||
| return 'text'; | |||
| } | |||
| }, | |||
| openMustache: function(parserContext, contextStack) { | |||
| switch (parserContext.tokens[parserContext.index]) { | |||
| case '{': | |||
| parserContext.stack.push({tagType:'unescapedVariable', subtype: 'tripleMustache'}); | |||
| return 'keyName'; | |||
| case '&': | |||
| parserContext.stack.push({tagType:'unescapedVariable'}); | |||
| return 'keyName'; | |||
| case '#': | |||
| parserContext.stack.push({tagType:'section'}); | |||
| return 'keyName'; | |||
| case '^': | |||
| parserContext.stack.push({tagType:'invertedSection'}); | |||
| return 'keyName'; | |||
| case '>': | |||
| parserContext.stack.push({tagType:'partial'}); | |||
| return 'simpleKeyName'; | |||
| case '=': | |||
| parserContext.stack.push({tagType: 'setDelimiter'}); | |||
| return 'setDelimiterStart'; | |||
| case '!': | |||
| return 'discard'; | |||
| case '%': | |||
| throw new ParserException('Pragmas are currently unsupported.'); | |||
| case '/': // close mustache | |||
| throw new ParserException('Unexpected closing tag.'); | |||
| case '}': // close triple mustache | |||
| throw new ParserException('Unexpected token encountered.'); | |||
| default: | |||
| parserContext.stack.push({tagType:'variable'}); | |||
| return this.stateMachine.keyName.call(this, parserContext, contextStack); | |||
| } | |||
| }, | |||
| closeMustache: function(parserContext, contextStack) { | |||
| if (this.isWhitespace(parserContext.tokens[parserContext.index])) { | |||
| return 'closeMustache'; | |||
| } else if (parserContext.tokens[parserContext.index]===parserContext.closeTag) { | |||
| return this.dispatchCommand(parserContext, contextStack); | |||
| } | |||
| }, | |||
| expectClosingMustache: function(parserContext, contextStack) { | |||
| if (parserContext.closeTag==='}}' && | |||
| parserContext.tokens[parserContext.index]==='}}') { | |||
| return 'expectClosingParenthesis'; | |||
| } else if (parserContext.tokens[parserContext.index]==='}') { | |||
| return 'closeMustache'; | |||
| } else { | |||
| throw new ParserException('Unexpected token encountered.'); | |||
| } | |||
| }, | |||
| expectClosingParenthesis: function(parserContext, contextStack) { | |||
| if (parserContext.tokens[parserContext.index]==='}') { | |||
| return this.dispatchCommand(parserContext, contextStack); | |||
| } else { | |||
| throw new ParserException('Unexpected token encountered.'); | |||
| } | |||
| }, | |||
| keyName: function(parserContext, contextStack) { | |||
| var result = this.stateMachine.simpleKeyName.call(this, parserContext, contextStack); | |||
| if (result==='closeMustache') { | |||
| var tagKey = parserContext.stack.pop(); | |||
| var tag = parserContext.stack.pop(); | |||
| if (tag.tagType==='unescapedVariable' && tag.subtype==='tripleMustache') { | |||
| parserContext.stack.push({tagType:'unescapedVariable'}); | |||
| parserContext.stack.push(tagKey); | |||
| return 'expectClosingMustache'; | |||
| } else { | |||
| parserContext.stack.push(tag); | |||
| parserContext.stack.push(tagKey); | |||
| return 'closeMustache'; | |||
| } | |||
| } else if (result==='simpleKeyName') { | |||
| return 'keyName'; | |||
| } else { | |||
| throw new ParserException('Unexpected branch in tag name: ' + result); | |||
| } | |||
| }, | |||
| simpleKeyName: function(parserContext, contextStack) { | |||
| if (this.isWhitespace(parserContext.tokens[parserContext.index])) { | |||
| return 'simpleKeyName'; | |||
| } else { | |||
| parserContext.stack.push(parserContext.tokens[parserContext.index]); | |||
| return 'closeMustache'; | |||
| } | |||
| }, | |||
| setDelimiterStart: function(parserContext, contextStack) { | |||
| if (this.isWhitespace(parserContext.tokens[parserContext.index]) || | |||
| parserContext.tokens[parserContext.index]==='=') { | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } else { | |||
| parserContext.stack.push(parserContext.tokens[parserContext.index]); | |||
| return 'setDelimiterStartOrWhitespace'; | |||
| } | |||
| }, | |||
| setDelimiterStartOrWhitespace: function(parserContext, contextStack) { | |||
| if (this.isWhitespace(parserContext.tokens[parserContext.index])) { | |||
| return 'setDelimiterEnd'; | |||
| } else if (parserContext.tokens[parserContext.index]==='='){ | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } else { | |||
| parserContext.stack.push(parserContext.stack.pop() + parserContext.tokens[parserContext.index]); | |||
| return 'setDelimiterStartOrWhitespace'; | |||
| } | |||
| }, | |||
| setDelimiterEnd: function(parserContext, contextStack) { | |||
| if (this.isWhitespace(parserContext.tokens[parserContext.index])) { | |||
| return 'setDelimiterEnd'; | |||
| } else if (parserContext.tokens[parserContext.index]==='=') { | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } else { | |||
| parserContext.stack.push(parserContext.tokens[parserContext.index]); | |||
| return 'setDelimiterEndOrEqualSign'; | |||
| } | |||
| }, | |||
| setDelimiterEndOrEqualSign: function(parserContext, contextStack) { | |||
| if (parserContext.tokens[parserContext.index]==='=') { | |||
| return 'setDelimiterExpectClosingTag'; | |||
| } else if (this.isWhitespace(parserContext.tokens[parserContext.index])) { | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } else { | |||
| parserContext.stack.push(parserContext.stack.pop() + parserContext.tokens[parserContext.index]); | |||
| return 'setDelimiterEndOrEqualSign'; | |||
| } | |||
| }, | |||
| setDelimiterExpectClosingTag: function(parserContext, contextStack) { | |||
| if (parserContext.tokens[parserContext.index]===parserContext.closeTag) { | |||
| var newCloseTag = parserContext.stack.pop(); | |||
| var newOpenTag = parserContext.stack.pop(); | |||
| var command = parserContext.stack.pop(); | |||
| if (command.tagType!=='setDelimiter') { | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } else { | |||
| var tokens = this.tokenize( | |||
| parserContext.tokens.slice(parserContext.index+1).join(''), | |||
| newOpenTag, | |||
| newCloseTag); | |||
| var newParserContext = this.createParserContext(tokens, | |||
| parserContext.partials, | |||
| newOpenTag, | |||
| newCloseTag); | |||
| parserContext.tokens = newParserContext.tokens; | |||
| parserContext.index = -1; | |||
| parserContext.length = newParserContext.length; | |||
| parserContext.openTag = newParserContext.openTag; | |||
| parserContext.closeTag = newParserContext.closeTag; | |||
| return 'text'; | |||
| } | |||
| } else { | |||
| throw new ParserException('Syntax error in Set Delimiter tag'); | |||
| } | |||
| }, | |||
| endSectionScan: function(parserContext, contextStack) { | |||
| switch (parserContext.tokens[parserContext.index]) { | |||
| case parserContext.openTag: | |||
| return 'expectSectionOrEndSection'; | |||
| default: | |||
| parserContext.stack[parserContext.stack.length-1].content.push(parserContext.tokens[parserContext.index]); | |||
| return 'endSectionScan'; | |||
| } | |||
| }, | |||
| expectSectionOrEndSection: function(parserContext, contextStack) { | |||
| switch (parserContext.tokens[parserContext.index]) { | |||
| case '#': | |||
| case '^': | |||
| parserContext.stack[parserContext.stack.length-1].depth++; | |||
| parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag + parserContext.tokens[parserContext.index]); | |||
| return 'endSectionScan'; | |||
| case '/': | |||
| parserContext.stack.push({tagType:'endSection'}); | |||
| return 'simpleKeyName'; | |||
| default: | |||
| parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag + parserContext.tokens[parserContext.index]); | |||
| return 'endSectionScan'; | |||
| } | |||
| }, | |||
| discard: function(parserContext, contextStack) { | |||
| if (parserContext.tokens[parserContext.index]===parserContext.closeTag) { | |||
| return 'text'; | |||
| } else { | |||
| return 'discard'; | |||
| } | |||
| }, | |||
| endOfDoc: function(parserContext, contextStack) { | |||
| // eventually we may want to give better error messages | |||
| throw new ParserException('Unexpected end of document.'); | |||
| } | |||
| }, | |||
| dispatchCommand: function(parserContext, contextStack) { | |||
| var key = parserContext.stack.pop(); | |||
| var command = parserContext.stack.pop(); | |||
| switch (command.tagType) { | |||
| case 'section': | |||
| parserContext.stack.push({sectionType:'section', key:key, content:[], depth:1}); | |||
| return 'endSectionScan'; | |||
| case 'invertedSection': | |||
| parserContext.stack.push({sectionType:'invertedSection', key:key, content:[], depth:1}); | |||
| return 'endSectionScan'; | |||
| case 'variable': | |||
| this.render_variable(key, contextStack); | |||
| return 'text'; | |||
| case 'unescapedVariable': | |||
| this.render_unescaped_variable(key, contextStack); | |||
| return 'text'; | |||
| case 'partial': | |||
| this.render_partial(key, | |||
| contextStack, | |||
| parserContext.partials, | |||
| parserContext.openTag, | |||
| parserContext.closeTag); | |||
| return 'text'; | |||
| case 'endSection': | |||
| var section = parserContext.stack.pop(); | |||
| if (--section.depth === 0) { | |||
| if (section.key === key) { | |||
| this.render_section(section.sectionType, | |||
| section.content.join(''), | |||
| key, | |||
| contextStack, | |||
| parserContext.partials, | |||
| parserContext.openTag, | |||
| parserContext.closeTag); | |||
| return 'text'; | |||
| } else { | |||
| throw new ParserException('Unbalanced open/close section tags'); | |||
| } | |||
| } else { | |||
| section.content.push('{{/' + key + '}}'); | |||
| parserContext.stack.push(section); | |||
| return 'endSectionScan'; | |||
| } | |||
| default: | |||
| throw new ParserException('Unknown dispatch command: ' + command.tagType); | |||
| } | |||
| }, | |||
| /* | |||
| find `name` in current `context`. That is find me a value | |||
| from the view object | |||
| */ | |||
| find: function(name, context) { | |||
| // Checks whether a value is truthy or false or 0 | |||
| function is_kinda_truthy(bool) { | |||
| return bool === false || bool === 0 || bool; | |||
| } | |||
| var value; | |||
| if (is_kinda_truthy(context[name])) { | |||
| value = context[name]; | |||
| } | |||
| if (this.is_function(value)) { | |||
| return value.apply(context); | |||
| } | |||
| return value; | |||
| }, | |||
| find_in_stack: function(name, contextStack) { | |||
| var value; | |||
| for (var i=contextStack.length-1; i>=0; --i) { | |||
| value = this.find(name, contextStack[i]); | |||
| if (value) { | |||
| return value; | |||
| } | |||
| } | |||
| return undefined; | |||
| }, | |||
| is_function: function(a) { | |||
| return a && typeof a === 'function'; | |||
| }, | |||
| is_object: function(a) { | |||
| return a && typeof a === 'object'; | |||
| }, | |||
| is_array: function(a) { | |||
| return Object.prototype.toString.call(a) === '[object Array]'; | |||
| }, | |||
| render_variable: function(key, contextStack) { | |||
| function escapeHTML(str) { | |||
| return ('' + str).replace(/&/g,'&') | |||
| .replace(/</g,'<') | |||
| .replace(/>/g,'>'); | |||
| } | |||
| var result = this.find(key, contextStack); | |||
| if (result!==undefined) { | |||
| this.send_func(escapeHTML(result)); | |||
| } | |||
| }, | |||
| render_unescaped_variable: function(key, contextStack) { | |||
| var result = this.find(key, contextStack); | |||
| if (result!==undefined) { | |||
| this.send_func(result); | |||
| } | |||
| }, | |||
| render_partial: function(key, contextStack, partials, openTag, closeTag) { | |||
| if (!partials || partials[key] === undefined) { | |||
| throw new ParserException('Unknown partial \'' + key + '\''); | |||
| } | |||
| var res = this.find(key, contextStack[contextStack.length-1]); | |||
| if (this.is_object(res)) { | |||
| contextStack.push(res); | |||
| } | |||
| var tokens = this.tokenize(partials[key], openTag, closeTag); | |||
| this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| if (this.is_object(res)) { | |||
| contextStack.pop(); | |||
| } | |||
| }, | |||
| render_section: function(sectionType, mustacheFragment, key, contextStack, partials, openTag, closeTag) { | |||
| // by @langalex, support for arrays of strings | |||
| var that = this; | |||
| function create_context(_context) { | |||
| if(that.is_object(_context)) { | |||
| return _context; | |||
| } else { | |||
| var iterator = '.'; | |||
| // NO IMPLICIT-ITERATOR implementation yet | |||
| //if(that.pragmas["IMPLICIT-ITERATOR"] && | |||
| // that.pragmas["IMPLICIT-ITERATOR"].iterator) { | |||
| // iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator; | |||
| //} | |||
| var ctx = {}; | |||
| ctx[iterator] = _context; | |||
| return ctx; | |||
| } | |||
| } | |||
| var value = this.find(key, contextStack[contextStack.length-1]); | |||
| var tokens; | |||
| if (sectionType==='invertedSection') { | |||
| if (!value || this.is_array(value) && value.length === 0) { | |||
| // false or empty list, render it | |||
| tokens = this.tokenize(mustacheFragment, openTag, closeTag); | |||
| this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| } | |||
| } else if (sectionType==='section') { | |||
| if (this.is_array(value)) { // Enumerable, Let's loop! | |||
| tokens = this.tokenize(mustacheFragment, openTag, closeTag); | |||
| for (var i=0, n=value.length; i<n; ++i) { | |||
| contextStack.push(create_context(value[i])); | |||
| this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| contextStack.pop(); | |||
| } | |||
| } else if (this.is_object(value)) { // Object, Use it as subcontext! | |||
| tokens = this.tokenize(mustacheFragment, openTag, closeTag); | |||
| contextStack.push(value); | |||
| this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| contextStack.pop(); | |||
| } else if (this.is_function(value)) { | |||
| // higher order section | |||
| var that = this; | |||
| var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) { | |||
| var tempStream = []; | |||
| var old_send_func = that.send_func; | |||
| that.send_func = function(text) { tempStream.push(text); }; | |||
| tokens = that.tokenize(resultFragment, openTag, closeTag); | |||
| that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| that.send_func = old_send_func; | |||
| return tempStream.join(''); | |||
| }); | |||
| this.send_func(result); | |||
| } else if (value) { // boolean section | |||
| tokens = this.tokenize(mustacheFragment, openTag, closeTag); | |||
| this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack); | |||
| } | |||
| } else { | |||
| throw new ParserException('Unknown section type ' + sectionType); | |||
| } | |||
| } | |||
| } | |||
| return({ | |||
| name: "mustache.js", | |||
| version: "0.4.0-vcs", | |||
| /* | |||
| Turns a template and view into HTML | |||
| */ | |||
| to_html: function(template, view, partials, send_func) { | |||
| var o = []; | |||
| var s = send_func || function(output) { o.push(output); }; | |||
| var renderer = new Renderer(s); | |||
| renderer.render(template, view, partials); | |||
| if (!send_func) { | |||
| return o.join(''); | |||
| } | |||
| } | |||
| }); | |||
| }(); | |||