No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

541 líneas
15KB

  1. /*!
  2. * mustache.js - Logic-less {{mustache}} templates with JavaScript
  3. * http://github.com/janl/mustache.js
  4. */
  5. var Mustache = (typeof module !== "undefined" && module.exports) || {};
  6. (function (exports) {
  7. exports.name = "mustache.js";
  8. exports.version = "0.5.0-dev";
  9. exports.tags = ["{{", "}}"];
  10. exports.parse = parse;
  11. exports.compile = compile;
  12. exports.render = render;
  13. exports.clearCache = clearCache;
  14. exports.to_html = render; // keep backwards compatibility
  15. var _toString = Object.prototype.toString;
  16. var _isArray = Array.isArray;
  17. var _forEach = Array.prototype.forEach;
  18. var _trim = String.prototype.trim;
  19. var isArray;
  20. if (_isArray) {
  21. isArray = _isArray;
  22. } else {
  23. isArray = function (obj) {
  24. return _toString.call(obj) === "[object Array]";
  25. };
  26. }
  27. var forEach;
  28. if (_forEach) {
  29. forEach = function (obj, callback, scope) {
  30. return _forEach.call(obj, callback, scope);
  31. };
  32. } else {
  33. forEach = function (obj, callback, scope) {
  34. for (var i = 0, len = obj.length; i < len; ++i) {
  35. callback.call(scope, obj[i], i, obj);
  36. }
  37. };
  38. }
  39. var spaceRe = /^\s*$/;
  40. function isWhitespace(string) {
  41. return spaceRe.test(string);
  42. }
  43. var trim;
  44. if (_trim) {
  45. trim = function (string) {
  46. return string == null ? "" : _trim.call(string);
  47. };
  48. } else {
  49. var trimLeft, trimRight;
  50. if (isWhitespace("\xA0")) {
  51. trimLeft = /^\s+/;
  52. trimRight = /\s+$/;
  53. } else {
  54. // IE doesn't match non-breaking spaces with \s, thanks jQuery.
  55. trimLeft = /^[\s\xA0]+/;
  56. trimRight = /[\s\xA0]+$/;
  57. }
  58. trim = function (string) {
  59. return string == null ? "" :
  60. String(string).replace(trimLeft, "").replace(trimRight, "");
  61. };
  62. }
  63. var escapeMap = {
  64. "&": "&amp;",
  65. "<": "&lt;",
  66. ">": "&gt;",
  67. '"': '&quot;',
  68. "'": '&#39;'
  69. };
  70. function escapeHTML(string) {
  71. return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
  72. return escapeMap[s] || s;
  73. });
  74. }
  75. /**
  76. * Adds the `template`, `line`, and `file` properties to the given error
  77. * object and alters the message to provide more useful debugging information.
  78. */
  79. function debug(e, template, line, file) {
  80. file = file || "<template>";
  81. var lines = template.split("\n"),
  82. start = Math.max(line - 3, 0),
  83. end = Math.min(lines.length, line + 3),
  84. context = lines.slice(start, end);
  85. var c;
  86. for (var i = 0, len = context.length; i < len; ++i) {
  87. c = i + start + 1;
  88. context[i] = (c === line ? " >> " : " ") + context[i];
  89. }
  90. e.template = template;
  91. e.line = line;
  92. e.file = file;
  93. e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
  94. return e;
  95. }
  96. /**
  97. * Looks up the value of the given `name` in the given context `stack`.
  98. */
  99. function findName(name, stack, returnNull) {
  100. if (name === ".") {
  101. return stack[stack.length - 1];
  102. }
  103. var names = name.split(".");
  104. var lastIndex = names.length - 1;
  105. var target = names[lastIndex];
  106. var value, context, i = stack.length, j, localStack;
  107. while (i) {
  108. localStack = stack.slice(0);
  109. context = stack[--i];
  110. j = 0;
  111. while (j < lastIndex) {
  112. context = context[names[j++]];
  113. if (context == null) {
  114. break;
  115. }
  116. localStack.push(context);
  117. }
  118. if (context && target in context) {
  119. value = context[target];
  120. break;
  121. }
  122. }
  123. // If the value is a function, call it in the current context.
  124. if (typeof value === "function") {
  125. value = value.call(localStack[localStack.length - 1]);
  126. }
  127. if (value == null && !returnNull) {
  128. return "";
  129. }
  130. return value;
  131. }
  132. function sendSection(send, name, callback, stack, inverted) {
  133. var value = findName(name, stack, true);
  134. if (inverted) {
  135. // From the spec: inverted sections may render text once based on the
  136. // inverse value of the key. That is, they will be rendered if the key
  137. // doesn't exist, is false, or is an empty list.
  138. if (value == null || value === false || (isArray(value) && value.length === 0)) {
  139. send(callback());
  140. }
  141. } else if (isArray(value)) {
  142. forEach(value, function (value) {
  143. stack.push(value);
  144. send(callback());
  145. stack.pop();
  146. });
  147. } else if (typeof value === "object") {
  148. stack.push(value);
  149. send(callback());
  150. stack.pop();
  151. } else if (typeof value === "function") {
  152. var scope = stack[stack.length - 1];
  153. var scopedRender = function (template) {
  154. return render(template, scope);
  155. };
  156. send(value.call(scope, callback(), scopedRender) || "");
  157. } else if (value) {
  158. send(callback());
  159. }
  160. }
  161. /**
  162. * Parses the given `template` and returns the source of a function that,
  163. * with the proper arguments, will render the template. Recognized options
  164. * include the following:
  165. *
  166. * - file The name of the file the template comes from (displayed in
  167. * error messages)
  168. * - tags An array of open and close tags the `template` uses. Defaults
  169. * to the value of Mustache.tags
  170. * - debug Set `true` to log the body of the generated function to the
  171. * console
  172. * - space Set `true` to preserve whitespace from lines that otherwise
  173. * contain only a {{tag}}. Defaults to `false`
  174. */
  175. function parse(template, options) {
  176. options = options || {};
  177. var tags = options.tags || exports.tags,
  178. openTag = tags[0],
  179. closeTag = tags[tags.length - 1];
  180. var code = [
  181. "var line = 1;", // keep track of source line number
  182. "\ntry {",
  183. '\nsend("'
  184. ];
  185. var spaces = [], // indices of whitespace in code on the current line
  186. hasTag = false, // is there a {{tag}} on the current line?
  187. nonSpace = false; // is there a non-space char on the current line?
  188. // Strips all space characters from the code array for the current line
  189. // if there was a {{tag}} on it and otherwise only spaces.
  190. var stripSpace = function () {
  191. if (hasTag && !nonSpace && !options.space) {
  192. while (spaces.length) {
  193. code.splice(spaces.pop(), 1);
  194. }
  195. } else {
  196. spaces = [];
  197. }
  198. hasTag = false;
  199. nonSpace = false;
  200. };
  201. var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
  202. var setTags = function (source) {
  203. tags = trim(source).split(/\s+/);
  204. nextOpenTag = tags[0];
  205. nextCloseTag = tags[tags.length - 1];
  206. };
  207. var includePartial = function (source) {
  208. code.push(
  209. '");',
  210. updateLine,
  211. '\nvar partial = partials["' + trim(source) + '"];',
  212. '\nif (partial) {',
  213. '\n send(render(partial, stack[stack.length - 1], partials));',
  214. '\n}',
  215. '\nsend("'
  216. );
  217. };
  218. var openSection = function (source, inverted) {
  219. var name = trim(source);
  220. if (name === "") {
  221. throw debug(new Error("Section name may not be empty"), template, line, options.file);
  222. }
  223. sectionStack.push({name: name, inverted: inverted});
  224. code.push(
  225. '");',
  226. updateLine,
  227. '\nvar name = "' + name + '";',
  228. '\nvar callback = (function () {',
  229. '\n var buffer, send = function (chunk) { buffer.push(chunk); };',
  230. '\n return function () {',
  231. '\n buffer = [];',
  232. '\nsend("'
  233. );
  234. };
  235. var openInvertedSection = function (source) {
  236. openSection(source, true);
  237. };
  238. var closeSection = function (source) {
  239. var name = trim(source);
  240. var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
  241. if (!openName || name != openName) {
  242. throw debug(new Error('Section named "' + name + '" was never opened'), template, line, file);
  243. }
  244. var section = sectionStack.pop();
  245. code.push(
  246. '");',
  247. '\n return buffer.join("");',
  248. '\n };',
  249. '\n})();'
  250. );
  251. if (section.inverted) {
  252. code.push("\nsendSection(send,name,callback,stack,true);");
  253. } else {
  254. code.push("\nsendSection(send,name,callback,stack);");
  255. }
  256. code.push('\nsend("');
  257. };
  258. var sendPlain = function (source) {
  259. code.push(
  260. '");',
  261. updateLine,
  262. '\nsend(findName("' + trim(source) + '", stack));',
  263. '\nsend("'
  264. );
  265. };
  266. var sendEscaped = function (source) {
  267. code.push(
  268. '");',
  269. updateLine,
  270. '\nsend(escapeHTML(findName("' + trim(source) + '", stack)));',
  271. '\nsend("'
  272. );
  273. };
  274. var line = 1, c, callback;
  275. for (var i = 0, len = template.length; i < len; ++i) {
  276. if (template.slice(i, i + openTag.length) === openTag) {
  277. i += openTag.length;
  278. c = template.substr(i, 1);
  279. updateLine = '\nline = ' + line + ';';
  280. nextOpenTag = openTag;
  281. nextCloseTag = closeTag;
  282. hasTag = true;
  283. switch (c) {
  284. case "!": // comment
  285. i++;
  286. callback = null;
  287. break;
  288. case "=": // change open/close tags, e.g. {{=<% %>=}}
  289. i++;
  290. closeTag = "=" + closeTag;
  291. callback = setTags;
  292. break;
  293. case ">": // include partial
  294. i++;
  295. callback = includePartial;
  296. break;
  297. case "#": // start section
  298. i++;
  299. callback = openSection;
  300. break;
  301. case "^": // start inverted section
  302. i++;
  303. callback = openInvertedSection;
  304. break;
  305. case "/": // end section
  306. i++;
  307. callback = closeSection;
  308. break;
  309. case "{": // plain variable
  310. closeTag = "}" + closeTag;
  311. // fall through
  312. case "&": // plain variable
  313. i++;
  314. nonSpace = true;
  315. callback = sendPlain;
  316. break;
  317. default: // escaped variable
  318. nonSpace = true;
  319. callback = sendEscaped;
  320. }
  321. var end = template.indexOf(closeTag, i);
  322. if (end === -1) {
  323. throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
  324. }
  325. var source = template.substring(i, end);
  326. if (callback) {
  327. callback(source);
  328. }
  329. // Maintain line count for \n in source.
  330. var n = 0;
  331. while (~(n = source.indexOf("\n", n))) {
  332. line++;
  333. n++;
  334. }
  335. i = end + closeTag.length - 1;
  336. openTag = nextOpenTag;
  337. closeTag = nextCloseTag;
  338. } else {
  339. c = template.substr(i, 1);
  340. switch (c) {
  341. case '"':
  342. case "\\":
  343. nonSpace = true;
  344. code.push("\\" + c);
  345. break;
  346. case "\n":
  347. spaces.push(code.length);
  348. code.push("\\n");
  349. stripSpace(); // Check for whitespace on the current line.
  350. line++;
  351. break;
  352. default:
  353. if (isWhitespace(c)) {
  354. spaces.push(code.length);
  355. } else {
  356. nonSpace = true;
  357. }
  358. code.push(c);
  359. }
  360. }
  361. }
  362. if (sectionStack.length != 0) {
  363. throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
  364. }
  365. // Clean up any whitespace from a closing {{tag}} that was at the end
  366. // of the template without a trailing \n.
  367. stripSpace();
  368. code.push(
  369. '");',
  370. "\nsend(null);", // Send null as the last operation.
  371. "\n} catch (e) { throw {error: e, line: line}; }"
  372. );
  373. // Ignore empty send("") statements.
  374. var body = code.join("").replace(/send\(""\);\n/g, "");
  375. if (options.debug) {
  376. if (typeof console != "undefined" && console.log) {
  377. console.log(body);
  378. } else if (typeof print === "function") {
  379. print(body);
  380. }
  381. }
  382. return body;
  383. }
  384. /**
  385. * Used by `compile` to generate a reusable function for the given `template`.
  386. */
  387. function _compile(template, options) {
  388. var args = "view,partials,send,stack,findName,escapeHTML,sendSection,render";
  389. var body = parse(template, options);
  390. var fn = new Function(args, body);
  391. // This anonymous function wraps the generated function so we can do
  392. // argument coercion, setup some variables, and handle any errors
  393. // encountered while executing it.
  394. return function (view, partials, callback) {
  395. if (typeof partials === "function") {
  396. callback = partials;
  397. partials = {};
  398. }
  399. partials = partials || {};
  400. var buffer = []; // output buffer
  401. var send = callback || function (chunk) {
  402. buffer.push(chunk);
  403. };
  404. var stack = [view]; // context stack
  405. try {
  406. fn(view, partials, send, stack, findName, escapeHTML, sendSection, render);
  407. } catch (e) {
  408. throw debug(e.error, template, e.line, options.file);
  409. }
  410. return buffer.join("");
  411. };
  412. }
  413. // Cache of pre-compiled templates.
  414. var _cache = {};
  415. /**
  416. * Clear the cache of compiled templates.
  417. */
  418. function clearCache() {
  419. _cache = {};
  420. }
  421. /**
  422. * Compiles the given `template` into a reusable function using the given
  423. * `options`. In addition to the options accepted by Mustache.parse,
  424. * recognized options include the following:
  425. *
  426. * - cache Set `false` to bypass any pre-compiled version of the given
  427. * template. Otherwise, a given `template` string will be cached
  428. * the first time it is parsed
  429. */
  430. function compile(template, options) {
  431. options = options || {};
  432. // Use a pre-compiled version from the cache if we have one.
  433. if (options.cache !== false) {
  434. if (!_cache[template]) {
  435. _cache[template] = _compile(template, options);
  436. }
  437. return _cache[template];
  438. }
  439. return _compile(template, options);
  440. }
  441. /**
  442. * High-level function that renders the given `template` using the given
  443. * `view`, `partials`, and `callback`. The `callback` is used to return the
  444. * output piece by piece, as it is rendered. When finished, the callback will
  445. * receive `null` as its argument, after which it will not be called any more.
  446. * If no callback is given, the complete rendered template will be used as the
  447. * return value for the function.
  448. *
  449. * Note: If no partials are needed, the third argument may be the callback.
  450. * If you need to use any of the template options (see `compile` above), you
  451. * must compile in a separate step, and then call that compiled function.
  452. */
  453. function render(template, view, partials, callback) {
  454. return compile(template)(view, partials, callback);
  455. }
  456. })(Mustache);