| @@ -101,6 +101,7 @@ var Mustache = (function(undefined) { | |||||
| } | } | ||||
| })(); | })(); | ||||
| /* BEGIN Helpers */ | |||||
| function noop() {} | function noop() {} | ||||
| var escapeCompiledRegex; | var escapeCompiledRegex; | ||||
| @@ -116,17 +117,62 @@ var Mustache = (function(undefined) { | |||||
| return text.replace(escapeCompiledRegex, '\\$1'); | 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*?=}})/; | 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 || '{{'; | openTag = openTag || '{{'; | ||||
| closeTag = closeTag || '}}'; | closeTag = closeTag || '}}'; | ||||
| @@ -146,16 +192,15 @@ var Mustache = (function(undefined) { | |||||
| , partials: partials || {} | , partials: partials || {} | ||||
| , openTag: openTag | , openTag: openTag | ||||
| , closeTag: closeTag | , closeTag: closeTag | ||||
| , state: 'normal' | |||||
| , parser: default_parser | |||||
| , pragmas: {} | , pragmas: {} | ||||
| , code: code | , code: code | ||||
| , send_code_func: function(f) { | , send_code_func: function(f) { | ||||
| code.push(f); | code.push(f); | ||||
| } | } | ||||
| }; | }; | ||||
| // prefilter pragmas | |||||
| pragmas(state); | |||||
| pragmas(state); // use pragmas to determine parsing behaviour | |||||
| // tokenize and initialize a cursor | // tokenize and initialize a cursor | ||||
| state.tokens = splitFunc.call(state.template, tokenizer); | state.tokens = splitFunc.call(state.template, tokenizer); | ||||
| @@ -164,18 +209,60 @@ var Mustache = (function(undefined) { | |||||
| return state; | 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 | find `name` in current `context`. That is find me a value | ||||
| from the view object | from the view object | ||||
| @@ -211,33 +298,25 @@ var Mustache = (function(undefined) { | |||||
| return 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) { | function escapeHTML(str) { | ||||
| return str.replace(/&/g,'&') | return str.replace(/&/g,'&') | ||||
| .replace(/</g,'<') | .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); | 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 | template = state.partials[variable]; // remember what the partial was | ||||
| state.partials[variable] = noop; // avoid infinite recursion | state.partials[variable] = noop; // avoid infinite recursion | ||||
| program = parse(create_parser_state( | |||||
| program = compile(create_compiler_state( | |||||
| template | template | ||||
| , state.partials | , state.partials | ||||
| )); | )); | ||||
| @@ -295,13 +374,6 @@ var Mustache = (function(undefined) { | |||||
| } | } | ||||
| function section(state) { | 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 | // by @langalex, support for arrays of strings | ||||
| function create_context(_context) { | function create_context(_context) { | ||||
| if(is_object(_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) { | if (s.inverted) { | ||||
| state.send_code_func((function(program, variable){ return function(context, send_func) { | state.send_code_func((function(program, variable){ return function(context, send_func) { | ||||
| @@ -347,7 +422,7 @@ var Mustache = (function(undefined) { | |||||
| var o = [], | var o = [], | ||||
| user_send_func = function(str) { o.push(str); }; | 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(''); | return o.join(''); | ||||
| })); | })); | ||||
| @@ -357,55 +432,47 @@ var Mustache = (function(undefined) { | |||||
| };})(program, s.variable, template, state.partials)); | };})(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) { | function change_delimiter(state, token) { | ||||
| @@ -415,7 +482,7 @@ var Mustache = (function(undefined) { | |||||
| throw new Error('Malformed change delimiter token: ' + token); | 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.tokens.slice(state.cursor+1).join('') | ||||
| , state.partials | , state.partials | ||||
| , matches[1] | , matches[1] | ||||
| @@ -425,14 +492,15 @@ var Mustache = (function(undefined) { | |||||
| state.cursor = state.tokens.length; // finish off this level | 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 = { | state.section = { | ||||
| variable: variable | variable: variable | ||||
| , template_buffer: [] | , 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) { | function end_section(state, token) { | ||||
| var variable = get_variable_name(state, token, true); | var variable = get_variable_name(state, token, true); | ||||
| @@ -456,104 +528,13 @@ var Mustache = (function(undefined) { | |||||
| } else if (state.section.variable===variable) { | } else if (state.section.variable===variable) { | ||||
| section(state); | section(state); | ||||
| delete state.section; | delete state.section; | ||||
| state.state = 'normal'; | |||||
| state.parser = default_parser; | |||||
| } else { | } else { | ||||
| throw new Error('Unexpected section end tag. Expected: ' + state.section.variable); | 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({ | return({ | ||||
| name: "mustache.js", | 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) { | return function(view, send_func) { | ||||
| var o = [], | var o = [], | ||||
| user_send_func = send_func || function(str) { | user_send_func = send_func || function(str) { | ||||