酒店预订平台
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

367 lines
8.4 KiB

  1. /**
  2. * 编译模板
  3. * 2012-6-6 @TooBug: define 方法名改为 compile,与 Node Express 保持一致
  4. * @name template.compile
  5. * @param {String} 模板字符串
  6. * @param {Object} 编译选项
  7. *
  8. * - openTag {String}
  9. * - closeTag {String}
  10. * - filename {String}
  11. * - escape {Boolean}
  12. * - compress {Boolean}
  13. * - debug {Boolean}
  14. * - cache {Boolean}
  15. * - parser {Function}
  16. *
  17. * @return {Function} 渲染方法
  18. */
  19. var compile = template.compile = function (source, options) {
  20. // 合并默认配置
  21. options = options || {};
  22. for (var name in defaults) {
  23. if (options[name] === undefined) {
  24. options[name] = defaults[name];
  25. }
  26. }
  27. var filename = options.filename;
  28. try {
  29. var Render = compiler(source, options);
  30. } catch (e) {
  31. e.filename = filename || 'anonymous';
  32. e.name = 'Syntax Error';
  33. return showDebugInfo(e);
  34. }
  35. // 对编译结果进行一次包装
  36. function render (data) {
  37. try {
  38. return new Render(data, filename) + '';
  39. } catch (e) {
  40. // 运行时出错后自动开启调试模式重新编译
  41. if (!options.debug) {
  42. options.debug = true;
  43. return compile(source, options)(data);
  44. }
  45. return showDebugInfo(e)();
  46. }
  47. }
  48. render.prototype = Render.prototype;
  49. render.toString = function () {
  50. return Render.toString();
  51. };
  52. if (filename && options.cache) {
  53. cacheStore[filename] = render;
  54. }
  55. return render;
  56. };
  57. // 数组迭代
  58. var forEach = utils.$each;
  59. // 静态分析模板变量
  60. var KEYWORDS =
  61. // 关键字
  62. 'break,case,catch,continue,debugger,default,delete,do,else,false'
  63. + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
  64. + ',throw,true,try,typeof,var,void,while,with'
  65. // 保留字
  66. + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
  67. + ',final,float,goto,implements,import,int,interface,long,native'
  68. + ',package,private,protected,public,short,static,super,synchronized'
  69. + ',throws,transient,volatile'
  70. // ECMA 5 - use strict
  71. + ',arguments,let,yield'
  72. + ',undefined';
  73. var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
  74. var SPLIT_RE = /[^\w$]+/g;
  75. var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
  76. var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
  77. var BOUNDARY_RE = /^,+|,+$/g;
  78. var SPLIT2_RE = /^$|,+/;
  79. // 获取变量
  80. function getVariable (code) {
  81. return code
  82. .replace(REMOVE_RE, '')
  83. .replace(SPLIT_RE, ',')
  84. .replace(KEYWORDS_RE, '')
  85. .replace(NUMBER_RE, '')
  86. .replace(BOUNDARY_RE, '')
  87. .split(SPLIT2_RE);
  88. };
  89. // 字符串转义
  90. function stringify (code) {
  91. return "'" + code
  92. // 单引号与反斜杠转义
  93. .replace(/('|\\)/g, '\\$1')
  94. // 换行符转义(windows + linux)
  95. .replace(/\r/g, '\\r')
  96. .replace(/\n/g, '\\n') + "'";
  97. }
  98. function compiler (source, options) {
  99. var debug = options.debug;
  100. var openTag = options.openTag;
  101. var closeTag = options.closeTag;
  102. var parser = options.parser;
  103. var compress = options.compress;
  104. var escape = options.escape;
  105. var line = 1;
  106. var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
  107. var isNewEngine = ''.trim;// '__proto__' in {}
  108. var replaces = isNewEngine
  109. ? ["$out='';", "$out+=", ";", "$out"]
  110. : ["$out=[];", "$out.push(", ");", "$out.join('')"];
  111. var concat = isNewEngine
  112. ? "$out+=text;return $out;"
  113. : "$out.push(text);";
  114. var print = "function(){"
  115. + "var text=''.concat.apply('',arguments);"
  116. + concat
  117. + "}";
  118. var include = "function(filename,data){"
  119. + "data=data||$data;"
  120. + "var text=$utils.$include(filename,data,$filename);"
  121. + concat
  122. + "}";
  123. var headerCode = "'use strict';"
  124. + "var $utils=this,$helpers=$utils.$helpers,"
  125. + (debug ? "$line=0," : "");
  126. var mainCode = replaces[0];
  127. var footerCode = "return new String(" + replaces[3] + ");"
  128. // html与逻辑语法分离
  129. forEach(source.split(openTag), function (code) {
  130. code = code.split(closeTag);
  131. var $0 = code[0];
  132. var $1 = code[1];
  133. // code: [html]
  134. if (code.length === 1) {
  135. mainCode += html($0);
  136. // code: [logic, html]
  137. } else {
  138. mainCode += logic($0);
  139. if ($1) {
  140. mainCode += html($1);
  141. }
  142. }
  143. });
  144. var code = headerCode + mainCode + footerCode;
  145. // 调试语句
  146. if (debug) {
  147. code = "try{" + code + "}catch(e){"
  148. + "throw {"
  149. + "filename:$filename,"
  150. + "name:'Render Error',"
  151. + "message:e.message,"
  152. + "line:$line,"
  153. + "source:" + stringify(source)
  154. + ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
  155. + "};"
  156. + "}";
  157. }
  158. try {
  159. var Render = new Function("$data", "$filename", code);
  160. Render.prototype = utils;
  161. return Render;
  162. } catch (e) {
  163. e.temp = "function anonymous($data,$filename) {" + code + "}";
  164. throw e;
  165. }
  166. // 处理 HTML 语句
  167. function html (code) {
  168. // 记录行号
  169. line += code.split(/\n/).length - 1;
  170. // 压缩多余空白与注释
  171. if (compress) {
  172. code = code
  173. .replace(/\s+/g, ' ')
  174. .replace(/<!--[\w\W]*?-->/g, '');
  175. }
  176. if (code) {
  177. code = replaces[1] + stringify(code) + replaces[2] + "\n";
  178. }
  179. return code;
  180. }
  181. // 处理逻辑语句
  182. function logic (code) {
  183. var thisLine = line;
  184. if (parser) {
  185. // 语法转换插件钩子
  186. code = parser(code, options);
  187. } else if (debug) {
  188. // 记录行号
  189. code = code.replace(/\n/g, function () {
  190. line ++;
  191. return "$line=" + line + ";";
  192. });
  193. }
  194. // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
  195. // <%=#value%> 等同 v2.0.3 之前的 <%==value%>
  196. if (code.indexOf('=') === 0) {
  197. var escapeSyntax = escape && !/^=[=#]/.test(code);
  198. code = code.replace(/^=[=#]?|[\s;]*$/g, '');
  199. // 对内容编码
  200. if (escapeSyntax) {
  201. var name = code.replace(/\s*\([^\)]+\)/, '');
  202. // 排除 utils.* | include | print
  203. if (!utils[name] && !/^(include|print)$/.test(name)) {
  204. code = "$escape(" + code + ")";
  205. }
  206. // 不编码
  207. } else {
  208. code = "$string(" + code + ")";
  209. }
  210. code = replaces[1] + code + replaces[2];
  211. }
  212. if (debug) {
  213. code = "$line=" + thisLine + ";" + code;
  214. }
  215. // 提取模板中的变量名
  216. forEach(getVariable(code), function (name) {
  217. // name 值可能为空,在安卓低版本浏览器下
  218. if (!name || uniq[name]) {
  219. return;
  220. }
  221. var value;
  222. // 声明模板变量
  223. // 赋值优先级:
  224. // [include, print] > utils > helpers > data
  225. if (name === 'print') {
  226. value = print;
  227. } else if (name === 'include') {
  228. value = include;
  229. } else if (utils[name]) {
  230. value = "$utils." + name;
  231. } else if (helpers[name]) {
  232. value = "$helpers." + name;
  233. } else {
  234. value = "$data." + name;
  235. }
  236. headerCode += name + "=" + value + ",";
  237. uniq[name] = true;
  238. });
  239. return code + "\n";
  240. }
  241. };