UNPKG

lite

Version:

A cross platform template engine base on xml/html and javascript expression.

762 lines (702 loc) 22.4 kB
/* * List Template * License LGPL(您可以在任何地方免费使用,但请不要吝啬您对框架本身的改进) * http://www.xidea.org/project/lite/ * @author jindw * @version $Id: template.js,v 1.4 2008/02/28 14:39:06 jindw Exp $ */ var ELSE_TYPE=require('./template-token').ELSE_TYPE; var TranslateContext=require('./translate-context').TranslateContext; var Expression=require('js-el').Expression; var GLOBAL_DEF_MAP ={ "parseInt":1, "parseFloat":1, "encodeURIComponent":1, "decodeURIComponent":1, "encodeURI":1, "decodeURI":1, "isFinite":1, "isNaN":1 }; var GLOBAL_VAR_MAP ={ "JSON":1, "Math":1 } var ID_EXP = /^[a-z\$\_][\w\$]*$/ copy(GLOBAL_DEF_MAP,GLOBAL_VAR_MAP); /** * @param config {waitPromise:true,liteImpl:'liteImpl'} * JS原生代码翻译器实现 */ function JSTranslator(config){ this.config = config||{}; } /** * <code> function(__context__,__out__){ //gen function __x__(){} //gen defs function def(p1,pe){ return [p1,'+',p2].join('') } function def(arg1){ var __out__ = []; if(arg1){ __out__.push('[',arg1,']'); } return __out__.join(''); } function def2(){arg2}{ return '['+arg2+']'; } //gen output1 //return function(){ //gen model vars var $var1 = __context__.var1; var $var2 = __context__.var2; var $var3 = __context__.var3; __out__.push($var1.x,$var2.y); if($var3){ __out__.push($var3) } __out__.push($var3) .... return __out__.join(''); //}() //gen output2 //return function*(){ //gen model vars var $var1 = __context__.var1; var $var2 = __context__.var2; var $var3 = __context__.var3; var1 = yield var1; var2 = yield var2; __out__.push(var1.x,var2.y); var3 = yield var3; if(var3){ __out__.push(var3) } __out__.push(var3) //bigpiple 输出 __out__.push(function* __lazy__id__(__out__){ if(var1){ __out__.push(var1) } __out__.push(var1) }); __out__.push('<div id="__lazy__id__" style="display:block" class="lazy"></div>'); .... //return __out__.join('');//__out__.end(); } } */ JSTranslator.prototype = { /** * @param list * @param config {name:'functionName',params:['arg1','arg2'],defaults:['arg2value']} */ translate:function(list,config){ config = config||{}; var params = config.params; var functionName = config.name||''; //console.log('js translate:',list) var ctx = new JSTranslateContext(list,params,config.defaults); var translatorConfig = this.config || {}; var liteImpl = translatorConfig.liteImpl if(translatorConfig.waitPromise){ var tops = []; var scope = ctx.scope; var vars = {}; copy(scope.varMap,vars); copy(scope.callMap,vars); tops.push.apply(tops,Object.keys(vars)) ctx.waitPromise = [tops]; } ctx.hasBuildIn = !!liteImpl; ctx.liteImpl = liteImpl && (typeof liteImpl == 'string'?liteImpl:'liteImpl'); ctx.parse(); var functionPrefix = genFunctionPrefix(ctx,functionName) var code = genSource(ctx,functionPrefix);//ctx.header + ctx.body; //console.log('###'+code+'@@@') try{ var fn = new Function('return '+code); //if(params ==null|| params.length == 0) { var scope = ctx.scope; var refMap = scope.refMap; var varMap = scope.varMap; var externalRefs = Object.keys(refMap).filter(function(n){return !(n in varMap)}) if(externalRefs == 0 && params){ return functionPrefix+'(){return '+JSON.stringify(fn()()) + '}' } //} }catch(e){ var error = ["invalid code:",e,'<code>'+code+'</code>'].join(''); console.log(error) code = functionPrefix+'(){return '+JSON.stringify(error)+';}'; } return code.replace(/<\/script>/g,'<\\/script>').replace(/^\s*[\r\n]+/,''); } } function genFunctionPrefix(ctx,functionName){ var fnkey = ctx.waitPromise?'function*':'function' if(functionName.match(ID_EXP)){ return fnkey+' '+functionName; }else if(functionName.match(/[\.\[]/)){ return functionName+'='+fnkey }else{ return fnkey } } //function n2(v){if(p[0]){p[0] = p[0](v)};__n__()} function genSource(ctx,functionPrefix){ var header = ctx.header; var body = ctx.body; var params = ctx.params var args = params?params.join(','):'__context__,__out__'; var result = [functionPrefix,"(",args,'){\n',header,'\n'] if(params){ var m = body.match(/^\s*__out__\.push\((.*?)\);?\s*$/) if(m){ var item = '\treturn ['+m[1]+']' try{ new Function(item) if(item.indexOf(',')>0){ result.push(item,'.join("");\n}') }else{ result.push('\treturn ',m[1],';\n}'); } return result.join(''); }catch(e){} } result.push('\tvar __out__ = [];\n'); }else{ result.push('\tvar __out__ = __out__||[];\n'); } result.push(body,'\n\treturn __out__.join("");\n}\n'); return result.join(''); } /** * 增加默认参数值支持。defaults */ function genDecFunction(contents,functionName,params,defaults,modelVarsDec){ var modelVarsDecAndParams = modelVarsDec.concat(); //生成参数表 var args = params?params.join(','):'__context__'; if(params && defaults && defaults.length){ //处理默认参数 modelVarsDecAndParams.push('\tswitch(arguments.length){\n'); var begin = params.length - defaults.length for(var i =0;i<params.length;i++){ modelVarsDecAndParams.push('\t case ',i,':\n'); if(i>=begin){ modelVarsDecAndParams.push('\t ',params[i],'=',JSON.stringify(defaults[i-begin]),';\n'); } } modelVarsDecAndParams.push('\t}\n'); } //优化内容(合并join 串) var source = contents.join('') var SP = /^\s*\__out__\.push\((?:(.*)\)\s*;?)\s*$/g; if(SP.test(source)){ var c =source.replace(SP,'$1'); if(c.indexOf(',')>0){ //安全方式吧. source = "\treturn ["+c+"].join('');"; }else{ source = "\treturn "+c+';'; } }else{ source = "\tvar __out__=[]\n"+source.replace(/^\s*\n|\s*\n\s*$/g,'')+"\n\treturn __out__.join('');\n"; } return 'function '+functionName+"("+args+'){\n'+modelVarsDecAndParams.join('')+source.replace(/^[\r\n]+/,'')+'\n}\n' } function genModelDecVars(ctx,scope,params){ var result = []; var map = {}; var refMap = scope.externalRefMap; var callMap = scope.callMap; var varMap = scope.varMap; var paramMap = scope.paramMap; //console.log('genModelDecVars::::',params) copy(refMap,map); //copy(callMap,map); var vars = []; for(var n in map){ if(n != '*' && !((n in GLOBAL_VAR_MAP)|| (n in varMap) || (n in paramMap))){ if(params){//no __context__ //result.push('\tvar ',n,'=',ctx.liteImpl,'["',n,'"];\n'); }else{ //result.push('\tvar ',n,'=("',n,'" in __context__? __context__:',ctx.liteImpl,')["',n,'"];\n'); result.push('\tvar ',n,'=("',n,'" in __context__?__context__:this)["',n,'"];\n'); vars.push(n); } } } return result; } /** * 构建内容头部 */ function genBuildInSource(ctx){ if(ctx.hasBuildIn){return ''} var buf = ['']; var c = ctx.xmlEncoder + ctx.entityEncoder*2; if(c){ if(c>3){ ctx.optimizedEncoder = true; buf.push(" function __x__(source,e){return String(source).replace(e||/&(?!#\\d+;|#x[\\da-f]+;|[a-z]+;)|[<\"]/ig,function(c){return '&#'+c.charCodeAt()+';'});}\n"); }else{ buf.push(" function __r__(c){return '&#'+c.charCodeAt()+';'}\n"); } } if(ctx.safeGetter){ buf.push(' function __get__(o,p,a){try{return a?o[p].apply(o,a):o[p]}catch(e){return e}}\n') } //if(ctx.entityEncoder){buf.push( 'var __e__ = __x__;\n');} if(ctx.forStack.hit){ //ie7,ie8 buf.push(" if(!Object.keys)Object.keys=function(o){var r=[];for(var n in o){r.push(n)};return r;};\n") } var df = ctx.dateFormat; if(df.hit){ var dlstart = df.isFixLen?'__dl__(':'' var dlend = df.isFixLen?',format.length)':'' if(dlstart) buf.push(" function __dl__(date,len){return len > 1? ('000'+date).slice(-len):date;}\n"); if(df.T) buf.push(" function __tz__(offset){return offset?(offset>0?'-':offset*=-1,'+')+__dl__(offset/60,2)+':'+__dl__(offset%60,2):'Z'}\n"); if(df) buf.push(" function __df__(pattern,date){\n"); if(df) buf.push(" date = date?new Date(date):new Date();\n"); if(df) buf.push(" return pattern.replace(/", df.qute?"'[^']+'|\"[^\"]+\"|":'', "([YMDhms])\\1*", df['.']?"|\\.s":'', df.T?"|TZD$":'', "/g,function(format){\n"); if(df) buf.push(" switch(format.charAt()){\n"); if(df.Y) buf.push(" case 'Y' :return ",dlstart,"date.getFullYear()",dlend,";\n"); if(df.M) buf.push(" case 'M' :return ",dlstart,"date.getMonth()+1",dlend,";\n"); if(df.D) buf.push(" case 'D' :return ",dlstart,"date.getDate()",dlend,";\n"); if(df.h) buf.push(" case 'h' :return ",dlstart,"date.getHours()",dlend,";\n"); if(df.m) buf.push(" case 'm' :return ",dlstart,"date.getMinutes()",dlend,";\n"); if(df.s) buf.push(" case 's' :return ",dlstart,"date.getSeconds()",dlend,";\n"); if(df['.']) buf.push(" case '.':return '.'+",dlstart,"date.getMilliseconds(),3);\n"); if(df.T) buf.push(" case 'T':return __tz__(date.getTimezoneOffset());\n"); if(df.qute) buf.push(" case '\'':case '\"':return format.slice(1,-1);\n"); if(df) buf.push(" default :return format;\n"); if(df) buf.push(" }\n"); if(df) buf.push(" });\n"); if(df) buf.push(" }\n"); } return buf.join(''); } function createDateFormat(ctx,pattern,date){ var df = ctx.dateFormat; var patternSample=pattern[1]; var maxLen = 0; if(pattern[0] != -1){//非常量,JSEL:VALUE_CONSTANTS patternSample='YYMMDDhhmmss.sTZD'; } patternSample.replace(/([YMDhms])\1*|\.s|TZD/g,function(c){ var len = c.length; c = c.charAt(); if(c == '"' || c== '\''){ df.qute = 1; } maxLen = Math.max(maxLen,df[c]=Math.max(df[c]||0,len)); }) //变量 ,JSEL:VALUE_VAR df.isEL = df.isEL || date[0] != -2; df.isFixLen = df.isFixLen || maxLen>1; df.hit ++; pattern = ctx.stringifyEL(pattern); date = ctx.stringifyEL(date) return {toString:function(){ return '__df__('+pattern+','+date+')'; }} } function JSTranslateContext(code,params,defaults){ TranslateContext.call(this,code,params,defaults); this.forStack = []; this.defaults = defaults; this.xmlEncoder = 0; this.entityEncoder=0; this.dateFormat = {hit:0}; this.safeGetter = {hit:0} } JSTranslateContext.prototype = new TranslateContext(); JSTranslateContext.prototype.parse=function(){ var params = this.params; this.out = []; //add function var defs = this.scope.defs; var thiz = this; var defVars = [] //生成函数定义 var waitPromise = this.waitPromise; this.waitPromise = null;//函数内部不能用wait Promise for(var i=0;i<defs.length;i++){ var def = this.scope.defMap[defs[i]]; this.outputIndent=1; this.appendCode(def.code); var vars = genModelDecVars(this,def,def.params); var contents = thiz.reset(); //添加一个函数 defVars.push({ params:def.params, defaults:def.defaults, contents:contents, vars:vars, name:def.name, toString:function(){ var fn = genDecFunction(this.contents,this.name,this.params,this.defaults,[]); return String(fn).replace(/^(.)/mg,'\t$1'); }}); } this.waitPromise = waitPromise; try{ this.outputIndent=0; this.outputIndent++; this.appendCode(this.scope.code); this.outputIndent--; }catch(e){ //alert(["编译失败:",buf.join(""),this.scope.code]) throw e; } //放在后面,这时 如下信息是正确的! // this.xmlEncoder = 0; //this.entityEncoder=0; //this.dateFormat = {hit:0}; //this.forStack.hit = true if(this.waitPromise){ this.waitPromise[this.waitPromise.length-1].push() } var headers = []; var headers = genModelDecVars(this,this.scope,this.params); var buildIn = genBuildInSource(this); if(buildIn){ headers.unshift(buildIn); } this.header = headers.concat(defVars).join(''); //vars.unshift(fs.join('')); this.body = this.reset().join('').replace(/^[\r\n]+/,'')////;optimizeFunction(this.reset(),this.name,this.params,this.defaults,vars.concat(defVars)); } JSTranslateContext.prototype.appendStatic = function(item){ appendOutput(this,JSON.stringify(item)); } JSTranslateContext.prototype.appendEL=function(item){ appendOutput(this,this.stringifyEL(item[1])) } JSTranslateContext.prototype.appendXT=function(item){ appendOutput(this,createXMLEncoder(this,item[1])) } JSTranslateContext.prototype.appendXA=function(item){ //[7,[[0,"value"]],"attribute"] var el = item[1]; var value = this.stringifyEL(el); var attributeName = item.length>2 && item[2]; if(attributeName){ var testId = this.allocateId(value); if(testId != value){ el = new Expression(testId).token; this.append("var ",testId,"=",value,';'); } this.append("if(",testId,"!=null){"); this.pushBlock(); appendOutput(this,'" '+attributeName+'=\'"',createXMLEncoder(this,el,true),"\"'\""); //appendOutput(this,"' "+attributeName+"=\"'",createXMLEncoder(this,el,true),"'\"'"); this.popBlock(); this.append("}"); this.freeId(testId); }else{ appendOutput(this,createXMLEncoder(this,el,true)) } } JSTranslateContext.prototype.appendVar=function(item){ this.append("var ",item[2],"=",this.stringifyEL(item[1]),";"); }, JSTranslateContext.prototype.appendEncodePlugin=function(item){//&#233;&#0xDDS; appendOutput(this,createEntityEncoder(this,item[1])); }, JSTranslateContext.prototype.appendDatePlugin=function(pattern,date){//&#233;&#0xDDS; appendOutput(this,createDateFormat(this,pattern[1],date[1])) } JSTranslateContext.prototype.processCapture = function(item){ var childCode = item[1]; if(childCode.length == 1 && childCode[0].constructor == String){ item[1] = JSON.stringify(childCode[0]); this.appendVar(item); }else{ var varName = item[2]; var bufbak = this.allocateId(); this.append("var ",bufbak,"=__out__;__out__=[];") this.appendCode(childCode); this.append("var ",varName,"=__out__.join('');__out__=",bufbak,";"); this.freeId(bufbak); } }, JSTranslateContext.prototype.processIf=function(code,i){ var item = code[i]; var childCode = item[1]; var testEL = item[2]; var test = this.stringifyEL(testEL); var wel = genWaitEL(this,testEL); //visited el intercept function call this.append('if(',wel?'('+wel+',1)&&('+test+')':test,'){'); this.pushBlock(); this.appendCode(childCode) this.popBlock(); this.append("}"); var nextElse = code[i+1]; var notEnd = true; this.pushBlock(true); var nestedElseIf = 0; while(nextElse && nextElse[0] == ELSE_TYPE){ i++; var childCode = nextElse[1]; var testEL = nextElse[2]; var test = this.stringifyEL(testEL); if(test){ this.pushBlock(true); nestedElseIf++; var wel = genWaitEL(this,testEL); this.append('else if(',wel?'('+wel+',1)&&('+test+')':test,'){'); }else{ notEnd = false; this.append("else{"); } this.pushBlock(); this.appendCode(childCode) this.popBlock(); this.append("}"); nextElse = code[i+1]; } while(nestedElseIf--){ this.popBlock(true); } this.popBlock(true); return i; } JSTranslateContext.prototype.processFor=function(code,i){ this.forStack.hit = true; var item = code[i]; var indexId = this.allocateId(); var lastIndexId = this.allocateId(); var itemsId = this.allocateId(); var itemsEL = this.stringifyEL(item[2]); var varNameId = item[3]; //var statusNameId = item[4]; var childCode = item[1]; var forInfo = this.findForStatus(item) //初始化 items 开始 this.append("var ",itemsId,'=',itemsEL,';'); this.append("var ",indexId,"=0;") this.append("var ",lastIndexId," = (", itemsId,'=',itemsId,' instanceof Array?',itemsId,':',itemsId,' instanceof Object ?Object.keys(',itemsId,'):Array(',itemsId,')' ,").length-1;"); //初始化 for状态 var forRef = forInfo.ref ; var forAttr = forInfo.index || forInfo.lastIndex; if(forRef){ var statusId = this.allocateId(); this.forStack.unshift([statusId,indexId,lastIndexId]); this.append("var ",statusId," = {lastIndex:",lastIndexId,"};"); }else if(forAttr){ this.forStack.unshift(['for',indexId,lastIndexId]); } this.append("for(;",indexId,"<=",lastIndexId,";",indexId,"++){"); this.pushBlock(); if(forRef){ this.append(statusId,".index=",indexId,";"); } this.append("var ",varNameId,"=",itemsId,"[",indexId,"];"); this.appendCode(childCode); this.popBlock(); this.append("}"); var nextElse = code[i+1]; var notEnd = true; var elseIndex = 0; var nestedElseIf = 0 this.pushBlock(true); while(notEnd && nextElse && nextElse[0] == ELSE_TYPE){ i++; elseIndex++; var childCode = nextElse[1]; var testEL = nextElse[2]; var test = this.stringifyEL(testEL); var ifstart = elseIndex >1 ?'else if' :'if'; if(test){ this.pushBlock(true); nestedElseIf++; var wel = genWaitEL(this,testEL); this.append(ifstart,'(', '!',indexId, wel?'&&('+wel+',1)':'', '&&(',test,')){'); }else{ notEnd = false; this.append(ifstart,"(!",indexId,"){"); } this.pushBlock(); this.appendCode(childCode) this.popBlock(); this.append("}"); nextElse = code[i+1]; } while(nestedElseIf--){ this.popBlock(true); } this.popBlock(true); if(forRef){ this.freeId(statusId); this.forStack.shift(); }else if(forAttr){ this.forStack.shift(); } this.freeId(lastIndexId); this.freeId(itemsId);; this.freeId(indexId); return i; } JSTranslateContext.prototype.pushBlock = function(ignoreIndent){ if(!ignoreIndent){ this.outputIndent++ } var waitPromise = this.waitPromise; if(waitPromise){ var topStatus = waitPromise[waitPromise.length-1] waitPromise.push(topStatus?topStatus.concat():[]) } } JSTranslateContext.prototype.popBlock = function(ignoreIndent){ if(!ignoreIndent){ this.outputIndent--; } if(this.waitPromise){ this.waitPromise.pop() } } JSTranslateContext.prototype.appendModulePlugin = function(child,config){ if(this.waitPromise){ this.append('__out__.push(function* __widget_',config.id,'(__out__){'); this.pushBlock();//TODO:lazy push, 最后执行的元素可以最后检测waitEL this.appendCode(child) this.popBlock(); this.append('})'); }else{ this.appendCode(child) } } JSTranslateContext.prototype.stringifyEL= function (el){ return el?new Expression(el).toString(this):null; }; JSTranslateContext.prototype.visitEL= function (el,type){ el = el && genWaitEL(this,el); el && this.append(el); }; JSTranslateContext.prototype.getVarName = function(name){ if(name == 'for'){ console.error('invalue getVarName:') //throw new Error() } return name; }; JSTranslateContext.prototype.getForName = function(){ var f = this.forStack[0]; //console.log('getForName:',f[0]) return f && f[0]; }; JSTranslateContext.prototype.genGetCode = function(owner,property){ //safe if(this.safeGetter){ this.safeGetter.hit = true; return '__get__('+owner+','+property+')' }else{ //fast if(/^"[a-zA-Z_\$][_\$\w]*"$/.test(property)){ return owner+'.'+property.slice(1,-1); }else{ return owner+'['+property+']'; } } }; JSTranslateContext.prototype.findForAttribute= function(forName,forAttribute){ var stack = this.forStack; var index = forAttribute == 'index'?1:(forAttribute == 'lastIndex'?2:0); for(var i=0;index && i<stack.length;i++){ var s = stack[i]; if(s && s[0] == forName){ return s[index]; } } } function genWaitEL(ctx,el){ if(ctx.waitPromise){ var topWaitedVars = ctx.waitPromise[ctx.waitPromise.length-1]; if(topWaitedVars){ var vars = Object.keys(new Expression(el).getVarMap()); var vars2 = []; for(var i=0;i<vars.length;i++){ var v = vars[i]; if(v != 'for' && topWaitedVars.indexOf(v)<0){ vars2.push(v) topWaitedVars.push(v) } } if (vars2.length) { return vars2.join(',').replace(/[^,]+/g,'$&=yield $&') //return 'yield* __out__.wait('+vars2.join(',')+')' }; } } } function appendOutput(ctx){ //console.log('!!!'+JSON.stringify(arguments)) var outList = ctx.out; var lastIndex = outList.length-1; var args = [].splice.call(arguments,1,arguments.length-1); var lastOutGroup = ctx._lastOutGroup;//不能用闭包var代替 if(lastOutGroup && outList[lastIndex] === lastOutGroup){ lastOutGroup.list.push.apply(lastOutGroup.list,args) }else{ ctx.append(ctx._lastOutGroup = new OutputGroup(args)); } //console.log('!!!'+ctx._lastOutGroup.list) //console.log('###'+args); } function OutputGroup(args){ this.list = args; } OutputGroup.prototype.toString = function(){ return '__out__.push('+this.list.join(',')+');' } function createXMLEncoder(thiz,el,isAttr){ thiz.xmlEncoder ++; el = thiz.stringifyEL(el); return {toString:function(){ var e = (isAttr?"/[&<']/g":'/[&<]/g'); if(thiz.optimizedEncoder||thiz.hasBuildIn){ return '__x__('+el+','+e+')'; }else{ return 'String('+el+').replace('+e+',__r__)' } }} } function createEntityEncoder(thiz,el){ el = thiz.stringifyEL(el); thiz.entityEncoder ++; return { toString:function(){ if(thiz.optimizedEncoder || thiz.hasBuildIn){ return '__x__('+el+')'; }else{ return 'String('+el+').replace(/&(?!#\\d+;|#x[\\da-f]+;|[a-z]+;)|[<"]/ig,__r__)' } }} } function copy(source,target){ for(var n in source){ target[n] = source[n]; } } exports.JSTranslator=JSTranslator; exports.GLOBAL_DEF_MAP=GLOBAL_DEF_MAP; exports.GLOBAL_VAR_MAP=GLOBAL_VAR_MAP;