whisker
Version: 
A Logic-Less & Extensible template engine with javascript
1,012 lines (942 loc) • 34.7 kB
JavaScript
/**
 * Whisker.js
 * A logic-less and extensible template engine
 * @author exolution
 * @version
 * v0.3.9 fix expression in defer
 * @change-log
 *      v0.3.8 add renderSimple &fix some bug
 *      v0.3.7 fix some bug
 *
 *
 * */
;
!function () {
    //解析状态
    var _ParseState = {OUT_BLOCK: 0,IN_BLOCK: 1},
        _config={},
        _simpleRenderReg,
        _regReserved=function(){//生成正则表达式的保留字表 用于决定是否转义
            var tok='^$()[]{}.?+*|'.split(''),res={};
            for(var i=0;i<tok.length;i++){
                res[tok[i]]=true;
            }
            return res;
        }();
    
    
    
    function setSimpleReg(begin,end){
        var simpleReg=begin+'$#'+end;
        simpleReg=simpleReg.replace(/./g,function(a){
            if(_regReserved[a]){
                return '\\'+a;
            }
            else if(a=='#'){
                return '([_a-zA-Z0-9]*)';
            }
            else {
                return a;
            }
        });
        return new RegExp(simpleReg,'g');
    }
   
    function Delimeter(begin,end){
        end=end||begin;
        if(begin.length+end.length<=4){
            this.beginNum=begin.length;
            this.begin=begin;
            if(this.beginNum>1){
                this.b=[begin.charAt(0),begin.charAt(1)];
            }
            this.endNum=end.length;
            this.end=end;
            if(this.endNum>1){
                this.e=[end.charAt(0),end.charAt(1)];
            }
        }
        else{
            console&&console.warn&&console.warn('max delimeter length is 2');
        }
        _simpleRenderReg=setSimpleReg(begin,end)
    }
    Delimeter.prototype={
        isBegin:function(ch,idx,text){
            if(this.beginNum==1){
                return ch==this.begin;
            }
            else{
                return ch==this.b[0]&&text.charAt(idx+1)==this.b[1];
            }
        },
        isEnd:function(ch,idx,text){
            if(this.endNum==1){
                return ch==this.end;
            }
            else{
                return ch==this.e[0]&&text.charAt(idx+1)==this.e[1];
            }
        }
    };
    _config.delimeter=new Delimeter('{','}');
    function Eval(exp){
        return new Function('','return '+exp)();
    }
    function Block(context,name, args, scope){
            this.result=[];
            this.branchStack= [];
            this.blockName=name;
            this.blockArgs=args;
            this.blockScope=scope ;
            this.type='block';
            this.idx=context.idx;
            this.parent=context.Block();
            this.context=context;
    }
    Block.prototype={
      resolve:function(){
      }
    };
    function Context(html, scope,partials) {//解析的上下文 整个模板解析过程都依赖这个结构
        scope=scope||{};
        this.blockStack = [ //块结构 用于处理嵌套结构
            {
                result: [],//一个块的结果集
                blockName: '',//块名字
                blockArgs: '',//块参数
                blockScope: scope,//块的作用域对象
                type: 'block',//块本身作为结果集里的一种节点 类型就是block
                branchStack: []//分支栈 用于处理if endif嵌套关系的栈
            }
        ];
        this.text = '';//当前解析的文本
        this.idx = 0;//解析索引
        this.skipMode = false;//忽略模式 最外层block的if语句会及时求值如果条件为false 则会进入忽略模式 忽略模式忽略所有内容的解析,直到if块的结束
        this.scope = scope;//全局的作用域对象 (整个模板被定义为一个块 最外层的块 这个记录该块的作用与对象 其实就是用户传给模板引擎的对象)
        this.blockContent = {};//储存解析过程中块的内容即{}之间的文本
        this.partials=partials||{};
        this.html = html;//模板
        this.htmlStack=[];
        this.deferEval = false;//延时求值模式 除了最外层的块 任何一个块里的解析都不会立即求值只会暂存 因为当前块的scope由上层块决定 此时无法确定
        this.Block = function () {
            return this.blockStack[this.blockStack.length - 1];
        };
        for (var i = 0; i < BlockMode.mode.length; i++) {
            this.blockContent[BlockMode.mode.charAt(i)] = '';
        }
    }
    Context.prototype = {
        startPartials:function(name){
            var partials = this.partials[name];
            if (partials) {
                this.htmlStack.push({html: this.html, idx: this.idx + 1});
                this.html = partials;
                this.idx = -1;
            }
            else {
                this.throwError('undefined partials! :"' + name + '"');
            }
        },
        endPartials:function(){
            if(this.idx>=this.html.length&&this.htmlStack.length>0){
                do{
                    var obj=this.htmlStack.pop();
                    this.html=obj.html;
                    this.idx=obj.idx;
                }
                while(this.idx>=this.html.length&&this.htmlStack.length>0);
            }
        },
        saveVar: function (exp, type) {//在当前结果集里储存一个节点 节点类型由type确定 可能是一个延时求值的节点 可能是一个嵌套的block
            this.Block().result.push({ exp: exp, type: type, idx: this.idx});
        },
        saveBranch: function (criteria) {//在当前的结果集里储存一个分支节点(即if 该节点结构中包含其else节点列表 以控制流程)
            var branchNode = {type: 'branch', exp: criteria, elseGroup: []};
            var block = this.Block();
            block.result.push(branchNode);
            block.branchStack.push(branchNode);
        },
        setElse: function (criteria, flag) {//在当前的结果集里储存一个else节点 并更新到当前的if节点的else列表中
            var block = this.Block();
            var branch = block.branchStack[block.branchStack.length - 1];
            var elseNode = {exp: criteria, If: branch, flag: flag, type: 'else'};
            if (!branch) {
                this.throwError('else or else if need a if in this block "' + block.blockName + '"', this);
                return;
            }
            var prevElse = branch.elseGroup[branch.elseGroup.length - 1];
            if (prevElse && prevElse.flag && !criteria) {
                this.throwError('else if or else can\'t after else', this);
            }
            if (!prevElse) {
                prevElse = branch;
            }
            prevElse.nextIndex = block.result.length;
            block.result.push(elseNode);
            branch.elseGroup.push(elseNode);
        },
        endBranch: function () {//闭合分支节点
            var block = this.Block();
            var branch = block.branchStack.pop();
            if (branch) {
                branch.endIndex = block.result.length;
                var lastElse = branch.elseGroup[branch.elseGroup.length - 1];
                if (lastElse) {
                    lastElse.nextIndex = block.result.length;
                }
            } else {
                this.throwError('{/if} need a if', this);
            }
        },
        newBlock: function (name, args, scope) {//新建block 其实就是当前block入栈
            this.blockStack.push({
                result: [],
                branchStack: [],
                blockName: name,
                blockArgs: args,
                blockScope: scope || this.Block().blockStack,
                type: 'block',
                idx: this.idx,
                parent: this.Block()
            });
        },
        closeBlock: function () {//结束当前block
            var cur = this.blockStack.pop();
            if (cur.branchStack.length > 0) {
                this.throwError('unclosed if!', this);
            }
            this.Block().result.push(cur);
            if (this.blockStack.length == 1) {
                this.deferEval = false;
            }
        },
        test: function (scope, exp) {//条件表达式测试
            var self = this, oldExp = exp, f;
            var m = /^(\!)?(\$\^?[$_a-zA-Z0-9.$]*)$/.exec(exp);//对于没有表达式的情况进行优化。
            if (m != null) {
                var obj=this.eval(scope,m[2].slice(1));
                if(obj instanceof Array){
                    f=obj.length>0;
                }
                else {
                    f=!!obj;
                }
                if(m[1]=='!'){
                    f=!f;
                }
                return f;
            }
            else {
                exp = exp.replace(/\$(\^?[_a-zA-Z0-9.$]*)/g, function (a, b) {
                    var v = self.eval(scope, b);
                    if (typeof v == 'string') {
                        v = '"' + v + '"';
                    }
                    else if (v == undefined) {
                        return 'undefined';
                    }
                    else if (typeof v == 'object') {
                        return !!v;
                    }
                    return v.toString();
                });
                try {
                    f = Eval(exp);
                }
                catch (e) {
                    this.throwError('criteria error :"' + oldExp + '" real value="' + exp + '"');
                }
            }
            return f;
        },
        invoke: function (scope, exp, bind) {//调用方法
            var ret = /\((.*)\)/.exec(exp);
            if (ret && ret[1]) {
                var tok = ret[1].split(','), args = [];
                for (var i = 0; i < tok.length; i++) {
                    var val = tok[i];
                    if (!isNaN(+val)) {
                        args.push(+val);
                    }
                    else if (val.charAt(0) == "'" && val.charAt(val.length - 1) == "'") {
                        args.push(val.slice(1,-1));
                    }
                    else if (/^\$\^?[_a-zA-Z0-9.$]*$/.test(val)) {
                        args.push(this.eval(scope, val.slice(1)));
                    }
                    else {
                        this.throwError('unrecognized arguments "' + val + '" for invoke "' + exp + '"', this);
                    }
                }
            }
            else {
                args = [];
            }
            i = exp.indexOf('(');
            if (i != -1) {
                exp = exp.slice(0, i);
            }
            var func = NativeMethod[exp] || this.eval(scope, exp);
            return func && func.apply(scope, args);
        },
        eval: function (scope, exp) {//求解变量的值
            var idx = 0,
                name,
                curObj = scope;
            if (!exp) {
                return scope;
            }
            exp = exp.replace(/\$([^.]*)/g, function (a, b) {//解析$表达式中的$xx替换成其值的字面量
                if(b==''){
                    return curObj;
                }
                else{
                    return curObj[b];
                }
            });
            if (exp.charAt(0) == '^') {
                curObj = this.scope;
                exp = exp.slice(1);
            }
            if (exp.charAt(0) == '.') {
                exp = exp.slice(1);
            }
            var tok = exp.split('.');
            while (name = tok[idx++]) {
                curObj = curObj[name];
                if (typeof curObj != 'object') {
                    break;
                }
            }
            if (curObj == undefined) {
                var curBlock = this.curBlock || this.Block();
                while (curBlock = curBlock.parent) {
                    curObj = curBlock.blockScope;
                    idx = 0;
                    while (name = tok[idx++]) {
                        curObj = curObj[name];
                        if (typeof curObj != 'object') {
                            break;
                        }
                    }
                    if (curObj != undefined) {
                        break;
                    }
                }
            }
            return curObj;
        },
        resolveBlock: function (block, scope) {//计算一个块的结果
            block = block || this.Block();
            scope = scope || block.blockScope;
            this.curBlock = block;
            block.blockScope = scope;
            var ctx = {i: 0, result: ''};
            for (ctx.i = 0; ctx.i < block.result.length; ctx.i++) {
                var varNode = block.result[ctx.i];
                if (typeof varNode == 'object') {
                    if (varNode.idx >= 0) {
                        this.idx = varNode.idx;
                    }
                    VarNodeManager.handlers[varNode.type].call(this, varNode, scope, ctx);
                }
                else {
                    ctx.result += varNode;
                }
            }
            return ctx.result;
        },
        throwError: function (msg) {//抛出错误 会显示错误位置
            msg = 'Parse Error:' + msg;
            var stack = this.html.substring(this.idx - 40, this.idx) + '"'
                + this.html.charAt(this.idx) + '"'
                + this.html.substring(this.idx + 1, this.idx + 40);
            if (console && typeof console.log == 'function') {
                console.log(msg + '\n at: ' + stack);
            }
            this.errorMsg = msg + '\n at: ' + stack;
            this.error = true;
        }
    };
    var Format = {
        flag:true,
        after: function (context) {
            if(this.flag){
                var nextChar = context.html.charAt(context.idx + 1);
                if (nextChar == '\n') {
                    context.idx++;
                }
                if (nextChar == '\r') {
                    context.idx++;
                    if (context.html.charAt(context.idx + 1) == '\n') {
                        context.idx++;
                    }
                }
            }
        },
        before: function (context) {//格式化
            if(this.flag){
                var result = context.Block().result,
                    prev = result[result.length - 1];
                if (typeof prev == 'string') {
                    var len = prev.length, ct = len;
                    while (prev.charAt(--len) == ' ') {
                        ct--;
                    }
                    result[result.length - 1] = prev.slice(0, ct)
                }
            }
        }
    };
    var NativeMethod = {
        add: function (name, func) {
            this[name] = func;
        }
    };
    var BlockMode = {//块模式管理器 $#/
        mode: '',
        addMode: function (mode, handler) {
            this.mode += mode;
            this.filter = new RegExp('[' + this.mode + ']');
            this.handlers[mode] = handler;
        },
        handlers: {}
    };
    function resolveChar(ch, context) {
        var nch, handler;
        if (context.state != _ParseState.IN_BLOCK && _config.delimeter.isBegin(ch,context.idx,context.html)) {
            nch = context.html.charAt(context.idx + _config.delimeter.beginNum);
            if (BlockMode.filter.test(nch)) {
                if (!context.skipMode) {
                    context.Block().result.push(context.text);
                    context.text = '';
                }
                context.state = _ParseState.IN_BLOCK;
                context.blockType = nch;
                handler = BlockMode.handlers[nch];
                handler.onStartBlock && handler.onStartBlock(context);
                context.idx+= _config.delimeter.beginNum;
            }
            else {
                if (!context.skipMode) {
                    context.text += ch;
                }
            }
        }
        else if (context.state == _ParseState.IN_BLOCK) {
            if (_config.delimeter.isEnd(ch,context.idx,context.html)) {
                context.state = _ParseState.OUT_BLOCK;
                var blockContent = context.blockContent[context.blockType];
                handler = BlockMode.handlers[context.blockType];
                context.blockContent[context.blockType] = '';
                handler.onEndBlock && handler.onEndBlock(blockContent, context);
                context.idx+=_config.delimeter.endNum-1;
            }
            else {
                handler = BlockMode.handlers[context.blockType];
                if (handler.onInBlock) {
                    handler.onInBlock(ch, context);
                }
                else {
                    context.blockContent[context.blockType] += ch;
                }
            }
        }
        else {
            if (!context.skipMode) {
                context.text += ch;
            }
        }
    }
    var VarNodeManager = {
        handlers: {},
        add: function (type, func) {
            this.handlers[type] = func;
        }
    };
    VarNodeManager.add('method', function (varNode, scope, ctx) {
        ctx.result += this.invoke(scope, varNode.exp);
    });
    VarNodeManager.add('expression',function(varNode,scope,ctx){
        var self=this;
        var exp = varNode.exp.replace(/\$(\^?[_a-zA-Z0-9.$]*)/g, function (a, b) {
            var v = self.eval(scope, b);
            if (typeof v == 'string') {
                v = '"' + v + '"';
            }
            else if (v == undefined) {
                return 'undefined';
            }
            else if (typeof v == 'object') {
                return !!v;
            }
            return v.toString();
        });
        try {
            var res = Eval(exp);
        } catch (e) {
            this.throwError('%express error:"' + varNode.exp + '" real value="' + exp + '"');
        }
        ctx.result+=res;
    });
    VarNodeManager.add('property', function (varNode, scope, ctx) {
        var flag=false;
        if(varNode.exp.indexOf('~')!=-1){
            flag=true;
            varNode.exp=varNode.exp.replace('~','');
        }
        var res= this.eval(scope, varNode.exp);
        if(res&&flag){
            res=res.replace('<','<').replace('>','>');
        }
        ctx.result +=res;
    });
    VarNodeManager.add('branch', function (varNode, scope, ctx) {
        var criteria = this.test(scope, varNode.exp);
        if (criteria) {
            varNode.flag = true;
        }
        else {
            varNode.flag = false;
            ctx.i = (varNode.nextIndex || varNode.endIndex) - 1;
        }
    });
    VarNodeManager.add('else', function (varNode, scope, ctx) {
        if (varNode.If.flag == true) {
            ctx.i = varNode.If.endIndex - 1;
        }
        else {
            if (varNode.flag || (varNode.flag == undefined && this.test(scope, varNode.exp))) {
                varNode.If.flag = true;
            }
            else {
                ctx.i = varNode.nextIndex - 1;
            }
        }
    });
    VarNodeManager.add('block', function (varNode, scope, ctx) {
        varNode.blockScope = scope;
        ctx.result += GroupManager[varNode.blockName].onClose.call(varNode.blockName, this, varNode);
    });
    var MethodHandler = {
        onEndBlock: function (blockContent, context) {
            if (!context.skipMode) {
                if (context.deferEval) {
                    context.saveVar(blockContent, 'method');
                }
                else {
                    var block = context.Block();
                    block.result.push(context.invoke(block.blockScope, blockContent));
                }
            }
        }
    };
    var PropertyHandler = {
        onEndBlock: function (blockContent, context) {
            if (!context.skipMode) {
                if (context.deferEval) {
                    context.saveVar(blockContent, 'property');
                }
                else {
                    var flag=false;
                    block = context.Block();
                    if(blockContent.indexOf('~')!=-1){
                        flag=true;
                        blockContent=blockContent.replace('~','');
                    }
                    var res = context.eval(block.blockScope, blockContent);
                    if (typeof res == 'object') {
                        res = res.toString();
                    }
                    if(flag&&res){
                        res=res.replace('<','<').replace('>','>');
                    }
                    block.result.push(res);
                }
            }
        },
        onInBlock: function (ch, context) {
            if (!context.skipMode) {
                if((ch=='~'&&context.blockContent[context.blockType].length==0)||/[$^_a-zA-Z0-9.]/.test(ch)){
                    context.blockContent[context.blockType] += ch;
                }
                else {
                    context.text += _config.delimeter.begin + context.blockType + context.blockContent[context.blockType] + ch;
                    context.state = _ParseState.OUT_BLOCK;
                }
            }
        }
    };
    var GroupHandler = {
        onStartBlock: Format.before,
        onEndBlock: function (blockContent, context) {
            var split = blockContent.indexOf(' ');
            if (split == -1) {
                split = blockContent.length;
            }
            var blockName = blockContent.substring(0, split),
                blockArgs = blockContent.slice(split + 1).replace(/^ *| *$/g, ''),
                group = GroupManager[blockName];
            if (group) {
                if (context.skipMode) {
                    group.onSkipBegin && group.onSkipBegin(context, blockArgs);
                }
                else {
                    group.onBegin && group.onBegin.call(blockName, context, blockArgs);
                }
            }
            else {
                if (console && console.log) {
                    console.log('Warning:unidentified group name:' + blockContent);
                }
            }
            Format.after(context);
        }
    };
    var EndGroupHandler = {
        onStartBlock: function (context) {
            var result = context.Block().result,
                prev = result[result.length - 1];
            if (typeof prev == 'string') {//格式化
                var len = prev.length, ct = len;
                while (prev.charAt(--len) == ' ') {
                    ct--;
                }
                result[result.length - 1] = prev.slice(0, ct)
            }
        },
        onEndBlock: function (blockContent, context) {
            var group = GroupManager[blockContent];
            if (group) {
                if (context.skipMode) {
                    group.onSkipClose && group.onSkipClose(context);
                } else {
                    if (group.onClose) {
                        var result = group.onClose.call(blockContent, context, context.Block());
                        if (result != undefined) {
                            context.Block().result.push(result);
                        }
                    }
                    else {
                        if (console && console.log) {
                            console.log('Warning:group "' + blockContent + '" need a onClose Handler');
                        }
                    }
                }
            }
            Format.after(context);
        }
    };
    BlockMode.addMode('$', PropertyHandler);
    BlockMode.addMode('#', GroupHandler);
    BlockMode.addMode('/', EndGroupHandler);
    BlockMode.addMode('@', MethodHandler);
    BlockMode.addMode('%', {
        onEndBlock: function (blockContent, context) {
            if (!context.skipMode) {
                if (context.deferEval) {
                    context.saveVar(blockContent, 'expression');
                } else {
                    var block = context.Block();
                    var exp = blockContent.replace(/\$(\^?[_a-zA-Z0-9.$]*)/g, function (a, b) {
                        var v = context.eval(block.blockScope, b);
                        if (typeof v == 'string') {
                            v = '"' + v + '"';
                        }
                        else if (v == undefined) {
                            return 'undefined';
                        }
                        else if (typeof v == 'object') {
                            return !!v;
                        }
                        return v.toString();
                    });
                    try {
                        var res = Eval(exp);
                    } catch (e) {
                        context.throwError('%express error:"' + blockContent + '" real value="' + exp + '"');
                    }
                    context.Block().result.push(res);
                }
            }
        }
    });
    BlockMode.addMode('<',{
        onEndBlock:function(blockContent,context){
            context.startPartials(blockContent);
        }
    });
    BlockMode.addMode('!', {
        onEndBlock: function (b, context) {
        }
    });
    var GroupManager = {
        register: function (name, handler) {
            this[name] = handler;
        }
    };
    var blockHandler = {
        onBegin: function (context, blockArgs) {
            context.newBlock(this.valueOf(), blockArgs, context.Block().blockScope);
            context.deferEval = true;
        },
        onClose: function (context, block) {
            if (block.blockName == this) {//this为当前的blockname
                if (!context.deferEval) {
                    return BlockManager[this](context, block);
                }
                else {
                    context.closeBlock();
                }
            }
            else {
                context.throwError('unmatched {/' + this + '}', context);
            }
        }
    };
    var BlockManager = {
        register: function (name, handler) {
            this[name] = handler;
            GroupManager.register(name, blockHandler);
        }
    };
    BlockManager.register('each', function (context, block) {
        var result = '';
        var params = /^\$(\^?[a-zA-Z0-9_.$]*)(?:\(\$([a-zA-Z0-9_]+)=>\$([a-zA-Z0-9_]+)\))?$/.exec(block.blockArgs);
        if (params) {
            block.blockScope = context.eval(block.blockScope, params[1]);
            if (params[2]) {
                var key = params[2];
                var val = params[3];
            }
            var list = block.blockScope;
            if (list) {
                if (list.length > 0) {
                    for (var i = 0; i < list.length; i++) {
                        if (key) {
                            var scope = {};
                            scope[key] = i;
                            scope[val] = list[i];
                        }
                        else {
                            scope = list[i];
                        }
                        result += context.resolveBlock(block, scope);
                    }
                }
                else {
                    for (var k in list) {
                        if (key) {
                            scope = {};
                            scope[key] = k;
                            scope[val] = list[k];
                        }
                        else {
                            scope = list[k];
                        }
                        result += context.resolveBlock(block, scope);
                    }
                }
            }
        }
        else context.throwError('can\'t resolve arguments of {each}:"' + block.blockArgs + '"');
        return result;
    });
    GroupManager.register('if', {
        onSkipBegin: function (context) {
            context.Block().branchStack.push(null);
        },
        onBegin: function (context, blockArgs) {
            if (context.deferEval) {
                context.saveBranch(blockArgs);
            }
            else {
                var criteria = context.test(context.Block().blockScope, blockArgs);
                if (criteria) {
                    context.skipMode = false;
                    context.Block().branchStack.push({flag: true});
                }
                else {
                    context.skipMode = true;
                    context.Block().branchStack.push({flag: false});
                }
            }
        },
        onSkipClose: function (context) {
            var If = context.Block().branchStack.pop();
            if (If) {
                context.skipMode = false;
            }
        },
        onClose: function (context) {
            if (context.deferEval) {
                context.endBranch();
                context.skipMode = false;
            }
            else {
                var If = context.Block().branchStack.pop();
                if (If) {
                    context.skipMode = false;
                }
                if (If == undefined) {
                    context.throwError('unmatched /if', context);
                }
            }
        }
    });
    GroupManager.register('else', {
        onSkipBegin: function (context) {
            var branchStack = context.Block().branchStack;
            var If = branchStack[branchStack.length - 1];
            if (If) {
                context.skipMode = If.flag;
            }
        },
        onBegin: function (context, blockArgs) {
            if (context.deferEval) {
                context.setElse('', true);
            }
            else {
                var branchStack = context.Block().branchStack;
                var If = branchStack[branchStack.length - 1];
                if (branchStack.length == 0) {
                    context.throwError('else need a if', context);
                }
                if (If) {
                    context.skipMode = If.flag;
                }
            }
        }
    });
    GroupManager.register('elseif', {
        onSkipBegin: function (context, blockArgs) {
            var branchStack = context.Block().branchStack,
                If = branchStack[branchStack.length - 1];
            if (If) {
                if (!If.flag) {
                    var criteria = context.test(context.Block().blockScope, blockArgs);
                    if (criteria) {
                        context.skipMode = false;
                        If.flag = true;
                    }
                } else {
                    context.skipMode = true;
                }
            }
        },
        onBegin: function (context, blockArgs) {
            if (context.deferEval) {
                context.setElse(blockArgs);
            }
            else {
                var branchStack = context.Block().branchStack;
                if (branchStack.length == 0) {
                    context.throwError('elseif need a if', context);
                }
                var If = branchStack[branchStack.length - 1];
                if (If) {
                    if (!If.flag) {
                        var criteria = context.test(context.Block().blockScope, blockArgs);
                        if (criteria) {
                            context.skipMode = false;
                            If.flag = true;
                        }
                    }
                    else {
                        context.skipMode = true;
                    }
                }
            }
        }
    });
    function render(html, data,partials) {
        if (!html) {
            return html;
        }
        var ch = '',
            context = new Context(html, data,partials);
        while (context.idx < context.html.length) {
            ch = context.html.charAt(context.idx);
            resolveChar(ch, context);
            if (context.error) {
                break;
            }
            context.idx++;
            context.endPartials();
        }
        if (context.error) {
            return context.errorMsg;
        }
        if (context.blockStack.length > 1) {
            var blockName=context.Block().blockName;
            context.throwError('{#' + blockName + '} need a close block "{/' + blockName + '}"');
        }
        context.Block().result.push(context.text);
        return context.resolveBlock();
    }
    function renderSimple(html,data){
        return html.replace(_simpleRenderReg,function(a,b){
            var val=data[b];
            if(val==undefined){
                return 'undefined';
            }
            else {
                return val.toString();
            }
        });
    }
    var whisker = {};
    whisker.Context = Context;
    whisker.GroupManager = GroupManager;
    whisker.BlockMode = BlockMode;
    whisker.render = render;
    whisker.renderSimple=renderSimple;
    whisker.tmpl=function(id){
        var el=document.getElementById(id);
        return el&&el.innerHTML;
    };
    whisker.setDelimeter=function(begin,end){
        _config.delimeter=new Delimeter(begin,end);
    };
    whisker.setFormat=function(flag){
        Format.flag=flag;
    };
    whisker.config=function(name,args){
        this['set'+name.charAt(0).toUpperCase()+name.slice(1)].apply(this,Array.prototype.slice.call(arguments,1));
    };
    whisker.register = function (name, handler) {
        BlockManager.register(name, handler);
    };
    whisker.register('repeat', function (context, block) {
        var args = block.blockArgs, result = '';
        if (args.charAt(0) == '$') {
            args = context.eval(block.blockScope, args.slice(1));
        }
        var n = +args;
        if (isNaN(n)) {
            context.throwError('repeat need a number as its argument! error param:"' + args + '"');
        }
        else {
            for (var i = 0; i < n; i++) {
                result += context.resolveBlock(block, {INDEX: i, SEQ: i + 1});
            }
        }
        return result;
    });
    if (typeof exports === "object" && exports) {
        module.exports = whisker;
    } else {
        if (typeof define === "function") {
            if (define.cmd) { //for cmd
                define(function (require, exports, module) {
                    module.exports = whisker;
                });
            }
            else if (define.amd) { //for amd
                define(whisker);
            }
            else {
                window.Whisker = whisker;
            }
        } else {
            window.Whisker = whisker; // for <script>
        }
    }
}();