Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

538 wiersze
14KB

  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 for the current line
  186. nonSpace = false; // is there a non-space char on the current line?
  187. // Strips all space characters from the code array for the current line
  188. // if there was a {{tag}} on it and otherwise only spaces.
  189. var stripSpace = function () {
  190. if (!nonSpace && !options.space) {
  191. while (spaces.length) {
  192. code.splice(spaces.pop(), 1);
  193. }
  194. } else {
  195. spaces = [];
  196. }
  197. nonSpace = false;
  198. };
  199. var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
  200. var setTags = function (source) {
  201. tags = trim(source).split(/\s+/);
  202. nextOpenTag = tags[0];
  203. nextCloseTag = tags[tags.length - 1];
  204. };
  205. var includePartial = function (source) {
  206. code.push(
  207. '");',
  208. updateLine,
  209. '\nvar partial = partials["' + trim(source) + '"];',
  210. '\nif (partial) {',
  211. '\n send(render(partial, stack[stack.length - 1], partials));',
  212. '\n}',
  213. '\nsend("'
  214. );
  215. };
  216. var openSection = function (source, inverted) {
  217. var name = trim(source);
  218. if (name === "") {
  219. throw debug(new Error("Section name may not be empty"), template, line, options.file);
  220. }
  221. sectionStack.push({name: name, inverted: inverted});
  222. code.push(
  223. '");',
  224. updateLine,
  225. '\nvar name = "' + name + '";',
  226. '\nvar callback = (function () {',
  227. '\n var buffer, send = function (chunk) { buffer.push(chunk); };',
  228. '\n return function () {',
  229. '\n buffer = [];',
  230. '\nsend("'
  231. );
  232. };
  233. var openInvertedSection = function (source) {
  234. openSection(source, true);
  235. };
  236. var closeSection = function (source) {
  237. var name = trim(source);
  238. var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
  239. if (!openName || name != openName) {
  240. throw debug(new Error('Section named "' + name + '" was never opened'), template, line, file);
  241. }
  242. var section = sectionStack.pop();
  243. code.push(
  244. '");',
  245. '\n return buffer.join("");',
  246. '\n };',
  247. '\n})();'
  248. );
  249. if (section.inverted) {
  250. code.push("\nsendSection(send,name,callback,stack,true);");
  251. } else {
  252. code.push("\nsendSection(send,name,callback,stack);");
  253. }
  254. code.push('\nsend("');
  255. };
  256. var sendPlain = function (source) {
  257. code.push(
  258. '");',
  259. updateLine,
  260. '\nsend(findName("' + trim(source) + '", stack));',
  261. '\nsend("'
  262. );
  263. };
  264. var sendEscaped = function (source) {
  265. code.push(
  266. '");',
  267. updateLine,
  268. '\nsend(escapeHTML(findName("' + trim(source) + '", stack)));',
  269. '\nsend("'
  270. );
  271. };
  272. var line = 1, c, callback;
  273. for (var i = 0, len = template.length; i < len; ++i) {
  274. if (template.slice(i, i + openTag.length) === openTag) {
  275. i += openTag.length;
  276. c = template.substr(i, 1);
  277. updateLine = '\nline = ' + line + ';';
  278. nextOpenTag = openTag;
  279. nextCloseTag = closeTag;
  280. switch (c) {
  281. case "!": // comment
  282. i++;
  283. callback = null;
  284. break;
  285. case "=": // change open/close tags, e.g. {{=<% %>=}}
  286. i++;
  287. closeTag = "=" + closeTag;
  288. callback = setTags;
  289. break;
  290. case ">": // include partial
  291. i++;
  292. callback = includePartial;
  293. break;
  294. case "#": // start section
  295. i++;
  296. callback = openSection;
  297. break;
  298. case "^": // start inverted section
  299. i++;
  300. callback = openInvertedSection;
  301. break;
  302. case "/": // end section
  303. i++;
  304. callback = closeSection;
  305. break;
  306. case "{": // plain variable
  307. closeTag = "}" + closeTag;
  308. // fall through
  309. case "&": // plain variable
  310. i++;
  311. nonSpace = true;
  312. callback = sendPlain;
  313. break;
  314. default: // escaped variable
  315. nonSpace = true;
  316. callback = sendEscaped;
  317. }
  318. var end = template.indexOf(closeTag, i);
  319. if (end === -1) {
  320. throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
  321. }
  322. var source = template.substring(i, end);
  323. if (callback) {
  324. callback(source);
  325. }
  326. // Maintain line count for \n in source.
  327. var n = 0;
  328. while (~(n = source.indexOf("\n", n))) {
  329. line++;
  330. n++;
  331. }
  332. i = end + closeTag.length - 1;
  333. openTag = nextOpenTag;
  334. closeTag = nextCloseTag;
  335. } else {
  336. c = template.substr(i, 1);
  337. switch (c) {
  338. case '"':
  339. case "\\":
  340. nonSpace = true;
  341. code.push("\\" + c);
  342. break;
  343. case "\n":
  344. spaces.push(code.length);
  345. code.push("\\n");
  346. stripSpace(); // Check for whitespace on the current line.
  347. line++;
  348. break;
  349. default:
  350. if (isWhitespace(c)) {
  351. spaces.push(code.length);
  352. } else {
  353. nonSpace = true;
  354. }
  355. code.push(c);
  356. }
  357. }
  358. }
  359. if (sectionStack.length != 0) {
  360. throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
  361. }
  362. // Clean up any whitespace from a closing {{tag}} that was at the end
  363. // of the template without a trailing \n.
  364. stripSpace();
  365. code.push(
  366. '");',
  367. "\nsend(null);", // Send null as the last operation.
  368. "\n} catch (e) { throw {error: e, line: line}; }"
  369. );
  370. // Ignore empty send("") statements.
  371. var body = code.join("").replace(/send\(""\);\n/g, "");
  372. if (options.debug) {
  373. if (typeof console != "undefined" && console.log) {
  374. console.log(body);
  375. } else if (typeof print === "function") {
  376. print(body);
  377. }
  378. }
  379. return body;
  380. }
  381. /**
  382. * Used by `compile` to generate a reusable function for the given `template`.
  383. */
  384. function _compile(template, options) {
  385. var args = "view,partials,send,stack,findName,escapeHTML,sendSection,render";
  386. var body = parse(template, options);
  387. var fn = new Function(args, body);
  388. // This anonymous function wraps the generated function so we can do
  389. // argument coercion, setup some variables, and handle any errors
  390. // encountered while executing it.
  391. return function (view, partials, callback) {
  392. if (typeof partials === "function") {
  393. callback = partials;
  394. partials = {};
  395. }
  396. partials = partials || {};
  397. var buffer = []; // output buffer
  398. var send = callback || function (chunk) {
  399. buffer.push(chunk);
  400. };
  401. var stack = [view]; // context stack
  402. try {
  403. fn(view, partials, send, stack, findName, escapeHTML, sendSection, render);
  404. } catch (e) {
  405. throw debug(e.error, template, e.line, options.file);
  406. }
  407. return buffer.join("");
  408. };
  409. }
  410. // Cache of pre-compiled templates.
  411. var _cache = {};
  412. /**
  413. * Clear the cache of compiled templates.
  414. */
  415. function clearCache() {
  416. _cache = {};
  417. }
  418. /**
  419. * Compiles the given `template` into a reusable function using the given
  420. * `options`. In addition to the options accepted by Mustache.parse,
  421. * recognized options include the following:
  422. *
  423. * - cache Set `false` to bypass any pre-compiled version of the given
  424. * template. Otherwise, a given `template` string will be cached
  425. * the first time it is parsed
  426. */
  427. function compile(template, options) {
  428. options = options || {};
  429. // Use a pre-compiled version from the cache if we have one.
  430. if (options.cache !== false) {
  431. if (!_cache[template]) {
  432. _cache[template] = _compile(template, options);
  433. }
  434. return _cache[template];
  435. }
  436. return _compile(template, options);
  437. }
  438. /**
  439. * High-level function that renders the given `template` using the given
  440. * `view`, `partials`, and `callback`. The `callback` is used to return the
  441. * output piece by piece, as it is rendered. When finished, the callback will
  442. * receive `null` as its argument, after which it will not be called any more.
  443. * If no callback is given, the complete rendered template will be used as the
  444. * return value for the function.
  445. *
  446. * Note: If no partials are needed, the third argument may be the callback.
  447. * If you need to use any of the template options (see `compile` above), you
  448. * must compile in a separate step, and then call that compiled function.
  449. */
  450. function render(template, view, partials, callback) {
  451. return compile(template)(view, partials, callback);
  452. }
  453. })(Mustache);