Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

437 строки
12KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = function () {
  6. var _toString = Object.prototype.toString;
  7. Array.isArray = Array.isArray || function (obj) {
  8. return _toString.call(obj) == "[object Array]";
  9. }
  10. var _trim = String.prototype.trim, trim;
  11. if (_trim) {
  12. trim = function (text) {
  13. return text == null ? "" : _trim.call(text);
  14. }
  15. } else {
  16. var trimLeft, trimRight;
  17. // IE doesn't match non-breaking spaces with \s.
  18. if ((/\S/).test("\xA0")) {
  19. trimLeft = /^[\s\xA0]+/;
  20. trimRight = /[\s\xA0]+$/;
  21. } else {
  22. trimLeft = /^\s+/;
  23. trimRight = /\s+$/;
  24. }
  25. trim = function (text) {
  26. return text == null ? "" :
  27. text.toString().replace(trimLeft, "").replace(trimRight, "");
  28. }
  29. }
  30. var escapeMap = {
  31. "&": "&",
  32. "<": "&lt;",
  33. ">": "&gt;",
  34. '"': '&quot;',
  35. "'": '&apos;'
  36. };
  37. function escapeHTML(string) {
  38. return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
  39. return escapeMap[s] || s;
  40. });
  41. }
  42. var regexCache = {};
  43. var Renderer = function () {};
  44. Renderer.prototype = {
  45. otag: "{{",
  46. ctag: "}}",
  47. pragmas: {},
  48. buffer: [],
  49. pragmas_implemented: {
  50. "IMPLICIT-ITERATOR": true
  51. },
  52. context: {},
  53. render: function (template, context, partials, in_recursion) {
  54. // reset buffer & set context
  55. if (!in_recursion) {
  56. this.context = context;
  57. this.buffer = []; // TODO: make this non-lazy
  58. }
  59. // fail fast
  60. if (!this.includes("", template)) {
  61. if (in_recursion) {
  62. return template;
  63. } else {
  64. this.send(template);
  65. return;
  66. }
  67. }
  68. // get the pragmas together
  69. template = this.render_pragmas(template);
  70. // render the template
  71. var html = this.render_section(template, context, partials);
  72. // render_section did not find any sections, we still need to render the tags
  73. if (html === false) {
  74. html = this.render_tags(template, context, partials, in_recursion);
  75. }
  76. if (in_recursion) {
  77. return html;
  78. } else {
  79. this.sendLines(html);
  80. }
  81. },
  82. /*
  83. Sends parsed lines
  84. */
  85. send: function (line) {
  86. if (line !== "") {
  87. this.buffer.push(line);
  88. }
  89. },
  90. sendLines: function (text) {
  91. if (text) {
  92. var lines = text.split("\n");
  93. for (var i = 0; i < lines.length; i++) {
  94. this.send(lines[i]);
  95. }
  96. }
  97. },
  98. /*
  99. Looks for %PRAGMAS
  100. */
  101. render_pragmas: function (template) {
  102. // no pragmas
  103. if (!this.includes("%", template)) {
  104. return template;
  105. }
  106. var that = this;
  107. var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) {
  108. return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
  109. });
  110. return template.replace(regex, function (match, pragma, options) {
  111. if (!that.pragmas_implemented[pragma]) {
  112. throw({message:
  113. "This implementation of mustache doesn't understand the '" +
  114. pragma + "' pragma"});
  115. }
  116. that.pragmas[pragma] = {};
  117. if (options) {
  118. var opts = options.split("=");
  119. that.pragmas[pragma][opts[0]] = opts[1];
  120. }
  121. return "";
  122. // ignore unknown pragmas silently
  123. });
  124. },
  125. /*
  126. Tries to find a partial in the curent scope and render it
  127. */
  128. render_partial: function (name, context, partials) {
  129. name = trim(name);
  130. if (!partials || partials[name] === undefined) {
  131. throw({message: "unknown_partial '" + name + "'"});
  132. }
  133. if (!context || typeof context[name] != "object") {
  134. return this.render(partials[name], context, partials, true);
  135. }
  136. return this.render(partials[name], context[name], partials, true);
  137. },
  138. /*
  139. Renders inverted (^) and normal (#) sections
  140. */
  141. render_section: function (template, context, partials) {
  142. if (!this.includes("#", template) && !this.includes("^", template)) {
  143. // did not render anything, there were no sections
  144. return false;
  145. }
  146. var that = this;
  147. var regex = this.getCachedRegex("render_section", function (otag, ctag) {
  148. // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
  149. return new RegExp(
  150. "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)
  151. otag + // {{
  152. "(\\^|\\#)\\s*(.+?)\\s*" +// #foo (# == $2, foo == $3), not greedy
  153. ctag + // }}
  154. "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped
  155. otag + // {{
  156. "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
  157. ctag + // }}
  158. "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.
  159. "g");
  160. });
  161. // for each {{#foo}}{{/foo}} section do...
  162. return template.replace(regex, function (match, before, type, name, content, after) {
  163. // before contains only tags, no sections
  164. var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",
  165. // after may contain both sections and tags, so use full rendering function
  166. renderedAfter = after ? that.render(after, context, partials, true) : "",
  167. // will be computed below
  168. renderedContent,
  169. value = that.find(name, context);
  170. if (type === "^") { // inverted section
  171. if (!value || Array.isArray(value) && value.length === 0) {
  172. // false or empty list, render it
  173. renderedContent = that.render(content, context, partials, true);
  174. } else {
  175. renderedContent = "";
  176. }
  177. } else if (type === "#") { // normal section
  178. if (Array.isArray(value)) { // Enumerable, Let's loop!
  179. renderedContent = that.map(value, function (row) {
  180. return that.render(content, that.create_context(row), partials, true);
  181. }).join("");
  182. } else if (that.is_object(value)) { // Object, Use it as subcontext!
  183. renderedContent = that.render(content, that.create_context(value),
  184. partials, true);
  185. } else if (typeof value == "function") {
  186. // higher order section
  187. renderedContent = value.call(context, content, function (text) {
  188. return that.render(text, context, partials, true);
  189. });
  190. } else if (value) { // boolean section
  191. renderedContent = that.render(content, context, partials, true);
  192. } else {
  193. renderedContent = "";
  194. }
  195. }
  196. return renderedBefore + renderedContent + renderedAfter;
  197. });
  198. },
  199. /*
  200. Replace {{foo}} and friends with values from our view
  201. */
  202. render_tags: function (template, context, partials, in_recursion) {
  203. // tit for tat
  204. var that = this;
  205. var new_regex = function () {
  206. return that.getCachedRegex("render_tags", function (otag, ctag) {
  207. return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g");
  208. });
  209. };
  210. var regex = new_regex();
  211. var tag_replace_callback = function (match, operator, name) {
  212. switch(operator) {
  213. case "!": // ignore comments
  214. return "";
  215. case "=": // set new delimiters, rebuild the replace regexp
  216. that.set_delimiters(name);
  217. regex = new_regex();
  218. return "";
  219. case ">": // render partial
  220. return that.render_partial(name, context, partials);
  221. case "{": // the triple mustache is unescaped
  222. case "&": // & operator is an alternative unescape method
  223. return that.find(name, context);
  224. default: // escape the value
  225. return escapeHTML(that.find(name, context));
  226. }
  227. };
  228. var lines = template.split("\n");
  229. for(var i = 0; i < lines.length; i++) {
  230. lines[i] = lines[i].replace(regex, tag_replace_callback, this);
  231. if (!in_recursion) {
  232. this.send(lines[i]);
  233. }
  234. }
  235. if (in_recursion) {
  236. return lines.join("\n");
  237. }
  238. },
  239. set_delimiters: function (delimiters) {
  240. var dels = delimiters.split(" ");
  241. this.otag = this.escape_regex(dels[0]);
  242. this.ctag = this.escape_regex(dels[1]);
  243. },
  244. escape_regex: function (text) {
  245. // thank you Simon Willison
  246. if (!arguments.callee.sRE) {
  247. var specials = [
  248. '/', '.', '*', '+', '?', '|',
  249. '(', ')', '[', ']', '{', '}', '\\'
  250. ];
  251. arguments.callee.sRE = new RegExp(
  252. '(\\' + specials.join('|\\') + ')', 'g'
  253. );
  254. }
  255. return text.replace(arguments.callee.sRE, '\\$1');
  256. },
  257. /*
  258. find `name` in current `context`. That is find me a value
  259. from the view object
  260. */
  261. find: function (name, context) {
  262. name = trim(name);
  263. // Checks whether a value is thruthy or false or 0
  264. function is_kinda_truthy(bool) {
  265. return bool === false || bool === 0 || bool;
  266. }
  267. var value;
  268. // check for dot notation eg. foo.bar
  269. if (name.match(/([a-z_]+)\./ig)) {
  270. var childValue = this.walk_context(name, context);
  271. if (is_kinda_truthy(childValue)) {
  272. value = childValue;
  273. }
  274. } else {
  275. if (is_kinda_truthy(context[name])) {
  276. value = context[name];
  277. } else if (is_kinda_truthy(this.context[name])) {
  278. value = this.context[name];
  279. }
  280. }
  281. if (typeof value == "function") {
  282. return value.apply(context);
  283. }
  284. if (value !== undefined) {
  285. return value;
  286. }
  287. // silently ignore unkown variables
  288. return "";
  289. },
  290. walk_context: function (name, context) {
  291. var path = name.split('.');
  292. // if the var doesn't exist in current context, check the top level context
  293. var value_context = (context[path[0]] != undefined) ? context : this.context;
  294. var value = value_context[path.shift()];
  295. while (value != undefined && path.length > 0) {
  296. value_context = value;
  297. value = value[path.shift()];
  298. }
  299. // if the value is a function, call it, binding the correct context
  300. if (typeof value == "function") {
  301. return value.apply(value_context);
  302. }
  303. return value;
  304. },
  305. // Utility methods
  306. /* includes tag */
  307. includes: function (needle, haystack) {
  308. return haystack.indexOf(this.otag + needle) != -1;
  309. },
  310. // by @langalex, support for arrays of strings
  311. create_context: function (_context) {
  312. if (this.is_object(_context)) {
  313. return _context;
  314. } else {
  315. var iterator = ".";
  316. if (this.pragmas["IMPLICIT-ITERATOR"]) {
  317. iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
  318. }
  319. var ctx = {};
  320. ctx[iterator] = _context;
  321. return ctx;
  322. }
  323. },
  324. is_object: function (a) {
  325. return a && typeof a == "object";
  326. },
  327. /*
  328. Why, why, why? Because IE. Cry, cry cry.
  329. */
  330. map: function (array, fn) {
  331. if (typeof array.map == "function") {
  332. return array.map(fn);
  333. } else {
  334. var r = [];
  335. var l = array.length;
  336. for(var i = 0; i < l; i++) {
  337. r.push(fn(array[i]));
  338. }
  339. return r;
  340. }
  341. },
  342. getCachedRegex: function (name, generator) {
  343. var byOtag = regexCache[this.otag];
  344. if (!byOtag) {
  345. byOtag = regexCache[this.otag] = {};
  346. }
  347. var byCtag = byOtag[this.ctag];
  348. if (!byCtag) {
  349. byCtag = byOtag[this.ctag] = {};
  350. }
  351. var regex = byCtag[name];
  352. if (!regex) {
  353. regex = byCtag[name] = generator(this.otag, this.ctag);
  354. }
  355. return regex;
  356. }
  357. };
  358. return({
  359. name: "mustache.js",
  360. version: "0.4.1-dev",
  361. /*
  362. Turns a template and view into HTML
  363. */
  364. to_html: function (template, view, partials, send_fun) {
  365. var renderer = new Renderer();
  366. if (send_fun) {
  367. renderer.send = send_fun;
  368. }
  369. renderer.render(template, view || {}, partials);
  370. if (!send_fun) {
  371. return renderer.buffer.join("\n");
  372. }
  373. }
  374. });
  375. }();