пятница, 19 февраля 2016 г.

Полноценный JavaScript-шаблонизатор Template аналог EJS

;(function (global, factory) {
    if (typeof define === "function" && define.amd) {
        define(factory);
    } else if (typeof module === "object" && typeof module.exports === "object") {
        module.exports = factory();
    } else {
        global.TPL = factory();
    }
})(this, function () {

    // Usage:
    // console.log(new TPL.Template("<div>Total: {{= value }}<div>", {delimiters: ["{{", "}}"]}).render({value: "<span>15</span>"}));

    // Options:
    // delimiters        - characters to use for open and close delimiters
    // rmWhitespace - remove whitespace between HTML-tags

    // Tags:
    // <%    - scriptlet tag for control-flow, no output
    // <%% - outputs raw template part
    // <%-   - outputs the raw unescaped value into the template
    // <%=  - outputs the HTML escaped value into the template
    // <%#  - comment tag, no execution, no output
    // %>    - plain ending tag

    function Template (templateString, newSettingsObject) {
        var settingsObject = {
              delimiters: ["<%", "%>"]
            , rmWhitespace: false // removeWhitespace
        };
        if (newSettingsObject) {setNewSettings(settingsObject, newSettingsObject);}
        var renderFunction = createRenderFunction(templateString, settingsObject); // console.log(renderFunction.toString());
        return {render: function (dataObject) {return renderFunction(dataObject, settingsObject, escapeHtmlFunction, trim);}};
    }

    function setNewSettings (settingsObject, newSettingsObject) {
        for (var key in newSettingsObject) {
            if (newSettingsObject.hasOwnProperty(key)) {
                settingsObject[key] = newSettingsObject[key];
            }
        }
    }

    function trim (text) {
        return text == null ? "" : ("" + text).replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ""); // Trim space: \s, BOM: \uFEFF, NBSP: \xA0.
    }

    function createRenderFunction (templateString, settingsObject) {
        var matchRegExp = createMatchRegExp(settingsObject)
            , renderFunction;
        try {
            renderFunction = new Function ("dataObject", "_settingsObject_", "_escapeHtmlFunction_", "_trim_" // ) {
                     , "if (!dataObject) {dataObject = {};}" + "\n"
                    + "var _parsedTemplateString_ = '';" + "\n"
                    + "with (dataObject) {" + "\n"
                    +     "_parsedTemplateString_ += '" + (function(){
                                    var resultString = ""
                                        , index = 0;
                                    templateString.replace(matchRegExp, function (match, prerenderCodeString, rawInterpolateCodeString, interpolateAndEscapeCodeString, commentCodeString, evaluateCodeString, offset) {
                                        resultString += escapeString(templateString.slice(index, offset));
                                        index = offset + match.length;
                                        if (prerenderCodeString !== undefined) {
                                            resultString += "' + '" + settingsObject.delimiters[0] + prerenderCodeString + settingsObject.delimiters[1] + "' + '";
                                        } else if (rawInterpolateCodeString !== undefined) {
                                            resultString += "'" + "\n"
                                                                 + "+ (function(){" + "\n"
                                                                 +            "var _value_ = " + rawInterpolateCodeString + ";" + "\n"
                                                                 +            "if (_value_ === undefined || _value_ === null) {return '';} else {return _value_;}" + "\n"
                                                                 +        "})()" + "\n"
                                                                 + "+ '";
                                        } else if (interpolateAndEscapeCodeString !== undefined) {
                                            resultString += "'" + "\n"
                                                                 + "+ (function(){" + "\n"
                                                                 +            "var _value_ = " + interpolateAndEscapeCodeString + ";" + "\n"
                                                                 +            "if (_value_ === undefined || _value_ === null) {return '';} else {return _escapeHtmlFunction_(_value_);}" + "\n"
                                                                 +        "})()" + "\n"
                                                                 + "+ '";
                                        } else if (commentCodeString !== undefined) {
                                            // Do nothing
                                        } else if (evaluateCodeString !== undefined) {
                                            resultString += "';" + "\n"
                                                                 + evaluateCodeString + "\n"
                                                                 + "_parsedTemplateString_ += '";
                                        }
                                        return match;
                                    });
                                    return resultString;
                                })()
                    +     "';" + "\n"
                    + "}" + "\n"
                    + "if (_settingsObject_.rmWhitespace) {_parsedTemplateString_ = _trim_(_parsedTemplateString_.replace(/\\\\r/g, ''));}" + "\n"
                    + "return _parsedTemplateString_;"
            // }
            );
        } catch (error) {
            if (error instanceof SyntaxError) {error.message += " while compiling template: " + templateString;}
            throw error;
        }
        return renderFunction;
    }

    function createMatchRegExp (settingsObject) {
        var openDelimiter = settingsObject.delimiters[0] // <%
            , closeDelimiter = settingsObject.delimiters[1] // %>
            , stringInsideDelimiters = "([\\s\\S]+?)"          // some string
            , tags = {
                  prerender:                   openDelimiter + "%" + stringInsideDelimiters + closeDelimiter // <%% some string %>
                , rawInterpolate:            openDelimiter + "-" + stringInsideDelimiters + closeDelimiter  // <%= some string %>
                , interpolateAndEscape: openDelimiter + "=" + stringInsideDelimiters + closeDelimiter  // <%- some string %>
                , comment:                    openDelimiter + "#" + stringInsideDelimiters + closeDelimiter  // <%# some string %>
                , evaluate:                     openDelimiter + stringInsideDelimiters + closeDelimiter            // <% some string %>
              }
            , matchRegExp = new RegExp([
                  tags.prerender // Important!!! Must be first!
                , tags.rawInterpolate
                , tags.interpolateAndEscape
                , tags.comment
                , tags.evaluate // Important!!! Must be last!
              ].join("|") + "|$", "g"); // /<%%([\s\S]+?)%>|<%-([\s\S]+?)%>|<%=([\s\S]+?)%>|<%#([\s\S]+?)%>|<%([\s\S]+?)%>|$/g
        return matchRegExp;
    }

    function escapeString (string) {
        return string.replace(/\\|'|\r|\n|\u2028|\u2029/g, function (match) {
            var escapeCharacters = {
                  "'":          "\\'"
                , "\\":         "\\\\"
                , "\r":         "\\r"
                , "\n":        "\\n"
                , "\u2028": "\\u2028"
                , "\u2029": "\\u2029"
            };
            return escapeCharacters[match];
        });
    }

    function escapeHtmlFunction (string) {
        string = "" + string;
        var pattern = "(?:&|<|>|\"|'|`)"
            , testRegExp = new RegExp(pattern)
            , replaceRegExp = new RegExp(pattern, "g");
        if (testRegExp.test(string)) {
            return string.replace(replaceRegExp, function (match) {
                var escapeCharacters = {
                      "&":  "&amp;"
                    , "<":  "&lt;"
                    , ">":  "&gt;"
                    , "\"": "&quot;"
                    , "'":   "&#x27;"
                    , "`":  "&#x60;"
                };
                return escapeCharacters[match];
            });
        } else {
            return string;
        }
    }

    return {
          Template: Template
        , compile: function (templateString, newSettingsObject) {return new Template(templateString, newSettingsObject);}
        , render: function (templateString, dataObject, newSettingsObject) {return new Template(templateString, newSettingsObject).render(dataObject);}
    };

});

// Тесты

//console.log(new TPL.Template("  \r \r  Total: <% if (true) { %>1<% } %> Total: <%= value %> Total: <%- value %> Total: <%# comment %> Total: <%%- value %>    ", {rmWhitespace: true}).render({value: "<div>15</div>"}));

Комментариев нет:

Отправить комментарий