mirror of
https://github.com/balloonwj/TeamTalk.git
synced 2026-10-22 01:06:32 +08:00
492 lines
18 KiB
JavaScript
492 lines
18 KiB
JavaScript
/*
|
|
********** Juicer **********
|
|
${A Fast template engine}
|
|
Project Home: http://juicer.name
|
|
|
|
Author: Guokai
|
|
Gtalk: badkaikai@gmail.com
|
|
Blog: http://benben.cc
|
|
Licence: MIT License
|
|
Version: 0.6.5-stable
|
|
*/
|
|
|
|
(function() {
|
|
|
|
// This is the main function for not only compiling but also rendering.
|
|
// there's at least two parameters need to be provided, one is the tpl,
|
|
// another is the data, the tpl can either be a string, or an id like #id.
|
|
// if only tpl was given, it'll return the compiled reusable function.
|
|
// if tpl and data were given at the same time, it'll return the rendered
|
|
// result immediately.
|
|
|
|
var juicer = function() {
|
|
var args = [].slice.call(arguments);
|
|
|
|
args.push(juicer.options);
|
|
|
|
if(args[0].match(/^\s*#([\w:\-\.]+)\s*$/igm)) {
|
|
args[0].replace(/^\s*#([\w:\-\.]+)\s*$/igm, function($, $id) {
|
|
var _document = document;
|
|
var elem = _document && _document.getElementById($id);
|
|
args[0] = elem ? (elem.value || elem.innerHTML) : $;
|
|
});
|
|
}
|
|
|
|
if(arguments.length == 1) {
|
|
return juicer.compile.apply(juicer, args);
|
|
}
|
|
|
|
if(arguments.length >= 2) {
|
|
return juicer.to_html.apply(juicer, args);
|
|
}
|
|
};
|
|
|
|
var __escapehtml = {
|
|
escapehash: {
|
|
'<': '<',
|
|
'>': '>',
|
|
'&': '&',
|
|
'"': '"',
|
|
"'": ''',
|
|
'/': '/'
|
|
},
|
|
escapereplace: function(k) {
|
|
return __escapehtml.escapehash[k];
|
|
},
|
|
escaping: function(str) {
|
|
return typeof(str) !== 'string' ? str : str.replace(/[&<>"]/igm, this.escapereplace);
|
|
},
|
|
detection: function(data) {
|
|
return typeof(data) === 'undefined' ? '' : data;
|
|
}
|
|
};
|
|
|
|
var __throw = function(error) {
|
|
if(typeof(console) !== 'undefined') {
|
|
if(console.warn) {
|
|
console.warn(error);
|
|
return;
|
|
}
|
|
|
|
if(console.log) {
|
|
console.log(error);
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw(error);
|
|
};
|
|
|
|
var __creator = function(o, proto) {
|
|
o = o !== Object(o) ? {} : o;
|
|
|
|
if(o.__proto__) {
|
|
o.__proto__ = proto;
|
|
return o;
|
|
}
|
|
|
|
var empty = function() {};
|
|
var n = Object.create ?
|
|
Object.create(proto) :
|
|
new(empty.prototype = proto, empty);
|
|
|
|
for(var i in o) {
|
|
if(o.hasOwnProperty(i)) {
|
|
n[i] = o[i];
|
|
}
|
|
}
|
|
|
|
return n;
|
|
};
|
|
|
|
juicer.__cache = {};
|
|
juicer.version = '0.6.5-stable';
|
|
juicer.settings = {};
|
|
|
|
juicer.tags = {
|
|
operationOpen: '{@',
|
|
operationClose: '}',
|
|
interpolateOpen: '\\${',
|
|
interpolateClose: '}',
|
|
noneencodeOpen: '\\$\\${',
|
|
noneencodeClose: '}',
|
|
commentOpen: '\\{#',
|
|
commentClose: '\\}'
|
|
};
|
|
|
|
juicer.options = {
|
|
cache: true,
|
|
strip: true,
|
|
errorhandling: true,
|
|
detection: true,
|
|
_method: __creator({
|
|
__escapehtml: __escapehtml,
|
|
__throw: __throw,
|
|
__juicer: juicer
|
|
}, {})
|
|
};
|
|
|
|
juicer.tagInit = function() {
|
|
var forstart = juicer.tags.operationOpen + 'each\\s*([^}]*?)\\s*as\\s*(\\w*?)\\s*(,\\s*\\w*?)?' + juicer.tags.operationClose;
|
|
var forend = juicer.tags.operationOpen + '\\/each' + juicer.tags.operationClose;
|
|
var ifstart = juicer.tags.operationOpen + 'if\\s*([^}]*?)' + juicer.tags.operationClose;
|
|
var ifend = juicer.tags.operationOpen + '\\/if' + juicer.tags.operationClose;
|
|
var elsestart = juicer.tags.operationOpen + 'else' + juicer.tags.operationClose;
|
|
var elseifstart = juicer.tags.operationOpen + 'else if\\s*([^}]*?)' + juicer.tags.operationClose;
|
|
var interpolate = juicer.tags.interpolateOpen + '([\\s\\S]+?)' + juicer.tags.interpolateClose;
|
|
var noneencode = juicer.tags.noneencodeOpen + '([\\s\\S]+?)' + juicer.tags.noneencodeClose;
|
|
var inlinecomment = juicer.tags.commentOpen + '[^}]*?' + juicer.tags.commentClose;
|
|
var rangestart = juicer.tags.operationOpen + 'each\\s*(\\w*?)\\s*in\\s*range\\(([^}]+?)\\s*,\\s*([^}]+?)\\)' + juicer.tags.operationClose;
|
|
var include = juicer.tags.operationOpen + 'include\\s*([^}]*?)\\s*,\\s*([^}]*?)' + juicer.tags.operationClose;
|
|
|
|
juicer.settings.forstart = new RegExp(forstart, 'igm');
|
|
juicer.settings.forend = new RegExp(forend, 'igm');
|
|
juicer.settings.ifstart = new RegExp(ifstart, 'igm');
|
|
juicer.settings.ifend = new RegExp(ifend, 'igm');
|
|
juicer.settings.elsestart = new RegExp(elsestart, 'igm');
|
|
juicer.settings.elseifstart = new RegExp(elseifstart, 'igm');
|
|
juicer.settings.interpolate = new RegExp(interpolate, 'igm');
|
|
juicer.settings.noneencode = new RegExp(noneencode, 'igm');
|
|
juicer.settings.inlinecomment = new RegExp(inlinecomment, 'igm');
|
|
juicer.settings.rangestart = new RegExp(rangestart, 'igm');
|
|
juicer.settings.include = new RegExp(include, 'igm');
|
|
};
|
|
|
|
juicer.tagInit();
|
|
|
|
// Using this method to set the options by given conf-name and conf-value,
|
|
// you can also provide more than one key-value pair wrapped by an object.
|
|
// this interface also used to custom the template tag delimater, for this
|
|
// situation, the conf-name must begin with tag::, for example: juicer.set
|
|
// ('tag::operationOpen', '{@').
|
|
|
|
juicer.set = function(conf, value) {
|
|
var that = this;
|
|
|
|
var escapePattern = function(v) {
|
|
return v.replace(/[\$\(\)\[\]\+\^\{\}\?\*\|\.]/igm, function($) {
|
|
return '\\' + $;
|
|
});
|
|
};
|
|
|
|
var set = function(conf, value) {
|
|
var tag = conf.match(/^tag::(.*)$/i);
|
|
|
|
if(tag) {
|
|
that.tags[tag[1]] = escapePattern(value);
|
|
that.tagInit();
|
|
return;
|
|
}
|
|
|
|
that.options[conf] = value;
|
|
};
|
|
|
|
if(arguments.length === 2) {
|
|
set(conf, value);
|
|
return;
|
|
}
|
|
|
|
if(conf === Object(conf)) {
|
|
for(var i in conf) {
|
|
if(conf.hasOwnProperty(i)) {
|
|
set(i, conf[i]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Before you're using custom functions in your template like ${name | fnName},
|
|
// you need to register this fn by juicer.register('fnName', fn).
|
|
|
|
juicer.register = function(fname, fn) {
|
|
var _method = this.options._method;
|
|
|
|
if(_method.hasOwnProperty(fname)) {
|
|
return false;
|
|
}
|
|
|
|
return _method[fname] = fn;
|
|
};
|
|
|
|
// remove the registered function in the memory by the provided function name.
|
|
// for example: juicer.unregister('fnName').
|
|
|
|
juicer.unregister = function(fname) {
|
|
var _method = this.options._method;
|
|
|
|
if(_method.hasOwnProperty(fname)) {
|
|
return delete _method[fname];
|
|
}
|
|
};
|
|
|
|
juicer.template = function(options) {
|
|
var that = this;
|
|
|
|
this.options = options;
|
|
|
|
this.__interpolate = function(_name, _escape, options) {
|
|
var _define = _name.split('|'), _fn = _define[0] || '', _cluster;
|
|
|
|
if(_define.length > 1) {
|
|
_name = _define.shift();
|
|
_cluster = _define.shift().split(',');
|
|
_fn = '_method.' + _cluster.shift() + '.call({}, ' + [_name].concat(_cluster) + ')';
|
|
}
|
|
|
|
return '<%= ' + (_escape ? '_method.__escapehtml.escaping' : '') + '(' +
|
|
(!options || options.detection !== false ? '_method.__escapehtml.detection' : '') + '(' +
|
|
_fn +
|
|
')' +
|
|
')' +
|
|
' %>';
|
|
};
|
|
|
|
this.__removeShell = function(tpl, options) {
|
|
var _counter = 0;
|
|
|
|
tpl = tpl
|
|
// for expression
|
|
.replace(juicer.settings.forstart, function($, _name, alias, key) {
|
|
var alias = alias || 'value', key = key && key.substr(1);
|
|
var _iterate = 'i' + _counter++;
|
|
return '<% ~function() {' +
|
|
'for(var ' + _iterate + ' in ' + _name + ') {' +
|
|
'if(' + _name + '.hasOwnProperty(' + _iterate + ')) {' +
|
|
'var ' + alias + '=' + _name + '[' + _iterate + '];' +
|
|
(key ? ('var ' + key + '=' + _iterate + ';') : '') +
|
|
' %>';
|
|
})
|
|
.replace(juicer.settings.forend, '<% }}}(); %>')
|
|
|
|
// if expression
|
|
.replace(juicer.settings.ifstart, function($, condition) {
|
|
return '<% if(' + condition + ') { %>';
|
|
})
|
|
.replace(juicer.settings.ifend, '<% } %>')
|
|
|
|
// else expression
|
|
.replace(juicer.settings.elsestart, function($) {
|
|
return '<% } else { %>';
|
|
})
|
|
|
|
// else if expression
|
|
.replace(juicer.settings.elseifstart, function($, condition) {
|
|
return '<% } else if(' + condition + ') { %>';
|
|
})
|
|
|
|
// interpolate without escape
|
|
.replace(juicer.settings.noneencode, function($, _name) {
|
|
return that.__interpolate(_name, false, options);
|
|
})
|
|
|
|
// interpolate with escape
|
|
.replace(juicer.settings.interpolate, function($, _name) {
|
|
return that.__interpolate(_name, true, options);
|
|
})
|
|
|
|
// clean up comments
|
|
.replace(juicer.settings.inlinecomment, '')
|
|
|
|
// range expression
|
|
.replace(juicer.settings.rangestart, function($, _name, start, end) {
|
|
var _iterate = 'j' + _counter++;
|
|
return '<% ~function() {' +
|
|
'for(var ' + _iterate + '=' + start + ';' + _iterate + '<' + end + ';' + _iterate + '++) {{' +
|
|
'var ' + _name + '=' + _iterate + ';' +
|
|
' %>';
|
|
})
|
|
|
|
// include sub-template
|
|
.replace(juicer.settings.include, function($, tpl, data) {
|
|
// compatible for node.js
|
|
if(tpl.match(/^file\:\/\//igm)) return $;
|
|
return '<%= _method.__juicer(' + tpl + ', ' + data + '); %>';
|
|
});
|
|
|
|
// exception handling
|
|
if(!options || options.errorhandling !== false) {
|
|
tpl = '<% try { %>' + tpl;
|
|
tpl += '<% } catch(e) {_method.__throw("Juicer Render Exception: "+e.message);} %>';
|
|
}
|
|
|
|
return tpl;
|
|
};
|
|
|
|
this.__toNative = function(tpl, options) {
|
|
return this.__convert(tpl, !options || options.strip);
|
|
};
|
|
|
|
this.__lexicalAnalyze = function(tpl) {
|
|
var buffer = [];
|
|
var method = [];
|
|
var prefix = '';
|
|
var reserved = [
|
|
'if', 'each', '_', '_method', 'console',
|
|
'break', 'case', 'catch', 'continue', 'debugger', 'default', 'delete', 'do',
|
|
'finally', 'for', 'function', 'in', 'instanceof', 'new', 'return', 'switch',
|
|
'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'null', 'typeof',
|
|
'class', 'enum', 'export', 'extends', 'import', 'super', 'implements', 'interface',
|
|
'let', 'package', 'private', 'protected', 'public', 'static', 'yield', 'const', 'arguments',
|
|
'true', 'false', 'undefined', 'NaN'
|
|
];
|
|
|
|
var indexOf = function(array, item) {
|
|
if (Array.prototype.indexOf && array.indexOf === Array.prototype.indexOf) {
|
|
return array.indexOf(item);
|
|
}
|
|
|
|
for(var i=0; i < array.length; i++) {
|
|
if(array[i] === item) return i;
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
|
|
var variableAnalyze = function($, statement) {
|
|
statement = statement.match(/\w+/igm)[0];
|
|
|
|
if(indexOf(buffer, statement) === -1 && indexOf(reserved, statement) === -1 && indexOf(method, statement) === -1) {
|
|
|
|
// avoid re-declare native function, if not do this, template
|
|
// `{@if encodeURIComponent(name)}` could be throw undefined.
|
|
|
|
if(typeof(window) !== 'undefined' && typeof(window[statement]) === 'function' && window[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
|
|
return $;
|
|
}
|
|
|
|
// compatible for node.js
|
|
if(typeof(global) !== 'undefined' && typeof(global[statement]) === 'function' && global[statement].toString().match(/^\s*?function \w+\(\) \{\s*?\[native code\]\s*?\}\s*?$/i)) {
|
|
return $;
|
|
}
|
|
|
|
// avoid re-declare registered function, if not do this, template
|
|
// `{@if registered_func(name)}` could be throw undefined.
|
|
|
|
if(typeof(juicer.options._method[statement]) === 'function' || juicer.options._method.hasOwnProperty(statement)) {
|
|
method.push(statement);
|
|
return $;
|
|
}
|
|
|
|
buffer.push(statement); // fuck ie
|
|
}
|
|
|
|
return $;
|
|
};
|
|
|
|
tpl.replace(juicer.settings.forstart, variableAnalyze).
|
|
replace(juicer.settings.interpolate, variableAnalyze).
|
|
replace(juicer.settings.ifstart, variableAnalyze).
|
|
replace(juicer.settings.elseifstart, variableAnalyze).
|
|
replace(juicer.settings.include, variableAnalyze).
|
|
replace(/[\+\-\*\/%!\?\|\^&~<>=,\(\)\[\]]\s*([A-Za-z_]+)/igm, variableAnalyze);
|
|
|
|
for(var i = 0;i < buffer.length; i++) {
|
|
prefix += 'var ' + buffer[i] + '=_.' + buffer[i] + ';';
|
|
}
|
|
|
|
for(var i = 0;i < method.length; i++) {
|
|
prefix += 'var ' + method[i] + '=_method.' + method[i] + ';';
|
|
}
|
|
|
|
return '<% ' + prefix + ' %>';
|
|
};
|
|
|
|
this.__convert=function(tpl, strip) {
|
|
var buffer = [].join('');
|
|
|
|
buffer += "'use strict';"; // use strict mode
|
|
buffer += "var _=_||{};";
|
|
buffer += "var _out='';_out+='";
|
|
|
|
if(strip !== false) {
|
|
buffer += tpl
|
|
.replace(/\\/g, "\\\\")
|
|
.replace(/[\r\t\n]/g, " ")
|
|
.replace(/'(?=[^%]*%>)/g, "\t")
|
|
.split("'").join("\\'")
|
|
.split("\t").join("'")
|
|
.replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
|
|
.split("<%").join("';")
|
|
.split("%>").join("_out+='")+
|
|
"';return _out;";
|
|
|
|
return buffer;
|
|
}
|
|
|
|
buffer += tpl
|
|
.replace(/\\/g, "\\\\")
|
|
.replace(/[\r]/g, "\\r")
|
|
.replace(/[\t]/g, "\\t")
|
|
.replace(/[\n]/g, "\\n")
|
|
.replace(/'(?=[^%]*%>)/g, "\t")
|
|
.split("'").join("\\'")
|
|
.split("\t").join("'")
|
|
.replace(/<%=(.+?)%>/g, "';_out+=$1;_out+='")
|
|
.split("<%").join("';")
|
|
.split("%>").join("_out+='")+
|
|
"';return _out.replace(/[\\r\\n]\\s+[\\r\\n]/g, '\\r\\n');";
|
|
|
|
return buffer;
|
|
};
|
|
|
|
this.parse = function(tpl, options) {
|
|
var _that = this;
|
|
|
|
if(!options || options.loose !== false) {
|
|
tpl = this.__lexicalAnalyze(tpl) + tpl;
|
|
}
|
|
|
|
tpl = this.__removeShell(tpl, options);
|
|
tpl = this.__toNative(tpl, options);
|
|
|
|
this._render = new Function('_, _method', tpl);
|
|
|
|
this.render = function(_, _method) {
|
|
if(!_method || _method !== that.options._method) {
|
|
_method = __creator(_method, that.options._method);
|
|
}
|
|
|
|
return _that._render.call(this, _, _method);
|
|
};
|
|
|
|
return this;
|
|
};
|
|
};
|
|
|
|
juicer.compile = function(tpl, options) {
|
|
if(!options || options !== this.options) {
|
|
options = __creator(options, this.options);
|
|
}
|
|
|
|
try {
|
|
var engine = this.__cache[tpl] ?
|
|
this.__cache[tpl] :
|
|
new this.template(this.options).parse(tpl, options);
|
|
|
|
if(!options || options.cache !== false) {
|
|
this.__cache[tpl] = engine;
|
|
}
|
|
|
|
return engine;
|
|
|
|
} catch(e) {
|
|
__throw('Juicer Compile Exception: ' + e.message);
|
|
|
|
return {
|
|
render: function() {} // noop
|
|
};
|
|
}
|
|
};
|
|
|
|
juicer.to_html = function(tpl, data, options) {
|
|
if(!options || options !== this.options) {
|
|
options = __creator(options, this.options);
|
|
}
|
|
|
|
return this.compile(tpl, options).render(data, options._method);
|
|
};
|
|
|
|
typeof(module) !== 'undefined' && module.exports ? module.exports = juicer : this.juicer = juicer;
|
|
|
|
})();
|