template-debug.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. /*!
  2. * artTemplate - Template Engine
  3. * https://github.com/aui/artTemplate
  4. * Released under the MIT, BSD, and GPL Licenses
  5. */
  6. !(function () {
  7. /**
  8. * 模板引擎
  9. * @name template
  10. * @param {String} 模板名
  11. * @param {Object, String} 数据。如果为字符串则编译并缓存编译结果
  12. * @return {String, Function} 渲染好的HTML字符串或者渲染方法
  13. */
  14. var template = function (filename, content) {
  15. return typeof content === 'string'
  16. ? compile(content, {
  17. filename: filename
  18. })
  19. : renderFile(filename, content);
  20. };
  21. template.version = '3.0.0';
  22. /**
  23. * 设置全局配置
  24. * @name template.config
  25. * @param {String} 名称
  26. * @param {Any} 值
  27. */
  28. template.config = function (name, value) {
  29. defaults[name] = value;
  30. };
  31. var defaults = template.defaults = {
  32. openTag: '<%', // 逻辑语法开始标签
  33. closeTag: '%>', // 逻辑语法结束标签
  34. escape: true, // 是否编码输出变量的 HTML 字符
  35. cache: true, // 是否开启缓存(依赖 options 的 filename 字段)
  36. compress: false, // 是否压缩输出
  37. parser: null // 自定义语法格式器 @see: template-syntax.js
  38. };
  39. var cacheStore = template.cache = {};
  40. /**
  41. * 渲染模板
  42. * @name template.render
  43. * @param {String} 模板
  44. * @param {Object} 数据
  45. * @return {String} 渲染好的字符串
  46. */
  47. template.render = function (source, options) {
  48. return compile(source)(options);
  49. };
  50. /**
  51. * 渲染模板(根据模板名)
  52. * @name template.render
  53. * @param {String} 模板名
  54. * @param {Object} 数据
  55. * @return {String} 渲染好的字符串
  56. */
  57. var renderFile = template.renderFile = function (filename, data) {
  58. var fn = template.get(filename) || showDebugInfo({
  59. filename: filename,
  60. name: 'Render Error',
  61. message: 'Template not found'
  62. });
  63. return data ? fn(data) : fn;
  64. };
  65. /**
  66. * 获取编译缓存(可由外部重写此方法)
  67. * @param {String} 模板名
  68. * @param {Function} 编译好的函数
  69. */
  70. template.get = function (filename) {
  71. var cache;
  72. if (cacheStore[filename]) {
  73. // 使用内存缓存
  74. cache = cacheStore[filename];
  75. } else if (typeof document === 'object') {
  76. // 加载模板并编译
  77. var elem = document.getElementById(filename);
  78. if (elem) {
  79. var source = (elem.value || elem.innerHTML)
  80. .replace(/^\s*|\s*$/g, '');
  81. cache = compile(source, {
  82. filename: filename
  83. });
  84. }
  85. }
  86. return cache;
  87. };
  88. var toString = function (value, type) {
  89. if (typeof value !== 'string') {
  90. type = typeof value;
  91. if (type === 'number') {
  92. value += '';
  93. } else if (type === 'function') {
  94. value = toString(value.call(value));
  95. } else {
  96. value = '';
  97. }
  98. }
  99. return value;
  100. };
  101. var escapeMap = {
  102. "<": "&#60;",
  103. ">": "&#62;",
  104. '"': "&#34;",
  105. "'": "&#39;",
  106. "&": "&#38;"
  107. };
  108. var escapeFn = function (s) {
  109. return escapeMap[s];
  110. };
  111. var escapeHTML = function (content) {
  112. return toString(content)
  113. .replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
  114. };
  115. var isArray = Array.isArray || function (obj) {
  116. return ({}).toString.call(obj) === '[object Array]';
  117. };
  118. var each = function (data, callback) {
  119. var i, len;
  120. if (isArray(data)) {
  121. for (i = 0, len = data.length; i < len; i++) {
  122. callback.call(data, data[i], i, data);
  123. }
  124. } else {
  125. for (i in data) {
  126. callback.call(data, data[i], i);
  127. }
  128. }
  129. };
  130. var utils = template.utils = {
  131. $helpers: {},
  132. $include: renderFile,
  133. $string: toString,
  134. $escape: escapeHTML,
  135. $each: each
  136. };/**
  137. * 添加模板辅助方法
  138. * @name template.helper
  139. * @param {String} 名称
  140. * @param {Function} 方法
  141. */
  142. template.helper = function (name, helper) {
  143. helpers[name] = helper;
  144. };
  145. var helpers = template.helpers = utils.$helpers;
  146. /**
  147. * 模板错误事件(可由外部重写此方法)
  148. * @name template.onerror
  149. * @event
  150. */
  151. template.onerror = function (e) {
  152. var message = 'Template Error\n\n';
  153. for (var name in e) {
  154. message += '<' + name + '>\n' + e[name] + '\n\n';
  155. }
  156. if (typeof console === 'object') {
  157. console.error(message);
  158. }
  159. };
  160. // 模板调试器
  161. var showDebugInfo = function (e) {
  162. template.onerror(e);
  163. return function () {
  164. return '{Template Error}';
  165. };
  166. };
  167. /**
  168. * 编译模板
  169. * 2012-6-6 @TooBug: define 方法名改为 compile,与 Node Express 保持一致
  170. * @name template.compile
  171. * @param {String} 模板字符串
  172. * @param {Object} 编译选项
  173. *
  174. * - openTag {String}
  175. * - closeTag {String}
  176. * - filename {String}
  177. * - escape {Boolean}
  178. * - compress {Boolean}
  179. * - debug {Boolean}
  180. * - cache {Boolean}
  181. * - parser {Function}
  182. *
  183. * @return {Function} 渲染方法
  184. */
  185. var compile = template.compile = function (source, options) {
  186. // 合并默认配置
  187. options = options || {};
  188. for (var name in defaults) {
  189. if (options[name] === undefined) {
  190. options[name] = defaults[name];
  191. }
  192. }
  193. var filename = options.filename;
  194. try {
  195. var Render = compiler(source, options);
  196. } catch (e) {
  197. e.filename = filename || 'anonymous';
  198. e.name = 'Syntax Error';
  199. return showDebugInfo(e);
  200. }
  201. // 对编译结果进行一次包装
  202. function render (data) {
  203. try {
  204. return new Render(data, filename) + '';
  205. } catch (e) {
  206. // 运行时出错后自动开启调试模式重新编译
  207. if (!options.debug) {
  208. options.debug = true;
  209. return compile(source, options)(data);
  210. }
  211. return showDebugInfo(e)();
  212. }
  213. }
  214. render.prototype = Render.prototype;
  215. render.toString = function () {
  216. return Render.toString();
  217. };
  218. if (filename && options.cache) {
  219. cacheStore[filename] = render;
  220. }
  221. return render;
  222. };
  223. // 数组迭代
  224. var forEach = utils.$each;
  225. // 静态分析模板变量
  226. var KEYWORDS =
  227. // 关键字
  228. 'break,case,catch,continue,debugger,default,delete,do,else,false'
  229. + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
  230. + ',throw,true,try,typeof,var,void,while,with'
  231. // 保留字
  232. + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
  233. + ',final,float,goto,implements,import,int,interface,long,native'
  234. + ',package,private,protected,public,short,static,super,synchronized'
  235. + ',throws,transient,volatile'
  236. // ECMA 5 - use strict
  237. + ',arguments,let,yield'
  238. + ',undefined';
  239. var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
  240. var SPLIT_RE = /[^\w$]+/g;
  241. var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
  242. var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
  243. var BOUNDARY_RE = /^,+|,+$/g;
  244. var SPLIT2_RE = /^$|,+/;
  245. // 获取变量
  246. function getVariable (code) {
  247. return code
  248. .replace(REMOVE_RE, '')
  249. .replace(SPLIT_RE, ',')
  250. .replace(KEYWORDS_RE, '')
  251. .replace(NUMBER_RE, '')
  252. .replace(BOUNDARY_RE, '')
  253. .split(SPLIT2_RE);
  254. };
  255. // 字符串转义
  256. function stringify (code) {
  257. return "'" + code
  258. // 单引号与反斜杠转义
  259. .replace(/('|\\)/g, '\\$1')
  260. // 换行符转义(windows + linux)
  261. .replace(/\r/g, '\\r')
  262. .replace(/\n/g, '\\n') + "'";
  263. }
  264. function compiler (source, options) {
  265. var debug = options.debug;
  266. var openTag = options.openTag;
  267. var closeTag = options.closeTag;
  268. var parser = options.parser;
  269. var compress = options.compress;
  270. var escape = options.escape;
  271. var line = 1;
  272. var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
  273. var isNewEngine = ''.trim;// '__proto__' in {}
  274. var replaces = isNewEngine
  275. ? ["$out='';", "$out+=", ";", "$out"]
  276. : ["$out=[];", "$out.push(", ");", "$out.join('')"];
  277. var concat = isNewEngine
  278. ? "$out+=text;return $out;"
  279. : "$out.push(text);";
  280. var print = "function(){"
  281. + "var text=''.concat.apply('',arguments);"
  282. + concat
  283. + "}";
  284. var include = "function(filename,data){"
  285. + "data=data||$data;"
  286. + "var text=$utils.$include(filename,data,$filename);"
  287. + concat
  288. + "}";
  289. var headerCode = "'use strict';"
  290. + "var $utils=this,$helpers=$utils.$helpers,"
  291. + (debug ? "$line=0," : "");
  292. var mainCode = replaces[0];
  293. var footerCode = "return new String(" + replaces[3] + ");"
  294. // html与逻辑语法分离
  295. forEach(source.split(openTag), function (code) {
  296. code = code.split(closeTag);
  297. var $0 = code[0];
  298. var $1 = code[1];
  299. // code: [html]
  300. if (code.length === 1) {
  301. mainCode += html($0);
  302. // code: [logic, html]
  303. } else {
  304. mainCode += logic($0);
  305. if ($1) {
  306. mainCode += html($1);
  307. }
  308. }
  309. });
  310. var code = headerCode + mainCode + footerCode;
  311. // 调试语句
  312. if (debug) {
  313. code = "try{" + code + "}catch(e){"
  314. + "throw {"
  315. + "filename:$filename,"
  316. + "name:'Render Error',"
  317. + "message:e.message,"
  318. + "line:$line,"
  319. + "source:" + stringify(source)
  320. + ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
  321. + "};"
  322. + "}";
  323. }
  324. try {
  325. var Render = new Function("$data", "$filename", code);
  326. Render.prototype = utils;
  327. return Render;
  328. } catch (e) {
  329. e.temp = "function anonymous($data,$filename) {" + code + "}";
  330. throw e;
  331. }
  332. // 处理 HTML 语句
  333. function html (code) {
  334. // 记录行号
  335. line += code.split(/\n/).length - 1;
  336. // 压缩多余空白与注释
  337. if (compress) {
  338. code = code
  339. .replace(/\s+/g, ' ')
  340. .replace(/<!--[\w\W]*?-->/g, '');
  341. }
  342. if (code) {
  343. code = replaces[1] + stringify(code) + replaces[2] + "\n";
  344. }
  345. return code;
  346. }
  347. // 处理逻辑语句
  348. function logic (code) {
  349. var thisLine = line;
  350. if (parser) {
  351. // 语法转换插件钩子
  352. code = parser(code, options);
  353. } else if (debug) {
  354. // 记录行号
  355. code = code.replace(/\n/g, function () {
  356. line ++;
  357. return "$line=" + line + ";";
  358. });
  359. }
  360. // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
  361. // <%=#value%> 等同 v2.0.3 之前的 <%==value%>
  362. if (code.indexOf('=') === 0) {
  363. var escapeSyntax = escape && !/^=[=#]/.test(code);
  364. code = code.replace(/^=[=#]?|[\s;]*$/g, '');
  365. // 对内容编码
  366. if (escapeSyntax) {
  367. var name = code.replace(/\s*\([^\)]+\)/, '');
  368. // 排除 utils.* | include | print
  369. if (!utils[name] && !/^(include|print)$/.test(name)) {
  370. code = "$escape(" + code + ")";
  371. }
  372. // 不编码
  373. } else {
  374. code = "$string(" + code + ")";
  375. }
  376. code = replaces[1] + code + replaces[2];
  377. }
  378. if (debug) {
  379. code = "$line=" + thisLine + ";" + code;
  380. }
  381. // 提取模板中的变量名
  382. forEach(getVariable(code), function (name) {
  383. // name 值可能为空,在安卓低版本浏览器下
  384. if (!name || uniq[name]) {
  385. return;
  386. }
  387. var value;
  388. // 声明模板变量
  389. // 赋值优先级:
  390. // [include, print] > utils > helpers > data
  391. if (name === 'print') {
  392. value = print;
  393. } else if (name === 'include') {
  394. value = include;
  395. } else if (utils[name]) {
  396. value = "$utils." + name;
  397. } else if (helpers[name]) {
  398. value = "$helpers." + name;
  399. } else {
  400. value = "$data." + name;
  401. }
  402. headerCode += name + "=" + value + ",";
  403. uniq[name] = true;
  404. });
  405. return code + "\n";
  406. }
  407. };
  408. // 定义模板引擎的语法
  409. defaults.openTag = '{{';
  410. defaults.closeTag = '}}';
  411. var filtered = function (js, filter) {
  412. var parts = filter.split(':');
  413. var name = parts.shift();
  414. var args = parts.join(':') || '';
  415. if (args) {
  416. args = ', ' + args;
  417. }
  418. return '$helpers.' + name + '(' + js + args + ')';
  419. }
  420. defaults.parser = function (code, options) {
  421. // var match = code.match(/([\w\$]*)(\b.*)/);
  422. // var key = match[1];
  423. // var args = match[2];
  424. // var split = args.split(' ');
  425. // split.shift();
  426. code = code.replace(/^\s/, '');
  427. var split = code.split(' ');
  428. var key = split.shift();
  429. var args = split.join(' ');
  430. switch (key) {
  431. case 'if':
  432. code = 'if(' + args + '){';
  433. break;
  434. case 'else':
  435. if (split.shift() === 'if') {
  436. split = ' if(' + split.join(' ') + ')';
  437. } else {
  438. split = '';
  439. }
  440. code = '}else' + split + '{';
  441. break;
  442. case '/if':
  443. code = '}';
  444. break;
  445. case 'each':
  446. var object = split[0] || '$data';
  447. var as = split[1] || 'as';
  448. var value = split[2] || '$value';
  449. var index = split[3] || '$index';
  450. var param = value + ',' + index;
  451. if (as !== 'as') {
  452. object = '[]';
  453. }
  454. code = '$each(' + object + ',function(' + param + '){';
  455. break;
  456. case '/each':
  457. code = '});';
  458. break;
  459. case 'echo':
  460. code = 'print(' + args + ');';
  461. break;
  462. case 'print':
  463. case 'include':
  464. code = key + '(' + split.join(',') + ');';
  465. break;
  466. default:
  467. // 过滤器(辅助方法)
  468. // {{value | filterA:'abcd' | filterB}}
  469. // >>> $helpers.filterB($helpers.filterA(value, 'abcd'))
  470. // TODO: {{ddd||aaa}} 不包含空格
  471. if (/^\s*\|\s*[\w\$]/.test(args)) {
  472. var escape = true;
  473. // {{#value | link}}
  474. if (code.indexOf('#') === 0) {
  475. code = code.substr(1);
  476. escape = false;
  477. }
  478. var i = 0;
  479. var array = code.split('|');
  480. var len = array.length;
  481. var val = array[i++];
  482. for (; i < len; i ++) {
  483. val = filtered(val, array[i]);
  484. }
  485. code = (escape ? '=' : '=#') + val;
  486. // 即将弃用 {{helperName value}}
  487. } else if (template.helpers[key]) {
  488. code = '=#' + key + '(' + split.join(',') + ');';
  489. // 内容直接输出 {{value}}
  490. } else {
  491. code = '=' + code;
  492. }
  493. break;
  494. }
  495. return code;
  496. };
  497. // CommonJs
  498. if (typeof exports === 'object' && typeof module !== 'undefined') {
  499. module.exports = template;
  500. // RequireJS && SeaJS
  501. } else if (typeof define === 'function') {
  502. define(function() {
  503. return template;
  504. });
  505. } else {
  506. this.template = template;
  507. }
  508. })();