doT.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. "use strict"
  2. // doT.js
  3. // 2011-2014, Laura Doktorova, https://github.com/olado/doT
  4. // Licensed under the MIT license.
  5. const doT = {
  6. templateSettings: {
  7. argName: "it",
  8. encoders: {},
  9. selfContained: false,
  10. strip: true,
  11. internalPrefix: "_val",
  12. encodersPrefix: "_enc",
  13. delimiters: {
  14. start: "{{",
  15. end: "}}",
  16. },
  17. },
  18. template,
  19. compile,
  20. setDelimiters,
  21. }
  22. module.exports = doT
  23. // depends on selfContained mode
  24. const encoderType = {
  25. false: "function",
  26. true: "string",
  27. }
  28. const defaultSyntax = {
  29. evaluate: /\{\{([\s\S]+?(\}?)+)\}\}/g,
  30. interpolate: /\{\{=([\s\S]+?)\}\}/g,
  31. typeInterpolate: /\{\{%([nsb])=([\s\S]+?)\}\}/g,
  32. encode: /\{\{([a-z_$]+[\w$]*)?!([\s\S]+?)\}\}/g,
  33. use: /\{\{#([\s\S]+?)\}\}/g,
  34. useParams: /(^|[^\w$])def(?:\.|\[[\'\"])([\w$\.]+)(?:[\'\"]\])?\s*\:\s*([\w$]+(?:\.[\w$]+|\[[^\]]+\])*|\"[^\"]+\"|\'[^\']+\'|\{[^\}]+\})/g,
  35. define: /\{\{##\s*([\w\.$]+)\s*(\:|=)([\s\S]+?)#\}\}/g,
  36. defineParams: /^\s*([\w$]+):([\s\S]+)/,
  37. conditional: /\{\{\?(\?)?\s*([\s\S]*?)\s*\}\}/g,
  38. iterate: /\{\{~\s*(?:\}\}|([\s\S]+?)\s*\:\s*([\w$]+)\s*(?:\:\s*([\w$]+))?\s*\}\})/g,
  39. }
  40. let currentSyntax = {...defaultSyntax}
  41. const TYPES = {
  42. n: "number",
  43. s: "string",
  44. b: "boolean",
  45. }
  46. function resolveDefs(c, syn, block, def) {
  47. return (typeof block === "string" ? block : block.toString())
  48. .replace(syn.define, (_, code, assign, value) => {
  49. if (code.indexOf("def.") === 0) {
  50. code = code.substring(4)
  51. }
  52. if (!(code in def)) {
  53. if (assign === ":") {
  54. value.replace(syn.defineParams, (_, param, v) => {
  55. def[code] = {arg: param, text: v}
  56. })
  57. if (!(code in def)) def[code] = value
  58. } else {
  59. new Function("def", `def['${code}']=${value}`)(def)
  60. }
  61. }
  62. return ""
  63. })
  64. .replace(syn.use, (_, code) => {
  65. code = code.replace(syn.useParams, (_, s, d, param) => {
  66. if (def[d] && def[d].arg && param) {
  67. const rw = unescape((d + ":" + param).replace(/'|\\/g, "_"))
  68. def.__exp = def.__exp || {}
  69. def.__exp[rw] = def[d].text.replace(
  70. new RegExp(`(^|[^\\w$])${def[d].arg}([^\\w$])`, "g"),
  71. `$1${param}$2`
  72. )
  73. return s + `def.__exp['${rw}']`
  74. }
  75. })
  76. const v = new Function("def", "return " + code)(def)
  77. return v ? resolveDefs(c, syn, v, def) : v
  78. })
  79. }
  80. function unescape(code) {
  81. return code.replace(/\\('|\\)/g, "$1").replace(/[\r\t\n]/g, " ")
  82. }
  83. function template(tmpl, c, def) {
  84. const ds = c && c.delimiters
  85. const syn = ds && !sameDelimiters(ds) ? getSyntax(ds) : currentSyntax
  86. c = c ? {...doT.templateSettings, ...c} : doT.templateSettings
  87. let sid = 0
  88. let str = resolveDefs(c, syn, tmpl, def || {})
  89. const needEncoders = {}
  90. str = (
  91. "let out='" +
  92. (c.strip
  93. ? str
  94. .trim()
  95. .replace(/[\t ]+(\r|\n)/g, "\n") // remove trailing spaces
  96. .replace(/(\r|\n)[\t ]+/g, " ") // leading spaces reduced to " "
  97. .replace(/\r|\n|\t|\/\*[\s\S]*?\*\//g, "") // remove breaks, tabs and JS comments
  98. : str
  99. )
  100. .replace(/'|\\/g, "\\$&")
  101. .replace(syn.interpolate, (_, code) => `'+(${unescape(code)})+'`)
  102. .replace(syn.typeInterpolate, (_, typ, code) => {
  103. sid++
  104. const val = c.internalPrefix + sid
  105. const error = `throw new Error("expected ${TYPES[typ]}, got "+ (typeof ${val}))`
  106. return `';const ${val}=(${unescape(code)});if(typeof ${val}!=="${
  107. TYPES[typ]
  108. }") ${error};out+=${val}+'`
  109. })
  110. .replace(syn.encode, (_, enc = "", code) => {
  111. needEncoders[enc] = true
  112. code = unescape(code)
  113. const e = c.selfContained ? enc : enc ? "." + enc : '[""]'
  114. return `'+${c.encodersPrefix}${e}(${code})+'`
  115. })
  116. .replace(syn.conditional, (_, elseCase, code) => {
  117. if (code) {
  118. code = unescape(code)
  119. return elseCase ? `';}else if(${code}){out+='` : `';if(${code}){out+='`
  120. }
  121. return elseCase ? "';}else{out+='" : "';}out+='"
  122. })
  123. .replace(syn.iterate, (_, arr, vName, iName) => {
  124. if (!arr) return "';} } out+='"
  125. sid++
  126. const defI = iName ? `let ${iName}=-1;` : ""
  127. const incI = iName ? `${iName}++;` : ""
  128. const val = c.internalPrefix + sid
  129. return `';const ${val}=${unescape(
  130. arr
  131. )};if(${val}){${defI}for (const ${vName} of ${val}){${incI}out+='`
  132. })
  133. .replace(syn.evaluate, (_, code) => `';${unescape(code)}out+='`) +
  134. "';return out;"
  135. )
  136. .replace(/\n/g, "\\n")
  137. .replace(/\t/g, "\\t")
  138. .replace(/\r/g, "\\r")
  139. .replace(/(\s|;|\}|^|\{)out\+='';/g, "$1")
  140. .replace(/\+''/g, "")
  141. const args = Array.isArray(c.argName) ? properties(c.argName) : c.argName
  142. if (Object.keys(needEncoders).length === 0) {
  143. return try_(() => new Function(args, str))
  144. }
  145. checkEncoders(c, needEncoders)
  146. str = `return function(${args}){${str}};`
  147. return try_(() =>
  148. c.selfContained
  149. ? new Function((str = addEncoders(c, needEncoders) + str))()
  150. : new Function(c.encodersPrefix, str)(c.encoders)
  151. )
  152. function try_(f) {
  153. try {
  154. return f()
  155. } catch (e) {
  156. console.log("Could not create a template function: " + str)
  157. throw e
  158. }
  159. }
  160. }
  161. function compile(tmpl, def) {
  162. return template(tmpl, null, def)
  163. }
  164. function sameDelimiters({start, end}) {
  165. const d = doT.templateSettings.delimiters
  166. return d.start === start && d.end === end
  167. }
  168. function setDelimiters(delimiters) {
  169. if (sameDelimiters(delimiters)) {
  170. console.log("delimiters did not change")
  171. return
  172. }
  173. currentSyntax = getSyntax(delimiters)
  174. doT.templateSettings.delimiters = delimiters
  175. }
  176. function getSyntax({start, end}) {
  177. start = escape(start)
  178. end = escape(end)
  179. const syntax = {}
  180. for (const syn in defaultSyntax) {
  181. const s = defaultSyntax[syn]
  182. .toString()
  183. .replace(/\\\{\\\{/g, start)
  184. .replace(/\\\}\\\}/g, end)
  185. syntax[syn] = strToRegExp(s)
  186. }
  187. return syntax
  188. }
  189. const escapeCharacters = /([{}[\]()<>\\\/^$\-.+*?!=|&:])/g
  190. function escape(str) {
  191. return str.replace(escapeCharacters, "\\$1")
  192. }
  193. const regexpPattern = /^\/(.*)\/([\w]*)$/
  194. function strToRegExp(str) {
  195. const [, rx, flags] = str.match(regexpPattern)
  196. return new RegExp(rx, flags)
  197. }
  198. function properties(args) {
  199. return args.reduce((s, a, i) => s + (i ? "," : "") + a, "{") + "}"
  200. }
  201. function checkEncoders(c, encoders) {
  202. const typ = encoderType[c.selfContained]
  203. for (const enc in encoders) {
  204. const e = c.encoders[enc]
  205. if (!e) throw new Error(`unknown encoder "${enc}"`)
  206. if (typeof e !== typ)
  207. throw new Error(`selfContained ${c.selfContained}: encoder type must be "${typ}"`)
  208. }
  209. }
  210. function addEncoders(c, encoders) {
  211. let s = ""
  212. for (const enc in encoders) s += `const ${c.encodersPrefix}${enc}=${c.encoders[enc]};`
  213. return s
  214. }