1 /**
  2  * @fileOverview KISSY Template Engine
  3  * @author yyfrankyy@gmail.com
  4  */
  5 KISSY.add('template', function (S) {
  6 
  7     var // Template Cache
  8         templateCache = {},
  9 
 10         // start/end tag mark
 11         tagStartEnd = {
 12             '#':'start',
 13             '@':'start',
 14             '/':'end'
 15         },
 16 
 17         // static string
 18         KS_TEMPL_STAT_PARAM = 'KS_TEMPL_STAT_PARAM',
 19         KS_TEMPL_STAT_PARAM_REG = new RegExp(KS_TEMPL_STAT_PARAM, "g"),
 20         KS_TEMPL = 'KS_TEMPL',
 21         KS_DATA = 'KS_DATA_',
 22         KS_AS = 'as',
 23 
 24         // note : double quote for generated code
 25         PREFIX = '");',
 26         SUFFIX = KS_TEMPL + '.push("',
 27 
 28         PARSER_SYNTAX_ERROR = 'KISSY.Template: Syntax Error. ',
 29         PARSER_RENDER_ERROR = 'KISSY.Template: Render Error. ',
 30 
 31         PARSER_PREFIX = 'var ' + KS_TEMPL + '=[],' +
 32             KS_TEMPL_STAT_PARAM + '=false;with(',
 33 
 34         PARSER_MIDDLE = '||{}){try{' + KS_TEMPL + '.push("',
 35 
 36         PARSER_SUFFIX = '");}catch(e){' + KS_TEMPL + '=["' +
 37             PARSER_RENDER_ERROR + '" + e.message]}};return ' +
 38             KS_TEMPL + '.join("");',
 39 
 40         // restore double quote in logic template variable
 41         restoreQuote = function (str) {
 42             return str.replace(/\\"/g, '"');
 43         },
 44 
 45         // escape double quote in template
 46         escapeQuote = function (str) {
 47             return str.replace(/"/g, '\\"');
 48         },
 49 
 50         trim = S.trim,
 51 
 52         // build a static parser
 53         buildParser = function (tpl) {
 54             var _parser,
 55                 _empty_index;
 56             return escapeQuote(trim(tpl)
 57                 .replace(/[\r\t\n]/g, ' ')
 58                 // escape escape ... . in case \ is consumed when run tpl parser function
 59                 // '{{y}}\\x{{/y}}' =>tmpl.push('\x'); => tmpl.push('\\x');
 60                 .replace(/\\/g, '\\\\'))
 61                 .replace(/\{\{([#/@]?)(?!\}\})([^}]*)\}\}/g,
 62                 function (all, expr, body) {
 63                     _parser = "";
 64                     // must restore quote , if str is used as code directly
 65                     body = restoreQuote(trim(body));
 66                     // is an expression
 67                     if (expr) {
 68                         _empty_index = body.indexOf(' ');
 69                         body = _empty_index === -1 ?
 70                             [ body, '' ] :
 71                             [
 72                                 body.substring(0, _empty_index),
 73                                 body.substring(_empty_index)
 74                             ];
 75 
 76                         var operator = body[0],
 77                             fn,
 78                             args = trim(body[1]),
 79                             opStatement = Statements[operator];
 80 
 81                         if (opStatement && tagStartEnd[expr]) {
 82                             // get expression definition function/string
 83                             fn = opStatement[tagStartEnd[expr]];
 84                             _parser = String(S.isFunction(fn) ?
 85                                 fn.apply(this, args.split(/\s+/)) :
 86                                 fn.replace(KS_TEMPL_STAT_PARAM_REG, args));
 87                         }
 88                     }
 89                     // return array directly
 90                     else {
 91                         _parser = KS_TEMPL +
 92                             '.push(' +
 93                             // prevent variable undefined error when look up in with, simple variable substitution
 94                             // with({}){alert(x);} => ReferenceError: x is not defined
 95                             'typeof (' + body + ') ==="undefined"?"":' + body +
 96                             ');';
 97                     }
 98                     return PREFIX + _parser + SUFFIX;
 99 
100                 });
101         },
102 
103         // expression
104         Statements = {
105             'if':{
106                 start:'if(typeof (' + KS_TEMPL_STAT_PARAM + ') !=="undefined" && ' + KS_TEMPL_STAT_PARAM + '){',
107                 end:'}'
108             },
109 
110             'else':{
111                 start:'}else{'
112             },
113 
114             'elseif':{
115                 start:'}else if(' + KS_TEMPL_STAT_PARAM + '){'
116             },
117 
118             // KISSY.each function wrap
119             'each':{
120                 start:function (obj, as, v, k) {
121                     var _ks_value = '_ks_value',
122                         _ks_index = '_ks_index';
123                     if (as === KS_AS && v) {
124                         _ks_value = v || _ks_value;
125                         _ks_index = k || _ks_index;
126                     }
127                     return 'KISSY.each(' + obj +
128                         ', function(' + _ks_value +
129                         ', ' + _ks_index + '){';
130                 },
131                 end:'});'
132             },
133 
134             // comments
135             '!':{
136                 start:'/*' + KS_TEMPL_STAT_PARAM + '*/'
137             }
138         };
139 
140     /**
141      * Template
142      * @param {String} tpl template to be rendered.
143      * @return {Object} return this for chain.
144      */
145     function Template(tpl) {
146         if (!(templateCache[tpl])) {
147             var _ks_data = S.guid(KS_DATA),
148                 func,
149                 o,
150                 _parser = [
151                     PARSER_PREFIX,
152                     _ks_data,
153                     PARSER_MIDDLE,
154                     o = buildParser(tpl),
155                     PARSER_SUFFIX
156                 ];
157 
158             try {
159                 func = new Function(_ks_data, _parser.join(""));
160             } catch (e) {
161                 _parser[3] = PREFIX + SUFFIX +
162                     PARSER_SYNTAX_ERROR + ',' +
163                     e.message + PREFIX + SUFFIX;
164                 func = new Function(_ks_data, _parser.join(""));
165             }
166 
167             templateCache[tpl] = {
168                 name:_ks_data,
169                 o:o,
170                 parser:_parser.join(""),
171                 render:func
172             };
173         }
174         return templateCache[tpl];
175     }
176 
177     S.mix(Template, {
178         /**
179          * Logging Compiled Template Codes
180          * @param {String} tpl template string.
181          */
182         log:function (tpl) {
183             if (tpl in templateCache) {
184                 //     if ('js_beautify' in window) {
185 //                        S.log(js_beautify(templateCache[tpl].parser, {
186 //                            indent_size: 4,
187 //                            indent_char: ' ',
188 //                            preserve_newlines: true,
189 //                            braces_on_own_line: false,
190 //                            keep_array_indentation: false,
191 //                            space_after_anon_function: true
192 //                        }), 'info');
193                 // } else {
194                 S.log(templateCache[tpl].parser, 'info');
195                 // }
196             } else {
197                 Template(tpl);
198                 this.log(tpl);
199             }
200         },
201 
202         /**
203          * add statement for extending template tags
204          * @param {String} statement tag name.
205          * @param {String} o extent tag object.
206          */
207         addStatement:function (statement, o) {
208             if (S.isString(statement)) {
209                 Statements[statement] = o;
210             } else {
211                 S.mix(Statements, statement);
212             }
213         }
214 
215     });
216 
217     return Template;
218 
219 });
220 /**
221  * TODO: check spec
222  *  - http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html
223  *
224  * 2012-04-24 yiminghe@gmail.com
225  *      - support {{@if test}}t{{/if}} to prevent collision with velocity template engine
226  *
227  *
228  * 2011-09-20 note by yiminghe :
229  *      - code style change
230  *      - remove reg cache , ugly to see
231  *      - fix escape by escape
232  *      - expect(T('{{#if a=="a"}}{{b}}\\"{{/if}}').render({a:"a",b:"b"})).toBe('b\\"');
233  */
234