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.
1732 lines
48 KiB
1732 lines
48 KiB
1 year ago
|
/**
|
||
|
* 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<l;i++)if(d!=null)d = d[ps[i]];'
|
||
|
+ 'return d;'
|
||
|
+ '},'
|
||
|
+ 'ts=function(s){'
|
||
|
+ 'if(typeof s==="string"){return s;}'
|
||
|
+ 'if(s==null){s="";}'
|
||
|
+ 'return ""+s;'
|
||
|
+ '};'
|
||
|
;
|
||
|
// v: variables
|
||
|
// fs: filters
|
||
|
// gv: getVariable
|
||
|
// ts: toString
|
||
|
// n: name
|
||
|
// ps: properties
|
||
|
// hg: hasGetter
|
||
|
|
||
|
/**
|
||
|
* Target命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function TargetCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.master = RegExp.$3;
|
||
|
this.name = RegExp.$1;
|
||
|
Command.call( this, value, engine );
|
||
|
this.contents = {};
|
||
|
}
|
||
|
|
||
|
// 创建Target命令节点继承关系
|
||
|
inherits( TargetCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Master命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function MasterCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*(\(\s*master\s*=\s*([a-z0-9_-]+)\s*\))?\s*/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.master = RegExp.$3;
|
||
|
this.name = RegExp.$1;
|
||
|
Command.call( this, value, engine );
|
||
|
this.contents = {};
|
||
|
}
|
||
|
|
||
|
// 创建Master命令节点继承关系
|
||
|
inherits( MasterCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Content命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ContentCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建Content命令节点继承关系
|
||
|
inherits( ContentCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* ContentPlaceHolder命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ContentPlaceHolderCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建ContentPlaceHolder命令节点继承关系
|
||
|
inherits( ContentPlaceHolderCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Import命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ImportCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建Import命令节点继承关系
|
||
|
inherits( ImportCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Var命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function VarCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_]+)\s*=([\s\S]*)$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
this.expr = RegExp.$2;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建Var命令节点继承关系
|
||
|
inherits( VarCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* filter命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function FilterCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
this.args = RegExp.$3;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建filter命令节点继承关系
|
||
|
inherits( FilterCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Use命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function UseCommand( value, engine ) {
|
||
|
if ( !/^\s*([a-z0-9_-]+)\s*(\(([\s\S]*)\))?\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.name = RegExp.$1;
|
||
|
this.args = RegExp.$3;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建Use命令节点继承关系
|
||
|
inherits( UseCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* for命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ForCommand( value, engine ) {
|
||
|
if ( !/^\s*(\$\{[\s\S]+\})\s+as\s+\$\{([0-9a-z_]+)\}\s*(,\s*\$\{([0-9a-z_]+)\})?\s*$/i.test( value ) ) {
|
||
|
throw new Error( 'Invalid ' + this.type + ' syntax: ' + value );
|
||
|
}
|
||
|
|
||
|
this.list = RegExp.$1;
|
||
|
this.item = RegExp.$2;
|
||
|
this.index = RegExp.$4;
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建for命令节点继承关系
|
||
|
inherits( ForCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* if命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function IfCommand( value, engine ) {
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建if命令节点继承关系
|
||
|
inherits( IfCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* elif命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ElifCommand( value, engine ) {
|
||
|
IfCommand.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建elif命令节点继承关系
|
||
|
inherits( ElifCommand, IfCommand );
|
||
|
|
||
|
/**
|
||
|
* else命令节点类
|
||
|
*
|
||
|
* @inner
|
||
|
* @constructor
|
||
|
* @param {string} value 命令节点的value
|
||
|
* @param {Engine} engine 引擎实例
|
||
|
*/
|
||
|
function ElseCommand( value, engine ) {
|
||
|
Command.call( this, value, engine );
|
||
|
}
|
||
|
|
||
|
// 创建else命令节点继承关系
|
||
|
inherits( ElseCommand, Command );
|
||
|
|
||
|
/**
|
||
|
* Target和Master的节点状态
|
||
|
*
|
||
|
* @inner
|
||
|
*/
|
||
|
var TMNodeState = {
|
||
|
READING: 1,
|
||
|
READED: 2,
|
||
|
APPLIED: 3,
|
||
|
READY: 4
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 节点闭合,解析结束
|
||
|
*
|
||
|
* @param {Object} context 语法分析环境对象
|
||
|
*/
|
||
|
MasterCommand.prototype.close =
|
||
|
|
||
|
/**
|
||
|
* 节点闭合,解析结束。自闭合时被调用
|
||
|
*
|
||
|
* @param {Object} context 语法分析环境对象
|
||
|
*/
|
||
|
MasterCommand.prototype.autoClose =
|
||
|
|
||
|
/**
|
||
|
* 节点闭合,解析结束
|
||
|
*
|
||
|
* @param {Object} context 语法分析环境对象
|
||
|
*/
|
||
|
TargetCommand.prototype.close =
|
||
|
|
||
|
/**
|
||
|
* 节点闭合,解析结束。自闭合时被调用
|
||
|
*
|
||
|
* @param {Object} context 语法分析环境对象
|
||
|
*/
|
||
|
TargetCommand.prototype.autoClose = function ( context ) {
|
||
|
Command.prototype.close.call( this, context );
|
||
|
this.state = this.master ? TMNodeState.READED : TMNodeState.APPLIED;
|
||
|
context.targetOrMaster = null;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* 应用其继承的母版,返回是否成功应用母版
|
||
|
*
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
TargetCommand.prototype.applyMaster =
|
||
|
|
||
|
/**
|
||
|
* 应用其继承的母版,返回是否成功应用母版
|
||
|
*
|
||
|
* @return {boolean}
|
||
|
*/
|
||
|
MasterCommand.prototype.applyMaster = function () {
|
||
|
if ( this.state >= 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: '<!--',
|
||
|
commandClose: '-->',
|
||
|
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 <script src="..."
|
||
|
root.etpl = etpl;
|
||
|
}
|
||
|
})(this);
|