农燊高科官方网站
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.

template-debug.js 16 KiB

4 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  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. // 获取变量
  245. function getVariable (code) {
  246. return code
  247. .replace(REMOVE_RE, '')
  248. .replace(SPLIT_RE, ',')
  249. .replace(KEYWORDS_RE, '')
  250. .replace(NUMBER_RE, '')
  251. .replace(BOUNDARY_RE, '')
  252. .split(/^$|,+/);
  253. };
  254. // 字符串转义
  255. function stringify (code) {
  256. return "'" + code
  257. // 单引号与反斜杠转义
  258. .replace(/('|\\)/g, '\\$1')
  259. // 换行符转义(windows + linux)
  260. .replace(/\r/g, '\\r')
  261. .replace(/\n/g, '\\n') + "'";
  262. }
  263. function compiler (source, options) {
  264. var debug = options.debug;
  265. var openTag = options.openTag;
  266. var closeTag = options.closeTag;
  267. var parser = options.parser;
  268. var compress = options.compress;
  269. var escape = options.escape;
  270. var line = 1;
  271. var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
  272. var isNewEngine = ''.trim;// '__proto__' in {}
  273. var replaces = isNewEngine
  274. ? ["$out='';", "$out+=", ";", "$out"]
  275. : ["$out=[];", "$out.push(", ");", "$out.join('')"];
  276. var concat = isNewEngine
  277. ? "$out+=text;return $out;"
  278. : "$out.push(text);";
  279. var print = "function(){"
  280. + "var text=''.concat.apply('',arguments);"
  281. + concat
  282. + "}";
  283. var include = "function(filename,data){"
  284. + "data=data||$data;"
  285. + "var text=$utils.$include(filename,data,$filename);"
  286. + concat
  287. + "}";
  288. var headerCode = "'use strict';"
  289. + "var $utils=this,$helpers=$utils.$helpers,"
  290. + (debug ? "$line=0," : "");
  291. var mainCode = replaces[0];
  292. var footerCode = "return new String(" + replaces[3] + ");"
  293. // html与逻辑语法分离
  294. forEach(source.split(openTag), function (code) {
  295. code = code.split(closeTag);
  296. var $0 = code[0];
  297. var $1 = code[1];
  298. // code: [html]
  299. if (code.length === 1) {
  300. mainCode += html($0);
  301. // code: [logic, html]
  302. } else {
  303. mainCode += logic($0);
  304. if ($1) {
  305. mainCode += html($1);
  306. }
  307. }
  308. });
  309. var code = headerCode + mainCode + footerCode;
  310. // 调试语句
  311. if (debug) {
  312. code = "try{" + code + "}catch(e){"
  313. + "throw {"
  314. + "filename:$filename,"
  315. + "name:'Render Error',"
  316. + "message:e.message,"
  317. + "line:$line,"
  318. + "source:" + stringify(source)
  319. + ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
  320. + "};"
  321. + "}";
  322. }
  323. try {
  324. var Render = new Function("$data", "$filename", code);
  325. Render.prototype = utils;
  326. return Render;
  327. } catch (e) {
  328. e.temp = "function anonymous($data,$filename) {" + code + "}";
  329. throw e;
  330. }
  331. // 处理 HTML 语句
  332. function html (code) {
  333. // 记录行号
  334. line += code.split(/\n/).length - 1;
  335. // 压缩多余空白与注释
  336. if (compress) {
  337. code = code
  338. .replace(/\s+/g, ' ')
  339. .replace(/<!--.*?-->/g, '');
  340. }
  341. if (code) {
  342. code = replaces[1] + stringify(code) + replaces[2] + "\n";
  343. }
  344. return code;
  345. }
  346. // 处理逻辑语句
  347. function logic (code) {
  348. var thisLine = line;
  349. if (parser) {
  350. // 语法转换插件钩子
  351. code = parser(code, options);
  352. } else if (debug) {
  353. // 记录行号
  354. code = code.replace(/\n/g, function () {
  355. line ++;
  356. return "$line=" + line + ";";
  357. });
  358. }
  359. // 输出语句. 编码: <%=value%> 不编码:<%=#value%>
  360. // <%=#value%> 等同 v2.0.3 之前的 <%==value%>
  361. if (code.indexOf('=') === 0) {
  362. var escapeSyntax = escape && !/^=[=#]/.test(code);
  363. code = code.replace(/^=[=#]?|[\s;]*$/g, '');
  364. // 对内容编码
  365. if (escapeSyntax) {
  366. var name = code.replace(/\s*\([^\)]+\)/, '');
  367. // 排除 utils.* | include | print
  368. if (!utils[name] && !/^(include|print)$/.test(name)) {
  369. code = "$escape(" + code + ")";
  370. }
  371. // 不编码
  372. } else {
  373. code = "$string(" + code + ")";
  374. }
  375. code = replaces[1] + code + replaces[2];
  376. }
  377. if (debug) {
  378. code = "$line=" + thisLine + ";" + code;
  379. }
  380. // 提取模板中的变量名
  381. forEach(getVariable(code), function (name) {
  382. // name 值可能为空,在安卓低版本浏览器下
  383. if (!name || uniq[name]) {
  384. return;
  385. }
  386. var value;
  387. // 声明模板变量
  388. // 赋值优先级:
  389. // [include, print] > utils > helpers > data
  390. if (name === 'print') {
  391. value = print;
  392. } else if (name === 'include') {
  393. value = include;
  394. } else if (utils[name]) {
  395. value = "$utils." + name;
  396. } else if (helpers[name]) {
  397. value = "$helpers." + name;
  398. } else {
  399. value = "$data." + name;
  400. }
  401. headerCode += name + "=" + value + ",";
  402. uniq[name] = true;
  403. });
  404. return code + "\n";
  405. }
  406. };
  407. // 定义模板引擎的语法
  408. defaults.openTag = '{{';
  409. defaults.closeTag = '}}';
  410. var filtered = function (js, filter) {
  411. var parts = filter.split(':');
  412. var name = parts.shift();
  413. var args = parts.join(':') || '';
  414. if (args) {
  415. args = ', ' + args;
  416. }
  417. return '$helpers.' + name + '(' + js + args + ')';
  418. }
  419. defaults.parser = function (code, options) {
  420. code = code.replace(/^\s/, '');
  421. var split = code.split(' ');
  422. var key = split.shift();
  423. var args = split.join(' ');
  424. switch (key) {
  425. case 'if':
  426. code = 'if(' + args + '){';
  427. break;
  428. case 'else':
  429. if (split.shift() === 'if') {
  430. split = ' if(' + split.join(' ') + ')';
  431. } else {
  432. split = '';
  433. }
  434. code = '}else' + split + '{';
  435. break;
  436. case '/if':
  437. code = '}';
  438. break;
  439. case 'each':
  440. var object = split[0] || '$data';
  441. var as = split[1] || 'as';
  442. var value = split[2] || '$value';
  443. var index = split[3] || '$index';
  444. var param = value + ',' + index;
  445. if (as !== 'as') {
  446. object = '[]';
  447. }
  448. code = '$each(' + object + ',function(' + param + '){';
  449. break;
  450. case '/each':
  451. code = '});';
  452. break;
  453. case 'echo':
  454. code = 'print(' + args + ');';
  455. break;
  456. case 'print':
  457. case 'include':
  458. code = key + '(' + split.join(',') + ');';
  459. break;
  460. default:
  461. // 过滤器(辅助方法)
  462. // {{value | filterA:'abcd' | filterB}}
  463. // >>> $helpers.filterB($helpers.filterA(value, 'abcd'))
  464. if (args.indexOf('|') !== -1) {
  465. var escape = options.escape;
  466. // {{#value | link}}
  467. if (code.indexOf('#') === 0) {
  468. code = code.substr(1);
  469. escape = false;
  470. }
  471. var i = 0;
  472. var array = code.split('|');
  473. var len = array.length;
  474. var pre = escape ? '$escape' : '$string';
  475. var val = pre + '(' + array[i++] + ')';
  476. for (; i < len; i ++) {
  477. val = filtered(val, array[i]);
  478. }
  479. code = '=#' + val;
  480. // 即将弃用 {{helperName value}}
  481. } else if (template.helpers[key]) {
  482. code = '=#' + key + '(' + split.join(',') + ');';
  483. // 内容直接输出 {{value}}
  484. } else {
  485. code = '=' + code;
  486. }
  487. break;
  488. }
  489. return code;
  490. };
  491. // RequireJS && SeaJS
  492. if (typeof define === 'function') {
  493. define(function() {
  494. return template;
  495. });
  496. // NodeJS
  497. } else if (typeof exports !== 'undefined') {
  498. module.exports = template;
  499. } else {
  500. this.template = template;
  501. }
  502. })();