/** * ETPL (Enterprise Template) * Copyright 2013 Baidu Inc. All rights reserved. * * @file 模板引擎 * @author errorrik(errorrik@gmail.com) * otakustay(otakustay@gmail.com) */ // 有的正则比较长,所以特别放开一些限制 /* jshint maxdepth: 10, unused: false, white: false */ // HACK: 可见的重复代码未抽取成function和var是为了gzip size,吐槽的一边去 (function (root) { /** * 对象属性拷贝 * * @inner * @param {Object} target 目标对象 * @param {Object} source 源对象 * @return {Object} 返回目标对象 */ function extend( target, source ) { for ( var key in source ) { if ( source.hasOwnProperty( key ) ) { target[ key ] = source[ key ]; } } return target; } /** * 随手写了个栈 * * @inner * @constructor */ function Stack() { this.raw = []; this.length = 0; } Stack.prototype = { /** * 添加元素进栈 * * @param {*} elem 添加项 */ push: function ( elem ) { this.raw[ this.length++ ] = elem; }, /** * 弹出顶部元素 * * @return {*} */ pop: function () { if ( this.length > 0 ) { var elem = this.raw[ --this.length ]; this.raw.length = this.length; return elem; } }, /** * 获取顶部元素 * * @return {*} */ top: function () { return this.raw[ this.length - 1 ]; }, /** * 获取底部元素 * * @return {*} */ bottom: function () { return this.raw[ 0 ]; }, /** * 根据查询条件获取元素 * * @param {Function} condition 查询函数 * @return {*} */ find: function ( condition ) { var index = this.length; while ( index-- ) { var item = this.raw[ index ]; if ( condition( item ) ) { return item; } } } }; /** * 唯一id的起始值 * * @inner * @type {number} */ var guidIndex = 0x2B845; /** * 获取唯一id,用于匿名target或编译代码的变量名生成 * * @inner * @return {string} */ function generateGUID() { return '___' + (guidIndex++); } /** * 构建类之间的继承关系 * * @inner * @param {Function} subClass 子类函数 * @param {Function} superClass 父类函数 */ function inherits( subClass, superClass ) { var F = new Function(); F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; // 由于引擎内部的使用场景都是inherits后,逐个编写子类的prototype方法 // 所以,不考虑将原有子类prototype缓存再逐个拷贝回去 } /** * HTML Filter替换的字符实体表 * * @const * @inner * @type {Object} */ var HTML_ENTITY = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }; /** * HTML Filter的替换函数 * * @inner * @param {string} c 替换字符 * @return {string} */ function htmlFilterReplacer( c ) { return HTML_ENTITY[ c ]; } /** * 默认filter * * @inner * @const * @type {Object} */ var DEFAULT_FILTERS = { /** * HTML转义filter * * @param {string} source 源串 * @return {string} */ html: function ( source ) { return source.replace( /[&<>"']/g, htmlFilterReplacer ); }, /** * URL编码filter * * @param {string} source 源串 * @return {string} */ url: encodeURIComponent, /** * 源串filter,用于在默认开启HTML转义时获取源串,不进行转义 * * @param {string} source 源串 * @return {string} */ raw: function ( source ) { return source; } }; /** * 字符串字面化 * * @inner * @param {string} source 需要字面化的字符串 * @return {string} */ function stringLiteralize( source ) { return '"' + source .replace( /\x5C/g, '\\\\' ) .replace( /"/g, '\\"' ) .replace( /\x0A/g, '\\n' ) .replace( /\x09/g, '\\t' ) .replace( /\x0D/g, '\\r' ) // .replace( /\x08/g, '\\b' ) // .replace( /\x0C/g, '\\f' ) + '"'; } /** * 字符串格式化 * * @inner * @param {string} source 目标模版字符串 * @param {...string} replacements 字符串替换项集合 * @return {string} */ function stringFormat( source ) { var args = arguments; return source.replace( /\{([0-9]+)\}/g, function ( match, index ) { return args[ index - 0 + 1 ]; } ); } /** * 用于render的字符串变量声明语句 * * @inner * @const * @type {string} */ var RENDER_STRING_DECLATION = 'var r="";'; /** * 用于render的字符串内容添加语句(起始) * * @inner * @const * @type {string} */ var RENDER_STRING_ADD_START = 'r+='; /** * 用于render的字符串内容添加语句(结束) * * @inner * @const * @type {string} */ var RENDER_STRING_ADD_END = ';'; /** * 用于render的字符串内容返回语句 * * @inner * @const * @type {string} */ var RENDER_STRING_RETURN = 'return r;'; // HACK: IE8-时,编译后的renderer使用join Array的策略进行字符串拼接 if ( typeof navigator != 'undefined' && /msie\s*([0-9]+)/i.test( navigator.userAgent ) && RegExp.$1 - 0 < 8 ) { RENDER_STRING_DECLATION = 'var r=[],ri=0;'; RENDER_STRING_ADD_START = 'r[ri++]='; RENDER_STRING_RETURN = 'return r.join("");'; } /** * 将访问变量名称转换成getVariable调用的编译语句 * 用于if、var等命令生成编译代码 * * @inner * @param {string} name 访问变量名 * @return {string} */ function toGetVariableLiteral( name ) { name = name.replace( /^\s*\*/, '' ); return stringFormat( 'gv({0},["{1}"])', stringLiteralize( name ), name.replace( /\[['"]?([^'"]+)['"]?\]/g, function ( match, name ) { return '.' + name; } ) .split( '.' ) .join( '","' ) ); } /** * 解析文本片段中以固定字符串开头和结尾的包含块 * 用于 命令串: 和 变量替换串:${...} 的解析 * * @inner * @param {string} source 要解析的文本 * @param {string} open 包含块开头 * @param {string} close 包含块结束 * @param {boolean} greedy 是否贪婪匹配 * @param {function({string})} onInBlock 包含块内文本的处理函数 * @param {function({string})} onOutBlock 非包含块内文本的处理函数 */ function parseTextBlock( source, open, close, greedy, onInBlock, onOutBlock ) { var closeLen = close.length; var texts = source.split( open ); var level = 0; var buf = []; for ( var i = 0, len = texts.length; i < len; i++ ) { var text = texts[ i ]; if ( i ) { var openBegin = 1; level++; while ( 1 ) { var closeIndex = text.indexOf( close ); if ( closeIndex < 0 ) { buf.push( level > 1 && openBegin ? open : '', text ); break; } level = greedy ? level - 1 : 0; buf.push( level > 0 && openBegin ? open : '', text.slice( 0, closeIndex ), level > 0 ? close : '' ); text = text.slice( closeIndex + closeLen ); openBegin = 0; if ( level === 0 ) { break; } } if ( level === 0 ) { onInBlock( buf.join( '' ) ); onOutBlock( text ); buf = []; } } else { text && onOutBlock( text ); } } if ( level > 0 && buf.length > 0 ) { onOutBlock( open ); onOutBlock( buf.join( '' ) ); } } /** * 编译变量访问和变量替换的代码 * 用于普通文本或if、var、filter等命令生成编译代码 * * @inner * @param {string} source 源代码 * @param {Engine} engine 引擎实例 * @param {boolean} forText 是否为输出文本的变量替换 * @return {string} */ function compileVariable( source, engine, forText ) { var code = []; var options = engine.options; var toStringHead = ''; var toStringFoot = ''; var wrapHead = ''; var wrapFoot = ''; // 默认的filter,当forText模式时有效 var defaultFilter; if ( forText ) { toStringHead = 'ts('; toStringFoot = ')'; wrapHead = RENDER_STRING_ADD_START; wrapFoot = RENDER_STRING_ADD_END; defaultFilter = options.defaultFilter } parseTextBlock( source, options.variableOpen, options.variableClose, 1, function ( text ) { // 加入默认filter // 只有当处理forText时,需要加入默认filter // 处理if/var/use等command时,不需要加入默认filter if ( forText && text.indexOf( '|' ) < 0 && defaultFilter ) { text += '|' + defaultFilter; } // variableCode是一个gv调用,然后通过循环,在外面包filter的调用 // 形成filter["b"](filter["a"](gv(...))) // // 当forText模式,处理的是文本中的变量替换时 // 传递给filter的需要是字符串形式,所以gv外需要包一层ts调用 // 形成filter["b"](filter["a"](ts(gv(...)))) // // 当variableName以*起始时,忽略ts调用,直接传递原值给filter var filterCharIndex = text.indexOf( '|' ); var variableName = (filterCharIndex > 0 ? text.slice( 0, filterCharIndex ) : text).replace( /^\s+/, '' ).replace( /\s+$/, '' ); var filterSource = filterCharIndex > 0 ? text.slice( filterCharIndex + 1 ) : ''; var variableRawValue = variableName.indexOf( '*' ) === 0; var variableCode = [ variableRawValue ? '' : toStringHead, toGetVariableLiteral( variableName ), variableRawValue ? '' : toStringFoot ]; if ( filterSource ) { filterSource = compileVariable( filterSource, engine ); var filterSegs = filterSource.split( '|' ); for ( var i = 0, len = filterSegs.length; i < len; i++ ) { var seg = filterSegs[ i ]; if ( /^\s*([a-z0-9_-]+)(\((.*)\))?\s*$/i.test( seg ) ) { variableCode.unshift( 'fs["' + RegExp.$1 + '"](' ); if ( RegExp.$3 ) { variableCode.push( ',', RegExp.$3 ); } variableCode.push( ')' ); } } } code.push( wrapHead, variableCode.join( '' ), wrapFoot ); }, function ( text ) { code.push( wrapHead, forText ? stringLiteralize( text ) : text, wrapFoot ); } ); return code.join( '' ); } /** * 文本节点类 * * @inner * @constructor * @param {string} value 文本节点的内容文本 * @param {Engine} engine 引擎实例 */ function TextNode( value, engine ) { this.value = value; this.engine = engine; } TextNode.prototype = { /** * 获取renderer body的生成代码 * * @return {string} */ getRendererBody: function () { var value = this.value; var options = this.engine.options; if ( !value || ( options.strip && /^\s*$/.test( value ) ) ) { return ''; } return compileVariable( value, this.engine, 1 ); }, /** * 获取内容 * * @return {string} */ getContent: function () { return this.value; } }; /** * 命令节点类 * * @inner * @constructor * @param {string} value 命令节点的value * @param {Engine} engine 引擎实例 */ function Command( value, engine ) { this.value = value; this.engine = engine; this.children = []; } Command.prototype = { /** * 添加子节点 * * @param {TextNode|Command} node 子节点 */ addChild: function ( node ) { this.children.push( node ); }, /** * 节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ open: function ( context ) { var parent = context.stack.top(); this.parent = parent; parent && parent.addChild( this ); context.stack.push( this ); }, /** * 节点闭合,解析结束 * * @param {Object} context 语法分析环境对象 */ close: function ( context ) { while (context.stack.pop().constructor !== this.constructor) {} }, /** * 添加文本节点 * * @param {TextNode} node 节点 */ addTextNode: function ( node ) { this.addChild( node ); }, /** * 获取renderer body的生成代码 * * @return {string} */ getRendererBody: function () { var buf = []; var children = this.children; for ( var i = 0; i < children.length; i++ ) { buf.push( children[ i ].getRendererBody() ); } return buf.join( '' ); } }; /** * 命令自动闭合 * * @inner * @param {Object} context 语法分析环境对象 * @param {Function=} CommandType 自闭合的节点类型 */ function autoCloseCommand( context, CommandType ) { var stack = context.stack; var closeEnd = CommandType ? stack.find( function ( item ) { return item instanceof CommandType; } ) : stack.bottom(); if ( closeEnd ) { var node; do { node = stack.top(); // 如果节点对象不包含autoClose方法 // 则认为该节点不支持自动闭合,需要抛出错误 // for等节点不支持自动闭合 if ( !node.autoClose ) { throw new Error( node.type + ' must be closed manually: ' + node.value ); } node.autoClose( context ); } while ( node !== closeEnd ); } return closeEnd; } /** * renderer body起始代码段 * * @inner * @const * @type {string} */ var RENDERER_BODY_START = '' + 'data=data||{};' + 'var v={},fs=engine.filters,hg=typeof data.get=="function",' + 'gv=function(n,ps){' + 'var p=ps[0],d=v[p];' + 'if(d==null){' + 'if(hg){return data.get(n);}' + 'd=data[p];' + '}' + 'for(var i=1,l=ps.length;i= TMNodeState.APPLIED ) { return 1; } var masterNode = this.engine.masters[ this.master ]; if ( masterNode && masterNode.applyMaster() ) { this.children = []; for ( var i = 0, len = masterNode.children.length; i < len; i++ ) { var child = masterNode.children[ i ]; if ( child instanceof ContentPlaceHolderCommand ) { this.children.push.apply( this.children, (this.contents[ child.name ] || child).children ); } else { this.children.push( child ); } } this.state = TMNodeState.APPLIED; return 1; } }; /** * 判断target是否ready * 包括是否成功应用母版,以及import和use语句依赖的target是否ready * * @return {boolean} */ TargetCommand.prototype.isReady = function () { if ( this.state >= TMNodeState.READY ) { return 1; } var engine = this.engine; var readyState = 1; /** * 递归检查节点的ready状态 * * @inner * @param {Command|TextNode} node 目标节点 */ function checkReadyState( node ) { for ( var i = 0, len = node.children.length; i < len; i++ ) { var child = node.children[ i ]; if ( child instanceof ImportCommand ) { var target = engine.targets[ child.name ]; readyState = readyState && target && target.isReady( engine ); } else if ( child instanceof Command ) { checkReadyState( child ); } } } if ( this.applyMaster() ) { checkReadyState( this ); readyState && (this.state = TMNodeState.READY); return readyState; } }; /** * 获取target的renderer函数 * * @return {function(Object):string} */ TargetCommand.prototype.getRenderer = function () { if ( this.renderer ) { return this.renderer; } if ( this.isReady() ) { // console.log( this.name + ' ------------------' ); // console.log(RENDERER_BODY_START +RENDER_STRING_DECLATION // + this.getRendererBody() // + RENDER_STRING_RETURN); var realRenderer = new Function( 'data', 'engine', [ RENDERER_BODY_START, RENDER_STRING_DECLATION, this.getRendererBody(), RENDER_STRING_RETURN ].join( '\n' ) ); var engine = this.engine; this.renderer = function ( data ) { return realRenderer( data, engine ); }; return this.renderer; } return null; }; /** * 获取内容 * * @return {string} */ TargetCommand.prototype.getContent = function () { if ( this.isReady() ) { var buf = []; var children = this.children; for ( var i = 0; i < children.length; i++ ) { buf.push( children[ i ].getContent() ); } return buf.join( '' ); } return ''; }; /** * 将target或master节点对象添加到语法分析环境中 * * @inner * @param {TargetCommand|MasterCommand} targetOrMaster target或master节点对象 * @param {Object} context 语法分析环境对象 */ function addTargetOrMasterToContext( targetOrMaster, context ) { context.targetOrMaster = targetOrMaster; var engine = context.engine; var name = targetOrMaster.name; var isTarget = targetOrMaster instanceof TargetCommand; var prop = isTarget ? 'targets' : 'masters'; if ( engine[ prop ][ name ] ) { switch ( engine.options.namingConflict ) { case 'override': engine[ prop ][ name ] = targetOrMaster; isTarget && context.targets.push( name ); case 'ignore': break; default: throw new Error( ( isTarget ? 'Target' :'Master' ) + ' is exists: ' + name ); } } else { engine[ prop ][ name ] = targetOrMaster; isTarget && context.targets.push( name ); } } /** * target节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ TargetCommand.prototype.open = /** * master节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ MasterCommand.prototype.open = function ( context ) { autoCloseCommand( context ); Command.prototype.open.call( this, context ); this.state = TMNodeState.READING; addTargetOrMasterToContext( this, context ); }; /** * Import节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ ImportCommand.prototype.open = /** * Var节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ VarCommand.prototype.open = /** * Use节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ UseCommand.prototype.open = function ( context ) { var parent = context.stack.top(); this.parent = parent; parent.addChild( this ); }; /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ UseCommand.prototype.beforeOpen = /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ ImportCommand.prototype.beforeOpen = /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ VarCommand.prototype.beforeOpen = /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ ForCommand.prototype.beforeOpen = /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ FilterCommand.prototype.beforeOpen = /** * 节点open前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ IfCommand.prototype.beforeOpen = /** * 文本节点被添加到分析环境前的处理动作:节点不在target中时,自动创建匿名target * * @param {Object} context 语法分析环境对象 */ TextNode.prototype.beforeAdd = function ( context ) { if ( context.stack.bottom() ) { return; } var target = new TargetCommand( generateGUID(), context.engine ); target.open( context ); }; /** * 节点解析结束 * 由于use节点无需闭合,处理时不会入栈,所以将close置为空函数 * * @param {Object} context 语法分析环境对象 */ UseCommand.prototype.close = /** * 节点解析结束 * 由于import节点无需闭合,处理时不会入栈,所以将close置为空函数 * * @param {Object} context 语法分析环境对象 */ ImportCommand.prototype.close = /** * 节点解析结束 * 由于else节点无需闭合,处理时不会入栈,闭合由if负责。所以将close置为空函数 * * @param {Object} context 语法分析环境对象 */ ElseCommand.prototype.close = /** * 节点解析结束 * 由于var节点无需闭合,处理时不会入栈,所以将close置为空函数 * * @param {Object} context 语法分析环境对象 */ VarCommand.prototype.close = function () {}; /** * 获取内容 * * @return {string} */ ImportCommand.prototype.getContent = function () { var target = this.engine.targets[ this.name ]; return target.getContent(); }; /** * 获取renderer body的生成代码 * * @return {string} */ ImportCommand.prototype.getRendererBody = function () { var target = this.engine.targets[ this.name ]; return target.getRendererBody(); }; /** * 获取renderer body的生成代码 * * @return {string} */ UseCommand.prototype.getRendererBody = function () { return stringFormat( '{0}engine.render({2},{{3}}){1}', RENDER_STRING_ADD_START, RENDER_STRING_ADD_END, stringLiteralize( this.name ), compileVariable( this.args, this.engine ).replace( /(^|,)\s*([a-z0-9_]+)\s*=/ig, function ( match, start, argName ) { return (start || '') + stringLiteralize( argName ) + ':'; } ) ); }; /** * 获取renderer body的生成代码 * * @return {string} */ VarCommand.prototype.getRendererBody = function () { if ( this.expr ) { return stringFormat( 'v[{0}]={1};', stringLiteralize( this.name ), compileVariable( this.expr, this.engine ) ); } return ''; }; /** * 获取renderer body的生成代码 * * @return {string} */ IfCommand.prototype.getRendererBody = function () { var rendererBody = stringFormat( 'if({0}){{1}}', compileVariable( this.value, this.engine ), Command.prototype.getRendererBody.call( this ) ); var elseCommand = this[ 'else' ]; if ( elseCommand ) { return [ rendererBody, stringFormat( 'else{{0}}', elseCommand.getRendererBody() ) ].join( '' ); } return rendererBody; }; /** * 获取renderer body的生成代码 * * @return {string} */ ForCommand.prototype.getRendererBody = function () { return stringFormat( '' + 'var {0}={1};' + 'if({0} instanceof Array)' + 'for (var {4}=0,{5}={0}.length;{4}<{5};{4}++){v[{2}]={4};v[{3}]={0}[{4}];{6}}' + 'else if(typeof {0}==="object")' + 'for(var {4} in {0}){v[{2}]={4};v[{3}]={0}[{4}];{6}}', generateGUID(), compileVariable( this.list, this.engine ), stringLiteralize( this.index || generateGUID() ), stringLiteralize( this.item ), generateGUID(), generateGUID(), Command.prototype.getRendererBody.call( this ) ); }; /** * 获取renderer body的生成代码 * * @return {string} */ FilterCommand.prototype.getRendererBody = function () { var args = this.args; return stringFormat( '{2}fs[{5}]((function(){{0}{4}{1}})(){6}){3}', RENDER_STRING_DECLATION, RENDER_STRING_RETURN, RENDER_STRING_ADD_START, RENDER_STRING_ADD_END, Command.prototype.getRendererBody.call( this ), stringLiteralize( this.name ), args ? ',' + compileVariable( args, this.engine ) : '' ); }; /** * content节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ ContentCommand.prototype.open = function ( context ) { autoCloseCommand( context, ContentCommand ); Command.prototype.open.call( this, context ); context.targetOrMaster.contents[ this.name ] = this; }; /** * content节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ ContentPlaceHolderCommand.prototype.open = function ( context ) { autoCloseCommand( context, ContentPlaceHolderCommand ); Command.prototype.open.call( this, context ); }; /** * 节点自动闭合,解析结束 * * @param {Object} context 语法分析环境对象 */ ContentCommand.prototype.autoClose = /** * 节点自动闭合,解析结束 * * @param {Object} context 语法分析环境对象 */ IfCommand.prototype.autoClose = Command.prototype.close; /** * 节点自动闭合,解析结束 * contentplaceholder的自动结束逻辑为,在其开始位置后马上结束 * 所以,其自动结束时children应赋予其所属的parent,也就是master * * @param {Object} context 语法分析环境对象 */ ContentPlaceHolderCommand.prototype.autoClose = function ( context ) { var parentChildren = this.parent.children; parentChildren.push.apply( parentChildren, this.children ); this.children.length = 0; this.close( context ); }; /** * 添加子节点 * * @param {TextNode|Command} node 子节点 */ IfCommand.prototype.addChild = function ( node ) { var elseCommand = this[ 'else' ]; ( elseCommand ? elseCommand.children : this.children ).push( node ); }; /** * elif节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ ElifCommand.prototype.open = function ( context ) { var elseCommand = new ElseCommand(); elseCommand.open( context ); var ifCommand = autoCloseCommand( context, IfCommand ); ifCommand.addChild( this ); context.stack.push( this ); }; /** * else节点open,解析开始 * * @param {Object} context 语法分析环境对象 */ ElseCommand.prototype.open = function ( context ) { var ifCommand = autoCloseCommand( context, IfCommand ); ifCommand[ 'else' ] = this; context.stack.push( ifCommand ); }; /** * 命令类型集合 * * @type {Object} */ var commandTypes = {}; /** * 添加命令类型 * * @inner * @param {string} name 命令名称 * @param {Function} Type 处理命令用到的类 */ function addCommandType( name, Type ) { commandTypes[ name ] = Type; Type.prototype.type = name; } addCommandType( 'target', TargetCommand ); addCommandType( 'master', MasterCommand ); addCommandType( 'content', ContentCommand ); addCommandType( 'contentplaceholder', ContentPlaceHolderCommand ); addCommandType( 'import', ImportCommand ); addCommandType( 'use', UseCommand ); addCommandType( 'var', VarCommand ); addCommandType( 'for', ForCommand ); addCommandType( 'if', IfCommand ); addCommandType( 'elif', ElifCommand ); addCommandType( 'else', ElseCommand ); addCommandType( 'filter', FilterCommand ); /** * etpl引擎类 * * @constructor * @param {Object=} options 引擎参数 * @param {string=} options.commandOpen 命令语法起始串 * @param {string=} options.commandClose 命令语法结束串 * @param {string=} options.defaultFilter 默认变量替换的filter * @param {boolean=} options.strip 是否清除命令标签前后的空白字符 * @param {string=} options.namingConflict target或master名字冲突时的处理策略 */ function Engine( options ) { this.options = { commandOpen: '', variableOpen: '${', variableClose: '}', defaultFilter: 'html' }; this.config( options ); this.masters = {}; this.targets = {}; this.filters = extend({}, DEFAULT_FILTERS); } /** * 配置引擎参数,设置的参数将被合并到现有参数中 * * @param {Object} options 参数对象 * @param {string=} options.commandOpen 命令语法起始串 * @param {string=} options.commandClose 命令语法结束串 * @param {string=} options.defaultFilter 默认变量替换的filter * @param {boolean=} options.strip 是否清除命令标签前后的空白字符 * @param {string=} options.namingConflict target或master名字冲突时的处理策略 */ Engine.prototype.config = function ( options ) { extend( this.options, options ); }; /** * 解析模板并编译,返回第一个target编译后的renderer函数。 * * @param {string} source 模板源代码 * @return {function(Object):string} */ Engine.prototype.compile = /** * 解析模板并编译,返回第一个target编译后的renderer函数。 * 该方法的存在为了兼容老模板引擎 * * @param {string} source 模板源代码 * @return {function(Object):string} */ Engine.prototype.parse = function ( source ) { if ( source ) { var targetNames = parseSource( source, this ); if ( targetNames.length ) { return this.targets[ targetNames[ 0 ] ].getRenderer(); } } return new Function('return ""'); }; /** * 根据target名称获取编译后的renderer函数 * * @param {string} name target名称 * @return {function(Object):string} */ Engine.prototype.getRenderer = function ( name ) { var target = this.targets[ name ]; if ( target ) { return target.getRenderer(); } }; /** * 根据target名称获取模板内容 * * @param {string} name target名称 * @return {string} */ Engine.prototype.get = function ( name ) { var target = this.targets[ name ]; if ( target ) { return target.getContent(); } return ''; }; /** * 执行模板渲染,返回渲染后的字符串。 * * @param {string} name target名称 * @param {Object=} data 模板数据。 * 可以是plain object, * 也可以是带有 {string}get({string}name) 方法的对象 * @return {string} */ Engine.prototype.render = function ( name, data ) { var renderer = this.getRenderer( name ); if ( renderer ) { return renderer( data ); } return ''; }; /** * 增加过滤器 * * @param {string} name 过滤器名称 * @param {Function} filter 过滤函数 */ Engine.prototype.addFilter = function ( name, filter ) { if ( typeof filter == 'function' ) { this.filters[ name ] = filter; } }; /** * 解析源代码 * * @inner * @param {string} source 模板源代码 * @param {Engine} engine 引擎实例 * @return {Array} target名称列表 */ function parseSource( source, engine ) { var commandOpen = engine.options.commandOpen; var commandClose = engine.options.commandClose; var stack = new Stack(); var analyseContext = { engine: engine, targets: [], stack: stack }; // text节点内容缓冲区,用于合并多text var textBuf = []; /** * 将缓冲区中的text节点内容写入 * * @inner */ function flushTextBuf() { if ( textBuf.length > 0 ) { var text = textBuf.join( '' ); var textNode = new TextNode( text, engine ); textNode.beforeAdd( analyseContext ); stack.top().addTextNode( textNode ); textBuf = []; if ( engine.options.strip && analyseContext.current instanceof Command ) { textNode.value = text.replace( /^[\x20\t\r]*\n/, '' ); } analyseContext.current = textNode; } } var NodeType; /** * 判断节点是否是NodeType类型的实例 * 用于在stack中find提供filter * * @inner * @param {Command} node 目标节点 * @return {boolean} */ function isInstanceofNodeType( node ) { return node instanceof NodeType; } parseTextBlock( source, commandOpen, commandClose, 0, function ( text ) { // 内文本的处理函数 var match = /^\s*(\/)?([a-z]+)\s*(:([\s\S]*))?$/.exec( text ); // 符合command规则,并且存在相应的Command类,说明是合法有含义的Command // 否则,为不具有command含义的普通文本 if ( match && ( NodeType = commandTypes[ match[2].toLowerCase() ] ) && typeof NodeType == 'function' ) { // 先将缓冲区中的text节点内容写入 flushTextBuf(); var currentNode = analyseContext.current; if ( engine.options.strip && currentNode instanceof TextNode ) { currentNode.value = currentNode.value .replace( /\r?\n[\x20\t]*$/, '\n' ); } if ( match[1] ) { currentNode = stack.find( isInstanceofNodeType ); currentNode && currentNode.close( analyseContext ); } else { currentNode = new NodeType( match[4], engine ); if ( typeof currentNode.beforeOpen == 'function' ) { currentNode.beforeOpen( analyseContext ); } currentNode.open( analyseContext ); } analyseContext.current = currentNode; } else if ( !/^\s*\/\//.test( text ) ) { // 如果不是模板注释,则作为普通文本,写入缓冲区 textBuf.push( commandOpen, text, commandClose ); } NodeType = null; }, function ( text ) { // 外,普通文本的处理函数 // 普通文本直接写入缓冲区 textBuf.push( text ); } ); flushTextBuf(); // 将缓冲区中的text节点内容写入 autoCloseCommand( analyseContext ); return analyseContext.targets; } var etpl = new Engine(); etpl.Engine = Engine; if ( typeof exports == 'object' && typeof module == 'object' ) { // For CommonJS exports = module.exports = etpl; } else if ( typeof define == 'function' && define.amd ) { // For AMD define( etpl ); } else { // For