allocatr/public/assets/libs/art-template/src/compile.js
2025-02-26 16:44:49 +08:00

367 lines
8.4 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 编译模板
* 2012-6-6 @TooBug: define 方法名改为 compile与 Node Express 保持一致
* @name template.compile
* @param {String} 模板字符串
* @param {Object} 编译选项
*
* - openTag {String}
* - closeTag {String}
* - filename {String}
* - escape {Boolean}
* - compress {Boolean}
* - debug {Boolean}
* - cache {Boolean}
* - parser {Function}
*
* @return {Function} 渲染方法
*/
var compile = template.compile = function (source, options) {
// 合并默认配置
options = options || {};
for (var name in defaults) {
if (options[name] === undefined) {
options[name] = defaults[name];
}
}
var filename = options.filename;
try {
var Render = compiler(source, options);
} catch (e) {
e.filename = filename || 'anonymous';
e.name = 'Syntax Error';
return showDebugInfo(e);
}
// 对编译结果进行一次包装
function render (data) {
try {
return new Render(data, filename) + '';
} catch (e) {
// 运行时出错后自动开启调试模式重新编译
if (!options.debug) {
options.debug = true;
return compile(source, options)(data);
}
return showDebugInfo(e)();
}
}
render.prototype = Render.prototype;
render.toString = function () {
return Render.toString();
};
if (filename && options.cache) {
cacheStore[filename] = render;
}
return render;
};
// 数组迭代
var forEach = utils.$each;
// 静态分析模板变量
var KEYWORDS =
// 关键字
'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'
+ ',undefined';
var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
var SPLIT_RE = /[^\w$]+/g;
var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
var BOUNDARY_RE = /^,+|,+$/g;
var SPLIT2_RE = /^$|,+/;
// 获取变量
function getVariable (code) {
return code
.replace(REMOVE_RE, '')
.replace(SPLIT_RE, ',')
.replace(KEYWORDS_RE, '')
.replace(NUMBER_RE, '')
.replace(BOUNDARY_RE, '')
.split(SPLIT2_RE);
};
// 字符串转义
function stringify (code) {
return "'" + code
// 单引号与反斜杠转义
.replace(/('|\\)/g, '\\$1')
// 换行符转义(windows + linux)
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n') + "'";
}
function compiler (source, options) {
var debug = options.debug;
var openTag = options.openTag;
var closeTag = options.closeTag;
var parser = options.parser;
var compress = options.compress;
var escape = options.escape;
var line = 1;
var uniq = {$data:1,$filename:1,$utils:1,$helpers:1,$out:1,$line:1};
var isNewEngine = ''.trim;// '__proto__' in {}
var replaces = isNewEngine
? ["$out='';", "$out+=", ";", "$out"]
: ["$out=[];", "$out.push(", ");", "$out.join('')"];
var concat = isNewEngine
? "$out+=text;return $out;"
: "$out.push(text);";
var print = "function(){"
+ "var text=''.concat.apply('',arguments);"
+ concat
+ "}";
var include = "function(filename,data){"
+ "data=data||$data;"
+ "var text=$utils.$include(filename,data,$filename);"
+ concat
+ "}";
var headerCode = "'use strict';"
+ "var $utils=this,$helpers=$utils.$helpers,"
+ (debug ? "$line=0," : "");
var mainCode = replaces[0];
var footerCode = "return new String(" + replaces[3] + ");"
// html与逻辑语法分离
forEach(source.split(openTag), function (code) {
code = code.split(closeTag);
var $0 = code[0];
var $1 = code[1];
// code: [html]
if (code.length === 1) {
mainCode += html($0);
// code: [logic, html]
} else {
mainCode += logic($0);
if ($1) {
mainCode += html($1);
}
}
});
var code = headerCode + mainCode + footerCode;
// 调试语句
if (debug) {
code = "try{" + code + "}catch(e){"
+ "throw {"
+ "filename:$filename,"
+ "name:'Render Error',"
+ "message:e.message,"
+ "line:$line,"
+ "source:" + stringify(source)
+ ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
+ "};"
+ "}";
}
try {
var Render = new Function("$data", "$filename", code);
Render.prototype = utils;
return Render;
} catch (e) {
e.temp = "function anonymous($data,$filename) {" + code + "}";
throw e;
}
// 处理 HTML 语句
function html (code) {
// 记录行号
line += code.split(/\n/).length - 1;
// 压缩多余空白与注释
if (compress) {
code = code
.replace(/\s+/g, ' ')
.replace(/<!--[\w\W]*?-->/g, '');
}
if (code) {
code = replaces[1] + stringify(code) + replaces[2] + "\n";
}
return code;
}
// 处理逻辑语句
function logic (code) {
var thisLine = line;
if (parser) {
// 语法转换插件钩子
code = parser(code, options);
} else if (debug) {
// 记录行号
code = code.replace(/\n/g, function () {
line ++;
return "$line=" + line + ";";
});
}
// 输出语句. 编码: <%=value%> 不编码:<%=#value%>
// <%=#value%> 等同 v2.0.3 之前的 <%==value%>
if (code.indexOf('=') === 0) {
var escapeSyntax = escape && !/^=[=#]/.test(code);
code = code.replace(/^=[=#]?|[\s;]*$/g, '');
// 对内容编码
if (escapeSyntax) {
var name = code.replace(/\s*\([^\)]+\)/, '');
// 排除 utils.* | include | print
if (!utils[name] && !/^(include|print)$/.test(name)) {
code = "$escape(" + code + ")";
}
// 不编码
} else {
code = "$string(" + code + ")";
}
code = replaces[1] + code + replaces[2];
}
if (debug) {
code = "$line=" + thisLine + ";" + code;
}
// 提取模板中的变量名
forEach(getVariable(code), function (name) {
// name 值可能为空,在安卓低版本浏览器下
if (!name || uniq[name]) {
return;
}
var value;
// 声明模板变量
// 赋值优先级:
// [include, print] > utils > helpers > data
if (name === 'print') {
value = print;
} else if (name === 'include') {
value = include;
} else if (utils[name]) {
value = "$utils." + name;
} else if (helpers[name]) {
value = "$helpers." + name;
} else {
value = "$data." + name;
}
headerCode += name + "=" + value + ",";
uniq[name] = true;
});
return code + "\n";
}
};