25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

288 satır
9.2KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. Rewrite as parser by Nathan Vander Wilt, 2010 May 22
  5. */
  6. var Mustache = function() {
  7. // `sender` is a function to buffer or stream parsed chunks
  8. var Renderer = function(sender) {
  9. this.send = sender;
  10. };
  11. Renderer.prototype = {
  12. otag: "{{",
  13. ctag: "}}",
  14. pragmas_implemented: {
  15. "IMPLICIT-ITERATOR": true
  16. },
  17. // state created per-instance to avoid sharing
  18. pragmas: null,
  19. // the main parser (return value for internal use only)
  20. render: function(template, context, partials) {
  21. //console.log("Context", context);
  22. this.pragmas = {};
  23. var tokens = this.splitTemplate(template);
  24. //console.log("Tokens", tokens);
  25. var tree = this.formTree(tokens);
  26. //console.log("Tree", tree);
  27. this.renderTree(tree, context, partials, template);
  28. },
  29. // returns {tag, start, end} or nothing
  30. findToken: function(template, startPos) {
  31. var tokenStart = template.indexOf(this.otag, startPos);
  32. if (tokenStart == -1) return;
  33. var tokenEnd = template.indexOf(this.ctag, tokenStart + this.otag.length);
  34. if (tokenEnd == -1) {
  35. var context = template.substr(tokenStart, 15);
  36. throw new Error("Unclosed token '" + context + "...'.");
  37. }
  38. var tokenInnards = template.slice(tokenStart + this.otag.length, tokenEnd);
  39. var tokenParts = tokenInnards.match(/([=%!{&>#^\/])?\s*(.+?)\s*\1?$/);
  40. var token = {
  41. "operator": tokenParts[1],
  42. "tag": tokenParts[2],
  43. "text": this.otag + tokenInnards + this.ctag,
  44. "start": tokenStart,
  45. "end": tokenEnd + this.ctag.length
  46. };
  47. if (token.operator == "{" && template[token.end] == "}") {
  48. // adjust for symmetrical unescaped tag with default delimiters
  49. token.end += 1;
  50. }
  51. return token;
  52. },
  53. splitTemplate: function(template) {
  54. var tokens = [];
  55. var token;
  56. var parsePosition = 0;
  57. while (token = this.findToken(template, parsePosition)) {
  58. var text = template.slice(parsePosition, token.start);
  59. tokens.push({"text": text, "start": parsePosition, "end": token.start});
  60. tokens.push(token);
  61. parsePosition = token.end;
  62. if (token.operator == "=") {
  63. // set new delimiters
  64. var delimiters = token.tag.split(" ");
  65. this.otag = delimiters[0];
  66. this.ctag = delimiters[1];
  67. } else if (token.operator == "%") {
  68. // store pragma
  69. var pragmaInfo = token.tag.match(/([\w_-]+) ?([\w]+=[\w]+)?/);
  70. var pragma = pragmaInfo[1];
  71. if (!this.pragmas_implemented[pragma]) {
  72. throw new Error("This mustache implementation doesn't understand the '" +
  73. pragma + "' pragma");
  74. }
  75. var options = {}
  76. var optionStr = pragmaInfo[2];
  77. if (optionStr) {
  78. var opts = optionStr.split("=");
  79. options[opts[0]] = opts[1];
  80. }
  81. this.pragmas[pragma] = options;
  82. }
  83. }
  84. var finalText = template.slice(parsePosition, template.length);
  85. tokens.push({"text": finalText, "start": parsePosition, "end": template.length});
  86. return tokens;
  87. },
  88. // NOTE: empties tokens parameter and modifies its former subobjects
  89. formTree: function(tokens, section) {
  90. var tree = [];
  91. var token;
  92. while (token = tokens.shift()) {
  93. if (token.start == token.end) {
  94. // drop empty tokens
  95. continue;
  96. } else if (token.tag) {
  97. if (token.operator == "#" || token.operator == "^") {
  98. token.section = true;
  99. token.invert = (token.operator == "^");
  100. token.tree = this.formTree(tokens, token.tag);
  101. } else if (token.operator == "/") {
  102. if (token.tag != section) {
  103. throw new Error("Badly nested section '" + section + "'" +
  104. " (left via '" + token.tag +"').");
  105. }
  106. break;
  107. } else if (token.operator == ">") {
  108. token.partial = true;
  109. } else if (token.operator == "{" || token.operator == "&") {
  110. token.noEscape = true;
  111. }
  112. }
  113. tree.push(token);
  114. }
  115. return tree;
  116. },
  117. renderTree: function(tree, context, partials, template) {
  118. for (var i = 0, len = tree.length; i < len; ++i) {
  119. var item = tree[i];
  120. if (item.section) {
  121. var iterator = this.valueIterator(item.tag, context);
  122. var value;
  123. if (item.invert) {
  124. value = iterator();
  125. if (!value) {
  126. this.renderTree(item.tree, context, partials, template);
  127. }
  128. } else while (value = iterator()) {
  129. if (value instanceof Function) {
  130. var subtree = item.tree;
  131. var lastSubitem = subtree[subtree.length-1];
  132. var subtext = template.slice(item.end, lastSubitem && lastSubitem.end);
  133. var renderer = function(text) {
  134. return Mustache.to_html(text, context, partials);
  135. }
  136. var lambdaResult = value.call(context, subtext, renderer);
  137. if (lambdaResult) {
  138. this.send(lambdaResult);
  139. }
  140. } else {
  141. var subContext = this.mergedCopy(context, value);
  142. this.renderTree(item.tree, subContext, partials, template);
  143. }
  144. }
  145. } else if (item.partial) {
  146. var subTemplate = partials[item.tag];
  147. if (!subTemplate) {
  148. throw new Error("Unknown partial '" + item.tag + "'");
  149. }
  150. this.render(subTemplate, context, partials);
  151. // TODO: below matches @janl's mustache, but not mustache(5)
  152. /*
  153. var subContext = context[item.tag];
  154. if (typeof(subContext) == "object") {
  155. this.render(subTemplate, subContext, partials);
  156. } else {
  157. this.send(subTemplate);
  158. }
  159. */
  160. } else if (item.operator && !item.noEscape) {
  161. // ignore other operators
  162. } else if (item.tag) {
  163. var rawValue = this.lookupValue(item.tag, context);
  164. if (rawValue) {
  165. var value = rawValue.toString();
  166. this.send((item.noEscape) ? value : this.escapeHTML(value));
  167. }
  168. } else {
  169. this.send(item.text);
  170. }
  171. }
  172. },
  173. // find `name` value in current view `context`
  174. lookupValue: function(name, context) {
  175. var value = context[name];
  176. // evaluate plain-function value (only once)
  177. if (value instanceof Function && !value.iterator) {
  178. value = value.apply(context);
  179. }
  180. // silently ignore unkown variables
  181. if (!value) {
  182. value = "";
  183. }
  184. return value;
  185. },
  186. objectValue: function(value, context) {
  187. if (value instanceof Function) {
  188. return value;
  189. }
  190. var obj = (value != null) ? {} : null;
  191. if (Object.prototype.toString.call(value) == '[object Object]') {
  192. obj = value;
  193. } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
  194. // original credit to @langalex, support for arrays of strings
  195. var iteratorKey = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
  196. obj[iteratorKey] = value;
  197. }
  198. return obj;
  199. },
  200. // always returns iterator function returning object/null
  201. valueIterator: function(name, context) {
  202. var value = this.lookupValue(name, context);
  203. var me = this;
  204. if (!value) {
  205. return function(){};
  206. } else if (value instanceof Function && value.iterator) {
  207. return value;
  208. } else if (value instanceof Array) {
  209. var i = 0;
  210. var l = value.length;
  211. return function() {
  212. return (i < l) ? me.objectValue(value[i++], context) : null;
  213. }
  214. } else {
  215. return function() {
  216. var v = value;
  217. value = null;
  218. return me.objectValue(v, context);
  219. };
  220. }
  221. },
  222. // copies contents of `b` over copy of `a`
  223. mergedCopy: function(a, b) {
  224. var copy = {};
  225. for (var key in a) if (a.hasOwnProperty(key)) {
  226. copy[key] = a[key];
  227. }
  228. for (var key in b) if (b.hasOwnProperty(key)) {
  229. copy[key] = b[key];
  230. }
  231. return copy;
  232. },
  233. // converts special HTML characters
  234. escapeHTML: function(s) {
  235. var htmlCharsRE = new RegExp("&(?!\\w+;)|[\"<>\\\\]", "g");
  236. return s.replace(htmlCharsRE, function(c) {
  237. switch(c) {
  238. case "&": return "&amp;";
  239. case "\\": return "\\\\";
  240. case '"': return '\"';
  241. case "<": return "&lt;";
  242. case ">": return "&gt;";
  243. default: return c;
  244. }
  245. });
  246. }
  247. };
  248. return({
  249. name: "mustache.js",
  250. version: "0.4.0-dev",
  251. // wrap internal render function
  252. to_html: function(template, view, partials, sender) {
  253. var buffer = [];
  254. var renderSender = sender || function(chunk) {
  255. if (chunk.length) {
  256. buffer.push(chunk);
  257. }
  258. }
  259. var renderer = new Renderer(renderSender);
  260. renderer.render(template, view, partials);
  261. if (!sender) {
  262. return buffer.join("");
  263. }
  264. }
  265. });
  266. }();