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.

653 wiersze
17KB

  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 broken String.split implementation
  8. /* Cross-Browser Split 1.0.1
  9. (c) Steven Levithan <stevenlevithan.com>; MIT License
  10. An ECMA-compliant, uniform cross-browser split method
  11. */
  12. var compliantExecNpcg = /()??/.exec("")[1] === undefined; // NPCG: nonparticipating capturing group
  13. function capturingSplit(separator) {
  14. var str = this;
  15. var limit = undefined;
  16. // if `separator` is not a regex, use the native `split`
  17. if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
  18. return String.prototype.split.call(str, separator, limit);
  19. }
  20. var output = [],
  21. lastLastIndex = 0,
  22. flags = (separator.ignoreCase ? "i" : "") +
  23. (separator.multiline ? "m" : "") +
  24. (separator.sticky ? "y" : ""),
  25. separator = RegExp(separator.source, flags + "g"), // make `global` and avoid `lastIndex` issues by working with a copy
  26. separator2, match, lastIndex, lastLength;
  27. str = str + ""; // type conversion
  28. if (!compliantExecNpcg) {
  29. separator2 = RegExp("^" + separator.source + "$(?!\\s)", flags); // doesn't need /g or /y, but they don't hurt
  30. }
  31. /* behavior for `limit`: if it's...
  32. - `undefined`: no limit.
  33. - `NaN` or zero: return an empty array.
  34. - a positive number: use `Math.floor(limit)`.
  35. - a negative number: no limit.
  36. - other: type-convert, then use the above rules. */
  37. if (limit === undefined || +limit < 0) {
  38. limit = Infinity;
  39. } else {
  40. limit = Math.floor(+limit);
  41. if (!limit) {
  42. return [];
  43. }
  44. }
  45. while (match = separator.exec(str)) {
  46. lastIndex = match.index + match[0].length; // `separator.lastIndex` is not reliable cross-browser
  47. if (lastIndex > lastLastIndex) {
  48. output.push(str.slice(lastLastIndex, match.index));
  49. // fix browsers whose `exec` methods don't consistently return `undefined` for nonparticipating capturing groups
  50. if (!compliantExecNpcg && match.length > 1) {
  51. match[0].replace(separator2, function () {
  52. for (var i = 1; i < arguments.length - 2; i++) {
  53. if (arguments[i] === undefined) {
  54. match[i] = undefined;
  55. }
  56. }
  57. });
  58. }
  59. if (match.length > 1 && match.index < str.length) {
  60. Array.prototype.push.apply(output, match.slice(1));
  61. }
  62. lastLength = match[0].length;
  63. lastLastIndex = lastIndex;
  64. if (output.length >= limit) {
  65. break;
  66. }
  67. }
  68. if (separator.lastIndex === match.index) {
  69. separator.lastIndex++; // avoid an infinite loop
  70. }
  71. }
  72. if (lastLastIndex === str.length) {
  73. if (lastLength || !separator.test("")) {
  74. output.push("");
  75. }
  76. } else {
  77. output.push(str.slice(lastLastIndex));
  78. }
  79. return output.length > limit ? output.slice(0, limit) : output;
  80. }
  81. if ('lol'.split(/(o)/).length !== 3) {
  82. return capturingSplit;
  83. } else {
  84. return String.prototype.split;
  85. }
  86. })();
  87. /* BEGIN Helpers */
  88. function noop() {}
  89. var escapeCompiledRegex;
  90. function escape_regex(text) {
  91. // thank you Simon Willison
  92. if (!escapeCompiledRegex) {
  93. var specials = [
  94. '/', '.', '*', '+', '?', '|',
  95. '(', ')', '[', ']', '{', '}', '\\'
  96. ];
  97. escapeCompiledRegex = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
  98. }
  99. return text.replace(escapeCompiledRegex, '\\$1');
  100. }
  101. function is_newline(token) {
  102. return token.match(/\r?\n/);
  103. }
  104. function is_function(a) {
  105. return a && typeof a === 'function';
  106. }
  107. function is_object(a) {
  108. return a && typeof a === 'object';
  109. }
  110. function is_array(a) {
  111. return Object.prototype.toString.call(a) === '[object Array]';
  112. }
  113. var MustacheError = function(message, metrics) {
  114. var str = '';
  115. this.prototype = Error.prototype;
  116. this.name = 'MustacheError';
  117. if (metrics) {
  118. str = '(' + metrics.line + ',' + metrics.character + '): ';
  119. if (metrics.partial) {
  120. str = '[' + metrics.partial + ']' + str;
  121. }
  122. }
  123. this.message = str + message;
  124. if (metrics) {
  125. this.line = metrics.line;
  126. this.character = metrics.character;
  127. this.partial = metrics.partial;
  128. }
  129. };
  130. /* END Helpers */
  131. /* BEGIN Compiler */
  132. function compile(state, noReturn) {
  133. var n, c, token;
  134. for (n = state.tokens.length;state.cursor<n && !state.terminated;++state.cursor) {
  135. token = state.tokens[state.cursor];
  136. if (token==='' || token===undefined) {
  137. continue;
  138. }
  139. if (token.indexOf(state.openTag)===0) {
  140. c = token.charAt(state.openTag.length);
  141. if (state.parser[c]) {
  142. state.parser[c](state, token, c);
  143. } else {
  144. state.parser.def(state, token);
  145. }
  146. } else {
  147. state.parser.text(state, token);
  148. }
  149. if (is_newline(token)) {
  150. state.metrics.character = 1;
  151. state.metrics.line++;
  152. } else {
  153. state.metrics.character+=token.length;
  154. }
  155. }
  156. if (state.parser === scan_section_parser && !state.terminated) {
  157. throw new MustacheError('Closing section tag "' + state.section.variable + '" expected.', state.metrics);
  158. }
  159. if (!noReturn) {
  160. var codeList = state.code;
  161. if (codeList.length === 0) {
  162. return noop;
  163. } else if (codeList.length === 1) {
  164. return codeList[0];
  165. } else {
  166. return function(context, send_func) {
  167. for (var i=0,n=codeList.length;i<n;++i) {
  168. codeList[i](context, send_func);
  169. }
  170. }
  171. }
  172. }
  173. }
  174. var default_tokenizer = /(\r?\n)|({{![\s\S]*?!}})|({{[#\^\/&>]?\s*[^!{=]\S*?\s*}})|({{{\s*\S*?\s*}}})|({{=\S*?\s*\S*?=}})/;
  175. function create_compiler_state(template, partials, openTag, closeTag) {
  176. openTag = openTag || '{{';
  177. closeTag = closeTag || '}}';
  178. var tokenizier;
  179. if (openTag === '{{' && closeTag === '}}') {
  180. tokenizer = default_tokenizer;
  181. } else {
  182. var rOTag = escape_regex(openTag),
  183. rETag = escape_regex(closeTag);
  184. var parts = [
  185. '(\\r?\\n)' // new lines
  186. , '(' + rOTag + '![\\s\\S]*?!' + rETag + ')' // comments
  187. , '(' + rOTag + '[#\^\/&>]?\\s*[^!{=]\\S*?\\s*' + rETag + ')' // all other tags
  188. , '(' + rOTag + '{\\s*\\S*?\\s*}' + rETag + ')' // { unescape token
  189. , '(' + rOTag + '=\\S*?\\s*\\S*?=' + rETag + ')' // set delimiter change
  190. ];
  191. tokenizer = new RegExp(parts.join('|'));
  192. }
  193. var code = [], state = {
  194. metrics: {
  195. partial: null
  196. , line: 1
  197. , character: 1
  198. }
  199. , template: template || ''
  200. , partials: partials || {}
  201. , openTag: openTag
  202. , closeTag: closeTag
  203. , parser: default_parser
  204. , pragmas: {}
  205. , code: code
  206. , send_code_func: function(f) {
  207. code.push(f);
  208. }
  209. };
  210. pragmas(state); // use pragmas to control parsing behaviour
  211. // tokenize and initialize a cursor
  212. state.tokens = splitFunc.call(state.template, tokenizer);
  213. state.cursor = 0;
  214. return state;
  215. }
  216. var pragma_directives = {
  217. 'IMPLICIT-ITERATOR': function(state, options) {
  218. state.pragmas['IMPLICIT-ITERATOR'] = {iterator: ((options || {iterator:undefined}).iterator) || '.'};
  219. }
  220. };
  221. function pragmas(state) {
  222. /* includes tag */
  223. function includes(needle, haystack) {
  224. return haystack.indexOf('{{' + needle) !== -1;
  225. }
  226. // no pragmas, easy escape
  227. if(!includes("%", state.template)) {
  228. return state.template;
  229. }
  230. state.template = state.template.replace(/{{%([\w-]+)(\s*)(.*?(?=}}))}}/, function(match, pragma, space, suffix) {
  231. var options = undefined,
  232. optionPairs, scratch,
  233. i, n;
  234. if (suffix.length>0) {
  235. optionPairs = suffix.split(',');
  236. options = {};
  237. for (i=0, n=optionPairs.length; i<n; ++i) {
  238. scratch = optionPairs[i].split('=');
  239. if (scratch.length !== 2) {
  240. throw new MustacheError('Malformed pragma option "' + optionPairs[i] + '".');
  241. }
  242. options[scratch[0]] = scratch[1];
  243. }
  244. }
  245. if (is_function(pragma_directives[pragma])) {
  246. pragma_directives[pragma](state, options);
  247. } else {
  248. throw new MustacheError('This implementation of mustache does not implement the "' + pragma + '" pragma.', undefined);
  249. }
  250. return ''; // blank out all pragmas
  251. });
  252. }
  253. /* END Compiler */
  254. /* BEGIN Run Time Helpers */
  255. /*
  256. find `name` in current `context`. That is find me a value
  257. from the view object
  258. */
  259. function find(name, context) {
  260. // Checks whether a value is truthy or false or 0
  261. function is_kinda_truthy(bool) {
  262. return bool === false || bool === 0 || bool;
  263. }
  264. var value;
  265. if (is_kinda_truthy(context[name])) {
  266. value = context[name];
  267. }
  268. if (is_function(value)) {
  269. return value.apply(context);
  270. }
  271. return value;
  272. }
  273. function find_in_stack(name, context_stack) {
  274. var value;
  275. value = find(name, context_stack[context_stack.length-1]);
  276. if (value!==undefined) { return value; }
  277. if (context_stack.length>1) {
  278. value = find(name, context_stack[0]);
  279. if (value!==undefined) { return value; }
  280. }
  281. return undefined;
  282. }
  283. /* END Run Time Helpers */
  284. function text(state, token) {
  285. state.send_code_func(function(context, send_func) { send_func(token); });
  286. }
  287. function interpolate(state, token, mark) {
  288. function escapeHTML(str) {
  289. return str.replace(/&/g,'&amp;')
  290. .replace(/</g,'&lt;')
  291. .replace(/>/g,'&gt;');
  292. }
  293. var escape, prefix, postfix;
  294. if (mark==='{') {
  295. escape = prefix = postfix = true;
  296. } else if (mark==='&') {
  297. escape = prefix = true;
  298. }
  299. var variable = get_variable_name(state, token, prefix, postfix);
  300. state.send_code_func((function(variable, escape) { return function(context, send_func) {
  301. var value = find_in_stack(variable, context);
  302. if (value!==undefined) {
  303. if (!escape) {
  304. value = escapeHTML('' + value);
  305. }
  306. send_func('' + value);
  307. }
  308. };})(variable, escape));
  309. }
  310. function partial(state, token) {
  311. var variable = get_variable_name(state, token, true),
  312. template, program;
  313. if (!state.partials[variable]) {
  314. throw new MustacheError('Unknown partial "' + variable + '".', state.metrics);
  315. }
  316. if (!is_function(state.partials[variable])) {
  317. // if the partial has not been compiled yet, do so now
  318. template = state.partials[variable]; // remember what the partial was
  319. state.partials[variable] = noop; // avoid infinite recursion
  320. var new_state = create_compiler_state(
  321. template
  322. , state.partials
  323. );
  324. new_state.metrics.partial = variable;
  325. // TODO: Determine if partials should inherit pragma state from parent
  326. program = compile(new_state);
  327. state.partials[variable] = function(context, send_func) {
  328. var value = find_in_stack(variable, context);
  329. if (value) {
  330. // TODO: According to mustache-spec, partials do not act as implicit sections
  331. // this behaviour was carried over from janl's mustache and should either
  332. // be discarded or replaced with a pragma
  333. context.push(value);
  334. }
  335. program(context, send_func);
  336. if (value) {
  337. // TODO: See above
  338. context.pop();
  339. }
  340. };
  341. }
  342. state.send_code_func(function(context, send_func) { state.partials[variable](context, send_func); });
  343. }
  344. function section(state) {
  345. // by @langalex, support for arrays of strings
  346. function create_context(_context) {
  347. if(is_object(_context)) {
  348. return _context;
  349. } else {
  350. var ctx = {},
  351. iterator = (state.pragmas['IMPLICIT-ITERATOR'] || {iterator: '.'}).iterator;
  352. ctx[iterator] = _context;
  353. return ctx;
  354. }
  355. }
  356. var s = state.section, template = s.template_buffer.join(''),
  357. program,
  358. new_state = create_compiler_state(template, state.partials, state.openTag, state.closeTag);
  359. new_state.metrics = s.metrics;
  360. program = compile(new_state);
  361. if (s.inverted) {
  362. state.send_code_func((function(program, variable){ return function(context, send_func) {
  363. var value = find_in_stack(variable, context);
  364. if (!value || is_array(value) && value.length === 0) { // false or empty list, render it
  365. program(context, send_func);
  366. }
  367. };})(program, s.variable));
  368. } else {
  369. state.send_code_func((function(program, variable, template, partials){ return function(context, send_func) {
  370. var value = find_in_stack(variable, context);
  371. if (is_array(value)) { // Enumerable, Let's loop!
  372. for (var i=0, n=value.length; i<n; ++i) {
  373. context.push(create_context(value[i]));
  374. program(context, send_func);
  375. context.pop();
  376. }
  377. } else if (is_object(value)) { // Object, Use it as subcontext!
  378. context.push(value);
  379. program(context, send_func);
  380. context.pop();
  381. } else if (is_function(value)) { // higher order section
  382. // note that HOS triggers a compilation on the hosFragment.
  383. // this is slow (in relation to a fully compiled template)
  384. // since it invokes a call to the parser
  385. send_func(value.call(context[context.length-1], template, function(hosFragment) {
  386. var o = [],
  387. user_send_func = function(str) { o.push(str); };
  388. var new_state = create_compiler_state(hosFragment, partials);
  389. new_state.metrics.partial = 'HOS@@anon';
  390. compile(new_state)(context, user_send_func);
  391. return o.join('');
  392. }));
  393. } else if (value) { // truthy
  394. program(context, send_func);
  395. }
  396. };})(program, s.variable, template, state.partials));
  397. }
  398. }
  399. /* BEGIN Parser */
  400. var default_parser = {
  401. '!': noop,
  402. '#': begin_section,
  403. '^': begin_section,
  404. '/': function(state, token) { throw new MustacheError('Unbalanced End Section tag "' + token + '".', state.metrics); },
  405. '&': interpolate,
  406. '{': interpolate,
  407. '>': partial,
  408. '=': change_delimiter,
  409. def: interpolate,
  410. text: text
  411. };
  412. var scan_section_parser = {
  413. '!': noop,
  414. '#': begin_section,
  415. '^': begin_section,
  416. '/': end_section,
  417. '&': buffer_section,
  418. '{': buffer_section,
  419. '>': buffer_section,
  420. '=': change_delimiter,
  421. def: buffer_section,
  422. text: buffer_section
  423. };
  424. function get_variable_name(state, token, prefix, postfix) {
  425. var fragment = token.substring(
  426. state.openTag.length + (prefix ? 1 : 0)
  427. , token.length - state.closeTag.length - (postfix ? 1 : 0)
  428. );
  429. if (String.prototype.trim) {
  430. fragment = fragment.trim();
  431. } else {
  432. fragment = fragment.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
  433. }
  434. if (fragment.indexOf(' ')!==-1) {
  435. throw new MustacheError('Malformed variable name "' + fragment + '".', state.metrics);
  436. }
  437. return fragment;
  438. }
  439. function change_delimiter(state, token) {
  440. var matches = token.match(new RegExp(escape_regex(state.openTag) + '=(\\S*?)\\s*(\\S*?)=' + escape_regex(state.closeTag)));
  441. if ((matches || []).length!==3) {
  442. throw new MustacheError('Malformed change delimiter token "' + token + '".', state.metrics);
  443. }
  444. var new_state = create_compiler_state(
  445. state.tokens.slice(state.cursor+1).join('')
  446. , state.partials
  447. , matches[1]
  448. , matches[2]);
  449. new_state.code = state.code;
  450. new_state.send_code_func = state.send_code_func;
  451. new_state.parser = state.parser;
  452. new_state.metrics.line = state.metrics.line;
  453. new_state.metrics.character = state.metrics.character + token.length;
  454. new_state.metrics.partial = state.metrics.partial;
  455. new_state.section = state.section;
  456. new_state.pragmas = state.pragmas;
  457. if (new_state.section) {
  458. new_state.section.template_buffer.push(token);
  459. }
  460. state.terminated = true; // finish off this level
  461. compile(new_state, true);
  462. }
  463. function begin_section(state, token, mark) {
  464. var inverted = mark === '^',
  465. variable = get_variable_name(state, token, true);
  466. if (state.parser===default_parser) {
  467. state.parser = scan_section_parser;
  468. state.section = {
  469. variable: variable
  470. , template_buffer: []
  471. , inverted: inverted
  472. , child_sections: []
  473. , metrics: {
  474. partial: state.metrics.partial
  475. , line: state.metrics.line
  476. , character: state.metrics.character + token.length
  477. }
  478. };
  479. } else {
  480. state.section.child_sections.push(variable);
  481. state.section.template_buffer.push(token);
  482. }
  483. }
  484. function buffer_section(state, token) {
  485. state.section.template_buffer.push(token);
  486. }
  487. function end_section(state, token) {
  488. var variable = get_variable_name(state, token, true);
  489. if (state.section.child_sections.length > 0) {
  490. var child_section = state.section.child_sections[state.section.child_sections.length-1];
  491. if (child_section === variable) {
  492. state.section.child_sections.pop();
  493. state.section.template_buffer.push(token);
  494. } else {
  495. throw new MustacheError('Unexpected section end tag "' + variable + '", expected "' + child_section + '".', state.metrics);
  496. }
  497. } else if (state.section.variable===variable) {
  498. section(state);
  499. delete state.section;
  500. state.parser = default_parser;
  501. } else {
  502. throw new MustacheError('Unexpected section end tag "' + variable + '", expected "' + state.section.variable + '".', state.metrics);
  503. }
  504. }
  505. /* END Parser */
  506. return({
  507. name: "mustache.js",
  508. version: "0.5.0-vcs",
  509. /*
  510. Turns a template and view into HTML
  511. */
  512. to_html: function(template, view, partials, send_func) {
  513. var program = Mustache.compile(template, partials),
  514. result = program(view, send_func);
  515. if (!send_func) {
  516. return result;
  517. }
  518. },
  519. compile: function(template, partials) {
  520. var p = {};
  521. if (partials) {
  522. for (var key in partials) {
  523. if (partials.hasOwnProperty(key)) {
  524. p[key] = partials[key];
  525. }
  526. }
  527. }
  528. var program = compile(create_compiler_state(template, p));
  529. return function(view, send_func) {
  530. var o = [],
  531. user_send_func = send_func || function(str) {
  532. o.push(str);
  533. };
  534. program([view || {}], user_send_func);
  535. if (!send_func) {
  536. return o.join('');
  537. }
  538. }
  539. },
  540. Error: MustacheError
  541. });
  542. })();