var Template = new Singletone ({
    vars: {},

    assign: function (name, value) {
        if (is_scalar(value)) {
            self.vars[name] = value;
        } else if (typeof value == 'object') {
            for (var i in value) {
                if (is_scalar(value[i])) {
                    self.vars[name + '.' + i] = value[i];
                }
            }
        } else {
            alert(typeof value + ' is not supported');
        }
    },

    transform: function (text) {
        for (var i in self.vars) {
            text = text.replace(new RegExp('\\{%' + RegExp.quote(i) + '\\}', 'g'), self.vars[i]);
        }
        return text;
    },

    /**
    * From http://forum.dklab.ru/viewtopic.php?p=124969&sid=a1dda9147736ad2ae07b859321817e90&noimg=
    *
    * sprintf(format, argument_list)
    *
    * The string function like one in C/C++, PHP, Perl
    * Each conversion specification is defined as below:
    *
    * %[index][alignment][padding][width][precision]type
    *
    * index     An optional index specifier that changes the order of the 
    *       arguments in the list to be displayed.
    * alignment An optional alignment specifier that says if the result should be 
    *       left-justified or right-justified. The default is 
    *       right-justified; a "-" character here will make it left-justified.
    * padding   An optional padding specifier that says what character will be 
    *       used for padding the results to the right string size. This may 
    *       be a space character or a "0" (zero character). The default is to 
    *       pad with spaces. An alternate padding character can be specified 
    *       by prefixing it with a single quote ('). See the examples below.
    * width     An optional number, a width specifier that says how many 
    *       characters (minimum) this conversion should result in.
    * precision An optional precision specifier that says how many decimal digits 
    *       should be displayed for floating-point numbers. This option has 
    *       no effect for other types than float.
    * type      A type specifier that says what type the argument data should be 
    *       treated as. Possible types:
    *
    * % - a literal percent character. No argument is required.  
    * b - the argument is treated as an integer, and presented as a binary number.
    * c - the argument is treated as an integer, and presented as the character 
    *   with that ASCII value.
    * d - the argument is treated as an integer, and presented as a decimal number.
    * u - the same as "d".
    * f - the argument is treated as a float, and presented as a floating-point.
    * o - the argument is treated as an integer, and presented as an octal number.
    * s - the argument is treated as and presented as a string.
    * x - the argument is treated as an integer and presented as a hexadecimal 
    *    number (with lowercase letters).
    * X - the argument is treated as an integer and presented as a hexadecimal 
    *    number (with uppercase letters).
    */
    sprintf: function () {
        var args = arguments;
        var frmt = arguments[0].replace(/%%/g, "\0\0");
        var result = "", prev = 0, index = 0;
        var re = /%(\d+[\$#])?([+-])?('.|0| )?(\d*)(\.\d*)?([bcdfosuxX])/g;
        /*
        * The re.exec() method returns the array with the following properties
        * wich are used in this function
        *   x.index contains the substring position found at the origin string
        *   x[0] contains the found substring
        *   x[1] contains the index specifier (as \d+\$ or \d+#)
        *   x[2] contains the alignment specifier ("+" or "-" or empty)
        *   x[3] contains the padding specifier (space char, "0" or defined as '.)
        *   x[4] contains the width specifier (as \d*)
        *   x[5] contains the floating-point precision specifier (as \.\d*)
        *   x[6] contains the type specifier (as [bcdfosuxX])
        */
        var x;
        while (x = re.exec(frmt)) {
            for (var i = 0; i < x.length; i++) if (x[i] == undefined) x[i] = "";
            index++;
            var ins = (x[1]) ? args[x[1].substring(0, x[1].length - 1)] : args[index];
            switch (x[6]) {
            case "b":
                ins = Number(ins).bin();
                break;
            case "c":
                ins = String.fromCharCode(ins);
                break;
            case "d":
            case "u":
                ins = Number(ins).dec();
                break;
            case "f":
                ins = Number(ins);
                if (x[5]) {
                    ins = ins.toFixed(x[5].substr(1));
                } else if (x[4]) {
                    ins = ins.toExponential(x[4]);
                } else {
                    ins = ins.toExponential();
                }
                break;
            case "o":
                ins = Number(ins).oct();
                break;
            case "s":
                ins = String(ins);
                break;
            case "x":
                ins = Number(ins).hexl();
                break;
            case "X":
                ins = Number(ins).hex();
                break;
            }
            result += frmt.substring(prev, x.index);
            prev = x.index + x[0].length;
            result += ins.padding(x[2] + x[4], x[3].substr(x[3].length - 1));
        }
        if ( prev < frmt.length ) {
            result += frmt.substr(prev);
        }
        result = result.replace(/\0\0/g, "%");
        return result;
    }

});
