/**
 * jTemplates 0.8 (http://jtemplates.tpython.com)
 * Copyright (c) 2007-2011 Tomasz Gloc (http://www.tpython.com)
 * 
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and/or GPL (GPL-LICENSE.txt) licenses.
 *
 * Id: $Id: jquery-jtemplates_uncompressed.js 187 2011-11-27 13:03:28Z tom $
 */
 
 /**
 * @fileOverview Template engine in JavaScript.
 * @name jTemplates
 * @author Tomasz Gloc
 * @date $Date: 2011-11-27 14:03:28 +0100 (N, 27 lis 2011) $
 */


if(window.jQuery && !window.jQuery.createTemplate) {(function (jQuery) {

    /**
    * [abstract]
    * @name BaseNode
    * @class Abstract node. [abstract]
    */

    /**
    * Process node and get the html string. [abstract]
    * @name get
    * @function
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    * @memberOf BaseNode
    */

    /**
    * [abstract]
    * @name BaseArray
    * @augments BaseNode
    * @class Abstract array/collection. [abstract]
    */

    /**
    * Add node 'e' to array.
    * @name push
    * @function
    * @param {BaseNode} e a node
    * @memberOf BaseArray
    */

    /**
    * See (http://jquery.com/).
    * @name jQuery
    * @class jQuery Library (http://jquery.com/)
    */

    /**
    * See (http://jquery.com/)
    * @name fn
    * @class jQuery Library (http://jquery.com/)
    * @memberOf jQuery
    */


    /**
    * Create new template from string s.
    * @name Template
    * @class A template or multitemplate.
    * @param {string} s A template string (like: "Text: {$T.txt}.").
    * @param {array} [includes] Array of included templates.
    * @param {object} [settings] Settings.
    * @config {boolean} [disallow_functions] Do not allow use function in data (default: true).
    * @config {boolean} [filter_data] Enable filter data using escapeHTML (default: true).
    * @config {boolean} [filter_params] Enable filter parameters using escapeHTML (default: false).
    * @config {boolean} [runnable_functions] Automatically run function (from data) inside {} [default: false].
    * @config {boolean} [clone_data] Clone input data [default: true]
    * @config {boolean} [clone_params] Clone input parameters [default: true]
    * @config {Function} [f_cloneData] Function used to data cloning
    * @config {Function} [f_escapeString] Function used to escape strings
    * @config {Function} [f_parseJSON] Function used to parse JSON
    * @augments BaseNode
    */
    var Template = function (s, includes, settings) {
        this._tree = [];
        this._param = {};
        this._includes = null;
        this._templates = {};
        this._templates_code = {};
        this._maintemplate = null;

        //default parameters
        this.settings = jQuery.extend({
            disallow_functions: false,
            filter_data: true,
            filter_params: false,
            runnable_functions: false,
            clone_data: true,
            clone_params: true
        }, settings);

        //set handlers
        this.f_cloneData = (this.settings.f_cloneData !== undefined) ? (this.settings.f_cloneData) : (TemplateUtils.cloneData);
        this.f_escapeString = (this.settings.f_escapeString !== undefined) ? (this.settings.f_escapeString) : (TemplateUtils.escapeHTML);
        this.f_parseJSON = (this.settings.f_parseJSON !== undefined) ? (this.settings.f_parseJSON) : ((this.settings.disallow_functions) ? (jQuery.parseJSON) : (TemplateUtils.parseJSON));

        if (s == null) {
            return;
        }

        //split multiteplate
        this.splitTemplates(s, includes);

        if (s) {
            //set main template
            this.setTemplate(this._templates_code['MAIN'], includes, this.settings, this);
        }

        this._templates_code = null;
    };

    /**
    * jTemplates version
    * @type string
    */
    Template.prototype.version = '0.8';

    /**
    * Debug mode (all errors are on), default: on
    * @type Boolean
    */
    Template.DEBUG_MODE = false;

    /**
    * Foreach loop limit (enable only when DEBUG_MODE = true)
    * @type integer
    */
    Template.FOREACH_LOOP_LIMIT = 10000;

    /**
    * Global guid
    * @type integer
    */
    Template.guid = 0;

    /**
    * Split multitemplate into multiple templates.
    * @param {string} s A template string (like: "Text: {$T.txt}.").
    * @param {array} includes Array of included templates.
    */
    Template.prototype.splitTemplates = function (s, includes) {
        var reg = /\{#template *(\w+) *(.*?) *\}/g; //split multitemplate into subtemplates
        var iter, tname, se;
        var lastIndex = null;

        var _template_settings = [];

        //while find new subtemplate
        while ((iter = reg.exec(s)) != null) {
            lastIndex = reg.lastIndex;
            tname = iter[1];
            se = s.indexOf('{#/template ' + tname + '}', lastIndex);
            if (se == -1) {
                throw new Error('jTemplates: Template "' + tname + '" is not closed.');
            }
            //save a subtemplate and parse options
            this._templates_code[tname] = s.substring(lastIndex, se);
            _template_settings[tname] = TemplateUtils.optionToObject(iter[2]);
        }
        //when no subtemplates, use all as main template
        if (lastIndex === null) {
            this._templates_code['MAIN'] = s;
            return;
        }

        //create a new object for every subtemplates
        for (var i in this._templates_code) {
            if (i != 'MAIN') {
                this._templates[i] = new Template();
            }
        }
        for (var i in this._templates_code) {
            if (i != 'MAIN') {
                this._templates[i].setTemplate(this._templates_code[i],
					jQuery.extend({}, includes || {}, this._templates || {}),
					jQuery.extend({}, this.settings, _template_settings[i]),
					this);
                this._templates_code[i] = null;
            }
        }
    };

    /**
    * Parse template. (should be template, not multitemplate).
    * @param {string} s A template string (like: "Text: {$T.txt}.").
    * @param {array} includes Array of included templates.
    * @param {object} [settings] Settings.
    * @param {object} maintemplate Main template
    */
    Template.prototype.setTemplate = function (s, includes, settings, maintemplate) {
        if (s == undefined) {
            this._tree.push(new TextNode('', 1, this));
            return;
        }
        s = s.replace(/[\n\r]/g, ''); //remove endlines
        s = s.replace(/\{\*.*?\*\}/g, ''); //remove comments
        this._includes = jQuery.extend({}, this._templates || {}, includes || {});
        this.settings = new Object(settings);
        this._maintemplate = maintemplate;
        var node = this._tree;
        var op = s.match(/\{#.*?\}/g); //find operators
        var ss = 0, se = 0;
        var e;
        var literalMode = 0;
        var elseif_level = 0;

        //loop operators
        for (var i = 0, l = (op) ? (op.length) : (0); i < l; ++i) {
            var this_op = op[i];

            //when literal mode is on, treat operator like a text
            if (literalMode) {
                se = s.indexOf('{#/literal}');
                if (se == -1) {
                    throw new Error("jTemplates: No end of literal.");
                }
                if (se > ss) {
                    node.push(new TextNode(s.substring(ss, se), 1, this));
                }
                ss = se + 11;
                literalMode = 0;
                i = jQuery.inArray('{#/literal}', op);
                continue;
            }

            se = s.indexOf(this_op, ss);
            if (se > ss) {
                node.push(new TextNode(s.substring(ss, se), literalMode, this));
            }
            var ppp = this_op.match(/\{#([\w\/]+).*?\}/); //find operator name
            var op_ = RegExp.$1;
            switch (op_) {
                case 'elseif':
                    ++elseif_level;
                    node.switchToElse();
                    //no break
                case 'if':
                    e = new opIF(this_op, node, this);
                    node.push(e);
                    node = e;
                    break;
                case 'else':
                    node.switchToElse();
                    break;
                case '/if':
                    while (elseif_level) {
                        node = node.getParent();
                        --elseif_level;
                    }
                    //no break
                case '/for':
                case '/foreach':
                    node = node.getParent();
                    break;
                case 'foreach':
                    e = new opFOREACH(this_op, node, this);
                    node.push(e);
                    node = e;
                    break;
                case 'for':
                    e = opFORFactory(this_op, node, this);
                    node.push(e);
                    node = e;
                    break;
                case 'continue':
                case 'break':
                    node.push(new JTException(op_));
                    break;
                case 'include':
                    node.push(new Include(this_op, this._includes, this));
                    break;
                case 'param':
                    node.push(new UserParam(this_op, this));
                    break;
                case 'var':
                    node.push(new UserVariable(this_op, this));
                    break;
                case 'cycle':
                    node.push(new Cycle(this_op));
                    break;
                case 'ldelim':
                    node.push(new TextNode('{', 1, this));
                    break;
                case 'rdelim':
                    node.push(new TextNode('}', 1, this));
                    break;
                case 'literal':
                    literalMode = 1;
                    break;
                case '/literal':
                    if (Template.DEBUG_MODE) {
                        throw new Error("jTemplates: Missing begin of literal.");
                    }
                    break;
                default:
                    if (Template.DEBUG_MODE) {
                        throw new Error('jTemplates: unknown tag: ' + op_ + '.');
                    }
            }

            ss = se + this_op.length;
        }

        if (s.length > ss) {
            node.push(new TextNode(s.substr(ss), literalMode, this));
        }
    };

    /**
    * Process template and get the html string.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    Template.prototype.get = function (d, param, element, deep) {
        ++deep;

        if (this._maintemplate == this && element != undefined) {
            jQuery.removeData(element, "jTemplatesRef");
        }

        var $T = d, _param1, _param2;
        this.EvalObj = new EvalClass(this);

        //create clone of data
        if (this.settings.clone_data) {
            $T = this.f_cloneData(d, { escapeData: (this.settings.filter_data && deep == 1), noFunc: this.settings.disallow_functions }, this.f_escapeString);
        }

        //create clone of parameters
        if (!this.settings.clone_params) {
            _param1 = this._param;
            _param2 = param;
        } else {
            _param1 = this.f_cloneData(this._param, { escapeData: (this.settings.filter_params), noFunc: false }, this.f_escapeString);
            _param2 = this.f_cloneData(param, { escapeData: (this.settings.filter_params && deep == 1), noFunc: false }, this.f_escapeString);
        }
        //split object and local parameters
        var $P = jQuery.extend({}, _param1, _param2);

        var $Q = (element != undefined) ? (element) : ({});
        $Q.version = this.version;

        var ret = '';
        for (var i = 0, l = this._tree.length; i < l; ++i) {
            ret += this._tree[i].get($T, $P, $Q, deep);
        }

        this.EvalObj = null;

        --deep;
        return ret;
    };

    /**
    * Set to parameter 'name' value 'value'.
    * @param {string} name
    * @param {object} value
    */
    Template.prototype.setParam = function (name, value) {
        this._param[name] = value;
    };


    /**
    * Template utilities.
    * @namespace Template utilities.
    */
    TemplateUtils = function () {
    };

    /**
    * Replace chars &, >, <, ", ' with html entities.
    * To disable function set settings: filter_data=false, filter_params=false
    * @param {string} string
    * @return {string}
    * @static
    * @memberOf TemplateUtils
    */
    TemplateUtils.escapeHTML = function (txt) {
        return txt.replace(/&/g, '&amp;').replace(/>/g, '&gt;').replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
    };

    /**
    * Make a copy od data 'd'. It also filters data (depend on 'filter').
    * @param {object} d input data
    * @param {object} filter a filters
    * @config {boolean} [escapeData] Use escapeHTML on every string.
    * @config {boolean} [noFunc] Do not allow to use function (throws exception).
    * @param {Function} f_escapeString function using to filter string (usually: TemplateUtils.escapeHTML)
    * @return {object} output data (filtered)
    * @static
    * @memberOf TemplateUtils
    */
    TemplateUtils.cloneData = function (d, filter, f_escapeString) {
        if (d == null) {
            return d;
        }
        switch (d.constructor) {
            case Object:
                var o = {};
                for (var i in d) {
                    o[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString);
                }
                if (!filter.noFunc) {
                    if (d.hasOwnProperty("toString"))
                        o.toString = d.toString;
                }
                return o;
            case Array:
                var o = [];
                for (var i = 0, l = d.length; i < l; ++i) {
                    o[i] = TemplateUtils.cloneData(d[i], filter, f_escapeString);
                }
                return o;
            case String:
                return (filter.escapeData) ? (f_escapeString(d)) : (d);
            case Function:
                if (filter.noFunc) {
                    if (Template.DEBUG_MODE)
                        throw new Error("jTemplates: Functions are not allowed.");
                    else
                        return undefined;
                }
                //no break
            default:
                return d;
        }
    };

    /**
    * Convert text-based option string to Object
    * @param {string} optionText text-based option string
    * @return {Object}
    * @static
    * @memberOf TemplateUtils
    */
    TemplateUtils.optionToObject = function (optionText) {
        if (optionText === null || optionText === undefined) {
            return {};
        }

        var o = optionText.split(/[= ]/);
        if (o[0] === '') {
            o.shift();
        }

        var obj = {};
        for (var i = 0, l = o.length; i < l; i += 2) {
            obj[o[i]] = o[i + 1];
        }

        return obj;
    };

    /**
    * Parse JSON string into object
    * @param {string} data Text JSON
    * @return {Object}
    * @static
    */
    TemplateUtils.parseJSON = function (data) {
        if (typeof data !== "string" || !data) {
            return null;
        }
        try {
            return (new Function("return " + jQuery.trim(data)))();
        } catch (e) {
            if (Template.DEBUG_MODE) {
                throw new Error("jTemplates: Invalid JSON");
            }
            return {};
        }
    };

    /**
    * Find parents nodes for a reference value and return it
    * @param {Element} el html element
    * @param {int} guid template process unique identificator
    * @param {int} id index
    * @return {object}
    * @static
    */
    TemplateUtils.ReturnRefValue = function (el, guid, id) {
        while (true) {
            if (el == null) {
                return null;
            }
            var d = jQuery.data(el, 'jTemplatesRef');
            if (d != undefined && d.guid == guid && d.d[id] != undefined) {
                return d.d[id];
            }
            el = el.parentNode;
        }
    };

    /**
    * Create a new text node.
    * @name TextNode
    * @class All text (block {..}) between control's block "{#..}".
    * @param {string} val text string
    * @param {boolean} literalMode When enable (true) template does not process blocks {..}.
    * @param {Template} Template object
    * @augments BaseNode
    */
    var TextNode = function (val, literalMode, template) {
        this._value = val;
        this._literalMode = literalMode;
        this._template = template;
    };

    /**
    * Get the html string for a text node.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    TextNode.prototype.get = function (d, param, element, deep) {
        if (this._literalMode) {
            return this._value;
        }
        var s = this._value;
        var result = "";
        var i = -1;
        var nested = 0;
        var sText = -1;
        var sExpr = 0;
        while (true) {
            var lm = s.indexOf("{", i + 1);
            var rm = s.indexOf("}", i + 1);
            if (lm < 0 && rm < 0) {
                break;
            }
            if ((lm != -1 && lm < rm) || (rm == -1)) {
                i = lm;
                if (++nested == 1) {
                    sText = lm;
                    result += s.substring(sExpr, i);
                    sExpr = -1;
                }
            } else {
                i = rm;
                if (--nested === 0) {
                    if (sText >= 0) {
                        result += this._template.EvalObj.evaluateContent(d, param, element, s.substring(sText, rm + 1));
                        sText = -1;
                        sExpr = i + 1;
                    }
                } else if (nested < 0) {
                    nested = 0;
                }
            }
        }
        if (sExpr > -1) {
            result += s.substr(sExpr);
        }
        return result;
    };

    /**
    * Virtual context for eval() (internal class)
    * @name EvalClass
    * @class Virtual bin for eval() evaluation
    * @param {Template} t template
    * @private
    */
    EvalClass = function (t) {
        this.__templ = t;
    };

    /**
    * Evaluate expression (template content)
    * @param {object} $T data
    * @param {object} $P parameters
    * @param {object} $Q element
    * @param {String} __value Template content
    * @return {String}
    */
    EvalClass.prototype.evaluateContent = function ($T, $P, $Q, __value) {
        try {
            var result = eval(__value);

            if (jQuery.isFunction(result)) {
                if (this.__templ.settings.disallow_functions || !this.__templ.settings.runnable_functions) {
                    return '';
                }
                result = result($T, $P, $Q);
            }
            return (result === undefined) ? ("") : (String(result));
        } catch (e) {
            if (Template.DEBUG_MODE) {
                if (e instanceof JTException) {
                    e.type = "subtemplate";
                }
                throw e;
            }
            return "";
        }
    };

    /**
    * Evaluate expression (simple eval)
    * @param {object} $T data
    * @param {object} $P parameters
    * @param {object} $Q element
    * @param {String} __value content to evaluate
    * @return {String}
    */
    EvalClass.prototype.evaluate = function ($T, $P, $Q, __value) {
        return eval(__value);
    };

    /**
    * Create a new conditional node.
    * @name opIF
    * @class A class represent: {#if}.
    * @param {string} oper content of operator {#..}
    * @param {object} par parent node
    * @param {Template} templ template
    * @augments BaseArray
    */
    var opIF = function (oper, par, templ) {
        this._parent = par;
        oper.match(/\{#(?:else)*if (.*?)\}/);
        this._cond = RegExp.$1;
        this._onTrue = [];
        this._onFalse = [];
        this._currentState = this._onTrue;
        this._templ = templ;
    };

    /**
    * Add node 'e' to array.
    * @param {BaseNode} e a node
    */
    opIF.prototype.push = function (e) {
        this._currentState.push(e);
    };

    /**
    * Get a parent node.
    * @return {BaseNode}
    */
    opIF.prototype.getParent = function () {
        return this._parent;
    };

    /**
    * Switch from collection onTrue to onFalse.
    */
    opIF.prototype.switchToElse = function () {
        this._currentState = this._onFalse;
    };

    /**
    * Process node depend on conditional and get the html string.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    opIF.prototype.get = function (d, param, element, deep) {
        var ret = '';

        try {
            var arr = (this._templ.EvalObj.evaluate(d, param, element, this._cond)) ? (this._onTrue) : (this._onFalse);
            for (var i = 0, l = arr.length; i < l; ++i) {
                ret += arr[i].get(d, param, element, deep);
            }
        } catch (e) {
            if (Template.DEBUG_MODE || (e instanceof JTException)) {
                throw e;
            }
        }
        return ret;
    };

    /**
    * Handler for a tag 'FOR'. Create new and return relative opFOREACH object.
    * @name opFORFactory
    * @class Handler for a tag 'FOR'. Create new and return relative opFOREACH object.
    * @param {string} oper content of operator {#..}
    * @param {object} par parent node
    * @param {Template} template a pointer to Template object
    * @return {opFOREACH}
    */
    opFORFactory = function (oper, par, template) {
        //create operator FOREACH with function as iterator
        if (oper.match(/\{#for (\w+?) *= *(\S+?) +to +(\S+?) *(?:step=(\S+?))*\}/)) {
            oper = '{#foreach (function(i){return i;}) as ' + RegExp.$1 + ' begin=' + (RegExp.$2 || 0) + ' end=' + (RegExp.$3 || -1) + ' step=' + (RegExp.$4 || 1) + ' extData=$T}';
            return new opFOREACH(oper, par, template);
        } else {
            throw new Error('jTemplates: Operator failed "find": ' + oper);
        }
    };

    /**
    * Create a new loop node.
    * @name opFOREACH
    * @class A class represent: {#foreach}.
    * @param {string} oper content of operator {#..}
    * @param {object} par parent node
    * @param {Template} template a pointer to Template object
    * @augments BaseArray
    */
    var opFOREACH = function (oper, par, template) {
        this._parent = par;
        this._template = template;
        oper.match(/\{#foreach +(.+?) +as +(\w+?)( .+)*\}/);
        this._arg = RegExp.$1;
        this._name = RegExp.$2;
        this._option = RegExp.$3 || null;
        this._option = TemplateUtils.optionToObject(this._option);

        this._onTrue = [];
        this._onFalse = [];
        this._currentState = this._onTrue;
    };

    /**
    * Add node 'e' to array.
    * @param {BaseNode} e
    */
    opFOREACH.prototype.push = function (e) {
        this._currentState.push(e);
    };

    /**
    * Get a parent node.
    * @return {BaseNode}
    */
    opFOREACH.prototype.getParent = function () {
        return this._parent;
    };

    /**
    * Switch from collection onTrue to onFalse.
    */
    opFOREACH.prototype.switchToElse = function () {
        this._currentState = this._onFalse;
    };

    /**
    * Process loop and get the html string.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    opFOREACH.prototype.get = function (d, param, element, deep) {
        try {
            var fcount = this._template.EvalObj.evaluate(d, param, element, this._arg); //array of elements in foreach
            if (fcount === $) {
                throw new Error("jTemplate: Variable '$' cannot be used as loop-function");
            }
            var key = []; //only for objects
            var mode = typeof fcount;
            if (mode == 'object') {
                //transform object to array
                var arr = [];
                jQuery.each(fcount, function (k, v) {
                    key.push(k);
                    arr.push(v);
                });
                fcount = arr;
            }
            //setup primary iterator, iterator can get data from options (using by operator FOR) or from data "$T"
            var extData = (this._option.extData !== undefined)
				? (this._template.EvalObj.evaluate(d, param, element, this._option.extData))
				: ((d != null) ? (d) : ({}));
            if (extData == null) {
                extData = {};
            }
            //calc start, end and step
            var s = Number(this._template.EvalObj.evaluate(d, param, element, this._option.begin) || 0), e; //start, end
            var step = Number(this._template.EvalObj.evaluate(d, param, element, this._option.step) || 1);
            if (mode != 'function') {
                e = fcount.length;
            } else {
                if (this._option.end === undefined || this._option.end === null) {
                    e = Number.MAX_VALUE;
                } else {
                    e = Number(this._template.EvalObj.evaluate(d, param, element, this._option.end)) + ((step > 0) ? (1) : (-1));
                }
            }
            var ret = ''; //result string
            var i, l; //local iterators

            if (this._option.count) {
                //limit number of loops
                var tmp = s + Number(this._template.EvalObj.evaluate(d, param, element, this._option.count));
                e = (tmp > e) ? (e) : (tmp);
            }

            if ((e > s && step > 0) || (e < s && step < 0)) {
                var iteration = 0;
                var _total = (mode != 'function') ? (Math.ceil((e - s) / step)) : undefined;
                var ckey, cval; //current key, current value
                var loopCounter = 0;
                for (; ((step > 0) ? (s < e) : (s > e)); s += step, ++iteration, ++loopCounter) {
                    if (Template.DEBUG_MODE && loopCounter > Template.FOREACH_LOOP_LIMIT) {
                        throw new Error("jTemplate: Foreach loop limit was exceed");
                    }
                    ckey = key[s];
                    if (mode != 'function') {
                        cval = fcount[s];  //get value from array
                    } else {
                        cval = fcount(s);  //calc function
                        //if no result from function then stop foreach
                        if (cval === undefined || cval === null) {
                            break;
                        }
                    }
                    if ((typeof cval == 'function') && (this._template.settings.disallow_functions || !this._template.settings.runnable_functions)) {
                        continue;
                    }
                    if ((mode == 'object') && (ckey in Object) && (cval === Object[ckey])) {
                        continue;
                    }
                    //backup on value
                    var prevValue = extData[this._name];
                    //set iterator properties
                    extData[this._name] = cval;
                    extData[this._name + '$index'] = s;
                    extData[this._name + '$iteration'] = iteration;
                    extData[this._name + '$first'] = (iteration == 0);
                    extData[this._name + '$last'] = (s + step >= e);
                    extData[this._name + '$total'] = _total;
                    extData[this._name + '$key'] = (ckey !== undefined && ckey.constructor == String) ? (this._template.f_escapeString(ckey)) : (ckey);
                    extData[this._name + '$typeof'] = typeof cval;
                    for (i = 0, l = this._onTrue.length; i < l; ++i) {
                        try {
                            ret += this._onTrue[i].get(extData, param, element, deep);
                        } catch (ex) {
                            if (ex instanceof JTException) {
                                switch (ex.type) {
                                    case 'continue':
                                        i = l; //force skip to next node
                                        break;
                                    case 'break':
                                        i = l;  //force skip to next node
                                        s = e;  //force skip outsite foreach
                                        break;
                                    default:
                                        throw ex;
                                }
                            } else {
                                throw ex;
                            }
                        }
                    }
                    //restore values
                    delete extData[this._name + '$index'];
                    delete extData[this._name + '$iteration'];
                    delete extData[this._name + '$first'];
                    delete extData[this._name + '$last'];
                    delete extData[this._name + '$total'];
                    delete extData[this._name + '$key'];
                    delete extData[this._name + '$typeof'];
                    delete extData[this._name];
                    extData[this._name] = prevValue;
                }
            } else {
                //no items to loop ("foreach->else")
                for (i = 0, l = this._onFalse.length; i < l; ++i) {
                    ret += this._onFalse[i].get(d, param, element, deep);
                }
            }
            return ret;
        } catch (e) {
            if (Template.DEBUG_MODE || (e instanceof JTException)) {
                throw e;
            }
            return "";
        }
    };

    /**
    * Template-control exceptions
    * @name JTException
    * @class A class used internals for a template-control exceptions
    * @param type {string} Type of exception
    * @augments Error
    * @augments BaseNode
    */
    var JTException = function (type) {
        this.type = type;
    };
    JTException.prototype = Error;

    /**
    * Throw a template-control exception
    * @throws It throws itself
    */
    JTException.prototype.get = function (d) {
        throw this;
    };

    /**
    * Create a new entry for included template.
    * @name Include
    * @class A class represent: {#include}.
    * @param {string} oper content of operator {#..}
    * @param {array} includes
    * @param {Template} templ template
    * @augments BaseNode
    */
    var Include = function (oper, includes, templ) {
        oper.match(/\{#include (.*?)(?: root=(.*?))?\}/);
        this._template = includes[RegExp.$1];
        if (this._template == undefined) {
            if (Template.DEBUG_MODE)
                throw new Error('jTemplates: Cannot find include: ' + RegExp.$1);
        }
        this._root = RegExp.$2;
        this._mainTempl = templ;
    };

    /**
    * Run method get on included template.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    Include.prototype.get = function (d, param, element, deep) {
        try {
            //run a subtemplates with a new root node
            return this._template.get(this._mainTempl.EvalObj.evaluate(d, param, element, this._root), param, element, deep);
        } catch (e) {
            if (Template.DEBUG_MODE || (e instanceof JTException)) {
                throw e;
            }
        }
        return '';
    };

    /**
    * Create new node for {#param}.
    * @name UserParam
    * @class A class represent: {#param}.
    * @param {string} oper content of operator {#..}
    * @param {Template} templ template
    * @augments BaseNode
    */
    var UserParam = function (oper, templ) {
        oper.match(/\{#param name=(\w*?) value=(.*?)\}/);
        this._name = RegExp.$1;
        this._value = RegExp.$2;
        this._templ = templ;
    };

    /**
    * Return value of selected parameter.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String} empty string
    */
    UserParam.prototype.get = function (d, param, element, deep) {
        try {
            param[this._name] = this._templ.EvalObj.evaluate(d, param, element, this._value);
        } catch (e) {
            if (Template.DEBUG_MODE || (e instanceof JTException)) {
                throw e;
            }
            param[this._name] = undefined;
        }
        return '';
    };

    /**
    * Create new node for {#var}.
    * @name UserVariable
    * @class A class represent: {#var}.
    * @param {string} oper content of operator {#..}
    * @param {Template} templ template
    * @augments BaseNode
    */
    var UserVariable = function (oper, templ) {
        oper.match(/\{#var (.*?)\}/);
        this._id = RegExp.$1;
        this._templ = templ;
    };

    /**
    * Return value of selected variable.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String} calling of function ReturnRefValue (as text string)
    */
    UserVariable.prototype.get = function (d, param, element, deep) {
        try {
            if (element == undefined) {
                return "";
            }
            var obj = this._templ.EvalObj.evaluate(d, param, element, this._id);
            var refobj = jQuery.data(element, "jTemplatesRef");
            if (refobj == undefined) {
                refobj = { guid: ++Template.guid, d: [] };
            }
            var i = refobj.d.push(obj);
            jQuery.data(element, "jTemplatesRef", refobj);
            return "(TemplateUtils.ReturnRefValue(this," + refobj.guid + "," + (i - 1) + "))";
        } catch (e) {
            if (Template.DEBUG_MODE || (e instanceof JTException)) {
                throw e;
            }
            return '';
        }
    };

    /**
    * Create a new cycle node.
    * @name Cycle
    * @class A class represent: {#cycle}.
    * @param {string} oper content of operator {#..}
    * @augments BaseNode
    */
    var Cycle = function (oper) {
        oper.match(/\{#cycle values=(.*?)\}/);
        this._values = eval(RegExp.$1);
        this._length = this._values.length;
        if (this._length <= 0) {
            throw new Error('jTemplates: cycle has no elements');
        }
        this._index = 0;
        this._lastSessionID = -1;
    };

    /**
    * Do a step on cycle and return value.
    * @param {object} d data
    * @param {object} param parameters
    * @param {Element} element a HTML element
    * @param {Number} deep
    * @return {String}
    */
    Cycle.prototype.get = function (d, param, element, deep) {
        var sid = jQuery.data(element, 'jTemplateSID');
        if (sid != this._lastSessionID) {
            this._lastSessionID = sid;
            this._index = 0;
        }
        var i = this._index++ % this._length;
        return this._values[i];
    };


    /**
    * Add a Template to HTML Elements.
    * @param {Template/string} s a Template or a template string
    * @param {array} [includes] Array of included templates.
    * @param {object} [settings] Settings (see Template)
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.setTemplate = function (s, includes, settings) {
        if (s.constructor === Template) {
            return jQuery(this).each(function () {
                jQuery.data(this, 'jTemplate', s);
                jQuery.data(this, 'jTemplateSID', 0);
            });
        } else {
            return jQuery(this).each(function () {
                jQuery.data(this, 'jTemplate', new Template(s, includes, settings));
                jQuery.data(this, 'jTemplateSID', 0);
            });
        }
    };

    /**
    * Add a Template (from URL) to HTML Elements.
    * @param {string} url_ URL to template
    * @param {array} [includes] Array of included templates.
    * @param {object} [settings] Settings (see Template)
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.setTemplateURL = function (url_, includes, settings) {
        var s = jQuery.ajax({
            url: url_,
            dataType: 'text',
            async: false
        }).responseText;

        return jQuery(this).setTemplate(s, includes, settings);
    };

    /**
    * Create a Template from element's content.
    * @param {string} elementName an ID of element
    * @param {array} [includes] Array of included templates.
    * @param {object} [settings] Settings (see Template)
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.setTemplateElement = function (elementName, includes, settings) {
        var s = jQuery('#' + elementName).val();
        if (s == null) {
            s = jQuery('#' + elementName).html();
            s = s.replace(/&lt;/g, "<").replace(/&gt;/g, ">");
        }
        
        s = jQuery.trim(s);
        s = s.replace(/^<\!\[CDATA\[([\s\S]*)\]\]>$/im, '$1');
        s = s.replace(/^<\!--([\s\S]*)-->$/im, '$1');

        return jQuery(this).setTemplate(s, includes, settings);
    };

    /**
    * Check it HTML Elements have a template. Return count of templates.
    * @return {number} Number of templates.
    * @memberOf jQuery.fn
    */
    jQuery.fn.hasTemplate = function () {
        var count = 0;
        jQuery(this).each(function () {
            if (jQuery.getTemplate(this)) {
                ++count;
            }
        });
        return count;
    };

    /**
    * Remote Template from HTML Element(s)
    * @return {jQuery} chainable jQuery class
    */
    jQuery.fn.removeTemplate = function () {
        jQuery(this).processTemplateStop();
        return jQuery(this).each(function () {
            jQuery.removeData(this, 'jTemplate');
        });
    };

    /**
    * Set to parameter 'name' value 'value'.
    * @param {string} name
    * @param {object} value
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.setParam = function (name, value) {
        return jQuery(this).each(function () {
            var t = jQuery.getTemplate(this);
            if (t === undefined) {
                if (Template.DEBUG_MODE)
                    throw new Error('jTemplates: Template is not defined.');
                else
                    return;
            }
            t.setParam(name, value);
        });
    };

    /**
    * Process template using data 'd' and parameters 'param'. Update HTML code.
    * @param {object} d data 
    * @param {object} [param] parameters
    * @option {object} [options] internal use only
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.processTemplate = function (d, param, options) {
        return jQuery(this).each(function () {
            var t = jQuery.getTemplate(this);
            if (t === undefined) {
                if (Template.DEBUG_MODE)
                    throw new Error('jTemplates: Template is not defined.');
                else
                    return;
            }
            if (options != undefined && options.StrToJSON) {
                d = t.f_parseJSON(d);
            }
            jQuery.data(this, 'jTemplateSID', jQuery.data(this, 'jTemplateSID') + 1);
            jQuery(this).html(t.get(d, param, jQuery.extend(true, {}, this), 0));
        });
    };

    /**
    * Process template using data from URL 'url_' (only format JSON) and parameters 'param'. Update HTML code.
    * @param {string} url_ URL to data (in JSON)
    * @param {object} [param] parameters
    * @param {object} options options and callbacks
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.processTemplateURL = function (url_, param, options) {
        var that = this;

        options = jQuery.extend({
            type: 'GET',
            async: true,
            cache: false
        }, options);

        jQuery.ajax({
            url: url_,
            type: options.type,
            data: options.data,
            dataFilter: options.dataFilter,
            async: options.async,
            cache: options.cache,
            timeout: options.timeout,
            dataType: 'text',
            success: function (d) {
                var r = jQuery(that).processTemplate(d, param, { StrToJSON: true });
                if (options.on_success) {
                    options.on_success(r);
                }
            },
            error: options.on_error,
            complete: options.on_complete
        });
        return this;
    };

    /**
    * Create new Updater.
    * @name Updater
    * @class This class is used for 'Live Refresh!'.
    * @param {string} url A destination URL
    * @param {object} param Parameters (for template)
    * @param {number} interval Time refresh interval
    * @param {object} args Additional URL parameters (in URL alter ?) as assoc array.
    * @param {array} objs An array of HTMLElement which will be modified by Updater.
    * @param {object} options options and callbacks
    */
    var Updater = function (url, param, interval, args, objs, options) {
        this._url = url;
        this._param = param;
        this._interval = interval;
        this._args = args;
        this.objs = objs;
        this.timer = null;
        this._options = options || {};

        var that = this;
        jQuery(objs).each(function () {
            jQuery.data(this, 'jTemplateUpdater', that);
        });
        this.run();
    };

    /**
    * Create new HTTP request to server, get data (as JSON) and send it to templates. Also check does HTMLElements still exists in Document.
    */
    Updater.prototype.run = function () {
        this.detectDeletedNodes();
        if (this.objs.length == 0) {
            return;
        }
        var that = this;
        jQuery.ajax({
            url: this._url,
            dataType: 'text',
            data: this._args,
            cache: false,
            success: function (d) {
                try {
                    var r = jQuery(that.objs).processTemplate(d, that._param, { StrToJSON: true });
                    if (that._options.on_success) {
                        that._options.on_success(r);
                    }
                } catch (ex) { }
            }
        });
        this.timer = setTimeout(function () { that.run(); }, this._interval);
    };

    /**
    * Check does HTMLElements still exists in HTML Document.
    * If not exist, delete it from property 'objs'.
    */
    Updater.prototype.detectDeletedNodes = function () {
        this.objs = jQuery.grep(this.objs, function (o) {
            if (jQuery.browser.msie) {
                var n = o.parentNode;
                while (n && n != document) {
                    n = n.parentNode;
                }
                return n != null;
            } else {
                return o.parentNode != null;
            }
        });
    };

    /**
    * Start 'Live Refresh!'.
    * @param {string} url A destination URL
    * @param {object} param Parameters (for template)
    * @param {number} interval Time refresh interval
    * @param {object} args Additional URL parameters (in URL alter ?) as assoc array.
    * @param {object} options options and callbacks
    * @return {Updater} an Updater object
    * @memberOf jQuery.fn
    */
    jQuery.fn.processTemplateStart = function (url, param, interval, args, options) {
        return new Updater(url, param, interval, args, this, options);
    };

    /**
    * Stop 'Live Refresh!'.
    * @return {jQuery} chainable jQuery class
    * @memberOf jQuery.fn
    */
    jQuery.fn.processTemplateStop = function () {
        return jQuery(this).each(function () {
            var updater = jQuery.data(this, 'jTemplateUpdater');
            if (updater == null) {
                return;
            }
            var that = this;
            updater.objs = jQuery.grep(updater.objs, function (o) {
                return o != that;
            });
            jQuery.removeData(this, 'jTemplateUpdater');
        });
    };

    jQuery.extend(/** @scope jQuery.prototype */{
    /**
    * Create new Template.
    * @param {string} s A template string (like: "Text: {$T.txt}.").
    * @param {array} includes Array of included templates.
    * @param {object} settings Settings. (see Template)
    * @return {Template}
    */
    createTemplate: function (s, includes, settings) {
        return new Template(s, includes, settings);
    },

    /**
    * Create new Template from URL.
    * @param {string} url_ URL to template
    * @param {array} includes Array of included templates.
    * @param {object} settings Settings. (see Template)
    * @return {Template}
    */
    createTemplateURL: function (url_, includes, settings) {
        var s = jQuery.ajax({
            url: url_,
            async: false
        }).responseText;

        return new Template(s, includes, settings);
    },

    /**
    * Get a Template for HTML node
    * @param {Element} HTML node
    * @return {Template} a Template or "undefined"
    */
    getTemplate: function (element) {
        return jQuery.data(element, 'jTemplate');
    },

    /**
    * Process template and return text content.
    * @param {Template} template A Template
    * @param {object} data data
    * @param {object} param parameters
    * @return {string} Content of template
    */
    processTemplateToText: function (template, data, parameter) {
        return template.get(data, parameter, undefined, 0);
    },

    /**
    * Set Debug Mode
    * @param {Boolean} value
    */
    jTemplatesDebugMode: function (value) {
        Template.DEBUG_MODE = value;
    }
});

})(jQuery);};

