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.

929 wiersze
28KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = function() {
  6. function ParserException(message) {
  7. this.message = message;
  8. }
  9. ParserException.prototype = {};
  10. var Renderer = function(send_func, mode) {
  11. this._escapeCompiledRegex = null;
  12. this.user_send_func = send_func;
  13. if (mode==='interpreter' || !mode) {
  14. this.commandSet = this.interpreter;
  15. this.send_func = function(text) {
  16. this.user_send_func(text);
  17. }
  18. } else if (mode==='compiler') {
  19. this.commandSet = this.compiler;
  20. this.cached_output = [];
  21. this.send_func = function(text) {
  22. this.cached_output.push(text);
  23. }
  24. } else {
  25. throw new ParserException('Unsupported mode.');
  26. }
  27. this.pragmas = {};
  28. // Fix up the stupidness that is IE's split implementation
  29. this._compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
  30. var hasCapturingSplit = '{{hi}}'.split(/(hi)/).length === 3;
  31. if (!hasCapturingSplit) {
  32. this.splitFunc = this.capturingSplit;
  33. } else {
  34. this.splitFunc = String.prototype.split;
  35. }
  36. };
  37. Renderer.prototype = {
  38. capturingSplit: function(separator) {
  39. // fix up the stupidness that is IE's broken String.split implementation
  40. // originally by Steven Levithan
  41. /* Cross-Browser Split 1.0.1
  42. (c) Steven Levithan <stevenlevithan.com>; MIT License
  43. An ECMA-compliant, uniform cross-browser split method */
  44. var str = this;
  45. var limit = undefined;
  46. // if `separator` is not a regex, use the native `split`
  47. if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
  48. return String.prototype.split.call(str, separator, limit);
  49. }
  50. var output = [],
  51. lastLastIndex = 0,
  52. flags = (separator.ignoreCase ? "i" : "") +
  53. (separator.multiline ? "m" : "") +
  54. (separator.sticky ? "y" : ""),
  55. separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
  56. separator2, match, lastIndex, lastLength;
  57. str = str + ""; // type conversion
  58. if (!this._compliantExecNpcg) {
  59. separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
  60. }
  61. /* behavior for `limit`: if it's...
  62. - `undefined`: no limit.
  63. - `NaN` or zero: return an empty array.
  64. - a positive number: use `Math.floor(limit)`.
  65. - a negative number: no limit.
  66. - other: type-convert, then use the above rules. */
  67. if (limit === undefined || +limit < 0) {
  68. limit = Infinity;
  69. } else {
  70. limit = Math.floor(+limit);
  71. if (!limit) {
  72. return [];
  73. }
  74. }
  75. while (match = separator.exec(str)) {
  76. lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
  77. if (lastIndex > lastLastIndex) {
  78. output.push(str.slice(lastLastIndex, match.index));
  79. // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
  80. if (!this._compliantExecNpcg && match.length > 1) {
  81. match[0].replace(separator2, function () {
  82. for (var i = 1; i < arguments.length - 2; i++) {
  83. if (arguments[i] === undefined) {
  84. match[i] = undefined;
  85. }
  86. }
  87. });
  88. }
  89. if (match.length > 1 && match.index < str.length) {
  90. Array.prototype.push.apply(output, match.slice(1));
  91. }
  92. lastLength = match[0].length;
  93. lastLastIndex = lastIndex;
  94. if (output.length >= limit) {
  95. break;
  96. }
  97. }
  98. if (separator.lastIndex === match.index) {
  99. separator.lastIndex++; // avoid an infinite loop
  100. }
  101. }
  102. if (lastLastIndex === str.length) {
  103. if (lastLength || !separator.test("")) {
  104. output.push("");
  105. }
  106. } else {
  107. output.push(str.slice(lastLastIndex));
  108. }
  109. return output.length > limit ? output.slice(0, limit) : output;
  110. },
  111. render: function(template, context, partials) {
  112. template = this.parse_pragmas(template, '{{', '}}');
  113. var tokens = this.tokenize(template, '{{', '}}');
  114. this.parse(this.createParserContext(tokens, partials, '{{', '}}'), [context]);
  115. },
  116. createParserContext: function(tokens, partials, openTag, closeTag) {
  117. return {
  118. tokens: tokens,
  119. token: function() { return this.tokens[this.index]; },
  120. index: 0,
  121. length: tokens.length,
  122. partials: partials,
  123. stack: [],
  124. openTag: openTag,
  125. closeTag: closeTag
  126. };
  127. },
  128. tokenize: function(template, openTag, closeTag) {
  129. var delimiters = [
  130. '\\{',
  131. '&',
  132. '\\}',
  133. '#',
  134. '\\^',
  135. '\\/',
  136. '>',
  137. '=',
  138. '%',
  139. '!',
  140. '\\s+'
  141. ];
  142. delimiters.unshift(this.escape_regex(openTag));
  143. delimiters.unshift(this.escape_regex(closeTag));
  144. var regex = new RegExp('(' + delimiters.join('|') + ')');
  145. var tokens = this.splitFunc.call(template, regex);
  146. var cleaned_tokens = [];
  147. for (var i = 0, n = tokens.length; i<n; ++i) {
  148. if (tokens[i]!=='') {
  149. cleaned_tokens.push(tokens[i]);
  150. }
  151. }
  152. return cleaned_tokens;
  153. },
  154. /*
  155. Looks for %PRAGMAS
  156. */
  157. parse_pragmas: function(template, openTag, closeTag) {
  158. /* includes tag */
  159. function includes(needle, haystack) {
  160. return haystack.indexOf(openTag + needle) !== -1;
  161. }
  162. // no pragmas, easy escape
  163. if(!includes("%", template)) {
  164. return template;
  165. }
  166. var that = this;
  167. var regex = new RegExp(this.escape_regex(openTag) + "%([\\w-]+)(\\s*)(.*?(?=" + this.escape_regex(closeTag) + "))" + this.escape_regex(closeTag));
  168. return template.replace(regex, function(match, pragma, space, suffix) {
  169. var options = undefined;
  170. if (suffix.length>0) {
  171. var optionPairs = suffix.split(',');
  172. var scratch;
  173. options = {};
  174. for (var i=0, n=optionPairs.length; i<n; ++i) {
  175. scratch = optionPairs[i].split('=');
  176. if (scratch.length !== 2) {
  177. throw new ParserException('Malformed pragma options');
  178. }
  179. options[scratch[0]] = scratch[1];
  180. }
  181. }
  182. if (that.is_function(that.pragmaDirectives[pragma])) {
  183. that.pragmaDirectives[pragma].call(that, options);
  184. } else {
  185. throw new ParserException("This implementation of mustache doesn't understand the '" + pragma + "' pragma");
  186. }
  187. return ""; // blank out all pragmas
  188. });
  189. },
  190. parse: function(parserContext, contextStack) {
  191. var state = 'text';
  192. for (; parserContext.index<parserContext.length; ++parserContext.index) {
  193. state = this.stateMachine[state].call(this, parserContext, contextStack);
  194. }
  195. // make sure the parser finished at an appropriate terminal state
  196. if (state!=='text') {
  197. this.stateMachine['endOfDoc'].call(this, parserContext, contextStack);
  198. } else {
  199. this.commandSet.text.call(this);
  200. }
  201. },
  202. escape_regex: function(text) {
  203. // thank you Simon Willison
  204. if (!this._escapeCompiledRegex) {
  205. var specials = [
  206. '/', '.', '*', '+', '?', '|',
  207. '(', ')', '[', ']', '{', '}', '\\'
  208. ];
  209. this._escapeCompiledRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
  210. }
  211. return text.replace(this._escapeCompiledRegex, '\\$1');
  212. },
  213. isWhitespace: function(token) {
  214. return token.match(/^\s+$/)!==null;
  215. },
  216. stateMachine: {
  217. text: function(parserContext, contextStack) {
  218. switch (parserContext.token()) {
  219. case parserContext.openTag:
  220. this.commandSet.text.call(this);
  221. return 'openMustache';
  222. default:
  223. this.send_func(parserContext.token());
  224. return 'text';
  225. }
  226. },
  227. openMustache: function(parserContext, contextStack) {
  228. switch (parserContext.token()) {
  229. case '{':
  230. parserContext.stack.push({tagType:'unescapedVariable', subtype: 'tripleMustache'});
  231. return 'keyName';
  232. case '&':
  233. parserContext.stack.push({tagType:'unescapedVariable'});
  234. return 'keyName';
  235. case '#':
  236. parserContext.stack.push({tagType:'section'});
  237. return 'keyName';
  238. case '^':
  239. parserContext.stack.push({tagType:'invertedSection'});
  240. return 'keyName';
  241. case '>':
  242. parserContext.stack.push({tagType:'partial'});
  243. return 'simpleKeyName';
  244. case '=':
  245. parserContext.stack.push({tagType: 'setDelimiter'});
  246. return 'setDelimiterStart';
  247. case '!':
  248. return 'discard';
  249. case '%':
  250. throw new ParserException('Pragmas are only supported as a preprocessing directive.');
  251. case '/': // close mustache
  252. throw new ParserException('Unexpected closing tag.');
  253. case '}': // close triple mustache
  254. throw new ParserException('Unexpected token encountered.');
  255. default:
  256. parserContext.stack.push({tagType:'variable'});
  257. return this.stateMachine.keyName.call(this, parserContext, contextStack);
  258. }
  259. },
  260. closeMustache: function(parserContext, contextStack) {
  261. if (this.isWhitespace(parserContext.token())) {
  262. return 'closeMustache';
  263. } else if (parserContext.token()===parserContext.closeTag) {
  264. return this.dispatchCommand(parserContext, contextStack);
  265. }
  266. },
  267. expectClosingMustache: function(parserContext, contextStack) {
  268. if (parserContext.closeTag==='}}' &&
  269. parserContext.token()==='}}') {
  270. return 'expectClosingParenthesis';
  271. } else if (parserContext.token()==='}') {
  272. return 'closeMustache';
  273. } else {
  274. throw new ParserException('Unexpected token encountered.');
  275. }
  276. },
  277. expectClosingParenthesis: function(parserContext, contextStack) {
  278. if (parserContext.token()==='}') {
  279. return this.dispatchCommand(parserContext, contextStack);
  280. } else {
  281. throw new ParserException('Unexpected token encountered.');
  282. }
  283. },
  284. keyName: function(parserContext, contextStack) {
  285. var result = this.stateMachine.simpleKeyName.call(this, parserContext, contextStack);
  286. if (result==='closeMustache') {
  287. var tagKey = parserContext.stack.pop();
  288. var tag = parserContext.stack.pop();
  289. if (tag.tagType==='unescapedVariable' && tag.subtype==='tripleMustache') {
  290. parserContext.stack.push({tagType:'unescapedVariable'});
  291. parserContext.stack.push(tagKey);
  292. return 'expectClosingMustache';
  293. } else {
  294. parserContext.stack.push(tag);
  295. parserContext.stack.push(tagKey);
  296. return 'closeMustache';
  297. }
  298. } else if (result==='simpleKeyName') {
  299. return 'keyName';
  300. } else {
  301. throw new ParserException('Unexpected branch in tag name: ' + result);
  302. }
  303. },
  304. simpleKeyName: function(parserContext, contextStack) {
  305. if (this.isWhitespace(parserContext.token())) {
  306. return 'simpleKeyName';
  307. } else {
  308. parserContext.stack.push(parserContext.token());
  309. return 'closeMustache';
  310. }
  311. },
  312. setDelimiterStart: function(parserContext, contextStack) {
  313. if (this.isWhitespace(parserContext.token()) ||
  314. parserContext.token()==='=') {
  315. throw new ParserException('Syntax error in Set Delimiter tag');
  316. } else {
  317. parserContext.stack.push(parserContext.token());
  318. return 'setDelimiterStartOrWhitespace';
  319. }
  320. },
  321. setDelimiterStartOrWhitespace: function(parserContext, contextStack) {
  322. if (this.isWhitespace(parserContext.token())) {
  323. return 'setDelimiterEnd';
  324. } else if (parserContext.token()==='='){
  325. throw new ParserException('Syntax error in Set Delimiter tag');
  326. } else {
  327. parserContext.stack.push(parserContext.stack.pop() + parserContext.token());
  328. return 'setDelimiterStartOrWhitespace';
  329. }
  330. },
  331. setDelimiterEnd: function(parserContext, contextStack) {
  332. if (this.isWhitespace(parserContext.token())) {
  333. return 'setDelimiterEnd';
  334. } else if (parserContext.token()==='=') {
  335. throw new ParserException('Syntax error in Set Delimiter tag');
  336. } else {
  337. parserContext.stack.push(parserContext.token());
  338. return 'setDelimiterEndOrEqualSign';
  339. }
  340. },
  341. setDelimiterEndOrEqualSign: function(parserContext, contextStack) {
  342. if (parserContext.token()==='=') {
  343. return 'setDelimiterExpectClosingTag';
  344. } else if (this.isWhitespace(parserContext.token())) {
  345. throw new ParserException('Syntax error in Set Delimiter tag');
  346. } else {
  347. parserContext.stack.push(parserContext.stack.pop() + parserContext.token());
  348. return 'setDelimiterEndOrEqualSign';
  349. }
  350. },
  351. setDelimiterExpectClosingTag: function(parserContext, contextStack) {
  352. if (parserContext.token()===parserContext.closeTag) {
  353. var newCloseTag = parserContext.stack.pop();
  354. var newOpenTag = parserContext.stack.pop();
  355. var command = parserContext.stack.pop();
  356. if (command.tagType!=='setDelimiter') {
  357. throw new ParserException('Syntax error in Set Delimiter tag');
  358. } else {
  359. var tokens = this.tokenize(
  360. parserContext.tokens.slice(parserContext.index+1).join(''),
  361. newOpenTag,
  362. newCloseTag);
  363. var newParserContext = this.createParserContext(tokens,
  364. parserContext.partials,
  365. newOpenTag,
  366. newCloseTag);
  367. parserContext.tokens = newParserContext.tokens;
  368. parserContext.index = -1;
  369. parserContext.length = newParserContext.length;
  370. parserContext.openTag = newParserContext.openTag;
  371. parserContext.closeTag = newParserContext.closeTag;
  372. return 'text';
  373. }
  374. } else {
  375. throw new ParserException('Syntax error in Set Delimiter tag');
  376. }
  377. },
  378. endSectionScan: function(parserContext, contextStack) {
  379. switch (parserContext.token()) {
  380. case parserContext.openTag:
  381. return 'expectSectionOrEndSection';
  382. default:
  383. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.token());
  384. return 'endSectionScan';
  385. }
  386. },
  387. expectSectionOrEndSection: function(parserContext, contextStack) {
  388. switch (parserContext.token()) {
  389. case '#':
  390. case '^':
  391. parserContext.stack[parserContext.stack.length-1].depth++;
  392. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag, parserContext.token());
  393. return 'endSectionScan';
  394. case '/':
  395. parserContext.stack.push({tagType:'endSection'});
  396. return 'simpleKeyName';
  397. default:
  398. parserContext.stack[parserContext.stack.length-1].content.push(parserContext.openTag, parserContext.token());
  399. return 'endSectionScan';
  400. }
  401. },
  402. discard: function(parserContext, contextStack) {
  403. if (parserContext.token()==='!') {
  404. return 'closeComment';
  405. } else {
  406. return 'discard';
  407. }
  408. },
  409. closeComment: function(parserContext, contextStack) {
  410. if (parserContext.token()!==parserContext.closeTag) {
  411. return 'discard';
  412. } else {
  413. return 'text';
  414. }
  415. },
  416. endOfDoc: function(parserContext, contextStack) {
  417. // eventually we may want to give better error messages
  418. throw new ParserException('Unexpected end of document.');
  419. }
  420. },
  421. dispatchCommand: function(parserContext, contextStack) {
  422. var key = parserContext.stack.pop();
  423. var command = parserContext.stack.pop();
  424. switch (command.tagType) {
  425. case 'section':
  426. case 'invertedSection':
  427. parserContext.stack.push({sectionType:command.tagType, key:key, content:[], depth:1});
  428. return 'endSectionScan';
  429. case 'variable':
  430. this.commandSet.variable.call(this, key, contextStack);
  431. return 'text';
  432. case 'unescapedVariable':
  433. this.commandSet.unescaped_variable.call(this, key, contextStack);
  434. return 'text';
  435. case 'partial':
  436. this.commandSet.partial.call(this, key,
  437. contextStack,
  438. parserContext.partials,
  439. parserContext.openTag,
  440. parserContext.closeTag);
  441. return 'text';
  442. case 'endSection':
  443. var section = parserContext.stack.pop();
  444. if (--section.depth === 0) {
  445. if (section.key === key) {
  446. this.commandSet.section.call(this, section.sectionType,
  447. section.content,
  448. key,
  449. contextStack,
  450. parserContext.partials,
  451. parserContext.openTag,
  452. parserContext.closeTag);
  453. return 'text';
  454. } else {
  455. throw new ParserException('Unbalanced open/close section tags');
  456. }
  457. } else {
  458. section.content.push('{{', '/', key, '}}');
  459. parserContext.stack.push(section);
  460. return 'endSectionScan';
  461. }
  462. default:
  463. throw new ParserException('Unknown dispatch command: ' + command.tagType);
  464. }
  465. },
  466. pragmaDirectives: {
  467. 'IMPLICIT-ITERATOR': function(options) {
  468. this.pragmas['IMPLICIT-ITERATOR'] = {};
  469. if (options) {
  470. this.pragmas['IMPLICIT-ITERATOR'].iterator = options['iterator'];
  471. }
  472. }
  473. },
  474. /*
  475. find `name` in current `context`. That is find me a value
  476. from the view object
  477. */
  478. find: function(name, context) {
  479. // Checks whether a value is truthy or false or 0
  480. function is_kinda_truthy(bool) {
  481. return bool === false || bool === 0 || bool;
  482. }
  483. var value;
  484. if (is_kinda_truthy(context[name])) {
  485. value = context[name];
  486. }
  487. if (this.is_function(value)) {
  488. return value.apply(context);
  489. }
  490. return value;
  491. },
  492. find_in_stack: function(name, contextStack) {
  493. var value;
  494. value = this.find(name, contextStack[contextStack.length-1]);
  495. if (value!==undefined) { return value; }
  496. if (contextStack.length>1) {
  497. value = this.find(name, contextStack[0]);
  498. if (value!==undefined) { return value; }
  499. }
  500. return undefined;
  501. },
  502. is_function: function(a) {
  503. return a && typeof a === 'function';
  504. },
  505. is_object: function(a) {
  506. return a && typeof a === 'object';
  507. },
  508. is_array: function(a) {
  509. return Object.prototype.toString.call(a) === '[object Array]';
  510. },
  511. interpreter: {
  512. text: function() {
  513. // in this implementation, rendering text is meaningless
  514. // since the send_func method simply forwards to user_send_func
  515. },
  516. variable: function(key, contextStack) {
  517. function escapeHTML(str) {
  518. return ('' + str).replace(/&/g,'&amp;')
  519. .replace(/</g,'&lt;')
  520. .replace(/>/g,'&gt;');
  521. }
  522. var result = this.find_in_stack(key, contextStack);
  523. if (result!==undefined) {
  524. this.user_send_func(escapeHTML(result));
  525. }
  526. },
  527. unescaped_variable: function(key, contextStack) {
  528. var result = this.find_in_stack(key, contextStack);
  529. if (result!==undefined) {
  530. this.user_send_func(result);
  531. }
  532. },
  533. partial: function(key, contextStack, partials, openTag, closeTag) {
  534. if (!partials || partials[key] === undefined) {
  535. throw new ParserException('Unknown partial \'' + key + '\'');
  536. }
  537. var res = this.find_in_stack(key, contextStack);
  538. if (this.is_object(res)) {
  539. contextStack.push(res);
  540. }
  541. var tokens = this.tokenize(partials[key], openTag, closeTag);
  542. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  543. if (this.is_object(res)) {
  544. contextStack.pop();
  545. }
  546. },
  547. section: function(sectionType, fragmentTokens, key, contextStack, partials, openTag, closeTag) {
  548. // by @langalex, support for arrays of strings
  549. var that = this;
  550. function create_context(_context) {
  551. if(that.is_object(_context)) {
  552. return _context;
  553. } else {
  554. var iterator = '.';
  555. if(that.pragmas["IMPLICIT-ITERATOR"] &&
  556. that.pragmas["IMPLICIT-ITERATOR"].iterator) {
  557. iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
  558. }
  559. var ctx = {};
  560. ctx[iterator] = _context;
  561. return ctx;
  562. }
  563. }
  564. var value = this.find_in_stack(key, contextStack);
  565. var tokens;
  566. if (sectionType==='invertedSection') {
  567. if (!value || this.is_array(value) && value.length === 0) {
  568. // false or empty list, render it
  569. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), contextStack);
  570. }
  571. } else if (sectionType==='section') {
  572. if (this.is_array(value)) { // Enumerable, Let's loop!
  573. for (var i=0, n=value.length; i<n; ++i) {
  574. contextStack.push(create_context(value[i]));
  575. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), contextStack);
  576. contextStack.pop();
  577. }
  578. } else if (this.is_object(value)) { // Object, Use it as subcontext!
  579. contextStack.push(value);
  580. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), contextStack);
  581. contextStack.pop();
  582. } else if (this.is_function(value)) {
  583. // higher order section
  584. var that = this;
  585. var result = value.call(contextStack[contextStack.length-1], fragmentTokens.join(''), function(resultFragment) {
  586. var tempStream = [];
  587. var old_send_func = that.user_send_func;
  588. that.user_send_func = function(text) { tempStream.push(text); };
  589. tokens = that.tokenize(resultFragment, openTag, closeTag);
  590. that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  591. that.user_send_func = old_send_func;
  592. return tempStream.join('');
  593. });
  594. this.user_send_func(result);
  595. } else if (value) {
  596. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), contextStack);
  597. }
  598. } else {
  599. throw new ParserException('Unknown section type ' + sectionType);
  600. }
  601. }
  602. },
  603. compiler: {
  604. text: function() {
  605. var outputText = this.cached_output.join('');
  606. this.cached_output = [];
  607. this.user_send_func(function(contextStack, send_func) {
  608. send_func(outputText);
  609. });
  610. },
  611. variable: function(key/*, contextStack*/) {
  612. function escapeHTML(str) {
  613. return ('' + str).replace(/&/g,'&amp;')
  614. .replace(/</g,'&lt;')
  615. .replace(/>/g,'&gt;');
  616. }
  617. var that = this;
  618. this.user_send_func(function(contextStack, send_func) {
  619. var result = that.find_in_stack(key, contextStack);
  620. if (result!==undefined) {
  621. send_func(escapeHTML(result));
  622. }
  623. });
  624. },
  625. unescaped_variable: function(key/*, contextStack*/) {
  626. var that = this;
  627. this.user_send_func(function(contextStack, send_func) {
  628. var result = that.find_in_stack(key, contextStack);
  629. if (result!==undefined) {
  630. send_func(result);
  631. }
  632. });
  633. },
  634. partial: function(key, reserved/*contextStack*/, partials, openTag, closeTag) {
  635. if (!partials || partials[key] === undefined) {
  636. throw new ParserException('Unknown partial \'' + key + '\'');
  637. }
  638. if (!this.is_function(partials[key])) {
  639. var old_user_send_func = this.user_send_func;
  640. var commands = [];
  641. this.user_send_func = function(command) { commands.push(command); };
  642. var tokens = this.tokenize(partials[key], openTag, closeTag);
  643. partials[key] = function() {}; // blank out the paritals so that infinite recursion doesn't happen
  644. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
  645. this.user_send_func = old_user_send_func;
  646. var that = this;
  647. partials[key] = function(contextStack, send_func) {
  648. var res = that.find_in_stack(key, contextStack);
  649. if (that.is_object(res)) {
  650. contextStack.push(res);
  651. }
  652. for (var i=0,n=commands.length; i<n; ++i) {
  653. commands[i](contextStack, send_func);
  654. }
  655. if (that.is_object(res)) {
  656. contextStack.pop();
  657. }
  658. };
  659. }
  660. this.user_send_func(function(contextStack, send_func) { partials[key](contextStack, send_func); });
  661. },
  662. section: function(sectionType, fragmentTokens, key, reserved/*contextStack*/, partials, openTag, closeTag) {
  663. // by @langalex, support for arrays of strings
  664. var that = this;
  665. function create_context(_context) {
  666. if(that.is_object(_context)) {
  667. return _context;
  668. } else {
  669. var iterator = '.';
  670. if(that.pragmas["IMPLICIT-ITERATOR"] &&
  671. that.pragmas["IMPLICIT-ITERATOR"].iterator) {
  672. iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
  673. }
  674. var ctx = {};
  675. ctx[iterator] = _context;
  676. return ctx;
  677. }
  678. }
  679. var old_user_send_func = this.user_send_func;
  680. var commands = [];
  681. this.user_send_func = function(command) { commands.push(command); };
  682. this.parse(this.createParserContext(fragmentTokens, partials, openTag, closeTag), reserved);
  683. this.user_send_func = old_user_send_func;
  684. var section_command = function(contextStack, send_func) {
  685. for (var i=0, n=commands.length; i<n; ++i) {
  686. commands[i](contextStack, send_func);
  687. }
  688. };
  689. var that = this;
  690. if (sectionType==='invertedSection') {
  691. this.user_send_func(function(contextStack, send_func) {
  692. var value = that.find_in_stack(key, contextStack);
  693. if (!value || that.is_array(value) && value.length === 0) {
  694. // false or empty list, render it
  695. section_command(contextStack, send_func);
  696. }
  697. });
  698. } else if (sectionType==='section') {
  699. this.user_send_func(function(contextStack, send_func) {
  700. var value = that.find_in_stack(key, contextStack);
  701. if (that.is_array(value)) { // Enumerable, Let's loop!
  702. for (var i=0, n=value.length; i<n; ++i) {
  703. contextStack.push(create_context(value[i]));
  704. section_command(contextStack, send_func);
  705. contextStack.pop();
  706. }
  707. } else if (that.is_object(value)) { // Object, Use it as subcontext!
  708. contextStack.push(value);
  709. section_command(contextStack, send_func);
  710. contextStack.pop();
  711. } else if (that.is_function(value)) {
  712. // higher order section
  713. // note that HOS triggers a compilation on the resultFragment.
  714. // this is slow (in relation to a fully compiled template)
  715. // since it invokes a call to the parser
  716. var result = value.call(contextStack[contextStack.length-1], fragmentTokens.join(''), function(resultFragment) {
  717. var cO = [];
  718. var s = function(command) { cO.push(command); };
  719. var hos_renderer = new Renderer(s, 'compiler');
  720. resultFragment = hos_renderer.parse_pragmas(resultFragment, openTag, closeTag);
  721. var tokens = hos_renderer.tokenize(resultFragment, openTag, closeTag);
  722. hos_renderer.parse(hos_renderer.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  723. var o = [];
  724. var sT = function(output) { o.push(output); };
  725. for (var i=0,n=cO.length; i<n; ++i) {
  726. commands[i](contextStack, sT);
  727. }
  728. return o.join('');
  729. });
  730. send_func(result);
  731. } else if (value) {
  732. section_command(contextStack, send_func);
  733. }
  734. });
  735. } else {
  736. throw new ParserException('Unknown section type ' + sectionType);
  737. }
  738. }
  739. }
  740. }
  741. return({
  742. name: "mustache.js",
  743. version: "0.4.0-vcs",
  744. /*
  745. Turns a template and view into HTML
  746. */
  747. to_html: function(template, view, partials, send_func) {
  748. if (!template) { return ''; }
  749. partials = partials || {};
  750. view = view || {};
  751. var o = send_func ? undefined : [];
  752. var s = send_func || function(output) { o.push(output); };
  753. var renderer = new Renderer(s, 'interpreter');
  754. renderer.render(template, view, partials);
  755. if (!send_func) {
  756. return o.join('');
  757. }
  758. },
  759. /*
  760. Compiles a template into an equivalent JS function for faster
  761. repeated execution.
  762. */
  763. compile: function(template, partials) {
  764. if (!template) { return function() { return '' }; }
  765. var p = {};
  766. if (partials) {
  767. for (var key in partials) {
  768. if (partials.hasOwnProperty(key)) {
  769. p[key] = partials[key];
  770. }
  771. }
  772. }
  773. var commands = [];
  774. var s = function(command) { commands.push(command); };
  775. var renderer = new Renderer(s, 'compiler');
  776. renderer.render(template, {}, p);
  777. return function(view, send_func) {
  778. view = view || {};
  779. var o = send_func ? undefined : [];
  780. var s = send_func || function(output) { o.push(output); };
  781. for (var i=0,n=commands.length; i<n; ++i) {
  782. commands[i]([view], s);
  783. }
  784. if (!send_func) {
  785. return o.join('');
  786. }
  787. };
  788. }
  789. });
  790. }();