Skip to content

Instantly share code, notes, and snippets.

@nathanborror
Created May 20, 2010 21:21
Show Gist options
  • Select an option

  • Save nathanborror/408123 to your computer and use it in GitHub Desktop.

Select an option

Save nathanborror/408123 to your computer and use it in GitHub Desktop.
function Jtl(tpl) {
this._nodelist = this._compile(tpl);
}
Jtl.prototype = {
_compile : function(tpl) {
var tag_str = this._formRegx();
var tag_re = new RegExp(tag_str,'g');
var bits = new Jtl.StringBuilder(tpl).bsplit(tag_re);
bits = new Jtl.ArrayBuilder(bits).filter('');
var tokens = new Jtl.ArrayBuilder(bits).map(this._createToken);
return new Jtl.Parser(tokens).parse();
},
_createToken : function(bit) {
var _re = Jtl._re;
if(new Jtl.StringBuilder(bit).startwith(_re.TAG_START)) {
return new Jtl.Token(Jtl.TAG_TOKEN,bit.slice(_re.TAG_START.length,-_re.TAG_END.length));
}
else if(new Jtl.StringBuilder(bit).startwith(_re.VAR_START)) {
return new Jtl.Token(Jtl.VAR_TOKEN,bit.slice(_re.VAR_START.length,-_re.VAR_END.length));
}
else{
return new Jtl.Token(Jtl.TEXT_TOKEN,bit);
}
},
_formRegx : function() {
var re = Jtl._re,ret = '';
ret += '(' + new Jtl.StringBuilder(re.TAG_START).rescape() + '.*?' + new Jtl.StringBuilder(re.TAG_END).rescape() +
'|' + new Jtl.StringBuilder(re.VAR_START).rescape() + '.*?' + new Jtl.StringBuilder(re.VAR_END).rescape() + '|$' + ')';
return ret;
},
render : function(context) {
var ret = [];
var nodelist = this._nodelist;
var node;
for(var i=0,ilen=nodelist.length;i<ilen;i++) {
node = nodelist[i];
if(typeof(node)=='object') {
typeof(node.render)=='function' ? ret.push(node.render(context)) : ret.push(node.toString());
}
else {
ret.push(node.toString());
}
}
return ret.join('');
}
}
Jtl.VAR_TOKEN = 0;
Jtl.TAG_TOKEN = 1;
Jtl.TEXT_TOKEN = 2;
Jtl._re = {
TAG_START : '{%',
TAG_END : '%}',
VAR_START : '{{',
VAR_END : '}}'
}
Jtl.regTag = function(tName,tFunc) {
Jtl.tag[tName] = tFunc;
}
Jtl.list_render = function(context,nodelist) {
var ret = [];
for(var i=0,ilen=nodelist.length;i<ilen;i++) {
ret.push(nodelist[i].render(context));
}
return ret.join('');
}
Jtl.getVar = function(context,varstr) {
var ret=context;
var va = varstr.split('.');
for(var i=0,ilen=va.length;i<ilen;i++) {
ret = ret[va[i]];
}
return ret;
}
Jtl.StringBuilder = function(str) {
this.str = str;
}
Jtl.StringBuilder.prototype = {
startwith : function(c) {
return this.str.indexOf(c)==0;
},
rescape : function() {
return this.str.replace(/(\(|\)|\{|\})/g,'\\$1');
},
bsplit : function(pat) {
var cursor = 0;
var ret = [];
var str = this.str;
this.str.replace(pat,function(m1,m2,n) {
ret.push(str.slice(cursor,n));
ret.push(m1);
cursor = n + m1.length;
})
return ret;
}
}
Jtl.ArrayBuilder = function(arr) {
this.arr = arr;
}
Jtl.ArrayBuilder.prototype = {
map : function(fn) {
var a = this.arr;
for(var i=0,ilen=a.length;i<ilen;i++) {
a[i] = fn(a[i]);
}
return a;
},
filter : function(con) {
var ret = [];
var a = this.arr;
for(var i=0,ilen=a.length;i<ilen;i++) {
if(a[i] !=con) {
ret.push(a[i]);
}
}
return ret;
},
contain : function(el) {
var a = this.arr;
for(var i=0,ilen=a.length;i<ilen;i++) {
if(a[i]==el)return true;
}
return false;
}
}
Jtl.VarNode = function(content) {
this.content = content;
this.varstr = this.content;
}
Jtl.VarNode.prototype = {
render : function(context) {
return Jtl.getVar(context,this.varstr);
}
}
Jtl.TextNode = function(s) {
this.s = s;
}
Jtl.TextNode.prototype = {
render : function() {
return this.s;
}
}
Jtl.CommentNode = function() {}
Jtl.CommentNode.prototype = {
render : function(context) {
return '';
}
}
Jtl.ifNode = function(link_type,boolpairs,nodelist_true,nodelist_false) {
this.link_type = link_type;
this.boolpairs = boolpairs;
this.nodelist_true = nodelist_true;
this.nodelist_false = nodelist_false;
}
Jtl.ifNode.prototype = {
render : function(context) {
var flag;
if(this.link_type==Jtl.ifNode._and) {
flag = true;
if(this.map_boolpair(false,context))flag=false;
}
else{
flag = false;
if(this.map_boolpair(true,context))flag=true;
}
return flag ? Jtl.list_render(context,this.nodelist_true) : Jtl.list_render(context,this.nodelist_false);
},
map_boolpair : function(tc,context) {
var tmpbp = '';
var bp = this.boolpairs;
for(var i=0,ilen=bp.length;i<ilen;i++) {
tmpbp = bp[i].split(' ');
if(tmpbp.length==2&&tmpbp[0]=='not') {
if(tc!=!!Jtl.getVar(context,tmpbp[1]))return true;
}
else{
if(tc==!!Jtl.getVar(context,tmpbp[0]))return true;
}
}
return false;
}
}
Jtl.ifNode._and = 0;
Jtl.ifNode._or = 1;
Jtl.forNode = function(loopvar,sequence,reversed,nodelist_loop) {
this.loopvar = loopvar;
this.sequence = sequence.split('.');
this.reversed = reversed;
this.nodelist_loop = nodelist_loop;
}
Jtl.forNode.prototype = {
render : function(context) {
var ret = [];
var parentloop;
var items = context;
if(context['forloop']) {
parentloop = context['forloop'];
}
for(var k=0,klen=this.sequence.length;k<klen;k++) {
items = items[this.sequence[k]];
}
//items = context[this.sequence] ? context[this.sequence] : [];
if(!(items instanceof Array)) {throw new Error('values is not a array')}
if(this.reversed) {
items = items.reverse();
}
for(var i=0,ilen=items.length;i<ilen;i++) {
context['forloop'] = {
//shortcuts for current loop iteration number
'counter0': i,
'counter': i+1,
//reverse counter iteration numbers
'revcounter': ilen - i,
'revcounter0': ilen - i - 1,
//boolean values designating first and last times through loop
'first': (i == 0),
'last': (i == ilen - 1),
'parentloop': parentloop
}
context[this.loopvar] = items[i];
ret.push(Jtl.list_render(context,this.nodelist_loop));
}
context['forloop'] = undefined;
return ret.join('');
}
}
Jtl.Token = function(tokentype,content) {
this.token_type = tokentype;
if(this.token_type != Jtl.TEXT_TOKEN) {
content= content.replace(/^\s+|\s+$/g,'');
}
this.content = content;
}
Jtl.Token.prototype = {
tsplit : function() {}
}
Jtl.Parser = function(tokens) {
this.tokens = tokens;
}
Jtl.Parser.prototype = {
parse : function(parse_until) {
if(!parse_until)parse_until = [];
if(!(parse_until instanceof Array)) {
parse_until=[parse_until];
}
var nodelist = [];
var token = null;
var tagFuncName = null;
var tagFunc = null;
while(this.tokens.length) {
token = this.tokens.shift();
if(token.token_type==Jtl.TEXT_TOKEN) {
nodelist.push(new Jtl.TextNode(token.content));
}
else if(token.token_type==Jtl.VAR_TOKEN) {
nodelist.push(new Jtl.VarNode(token.content));
}
else if(token.token_type==Jtl.TAG_TOKEN) {
if(new Jtl.ArrayBuilder(parse_until).contain(token.content)) {
this.prepend_token(token);
return nodelist;
//´ý´¦Àí
}
tagFuncName = token.content.split(/\s+/)[0];
if(!tagFuncName)throw(new Error('Empty Tag'));
tagFunc = Jtl.tag[tagFuncName];
if(!tagFunc)throw(new Error('Unknow Tag'));
nodelist.push(tagFunc(this,token));
}
}
return nodelist;
},
skip_past : function(endtag) {
var token = null;
while(this.tokens.length) {
token = this.tokens.shift();
if(token.token_type==Jtl.TAG_TOKEN&&token.content==endtag) {
return;
}
}
throw(new Error('Not Closed Tag'));
},
prepend_token : function(token) {
this.tokens.unshift(token);
},
next_token : function() {
return this.tokens.shift();
},
delete_first_token : function() {
this.tokens.shift();
return true;
}
}
Jtl.tag = {};
(function() {
function do_if(parser,token) {
var bits = token.content.split(/\s+/);
var link_type,nodelist_true,nodelist_false;
bits.shift();
if(!bits) {throw(new Error('If node need at least one args'))}
var bitstr = bits.join(' ');
var boolpairs = bitstr.split(' or ');
if(boolpairs.length==1) {
link_type = Jtl.ifNode._and;
boolpairs = bitstr.split(' and ');
}
else{
link_type = Jtl.ifNode._or;
if(bitstr.indexOf(' and ')!=-1) {throw(new Error('If node do not alow mix "and" & "or"'))}
}
nodelist_true = parser.parse(['else','endif']);
token = parser.next_token();
if(token.content.indexOf('else')!=-1) {
nodelist_false = parser.parse('endif');
parser.delete_first_token();
}
else{
nodelist_false = [];
}
return new Jtl.ifNode(link_type,boolpairs,nodelist_true,nodelist_false);
}
Jtl.regTag('if',do_if);
function comment(parser,token) {
parser.skip_past('endcomment');
return new Jtl.CommentNode();
}
Jtl.regTag('comment',comment)
function do_for(parser,token) {
var bits = token.content.split(/\s+/);
if(bits.length==5&&bits[4] != 'reversed') {
throw(new Error('The 4 args of for tag must be reversed'));
}
if(!new Jtl.ArrayBuilder([4,5]).contain(bits.length)) {
throw(new Error('The for tag should have 4 or 5 args'));
}
if(bits[2] != 'in') {
throw(new Error('The 2nd args of for tag must be "in"'));
}
var loopvar = bits[1];
var sequence = bits[3];
var reversed = (bits.length==5);
var nodelist_loop = parser.parse('endfor');
parser.delete_first_token();
return new Jtl.forNode(loopvar,sequence,reversed,nodelist_loop);
}
Jtl.regTag('for',do_for);
})();
Jtl.filter = {};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment