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

936 строки
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.join(''),
  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, mustacheFragment, 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. tokens = this.tokenize(mustacheFragment, openTag, closeTag);
  570. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  571. }
  572. } else if (sectionType==='section') {
  573. if (this.is_array(value)) { // Enumerable, Let's loop!
  574. tokens = this.tokenize(mustacheFragment, openTag, closeTag);
  575. for (var i=0, n=value.length; i<n; ++i) {
  576. contextStack.push(create_context(value[i]));
  577. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  578. contextStack.pop();
  579. }
  580. } else if (this.is_object(value)) { // Object, Use it as subcontext!
  581. tokens = this.tokenize(mustacheFragment, openTag, closeTag);
  582. contextStack.push(value);
  583. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  584. contextStack.pop();
  585. } else if (this.is_function(value)) {
  586. // higher order section
  587. var that = this;
  588. var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
  589. var tempStream = [];
  590. var old_send_func = that.user_send_func;
  591. that.user_send_func = function(text) { tempStream.push(text); };
  592. tokens = that.tokenize(resultFragment, openTag, closeTag);
  593. that.parse(that.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  594. that.user_send_func = old_send_func;
  595. return tempStream.join('');
  596. });
  597. this.user_send_func(result);
  598. } else if (value) {
  599. tokens = this.tokenize(mustacheFragment, openTag, closeTag);
  600. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  601. }
  602. } else {
  603. throw new ParserException('Unknown section type ' + sectionType);
  604. }
  605. }
  606. },
  607. compiler: {
  608. text: function() {
  609. var outputText = this.cached_output.join('');
  610. this.cached_output = [];
  611. this.user_send_func(function(contextStack, send_func) {
  612. send_func(outputText);
  613. });
  614. },
  615. variable: function(key/*, contextStack*/) {
  616. function escapeHTML(str) {
  617. return ('' + str).replace(/&/g,'&amp;')
  618. .replace(/</g,'&lt;')
  619. .replace(/>/g,'&gt;');
  620. }
  621. var that = this;
  622. this.user_send_func(function(contextStack, send_func) {
  623. var result = that.find_in_stack(key, contextStack);
  624. if (result!==undefined) {
  625. send_func(escapeHTML(result));
  626. }
  627. });
  628. },
  629. unescaped_variable: function(key/*, contextStack*/) {
  630. var that = this;
  631. this.user_send_func(function(contextStack, send_func) {
  632. var result = that.find_in_stack(key, contextStack);
  633. if (result!==undefined) {
  634. send_func(result);
  635. }
  636. });
  637. },
  638. partial: function(key, reserved/*contextStack*/, partials, openTag, closeTag) {
  639. if (!partials || partials[key] === undefined) {
  640. throw new ParserException('Unknown partial \'' + key + '\'');
  641. }
  642. if (!this.is_function(partials[key])) {
  643. var old_user_send_func = this.user_send_func;
  644. var commands = [];
  645. this.user_send_func = function(command) { commands.push(command); };
  646. var tokens = this.tokenize(partials[key], openTag, closeTag);
  647. partials[key] = function() {}; // blank out the paritals so that infinite recursion doesn't happen
  648. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
  649. this.user_send_func = old_user_send_func;
  650. var that = this;
  651. partials[key] = function(contextStack, send_func) {
  652. var res = that.find_in_stack(key, contextStack);
  653. if (that.is_object(res)) {
  654. contextStack.push(res);
  655. }
  656. for (var i=0,n=commands.length; i<n; ++i) {
  657. commands[i](contextStack, send_func);
  658. }
  659. if (that.is_object(res)) {
  660. contextStack.pop();
  661. }
  662. };
  663. }
  664. this.user_send_func(function(contextStack, send_func) { partials[key](contextStack, send_func); });
  665. },
  666. section: function(sectionType, mustacheFragment, key, reserved/*contextStack*/, partials, openTag, closeTag) {
  667. // by @langalex, support for arrays of strings
  668. var that = this;
  669. function create_context(_context) {
  670. if(that.is_object(_context)) {
  671. return _context;
  672. } else {
  673. var iterator = '.';
  674. if(that.pragmas["IMPLICIT-ITERATOR"] &&
  675. that.pragmas["IMPLICIT-ITERATOR"].iterator) {
  676. iterator = that.pragmas["IMPLICIT-ITERATOR"].iterator;
  677. }
  678. var ctx = {};
  679. ctx[iterator] = _context;
  680. return ctx;
  681. }
  682. }
  683. var old_user_send_func = this.user_send_func;
  684. var commands = [];
  685. this.user_send_func = function(command) { commands.push(command); };
  686. var tokens = this.tokenize(mustacheFragment, openTag, closeTag);
  687. this.parse(this.createParserContext(tokens, partials, openTag, closeTag), reserved);
  688. this.user_send_func = old_user_send_func;
  689. var section_command = function(contextStack, send_func) {
  690. for (var i=0, n=commands.length; i<n; ++i) {
  691. commands[i](contextStack, send_func);
  692. }
  693. };
  694. var that = this;
  695. if (sectionType==='invertedSection') {
  696. this.user_send_func(function(contextStack, send_func) {
  697. var value = that.find_in_stack(key, contextStack);
  698. if (!value || that.is_array(value) && value.length === 0) {
  699. // false or empty list, render it
  700. section_command(contextStack, send_func);
  701. }
  702. });
  703. } else if (sectionType==='section') {
  704. this.user_send_func(function(contextStack, send_func) {
  705. var value = that.find_in_stack(key, contextStack);
  706. if (that.is_array(value)) { // Enumerable, Let's loop!
  707. for (var i=0, n=value.length; i<n; ++i) {
  708. contextStack.push(create_context(value[i]));
  709. section_command(contextStack, send_func);
  710. contextStack.pop();
  711. }
  712. } else if (that.is_object(value)) { // Object, Use it as subcontext!
  713. contextStack.push(value);
  714. section_command(contextStack, send_func);
  715. contextStack.pop();
  716. } else if (that.is_function(value)) {
  717. // higher order section
  718. // note that HOS triggers a compilation on the resultFragment.
  719. // this is slow (in relation to a fully compiled template)
  720. // since it invokes a call to the parser
  721. var result = value.call(contextStack[contextStack.length-1], mustacheFragment, function(resultFragment) {
  722. var cO = [];
  723. var s = function(command) { cO.push(command); };
  724. var hos_renderer = new Renderer(s, 'compiler');
  725. resultFragment = hos_renderer.parse_pragmas(resultFragment, openTag, closeTag);
  726. var tokens = hos_renderer.tokenize(resultFragment, openTag, closeTag);
  727. hos_renderer.parse(hos_renderer.createParserContext(tokens, partials, openTag, closeTag), contextStack);
  728. var o = [];
  729. var sT = function(output) { o.push(output); };
  730. for (var i=0,n=cO.length; i<n; ++i) {
  731. commands[i](contextStack, sT);
  732. }
  733. return o.join('');
  734. });
  735. send_func(result);
  736. } else if (value) {
  737. section_command(contextStack, send_func);
  738. }
  739. });
  740. } else {
  741. throw new ParserException('Unknown section type ' + sectionType);
  742. }
  743. }
  744. }
  745. }
  746. return({
  747. name: "mustache.js",
  748. version: "0.4.0-vcs",
  749. /*
  750. Turns a template and view into HTML
  751. */
  752. to_html: function(template, view, partials, send_func) {
  753. if (!template) { return ''; }
  754. partials = partials || {};
  755. view = view || {};
  756. var o = send_func ? undefined : [];
  757. var s = send_func || function(output) { o.push(output); };
  758. var renderer = new Renderer(s, 'interpreter');
  759. renderer.render(template, view, partials);
  760. if (!send_func) {
  761. return o.join('');
  762. }
  763. },
  764. /*
  765. Compiles a template into an equivalent JS function for faster
  766. repeated execution.
  767. */
  768. compile: function(template, partials) {
  769. if (!template) { return function() { return '' }; }
  770. var p = {};
  771. if (partials) {
  772. for (var key in partials) {
  773. if (partials.hasOwnProperty(key)) {
  774. p[key] = partials[key];
  775. }
  776. }
  777. }
  778. var commands = [];
  779. var s = function(command) { commands.push(command); };
  780. var renderer = new Renderer(s, 'compiler');
  781. renderer.render(template, {}, p);
  782. return function(view, send_func) {
  783. view = view || {};
  784. var o = send_func ? undefined : [];
  785. var s = send_func || function(output) { o.push(output); };
  786. for (var i=0,n=commands.length; i<n; ++i) {
  787. commands[i]([view], s);
  788. }
  789. if (!send_func) {
  790. return o.join('');
  791. }
  792. };
  793. }
  794. });
  795. }();