Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

dojo.mustache.js 14KB

vor 14 Jahren
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. /*
  2. Shameless port of a shameless port
  3. @defunkt => @janl => @aq => @voodootikigod
  4. See http://github.com/defunkt/mustache for more info.
  5. */
  6. dojo.provide("dojox.mustache._base");
  7. (function(){
  8. /*!
  9. * mustache.js - Logic-less {{mustache}} templates with JavaScript
  10. * http://github.com/janl/mustache.js
  11. */
  12. var Mustache = (typeof module !== "undefined" && module.exports) || {};
  13. (function (exports) {
  14. exports.name = "mustache.js";
  15. exports.version = "0.5.14";
  16. exports.tags = ["{{", "}}"];
  17. exports.parse = parse;
  18. exports.compile = compile;
  19. exports.render = render;
  20. exports.clearCache = clearCache;
  21. // This is here for backwards compatibility with 0.4.x.
  22. exports.to_html = function (template, view, partials, send) {
  23. var result = render(template, view, partials);
  24. if (typeof send === "function") {
  25. send(result);
  26. } else {
  27. return result;
  28. }
  29. };
  30. var _toString = Object.prototype.toString;
  31. var _isArray = Array.isArray;
  32. var _forEach = Array.prototype.forEach;
  33. var _trim = String.prototype.trim;
  34. var isArray;
  35. if (_isArray) {
  36. isArray = _isArray;
  37. } else {
  38. isArray = function (obj) {
  39. return _toString.call(obj) === "[object Array]";
  40. };
  41. }
  42. var forEach;
  43. if (_forEach) {
  44. forEach = function (obj, callback, scope) {
  45. return _forEach.call(obj, callback, scope);
  46. };
  47. } else {
  48. forEach = function (obj, callback, scope) {
  49. for (var i = 0, len = obj.length; i < len; ++i) {
  50. callback.call(scope, obj[i], i, obj);
  51. }
  52. };
  53. }
  54. var spaceRe = /^\s*$/;
  55. function isWhitespace(string) {
  56. return spaceRe.test(string);
  57. }
  58. var trim;
  59. if (_trim) {
  60. trim = function (string) {
  61. return string == null ? "" : _trim.call(string);
  62. };
  63. } else {
  64. var trimLeft, trimRight;
  65. if (isWhitespace("\xA0")) {
  66. trimLeft = /^\s+/;
  67. trimRight = /\s+$/;
  68. } else {
  69. // IE doesn't match non-breaking spaces with \s, thanks jQuery.
  70. trimLeft = /^[\s\xA0]+/;
  71. trimRight = /[\s\xA0]+$/;
  72. }
  73. trim = function (string) {
  74. return string == null ? "" :
  75. String(string).replace(trimLeft, "").replace(trimRight, "");
  76. };
  77. }
  78. var escapeMap = {
  79. "&": "&amp;",
  80. "<": "&lt;",
  81. ">": "&gt;",
  82. '"': '&quot;',
  83. "'": '&#39;'
  84. };
  85. function escapeHTML(string) {
  86. return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) {
  87. return escapeMap[s] || s;
  88. });
  89. }
  90. /**
  91. * Adds the `template`, `line`, and `file` properties to the given error
  92. * object and alters the message to provide more useful debugging information.
  93. */
  94. function debug(e, template, line, file) {
  95. file = file || "<template>";
  96. var lines = template.split("\n"),
  97. start = Math.max(line - 3, 0),
  98. end = Math.min(lines.length, line + 3),
  99. context = lines.slice(start, end);
  100. var c;
  101. for (var i = 0, len = context.length; i < len; ++i) {
  102. c = i + start + 1;
  103. context[i] = (c === line ? " >> " : " ") + context[i];
  104. }
  105. e.template = template;
  106. e.line = line;
  107. e.file = file;
  108. e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n");
  109. return e;
  110. }
  111. /**
  112. * Looks up the value of the given `name` in the given context `stack`.
  113. */
  114. function lookup(name, stack, defaultValue) {
  115. if (name === ".") {
  116. return stack[stack.length - 1];
  117. }
  118. var names = name.split(".");
  119. var lastIndex = names.length - 1;
  120. var target = names[lastIndex];
  121. var value, context, i = stack.length, j, localStack;
  122. while (i) {
  123. localStack = stack.slice(0);
  124. context = stack[--i];
  125. j = 0;
  126. while (j < lastIndex) {
  127. context = context[names[j++]];
  128. if (context == null) {
  129. break;
  130. }
  131. localStack.push(context);
  132. }
  133. if (context && typeof context === "object" && target in context) {
  134. value = context[target];
  135. break;
  136. }
  137. }
  138. // If the value is a function, call it in the current context.
  139. if (typeof value === "function") {
  140. value = value.call(localStack[localStack.length - 1]);
  141. }
  142. if (value == null) {
  143. return defaultValue;
  144. }
  145. return value;
  146. }
  147. function renderSection(name, stack, callback, inverted) {
  148. var buffer = "";
  149. var value = lookup(name, stack);
  150. if (inverted) {
  151. // From the spec: inverted sections may render text once based on the
  152. // inverse value of the key. That is, they will be rendered if the key
  153. // doesn't exist, is false, or is an empty list.
  154. if (value == null || value === false || (isArray(value) && value.length === 0)) {
  155. buffer += callback();
  156. }
  157. } else if (isArray(value)) {
  158. forEach(value, function (value) {
  159. stack.push(value);
  160. buffer += callback();
  161. stack.pop();
  162. });
  163. } else if (typeof value === "object") {
  164. stack.push(value);
  165. buffer += callback();
  166. stack.pop();
  167. } else if (typeof value === "function") {
  168. var scope = stack[stack.length - 1];
  169. var scopedRender = function (template) {
  170. return render(template, scope);
  171. };
  172. buffer += value.call(scope, callback(), scopedRender) || "";
  173. } else if (value) {
  174. buffer += callback();
  175. }
  176. return buffer;
  177. }
  178. /**
  179. * Parses the given `template` and returns the source of a function that,
  180. * with the proper arguments, will render the template. Recognized options
  181. * include the following:
  182. *
  183. * - file The name of the file the template comes from (displayed in
  184. * error messages)
  185. * - tags An array of open and close tags the `template` uses. Defaults
  186. * to the value of Mustache.tags
  187. * - debug Set `true` to log the body of the generated function to the
  188. * console
  189. * - space Set `true` to preserve whitespace from lines that otherwise
  190. * contain only a {{tag}}. Defaults to `false`
  191. */
  192. function parse(template, options) {
  193. options = options || {};
  194. var tags = options.tags || exports.tags,
  195. openTag = tags[0],
  196. closeTag = tags[tags.length - 1];
  197. var code = [
  198. 'var buffer = "";', // output buffer
  199. "\nvar line = 1;", // keep track of source line number
  200. "\ntry {",
  201. '\nbuffer += "'
  202. ];
  203. var spaces = [], // indices of whitespace in code on the current line
  204. hasTag = false, // is there a {{tag}} on the current line?
  205. nonSpace = false; // is there a non-space char on the current line?
  206. // Strips all space characters from the code array for the current line
  207. // if there was a {{tag}} on it and otherwise only spaces.
  208. var stripSpace = function () {
  209. if (hasTag && !nonSpace && !options.space) {
  210. while (spaces.length) {
  211. code.splice(spaces.pop(), 1);
  212. }
  213. } else {
  214. spaces = [];
  215. }
  216. hasTag = false;
  217. nonSpace = false;
  218. };
  219. var sectionStack = [], updateLine, nextOpenTag, nextCloseTag;
  220. var setTags = function (source) {
  221. tags = trim(source).split(/\s+/);
  222. nextOpenTag = tags[0];
  223. nextCloseTag = tags[tags.length - 1];
  224. };
  225. var includePartial = function (source) {
  226. code.push(
  227. '";',
  228. updateLine,
  229. '\nvar partial = partials["' + trim(source) + '"];',
  230. '\nif (partial) {',
  231. '\n buffer += render(partial,stack[stack.length - 1],partials);',
  232. '\n}',
  233. '\nbuffer += "'
  234. );
  235. };
  236. var openSection = function (source, inverted) {
  237. var name = trim(source);
  238. if (name === "") {
  239. throw debug(new Error("Section name may not be empty"), template, line, options.file);
  240. }
  241. sectionStack.push({name: name, inverted: inverted});
  242. code.push(
  243. '";',
  244. updateLine,
  245. '\nvar name = "' + name + '";',
  246. '\nvar callback = (function () {',
  247. '\n return function () {',
  248. '\n var buffer = "";',
  249. '\nbuffer += "'
  250. );
  251. };
  252. var openInvertedSection = function (source) {
  253. openSection(source, true);
  254. };
  255. var closeSection = function (source) {
  256. var name = trim(source);
  257. var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name;
  258. if (!openName || name != openName) {
  259. throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file);
  260. }
  261. var section = sectionStack.pop();
  262. code.push(
  263. '";',
  264. '\n return buffer;',
  265. '\n };',
  266. '\n})();'
  267. );
  268. if (section.inverted) {
  269. code.push("\nbuffer += renderSection(name,stack,callback,true);");
  270. } else {
  271. code.push("\nbuffer += renderSection(name,stack,callback);");
  272. }
  273. code.push('\nbuffer += "');
  274. };
  275. var sendPlain = function (source) {
  276. code.push(
  277. '";',
  278. updateLine,
  279. '\nbuffer += lookup("' + trim(source) + '",stack,"");',
  280. '\nbuffer += "'
  281. );
  282. };
  283. var sendEscaped = function (source) {
  284. code.push(
  285. '";',
  286. updateLine,
  287. '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));',
  288. '\nbuffer += "'
  289. );
  290. };
  291. var line = 1, c, callback;
  292. for (var i = 0, len = template.length; i < len; ++i) {
  293. if (template.slice(i, i + openTag.length) === openTag) {
  294. i += openTag.length;
  295. c = template.substr(i, 1);
  296. updateLine = '\nline = ' + line + ';';
  297. nextOpenTag = openTag;
  298. nextCloseTag = closeTag;
  299. hasTag = true;
  300. switch (c) {
  301. case "!": // comment
  302. i++;
  303. callback = null;
  304. break;
  305. case "=": // change open/close tags, e.g. {{=<% %>=}}
  306. i++;
  307. closeTag = "=" + closeTag;
  308. callback = setTags;
  309. break;
  310. case ">": // include partial
  311. i++;
  312. callback = includePartial;
  313. break;
  314. case "#": // start section
  315. i++;
  316. callback = openSection;
  317. break;
  318. case "^": // start inverted section
  319. i++;
  320. callback = openInvertedSection;
  321. break;
  322. case "/": // end section
  323. i++;
  324. callback = closeSection;
  325. break;
  326. case "{": // plain variable
  327. closeTag = "}" + closeTag;
  328. // fall through
  329. case "&": // plain variable
  330. i++;
  331. nonSpace = true;
  332. callback = sendPlain;
  333. break;
  334. default: // escaped variable
  335. nonSpace = true;
  336. callback = sendEscaped;
  337. }
  338. var end = template.indexOf(closeTag, i);
  339. if (end === -1) {
  340. throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file);
  341. }
  342. var source = template.substring(i, end);
  343. if (callback) {
  344. callback(source);
  345. }
  346. // Maintain line count for \n in source.
  347. var n = 0;
  348. while (~(n = source.indexOf("\n", n))) {
  349. line++;
  350. n++;
  351. }
  352. i = end + closeTag.length - 1;
  353. openTag = nextOpenTag;
  354. closeTag = nextCloseTag;
  355. } else {
  356. c = template.substr(i, 1);
  357. switch (c) {
  358. case '"':
  359. case "\\":
  360. nonSpace = true;
  361. code.push("\\" + c);
  362. break;
  363. case "\r":
  364. // Ignore carriage returns.
  365. break;
  366. case "\n":
  367. spaces.push(code.length);
  368. code.push("\\n");
  369. stripSpace(); // Check for whitespace on the current line.
  370. line++;
  371. break;
  372. default:
  373. if (isWhitespace(c)) {
  374. spaces.push(code.length);
  375. } else {
  376. nonSpace = true;
  377. }
  378. code.push(c);
  379. }
  380. }
  381. }
  382. if (sectionStack.length != 0) {
  383. throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file);
  384. }
  385. // Clean up any whitespace from a closing {{tag}} that was at the end
  386. // of the template without a trailing \n.
  387. stripSpace();
  388. code.push(
  389. '";',
  390. "\nreturn buffer;",
  391. "\n} catch (e) { throw {error: e, line: line}; }"
  392. );
  393. // Ignore `buffer += "";` statements.
  394. var body = code.join("").replace(/buffer \+= "";\n/g, "");
  395. if (options.debug) {
  396. if (typeof console != "undefined" && console.log) {
  397. console.log(body);
  398. } else if (typeof print === "function") {
  399. print(body);
  400. }
  401. }
  402. return body;
  403. }
  404. /**
  405. * Used by `compile` to generate a reusable function for the given `template`.
  406. */
  407. function _compile(template, options) {
  408. var args = "view,partials,stack,lookup,escapeHTML,renderSection,render";
  409. var body = parse(template, options);
  410. var fn = new Function(args, body);
  411. // This anonymous function wraps the generated function so we can do
  412. // argument coercion, setup some variables, and handle any errors
  413. // encountered while executing it.
  414. return function (view, partials) {
  415. partials = partials || {};
  416. var stack = [view]; // context stack
  417. try {
  418. return fn(view, partials, stack, lookup, escapeHTML, renderSection, render);
  419. } catch (e) {
  420. throw debug(e.error, template, e.line, options.file);
  421. }
  422. };
  423. }
  424. // Cache of pre-compiled templates.
  425. var _cache = {};
  426. /**
  427. * Clear the cache of compiled templates.
  428. */
  429. function clearCache() {
  430. _cache = {};
  431. }
  432. /**
  433. * Compiles the given `template` into a reusable function using the given
  434. * `options`. In addition to the options accepted by Mustache.parse,
  435. * recognized options include the following:
  436. *
  437. * - cache Set `false` to bypass any pre-compiled version of the given
  438. * template. Otherwise, a given `template` string will be cached
  439. * the first time it is parsed
  440. */
  441. function compile(template, options) {
  442. options = options || {};
  443. // Use a pre-compiled version from the cache if we have one.
  444. if (options.cache !== false) {
  445. if (!_cache[template]) {
  446. _cache[template] = _compile(template, options);
  447. }
  448. return _cache[template];
  449. }
  450. return _compile(template, options);
  451. }
  452. /**
  453. * High-level function that renders the given `template` using the given
  454. * `view` and `partials`. If you need to use any of the template options (see
  455. * `compile` above), you must compile in a separate step, and then call that
  456. * compiled function.
  457. */
  458. function render(template, view, partials) {
  459. return compile(template)(view, partials);
  460. }
  461. })(Mustache);
  462. dojox.mustache = dojo.hitch(Mustache, "render");
  463. })();