").join(">").split('"').join(""").split("'").join("'")}});a.extend(a.tmpl,{tag:{tmpl:{_default:{$2:"null"},open:"if($notnull_1){__=__.concat($item.nest($1,$2));}"},wrap:{_default:{$2:"null"},open:"$item.calls(__,$1,$2);__=[];",close:"call=$item.calls();__=call._.concat($item.wrap(call,__));"},each:{_default:{$2:"$index, $value"},open:"if($notnull_1){$.each($1a,function($2){with(this){",close:"}});}"},"if":{open:"if(($notnull_1) && $1a){",close:"}"},"else":{_default:{$1:"true"},open:"}else if(($notnull_1) && $1a){"},html:{open:"if($notnull_1){__.push($1a);}"},"=":{_default:{$1:"$data"},open:"if($notnull_1){__.push($.encode($1a));}"},"!":{open:""}},complete:function(){b={}},afterManip:function(f,b,d){var e=b.nodeType===11?a.makeArray(b.childNodes):b.nodeType===1?[b]:[];d.call(f,b);m(e);c++}});function j(e,g,f){var b,c=f?a.map(f,function(a){return typeof a==="string"?e.key?a.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g,"$1 "+d+'="'+e.key+'" $2'):a:j(a,e,a._ctnt)}):e;if(g)return c;c=c.join("");c.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/,function(f,c,e,d){b=a(e).get();m(b);if(c)b=k(c).concat(b);if(d)b=b.concat(k(d))});return b?b:k(c)}function k(c){var b=document.createElement("div");b.innerHTML=c;return a.makeArray(b.childNodes)}function o(b){return new Function("jQuery","$item","var $=jQuery,call,__=[],$data=$item.data;with($data){__.push('"+a.trim(b).replace(/([\\'])/g,"\\$1").replace(/[\r\t\n]/g," ").replace(/\$\{([^\}]*)\}/g,"{{= $1}}").replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,function(m,l,k,g,b,c,d){var j=a.tmpl.tag[k],i,e,f;if(!j)throw"Unknown template tag: "+k;i=j._default||[];if(c&&!/\w$/.test(b)){b+=c;c=""}if(b){b=h(b);d=d?","+h(d)+")":c?")":"";e=c?b.indexOf(".")>-1?b+h(c):"("+b+").call($item"+d:b;f=c?e:"(typeof("+b+")==='function'?("+b+").call($item):("+b+"))"}else f=e=i.$1||"null";g=h(g);return"');"+j[l?"close":"open"].split("$notnull_1").join(b?"typeof("+b+")!=='undefined' && ("+b+")!=null":"true").split("$1a").join(f).split("$1").join(e).split("$2").join(g||i.$2||"")+"__.push('"})+"');}return __;")}function n(c,b){c._wrap=j(c,true,a.isArray(b)?b:[q.test(b)?b:a(b).html()]).join("")}function h(a){return a?a.replace(/\\'/g,"'").replace(/\\\\/g,"\\"):null}function s(b){var a=document.createElement("div");a.appendChild(b.cloneNode(true));return a.innerHTML}function m(o){var n="_"+c,k,j,l={},e,p,h;for(e=0,p=o.length;e=0;h--)m(j[h]);m(k)}function m(j){var p,h=j,k,e,m;if(m=j.getAttribute(d)){while(h.parentNode&&(h=h.parentNode).nodeType===1&&!(p=h.getAttribute(d)));if(p!==m){h=h.parentNode?h.nodeType===11?0:h.getAttribute(d)||0:0;if(!(e=b[m])){e=f[m];e=g(e,b[h]||f[h]);e.key=++i;b[i]=e}c&&o(m)}j.removeAttribute(d)}else if(c&&(e=a.data(j,"tmplItem"))){o(e.key);b[e.key]=e;h=a.data(j.parentNode,"tmplItem");h=h?h.key:0}if(e){k=e;while(k&&k.key!=h){k.nodes.push(j);k=k.parent}delete e._ctnt;delete e._wrap;a.data(j,"tmplItem",e)}function o(a){a=a+n;e=l[a]=l[a]||g(e,b[e.parent.key+n]||e.parent)}}}function u(a,d,c,b){if(!a)return l.pop();l.push({_:a,tmpl:d,item:this,data:c,options:b})}function w(d,c,b){return a.tmpl(a.template(d),c,b,this)}function x(b,d){var c=b.options||{};c.wrapped=d;return a.tmpl(a.template(b.tmpl),b.data,c,b.item)}function v(d,c){var b=this._wrap;return a.map(a(a.isArray(b)?b.join(""):b).filter(d||"*"),function(a){return c?a.innerText||a.textContent:a.outerHTML||s(a)})}function t(){var b=this.nodes;a.tmpl(null,null,null,this).insertBefore(b[0]);a(b).remove()}})(jQuery);
--------------------------------------------------------------------------------
/tests/test-debug.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tileTemplate Example
7 |
8 |
9 | 查看调试输出,请打开浏览器控制台
10 |
13 | output1
14 |
15 | output2
16 |
17 | output3
18 |
19 |
66 |
86 |
89 |
90 |
91 |
144 |
145 |
--------------------------------------------------------------------------------
/tests/js/qunit/qunit-1.16.0.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * QUnit 1.16.0
3 | * http://qunitjs.com/
4 | *
5 | * Copyright 2006, 2014 jQuery Foundation and other contributors
6 | * Released under the MIT license
7 | * http://jquery.org/license
8 | *
9 | * Date: 2014-12-03T16:32Z
10 | */
11 |
12 | /** Font Family and Sizes */
13 |
14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
16 | }
17 |
18 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
19 | #qunit-tests { font-size: smaller; }
20 |
21 |
22 | /** Resets */
23 |
24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 |
30 | /** Header */
31 |
32 | #qunit-header {
33 | padding: 0.5em 0 0.5em 1em;
34 |
35 | color: #8699A4;
36 | background-color: #0D3349;
37 |
38 | font-size: 1.5em;
39 | line-height: 1em;
40 | font-weight: 400;
41 |
42 | border-radius: 5px 5px 0 0;
43 | }
44 |
45 | #qunit-header a {
46 | text-decoration: none;
47 | color: #C2CCD1;
48 | }
49 |
50 | #qunit-header a:hover,
51 | #qunit-header a:focus {
52 | color: #FFF;
53 | }
54 |
55 | #qunit-testrunner-toolbar label {
56 | display: inline-block;
57 | padding: 0 0.5em 0 0.1em;
58 | }
59 |
60 | #qunit-banner {
61 | height: 5px;
62 | }
63 |
64 | #qunit-testrunner-toolbar {
65 | padding: 0.5em 1em 0.5em 1em;
66 | color: #5E740B;
67 | background-color: #EEE;
68 | overflow: hidden;
69 | }
70 |
71 | #qunit-userAgent {
72 | padding: 0.5em 1em 0.5em 1em;
73 | background-color: #2B81AF;
74 | color: #FFF;
75 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
76 | }
77 |
78 | #qunit-modulefilter-container {
79 | float: right;
80 | }
81 |
82 | /** Tests: Pass/Fail */
83 |
84 | #qunit-tests {
85 | list-style-position: inside;
86 | }
87 |
88 | #qunit-tests li {
89 | padding: 0.4em 1em 0.4em 1em;
90 | border-bottom: 1px solid #FFF;
91 | list-style-position: inside;
92 | }
93 |
94 | #qunit-tests > li {
95 | display: none;
96 | }
97 |
98 | #qunit-tests li.pass, #qunit-tests li.running, #qunit-tests li.fail {
99 | display: list-item;
100 | }
101 |
102 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
103 | display: none;
104 | }
105 |
106 | #qunit-tests li strong {
107 | cursor: pointer;
108 | }
109 |
110 | #qunit-tests li.skipped strong {
111 | cursor: default;
112 | }
113 |
114 | #qunit-tests li a {
115 | padding: 0.5em;
116 | color: #C2CCD1;
117 | text-decoration: none;
118 | }
119 | #qunit-tests li a:hover,
120 | #qunit-tests li a:focus {
121 | color: #000;
122 | }
123 |
124 | #qunit-tests li .runtime {
125 | float: right;
126 | font-size: smaller;
127 | }
128 |
129 | .qunit-assert-list {
130 | margin-top: 0.5em;
131 | padding: 0.5em;
132 |
133 | background-color: #FFF;
134 |
135 | border-radius: 5px;
136 | }
137 |
138 | .qunit-collapsed {
139 | display: none;
140 | }
141 |
142 | #qunit-tests table {
143 | border-collapse: collapse;
144 | margin-top: 0.2em;
145 | }
146 |
147 | #qunit-tests th {
148 | text-align: right;
149 | vertical-align: top;
150 | padding: 0 0.5em 0 0;
151 | }
152 |
153 | #qunit-tests td {
154 | vertical-align: top;
155 | }
156 |
157 | #qunit-tests pre {
158 | margin: 0;
159 | white-space: pre-wrap;
160 | word-wrap: break-word;
161 | }
162 |
163 | #qunit-tests del {
164 | background-color: #E0F2BE;
165 | color: #374E0C;
166 | text-decoration: none;
167 | }
168 |
169 | #qunit-tests ins {
170 | background-color: #FFCACA;
171 | color: #500;
172 | text-decoration: none;
173 | }
174 |
175 | /*** Test Counts */
176 |
177 | #qunit-tests b.counts { color: #000; }
178 | #qunit-tests b.passed { color: #5E740B; }
179 | #qunit-tests b.failed { color: #710909; }
180 |
181 | #qunit-tests li li {
182 | padding: 5px;
183 | background-color: #FFF;
184 | border-bottom: none;
185 | list-style-position: inside;
186 | }
187 |
188 | /*** Passing Styles */
189 |
190 | #qunit-tests li li.pass {
191 | color: #3C510C;
192 | background-color: #FFF;
193 | border-left: 10px solid #C6E746;
194 | }
195 |
196 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
197 | #qunit-tests .pass .test-name { color: #366097; }
198 |
199 | #qunit-tests .pass .test-actual,
200 | #qunit-tests .pass .test-expected { color: #999; }
201 |
202 | #qunit-banner.qunit-pass { background-color: #C6E746; }
203 |
204 | /*** Failing Styles */
205 |
206 | #qunit-tests li li.fail {
207 | color: #710909;
208 | background-color: #FFF;
209 | border-left: 10px solid #EE5757;
210 | white-space: pre;
211 | }
212 |
213 | #qunit-tests > li:last-child {
214 | border-radius: 0 0 5px 5px;
215 | }
216 |
217 | #qunit-tests .fail { color: #000; background-color: #EE5757; }
218 | #qunit-tests .fail .test-name,
219 | #qunit-tests .fail .module-name { color: #000; }
220 |
221 | #qunit-tests .fail .test-actual { color: #EE5757; }
222 | #qunit-tests .fail .test-expected { color: #008000; }
223 |
224 | #qunit-banner.qunit-fail { background-color: #EE5757; }
225 |
226 | /*** Skipped tests */
227 |
228 | #qunit-tests .skipped {
229 | background-color: #EBECE9;
230 | }
231 |
232 | #qunit-tests .qunit-skipped-label {
233 | background-color: #F4FF77;
234 | display: inline-block;
235 | font-style: normal;
236 | color: #366097;
237 | line-height: 1.8em;
238 | padding: 0 0.5em;
239 | margin: -0.4em 0.4em -0.4em 0;
240 | }
241 |
242 | /** Result */
243 |
244 | #qunit-testresult {
245 | padding: 0.5em 1em 0.5em 1em;
246 |
247 | color: #2B81AF;
248 | background-color: #D2E0E6;
249 |
250 | border-bottom: 1px solid #FFF;
251 | }
252 | #qunit-testresult .module-name {
253 | font-weight: 700;
254 | }
255 |
256 | /** Fixture */
257 |
258 | #qunit-fixture {
259 | position: absolute;
260 | top: -10000px;
261 | left: -10000px;
262 | width: 1000px;
263 | height: 1000px;
264 | }
265 |
--------------------------------------------------------------------------------
/examples/requirejs-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tileTemplate Example in Require.js
8 |
9 |
10 |
13 | output1
14 |
15 | output2
16 |
17 | output3
18 |
19 |
68 |
88 |
91 |
92 |
156 |
157 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tileTemplate Example
8 |
9 |
10 |
13 | output1
14 |
15 | output2
16 |
17 | output3
18 |
19 |
68 |
88 |
91 |
92 |
156 |
157 |
--------------------------------------------------------------------------------
/examples/seajs-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tileTemplate Example in Sea.js
8 |
9 |
10 |
13 | output1
14 |
15 | output2
16 |
17 | output3
18 |
19 |
73 |
110 |
130 |
133 |
137 |
140 |
162 |
163 |
174 |
175 |
--------------------------------------------------------------------------------
/tests/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | tileTemplate Test : basic
8 |
9 |
10 |
11 |
12 |
15 |
18 |
25 |
30 |
34 |
39 |
46 |
47 |
48 |
49 |
208 |
209 |
--------------------------------------------------------------------------------
/tests/js/templates/baiduTemplate.js:
--------------------------------------------------------------------------------
1 | /**
2 | * baiduTemplate简单好用的Javascript模板引擎 1.0.6 版本
3 | * http://baidufe.github.com/BaiduTemplate
4 | * 开源协议:BSD License
5 | * 浏览器环境占用命名空间 baidu.template ,nodejs环境直接安装 npm install baidutemplate
6 | * @param str{String} dom结点ID,或者模板string
7 | * @param data{Object} 需要渲染的json对象,可以为空。当data为{}时,仍然返回html。
8 | * @return 如果无data,直接返回编译后的函数;如果有data,返回html。
9 | * @author wangxiao
10 | * @email 1988wangxiao@gmail.com
11 | */
12 |
13 | ;(function(window){
14 |
15 | //取得浏览器环境的baidu命名空间,非浏览器环境符合commonjs规范exports出去
16 | //修正在nodejs环境下,采用baidu.template变量名
17 | var baidu = typeof module === 'undefined' ? (window.baidu = window.baidu || {}) : module.exports;
18 |
19 | //模板函数(放置于baidu.template命名空间下)
20 | baidu.template = function(str, data){
21 |
22 | //检查是否有该id的元素存在,如果有元素则获取元素的innerHTML/value,否则认为字符串为模板
23 | var fn = (function(){
24 |
25 | //判断如果没有document,则为非浏览器环境
26 | if(!window.document){
27 | return bt._compile(str);
28 | };
29 |
30 | //HTML5规定ID可以由任何不包含空格字符的字符串组成
31 | var element = document.getElementById(str);
32 | if (element) {
33 |
34 | //取到对应id的dom,缓存其编译后的HTML模板函数
35 | if (bt.cache[str]) {
36 | return bt.cache[str];
37 | };
38 |
39 | //textarea或input则取value,其它情况取innerHTML
40 | var html = /^(textarea|input)$/i.test(element.nodeName) ? element.value : element.innerHTML;
41 | return bt._compile(html);
42 |
43 | }else{
44 |
45 | //是模板字符串,则生成一个函数
46 | //如果直接传入字符串作为模板,则可能变化过多,因此不考虑缓存
47 | return bt._compile(str);
48 | };
49 |
50 | })();
51 |
52 | //有数据则返回HTML字符串,没有数据则返回函数 支持data={}的情况
53 | var result = bt._isObject(data) ? fn( data ) : fn;
54 | fn = null;
55 |
56 | return result;
57 | };
58 |
59 | //取得命名空间 baidu.template
60 | var bt = baidu.template;
61 |
62 | //标记当前版本
63 | bt.versions = bt.versions || [];
64 | bt.versions.push('1.0.6');
65 |
66 | //缓存 将对应id模板生成的函数缓存下来。
67 | bt.cache = {};
68 |
69 | //自定义分隔符,可以含有正则中的字符,可以是HTML注释开头
70 | bt.LEFT_DELIMITER = bt.LEFT_DELIMITER||'<%';
71 | bt.RIGHT_DELIMITER = bt.RIGHT_DELIMITER||'%>';
72 |
73 | //自定义默认是否转义,默认为默认自动转义
74 | bt.ESCAPE = true;
75 |
76 | //HTML转义
77 | bt._encodeHTML = function (source) {
78 | return String(source)
79 | .replace(/&/g,'&')
80 | .replace(//g,'>')
82 | .replace(/\\/g,'\')
83 | .replace(/"/g,'"')
84 | .replace(/'/g,''');
85 | };
86 |
87 | //转义影响正则的字符
88 | bt._encodeReg = function (source) {
89 | return String(source).replace(/([.*+?^=!:${}()|[\]/\\])/g,'\\$1');
90 | };
91 |
92 | //转义UI UI变量使用在HTML页面标签onclick等事件函数参数中
93 | bt._encodeEventHTML = function (source) {
94 | return String(source)
95 | .replace(/&/g,'&')
96 | .replace(//g,'>')
98 | .replace(/"/g,'"')
99 | .replace(/'/g,''')
100 | .replace(/\\\\/g,'\\')
101 | .replace(/\\\//g,'\/')
102 | .replace(/\\n/g,'\n')
103 | .replace(/\\r/g,'\r');
104 | };
105 |
106 | //将字符串拼接生成函数,即编译过程(compile)
107 | bt._compile = function(str){
108 | var funBody = "var _template_fun_array=[];\nvar fn=(function(__data__){\nvar _template_varName='';\nfor(name in __data__){\n_template_varName+=('var '+name+'=__data__[\"'+name+'\"];');\n};\neval(_template_varName);\n_template_fun_array.push('"+bt._analysisStr(str)+"');\n_template_varName=null;\n})(_template_object);\nfn = null;\nreturn _template_fun_array.join('');\n";
109 | return new Function("_template_object",funBody);
110 | };
111 |
112 | //判断是否是Object类型
113 | bt._isObject = function (source) {
114 | return 'function' === typeof source || !!(source && 'object' === typeof source);
115 | };
116 |
117 | //解析模板字符串
118 | bt._analysisStr = function(str){
119 |
120 | //取得分隔符
121 | var _left_ = bt.LEFT_DELIMITER;
122 | var _right_ = bt.RIGHT_DELIMITER;
123 |
124 | //对分隔符进行转义,支持正则中的元字符,可以是HTML注释
125 | var _left = bt._encodeReg(_left_);
126 | var _right = bt._encodeReg(_right_);
127 |
128 | str = String(str)
129 |
130 | //去掉分隔符中js注释
131 | .replace(new RegExp("("+_left+"[^"+_right+"]*)//.*\n","g"), "$1")
132 |
133 | //去掉注释内容 <%* 这里可以任意的注释 *%>
134 | //默认支持HTML注释,将HTML注释匹配掉的原因是用户有可能用 来做分割符
135 | .replace(new RegExp("", "g"),"")
136 | .replace(new RegExp(_left+"\\*.*?\\*"+_right, "g"),"")
137 |
138 | //把所有换行去掉 \r回车符 \t制表符 \n换行符
139 | .replace(new RegExp("[\\r\\t\\n]","g"), "")
140 |
141 | //用来处理非分隔符内部的内容中含有 斜杠 \ 单引号 ‘ ,处理办法为HTML转义
142 | .replace(new RegExp(_left+"(?:(?!"+_right+")[\\s\\S])*"+_right+"|((?:(?!"+_left+")[\\s\\S])+)","g"),function (item, $1) {
143 | var str = '';
144 | if($1){
145 |
146 | //将 斜杠 单引 HTML转义
147 | str = $1.replace(/\\/g,"\").replace(/'/g,''');
148 | while(/<[^<]*?'[^<]*?>/g.test(str)){
149 |
150 | //将标签内的单引号转义为\r 结合最后一步,替换为\'
151 | str = str.replace(/(<[^<]*?)'([^<]*?>)/g,'$1\r$2')
152 | };
153 | }else{
154 | str = item;
155 | }
156 | return str ;
157 | });
158 |
159 |
160 | str = str
161 | //定义变量,如果没有分号,需要容错 <%var val='test'%>
162 | .replace(new RegExp("("+_left+"[\\s]*?var[\\s]*?.*?[\\s]*?[^;])[\\s]*?"+_right,"g"),"$1;"+_right_)
163 |
164 | //对变量后面的分号做容错(包括转义模式 如<%:h=value%>) <%=value;%> 排除掉函数的情况 <%fun1();%> 排除定义变量情况 <%var val='test';%>
165 | .replace(new RegExp("("+_left+":?[hvu]?[\\s]*?=[\\s]*?[^;|"+_right+"]*?);[\\s]*?"+_right,"g"),"$1"+_right_)
166 |
167 | //按照 <% 分割为一个个数组,再用 \t 和在一起,相当于将 <% 替换为 \t
168 | //将模板按照<%分为一段一段的,再在每段的结尾加入 \t,即用 \t 将每个模板片段前面分隔开
169 | .split(_left_).join("\t");
170 |
171 | //支持用户配置默认是否自动转义
172 | if(bt.ESCAPE){
173 | str = str
174 |
175 | //找到 \t=任意一个字符%> 替换为 ‘,任意字符,'
176 | //即替换简单变量 \t=data%> 替换为 ',data,'
177 | //默认HTML转义 也支持HTML转义写法<%:h=value%>
178 | .replace(new RegExp("\\t=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':baidu.template._encodeHTML($1),'");
179 | }else{
180 | str = str
181 |
182 | //默认不转义HTML转义
183 | .replace(new RegExp("\\t=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':$1,'");
184 | };
185 |
186 | str = str
187 |
188 | //支持HTML转义写法<%:h=value%>
189 | .replace(new RegExp("\\t:h=(.*?)"+_right,"g"),"',typeof($1) === 'undefined'?'':baidu.template._encodeHTML($1),'")
190 |
191 | //支持不转义写法 <%:=value%>和<%-value%>
192 | .replace(new RegExp("\\t(?::=|-)(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':$1,'")
193 |
194 | //支持url转义 <%:u=value%>
195 | .replace(new RegExp("\\t:u=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':encodeURIComponent($1),'")
196 |
197 | //支持UI 变量使用在HTML页面标签onclick等事件函数参数中 <%:v=value%>
198 | .replace(new RegExp("\\t:v=(.*?)"+_right,"g"),"',typeof($1)==='undefined'?'':baidu.template._encodeEventHTML($1),'")
199 |
200 | //将字符串按照 \t 分成为数组,在用'); 将其合并,即替换掉结尾的 \t 为 ');
201 | //在if,for等语句前面加上 '); ,形成 ');if ');for 的形式
202 | .split("\t").join("');")
203 |
204 | //将 %> 替换为_template_fun_array.push('
205 | //即去掉结尾符,生成函数中的push方法
206 | //如:if(list.length=5){%>',list[4],'
');}
207 | //会被替换为 if(list.length=5){_template_fun_array.push('',list[4],'
');}
208 | .split(_right_).join("_template_fun_array.push('")
209 |
210 | //将 \r 替换为 \
211 | .split("\r").join("\\'");
212 |
213 | return str;
214 | };
215 |
216 | })(window);
217 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tileTemplate
2 |
3 | A simple, high performance Javascript template engine.
4 |
5 | 一个简单的、高性能的 Javascript 模板引擎。
6 |
7 | 
8 |
9 | > 注:测试结果会因环境而有所不同,仅供参考。
10 |
11 | ### 主要特性
12 |
13 | - 简单小巧,精简后只有 `5K`,开启 gzip 后只有 `2.4K`;
14 | - 原生语法,高性能预编译和渲染模板 [(性能测试)](http://pandao.github.io/tiletemplate/tests/test-speed.html "(性能测试)");
15 | - 安全机制,过滤和转义危险语句[(安全测试)](http://pandao.github.io/tiletemplate/tests/test-xss-filter.html "(安全测试)");
16 | - 支持各种前端模块化加载器(CommonJS / AMD / CMD 等)[( Require.js示例 ](http://pandao.github.io/tiletemplate/examples/requirejs-test.html) 、[Sea.js示例 )](http://pandao.github.io/tiletemplate/examples/seajs-test.html);
17 | - 支持在 `Node.js` 环境下运行(`v1.6.0 起支持预编译模板文件为 CMD 模块`),同时也支持 `Express.js`;
18 | - 支持调试,精确定位并通过控制台输出和显示错误或异常信息([查看调试](http://pandao.github.io/tiletemplate/tests/test-debug.html));
19 | - 支持所有主流的浏览器(`IE6+`);
20 | - 支持 `include` 和自定义标签语法;
21 |
22 | ### 下载和安装
23 |
24 | - [源码 tiletemplate.js](https://github.com/pandao/tileTemplate/tree/master/src/tiletemplate.js "源码")
25 | - [压缩版 tiletemplate.min.js](https://github.com/pandao/tileTemplate/tree/master/dist/tiletemplate.min.js "压缩版")
26 |
27 | 通过 NPM 安装:
28 |
29 | ```shell
30 | npm install tiletemplate
31 | ```
32 |
33 | 通过 Bower 安装:
34 |
35 | ```shell
36 | bower install tiletemplate
37 | ```
38 |
39 | ### 使用方法
40 |
41 | 编写模板:
42 |
43 | ```html
44 |
45 |
56 | ```
57 |
58 | 预编译模板:
59 |
60 | ```javascript
61 | var data = {
62 | title : "标题XXX",
63 | list : []
64 | };
65 |
66 | // 返回一个函数
67 | var compiler = tileTemplate.compile(document.getElementById('test-tpl').innerHTML, data);
68 | ```
69 |
70 | 渲染模板:
71 |
72 | ```javascript
73 | var data = {
74 | title : "标题XXX",
75 | list : []
76 | };
77 |
78 | for (var i = 0; i < 10; i ++) {
79 | data.list.push({
80 | index: (i+1),
81 | user: 'tileTemplate '+(i+1)+'',
82 | site: 'https://github.com/pandao/tileTemplate'
83 | });
84 | }
85 |
86 | // 输出HTML
87 | // document.getElementById('output').innerHTML = compiler(data);
88 | document.getElementById('output3').innerHTML = tileTemplate.render("test-tpl", data);
89 | ```
90 |
91 | > 注:同时也支持在 [Require.js](http://pandao.github.io/tiletemplate/examples/requirejs-test.html) 和 [Sea.js](http://pandao.github.io/tiletemplate/examples/seajs-test.html) 中使用。
92 |
93 | ### 在 Node.js 使用:
94 |
95 | ```javascript
96 | var tileTemplate = require("../src/tiletemplate.node");
97 |
98 | // 通过 NPM 安装的
99 | // var tileTemplate = require('tiletemplate');
100 |
101 | // 设置基本目录
102 | tileTemplate.config("basePath", __dirname + "/tpl/");
103 |
104 | // tileTemplate.render(文件名/模板内容, 数据, 编码);
105 | // console.log(tileTemplate.render("Hello <%=str%>", {str:"wolrd!"}));
106 |
107 | // 预编译某个模板,用于循环渲染
108 | //var compiler = tileTemplate.compile(tileTemplate.readFile("list"), {list : [...]});
109 |
110 | // v1.5.0 版本起无需填写扩展名,默认为 tile.html,可另行配置
111 | var html = tileTemplate.render("test", data);
112 | var http = require('http');
113 |
114 | http.createServer(function (request, response) {
115 | response.writeHead(200, {'Content-Type': 'text/html'});
116 | response.end(html);
117 | }).listen(8888);
118 |
119 | console.log('Server running at http://127.0.0.1:8888/');
120 | ```
121 |
122 | > 注:`tileTemplate.readFile(文件名, 编码)` 方法只能在 `Node.js` 下使用。
123 |
124 | ### 在 Express.js 中使用
125 |
126 | ```javascript
127 | var express = require('express');
128 | var app = express();
129 | var tileTemplate = require("tiletemplate");
130 |
131 | // 初始化Express支持
132 | tileTemplate.expressInit(app, __dirname + "/tpl/");
133 |
134 | app.get('/', function (req, res) {
135 | res.render('index', data); // v1.5.0 版本起无需填写扩展名,默认为 tile.html,可另行配置
136 | });
137 |
138 | var server = app.listen(3000, function() {
139 | console.log('Listening on port %d', server.address().port);
140 | });
141 | ```
142 |
143 | ### 主要语法
144 |
145 | `tileTemplate` 目前只支持原生语法。
146 |
147 | 文本输出:
148 |
149 | ```html
150 | <%=变量%>
151 |
152 | ```
153 |
154 | JS语句:
155 |
156 | ```html
157 | <% if (list.length > 0) { %>
158 | Total: <%=list.length%>
159 | <% } else { %>
160 | 暂时没有
161 | <% } %>
162 |
163 | <% var total = list.length; %>
164 |
165 | <%=(list.index>1?'>1':'<1')%>
166 | ...
167 | ```
168 |
169 | 变量注释:
170 |
171 | ```html
172 | <%=#变量%>
173 | ```
174 |
175 | 行注释:
176 |
177 | ```html
178 | //注释文本
179 | //<%=(list.index>1?'>1':'<1')%>
180 | <% /* 注释文本 */ %>
181 |
182 | ```
183 |
184 | 嵌套模板(支持多级嵌套):
185 |
186 | ```html
187 | <% include('模板id') %>
188 | ```
189 |
190 | 转义字符(默认不转义字符,需要的在前面加上@):
191 |
192 | ```html
193 |
194 | ```
195 |
196 | > 作用:过滤和防止 XSS 攻击。例如:当 `avatar` 的值为 `http://xxxx/xx.jpg" onload="alert(123)`。
197 |
198 | 自定义标签语句:
199 |
200 | ```javascript
201 | // 定义标签语句
202 | tileTemplate.tag("em", function(content) {
203 | if(content == 12) {
204 | var img = "http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/tootha_thumb.gif";
205 | return '
';
206 | } else {
207 | return content.toString();
208 | }
209 | });
210 |
211 | tileTemplate.tag("time", function() {
212 | return " time: " + (new Date).getTime();
213 | });
214 |
215 | /*
216 | // 使用标签语句
217 | <%=tag:em:12%>
218 | <%=tag:em:haha%>
219 | <%=tag:em:哈哈%>
220 | <%=tag:time%>
221 | */
222 | ```
223 |
224 | > 注:自定义标签语句只能输出字符串。
225 |
226 | ### 主要方法
227 |
228 | 默认选项:
229 |
230 | ```javascript
231 | {
232 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启;
233 | cached : true, // 是否开启缓存,默认开启,性能更好
234 | filter : true, // 是否过滤模板中的危险语句等,如alert等
235 | openTag : "<%", // 模板开始标签
236 | closeTag : "%>" // 模板结束标签
237 | }
238 | ```
239 |
240 | 修改和设置配置选项:
241 |
242 | ```javascript
243 | // 使用set或config方法修改配置选项,config为别名
244 | // 批量设置
245 | tileTemplate.set({
246 | openTag : "{{",
247 | closeTag : "}}"
248 | });
249 |
250 | tileTemplate.config({
251 | openTag : "{{",
252 | closeTag : "}}"
253 | });
254 |
255 | // 单个设置
256 | tileTemplate.set("openTag", "{{");
257 | tileTemplate.config("openTag", "{{");
258 | ```
259 |
260 | 渲染模板:
261 |
262 | ```javascript
263 | /**
264 | * @param {String} id 模板的 ID,或者直接传入模板内容
265 | * @param {Object} data 传入模板的数据
266 | * @param {String} filename 当不通过ID获取模板,而是直接传入模板,需要设置一个模板名称
267 | * @param {Boolean} isModule 是否预编译为 CMD 模块
268 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串
269 | */
270 |
271 | tileTemplate.render(id, data, filename, isModule);
272 | ```
273 |
274 | 预编译模板:
275 |
276 | ```javascript
277 | /**
278 | * @param {String} tpl 模板的内容
279 | * @param {Object} data 传入模板的数据,默认为 {}
280 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块
281 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串
282 | */
283 |
284 | tileTemplate.compile(tpl, data, options);
285 | ```
286 |
287 | 自定义标签语句:
288 |
289 | ```javascript
290 | /**
291 | * @param {String} name 标签名称
292 | * @param {Function} callback 处理标签的回调方法,参数为content,代表标签语句传入的参数
293 | * @return {Void} void 无返回值
294 | */
295 |
296 | tileTemplate.tag(name, callback);
297 | ```
298 |
299 | 清除某个模板的缓存:
300 |
301 | ```javascript
302 | /**
303 | * @param {String} id 模板的ID或者文件名
304 | * @return {Boolean} bool 返回 true
305 | */
306 |
307 | tileTemplate.clear(id);
308 | ```
309 |
310 | 扩展 tileTemplate:
311 |
312 | ```javascript
313 | tileTemplate.xxx = xxxx;
314 |
315 | // 或者
316 | tileTemplate.extend({
317 | xxx : "xxxx"
318 | methodXXX : function() {
319 | //...
320 | }
321 | });
322 | ```
323 |
324 | ### 更新日志
325 |
326 | [查看更新日志](https://github.com/pandao/tileTemplate/blob/master/CHANGE.md)
327 |
328 | ### License
329 |
330 | The MIT License (MIT)
331 |
332 | Copyright (c) 2014-2016 Pandao
333 |
--------------------------------------------------------------------------------
/tests/js/templates/template.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2012, KISSY UI Library v1.20
3 | MIT Licensed
4 | build time: Jan 10 19:03
5 | */
6 | /**
7 | * @fileoverview KISSY Template Engine.
8 | * @author yyfrankyy@gmail.com
9 | */
10 | KISSY.add('template/base', function(S) {
11 |
12 | var // Template Cache
13 | templateCache = {},
14 |
15 | // start/end tag mark
16 | tagStartEnd = {
17 | '#': 'start',
18 | '/': 'end'
19 | },
20 |
21 | // static string
22 | KS_TEMPL_STAT_PARAM = 'KS_TEMPL_STAT_PARAM',
23 | KS_TEMPL_STAT_PARAM_REG = new RegExp(KS_TEMPL_STAT_PARAM, "g"),
24 | KS_TEMPL = 'KS_TEMPL',
25 | KS_DATA = 'KS_DATA_',
26 | KS_AS = 'as',
27 |
28 | // note : double quote for generated code
29 | PREFIX = '");',
30 | SUFFIX = KS_TEMPL + '.push("',
31 |
32 | PARSER_SYNTAX_ERROR = 'KISSY.Template: Syntax Error. ',
33 | PARSER_RENDER_ERROR = 'KISSY.Template: Render Error. ',
34 |
35 | PARSER_PREFIX = 'var ' + KS_TEMPL + '=[],' +
36 | KS_TEMPL_STAT_PARAM + '=false;with(',
37 |
38 | PARSER_MIDDLE = '||{}){try{' + KS_TEMPL + '.push("',
39 |
40 | PARSER_SUFFIX = '");}catch(e){' + KS_TEMPL + '=["' +
41 | PARSER_RENDER_ERROR + '" + e.message]}};return ' +
42 | KS_TEMPL + '.join("");',
43 |
44 | // restore double quote in logic template variable
45 | restoreQuote = function(str) {
46 | return str.replace(/\\"/g, '"');
47 | },
48 |
49 | // escape double quote in template
50 | escapeQuote = function(str) {
51 | return str.replace(/"/g, '\\"');
52 | },
53 |
54 | trim = S.trim,
55 |
56 | // build a static parser
57 | buildParser = function(tpl) {
58 | var _parser,
59 | _empty_index;
60 | return escapeQuote(trim(tpl)
61 | .replace(/[\r\t\n]/g, ' ')
62 | // escape escape ... . in case \ is consumed when run tpl parser function
63 | // '{{y}}\\x{{/y}}' =>tmpl.push('\x'); => tmpl.push('\\x');
64 | .replace(/\\/g, '\\\\'))
65 | .replace(/\{\{([#/]?)(?!\}\})([^}]*)\}\}/g,
66 | function(all, expr, body) {
67 | _parser = "";
68 | // must restore quote , if str is used as code directly
69 | body = restoreQuote(trim(body));
70 | //body = trim(body);
71 | // is an expression
72 | if (expr) {
73 | _empty_index = body.indexOf(' ');
74 | body = _empty_index === -1 ?
75 | [ body, '' ] :
76 | [
77 | body.substring(0, _empty_index),
78 | body.substring(_empty_index)
79 | ];
80 |
81 | var operator = body[0],
82 | fn,
83 | args = trim(body[1]),
84 | opStatement = Statements[operator];
85 |
86 | if (opStatement && tagStartEnd[expr]) {
87 | // get expression definition function/string
88 | fn = opStatement[tagStartEnd[expr]];
89 | _parser = String(S.isFunction(fn) ?
90 | fn.apply(this, args.split(/\s+/)) :
91 | fn.replace(KS_TEMPL_STAT_PARAM_REG, args));
92 | }
93 | }
94 | // return array directly
95 | else {
96 | _parser = KS_TEMPL +
97 | '.push(' +
98 | // prevent variable undefined error when look up in with ,simple variable substitution
99 | // with({}){alert(x);} => ReferenceError: x is not defined
100 | 'typeof (' + body + ') ==="undefined"?"":' + body +
101 | ');';
102 | }
103 | return PREFIX + _parser + SUFFIX;
104 |
105 | });
106 | },
107 |
108 | // expression
109 | Statements = {
110 | 'if': {
111 | start: 'if(typeof (' + KS_TEMPL_STAT_PARAM + ') !=="undefined" && ' + KS_TEMPL_STAT_PARAM + '){',
112 | end: '}'
113 | },
114 |
115 | 'else': {
116 | start: '}else{'
117 | },
118 |
119 | 'elseif': {
120 | start: '}else if(' + KS_TEMPL_STAT_PARAM + '){'
121 | },
122 |
123 | // KISSY.each function wrap
124 | 'each': {
125 | start: function(obj, as, v, k) {
126 | var _ks_value = '_ks_value',
127 | _ks_index = '_ks_index';
128 | if (as === KS_AS && v) {
129 | _ks_value = v || _ks_value,
130 | _ks_index = k || _ks_index;
131 | }
132 | return 'KISSY.each(' + obj +
133 | ', function(' + _ks_value +
134 | ', ' + _ks_index + '){';
135 | },
136 | end: '});'
137 | },
138 |
139 | // comments
140 | '!': {
141 | start: '/*' + KS_TEMPL_STAT_PARAM + '*/'
142 | }
143 | };
144 |
145 | /**
146 | * Template
147 | * @param {String} tpl template to be rendered.
148 | * @return {Object} return this for chain.
149 | */
150 | function Template(tpl) {
151 | if (!(templateCache[tpl])) {
152 | var _ks_data = S.guid(KS_DATA),
153 | func,
154 | o,
155 | _parser = [
156 | PARSER_PREFIX,
157 | _ks_data,
158 | PARSER_MIDDLE,
159 | o = buildParser(tpl),
160 | PARSER_SUFFIX
161 | ];
162 |
163 | try {
164 | func = new Function(_ks_data, _parser.join(""));
165 | } catch (e) {
166 | _parser[3] = PREFIX + SUFFIX +
167 | PARSER_SYNTAX_ERROR + ',' +
168 | e.message + PREFIX + SUFFIX;
169 | func = new Function(_ks_data, _parser.join(""));
170 | }
171 |
172 | templateCache[tpl] = {
173 | name: _ks_data,
174 | o:o,
175 | parser: _parser.join(""),
176 | render: func
177 | };
178 | }
179 | return templateCache[tpl];
180 | }
181 |
182 | S.mix(Template, {
183 | /**
184 | * Logging Compiled Template Codes
185 | * @param {String} tpl template string.
186 | */
187 | log: function(tpl) {
188 | if (tpl in templateCache) {
189 | if ('js_beautify' in window) {
190 | // S.log(js_beautify(templateCache[tpl].parser, {
191 | // indent_size: 4,
192 | // indent_char: ' ',
193 | // preserve_newlines: true,
194 | // braces_on_own_line: false,
195 | // keep_array_indentation: false,
196 | // space_after_anon_function: true
197 | // }), 'info');
198 | } else {
199 | S.log(templateCache[tpl].parser, 'info');
200 | }
201 | } else {
202 | Template(tpl);
203 | this.log(tpl);
204 | }
205 | },
206 |
207 | /**
208 | * add statement for extending template tags
209 | * @param {String} statement tag name.
210 | * @param {String} o extent tag object.
211 | */
212 | addStatement: function(statement, o) {
213 | if (S.isString(statement)) {
214 | Statements[statement] = o;
215 | } else {
216 | S.mix(Statements, statement);
217 | }
218 | }
219 |
220 | });
221 |
222 | return Template;
223 |
224 | });
225 | /**
226 | * 2011-09-20 note by yiminghe :
227 | * - code style change
228 | * - remove reg cache , ugly to see
229 | * - fix escape by escape
230 | * - expect(T('{{#if a=="a"}}{{b}}\\"{{/if}}').render({a:"a",b:"b"})).toBe('b\\"');
231 | */
232 | /**
233 | * @fileoverview KISSY.Template Node.
234 | * @author 文河
235 | */
236 | KISSY.add('template/node', function(S, Template, Node) {
237 | var $ = Node.all;
238 | S.mix(S, {
239 | tmpl: function(selector, data) {
240 | return $(Template($(selector).html()).render(data));
241 | }
242 | });
243 |
244 | }, {requires:["./base",'node']});
245 | KISSY.add("template", function(S, T) {
246 | S.Template = T;
247 | return T;
248 | }, {
249 | requires:["template/base","template/node"]
250 | });
251 |
--------------------------------------------------------------------------------
/tests/js/templates/juicer.js:
--------------------------------------------------------------------------------
1 | /*
2 | ********** Juicer **********
3 | ${A Fast template engine}
4 | Project Home: http://juicer.name
5 |
6 | Author: Guokai
7 | Gtalk: badkaikai@gmail.com
8 | Blog: http://benben.cc
9 | Licence: MIT License
10 | Version: 0.4.0-dev
11 | */
12 |
13 | (function() {
14 | var juicer = function() {
15 | var args = [].slice.call(arguments);
16 | args.push(juicer.options);
17 |
18 | if(arguments.length == 1) {
19 | return juicer.compile.apply(juicer, args);
20 | }
21 |
22 | if(arguments.length >= 2) {
23 | return juicer.to_html.apply(juicer, args);
24 | }
25 | };
26 |
27 | var __escapehtml = {
28 | escapehash: {
29 | '<': '<',
30 | '>': '>',
31 | '&': '&',
32 | '"': '"',
33 | "'": ''',
34 | '/': '/'
35 | },
36 | escapereplace: function(k) {
37 | return __escapehtml.escapehash[k];
38 | },
39 | escaping: function(str) {
40 | return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace);
41 | },
42 | detection: function(data) {
43 | return typeof(data) === 'undefined' ? '' : data;
44 | }
45 | };
46 |
47 | var __throw = function(error) {
48 | if(console) {
49 | if(console.warn) {
50 | console.warn(error);
51 | return;
52 | }
53 |
54 | if(console.log) {
55 | console.log(error);
56 | return;
57 | }
58 | }
59 |
60 | throw(error);
61 | };
62 |
63 | var __creator = function(o, proto) {
64 | o = o !== Object(o) ? {} : o;
65 |
66 | if(o.__proto__) {
67 | o.__proto__ = proto;
68 | return o;
69 | }
70 |
71 | var _Empty = function() {};
72 | var n = new((_Empty).prototype = proto, _Empty);
73 |
74 | for(var i in o) {
75 | if(o.hasOwnProperty(i)) {
76 | n[i] = o[i];
77 | }
78 | }
79 |
80 | return n;
81 | };
82 |
83 | juicer.__cache = {};
84 | juicer.version = '0.4.0-dev';
85 |
86 | juicer.settings = {
87 | forstart: /{@each\s*([\w\.]*?)\s*as\s*(\w*?)\s*(,\s*\w*?)?}/igm,
88 | forend: /{@\/each}/igm,
89 | ifstart: /{@if\s*([^}]*?)}/igm,
90 | ifend: /{@\/if}/igm,
91 | elsestart: /{@else}/igm,
92 | elseifstart: /{@else if\s*([^}]*?)}/igm,
93 | interpolate: /\${([\s\S]+?)}/igm,
94 | noneencode: /\$\${([\s\S]+?)}/igm,
95 | inlinecomment: /{#[^}]*?}/igm,
96 | rangestart: /{@each\s*(\w*?)\s*in\s*range\((\d+?),(\d+?)\)}/igm
97 | };
98 |
99 | juicer.options = {
100 | cache: true,
101 | strip: true,
102 | errorhandling: true,
103 | detection: true,
104 | _method: __creator({
105 | __escapehtml: __escapehtml,
106 | __throw: __throw
107 | }, this)
108 | };
109 |
110 | juicer.set = function(conf, value) {
111 | if(arguments.length === 2) {
112 | this.options[conf] = value;
113 | return;
114 | }
115 |
116 | if(conf === Object(conf)) {
117 | for(var i in conf) {
118 | if(conf.hasOwnProperty(i)) {
119 | this.options[i] = conf[i];
120 | }
121 | }
122 | }
123 | };
124 |
125 | juicer.register = function(fname, fn) {
126 | var _method = this.options._method;
127 |
128 | if(_method.hasOwnProperty(fname)) {
129 | return false;
130 | }
131 |
132 | return _method[fname] = fn;
133 | };
134 |
135 | juicer.unregister = function(fname) {
136 | var _method = this.options._method;
137 |
138 | if(_method.hasOwnProperty(fname)) {
139 | return delete _method[fname];
140 | }
141 | };
142 |
143 | juicer.template = function(options) {
144 | var that = this;
145 |
146 | this.options = options;
147 |
148 | this.__interpolate = function(_name, _escape, options) {
149 | var _define = _name.split('|'), _fn = '';
150 |
151 | if(_define.length > 1) {
152 | _name = _define.shift();
153 | _fn = '_method.' + _define.shift();
154 | }
155 |
156 | return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' +
157 | (!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' +
158 | _fn + '(' +
159 | _name +
160 | ')' +
161 | ')' +
162 | ')' +
163 | ' %>';
164 | };
165 |
166 | this.__removeShell = function(tpl, options) {
167 | var _counter = 0;
168 |
169 | tpl = tpl
170 | //for expression
171 | .replace(juicer.settings.forstart, function($, _name, alias, key) {
172 | var alias = alias || 'value', key = key && key.substr(1);
173 | var _iterate = 'i' + _counter++;
174 | return '<% for(var ' + _iterate + '=0, l' + _iterate + '=' + _name + '.length;' + _iterate + '';
178 | })
179 | .replace(juicer.settings.forend, '<% } %>')
180 |
181 | //if expression
182 | .replace(juicer.settings.ifstart, function($, condition) {
183 | return '<% if(' + condition + ') { %>';
184 | })
185 | .replace(juicer.settings.ifend, '<% } %>')
186 |
187 | //else expression
188 | .replace(juicer.settings.elsestart, function($) {
189 | return '<% } else { %>';
190 | })
191 |
192 | //else if expression
193 | .replace(juicer.settings.elseifstart, function($, condition) {
194 | return '<% } else if(' + condition + ') { %>';
195 | })
196 |
197 | //interpolate without escape
198 | .replace(juicer.settings.noneencode, function($, _name) {
199 | return that.__interpolate(_name, false, options);
200 | })
201 |
202 | //interpolate with escape
203 | .replace(juicer.settings.interpolate, function($, _name) {
204 | return that.__interpolate(_name, true, options);
205 | })
206 |
207 | //clean up comments
208 | .replace(juicer.settings.inlinecomment, '')
209 |
210 | //range expression
211 | .replace(juicer.settings.rangestart, function($, _name, start, end) {
212 | var _iterate = 'j' + _counter++;
213 | return '<% for(var ' + _iterate + '=0;' + _iterate + '<' + (end - start) + ';' + _iterate + '++) {' +
214 | 'var ' + _name + '=' + _iterate + ';' +
215 | ' %>';
216 | });
217 |
218 | //exception handling
219 | if(!options || options.errorhandling !== false) {
220 | tpl = '<% try { %>' + tpl;
221 | tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';
222 | }
223 |
224 | return tpl;
225 | };
226 |
227 | this.__toNative = function(tpl, options) {
228 | return this.__convert(tpl, !options || options.strip);
229 | };
230 |
231 | this.__lexicalAnalyze = function(tpl) {
232 | var buffer = [];
233 | var prefix = '';
234 |
235 | var indexOf = function(array, item) {
236 | if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) {
237 | return array.indexOf(item);
238 | }
239 |
240 | for(var i=0; i < array.length; i++) {
241 | if(array[i] === item) return i;
242 | }
243 |
244 | return -1;
245 | };
246 |
247 | var variableAnalyze = function($, statement) {
248 | statement = statement.match(/\w+/igm)[0];
249 |
250 | if(indexOf(buffer, statement) === -1) {
251 | buffer.push(statement); //fuck ie
252 | }
253 | };
254 |
255 | tpl.replace(juicer.settings.forstart, variableAnalyze).
256 | replace(juicer.settings.interpolate, variableAnalyze).
257 | replace(juicer.settings.ifstart, variableAnalyze);
258 |
259 | for(var i = 0;i < buffer.length; i++) {
260 | prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';';
261 | }
262 | return '<% ' + prefix + ' %>';
263 | };
264 |
265 | this.__convert=function(tpl, strip) {
266 | var buffer = [].join('');
267 |
268 | buffer += "'use strict';"; //use strict mode
269 | buffer += "var _=_||{};";
270 | buffer += "var _out='';_out+='";
271 |
272 | if(strip !== false) {
273 | buffer += tpl
274 | .replace(/\\/g, "\\\\")
275 | .replace(/[\r\t\n]/g, " ")
276 | .replace(/'(?=[^%]*%>)/g, "\t")
277 | .split("'").join("\\'")
278 | .split("\t").join("'")
279 | .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
280 | .split("<%").join("';")
281 | .split("%>").join("_out+='")+
282 | "';return _out;";
283 |
284 | return buffer;
285 | }
286 |
287 | buffer += tpl
288 | .replace(/\\/g, "\\\\")
289 | .replace(/[\r]/g, "\\r")
290 | .replace(/[\t]/g, "\\t")
291 | .replace(/[\n]/g, "\\n")
292 | .replace(/'(?=[^%]*%>)/g, "\t")
293 | .split("'").join("\\'")
294 | .split("\t").join("'")
295 | .replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
296 | .split("<%").join("';")
297 | .split("%>").join("_out+='")+
298 | "';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');";
299 |
300 | return buffer;
301 | };
302 |
303 | this.parse = function(tpl, options) {
304 | var _that = this;
305 |
306 | if(!options || options.loose !== false) {
307 | tpl = this.__lexicalAnalyze(tpl) + tpl;
308 | }
309 |
310 | tpl = this.__removeShell(tpl, options);
311 | tpl = this.__toNative(tpl, options);
312 |
313 | this._render = new Function('_, _method', tpl);
314 |
315 | this.render = function(_, _method) {
316 | if(!_method || _method !== that.options._method) {
317 | _method = __creator(_method, that.options._method);
318 | }
319 |
320 | return _that._render.call(this, _, _method);
321 | };
322 |
323 | return this;
324 | };
325 | };
326 |
327 | juicer.compile = function(tpl, options) {
328 | if(!options || options !== this.options) {
329 | options = __creator(options, this.options);
330 | }
331 |
332 | try {
333 | var engine = this.__cache[tpl] ?
334 | this.__cache[tpl] :
335 | new this.template(this.options).parse(tpl, options);
336 |
337 | if(!options || options.cache !== false) {
338 | this.__cache[tpl] = engine;
339 | }
340 |
341 | return engine;
342 |
343 | } catch(e) {
344 | __throw('Juicer Compile Exception: ' + e.message);
345 |
346 | return {
347 | render: function() {} //noop
348 | };
349 | }
350 | };
351 |
352 | juicer.to_html = function(tpl, data, options) {
353 | if(!options || options !== this.options) {
354 | options = __creator(options, this.options);
355 | }
356 |
357 | return this.compile(tpl, options).render(data, options._method);
358 | };
359 |
360 | typeof(module) !== 'undefined' && module.exports ? module.exports = juicer : this.juicer = juicer;
361 | })();
--------------------------------------------------------------------------------
/tests/test-speed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | templates test
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
337 |
338 |
339 |
340 |
341 |
348 |
349 |
350 |
358 |
359 |
360 |
367 |
368 |
369 |
377 |
378 |
379 |
386 |
387 |
388 |
395 |
396 |
397 |
404 |
405 |
406 |
414 |
415 |
416 |
423 |
424 |
425 |
432 |
433 |
434 |
441 |
442 |
443 |
444 |
452 |
453 |
454 |
461 |
462 |
463 |
464 | 引擎渲染速度测试
465 | 特别说明:为了能直观有效的进行“比较”,本测试用例使用的是 artTemplate 的测试用例,感谢artTemplate的启发和工作。
466 | 条数据 × 次渲染测试
467 | tileTemplate [debug:false, filter:false, cached:true]
468 | artTemplate [escape:false, cache:true]
469 | 建议在拥有 v8 javascript 引擎的 chrome 浏览器上进行测试,避免浏览器停止响应
470 |
471 |
472 |
473 |
--------------------------------------------------------------------------------
/src/tiletemplate.js:
--------------------------------------------------------------------------------
1 | ;(function(tileTemplate) {
2 | if(typeof require === 'function' && typeof exports === "object" && typeof module === "object") {
3 | // CommonJS/Node.js
4 | tileTemplate(require, exports, module);
5 | } else if(typeof define === "function") {
6 | define(tileTemplate); // AMD/CMD/Sea.js
7 | } else {
8 | tileTemplate(function(){}, window['tileTemplate'] = {}, {});
9 | }
10 | }(function(require, exports, module) {
11 | "use strict";
12 |
13 | /**
14 | * @name $
15 | * @description 通过ID获取DOM对象
16 | * @param {String} id ID名称
17 | * @return {Mixed} return 返回DOM对象或ID
18 | */
19 |
20 | function $(id) {
21 | return (typeof document !== "undefined") ? document.getElementById(id) : id;
22 | }
23 |
24 | /**
25 | * @name trim
26 | * @description 扩展String对象的清除两边空格的方法
27 | * @return {String} return 返回String对象本身
28 | */
29 |
30 | if (typeof ''.trim !== "function") {
31 | String.prototype.trim = function () {
32 | return this.replace(/(^\s*)|(\s*$)/g, "");
33 | };
34 | }
35 |
36 | var _this = exports;
37 |
38 | /**
39 | * @name version
40 | * @description 版本号
41 | */
42 |
43 | exports.version = "1.5.0";
44 |
45 | /**
46 | * @name tags
47 | * @description 存放自定义标签语句的数组
48 | */
49 |
50 | exports.tags = [];
51 |
52 | /**
53 | * @name caches
54 | * @description 存放缓存的数组
55 | */
56 |
57 | exports.caches = [];
58 |
59 | /**
60 | * @name regex
61 | * @description 存放正则的对象
62 | */
63 |
64 | exports.regex = {};
65 |
66 | /**
67 | * @name extend
68 | * @description 扩展对象
69 | * @param {Object} defaults 要扩展进来的对象
70 | * @param {Object} options 扩展对象本身
71 | * @return {Object} options 返回扩展后的对象
72 | */
73 |
74 | exports.extend = function(defaults, options) {
75 | options = options || this;
76 |
77 | for (var i in defaults) {
78 | if(typeof options[i] == "undefined") options[i] = defaults[i];
79 | }
80 |
81 | return options;
82 | };
83 |
84 | /**
85 | * @name settings
86 | * @description 配置选项
87 | */
88 |
89 | exports.settings = {
90 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启;
91 | cached : true, // 是否开启缓存,默认开启,性能更好
92 | filter : true, // 是否过滤模板中的危险语句等,如alert等
93 | openTag : "<%", // 模板开始标签
94 | closeTag : "%>" // 模板结束标签
95 | };
96 |
97 | /**
98 | * @name set
99 | * @alias config
100 | * @description 过滤模板中的危险语句
101 | * @param {String|Object} key|options 模板文件的内容
102 | * @param {String} value 模板语句的开始标签
103 | * @return {String} result 返回模板文件的内容
104 | */
105 |
106 | exports.set = exports.config = function() {
107 | if(arguments && arguments.length > 1) {
108 | this.settings[arguments[0]] = arguments[1];
109 | } else {
110 | this.settings = this.extend(this.settings, arguments[0]);
111 | }
112 | };
113 |
114 | /**
115 | * @name htmlEncode
116 | * @description 对HTML内容进行编码
117 | * @param {String} html HTML内容
118 | * @return {String} result 返回编码后的HTML内容
119 | */
120 |
121 | exports.htmlEncode = function(html) {
122 | return html.replace(/\/igm, ">").replace(/\"/igm, """).replace(/\'/igm, "'");
123 | };
124 |
125 | /**
126 | * @name htmlDecode
127 | * @description 对编码后的HTML内容进行解码
128 | * @param {String} html 编码后的HTML内容
129 | * @return {String} result 返回解码后HTML内容
130 | */
131 |
132 | exports.htmlDecode = function(html) {
133 | return html.replace(/\</igm, "<").replace(/\>/igm, ">").replace(/\"/igm, "\"").replace(/\'/igm, "'");
134 | };
135 |
136 | /**
137 | * @name filter
138 | * @description 过滤模板中的危险语句
139 | * @param {String} tpl 模板文件的内容
140 | * @param {String} openTag 模板语句的开始标签
141 | * @param {String} closeTag 模板语句的结束标签
142 | * @return {String} result 返回模板文件的内容
143 | */
144 |
145 | exports.filter = function(tpl, openTag, closeTag) {
146 | tpl = tpl.replace(/\s*<\s*iframe\s*(.*)\s*>\s*<\s*\/iframe\s*>\s*/igm, "");
147 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*alert\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
148 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*confirm\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
149 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*prompt\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
150 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*window\s*(.|;)\s*(.*)"+closeTag, "igm"), "");
151 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*document\s*(.|;)\s*(.*)"+closeTag, "igm"), "");
152 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*createElement\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
153 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*(\\$|jQuery)\s*(.*)\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
154 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*eval\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
155 |
156 | return tpl.trim();
157 | };
158 |
159 | /**
160 | * @name compileInclude
161 | * @description 编译嵌套(include)的模板文件的回调函数
162 | * @param {String} data 传入的数据
163 | * @return {String} result 一个以字符串为形式的函数
164 | */
165 |
166 | exports.compileInclude = function(data) {
167 | var openTag = this.settings.openTag;
168 | var closeTag = this.settings.closeTag;
169 |
170 | return (function($1, $2, $3) {
171 | var guid = (new Date).getTime();
172 | var id = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", "");
173 | var includeContent = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML;
174 | var compile = _this.compile(includeContent, data, {include: true, name: id}).toString().split("\n").join('');
175 |
176 | compile = compile.replace('anonymous', 'anonymous' + guid);
177 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }';
178 | compile = openTag + " " + compile + " " + closeTag;
179 |
180 | return compile;
181 | });
182 | };
183 |
184 | /**
185 | * @name compile
186 | * @description 编译模板文件
187 | * @param {String} tpl 模板文件的内容
188 | * @param {Object} data 传入的数据
189 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块
190 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串
191 | */
192 |
193 | exports.compile = function(tpl, data, options) {
194 | var guid = (new Date).getTime();
195 | var defaults = {
196 | include : false, // 是否为引入
197 | name : "tile" + guid, // 匿名函数 ID
198 | isModule : false // 是否定义为模块,Sea.js / Require.js CMD 模块
199 | };
200 |
201 | data = data || {};
202 | options = options || {};
203 | options = this.extend(defaults, options);
204 |
205 | var include = options.include;
206 | var filter = this.settings.filter;
207 | var debug = this.settings.debug;
208 | var openTag = this.settings.openTag;
209 | var closeTag = this.settings.closeTag;
210 | var regex = new RegExp(openTag + "=(.*?)" + closeTag, "igm"); // or use eval
211 | var varCommentRegex = new RegExp(openTag + "=#(.*?)" + closeTag, "igm"); // variable comment
212 | var escapeRegex = new RegExp(openTag + "=@(.*?)" + closeTag, "igm"); // escape regex
213 | var lineCommentRegex = new RegExp("\/\/" + openTag + "?\\s*(.*?)\\s*" + closeTag + "?", "igm"); // line comment
214 | var tagRegex = /\s*tag:(\w+):?([\u4e00-\u9fa5]+|\w+|\d+)?\s*/igm;
215 | var includeRegex = new RegExp(openTag + "\\s*include\\s*((.*?))\\s*;?\\s*" + closeTag, "igm");
216 |
217 | var tagRegexHandler = function ($1, $2, $3) {
218 | var str = (typeof $3 == "undefined") ? _this.tags[$2]() : _this.tags[$2]($3.toString());
219 |
220 | return "'" + str.toString() + "'";
221 | };
222 |
223 | this.regex.include = includeRegex;
224 |
225 | tpl = tpl.replace(/(^\s*)|(\s*$)/g, "") // clear spaces
226 | .replace(varCommentRegex, function($1, $2) { return "'+'"; })
227 | .replace(lineCommentRegex, "")
228 | .replace(this.regex.include, this.compileInclude(data))
229 | .replace(escapeRegex, function($1, $2) { return "'+__escape(" + $2 + ")+'"; })
230 | .replace(regex, function($1, $2) { return "'+" + $2 + "+'"; })
231 | .split("\n");
232 |
233 | if (debug) {
234 | for (var i = 0, len = tpl.length; i < len; i++) {
235 | tpl[i] = tpl[i].toString().replace(/(^\s*)|(\s*$)/g, "") + "_&_" + openTag + " __line__=" + (i + 1) +"; " + closeTag + "_&_";
236 | }
237 |
238 | tpl = tpl.join('').split("_&_");
239 | }
240 |
241 | for (var i = 0, len = tpl.length; i < len; i++) {
242 | tpl[i] = tpl[i].trim();
243 |
244 | if(filter) tpl[i] = this.filter(tpl[i], openTag, closeTag);
245 |
246 | tpl[i] = "__out__+='" + tpl[i] + "';";
247 |
248 | tpl[i] = tpl[i].replace(tagRegex, tagRegexHandler);
249 |
250 | tpl[i] = tpl[i].replace("__out__+='" + openTag, "").replace(closeTag + "';", "").replace("}';", "}");
251 | tpl[i] = tpl[i].replace(/^__out__\+\=\'\/\/(.*?)\'\;$/, function($1, $2, $3) { return "/* " + $2 + " */"; }).replace("'';';", "'';");
252 | }
253 |
254 | var __escape = 'function __escape(str) {return str.replace(/[\'|\"|\>|\<|;]?/igm, "");}';
255 | var objectCount = "function objectCount(data){var total=0;for(var i in data){total++} return total;};";
256 | var errorHandler = "if (typeof console === 'object') {console.error('[tileTemplate]\\n" + options.name + "\\n\\n[type]\\nRender error\\n\\nmessage: '+e.message+'\\nline: '+(e.line+1));}";
257 | var debugCode = "try {" + tpl.join('') + "\n return __out__; } catch(e) { e.line=__line__; if(objectCount(data) > 0) { "+errorHandler+" }}";
258 |
259 | tpl = "var __out__ = '', __line__=0;\n " + ( (debug) ? debugCode : tpl.join('') + "\n return __out__;" );
260 |
261 | var vars = [];
262 |
263 | for (var i in data) {
264 | vars.push(i + ' = __data__.' + i);
265 | }
266 |
267 | vars = (vars.length > 0) ? 'var ' + vars.join(', ') + ';\n' : '';
268 |
269 | var fn = new Function('__data__', __escape + objectCount + vars + tpl);
270 |
271 | if (options.isModule) {
272 | fn = 'define(function(require, exports, module) {module.exports = '+ fn.toLocaleString().replace('anonymous', '') + '})';
273 | }
274 |
275 | return fn;
276 | };
277 |
278 | /**
279 | * @name render
280 | * @description 渲染模板文件
281 | * @param {String} id 模板ID或直接传入模板内容
282 | * @param {Object} data 传入的数据
283 | * @param {String} filename 指定模板名称
284 | * @param {Boolean} [isModule=false] 是否定义为模块
285 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串
286 | */
287 |
288 | exports.render = function(id, data, filename, isModule) {
289 | if (typeof isModule === 'undefined') {
290 | isModule = false;
291 | }
292 |
293 | data = data || {};
294 | filename = filename || "";
295 |
296 | var cached = this.settings.cached;
297 | var tpl = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML;
298 | var tplName = (filename == "") ? id : filename;
299 |
300 | if (cached && typeof this.caches[tplName] !== "undefined") { // use cache
301 | return this.caches[tplName];
302 | } else {
303 | var html = this.compile(tpl, data, {
304 | name : (filename == "") ? id : filename,
305 | isModule : isModule
306 | })(data);
307 |
308 | if (cached) {
309 | this.caches[tplName] = html;
310 | }
311 |
312 | return html;
313 | }
314 | };
315 |
316 | /**
317 | * @name tag
318 | * @description 自定义模板标签语句
319 | * @param {String} name 标签的名称
320 | * @param {Function} callback 处理标签内容的回调函数
321 | * @return {Void} void 无
322 | */
323 |
324 | exports.tag = function(name, callback) {
325 | this.tags[name] = callback;
326 | };
327 |
328 | /**
329 | * @name clear
330 | * @description 清除模板缓存
331 | * @param {String} id 要清除的模板 ID
332 | * @return {Boolean} bool 返回 true
333 | */
334 |
335 | exports.clear = function(id) {
336 | delete this.caches[id];
337 |
338 | return true;
339 | };
340 | }));
--------------------------------------------------------------------------------
/dist/tiletemplate.js:
--------------------------------------------------------------------------------
1 | /*
2 | * tileTemplate
3 | *
4 | * @file tiletemplate.js
5 | * @description A simple, high performance Javascript template engine.
6 | * @version v1.6.0
7 | * @author Pandao
8 | * {@link https://github.com/pandao/tileTemplate}
9 | * @updateTime 2016-12-04 15:02:47
10 | */
11 |
12 | ;(function(tileTemplate) {
13 | if(typeof require === 'function' && typeof exports === "object" && typeof module === "object") {
14 | // CommonJS/Node.js
15 | tileTemplate(require, exports, module);
16 | } else if(typeof define === "function") {
17 | define(tileTemplate); // AMD/CMD/Sea.js
18 | } else {
19 | tileTemplate(function(){}, window['tileTemplate'] = {}, {});
20 | }
21 | }(function(require, exports, module) {
22 | "use strict";
23 |
24 | /**
25 | * @name $
26 | * @description 通过ID获取DOM对象
27 | * @param {String} id ID名称
28 | * @return {Mixed} return 返回DOM对象或ID
29 | */
30 |
31 | function $(id) {
32 | return (typeof document !== "undefined") ? document.getElementById(id) : id;
33 | }
34 |
35 | /**
36 | * @name trim
37 | * @description 扩展String对象的清除两边空格的方法
38 | * @return {String} return 返回String对象本身
39 | */
40 |
41 | if (typeof ''.trim !== "function") {
42 | String.prototype.trim = function () {
43 | return this.replace(/(^\s*)|(\s*$)/g, "");
44 | };
45 | }
46 |
47 | var _this = exports;
48 |
49 | /**
50 | * @name version
51 | * @description 版本号
52 | */
53 |
54 | exports.version = "1.5.0";
55 |
56 | /**
57 | * @name tags
58 | * @description 存放自定义标签语句的数组
59 | */
60 |
61 | exports.tags = [];
62 |
63 | /**
64 | * @name caches
65 | * @description 存放缓存的数组
66 | */
67 |
68 | exports.caches = [];
69 |
70 | /**
71 | * @name regex
72 | * @description 存放正则的对象
73 | */
74 |
75 | exports.regex = {};
76 |
77 | /**
78 | * @name extend
79 | * @description 扩展对象
80 | * @param {Object} defaults 要扩展进来的对象
81 | * @param {Object} options 扩展对象本身
82 | * @return {Object} options 返回扩展后的对象
83 | */
84 |
85 | exports.extend = function(defaults, options) {
86 | options = options || this;
87 |
88 | for (var i in defaults) {
89 | if(typeof options[i] == "undefined") options[i] = defaults[i];
90 | }
91 |
92 | return options;
93 | };
94 |
95 | /**
96 | * @name settings
97 | * @description 配置选项
98 | */
99 |
100 | exports.settings = {
101 | debug : false, // 是否开启调试功能,默认不开启,在生产环境下,这样能获得更高的性能和速度;开发时,请开启;
102 | cached : true, // 是否开启缓存,默认开启,性能更好
103 | filter : true, // 是否过滤模板中的危险语句等,如alert等
104 | openTag : "<%", // 模板开始标签
105 | closeTag : "%>" // 模板结束标签
106 | };
107 |
108 | /**
109 | * @name set
110 | * @alias config
111 | * @description 过滤模板中的危险语句
112 | * @param {String|Object} key|options 模板文件的内容
113 | * @param {String} value 模板语句的开始标签
114 | * @return {String} result 返回模板文件的内容
115 | */
116 |
117 | exports.set = exports.config = function() {
118 | if(arguments && arguments.length > 1) {
119 | this.settings[arguments[0]] = arguments[1];
120 | } else {
121 | this.settings = this.extend(this.settings, arguments[0]);
122 | }
123 | };
124 |
125 | /**
126 | * @name htmlEncode
127 | * @description 对HTML内容进行编码
128 | * @param {String} html HTML内容
129 | * @return {String} result 返回编码后的HTML内容
130 | */
131 |
132 | exports.htmlEncode = function(html) {
133 | return html.replace(/\/igm, ">").replace(/\"/igm, """).replace(/\'/igm, "'");
134 | };
135 |
136 | /**
137 | * @name htmlDecode
138 | * @description 对编码后的HTML内容进行解码
139 | * @param {String} html 编码后的HTML内容
140 | * @return {String} result 返回解码后HTML内容
141 | */
142 |
143 | exports.htmlDecode = function(html) {
144 | return html.replace(/\</igm, "<").replace(/\>/igm, ">").replace(/\"/igm, "\"").replace(/\'/igm, "'");
145 | };
146 |
147 | /**
148 | * @name filter
149 | * @description 过滤模板中的危险语句
150 | * @param {String} tpl 模板文件的内容
151 | * @param {String} openTag 模板语句的开始标签
152 | * @param {String} closeTag 模板语句的结束标签
153 | * @return {String} result 返回模板文件的内容
154 | */
155 |
156 | exports.filter = function(tpl, openTag, closeTag) {
157 | tpl = tpl.replace(/\s*<\s*iframe\s*(.*)\s*>\s*<\s*\/iframe\s*>\s*/igm, "");
158 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*alert\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
159 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*confirm\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
160 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*prompt\s*\(\s*(.*)\s*\)\s*;?\s*(.*)"+closeTag, "igm"), "");
161 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*window\s*(.|;)\s*(.*)"+closeTag, "igm"), "");
162 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*document\s*(.|;)\s*(.*)"+closeTag, "igm"), "");
163 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*createElement\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
164 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*(\\$|jQuery)\s*(.*)\s*\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
165 | tpl = tpl.replace(new RegExp(openTag + "\s*(.*)\s*eval\(\s*(.*)\s*\)\s*(.*)"+closeTag, "igm"), "");
166 |
167 | return tpl.trim();
168 | };
169 |
170 | /**
171 | * @name compileInclude
172 | * @description 编译嵌套(include)的模板文件的回调函数
173 | * @param {String} data 传入的数据
174 | * @return {String} result 一个以字符串为形式的函数
175 | */
176 |
177 | exports.compileInclude = function(data) {
178 | var openTag = this.settings.openTag;
179 | var closeTag = this.settings.closeTag;
180 |
181 | return (function($1, $2, $3) {
182 | var guid = (new Date).getTime();
183 | var id = $2.replace("('", "").replace("')", "").replace("(\"", "").replace("\")", "");
184 | var includeContent = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML;
185 | var compile = _this.compile(includeContent, data, {include: true, name: id}).toString().split("\n").join('');
186 |
187 | compile = compile.replace('anonymous', 'anonymous' + guid);
188 | compile += ' if (typeof __data__ !== "undefined") { __out__ += anonymous' + guid + '(__data__); }';
189 | compile = openTag + " " + compile + " " + closeTag;
190 |
191 | return compile;
192 | });
193 | };
194 |
195 | /**
196 | * @name compile
197 | * @description 编译模板文件
198 | * @param {String} tpl 模板文件的内容
199 | * @param {Object} data 传入的数据
200 | * @param {Object} options 配置选项,参数为:include 是否为嵌套的模板,name 模板ID,isModule 是否预编译为 CMD 模块
201 | * @return {Function|String} fn 返回预编译函数或预编译模块的字符串
202 | */
203 |
204 | exports.compile = function(tpl, data, options) {
205 | var guid = (new Date).getTime();
206 | var defaults = {
207 | include : false, // 是否为引入
208 | name : "tile" + guid, // 匿名函数 ID
209 | isModule : false // 是否定义为模块,Sea.js / Require.js CMD 模块
210 | };
211 |
212 | data = data || {};
213 | options = options || {};
214 | options = this.extend(defaults, options);
215 |
216 | var include = options.include;
217 | var filter = this.settings.filter;
218 | var debug = this.settings.debug;
219 | var openTag = this.settings.openTag;
220 | var closeTag = this.settings.closeTag;
221 | var regex = new RegExp(openTag + "=(.*?)" + closeTag, "igm"); // or use eval
222 | var varCommentRegex = new RegExp(openTag + "=#(.*?)" + closeTag, "igm"); // variable comment
223 | var escapeRegex = new RegExp(openTag + "=@(.*?)" + closeTag, "igm"); // escape regex
224 | var lineCommentRegex = new RegExp("\/\/" + openTag + "?\\s*(.*?)\\s*" + closeTag + "?", "igm"); // line comment
225 | var tagRegex = /\s*tag:(\w+):?([\u4e00-\u9fa5]+|\w+|\d+)?\s*/igm;
226 | var includeRegex = new RegExp(openTag + "\\s*include\\s*((.*?))\\s*;?\\s*" + closeTag, "igm");
227 |
228 | var tagRegexHandler = function ($1, $2, $3) {
229 | var str = (typeof $3 == "undefined") ? _this.tags[$2]() : _this.tags[$2]($3.toString());
230 |
231 | return "'" + str.toString() + "'";
232 | };
233 |
234 | this.regex.include = includeRegex;
235 |
236 | tpl = tpl.replace(/(^\s*)|(\s*$)/g, "") // clear spaces
237 | .replace(varCommentRegex, function($1, $2) { return "'+'"; })
238 | .replace(lineCommentRegex, "")
239 | .replace(this.regex.include, this.compileInclude(data))
240 | .replace(escapeRegex, function($1, $2) { return "'+__escape(" + $2 + ")+'"; })
241 | .replace(regex, function($1, $2) { return "'+" + $2 + "+'"; })
242 | .split("\n");
243 |
244 | if (debug) {
245 | for (var i = 0, len = tpl.length; i < len; i++) {
246 | tpl[i] = tpl[i].toString().replace(/(^\s*)|(\s*$)/g, "") + "_&_" + openTag + " __line__=" + (i + 1) +"; " + closeTag + "_&_";
247 | }
248 |
249 | tpl = tpl.join('').split("_&_");
250 | }
251 |
252 | for (var i = 0, len = tpl.length; i < len; i++) {
253 | tpl[i] = tpl[i].trim();
254 |
255 | if(filter) tpl[i] = this.filter(tpl[i], openTag, closeTag);
256 |
257 | tpl[i] = "__out__+='" + tpl[i] + "';";
258 |
259 | tpl[i] = tpl[i].replace(tagRegex, tagRegexHandler);
260 |
261 | tpl[i] = tpl[i].replace("__out__+='" + openTag, "").replace(closeTag + "';", "").replace("}';", "}");
262 | tpl[i] = tpl[i].replace(/^__out__\+\=\'\/\/(.*?)\'\;$/, function($1, $2, $3) { return "/* " + $2 + " */"; }).replace("'';';", "'';");
263 | }
264 |
265 | var __escape = 'function __escape(str) {return str.replace(/[\'|\"|\>|\<|;]?/igm, "");}';
266 | var objectCount = "function objectCount(data){var total=0;for(var i in data){total++} return total;};";
267 | var errorHandler = "if (typeof console === 'object') {console.error('[tileTemplate]\\n" + options.name + "\\n\\n[type]\\nRender error\\n\\nmessage: '+e.message+'\\nline: '+(e.line+1));}";
268 | var debugCode = "try {" + tpl.join('') + "\n return __out__; } catch(e) { e.line=__line__; if(objectCount(data) > 0) { "+errorHandler+" }}";
269 |
270 | tpl = "var __out__ = '', __line__=0;\n " + ( (debug) ? debugCode : tpl.join('') + "\n return __out__;" );
271 |
272 | var vars = [];
273 |
274 | for (var i in data) {
275 | vars.push(i + ' = __data__.' + i);
276 | }
277 |
278 | vars = (vars.length > 0) ? 'var ' + vars.join(', ') + ';\n' : '';
279 |
280 | var fn = new Function('__data__', __escape + objectCount + vars + tpl);
281 |
282 | if (options.isModule) {
283 | fn = 'define(function(require, exports, module) {module.exports = '+ fn.toLocaleString().replace('anonymous', '') + '})';
284 | }
285 |
286 | return fn;
287 | };
288 |
289 | /**
290 | * @name render
291 | * @description 渲染模板文件
292 | * @param {String} id 模板ID或直接传入模板内容
293 | * @param {Object} data 传入的数据
294 | * @param {String} filename 指定模板名称
295 | * @param {Boolean} [isModule=false] 是否定义为模块
296 | * @return {Function|String} html 返回模板渲染后的 HTML 或预编译模块的字符串
297 | */
298 |
299 | exports.render = function(id, data, filename, isModule) {
300 | if (typeof isModule === 'undefined') {
301 | isModule = false;
302 | }
303 |
304 | data = data || {};
305 | filename = filename || "";
306 |
307 | var cached = this.settings.cached;
308 | var tpl = (typeof $(id) == "string") ? id : ($(id) == null) ? id : $(id).value || $(id).innerHTML;
309 | var tplName = (filename == "") ? id : filename;
310 |
311 | if (cached && typeof this.caches[tplName] !== "undefined") { // use cache
312 | return this.caches[tplName];
313 | } else {
314 | var html = this.compile(tpl, data, {
315 | name : (filename == "") ? id : filename,
316 | isModule : isModule
317 | })(data);
318 |
319 | if (cached) {
320 | this.caches[tplName] = html;
321 | }
322 |
323 | return html;
324 | }
325 | };
326 |
327 | /**
328 | * @name tag
329 | * @description 自定义模板标签语句
330 | * @param {String} name 标签的名称
331 | * @param {Function} callback 处理标签内容的回调函数
332 | * @return {Void} void 无
333 | */
334 |
335 | exports.tag = function(name, callback) {
336 | this.tags[name] = callback;
337 | };
338 |
339 | /**
340 | * @name clear
341 | * @description 清除模板缓存
342 | * @param {String} id 要清除的模板 ID
343 | * @return {Boolean} bool 返回 true
344 | */
345 |
346 | exports.clear = function(id) {
347 | delete this.caches[id];
348 |
349 | return true;
350 | };
351 | }));
--------------------------------------------------------------------------------
/examples/js/require.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.1.15 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var requirejs,require,define;
7 | (function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&
19 | (f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
20 | this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f);
21 | if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval",
22 | "fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.defineDep(b,
23 | a);this.check()}));this.errback&&q(a,"error",u(this,this.errback))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:j,contextName:b,registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p,
24 | nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b,
25 | a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,e){function j(c,d,m){var n,q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=
26 | !0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!==
27 | e&&(!("."===k||".."===k)||1e.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)):
34 | (e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=I,q.baseUrl||(E=s.split("/"),s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl=
35 | O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||
36 | (b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this);
--------------------------------------------------------------------------------
/tests/js/templates/mustache.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * mustache.js - Logic-less {{mustache}} templates with JavaScript
3 | * http://github.com/janl/mustache.js
4 | */
5 | var Mustache = (typeof module !== "undefined" && module.exports) || {};
6 |
7 | (function (exports) {
8 |
9 | exports.name = "mustache.js";
10 | exports.version = "0.5.0-dev";
11 | exports.tags = ["{{", "}}"];
12 | exports.parse = parse;
13 | exports.compile = compile;
14 | exports.render = render;
15 | exports.clearCache = clearCache;
16 |
17 | // This is here for backwards compatibility with 0.4.x.
18 | exports.to_html = function (template, view, partials, send) {
19 | var result = render(template, view, partials);
20 |
21 | if (typeof send === "function") {
22 | send(result);
23 | } else {
24 | return result;
25 | }
26 | };
27 |
28 | var _toString = Object.prototype.toString;
29 | var _isArray = Array.isArray;
30 | var _forEach = Array.prototype.forEach;
31 | var _trim = String.prototype.trim;
32 |
33 | var isArray;
34 | if (_isArray) {
35 | isArray = _isArray;
36 | } else {
37 | isArray = function (obj) {
38 | return _toString.call(obj) === "[object Array]";
39 | };
40 | }
41 |
42 | var forEach;
43 | if (_forEach) {
44 | forEach = function (obj, callback, scope) {
45 | return _forEach.call(obj, callback, scope);
46 | };
47 | } else {
48 | forEach = function (obj, callback, scope) {
49 | for (var i = 0, len = obj.length; i < len; ++i) {
50 | callback.call(scope, obj[i], i, obj);
51 | }
52 | };
53 | }
54 |
55 | var spaceRe = /^\s*$/;
56 |
57 | function isWhitespace(string) {
58 | return spaceRe.test(string);
59 | }
60 |
61 | var trim;
62 | if (_trim) {
63 | trim = function (string) {
64 | return string == null ? "" : _trim.call(string);
65 | };
66 | } else {
67 | var trimLeft, trimRight;
68 |
69 | if (isWhitespace("\xA0")) {
70 | trimLeft = /^\s+/;
71 | trimRight = /\s+$/;
72 | } else {
73 | // IE doesn't match non-breaking spaces with \s, thanks jQuery.
74 | trimLeft = /^[\s\xA0]+/;
75 | trimRight = /[\s\xA0]+$/;
76 | }
77 |
78 | trim = function (string) {
79 | return string == null ? "" :
80 | String(string).replace(trimLeft, "").replace(trimRight, "");
81 | };
82 | }
83 |
84 | var escapeMap = {
85 | "&": "&",
86 | "<": "<",
87 | ">": ">",
88 | '"': '"',
89 | "'": '''
90 | };
91 |
92 | function escapeHTML(string) {
93 | return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
94 | return escapeMap[s] || s;
95 | });
96 | }
97 |
98 | /**
99 | * Adds the `template`, `line`, and `file` properties to the given error
100 | * object and alters the message to provide more useful debugging information.
101 | */
102 | function debug(e, template, line, file) {
103 | file = file || "";
104 |
105 | var lines = template.split("\n"),
106 | start = Math.max(line - 3, 0),
107 | end = Math.min(lines.length, line + 3),
108 | context = lines.slice(start, end);
109 |
110 | var c;
111 | for (var i = 0, len = context.length; i < len; ++i) {
112 | c = i + start + 1;
113 | context[i] = (c === line ? " >> " : " ") + context[i];
114 | }
115 |
116 | e.template = template;
117 | e.line = line;
118 | e.file = file;
119 | e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
120 |
121 | return e;
122 | }
123 |
124 | /**
125 | * Looks up the value of the given `name` in the given context `stack`.
126 | */
127 | function lookup(name, stack, defaultValue) {
128 | if (name === ".") {
129 | return stack[stack.length - 1];
130 | }
131 |
132 | var names = name.split(".");
133 | var lastIndex = names.length - 1;
134 | var target = names[lastIndex];
135 |
136 | var value, context, i = stack.length, j, localStack;
137 | while (i) {
138 | localStack = stack.slice(0);
139 | context = stack[--i];
140 |
141 | j = 0;
142 | while (j < lastIndex) {
143 | context = context[names[j++]];
144 |
145 | if (context == null) {
146 | break;
147 | }
148 |
149 | localStack.push(context);
150 | }
151 |
152 | if (context && typeof context === "object" && target in context) {
153 | value = context[target];
154 | break;
155 | }
156 | }
157 |
158 | // If the value is a function, call it in the current context.
159 | if (typeof value === "function") {
160 | value = value.call(localStack[localStack.length - 1]);
161 | }
162 |
163 | if (value == null) {
164 | return defaultValue;
165 | }
166 |
167 | return value;
168 | }
169 |
170 | function renderSection(name, stack, callback, inverted) {
171 | var buffer = "";
172 | var value = lookup(name, stack);
173 |
174 | if (inverted) {
175 | // From the spec: inverted sections may render text once based on the
176 | // inverse value of the key. That is, they will be rendered if the key
177 | // doesn't exist, is false, or is an empty list.
178 | if (value == null || value === false || (isArray(value) && value.length === 0)) {
179 | buffer += callback();
180 | }
181 | } else if (isArray(value)) {
182 | forEach(value, function (value) {
183 | stack.push(value);
184 | buffer += callback();
185 | stack.pop();
186 | });
187 | } else if (typeof value === "object") {
188 | stack.push(value);
189 | buffer += callback();
190 | stack.pop();
191 | } else if (typeof value === "function") {
192 | var scope = stack[stack.length - 1];
193 | var scopedRender = function (template) {
194 | return render(template, scope);
195 | };
196 | buffer += value.call(scope, callback(), scopedRender) || "";
197 | } else if (value) {
198 | buffer += callback();
199 | }
200 |
201 | return buffer;
202 | }
203 |
204 | /**
205 | * Parses the given `template` and returns the source of a function that,
206 | * with the proper arguments, will render the template. Recognized options
207 | * include the following:
208 | *
209 | * - file The name of the file the template comes from (displayed in
210 | * error messages)
211 | * - tags An array of open and close tags the `template` uses. Defaults
212 | * to the value of Mustache.tags
213 | * - debug Set `true` to log the body of the generated function to the
214 | * console
215 | * - space Set `true` to preserve whitespace from lines that otherwise
216 | * contain only a {{tag}}. Defaults to `false`
217 | */
218 | function parse(template, options) {
219 | options = options || {};
220 |
221 | var tags = options.tags || exports.tags,
222 | openTag = tags[0],
223 | closeTag = tags[tags.length - 1];
224 |
225 | var code = [
226 | 'var buffer = "";', // output buffer
227 | "\nvar line = 1;", // keep track of source line number
228 | "\ntry {",
229 | '\nbuffer += "'
230 | ];
231 |
232 | var spaces = [], // indices of whitespace in code on the current line
233 | hasTag = false, // is there a {{tag}} on the current line?
234 | nonSpace = false; // is there a non-space char on the current line?
235 |
236 | // Strips all space characters from the code array for the current line
237 | // if there was a {{tag}} on it and otherwise only spaces.
238 | var stripSpace = function () {
239 | if (hasTag && !nonSpace && !options.space) {
240 | while (spaces.length) {
241 | code.splice(spaces.pop(), 1);
242 | }
243 | } else {
244 | spaces = [];
245 | }
246 |
247 | hasTag = false;
248 | nonSpace = false;
249 | };
250 |
251 | var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
252 |
253 | var setTags = function (source) {
254 | tags = trim(source).split(/\s+/);
255 | nextOpenTag = tags[0];
256 | nextCloseTag = tags[tags.length - 1];
257 | };
258 |
259 | var includePartial = function (source) {
260 | code.push(
261 | '";',
262 | updateLine,
263 | '\nvar partial = partials["' + trim(source) + '"];',
264 | '\nif (partial) {',
265 | '\n buffer += render(partial,stack[stack.length - 1],partials);',
266 | '\n}',
267 | '\nbuffer += "'
268 | );
269 | };
270 |
271 | var openSection = function (source, inverted) {
272 | var name = trim(source);
273 |
274 | if (name === "") {
275 | throw debug(new Error("Section name may not be empty"), template, line, options.file);
276 | }
277 |
278 | sectionStack.push({name: name, inverted: inverted});
279 |
280 | code.push(
281 | '";',
282 | updateLine,
283 | '\nvar name = "' + name + '";',
284 | '\nvar callback = (function () {',
285 | '\n return function () {',
286 | '\n var buffer = "";',
287 | '\nbuffer += "'
288 | );
289 | };
290 |
291 | var openInvertedSection = function (source) {
292 | openSection(source, true);
293 | };
294 |
295 | var closeSection = function (source) {
296 | var name = trim(source);
297 | var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
298 |
299 | if (!openName || name != openName) {
300 | throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
301 | }
302 |
303 | var section = sectionStack.pop();
304 |
305 | code.push(
306 | '";',
307 | '\n return buffer;',
308 | '\n };',
309 | '\n})();'
310 | );
311 |
312 | if (section.inverted) {
313 | code.push("\nbuffer += renderSection(name,stack,callback,true);");
314 | } else {
315 | code.push("\nbuffer += renderSection(name,stack,callback);");
316 | }
317 |
318 | code.push('\nbuffer += "');
319 | };
320 |
321 | var sendPlain = function (source) {
322 | code.push(
323 | '";',
324 | updateLine,
325 | '\nbuffer += lookup("' + trim(source) + '",stack,"");',
326 | '\nbuffer += "'
327 | );
328 | };
329 |
330 | var sendEscaped = function (source) {
331 | code.push(
332 | '";',
333 | updateLine,
334 | '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
335 | '\nbuffer += "'
336 | );
337 | };
338 |
339 | var line = 1, c, callback;
340 | for (var i = 0, len = template.length; i < len; ++i) {
341 | if (template.slice(i, i + openTag.length) === openTag) {
342 | i += openTag.length;
343 | c = template.substr(i, 1);
344 | updateLine = '\nline = ' + line + ';';
345 | nextOpenTag = openTag;
346 | nextCloseTag = closeTag;
347 | hasTag = true;
348 |
349 | switch (c) {
350 | case "!": // comment
351 | i++;
352 | callback = null;
353 | break;
354 | case "=": // change open/close tags, e.g. {{=<% %>=}}
355 | i++;
356 | closeTag = "=" + closeTag;
357 | callback = setTags;
358 | break;
359 | case ">": // include partial
360 | i++;
361 | callback = includePartial;
362 | break;
363 | case "#": // start section
364 | i++;
365 | callback = openSection;
366 | break;
367 | case "^": // start inverted section
368 | i++;
369 | callback = openInvertedSection;
370 | break;
371 | case "/": // end section
372 | i++;
373 | callback = closeSection;
374 | break;
375 | case "{": // plain variable
376 | closeTag = "}" + closeTag;
377 | // fall through
378 | case "&": // plain variable
379 | i++;
380 | nonSpace = true;
381 | callback = sendPlain;
382 | break;
383 | default: // escaped variable
384 | nonSpace = true;
385 | callback = sendEscaped;
386 | }
387 |
388 | var end = template.indexOf(closeTag, i);
389 |
390 | if (end === -1) {
391 | throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
392 | }
393 |
394 | var source = template.substring(i, end);
395 |
396 | if (callback) {
397 | callback(source);
398 | }
399 |
400 | // Maintain line count for \n in source.
401 | var n = 0;
402 | while (~(n = source.indexOf("\n", n))) {
403 | line++;
404 | n++;
405 | }
406 |
407 | i = end + closeTag.length - 1;
408 | openTag = nextOpenTag;
409 | closeTag = nextCloseTag;
410 | } else {
411 | c = template.substr(i, 1);
412 |
413 | switch (c) {
414 | case '"':
415 | case "\\":
416 | nonSpace = true;
417 | code.push("\\" + c);
418 | break;
419 | case "\r":
420 | // Ignore carriage returns.
421 | break;
422 | case "\n":
423 | spaces.push(code.length);
424 | code.push("\\n");
425 | stripSpace(); // Check for whitespace on the current line.
426 | line++;
427 | break;
428 | default:
429 | if (isWhitespace(c)) {
430 | spaces.push(code.length);
431 | } else {
432 | nonSpace = true;
433 | }
434 |
435 | code.push(c);
436 | }
437 | }
438 | }
439 |
440 | if (sectionStack.length != 0) {
441 | throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
442 | }
443 |
444 | // Clean up any whitespace from a closing {{tag}} that was at the end
445 | // of the template without a trailing \n.
446 | stripSpace();
447 |
448 | code.push(
449 | '";',
450 | "\nreturn buffer;",
451 | "\n} catch (e) { throw {error: e, line: line}; }"
452 | );
453 |
454 | // Ignore `buffer += "";` statements.
455 | var body = code.join("").replace(/buffer \+= "";\n/g, "");
456 |
457 | if (options.debug) {
458 | if (typeof console != "undefined" && console.log) {
459 | console.log(body);
460 | } else if (typeof print === "function") {
461 | print(body);
462 | }
463 | }
464 |
465 | return body;
466 | }
467 |
468 | /**
469 | * Used by `compile` to generate a reusable function for the given `template`.
470 | */
471 | function _compile(template, options) {
472 | var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
473 | var body = parse(template, options);
474 | var fn = new Function(args, body);
475 |
476 | // This anonymous function wraps the generated function so we can do
477 | // argument coercion, setup some variables, and handle any errors
478 | // encountered while executing it.
479 | return function (view, partials) {
480 | partials = partials || {};
481 |
482 | var stack = [view]; // context stack
483 |
484 | try {
485 | return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
486 | } catch (e) {
487 | throw debug(e.error, template, e.line, options.file);
488 | }
489 | };
490 | }
491 |
492 | // Cache of pre-compiled templates.
493 | var _cache = {};
494 |
495 | /**
496 | * Clear the cache of compiled templates.
497 | */
498 | function clearCache() {
499 | _cache = {};
500 | }
501 |
502 | /**
503 | * Compiles the given `template` into a reusable function using the given
504 | * `options`. In addition to the options accepted by Mustache.parse,
505 | * recognized options include the following:
506 | *
507 | * - cache Set `false` to bypass any pre-compiled version of the given
508 | * template. Otherwise, a given `template` string will be cached
509 | * the first time it is parsed
510 | */
511 | function compile(template, options) {
512 | options = options || {};
513 |
514 | // Use a pre-compiled version from the cache if we have one.
515 | if (options.cache !== false) {
516 | if (!_cache[template]) {
517 | _cache[template] = _compile(template, options);
518 | }
519 |
520 | return _cache[template];
521 | }
522 |
523 | return _compile(template, options);
524 | }
525 |
526 | /**
527 | * High-level function that renders the given `template` using the given
528 | * `view` and `partials`. If you need to use any of the template options (see
529 | * `compile` above), you must compile in a separate step, and then call that
530 | * compiled function.
531 | */
532 | function render(template, view, partials) {
533 | return compile(template)(view, partials);
534 | }
535 |
536 | })(Mustache);
--------------------------------------------------------------------------------