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

554 строки
15KB

  1. /*
  2. mustache.js — Logic-less templates in JavaScript
  3. See http://mustache.github.com/ for more info.
  4. */
  5. var Mustache = (function(undefined) {
  6. var splitFunc = (function() {
  7. // Fix up the stupidness that is IE's split implementation
  8. var compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
  9. function capturingSplit(separator) {
  10. // fix up the stupidness that is IE's broken String.split implementation
  11. // originally by Steven Levithan
  12. /* Cross-Browser Split 1.0.1
  13. (c) Steven Levithan <stevenlevithan.com>; MIT License
  14. An ECMA-compliant, uniform cross-browser split method */
  15. var str = this;
  16. var limit = undefined;
  17. // if `separator` is not a regex, use the native `split`
  18. if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
  19. return String.prototype.split.call(str, separator, limit);
  20. }
  21. var output = [],
  22. lastLastIndex = 0,
  23. flags = (separator.ignoreCase ? "i" : "") +
  24. (separator.multiline ? "m" : "") +
  25. (separator.sticky ? "y" : ""),
  26. separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
  27. separator2, match, lastIndex, lastLength;
  28. str = str + ""; // type conversion
  29. if (!compliantExecNpcg) {
  30. separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
  31. }
  32. /* behavior for `limit`: if it's...
  33. - `undefined`: no limit.
  34. - `NaN` or zero: return an empty array.
  35. - a positive number: use `Math.floor(limit)`.
  36. - a negative number: no limit.
  37. - other: type-convert, then use the above rules. */
  38. if (limit === undefined || +limit < 0) {
  39. limit = Infinity;
  40. } else {
  41. limit = Math.floor(+limit);
  42. if (!limit) {
  43. return [];
  44. }
  45. }
  46. while (match = separator.exec(str)) {
  47. lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
  48. if (lastIndex > lastLastIndex) {
  49. output.push(str.slice(lastLastIndex, match.index));
  50. // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
  51. if (!compliantExecNpcg && match.length > 1) {
  52. match[0].replace(separator2, function () {
  53. for (var i = 1; i < arguments.length - 2; i++) {
  54. if (arguments[i] === undefined) {
  55. match[i] = undefined;
  56. }
  57. }
  58. });
  59. }
  60. if (match.length > 1 && match.index < str.length) {
  61. Array.prototype.push.apply(output, match.slice(1));
  62. }
  63. lastLength = match[0].length;
  64. lastLastIndex = lastIndex;
  65. if (output.length >= limit) {
  66. break;
  67. }
  68. }
  69. if (separator.lastIndex === match.index) {
  70. separator.lastIndex++; // avoid an infinite loop
  71. }
  72. }
  73. if (lastLastIndex === str.length) {
  74. if (lastLength || !separator.test("")) {
  75. output.push("");
  76. }
  77. } else {
  78. output.push(str.slice(lastLastIndex));
  79. }
  80. return output.length > limit ? output.slice(0, limit) : output;
  81. }
  82. if ('lol'.split(/(o)/).length !== 3) {
  83. return capturingSplit;
  84. } else {
  85. return String.prototype.split;
  86. }
  87. })();
  88. var escapeCompiledRegex;
  89. function escape_regex(text) {
  90. // thank you Simon Willison
  91. if (!escapeCompiledRegex) {
  92. var specials = [
  93. '/', '.', '*', '+', '?', '|',
  94. '(', ')', '[', ']', '{', '}', '\\'
  95. ];
  96. escapeCompiledRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
  97. }
  98. return text.replace(escapeCompiledRegex, '\\$1');
  99. }
  100. function isWhitespace(token) {
  101. return token.match(/^\s+$/)!==null;
  102. }
  103. function isNewline(token) {
  104. return token.match(/\r?\n/)!==null;
  105. }
  106. function create_parser_context(template, partials, view, send_func, openTag, closeTag) {
  107. openTag = openTag || '{{';
  108. closeTag = closeTag || '}}';
  109. var rOTag = escape_regex(openTag),
  110. rETag = escape_regex(closeTag);
  111. var tokenizer = new RegExp('(\\r?\\n)|(' + rOTag + '![\\s\\S]*?' + rETag + ')|(' + rOTag + '[#\^\/&{>=]?\\s*\\S*?\\s*}?' + rETag + ')|(' + rOTag + '=\\S*\\s*\\S*=' + rETag + ')');
  112. var context = {
  113. template: template || ''
  114. , partials: partials || {}
  115. , contextStack: [view || {}]
  116. , user_send_func: send_func
  117. , openTag: openTag
  118. , closeTag: closeTag
  119. , state: 'normal'
  120. , pragmas: {}
  121. };
  122. // prefilter pragmas
  123. pragmas(context);
  124. // tokenize and initialize a cursor
  125. context.tokens = splitFunc.call(context.template, tokenizer);
  126. context.cursor = 0;
  127. return context;
  128. }
  129. function is_function(a) {
  130. return a && typeof a === 'function';
  131. }
  132. function is_object(a) {
  133. return a && typeof a === 'object';
  134. }
  135. function is_array(a) {
  136. return Object.prototype.toString.call(a) === '[object Array]';
  137. }
  138. /*
  139. find `name` in current `context`. That is find me a value
  140. from the view object
  141. */
  142. function find(name, context) {
  143. // Checks whether a value is truthy or false or 0
  144. function is_kinda_truthy(bool) {
  145. return bool === false || bool === 0 || bool;
  146. }
  147. var value;
  148. if (is_kinda_truthy(context[name])) {
  149. value = context[name];
  150. }
  151. if (is_function(value)) {
  152. return value.apply(context);
  153. }
  154. return value;
  155. }
  156. function find_in_stack(name, contextStack) {
  157. var value;
  158. value = find(name, contextStack[contextStack.length-1]);
  159. if (value!==undefined) { return value; }
  160. if (contextStack.length>1) {
  161. value = find(name, contextStack[0]);
  162. if (value!==undefined) { return value; }
  163. }
  164. return undefined;
  165. }
  166. function get_variable_name(parserContext, token, prefixes, postfixes) {
  167. var matches = token.match(new RegExp(escape_regex(parserContext.openTag) +
  168. '[' + escape_regex((prefixes || []).join('')) +
  169. ']?\\s*(\\S*?)\\s*[' +
  170. escape_regex((postfixes || []).join('')) +
  171. ']?' +
  172. escape_regex(parserContext.closeTag)));
  173. if ((matches || []).length!==2) {
  174. throw new Error('Malformed mustache tag: ' + token);
  175. } else {
  176. return matches[1];
  177. }
  178. }
  179. function interpolate(parserContext, token, escape) {
  180. function escapeHTML(str) {
  181. return str.replace(/&/g,'&amp;')
  182. .replace(/</g,'&lt;')
  183. .replace(/>/g,'&gt;');
  184. }
  185. var prefix = [], postfix = [];
  186. if (escape==='{') {
  187. prefix = ['{'];
  188. postfix = ['}'];
  189. } else if (escape==='&') {
  190. prefix = ['&'];
  191. }
  192. var res = find_in_stack(get_variable_name(parserContext, token, prefix, postfix), parserContext.contextStack);
  193. if (res!==undefined) {
  194. if (!escape) {
  195. res = escapeHTML('' + res);
  196. }
  197. parserContext.user_send_func('' + res);
  198. }
  199. }
  200. function partial(parserContext, token) {
  201. var variable = get_variable_name(parserContext, token, ['>']);
  202. var value = find_in_stack(variable, parserContext.contextStack);
  203. if (!parserContext.partials[variable]) {
  204. throw new Error('Unknown partial \'' + variable + '\'');
  205. }
  206. var new_parser_context = create_parser_context(
  207. parserContext.partials[variable]
  208. , parserContext.partials
  209. , null
  210. , parserContext.user_send_func);
  211. new_parser_context.contextStack = parserContext.contextStack;
  212. if (value) {
  213. // TODO: According to mustache-spec, partials do not act as implicit sections
  214. // this behaviour was carried over from janl's mustache and should either
  215. // be discarded or replaced with a pragma
  216. new_parser_context.contextStack.push(value);
  217. }
  218. parse(new_parser_context);
  219. if (value) {
  220. // TODO: See above
  221. new_parser_context.contextStack.pop();
  222. }
  223. }
  224. function section(parserContext) {
  225. function create_section_context(template) {
  226. var context = create_parser_context(template,
  227. parserContext.partials,
  228. null,
  229. parserContext.user_send_func,
  230. parserContext.openTag,
  231. parserContext.closeTag);
  232. context.contextStack = parserContext.contextStack;
  233. return context;
  234. }
  235. // by @langalex, support for arrays of strings
  236. function create_context(_context) {
  237. if(is_object(_context)) {
  238. return _context;
  239. } else {
  240. var ctx = {},
  241. iterator = (parserContext.pragmas['IMPLICIT-ITERATOR'] || {iterator: '.'}).iterator;
  242. ctx[iterator] = _context;
  243. return ctx;
  244. }
  245. }
  246. var s = parserContext.section;
  247. var value = find_in_stack(s.variable, parserContext.contextStack);
  248. var i, n;
  249. var new_parser_context;
  250. if (s.inverted) {
  251. if (!value || is_array(value) && value.length === 0) { // false or empty list, render it
  252. new_parser_context = create_section_context(s.template_buffer.join(''));
  253. parse(new_parser_context);
  254. }
  255. } else {
  256. if (is_array(value)) { // Enumerable, Let's loop!
  257. new_parser_context = create_section_context(s.template_buffer.join(''));
  258. for (i=0, n=value.length; i<n; ++i) {
  259. new_parser_context.cursor = 0;
  260. new_parser_context.contextStack.push(create_context(value[i]));
  261. parse(new_parser_context);
  262. new_parser_context.contextStack.pop();
  263. }
  264. } else if (is_object(value)) { // Object, Use it as subcontext!
  265. new_parser_context = create_section_context(s.template_buffer.join(''));
  266. new_parser_context.contextStack.push(value);
  267. parse(new_parser_context);
  268. new_parser_context.contextStack.pop();
  269. } else if (is_function(value)) { // higher order section
  270. parserContext.user_send_func(value.call(parserContext.contextStack[parserContext.contextStack.length-1], s.template_buffer.join(''), function(templateFragment) {
  271. var o = [];
  272. new_parser_context = create_section_context(templateFragment);
  273. new_parser_context.user_send_func = function(r) {
  274. o.push(r);
  275. }
  276. parse(new_parser_context);
  277. return o.join('');
  278. }));
  279. } else if (value) { // truthy
  280. new_parser_context = create_section_context(s.template_buffer.join(''));
  281. parse(new_parser_context);
  282. }
  283. }
  284. }
  285. function pragmas(parserContext) {
  286. /* includes tag */
  287. function includes(needle, haystack) {
  288. return haystack.indexOf('{{' + needle) !== -1;
  289. }
  290. var directives = {
  291. 'IMPLICIT-ITERATOR': function(options) {
  292. parserContext.pragmas['IMPLICIT-ITERATOR'] = {iterator: '.'};
  293. if (options) {
  294. parserContext.pragmas['IMPLICIT-ITERATOR'].iterator = options['iterator'];
  295. }
  296. }
  297. };
  298. // no pragmas, easy escape
  299. if(!includes("%", parserContext.template)) {
  300. return parserContext.template;
  301. }
  302. parserContext.template = parserContext.template.replace(/{{%([\w-]+)(\s*)(.*?(?=}}))}}/, function(match, pragma, space, suffix) {
  303. console.log(match, suffix);
  304. var options = undefined;
  305. if (suffix.length>0) {
  306. var optionPairs = suffix.split(',');
  307. var scratch;
  308. options = {};
  309. for (var i=0, n=optionPairs.length; i<n; ++i) {
  310. scratch = optionPairs[i].split('=');
  311. if (scratch.length !== 2) {
  312. throw new Error('Malformed pragma options:' + optionPairs[i]);
  313. }
  314. options[scratch[0]] = scratch[1];
  315. }
  316. }
  317. if (is_function(directives[pragma])) {
  318. directives[pragma](options);
  319. } else {
  320. throw new Error('This implementation of mustache does not implement the "' + pragma + '" pragma');
  321. }
  322. return ''; // blank out all pragmas
  323. });
  324. }
  325. function change_delimiter(parserContext, token) {
  326. var matches = token.match(new RegExp(escape_regex(parserContext.openTag) + '=(\\S*?)\\s*(\\S*?)=' + escape_regex(parserContext.closeTag)));
  327. if ((matches || []).length!==3) {
  328. throw new Error('Malformed change delimiter token: ' + token);
  329. }
  330. var context = create_parser_context(
  331. parserContext.tokens.slice(parserContext.cursor+1).join('')
  332. , parserContext.partials
  333. , null
  334. , parserContext.user_send_func
  335. , matches[1]
  336. , matches[2]);
  337. context.contextStack = parserContext.contextStack;
  338. parserContext.cursor = parserContext.tokens.length; // finish off this level
  339. parse(context);
  340. }
  341. function begin_section(parserContext, token, inverted) {
  342. var variable = get_variable_name(parserContext, token, ['#', '^']);
  343. if (parserContext.state==='normal') {
  344. parserContext.state = 'scan_section';
  345. parserContext.section = {
  346. variable: variable
  347. , template_buffer: []
  348. , inverted: inverted
  349. , child_sections: []
  350. };
  351. } else {
  352. parserContext.section.child_sections.push(variable);
  353. parserContext.section.template_buffer.push(token);
  354. }
  355. }
  356. function end_section(parserContext, token) {
  357. var variable = get_variable_name(parserContext, token, ['/']);
  358. if (parserContext.section.child_sections.length > 0 &&
  359. parserContext.section.child_sections[parserContext.section.child_sections.length-1] === variable) {
  360. parserContext.section.child_sections.pop();
  361. parserContext.section.template_buffer.push(token);
  362. } else if (parserContext.section.variable===variable) {
  363. section(parserContext);
  364. delete parserContext.section;
  365. parserContext.state = 'normal';
  366. } else {
  367. throw new Error('Unexpected section end tag. Expected: ' + parserContext.section.variable);
  368. }
  369. }
  370. function parse(parserContext) {
  371. var n, token;
  372. for (n = parserContext.tokens.length;parserContext.cursor<n;++parserContext.cursor) {
  373. token = parserContext.tokens[parserContext.cursor];
  374. if (token==='') {
  375. continue;
  376. }
  377. stateMachine[parserContext.state](parserContext, token);
  378. }
  379. }
  380. var stateMachine = {
  381. 'normal': function(parserContext, token) {
  382. if (token.indexOf(parserContext.openTag)===0) {
  383. // the token has the makings of a Mustache tag
  384. // perform the appropriate action based on the state machine
  385. switch (token.charAt(parserContext.openTag.length)) {
  386. case '!': // comment
  387. // comments are just discarded, nothing to do
  388. break;
  389. case '#': // section
  390. begin_section(parserContext, token, false);
  391. break;
  392. case '^': // inverted section
  393. begin_section(parserContext, token, true);
  394. break;
  395. case '/': // end section
  396. // in normal flow, this operation is absolutely meaningless
  397. throw new Error('Unbalanced End Section tag: ' + token);
  398. break;
  399. case '&': // unescaped variable
  400. case '{': // unescaped variable
  401. interpolate(parserContext, token, token.charAt(parserContext.openTag.length));
  402. break;
  403. case '>': // partial
  404. partial(parserContext, token);
  405. break;
  406. case '=': // set delimiter change
  407. change_delimiter(parserContext, token);
  408. break;
  409. default: // escaped variable
  410. interpolate(parserContext, token);
  411. break;
  412. }
  413. } else {
  414. // plain jane text
  415. parserContext.user_send_func(token);
  416. }
  417. }
  418. , 'scan_section': function(parserContext, token) {
  419. if (token.indexOf(parserContext.openTag)===0) {
  420. switch (token.charAt(parserContext.openTag.length)) {
  421. case '!': // comments
  422. // comments are just discarded, nothing to do
  423. break;
  424. case '#': // section
  425. begin_section(parserContext, token, false);
  426. break;
  427. case '^': // inverted section
  428. begin_section(parserContext, token, true);
  429. break;
  430. case '/': // end section
  431. end_section(parserContext, token);
  432. break;
  433. case '=': // set delimiter change
  434. change_delimiter(parserContext, token);
  435. break;
  436. default: // all others
  437. parserContext.section.template_buffer.push(token);
  438. break;
  439. }
  440. } else {
  441. parserContext.section.template_buffer.push(token);
  442. }
  443. }
  444. }
  445. return({
  446. name: "mustache.js",
  447. version: "0.5.0-vcs",
  448. /*
  449. Turns a template and view into HTML
  450. */
  451. to_html: function(template, view, partials, send_func) {
  452. var o = [];
  453. var user_send_func = send_func || function(str) {
  454. o.push(str);
  455. };
  456. parse(create_parser_context(template, partials, view, user_send_func));
  457. if (!send_func) {
  458. return o.join('');
  459. }
  460. },
  461. });
  462. })();