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.

559 líneas
15KB

  1. /*
  2. Shameless port of a shameless port
  3. @defunkt => @janl => @aq
  4. See http://github.com/defunkt/mustache for more info.
  5. */
  6. ;(function($) {
  7. /*!
  8. * mustache.js - Logic-less {{mustache}} templates with JavaScript
  9. * http://github.com/janl/mustache.js
  10. */
  11. /*global define: false*/
  12. (function (root, factory) {
  13. if (typeof exports === "object" && exports) {
  14. factory(exports); // CommonJS
  15. } else {
  16. var mustache = {};
  17. factory(mustache);
  18. if (typeof define === "function" && define.amd) {
  19. define(mustache); // AMD
  20. } else {
  21. root.Mustache = mustache; // <script>
  22. }
  23. }
  24. }(this, function (mustache) {
  25. var whiteRe = /\s*/;
  26. var spaceRe = /\s+/;
  27. var nonSpaceRe = /\S/;
  28. var eqRe = /\s*=/;
  29. var curlyRe = /\s*\}/;
  30. var tagRe = /#|\^|\/|>|\{|&|=|!/;
  31. // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
  32. // See https://github.com/janl/mustache.js/issues/189
  33. var RegExp_test = RegExp.prototype.test;
  34. function testRegExp(re, string) {
  35. return RegExp_test.call(re, string);
  36. }
  37. function isWhitespace(string) {
  38. return !testRegExp(nonSpaceRe, string);
  39. }
  40. var Object_toString = Object.prototype.toString;
  41. var isArray = Array.isArray || function (obj) {
  42. return Object_toString.call(obj) === '[object Array]';
  43. };
  44. function escapeRegExp(string) {
  45. return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
  46. }
  47. var entityMap = {
  48. "&": "&amp;",
  49. "<": "&lt;",
  50. ">": "&gt;",
  51. '"': '&quot;',
  52. "'": '&#39;',
  53. "/": '&#x2F;'
  54. };
  55. function escapeHtml(string) {
  56. return String(string).replace(/[&<>"'\/]/g, function (s) {
  57. return entityMap[s];
  58. });
  59. }
  60. function Scanner(string) {
  61. this.string = string;
  62. this.tail = string;
  63. this.pos = 0;
  64. }
  65. /**
  66. * Returns `true` if the tail is empty (end of string).
  67. */
  68. Scanner.prototype.eos = function () {
  69. return this.tail === "";
  70. };
  71. /**
  72. * Tries to match the given regular expression at the current position.
  73. * Returns the matched text if it can match, the empty string otherwise.
  74. */
  75. Scanner.prototype.scan = function (re) {
  76. var match = this.tail.match(re);
  77. if (match && match.index === 0) {
  78. this.tail = this.tail.substring(match[0].length);
  79. this.pos += match[0].length;
  80. return match[0];
  81. }
  82. return "";
  83. };
  84. /**
  85. * Skips all text until the given regular expression can be matched. Returns
  86. * the skipped string, which is the entire tail if no match can be made.
  87. */
  88. Scanner.prototype.scanUntil = function (re) {
  89. var match, pos = this.tail.search(re);
  90. switch (pos) {
  91. case -1:
  92. match = this.tail;
  93. this.pos += this.tail.length;
  94. this.tail = "";
  95. break;
  96. case 0:
  97. match = "";
  98. break;
  99. default:
  100. match = this.tail.substring(0, pos);
  101. this.tail = this.tail.substring(pos);
  102. this.pos += pos;
  103. }
  104. return match;
  105. };
  106. function Context(view, parent) {
  107. this.view = view || {};
  108. this.parent = parent;
  109. this._cache = {};
  110. }
  111. Context.make = function (view) {
  112. return (view instanceof Context) ? view : new Context(view);
  113. };
  114. Context.prototype.push = function (view) {
  115. return new Context(view, this);
  116. };
  117. Context.prototype.lookup = function (name) {
  118. var value = this._cache[name];
  119. if (!value) {
  120. if (name == '.') {
  121. value = this.view;
  122. } else {
  123. var context = this;
  124. while (context) {
  125. if (name.indexOf('.') > 0) {
  126. value = context.view;
  127. var names = name.split('.'), i = 0;
  128. while (value && i < names.length) {
  129. value = value[names[i++]];
  130. }
  131. } else {
  132. value = context.view[name];
  133. }
  134. if (value != null) break;
  135. context = context.parent;
  136. }
  137. }
  138. this._cache[name] = value;
  139. }
  140. if (typeof value === 'function') value = value.call(this.view);
  141. return value;
  142. };
  143. function Writer() {
  144. this.clearCache();
  145. }
  146. Writer.prototype.clearCache = function () {
  147. this._cache = {};
  148. this._partialCache = {};
  149. };
  150. Writer.prototype.compile = function (template, tags) {
  151. var fn = this._cache[template];
  152. if (!fn) {
  153. var tokens = mustache.parse(template, tags);
  154. fn = this._cache[template] = this.compileTokens(tokens, template);
  155. }
  156. return fn;
  157. };
  158. Writer.prototype.compilePartial = function (name, template, tags) {
  159. var fn = this.compile(template, tags);
  160. this._partialCache[name] = fn;
  161. return fn;
  162. };
  163. Writer.prototype.getPartial = function (name) {
  164. if (!(name in this._partialCache) && this._loadPartial) {
  165. this.compilePartial(name, this._loadPartial(name));
  166. }
  167. return this._partialCache[name];
  168. };
  169. Writer.prototype.compileTokens = function (tokens, template) {
  170. var self = this;
  171. return function (view, partials) {
  172. if (partials) {
  173. if (typeof partials === 'function') {
  174. self._loadPartial = partials;
  175. } else {
  176. for (var name in partials) {
  177. self.compilePartial(name, partials[name]);
  178. }
  179. }
  180. }
  181. return renderTokens(tokens, self, Context.make(view), template);
  182. };
  183. };
  184. Writer.prototype.render = function (template, view, partials) {
  185. return this.compile(template)(view, partials);
  186. };
  187. /**
  188. * Low-level function that renders the given `tokens` using the given `writer`
  189. * and `context`. The `template` string is only needed for templates that use
  190. * higher-order sections to extract the portion of the original template that
  191. * was contained in that section.
  192. */
  193. function renderTokens(tokens, writer, context, template) {
  194. var buffer = '';
  195. var token, tokenValue, value;
  196. for (var i = 0, len = tokens.length; i < len; ++i) {
  197. token = tokens[i];
  198. tokenValue = token[1];
  199. switch (token[0]) {
  200. case '#':
  201. value = context.lookup(tokenValue);
  202. if (typeof value === 'object') {
  203. if (isArray(value)) {
  204. for (var j = 0, jlen = value.length; j < jlen; ++j) {
  205. buffer += renderTokens(token[4], writer, context.push(value[j]), template);
  206. }
  207. } else if (value) {
  208. buffer += renderTokens(token[4], writer, context.push(value), template);
  209. }
  210. } else if (typeof value === 'function') {
  211. var text = template == null ? null : template.slice(token[3], token[5]);
  212. value = value.call(context.view, text, function (template) {
  213. return writer.render(template, context);
  214. });
  215. if (value != null) buffer += value;
  216. } else if (value) {
  217. buffer += renderTokens(token[4], writer, context, template);
  218. }
  219. break;
  220. case '^':
  221. value = context.lookup(tokenValue);
  222. // Use JavaScript's definition of falsy. Include empty arrays.
  223. // See https://github.com/janl/mustache.js/issues/186
  224. if (!value || (isArray(value) && value.length === 0)) {
  225. buffer += renderTokens(token[4], writer, context, template);
  226. }
  227. break;
  228. case '>':
  229. value = writer.getPartial(tokenValue);
  230. if (typeof value === 'function') buffer += value(context);
  231. break;
  232. case '&':
  233. value = context.lookup(tokenValue);
  234. if (value != null) buffer += value;
  235. break;
  236. case 'name':
  237. value = context.lookup(tokenValue);
  238. if (value != null) buffer += mustache.escape(value);
  239. break;
  240. case 'text':
  241. buffer += tokenValue;
  242. break;
  243. }
  244. }
  245. return buffer;
  246. }
  247. /**
  248. * Forms the given array of `tokens` into a nested tree structure where
  249. * tokens that represent a section have two additional items: 1) an array of
  250. * all tokens that appear in that section and 2) the index in the original
  251. * template that represents the end of that section.
  252. */
  253. function nestTokens(tokens) {
  254. var tree = [];
  255. var collector = tree;
  256. var sections = [];
  257. var token;
  258. for (var i = 0, len = tokens.length; i < len; ++i) {
  259. token = tokens[i];
  260. switch (token[0]) {
  261. case '#':
  262. case '^':
  263. sections.push(token);
  264. collector.push(token);
  265. collector = token[4] = [];
  266. break;
  267. case '/':
  268. var section = sections.pop();
  269. section[5] = token[2];
  270. collector = sections.length > 0 ? sections[sections.length - 1][4] : tree;
  271. break;
  272. default:
  273. collector.push(token);
  274. }
  275. }
  276. return tree;
  277. }
  278. /**
  279. * Combines the values of consecutive text tokens in the given `tokens` array
  280. * to a single token.
  281. */
  282. function squashTokens(tokens) {
  283. var squashedTokens = [];
  284. var token, lastToken;
  285. for (var i = 0, len = tokens.length; i < len; ++i) {
  286. token = tokens[i];
  287. if (token) {
  288. if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
  289. lastToken[1] += token[1];
  290. lastToken[3] = token[3];
  291. } else {
  292. lastToken = token;
  293. squashedTokens.push(token);
  294. }
  295. }
  296. }
  297. return squashedTokens;
  298. }
  299. function escapeTags(tags) {
  300. return [
  301. new RegExp(escapeRegExp(tags[0]) + "\\s*"),
  302. new RegExp("\\s*" + escapeRegExp(tags[1]))
  303. ];
  304. }
  305. /**
  306. * Breaks up the given `template` string into a tree of token objects. If
  307. * `tags` is given here it must be an array with two string values: the
  308. * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
  309. * course, the default is to use mustaches (i.e. Mustache.tags).
  310. */
  311. function parseTemplate(template, tags) {
  312. template = template || '';
  313. tags = tags || mustache.tags;
  314. if (typeof tags === 'string') tags = tags.split(spaceRe);
  315. if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', '));
  316. var tagRes = escapeTags(tags);
  317. var scanner = new Scanner(template);
  318. var sections = []; // Stack to hold section tokens
  319. var tokens = []; // Buffer to hold the tokens
  320. var spaces = []; // Indices of whitespace tokens on the current line
  321. var hasTag = false; // Is there a {{tag}} on the current line?
  322. var nonSpace = false; // Is there a non-space char on the current line?
  323. // Strips all whitespace tokens array for the current line
  324. // if there was a {{#tag}} on it and otherwise only space.
  325. function stripSpace() {
  326. if (hasTag && !nonSpace) {
  327. while (spaces.length) {
  328. delete tokens[spaces.pop()];
  329. }
  330. } else {
  331. spaces = [];
  332. }
  333. hasTag = false;
  334. nonSpace = false;
  335. }
  336. var start, type, value, chr, token;
  337. while (!scanner.eos()) {
  338. start = scanner.pos;
  339. // Match any text between tags.
  340. value = scanner.scanUntil(tagRes[0]);
  341. if (value) {
  342. for (var i = 0, len = value.length; i < len; ++i) {
  343. chr = value.charAt(i);
  344. if (isWhitespace(chr)) {
  345. spaces.push(tokens.length);
  346. } else {
  347. nonSpace = true;
  348. }
  349. tokens.push(['text', chr, start, start + 1]);
  350. start += 1;
  351. // Check for whitespace on the current line.
  352. if (chr == '\n') stripSpace();
  353. }
  354. }
  355. // Match the opening tag.
  356. if (!scanner.scan(tagRes[0])) break;
  357. hasTag = true;
  358. // Get the tag type.
  359. type = scanner.scan(tagRe) || 'name';
  360. scanner.scan(whiteRe);
  361. // Get the tag value.
  362. if (type === '=') {
  363. value = scanner.scanUntil(eqRe);
  364. scanner.scan(eqRe);
  365. scanner.scanUntil(tagRes[1]);
  366. } else if (type === '{') {
  367. value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1])));
  368. scanner.scan(curlyRe);
  369. scanner.scanUntil(tagRes[1]);
  370. type = '&';
  371. } else {
  372. value = scanner.scanUntil(tagRes[1]);
  373. }
  374. // Match the closing tag.
  375. if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos);
  376. token = [type, value, start, scanner.pos];
  377. tokens.push(token);
  378. if (type === '#' || type === '^') {
  379. sections.push(token);
  380. } else if (type === '/') {
  381. // Check section nesting.
  382. if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start);
  383. var openSection = sections.pop();
  384. if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
  385. } else if (type === 'name' || type === '{' || type === '&') {
  386. nonSpace = true;
  387. } else if (type === '=') {
  388. // Set the tags for the next time around.
  389. tags = value.split(spaceRe);
  390. if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', '));
  391. tagRes = escapeTags(tags);
  392. }
  393. }
  394. // Make sure there are no open sections when we're done.
  395. var openSection = sections.pop();
  396. if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
  397. tokens = squashTokens(tokens);
  398. return nestTokens(tokens);
  399. }
  400. mustache.name = "mustache.js";
  401. mustache.version = "0.7.2";
  402. mustache.tags = ["{{", "}}"];
  403. mustache.Scanner = Scanner;
  404. mustache.Context = Context;
  405. mustache.Writer = Writer;
  406. mustache.parse = parseTemplate;
  407. // Export the escaping function so that the user may override it.
  408. // See https://github.com/janl/mustache.js/issues/244
  409. mustache.escape = escapeHtml;
  410. // All Mustache.* functions use this writer.
  411. var defaultWriter = new Writer();
  412. /**
  413. * Clears all cached templates and partials in the default writer.
  414. */
  415. mustache.clearCache = function () {
  416. return defaultWriter.clearCache();
  417. };
  418. /**
  419. * Compiles the given `template` to a reusable function using the default
  420. * writer.
  421. */
  422. mustache.compile = function (template, tags) {
  423. return defaultWriter.compile(template, tags);
  424. };
  425. /**
  426. * Compiles the partial with the given `name` and `template` to a reusable
  427. * function using the default writer.
  428. */
  429. mustache.compilePartial = function (name, template, tags) {
  430. return defaultWriter.compilePartial(name, template, tags);
  431. };
  432. /**
  433. * Compiles the given array of tokens (the output of a parse) to a reusable
  434. * function using the default writer.
  435. */
  436. mustache.compileTokens = function (tokens, template) {
  437. return defaultWriter.compileTokens(tokens, template);
  438. };
  439. /**
  440. * Renders the `template` with the given `view` and `partials` using the
  441. * default writer.
  442. */
  443. mustache.render = function (template, view, partials) {
  444. return defaultWriter.render(template, view, partials);
  445. };
  446. // This is here for backwards compatibility with 0.4.x.
  447. mustache.to_html = function (template, view, partials, send) {
  448. var result = mustache.render(template, view, partials);
  449. if (typeof send === "function") {
  450. send(result);
  451. } else {
  452. return result;
  453. }
  454. };
  455. }));
  456. $.mustache = function (template, view, partials) {
  457. return Mustache.render(template, view, partials);
  458. };
  459. $.fn.mustache = function (view, partials) {
  460. return $(this).map(function (i, elm) {
  461. var template = $.trim($(elm).html());
  462. var output = $.mustache(template, view, partials);
  463. return $(output).get();
  464. });
  465. };
  466. })(jQuery);