| @@ -101,6 +101,7 @@ var Mustache = (function(undefined) { | |||
| } | |||
| })(); | |||
| /* BEGIN Helpers */ | |||
| function noop() {} | |||
| var escapeCompiledRegex; | |||
| @@ -116,17 +117,62 @@ var Mustache = (function(undefined) { | |||
| return text.replace(escapeCompiledRegex, '\\$1'); | |||
| } | |||
| function isWhitespace(token) { | |||
| return token.match(/^\s+$/)!==null; | |||
| function is_function(a) { | |||
| return a && typeof a === 'function'; | |||
| } | |||
| function isNewline(token) { | |||
| return token.match(/\r?\n/)!==null; | |||
| function is_object(a) { | |||
| return a && typeof a === 'object'; | |||
| } | |||
| function is_array(a) { | |||
| return Object.prototype.toString.call(a) === '[object Array]'; | |||
| } | |||
| /* END Helpers */ | |||
| /* BEGIN Compiler */ | |||
| function compile(state, noReturn) { | |||
| var n, c, token; | |||
| for (n = state.tokens.length;state.cursor<n;++state.cursor) { | |||
| token = state.tokens[state.cursor]; | |||
| if (token==='' || token===undefined) { | |||
| continue; | |||
| } | |||
| if (token.indexOf(state.openTag)===0) { | |||
| c = token.charAt(state.openTag.length); | |||
| if (state.parser[c]) { | |||
| state.parser[c](state, token, c); | |||
| } else { | |||
| state.parser.def(state, token); | |||
| } | |||
| } else { | |||
| state.parser.text(state, token); | |||
| } | |||
| } | |||
| if (!noReturn) { | |||
| var codeList = state.code; | |||
| if (codeList.length === 0) { | |||
| return noop; | |||
| } else if (codeList.length === 1) { | |||
| return codeList[0]; | |||
| } else { | |||
| return function(context, send_func) { | |||
| for (var i=0,n=codeList.length;i<n;++i) { | |||
| codeList[i](context, send_func); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| var default_tokenizer = /(\r?\n)|({{![\s\S]*?!}})|({{[#\^\/&{>=]?\s*\S*?\s*}?}})|({{=\S*?\s*\S*?=}})/; | |||
| function create_parser_state(template, partials, openTag, closeTag) { | |||
| function create_compiler_state(template, partials, openTag, closeTag) { | |||
| openTag = openTag || '{{'; | |||
| closeTag = closeTag || '}}'; | |||
| @@ -146,16 +192,15 @@ var Mustache = (function(undefined) { | |||
| , partials: partials || {} | |||
| , openTag: openTag | |||
| , closeTag: closeTag | |||
| , state: 'normal' | |||
| , parser: default_parser | |||
| , pragmas: {} | |||
| , code: code | |||
| , send_code_func: function(f) { | |||
| code.push(f); | |||
| } | |||
| }; | |||
| // prefilter pragmas | |||
| pragmas(state); | |||
| pragmas(state); // use pragmas to determine parsing behaviour | |||
| // tokenize and initialize a cursor | |||
| state.tokens = splitFunc.call(state.template, tokenizer); | |||
| @@ -164,18 +209,60 @@ var Mustache = (function(undefined) { | |||
| return state; | |||
| } | |||
| function is_function(a) { | |||
| return a && typeof a === 'function'; | |||
| } | |||
| function is_object(a) { | |||
| return a && typeof a === 'object'; | |||
| } | |||
| function pragmas(state) { | |||
| /* includes tag */ | |||
| function includes(needle, haystack) { | |||
| return haystack.indexOf('{{' + needle) !== -1; | |||
| } | |||
| var directives = { | |||
| 'IMPLICIT-ITERATOR': function(options) { | |||
| state.pragmas['IMPLICIT-ITERATOR'] = {iterator: '.'}; | |||
| if (options) { | |||
| state.pragmas['IMPLICIT-ITERATOR'].iterator = options['iterator']; | |||
| } | |||
| } | |||
| }; | |||
| // no pragmas, easy escape | |||
| if(!includes("%", state.template)) { | |||
| return state.template; | |||
| } | |||
| function is_array(a) { | |||
| return Object.prototype.toString.call(a) === '[object Array]'; | |||
| state.template = state.template.replace(/{{%([\w-]+)(\s*)(.*?(?=}}))}}/, function(match, pragma, space, suffix) { | |||
| var options = undefined, | |||
| optionPairs, scratch, | |||
| i, n; | |||
| if (suffix.length>0) { | |||
| optionPairs = suffix.split(','); | |||
| scratch; | |||
| options = {}; | |||
| for (i=0, n=optionPairs.length; i<n; ++i) { | |||
| scratch = optionPairs[i].split('='); | |||
| if (scratch.length !== 2) { | |||
| throw new Error('Malformed pragma options:' + optionPairs[i]); | |||
| } | |||
| options[scratch[0]] = scratch[1]; | |||
| } | |||
| } | |||
| if (is_function(directives[pragma])) { | |||
| directives[pragma](options); | |||
| } else { | |||
| throw new Error('This implementation of mustache does not implement the "' + pragma + '" pragma'); | |||
| } | |||
| return ''; // blank out all pragmas | |||
| }); | |||
| } | |||
| /* END Compiler */ | |||
| /* BEGIN Run Time Helpers */ | |||
| /* | |||
| find `name` in current `context`. That is find me a value | |||
| from the view object | |||
| @@ -211,33 +298,25 @@ var Mustache = (function(undefined) { | |||
| return undefined; | |||
| } | |||
| /* END Run Time Helpers */ | |||
| function get_variable_name(state, token, prefix, postfix) { | |||
| var fragment = token | |||
| .substring( | |||
| state.openTag.length + (prefix ? 1 : 0) | |||
| , token.length - state.closeTag.length - (postfix ? 1 : 0) | |||
| ); | |||
| if (String.prototype.trim) { | |||
| return fragment.trim(); | |||
| } else { | |||
| return fragment.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); | |||
| } | |||
| function text(state, token) { | |||
| state.send_code_func(function(context, send_func) { send_func(token); }); | |||
| } | |||
| function interpolate(state, token, escape) { | |||
| function interpolate(state, token, mark) { | |||
| function escapeHTML(str) { | |||
| return str.replace(/&/g,'&') | |||
| .replace(/</g,'<') | |||
| .replace(/>/g,'>'); | |||
| } | |||
| var prefix, postfix; | |||
| if (escape==='{') { | |||
| prefix = postfix = true; | |||
| } else if (escape==='&') { | |||
| prefix = true; | |||
| var escape, prefix, postfix; | |||
| if (mark==='{') { | |||
| escape = prefix = postfix = true; | |||
| } else if (mark==='&') { | |||
| escape = prefix = true; | |||
| } | |||
| var variable = get_variable_name(state, token, prefix, postfix); | |||
| @@ -267,7 +346,7 @@ var Mustache = (function(undefined) { | |||
| template = state.partials[variable]; // remember what the partial was | |||
| state.partials[variable] = noop; // avoid infinite recursion | |||
| program = parse(create_parser_state( | |||
| program = compile(create_compiler_state( | |||
| template | |||
| , state.partials | |||
| )); | |||
| @@ -295,13 +374,6 @@ var Mustache = (function(undefined) { | |||
| } | |||
| function section(state) { | |||
| function create_section_state(template) { | |||
| return create_parser_state(template, | |||
| state.partials, | |||
| state.openTag, | |||
| state.closeTag); | |||
| } | |||
| // by @langalex, support for arrays of strings | |||
| function create_context(_context) { | |||
| if(is_object(_context)) { | |||
| @@ -316,8 +388,11 @@ var Mustache = (function(undefined) { | |||
| } | |||
| } | |||
| var s = state.section, template = s.template_buffer.join('') | |||
| program = parse(create_section_state(template)); | |||
| var s = state.section, template = s.template_buffer.join(''), | |||
| program = compile(create_compiler_state(template, | |||
| state.partials, | |||
| state.openTag, | |||
| state.closeTag)); | |||
| if (s.inverted) { | |||
| state.send_code_func((function(program, variable){ return function(context, send_func) { | |||
| @@ -347,7 +422,7 @@ var Mustache = (function(undefined) { | |||
| var o = [], | |||
| user_send_func = function(str) { o.push(str); }; | |||
| parse(create_parser_state(hosFragment, partials))(context, user_send_func); | |||
| compile(create_compiler_state(hosFragment, partials))(context, user_send_func); | |||
| return o.join(''); | |||
| })); | |||
| @@ -357,55 +432,47 @@ var Mustache = (function(undefined) { | |||
| };})(program, s.variable, template, state.partials)); | |||
| } | |||
| } | |||
| /* BEGIN Parser */ | |||
| function pragmas(state) { | |||
| /* includes tag */ | |||
| function includes(needle, haystack) { | |||
| return haystack.indexOf('{{' + needle) !== -1; | |||
| } | |||
| var directives = { | |||
| 'IMPLICIT-ITERATOR': function(options) { | |||
| state.pragmas['IMPLICIT-ITERATOR'] = {iterator: '.'}; | |||
| if (options) { | |||
| state.pragmas['IMPLICIT-ITERATOR'].iterator = options['iterator']; | |||
| } | |||
| } | |||
| }; | |||
| var default_parser = { | |||
| '!': noop, | |||
| '#': begin_section, | |||
| '^': begin_section, | |||
| '/': function(state, token) { throw new Error('Unbalanced End Section tag: ' + token); }, | |||
| '&': interpolate, | |||
| '{': interpolate, | |||
| '>': partial, | |||
| '=': change_delimiter, | |||
| def: interpolate, | |||
| text: text | |||
| }; | |||
| var scan_section_parser = { | |||
| '!': noop, | |||
| '#': begin_section, | |||
| '^': begin_section, | |||
| '/': end_section, | |||
| '&': buffer_section, | |||
| '{': buffer_section, | |||
| '>': buffer_section, | |||
| '=': change_delimiter, | |||
| def: buffer_section, | |||
| text: buffer_section | |||
| }; | |||
| // no pragmas, easy escape | |||
| if(!includes("%", state.template)) { | |||
| return state.template; | |||
| } | |||
| state.template = state.template.replace(/{{%([\w-]+)(\s*)(.*?(?=}}))}}/, function(match, pragma, space, suffix) { | |||
| var options = undefined, | |||
| optionPairs, scratch, | |||
| i, n; | |||
| if (suffix.length>0) { | |||
| optionPairs = suffix.split(','); | |||
| scratch; | |||
| options = {}; | |||
| for (i=0, n=optionPairs.length; i<n; ++i) { | |||
| scratch = optionPairs[i].split('='); | |||
| if (scratch.length !== 2) { | |||
| throw new Error('Malformed pragma options:' + optionPairs[i]); | |||
| } | |||
| options[scratch[0]] = scratch[1]; | |||
| } | |||
| } | |||
| function get_variable_name(state, token, prefix, postfix) { | |||
| var fragment = token | |||
| .substring( | |||
| state.openTag.length + (prefix ? 1 : 0) | |||
| , token.length - state.closeTag.length - (postfix ? 1 : 0) | |||
| ); | |||
| if (is_function(directives[pragma])) { | |||
| directives[pragma](options); | |||
| } else { | |||
| throw new Error('This implementation of mustache does not implement the "' + pragma + '" pragma'); | |||
| } | |||
| return ''; // blank out all pragmas | |||
| }); | |||
| if (String.prototype.trim) { | |||
| return fragment.trim(); | |||
| } else { | |||
| return fragment.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); | |||
| } | |||
| } | |||
| function change_delimiter(state, token) { | |||
| @@ -415,7 +482,7 @@ var Mustache = (function(undefined) { | |||
| throw new Error('Malformed change delimiter token: ' + token); | |||
| } | |||
| var context = create_parser_state( | |||
| var context = create_compiler_state( | |||
| state.tokens.slice(state.cursor+1).join('') | |||
| , state.partials | |||
| , matches[1] | |||
| @@ -425,14 +492,15 @@ var Mustache = (function(undefined) { | |||
| state.cursor = state.tokens.length; // finish off this level | |||
| parse(context, true); | |||
| compile(context, true); | |||
| } | |||
| function begin_section(state, token, inverted) { | |||
| var variable = get_variable_name(state, token, true); | |||
| function begin_section(state, token, mark) { | |||
| var inverted = mark === '^', | |||
| variable = get_variable_name(state, token, true); | |||
| if (state.state==='normal') { | |||
| state.state = 'scan_section'; | |||
| if (state.parser===default_parser) { | |||
| state.parser = scan_section_parser; | |||
| state.section = { | |||
| variable: variable | |||
| , template_buffer: [] | |||
| @@ -445,6 +513,10 @@ var Mustache = (function(undefined) { | |||
| } | |||
| } | |||
| function buffer_section(state, token) { | |||
| state.section.template_buffer.push(token); | |||
| } | |||
| function end_section(state, token) { | |||
| var variable = get_variable_name(state, token, true); | |||
| @@ -456,104 +528,13 @@ var Mustache = (function(undefined) { | |||
| } else if (state.section.variable===variable) { | |||
| section(state); | |||
| delete state.section; | |||
| state.state = 'normal'; | |||
| state.parser = default_parser; | |||
| } else { | |||
| throw new Error('Unexpected section end tag. Expected: ' + state.section.variable); | |||
| } | |||
| } | |||
| function parse(state, noReturn) { | |||
| var n, token; | |||
| for (n = state.tokens.length;state.cursor<n;++state.cursor) { | |||
| token = state.tokens[state.cursor]; | |||
| if (token==='' || token===undefined) { | |||
| continue; | |||
| } | |||
| stateMachine[state.state](state, token); | |||
| } | |||
| if (!noReturn) { | |||
| var codeList = state.code; | |||
| if (codeList.length === 0) { | |||
| return noop; | |||
| } else if (codeList.length === 1) { | |||
| return codeList[0]; | |||
| } else { | |||
| return function(context, send_func) { | |||
| for (var i=0,n=codeList.length;i<n;++i) { | |||
| codeList[i](context, send_func); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| var stateMachine = { | |||
| 'normal': function(state, token) { | |||
| if (token.indexOf(state.openTag)===0) { | |||
| // the token has the makings of a Mustache tag | |||
| // perform the appropriate action based on the state machine | |||
| switch (token.charAt(state.openTag.length)) { | |||
| case '!': // comment | |||
| // comments are just discarded, nothing to do | |||
| break; | |||
| case '#': // section | |||
| begin_section(state, token, false); | |||
| break; | |||
| case '^': // inverted section | |||
| begin_section(state, token, true); | |||
| break; | |||
| case '/': // end section | |||
| // in normal flow, this operation is absolutely meaningless | |||
| throw new Error('Unbalanced End Section tag: ' + token); | |||
| case '&': // unescaped variable | |||
| case '{': // unescaped variable | |||
| interpolate(state, token, token.charAt(state.openTag.length)); | |||
| break; | |||
| case '>': // partial | |||
| partial(state, token); | |||
| break; | |||
| case '=': // set delimiter change | |||
| change_delimiter(state, token); | |||
| break; | |||
| default: // escaped variable | |||
| interpolate(state, token); | |||
| break; | |||
| } | |||
| } else { | |||
| // plain jane text | |||
| state.send_code_func(function(view, send_func) { send_func(token); }); | |||
| } | |||
| } | |||
| , 'scan_section': function(state, token) { | |||
| if (token.indexOf(state.openTag)===0) { | |||
| switch (token.charAt(state.openTag.length)) { | |||
| case '!': // comments | |||
| // comments are just discarded, nothing to do | |||
| break; | |||
| case '#': // section | |||
| begin_section(state, token, false); | |||
| break; | |||
| case '^': // inverted section | |||
| begin_section(state, token, true); | |||
| break; | |||
| case '/': // end section | |||
| end_section(state, token); | |||
| break; | |||
| case '=': // set delimiter change | |||
| change_delimiter(state, token); | |||
| break; | |||
| default: // all others | |||
| state.section.template_buffer.push(token); | |||
| break; | |||
| } | |||
| } else { | |||
| state.section.template_buffer.push(token); | |||
| } | |||
| } | |||
| } | |||
| /* END Parser */ | |||
| return({ | |||
| name: "mustache.js", | |||
| @@ -581,7 +562,7 @@ var Mustache = (function(undefined) { | |||
| } | |||
| } | |||
| var program = parse(create_parser_state(template, p)); | |||
| var program = compile(create_compiler_state(template, p)); | |||
| return function(view, send_func) { | |||
| var o = [], | |||
| user_send_func = send_func || function(str) { | |||