jinja.min.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. (function(global, factory) {
  2. 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 = {}))
  3. })(this, function(jinja) {
  4. "use strict";
  5. var STRINGS = /'(\\.|[^'])*'|"(\\.|[^"'"])*"/g;
  6. var IDENTS_AND_NUMS = /([$_a-z][$\w]*)|([+-]?\d+(\.\d+)?)/g;
  7. var NUMBER = /^[+-]?\d+(\.\d+)?$/;
  8. var NON_PRIMITIVES = /\[[@#~](,[@#~])*\]|\[\]|\{([@i]:[@#~])(,[@i]:[@#~])*\}|\{\}/g;
  9. var IDENTIFIERS = /[$_a-z][$\w]*/gi;
  10. var VARIABLES = /i(\.i|\[[@#i]\])*/g;
  11. var ACCESSOR = /(\.i|\[[@#i]\])/g;
  12. var OPERATORS = /(===?|!==?|>=?|<=?|&&|\|\||[+\-\*\/%])/g;
  13. var EOPS = /(^|[^$\w])(and|or|not|is|isnot)([^$\w]|$)/g;
  14. var LEADING_SPACE = /^\s+/;
  15. var TRAILING_SPACE = /\s+$/;
  16. var START_TOKEN = /\{\{\{|\{\{|\{%|\{#/;
  17. var TAGS = {
  18. "{{{": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}\}/,
  19. "{{": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?\}\}/,
  20. "{%": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?%\}/,
  21. "{#": /^('(\\.|[^'])*'|"(\\.|[^"'"])*"|.)+?#\}/
  22. };
  23. var delimeters = {
  24. "{%": "directive",
  25. "{{": "output",
  26. "{#": "comment"
  27. };
  28. var operators = {
  29. and: "&&",
  30. or: "||",
  31. not: "!",
  32. is: "==",
  33. isnot: "!="
  34. };
  35. var constants = {
  36. true: true,
  37. false: false,
  38. null: null
  39. };
  40. function Parser() {
  41. this.nest = [];
  42. this.compiled = [];
  43. this.childBlocks = 0;
  44. this.parentBlocks = 0;
  45. this.isSilent = false
  46. }
  47. Parser.prototype.push = function(line) {
  48. if (!this.isSilent) {
  49. this.compiled.push(line)
  50. }
  51. };
  52. Parser.prototype.parse = function(src) {
  53. this.tokenize(src);
  54. return this.compiled
  55. };
  56. Parser.prototype.tokenize = function(src) {
  57. var lastEnd = 0,
  58. parser = this,
  59. trimLeading = false;
  60. matchAll(src, START_TOKEN, function(open, index, src) {
  61. var match = src.slice(index + open.length).match(TAGS[open]);
  62. match = match ? match[0] : "";
  63. var simplified = match.replace(STRINGS, "@");
  64. if (!match || ~simplified.indexOf(open)) {
  65. return index + 1
  66. }
  67. var inner = match.slice(0, 0 - open.length);
  68. if (inner.charAt(0) === "-") var wsCollapseLeft = true;
  69. if (inner.slice(-1) === "-") var wsCollapseRight = true;
  70. inner = inner.replace(/^-|-$/g, "").trim();
  71. if (parser.rawMode && open + inner !== "{%endraw") {
  72. return index + 1
  73. }
  74. var text = src.slice(lastEnd, index);
  75. lastEnd = index + open.length + match.length;
  76. if (trimLeading) text = trimLeft(text);
  77. if (wsCollapseLeft) text = trimRight(text);
  78. if (wsCollapseRight) trimLeading = true;
  79. if (open === "{{{") {
  80. open = "{{";
  81. inner += "|safe"
  82. }
  83. parser.textHandler(text);
  84. parser.tokenHandler(open, inner)
  85. });
  86. var text = src.slice(lastEnd);
  87. if (trimLeading) text = trimLeft(text);
  88. this.textHandler(text)
  89. };
  90. Parser.prototype.textHandler = function(text) {
  91. this.push("write(" + JSON.stringify(text) + ");")
  92. };
  93. Parser.prototype.tokenHandler = function(open, inner) {
  94. var type = delimeters[open];
  95. if (type === "directive") {
  96. this.compileTag(inner)
  97. } else if (type === "output") {
  98. var extracted = this.extractEnt(inner, STRINGS, "@");
  99. extracted.src = extracted.src.replace(/\|\|/g, "~").split("|");
  100. extracted.src = extracted.src.map(function(part) {
  101. return part.split("~").join("||")
  102. });
  103. var parts = this.injectEnt(extracted, "@");
  104. if (parts.length > 1) {
  105. var filters = parts.slice(1).map(this.parseFilter.bind(this));
  106. this.push("filter(" + this.parseExpr(parts[0]) + "," + filters.join(",") + ");")
  107. } else {
  108. this.push("filter(" + this.parseExpr(parts[0]) + ");")
  109. }
  110. }
  111. };
  112. Parser.prototype.compileTag = function(str) {
  113. var directive = str.split(" ")[0];
  114. var handler = tagHandlers[directive];
  115. if (!handler) {
  116. throw new Error("Invalid tag: " + str)
  117. }
  118. handler.call(this, str.slice(directive.length).trim())
  119. };
  120. Parser.prototype.parseFilter = function(src) {
  121. src = src.trim();
  122. var match = src.match(/[:(]/);
  123. var i = match ? match.index : -1;
  124. if (i < 0) return JSON.stringify([src]);
  125. var name = src.slice(0, i);
  126. var args = src.charAt(i) === ":" ? src.slice(i + 1) : src.slice(i + 1, -1);
  127. args = this.parseExpr(args, {
  128. terms: true
  129. });
  130. return "[" + JSON.stringify(name) + "," + args + "]"
  131. };
  132. Parser.prototype.extractEnt = function(src, regex, placeholder) {
  133. var subs = [],
  134. isFunc = typeof placeholder == "function";
  135. src = src.replace(regex, function(str) {
  136. var replacement = isFunc ? placeholder(str) : placeholder;
  137. if (replacement) {
  138. subs.push(str);
  139. return replacement
  140. }
  141. return str
  142. });
  143. return {
  144. src: src,
  145. subs: subs
  146. }
  147. };
  148. Parser.prototype.injectEnt = function(extracted, placeholder) {
  149. var src = extracted.src,
  150. subs = extracted.subs,
  151. isArr = Array.isArray(src);
  152. var arr = isArr ? src : [src];
  153. var re = new RegExp("[" + placeholder + "]", "g"),
  154. i = 0;
  155. arr.forEach(function(src, index) {
  156. arr[index] = src.replace(re, function() {
  157. return subs[i++]
  158. })
  159. });
  160. return isArr ? arr : arr[0]
  161. };
  162. Parser.prototype.replaceComplex = function(s) {
  163. var parsed = this.extractEnt(s, /i(\.i|\[[@#i]\])+/g, "v");
  164. parsed.src = parsed.src.replace(NON_PRIMITIVES, "~");
  165. return this.injectEnt(parsed, "v")
  166. };
  167. Parser.prototype.parseExpr = function(src, opts) {
  168. opts = opts || {};
  169. var parsed1 = this.extractEnt(src, STRINGS, "@");
  170. parsed1.src = parsed1.src.replace(EOPS, function(s, before, op, after) {
  171. return op in operators ? before + operators[op] + after : s
  172. });
  173. var parsed2 = this.extractEnt(parsed1.src, IDENTS_AND_NUMS, function(s) {
  174. return s in constants || NUMBER.test(s) ? "#" : null
  175. });
  176. var parsed3 = this.extractEnt(parsed2.src, IDENTIFIERS, "i");
  177. parsed3.src = parsed3.src.replace(/\s+/g, "");
  178. var simplified = parsed3.src;
  179. while (simplified !== (simplified = this.replaceComplex(simplified)));
  180. while (simplified !== (simplified = simplified.replace(/i(\.i|\[[@#i]\])+/, "v")));
  181. simplified = simplified.replace(/[iv]\[v?\]/g, "x");
  182. simplified = simplified.replace(/[@#~v]/g, "i");
  183. simplified = simplified.replace(OPERATORS, "%");
  184. simplified = simplified.replace(/!+[i]/g, "i");
  185. var terms = opts.terms ? simplified.split(",") : [simplified];
  186. terms.forEach(function(term) {
  187. while (term !== (term = term.replace(/\(i(%i)*\)/g, "i")));
  188. if (!term.match(/^i(%i)*/)) {
  189. throw new Error("Invalid expression: " + src + " " + term)
  190. }
  191. });
  192. parsed3.src = parsed3.src.replace(VARIABLES, this.parseVar.bind(this));
  193. parsed2.src = this.injectEnt(parsed3, "i");
  194. parsed1.src = this.injectEnt(parsed2, "#");
  195. return this.injectEnt(parsed1, "@")
  196. };
  197. Parser.prototype.parseVar = function(src) {
  198. var args = Array.prototype.slice.call(arguments);
  199. var str = args.pop(),
  200. index = args.pop();
  201. if (src === "i" && str.charAt(index + 1) === ":") {
  202. return '"i"'
  203. }
  204. var parts = ['"i"'];
  205. src.replace(ACCESSOR, function(part) {
  206. if (part === ".i") {
  207. parts.push('"i"')
  208. } else if (part === "[i]") {
  209. parts.push('get("i")')
  210. } else {
  211. parts.push(part.slice(1, -1))
  212. }
  213. });
  214. return "get(" + parts.join(",") + ")"
  215. };
  216. Parser.prototype.escName = function(str) {
  217. return str.replace(/\W/g, function(s) {
  218. return "$" + s.charCodeAt(0).toString(16)
  219. })
  220. };
  221. Parser.prototype.parseQuoted = function(str) {
  222. if (str.charAt(0) === "'") {
  223. str = str.slice(1, -1).replace(/\\.|"/, function(s) {
  224. if (s === "\\'") return "'";
  225. return s.charAt(0) === "\\" ? s : "\\" + s
  226. });
  227. str = '"' + str + '"'
  228. }
  229. return JSON.parse(str)
  230. };
  231. var tagHandlers = {
  232. if: function(expr) {
  233. this.push("if (" + this.parseExpr(expr) + ") {");
  234. this.nest.unshift("if")
  235. },
  236. else: function() {
  237. if (this.nest[0] === "for") {
  238. this.push("}, function() {")
  239. } else {
  240. this.push("} else {")
  241. }
  242. },
  243. elseif: function(expr) {
  244. this.push("} else if (" + this.parseExpr(expr) + ") {")
  245. },
  246. endif: function() {
  247. this.nest.shift();
  248. this.push("}")
  249. },
  250. for: function(str) {
  251. var i = str.indexOf(" in ");
  252. var name = str.slice(0, i).trim();
  253. var expr = str.slice(i + 4).trim();
  254. this.push("each(" + this.parseExpr(expr) + "," + JSON.stringify(name) + ",function() {");
  255. this.nest.unshift("for")
  256. },
  257. endfor: function() {
  258. this.nest.shift();
  259. this.push("});")
  260. },
  261. raw: function() {
  262. this.rawMode = true
  263. },
  264. endraw: function() {
  265. this.rawMode = false
  266. },
  267. set: function(stmt) {
  268. var i = stmt.indexOf("=");
  269. var name = stmt.slice(0, i).trim();
  270. var expr = stmt.slice(i + 1).trim();
  271. this.push("set(" + JSON.stringify(name) + "," + this.parseExpr(expr) + ");")
  272. },
  273. block: function(name) {
  274. if (this.isParent) {
  275. ++this.parentBlocks;
  276. var blockName = "block_" + (this.escName(name) || this.parentBlocks);
  277. this.push("block(typeof " + blockName + ' == "function" ? ' + blockName + " : function() {")
  278. } else if (this.hasParent) {
  279. this.isSilent = false;
  280. ++this.childBlocks;
  281. blockName = "block_" + (this.escName(name) || this.childBlocks);
  282. this.push("function " + blockName + "() {")
  283. }
  284. this.nest.unshift("block")
  285. },
  286. endblock: function() {
  287. this.nest.shift();
  288. if (this.isParent) {
  289. this.push("});")
  290. } else if (this.hasParent) {
  291. this.push("}");
  292. this.isSilent = true
  293. }
  294. },
  295. extends: function(name) {
  296. name = this.parseQuoted(name);
  297. var parentSrc = this.readTemplateFile(name);
  298. this.isParent = true;
  299. this.tokenize(parentSrc);
  300. this.isParent = false;
  301. this.hasParent = true;
  302. this.isSilent = true
  303. },
  304. include: function(name) {
  305. name = this.parseQuoted(name);
  306. var incSrc = this.readTemplateFile(name);
  307. this.isInclude = true;
  308. this.tokenize(incSrc);
  309. this.isInclude = false
  310. }
  311. };
  312. tagHandlers.assign = tagHandlers.set;
  313. tagHandlers.elif = tagHandlers.elseif;
  314. var getRuntime = function runtime(data, opts) {
  315. var defaults = {
  316. autoEscape: "toJson"
  317. };
  318. var _toString = Object.prototype.toString;
  319. var _hasOwnProperty = Object.prototype.hasOwnProperty;
  320. var getKeys = Object.keys || function(obj) {
  321. var keys = [];
  322. for (var n in obj)
  323. if (_hasOwnProperty.call(obj, n)) keys.push(n);
  324. return keys
  325. };
  326. var isArray = Array.isArray || function(obj) {
  327. return _toString.call(obj) === "[object Array]"
  328. };
  329. var create = Object.create || function(obj) {
  330. function F() {}
  331. F.prototype = obj;
  332. return new F
  333. };
  334. var toString = function(val) {
  335. if (val == null) return "";
  336. return typeof val.toString == "function" ? val.toString() : _toString.call(val)
  337. };
  338. var extend = function(dest, src) {
  339. var keys = getKeys(src);
  340. for (var i = 0, len = keys.length; i < len; i++) {
  341. var key = keys[i];
  342. dest[key] = src[key]
  343. }
  344. return dest
  345. };
  346. var get = function() {
  347. var val, n = arguments[0],
  348. c = stack.length;
  349. while (c--) {
  350. val = stack[c][n];
  351. if (typeof val != "undefined") break
  352. }
  353. for (var i = 1, len = arguments.length; i < len; i++) {
  354. if (val == null) continue;
  355. n = arguments[i];
  356. val = _hasOwnProperty.call(val, n) ? val[n] : typeof val._get == "function" ? val[n] = val._get(n) : null
  357. }
  358. return val == null ? "" : val
  359. };
  360. var set = function(n, val) {
  361. stack[stack.length - 1][n] = val
  362. };
  363. var push = function(ctx) {
  364. stack.push(ctx || {})
  365. };
  366. var pop = function() {
  367. stack.pop()
  368. };
  369. var write = function(str) {
  370. output.push(str)
  371. };
  372. var filter = function(val) {
  373. for (var i = 1, len = arguments.length; i < len; i++) {
  374. var arr = arguments[i],
  375. name = arr[0],
  376. filter = filters[name];
  377. if (filter) {
  378. arr[0] = val;
  379. val = filter.apply(data, arr)
  380. } else {
  381. throw new Error("Invalid filter: " + name)
  382. }
  383. }
  384. if (opts.autoEscape && name !== opts.autoEscape && name !== "safe") {
  385. val = filters[opts.autoEscape].call(data, val)
  386. }
  387. output.push(val)
  388. };
  389. var each = function(obj, loopvar, fn1, fn2) {
  390. if (obj == null) return;
  391. var arr = isArray(obj) ? obj : getKeys(obj),
  392. len = arr.length;
  393. var ctx = {
  394. loop: {
  395. length: len,
  396. first: arr[0],
  397. last: arr[len - 1]
  398. }
  399. };
  400. push(ctx);
  401. for (var i = 0; i < len; i++) {
  402. extend(ctx.loop, {
  403. index: i + 1,
  404. index0: i
  405. });
  406. fn1(ctx[loopvar] = arr[i])
  407. }
  408. if (len === 0 && fn2) fn2();
  409. pop()
  410. };
  411. var block = function(fn) {
  412. push();
  413. fn();
  414. pop()
  415. };
  416. var render = function() {
  417. return output.join("")
  418. };
  419. data = data || {};
  420. opts = extend(defaults, opts || {});
  421. var filters = extend({
  422. html: function(val) {
  423. return toString(val).split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('"').join("&quot;")
  424. },
  425. safe: function(val) {
  426. return val
  427. },
  428. toJson: function(val) {
  429. if (typeof val === "object") {
  430. return JSON.stringify(val)
  431. }
  432. return toString(val)
  433. }
  434. }, opts.filters || {});
  435. var stack = [create(data || {})],
  436. output = [];
  437. return {
  438. get: get,
  439. set: set,
  440. push: push,
  441. pop: pop,
  442. write: write,
  443. filter: filter,
  444. each: each,
  445. block: block,
  446. render: render
  447. }
  448. };
  449. var runtime;
  450. jinja.compile = function(markup, opts) {
  451. opts = opts || {};
  452. var parser = new Parser;
  453. parser.readTemplateFile = this.readTemplateFile;
  454. var code = [];
  455. code.push("function render($) {");
  456. code.push("var get = $.get, set = $.set, push = $.push, pop = $.pop, write = $.write, filter = $.filter, each = $.each, block = $.block;");
  457. code.push.apply(code, parser.parse(markup));
  458. code.push("return $.render();");
  459. code.push("}");
  460. code = code.join("\n");
  461. if (opts.runtime === false) {
  462. var fn = new Function("data", "options", "return (" + code + ")(runtime(data, options))")
  463. } else {
  464. runtime = runtime || (runtime = getRuntime.toString());
  465. fn = new Function("data", "options", "return (" + code + ")((" + runtime + ")(data, options))")
  466. }
  467. return {
  468. render: fn
  469. }
  470. };
  471. jinja.render = function(markup, data, opts) {
  472. var tmpl = jinja.compile(markup);
  473. return tmpl.render(data, opts)
  474. };
  475. jinja.templateFiles = [];
  476. jinja.readTemplateFile = function(name) {
  477. var templateFiles = this.templateFiles || [];
  478. var templateFile = templateFiles[name];
  479. if (templateFile == null) {
  480. throw new Error("Template file not found: " + name)
  481. }
  482. return templateFile
  483. };
  484. function trimLeft(str) {
  485. return str.replace(LEADING_SPACE, "")
  486. }
  487. function trimRight(str) {
  488. return str.replace(TRAILING_SPACE, "")
  489. }
  490. function matchAll(str, reg, fn) {
  491. reg = new RegExp(reg.source, "g" + (reg.ignoreCase ? "i" : "") + (reg.multiline ? "m" : ""));
  492. var match;
  493. while (match = reg.exec(str)) {
  494. var result = fn(match[0], match.index, str);
  495. if (typeof result == "number") {
  496. reg.lastIndex = result
  497. }
  498. }
  499. }
  500. });