UNPKG

ti

Version:

A simple MVC framework for node web server.

502 lines (378 loc) 11.4 kB
var _cache = {}, _helpers = {}, _plugins={}, _isNewEngine = ''.trim, fs = require('fs'); /** * 前后标志符 */ exports.openTag = '<%'; exports.closeTag = '%>'; exports.templatePath = process.cwd(); // * // * 渲染模板 // * @name template.render // * @param {String} 模板ID // * @param {Object} 数据 // * @return {String} 渲染好的HTML字符串 exports.render = function (id, data,debug) { var cache = _getCache(id,debug); if (cache === undefined) { return _debug({ id: id, name: 'Render Error', message: 'Not get template' }); } return cache(data); }; /** * 编译模板 * @name template.compile * @param {String} 模板ID (可选) * @param {String} 模板字符串 * @return {Function} 渲染方法 */ exports.compile = function (id, source) { var debug = arguments[2]; if (typeof source !== 'string') { debug = source; source = id; id = null; } try { var cache = _compile(source, debug); } catch (e) { e.id = id || source; e.name = 'Syntax Error'; return _debug(e); } function render (data) { try { return cache.call(_helpers,data); } catch (e) { if (!debug) { return exports.compile(id, source, true)(data); } e.id = id || source; e.name = 'Render Error'; e.source = source; return _debug(e); }; }; render.toString = function () { return cache.toString(); }; if (id) { _cache[id] = render; } return render; }; /** * 扩展模板辅助方法 * @name template.helper * @param {String} 名称 * @param {Function} 方法 */ exports.helper = function (name, helper) { if (helper === undefined) { return _helpers[name]; } else { _helpers[name] = helper; } }; /** * 扩展模板插件方法 * @name template.helper * @param {String} 名称 * @param {Function} 方法 */ exports.plugin = function (name, plugin) { if (plugin === undefined) { return _plugins[name]; } else { _plugins[name] = plugin; } }; // 模板编译器 var _compile = function (source, debug) { var openTag = exports.openTag; var closeTag = exports.closeTag; var parser = exports.parser; var code = source; var tempCode = ''; var line = 1; var outKey = {}; var uniq = {$out:true,$line:true}; var variables = "var $helpers=this," + (debug ? "$line=0," : ""); var replaces = _isNewEngine ? ["$out='';", "$out+=", ";", "$out"] : ["$out=[];", "$out.push(", ");", "$out.join('')"]; var include = "function(id,data){" + "if(data===undefined){data=$data}" + "return $helpers.$render(id,data)" + "}"; // html与逻辑语法分离 _forEach.call(code.split(openTag), function (code, i) { code = code.split(closeTag); var $0 = code[0]; var $1 = code[1]; // code: [html] if (code.length === 1) { tempCode += html($0); // code: [logic, html] } else { tempCode += logic($0); if ($1) { tempCode += html($1); } } }); code = tempCode; // 调试语句 if (debug) { code = 'try{' + code + '}catch(e){' + 'e.line=$line;' + 'throw e' + '}'; } code = variables + replaces[0] + code + 'return ' + replaces[3]; try { return new Function('$data', code); } catch (e) { e.temp = 'function anonymous($data) {' + code + '}'; throw e; }; // 处理 HTML 语句 function html (code) { // 记录行号 line += code.split(/\n/).length - 1; code = code // 单双引号与反斜杠转义 .replace(/('|"|\\)/g, '\\$1') // 换行符转义(windows + linux) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n'); code = replaces[1] + "'" + code + "'" + replaces[2]; return code + '\n'; }; // 处理逻辑语句 function logic (code) { var thisLine = line; if (parser) { // 语法转换器 code = parser(code); } else if (debug) { // 记录行号 code = code.replace(/\n/g, function () { line ++; return '$line=' + line + ';'; }); } // 输出语句 if (code.indexOf('=') === 0) { //添加插件方法 var _scode = code.substring(1).replace(/[\s;]*$/, ''), vars,_plugin, trueCode, plugingFlag = _scode.split('|'), i=1,l=plugingFlag.length; if(l){ //变量 _scode = plugingFlag[0]; for(;i<l;i++){ _plugin = plugingFlag[i].split(":"); fn = _plugin[0]; vars = _plugin.slice(1); vars.unshift(_scode); vars.unshift('"'+fn+'"'); _scode = '$plugins('+vars.join(',')+')'; } trueCode = _scode; }else{ trueCode = _scode; } code = replaces[1] + (_isNewEngine ? '$getValue(' : '') + trueCode + (_isNewEngine ? ')' : '') + replaces[2]; } if (debug) { code = '$line=' + thisLine + ';' + code; } getKey(code); return code + '\n'; }; // 提取模板中的变量名 function getKey (code) { // 过滤注释、字符串、方法名 code = code.replace(/\/\*.*?\*\/|'[^']*'|"[^"]*"|\.[\$\w]+/g, ''); // 分词 _forEach.call(code.split(/[^\$\w\d]+/), function (name) { // 沙箱强制语法规范:禁止通过套嵌函数的 this 关键字获取全局权限 if (/^(this|\$helpers)$/.test(name)) { throw { message: 'Prohibit the use of the "' + name +'"' }; } // 过滤关键字与数字 if (!name || _keyWordsMap[name] || /^\d/.test(name)) { return; } // 除重 if (!uniq[name]) { setValue(name); uniq[name] = true; } }); }; // 声明模板变量 // 赋值优先级: 内置特权方法(include) > 公用模板方法 > 数据 function setValue (name) { var value; if (name === 'include') { value = include; } else if (_helpers[name]) { value = '$helpers.' + name; } else { value = '$data.' + name; } variables += name + '=' + value + ','; }; }; // 获取模板缓存 var _getCache = function (id,debug) { id = exports.templatePath+'/'+id; var cache = _cache[id]; if (cache === undefined ) { try{ var code = fs.readFileSync(id ,'utf8');; if (code) { exports.compile(id, code,debug); } }catch(e){ _cache[id] = ''; console.log(e); } return _cache[id]; } return cache; }; // 模板调试器 var _debug = function (e) { var content = '[template]:\n' + e.id + '\n\n[name]:\n' + e.name; if (e.message) { content += '\n\n[message]:\n' + e.message; } if (e.line) { content += '\n\n[line]:\n' + e.line; content += '\n\n[source]:\n' + e.source.split(/\n/)[e.line - 1].replace(/^[\s\t]+/, ''); } if (e.temp) { content += '\n\n[temp]:\n' + e.temp; } if (console) { console.error(content); } function error () { return error + ''; }; error.toString = function () { return '{Template Error}'; }; return error; }; // 数组迭代方法 var _forEach = Array.prototype.forEach || function (block, thisObject) { var len = this.length >>> 0; for (var i = 0; i < len; i++) { if (i in this) { block.call(thisObject, this[i], i, this); } } }; // javascript 关键字表 var _keyWordsMap = {}; _forEach.call(( // 关键字 'break,case,catch,continue,debugger,default,delete,do,else,false,finally,for,function,if' + ',in,instanceof,new,null,return,switch,this,throw,true,try,typeof,var,void,while,with' // 保留字 + ',abstract,boolean,byte,char,class,const,double,enum,export,extends,final,float,goto' + ',implements,import,int,interface,long,native,package,private,protected,public,short' + ',static,super,synchronized,throws,transient,volatile' // ECMA 5 - use strict + ',arguments,let,yield' ).split(','), function (key) { _keyWordsMap[key] = true; }); // 模板私有辅助方法 exports.helper('$forEach', _forEach); exports.helper('$render', exports.render); exports.helper('$getValue', function (value) { return value === undefined ? '' : value; }); //插件私有方法 exports.helper('$plugins',function(){ var args = Array.prototype.slice.call(arguments,0), name = args[0]; if(_plugins[name]){ return _plugins[name].apply(this,args.slice(1)); } }); /* * 常规通用插件 */ //截字 exports.plugin('truncate',function(str,num,buf){ buf = buf||'...'; if(str.length>num){ return str.substring(0,num)+buf; }else{ return str; } }); //encode plugin var htmlDecodeDict = { "quot": '"', "lt": "<", "gt": ">", "amp": "&", "nbsp": " " }; var htmlEncodeDict = { '"': "quot", "<": "lt", ">": "gt", "&": "amp", " ": "nbsp" }; exports.plugin('encode',function(str,type){ //html encode if(type === 'html'){ return String(str).replace(/["<>& ]/g, function(all) { return "&" + htmlEncodeDict[all] + ";"; }); }else if(type === 'url'){ return encodeURIComponent(String(str)); }else{ return str; } }); //decode plugin exports.plugin('decode',function(str,type){ if(type==='html'){ return String(str).replace(/["<>& ]/g, function(all) { return "&" + htmlEncodeDict[all] + ";"; }); }else if(type==='url'){ return decodeURIComponent(String(str)); }else{ return str; } }); exports.plugin('replace',function(str,parten,replacer){ return str.replace(parten,replacer); }); exports.plugin('default',function(str,val){ if(str==="") return val; return str; });