123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504 |
- (function(global, factory) {
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.jinja = {}))
- })(this, function(jinja) {
- "use strict";
- var STRINGS = /'(\\.|[^'])*'|"(\\.|[^"'"])*"/g;
- var IDENTS_AND_NUMS = /([$_a-z][$\w]*)|([+-]?\d+(\.\d+)?)/g;
- var NUMBER = /^[+-]?\d+(\.\d+)?$/;
- var NON_PRIMITIVES = /\[[@#~](,[@#~])*\]|\[\]|\{([@i]:[@#~])(,[@i]:[@#~])*\}|\{\}/g;
- var IDENTIFIERS = /[$_a-z][$\w]*/gi;
- var VARIABLES = /i(\.i|\[[@#i]\])*/g;
- var ACCESSOR = /(\.i|\[[@#i]\])/g;
- var OPERATORS = /(===?|!==?|>=?|<=?|&&|\|\||[+\-\*\/%])/g;
- var EOPS = /(^|[^$\w])(and|or|not|is|isnot)([^$\w]|$)/g;
- var LEADING_SPACE = /^\s+/;
- var TRAILING_SPACE = /\s+$/;
- var START_TOKEN = /\{\{\{|\{\{|\{%|\{#/;
- var TAGS = {
- "{{{": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}\}/,
- "{{": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}/,
- "{%": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?%\}/,
- "{#": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?#\}/
- };
- var delimeters = {
- "{%": "directive",
- "{{": "output",
- "{#": "comment"
- };
- var operators = {
- and: "&&",
- or: "||",
- not: "!",
- is: "==",
- isnot: "!="
- };
- var constants = {
- true: true,
- false: false,
- null: null
- };
- function Parser() {
- this.nest = [];
- this.compiled = [];
- this.childBlocks = 0;
- this.parentBlocks = 0;
- this.isSilent = false
- }
- Parser.prototype.push = function(line) {
- if (!this.isSilent) {
- this.compiled.push(line)
- }
- };
- Parser.prototype.parse = function(src) {
- this.tokenize(src);
- return this.compiled
- };
- Parser.prototype.tokenize = function(src) {
- var lastEnd = 0,
- parser = this,
- trimLeading = false;
- matchAll(src, START_TOKEN, function(open, index, src) {
- var match = src.slice(index + open.length).match(TAGS[open]);
- match = match ? match[0] : "";
- var simplified = match.replace(STRINGS, "@");
- if (!match || ~simplified.indexOf(open)) {
- return index + 1
- }
- var inner = match.slice(0, 0 - open.length);
- if (inner.charAt(0) === "-") var wsCollapseLeft = true;
- if (inner.slice(-1) === "-") var wsCollapseRight = true;
- inner = inner.replace(/^-|-$/g, "").trim();
- if (parser.rawMode && open + inner !== "{%endraw") {
- return index + 1
- }
- var text = src.slice(lastEnd, index);
- lastEnd = index + open.length + match.length;
- if (trimLeading) text = trimLeft(text);
- if (wsCollapseLeft) text = trimRight(text);
- if (wsCollapseRight) trimLeading = true;
- if (open === "{{{") {
- open = "{{";
- inner += "|safe"
- }
- parser.textHandler(text);
- parser.tokenHandler(open, inner)
- });
- var text = src.slice(lastEnd);
- if (trimLeading) text = trimLeft(text);
- this.textHandler(text)
- };
- Parser.prototype.textHandler = function(text) {
- this.push("write(" + JSON.stringify(text) + ");")
- };
- Parser.prototype.tokenHandler = function(open, inner) {
- var type = delimeters[open];
- if (type === "directive") {
- this.compileTag(inner)
- } else if (type === "output") {
- var extracted = this.extractEnt(inner, STRINGS, "@");
- extracted.src = extracted.src.replace(/\|\|/g, "~").split("|");
- extracted.src = extracted.src.map(function(part) {
- return part.split("~").join("||")
- });
- var parts = this.injectEnt(extracted, "@");
- if (parts.length > 1) {
- var filters = parts.slice(1).map(this.parseFilter.bind(this));
- this.push("filter(" + this.parseExpr(parts[0]) + "," + filters.join(",") + ");")
- } else {
- this.push("filter(" + this.parseExpr(parts[0]) + ");")
- }
- }
- };
- Parser.prototype.compileTag = function(str) {
- var directive = str.split(" ")[0];
- var handler = tagHandlers[directive];
- if (!handler) {
- throw new Error("Invalid tag: " + str)
- }
- handler.call(this, str.slice(directive.length).trim())
- };
- Parser.prototype.parseFilter = function(src) {
- src = src.trim();
- var match = src.match(/[:(]/);
- var i = match ? match.index : -1;
- if (i < 0) return JSON.stringify([src]);
- var name = src.slice(0, i);
- var args = src.charAt(i) === ":" ? src.slice(i + 1) : src.slice(i + 1, -1);
- args = this.parseExpr(args, {
- terms: true
- });
- return "[" + JSON.stringify(name) + "," + args + "]"
- };
- Parser.prototype.extractEnt = function(src, regex, placeholder) {
- var subs = [],
- isFunc = typeof placeholder == "function";
- src = src.replace(regex, function(str) {
- var replacement = isFunc ? placeholder(str) : placeholder;
- if (replacement) {
- subs.push(str);
- return replacement
- }
- return str
- });
- return {
- src: src,
- subs: subs
- }
- };
- Parser.prototype.injectEnt = function(extracted, placeholder) {
- var src = extracted.src,
- subs = extracted.subs,
- isArr = Array.isArray(src);
- var arr = isArr ? src : [src];
- var re = new RegExp("[" + placeholder + "]", "g"),
- i = 0;
- arr.forEach(function(src, index) {
- arr[index] = src.replace(re, function() {
- return subs[i++]
- })
- });
- return isArr ? arr : arr[0]
- };
- Parser.prototype.replaceComplex = function(s) {
- var parsed = this.extractEnt(s, /i(\.i|\[[@#i]\])+/g, "v");
- parsed.src = parsed.src.replace(NON_PRIMITIVES, "~");
- return this.injectEnt(parsed, "v")
- };
- Parser.prototype.parseExpr = function(src, opts) {
- opts = opts || {};
- var parsed1 = this.extractEnt(src, STRINGS, "@");
- parsed1.src = parsed1.src.replace(EOPS, function(s, before, op, after) {
- return op in operators ? before + operators[op] + after : s
- });
- var parsed2 = this.extractEnt(parsed1.src, IDENTS_AND_NUMS, function(s) {
- return s in constants || NUMBER.test(s) ? "#" : null
- });
- var parsed3 = this.extractEnt(parsed2.src, IDENTIFIERS, "i");
- parsed3.src = parsed3.src.replace(/\s+/g, "");
- var simplified = parsed3.src;
- while (simplified !== (simplified = this.replaceComplex(simplified)));
- while (simplified !== (simplified = simplified.replace(/i(\.i|\[[@#i]\])+/, "v")));
- simplified = simplified.replace(/[iv]\[v?\]/g, "x");
- simplified = simplified.replace(/[@#~v]/g, "i");
- simplified = simplified.replace(OPERATORS, "%");
- simplified = simplified.replace(/!+[i]/g, "i");
- var terms = opts.terms ? simplified.split(",") : [simplified];
- terms.forEach(function(term) {
- while (term !== (term = term.replace(/\(i(%i)*\)/g, "i")));
- if (!term.match(/^i(%i)*/)) {
- throw new Error("Invalid expression: " + src + " " + term)
- }
- });
- parsed3.src = parsed3.src.replace(VARIABLES, this.parseVar.bind(this));
- parsed2.src = this.injectEnt(parsed3, "i");
- parsed1.src = this.injectEnt(parsed2, "#");
- return this.injectEnt(parsed1, "@")
- };
- Parser.prototype.parseVar = function(src) {
- var args = Array.prototype.slice.call(arguments);
- var str = args.pop(),
- index = args.pop();
- if (src === "i" && str.charAt(index + 1) === ":") {
- return '"i"'
- }
- var parts = ['"i"'];
- src.replace(ACCESSOR, function(part) {
- if (part === ".i") {
- parts.push('"i"')
- } else if (part === "[i]") {
- parts.push('get("i")')
- } else {
- parts.push(part.slice(1, -1))
- }
- });
- return "get(" + parts.join(",") + ")"
- };
- Parser.prototype.escName = function(str) {
- return str.replace(/\W/g, function(s) {
- return "$" + s.charCodeAt(0).toString(16)
- })
- };
- Parser.prototype.parseQuoted = function(str) {
- if (str.charAt(0) === "'") {
- str = str.slice(1, -1).replace(/\\.|"/, function(s) {
- if (s === "\\'") return "'";
- return s.charAt(0) === "\\" ? s : "\\" + s
- });
- str = '"' + str + '"'
- }
- return JSON.parse(str)
- };
- var tagHandlers = {
- if: function(expr) {
- this.push("if (" + this.parseExpr(expr) + ") {");
- this.nest.unshift("if")
- },
- else: function() {
- if (this.nest[0] === "for") {
- this.push("}, function() {")
- } else {
- this.push("} else {")
- }
- },
- elseif: function(expr) {
- this.push("} else if (" + this.parseExpr(expr) + ") {")
- },
- endif: function() {
- this.nest.shift();
- this.push("}")
- },
- for: function(str) {
- var i = str.indexOf(" in ");
- var name = str.slice(0, i).trim();
- var expr = str.slice(i + 4).trim();
- this.push("each(" + this.parseExpr(expr) + "," + JSON.stringify(name) + ",function() {");
- this.nest.unshift("for")
- },
- endfor: function() {
- this.nest.shift();
- this.push("});")
- },
- raw: function() {
- this.rawMode = true
- },
- endraw: function() {
- this.rawMode = false
- },
- set: function(stmt) {
- var i = stmt.indexOf("=");
- var name = stmt.slice(0, i).trim();
- var expr = stmt.slice(i + 1).trim();
- this.push("set(" + JSON.stringify(name) + "," + this.parseExpr(expr) + ");")
- },
- block: function(name) {
- if (this.isParent) {
- ++this.parentBlocks;
- var blockName = "block_" + (this.escName(name) || this.parentBlocks);
- this.push("block(typeof " + blockName + ' == "function" ? ' + blockName + " : function() {")
- } else if (this.hasParent) {
- this.isSilent = false;
- ++this.childBlocks;
- blockName = "block_" + (this.escName(name) || this.childBlocks);
- this.push("function " + blockName + "() {")
- }
- this.nest.unshift("block")
- },
- endblock: function() {
- this.nest.shift();
- if (this.isParent) {
- this.push("});")
- } else if (this.hasParent) {
- this.push("}");
- this.isSilent = true
- }
- },
- extends: function(name) {
- name = this.parseQuoted(name);
- var parentSrc = this.readTemplateFile(name);
- this.isParent = true;
- this.tokenize(parentSrc);
- this.isParent = false;
- this.hasParent = true;
- this.isSilent = true
- },
- include: function(name) {
- name = this.parseQuoted(name);
- var incSrc = this.readTemplateFile(name);
- this.isInclude = true;
- this.tokenize(incSrc);
- this.isInclude = false
- }
- };
- tagHandlers.assign = tagHandlers.set;
- tagHandlers.elif = tagHandlers.elseif;
- var getRuntime = function runtime(data, opts) {
- var defaults = {
- autoEscape: "toJson"
- };
- var _toString = Object.prototype.toString;
- var _hasOwnProperty = Object.prototype.hasOwnProperty;
- var getKeys = Object.keys || function(obj) {
- var keys = [];
- for (var n in obj)
- if (_hasOwnProperty.call(obj, n)) keys.push(n);
- return keys
- };
- var isArray = Array.isArray || function(obj) {
- return _toString.call(obj) === "[object Array]"
- };
- var create = Object.create || function(obj) {
- function F() {}
- F.prototype = obj;
- return new F
- };
- var toString = function(val) {
- if (val == null) return "";
- return typeof val.toString == "function" ? val.toString() : _toString.call(val)
- };
- var extend = function(dest, src) {
- var keys = getKeys(src);
- for (var i = 0, len = keys.length; i < len; i++) {
- var key = keys[i];
- dest[key] = src[key]
- }
- return dest
- };
- var get = function() {
- var val, n = arguments[0],
- c = stack.length;
- while (c--) {
- val = stack[c][n];
- if (typeof val != "undefined") break
- }
- for (var i = 1, len = arguments.length; i < len; i++) {
- if (val == null) continue;
- n = arguments[i];
- val = _hasOwnProperty.call(val, n) ? val[n] : typeof val._get == "function" ? val[n] = val._get(n) : null
- }
- return val == null ? "" : val
- };
- var set = function(n, val) {
- stack[stack.length - 1][n] = val
- };
- var push = function(ctx) {
- stack.push(ctx || {})
- };
- var pop = function() {
- stack.pop()
- };
- var write = function(str) {
- output.push(str)
- };
- var filter = function(val) {
- for (var i = 1, len = arguments.length; i < len; i++) {
- var arr = arguments[i],
- name = arr[0],
- filter = filters[name];
- if (filter) {
- arr[0] = val;
- val = filter.apply(data, arr)
- } else {
- throw new Error("Invalid filter: " + name)
- }
- }
- if (opts.autoEscape && name !== opts.autoEscape && name !== "safe") {
- val = filters[opts.autoEscape].call(data, val)
- }
- output.push(val)
- };
- var each = function(obj, loopvar, fn1, fn2) {
- if (obj == null) return;
- var arr = isArray(obj) ? obj : getKeys(obj),
- len = arr.length;
- var ctx = {
- loop: {
- length: len,
- first: arr[0],
- last: arr[len - 1]
- }
- };
- push(ctx);
- for (var i = 0; i < len; i++) {
- extend(ctx.loop, {
- index: i + 1,
- index0: i
- });
- fn1(ctx[loopvar] = arr[i])
- }
- if (len === 0 && fn2) fn2();
- pop()
- };
- var block = function(fn) {
- push();
- fn();
- pop()
- };
- var render = function() {
- return output.join("")
- };
- data = data || {};
- opts = extend(defaults, opts || {});
- var filters = extend({
- html: function(val) {
- return toString(val).split("&").join("&").split("<").join("<").split(">").join(">").split('"').join(""")
- },
- safe: function(val) {
- return val
- },
- toJson: function(val) {
- if (typeof val === "object") {
- return JSON.stringify(val)
- }
- return toString(val)
- }
- }, opts.filters || {});
- var stack = [create(data || {})],
- output = [];
- return {
- get: get,
- set: set,
- push: push,
- pop: pop,
- write: write,
- filter: filter,
- each: each,
- block: block,
- render: render
- }
- };
- var runtime;
- jinja.compile = function(markup, opts) {
- opts = opts || {};
- var parser = new Parser;
- parser.readTemplateFile = this.readTemplateFile;
- var code = [];
- code.push("function render($) {");
- code.push("var get = $.get, set = $.set, push = $.push, pop = $.pop, write = $.write, filter = $.filter, each = $.each, block = $.block;");
- code.push.apply(code, parser.parse(markup));
- code.push("return $.render();");
- code.push("}");
- code = code.join("\n");
- if (opts.runtime === false) {
- var fn = new Function("data", "options", "return (" + code + ")(runtime(data, options))")
- } else {
- runtime = runtime || (runtime = getRuntime.toString());
- fn = new Function("data", "options", "return (" + code + ")((" + runtime + ")(data, options))")
- }
- return {
- render: fn
- }
- };
- jinja.render = function(markup, data, opts) {
- var tmpl = jinja.compile(markup);
- return tmpl.render(data, opts)
- };
- jinja.templateFiles = [];
- jinja.readTemplateFile = function(name) {
- var templateFiles = this.templateFiles || [];
- var templateFile = templateFiles[name];
- if (templateFile == null) {
- throw new Error("Template file not found: " + name)
- }
- return templateFile
- };
- function trimLeft(str) {
- return str.replace(LEADING_SPACE, "")
- }
- function trimRight(str) {
- return str.replace(TRAILING_SPACE, "")
- }
- function matchAll(str, reg, fn) {
- reg = new RegExp(reg.source, "g" + (reg.ignoreCase ? "i" : "") + (reg.multiline ? "m" : ""));
- var match;
- while (match = reg.exec(str)) {
- var result = fn(match[0], match.index, str);
- if (typeof result == "number") {
- reg.lastIndex = result
- }
- }
- }
- });
|