1 /* |
1 /*! |
2 mustache.js — Logic-less templates in JavaScript |
2 * mustache.js - Logic-less {{mustache}} templates with JavaScript |
3 |
3 * http://github.com/janl/mustache.js |
4 See http://mustache.github.com/ for more info. |
4 */ |
5 */ |
5 |
6 |
6 /*global define: false*/ |
7 var Mustache = function () { |
7 |
8 var _toString = Object.prototype.toString; |
8 (function (root, factory) { |
9 |
9 if (typeof exports === "object" && exports) { |
10 Array.isArray = Array.isArray || function (obj) { |
10 factory(exports); // CommonJS |
11 return _toString.call(obj) == "[object Array]"; |
|
12 } |
|
13 |
|
14 var _trim = String.prototype.trim, trim; |
|
15 |
|
16 if (_trim) { |
|
17 trim = function (text) { |
|
18 return text == null ? "" : _trim.call(text); |
|
19 } |
|
20 } else { |
11 } else { |
21 var trimLeft, trimRight; |
12 var mustache = {}; |
22 |
13 factory(mustache); |
23 // IE doesn't match non-breaking spaces with \s. |
14 if (typeof define === "function" && define.amd) { |
24 if ((/\S/).test("\xA0")) { |
15 define(mustache); // AMD |
25 trimLeft = /^[\s\xA0]+/; |
|
26 trimRight = /[\s\xA0]+$/; |
|
27 } else { |
16 } else { |
28 trimLeft = /^\s+/; |
17 root.Mustache = mustache; // <script> |
29 trimRight = /\s+$/; |
18 } |
30 } |
19 } |
31 |
20 }(this, function (mustache) { |
32 trim = function (text) { |
21 |
33 return text == null ? "" : |
22 var whiteRe = /\s*/; |
34 text.toString().replace(trimLeft, "").replace(trimRight, ""); |
23 var spaceRe = /\s+/; |
35 } |
24 var nonSpaceRe = /\S/; |
36 } |
25 var eqRe = /\s*=/; |
37 |
26 var curlyRe = /\s*\}/; |
38 var escapeMap = { |
27 var tagRe = /#|\^|\/|>|\{|&|=|!/; |
|
28 |
|
29 // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 |
|
30 // See https://github.com/janl/mustache.js/issues/189 |
|
31 var RegExp_test = RegExp.prototype.test; |
|
32 function testRegExp(re, string) { |
|
33 return RegExp_test.call(re, string); |
|
34 } |
|
35 |
|
36 function isWhitespace(string) { |
|
37 return !testRegExp(nonSpaceRe, string); |
|
38 } |
|
39 |
|
40 var Object_toString = Object.prototype.toString; |
|
41 var isArray = Array.isArray || function (obj) { |
|
42 return Object_toString.call(obj) === '[object Array]'; |
|
43 }; |
|
44 |
|
45 function escapeRegExp(string) { |
|
46 return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&"); |
|
47 } |
|
48 |
|
49 var entityMap = { |
39 "&": "&", |
50 "&": "&", |
40 "<": "<", |
51 "<": "<", |
41 ">": ">", |
52 ">": ">", |
42 '"': '"', |
53 '"': '"', |
43 "'": ''' |
54 "'": ''', |
44 }; |
55 "/": '/' |
45 |
56 }; |
46 function escapeHTML(string) { |
57 |
47 return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { |
58 function escapeHtml(string) { |
48 return escapeMap[s] || s; |
59 return String(string).replace(/[&<>"'\/]/g, function (s) { |
|
60 return entityMap[s]; |
49 }); |
61 }); |
50 } |
62 } |
51 |
63 |
52 var regexCache = {}; |
64 function Scanner(string) { |
53 var Renderer = function () {}; |
65 this.string = string; |
54 |
66 this.tail = string; |
55 Renderer.prototype = { |
67 this.pos = 0; |
56 otag: "{{", |
68 } |
57 ctag: "}}", |
69 |
58 pragmas: {}, |
70 /** |
59 buffer: [], |
71 * Returns `true` if the tail is empty (end of string). |
60 pragmas_implemented: { |
72 */ |
61 "IMPLICIT-ITERATOR": true |
73 Scanner.prototype.eos = function () { |
62 }, |
74 return this.tail === ""; |
63 context: {}, |
75 }; |
64 |
76 |
65 render: function (template, context, partials, in_recursion) { |
77 /** |
66 // reset buffer & set context |
78 * Tries to match the given regular expression at the current position. |
67 if (!in_recursion) { |
79 * Returns the matched text if it can match, the empty string otherwise. |
68 this.context = context; |
80 */ |
69 this.buffer = []; // TODO: make this non-lazy |
81 Scanner.prototype.scan = function (re) { |
70 } |
82 var match = this.tail.match(re); |
71 |
83 |
72 // fail fast |
84 if (match && match.index === 0) { |
73 if (!this.includes("", template)) { |
85 this.tail = this.tail.substring(match[0].length); |
74 if (in_recursion) { |
86 this.pos += match[0].length; |
75 return template; |
87 return match[0]; |
|
88 } |
|
89 |
|
90 return ""; |
|
91 }; |
|
92 |
|
93 /** |
|
94 * Skips all text until the given regular expression can be matched. Returns |
|
95 * the skipped string, which is the entire tail if no match can be made. |
|
96 */ |
|
97 Scanner.prototype.scanUntil = function (re) { |
|
98 var match, pos = this.tail.search(re); |
|
99 |
|
100 switch (pos) { |
|
101 case -1: |
|
102 match = this.tail; |
|
103 this.pos += this.tail.length; |
|
104 this.tail = ""; |
|
105 break; |
|
106 case 0: |
|
107 match = ""; |
|
108 break; |
|
109 default: |
|
110 match = this.tail.substring(0, pos); |
|
111 this.tail = this.tail.substring(pos); |
|
112 this.pos += pos; |
|
113 } |
|
114 |
|
115 return match; |
|
116 }; |
|
117 |
|
118 function Context(view, parent) { |
|
119 this.view = view || {}; |
|
120 this.parent = parent; |
|
121 this._cache = {}; |
|
122 } |
|
123 |
|
124 Context.make = function (view) { |
|
125 return (view instanceof Context) ? view : new Context(view); |
|
126 }; |
|
127 |
|
128 Context.prototype.push = function (view) { |
|
129 return new Context(view, this); |
|
130 }; |
|
131 |
|
132 Context.prototype.lookup = function (name) { |
|
133 var value = this._cache[name]; |
|
134 |
|
135 if (!value) { |
|
136 if (name == '.') { |
|
137 value = this.view; |
|
138 } else { |
|
139 var context = this; |
|
140 |
|
141 while (context) { |
|
142 if (name.indexOf('.') > 0) { |
|
143 value = context.view; |
|
144 var names = name.split('.'), i = 0; |
|
145 while (value && i < names.length) { |
|
146 value = value[names[i++]]; |
|
147 } |
|
148 } else { |
|
149 value = context.view[name]; |
|
150 } |
|
151 |
|
152 if (value != null) break; |
|
153 |
|
154 context = context.parent; |
|
155 } |
|
156 } |
|
157 |
|
158 this._cache[name] = value; |
|
159 } |
|
160 |
|
161 if (typeof value === 'function') value = value.call(this.view); |
|
162 |
|
163 return value; |
|
164 }; |
|
165 |
|
166 function Writer() { |
|
167 this.clearCache(); |
|
168 } |
|
169 |
|
170 Writer.prototype.clearCache = function () { |
|
171 this._cache = {}; |
|
172 this._partialCache = {}; |
|
173 }; |
|
174 |
|
175 Writer.prototype.compile = function (template, tags) { |
|
176 var fn = this._cache[template]; |
|
177 |
|
178 if (!fn) { |
|
179 var tokens = mustache.parse(template, tags); |
|
180 fn = this._cache[template] = this.compileTokens(tokens, template); |
|
181 } |
|
182 |
|
183 return fn; |
|
184 }; |
|
185 |
|
186 Writer.prototype.compilePartial = function (name, template, tags) { |
|
187 var fn = this.compile(template, tags); |
|
188 this._partialCache[name] = fn; |
|
189 return fn; |
|
190 }; |
|
191 |
|
192 Writer.prototype.getPartial = function (name) { |
|
193 if (!(name in this._partialCache) && this._loadPartial) { |
|
194 this.compilePartial(name, this._loadPartial(name)); |
|
195 } |
|
196 |
|
197 return this._partialCache[name]; |
|
198 }; |
|
199 |
|
200 Writer.prototype.compileTokens = function (tokens, template) { |
|
201 var self = this; |
|
202 return function (view, partials) { |
|
203 if (partials) { |
|
204 if (typeof partials === 'function') { |
|
205 self._loadPartial = partials; |
76 } else { |
206 } else { |
77 this.send(template); |
207 for (var name in partials) { |
78 return; |
208 self.compilePartial(name, partials[name]); |
79 } |
209 } |
80 } |
210 } |
81 |
211 } |
82 // get the pragmas together |
212 |
83 template = this.render_pragmas(template); |
213 return renderTokens(tokens, self, Context.make(view), template); |
84 |
214 }; |
85 // render the template |
215 }; |
86 var html = this.render_section(template, context, partials); |
216 |
87 |
217 Writer.prototype.render = function (template, view, partials) { |
88 // render_section did not find any sections, we still need to render the tags |
218 return this.compile(template)(view, partials); |
89 if (html === false) { |
219 }; |
90 html = this.render_tags(template, context, partials, in_recursion); |
220 |
91 } |
221 /** |
92 |
222 * Low-level function that renders the given `tokens` using the given `writer` |
93 if (in_recursion) { |
223 * and `context`. The `template` string is only needed for templates that use |
94 return html; |
224 * higher-order sections to extract the portion of the original template that |
|
225 * was contained in that section. |
|
226 */ |
|
227 function renderTokens(tokens, writer, context, template) { |
|
228 var buffer = ''; |
|
229 |
|
230 var token, tokenValue, value; |
|
231 for (var i = 0, len = tokens.length; i < len; ++i) { |
|
232 token = tokens[i]; |
|
233 tokenValue = token[1]; |
|
234 |
|
235 switch (token[0]) { |
|
236 case '#': |
|
237 value = context.lookup(tokenValue); |
|
238 |
|
239 if (typeof value === 'object') { |
|
240 if (isArray(value)) { |
|
241 for (var j = 0, jlen = value.length; j < jlen; ++j) { |
|
242 buffer += renderTokens(token[4], writer, context.push(value[j]), template); |
|
243 } |
|
244 } else if (value) { |
|
245 buffer += renderTokens(token[4], writer, context.push(value), template); |
|
246 } |
|
247 } else if (typeof value === 'function') { |
|
248 var text = template == null ? null : template.slice(token[3], token[5]); |
|
249 value = value.call(context.view, text, function (template) { |
|
250 return writer.render(template, context); |
|
251 }); |
|
252 if (value != null) buffer += value; |
|
253 } else if (value) { |
|
254 buffer += renderTokens(token[4], writer, context, template); |
|
255 } |
|
256 |
|
257 break; |
|
258 case '^': |
|
259 value = context.lookup(tokenValue); |
|
260 |
|
261 // Use JavaScript's definition of falsy. Include empty arrays. |
|
262 // See https://github.com/janl/mustache.js/issues/186 |
|
263 if (!value || (isArray(value) && value.length === 0)) { |
|
264 buffer += renderTokens(token[4], writer, context, template); |
|
265 } |
|
266 |
|
267 break; |
|
268 case '>': |
|
269 value = writer.getPartial(tokenValue); |
|
270 if (typeof value === 'function') buffer += value(context); |
|
271 break; |
|
272 case '&': |
|
273 value = context.lookup(tokenValue); |
|
274 if (value != null) buffer += value; |
|
275 break; |
|
276 case 'name': |
|
277 value = context.lookup(tokenValue); |
|
278 if (value != null) buffer += mustache.escape(value); |
|
279 break; |
|
280 case 'text': |
|
281 buffer += tokenValue; |
|
282 break; |
|
283 } |
|
284 } |
|
285 |
|
286 return buffer; |
|
287 } |
|
288 |
|
289 /** |
|
290 * Forms the given array of `tokens` into a nested tree structure where |
|
291 * tokens that represent a section have two additional items: 1) an array of |
|
292 * all tokens that appear in that section and 2) the index in the original |
|
293 * template that represents the end of that section. |
|
294 */ |
|
295 function nestTokens(tokens) { |
|
296 var tree = []; |
|
297 var collector = tree; |
|
298 var sections = []; |
|
299 |
|
300 var token; |
|
301 for (var i = 0, len = tokens.length; i < len; ++i) { |
|
302 token = tokens[i]; |
|
303 switch (token[0]) { |
|
304 case '#': |
|
305 case '^': |
|
306 sections.push(token); |
|
307 collector.push(token); |
|
308 collector = token[4] = []; |
|
309 break; |
|
310 case '/': |
|
311 var section = sections.pop(); |
|
312 section[5] = token[2]; |
|
313 collector = sections.length > 0 ? sections[sections.length - 1][4] : tree; |
|
314 break; |
|
315 default: |
|
316 collector.push(token); |
|
317 } |
|
318 } |
|
319 |
|
320 return tree; |
|
321 } |
|
322 |
|
323 /** |
|
324 * Combines the values of consecutive text tokens in the given `tokens` array |
|
325 * to a single token. |
|
326 */ |
|
327 function squashTokens(tokens) { |
|
328 var squashedTokens = []; |
|
329 |
|
330 var token, lastToken; |
|
331 for (var i = 0, len = tokens.length; i < len; ++i) { |
|
332 token = tokens[i]; |
|
333 if (token) { |
|
334 if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { |
|
335 lastToken[1] += token[1]; |
|
336 lastToken[3] = token[3]; |
|
337 } else { |
|
338 lastToken = token; |
|
339 squashedTokens.push(token); |
|
340 } |
|
341 } |
|
342 } |
|
343 |
|
344 return squashedTokens; |
|
345 } |
|
346 |
|
347 function escapeTags(tags) { |
|
348 return [ |
|
349 new RegExp(escapeRegExp(tags[0]) + "\\s*"), |
|
350 new RegExp("\\s*" + escapeRegExp(tags[1])) |
|
351 ]; |
|
352 } |
|
353 |
|
354 /** |
|
355 * Breaks up the given `template` string into a tree of token objects. If |
|
356 * `tags` is given here it must be an array with two string values: the |
|
357 * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of |
|
358 * course, the default is to use mustaches (i.e. Mustache.tags). |
|
359 */ |
|
360 function parseTemplate(template, tags) { |
|
361 template = template || ''; |
|
362 tags = tags || mustache.tags; |
|
363 |
|
364 if (typeof tags === 'string') tags = tags.split(spaceRe); |
|
365 if (tags.length !== 2) throw new Error('Invalid tags: ' + tags.join(', ')); |
|
366 |
|
367 var tagRes = escapeTags(tags); |
|
368 var scanner = new Scanner(template); |
|
369 |
|
370 var sections = []; // Stack to hold section tokens |
|
371 var tokens = []; // Buffer to hold the tokens |
|
372 var spaces = []; // Indices of whitespace tokens on the current line |
|
373 var hasTag = false; // Is there a {{tag}} on the current line? |
|
374 var nonSpace = false; // Is there a non-space char on the current line? |
|
375 |
|
376 // Strips all whitespace tokens array for the current line |
|
377 // if there was a {{#tag}} on it and otherwise only space. |
|
378 function stripSpace() { |
|
379 if (hasTag && !nonSpace) { |
|
380 while (spaces.length) { |
|
381 delete tokens[spaces.pop()]; |
|
382 } |
95 } else { |
383 } else { |
96 this.sendLines(html); |
384 spaces = []; |
97 } |
385 } |
98 }, |
386 |
99 |
387 hasTag = false; |
100 /* |
388 nonSpace = false; |
101 Sends parsed lines |
389 } |
102 */ |
390 |
103 send: function (line) { |
391 var start, type, value, chr, token; |
104 if (line !== "") { |
392 while (!scanner.eos()) { |
105 this.buffer.push(line); |
393 start = scanner.pos; |
106 } |
394 |
107 }, |
395 // Match any text between tags. |
108 |
396 value = scanner.scanUntil(tagRes[0]); |
109 sendLines: function (text) { |
397 if (value) { |
110 if (text) { |
398 for (var i = 0, len = value.length; i < len; ++i) { |
111 var lines = text.split("\n"); |
399 chr = value.charAt(i); |
112 for (var i = 0; i < lines.length; i++) { |
400 |
113 this.send(lines[i]); |
401 if (isWhitespace(chr)) { |
114 } |
402 spaces.push(tokens.length); |
115 } |
|
116 }, |
|
117 |
|
118 /* |
|
119 Looks for %PRAGMAS |
|
120 */ |
|
121 render_pragmas: function (template) { |
|
122 // no pragmas |
|
123 if (!this.includes("%", template)) { |
|
124 return template; |
|
125 } |
|
126 |
|
127 var that = this; |
|
128 var regex = this.getCachedRegex("render_pragmas", function (otag, ctag) { |
|
129 return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); |
|
130 }); |
|
131 |
|
132 return template.replace(regex, function (match, pragma, options) { |
|
133 if (!that.pragmas_implemented[pragma]) { |
|
134 throw({message: |
|
135 "This implementation of mustache doesn't understand the '" + |
|
136 pragma + "' pragma"}); |
|
137 } |
|
138 that.pragmas[pragma] = {}; |
|
139 if (options) { |
|
140 var opts = options.split("="); |
|
141 that.pragmas[pragma][opts[0]] = opts[1]; |
|
142 } |
|
143 return ""; |
|
144 // ignore unknown pragmas silently |
|
145 }); |
|
146 }, |
|
147 |
|
148 /* |
|
149 Tries to find a partial in the curent scope and render it |
|
150 */ |
|
151 render_partial: function (name, context, partials) { |
|
152 name = trim(name); |
|
153 if (!partials || partials[name] === undefined) { |
|
154 throw({message: "unknown_partial '" + name + "'"}); |
|
155 } |
|
156 if (!context || typeof context[name] != "object") { |
|
157 return this.render(partials[name], context, partials, true); |
|
158 } |
|
159 return this.render(partials[name], context[name], partials, true); |
|
160 }, |
|
161 |
|
162 /* |
|
163 Renders inverted (^) and normal (#) sections |
|
164 */ |
|
165 render_section: function (template, context, partials) { |
|
166 if (!this.includes("#", template) && !this.includes("^", template)) { |
|
167 // did not render anything, there were no sections |
|
168 return false; |
|
169 } |
|
170 |
|
171 var that = this; |
|
172 |
|
173 var regex = this.getCachedRegex("render_section", function (otag, ctag) { |
|
174 // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder |
|
175 return new RegExp( |
|
176 "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1) |
|
177 |
|
178 otag + // {{ |
|
179 "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) |
|
180 ctag + // }} |
|
181 |
|
182 "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped |
|
183 |
|
184 otag + // {{ |
|
185 "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). |
|
186 ctag + // }} |
|
187 |
|
188 "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. |
|
189 |
|
190 "g"); |
|
191 }); |
|
192 |
|
193 |
|
194 // for each {{#foo}}{{/foo}} section do... |
|
195 return template.replace(regex, function (match, before, type, name, content, after) { |
|
196 // before contains only tags, no sections |
|
197 var renderedBefore = before ? that.render_tags(before, context, partials, true) : "", |
|
198 |
|
199 // after may contain both sections and tags, so use full rendering function |
|
200 renderedAfter = after ? that.render(after, context, partials, true) : "", |
|
201 |
|
202 // will be computed below |
|
203 renderedContent, |
|
204 |
|
205 value = that.find(name, context); |
|
206 |
|
207 if (type === "^") { // inverted section |
|
208 if (!value || Array.isArray(value) && value.length === 0) { |
|
209 // false or empty list, render it |
|
210 renderedContent = that.render(content, context, partials, true); |
|
211 } else { |
403 } else { |
212 renderedContent = ""; |
404 nonSpace = true; |
213 } |
405 } |
214 } else if (type === "#") { // normal section |
406 |
215 if (Array.isArray(value)) { // Enumerable, Let's loop! |
407 tokens.push(['text', chr, start, start + 1]); |
216 renderedContent = that.map(value, function (row) { |
408 start += 1; |
217 return that.render(content, that.create_context(row), partials, true); |
409 |
218 }).join(""); |
410 // Check for whitespace on the current line. |
219 } else if (that.is_object(value)) { // Object, Use it as subcontext! |
411 if (chr == '\n') stripSpace(); |
220 renderedContent = that.render(content, that.create_context(value), |
412 } |
221 partials, true); |
413 } |
222 } else if (typeof value == "function") { |
414 |
223 // higher order section |
415 // Match the opening tag. |
224 renderedContent = value.call(context, content, function (text) { |
416 if (!scanner.scan(tagRes[0])) break; |
225 return that.render(text, context, partials, true); |
417 hasTag = true; |
226 }); |
418 |
227 } else if (value) { // boolean section |
419 // Get the tag type. |
228 renderedContent = that.render(content, context, partials, true); |
420 type = scanner.scan(tagRe) || 'name'; |
229 } else { |
421 scanner.scan(whiteRe); |
230 renderedContent = ""; |
422 |
231 } |
423 // Get the tag value. |
232 } |
424 if (type === '=') { |
233 |
425 value = scanner.scanUntil(eqRe); |
234 return renderedBefore + renderedContent + renderedAfter; |
426 scanner.scan(eqRe); |
235 }); |
427 scanner.scanUntil(tagRes[1]); |
236 }, |
428 } else if (type === '{') { |
237 |
429 value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1]))); |
238 /* |
430 scanner.scan(curlyRe); |
239 Replace {{foo}} and friends with values from our view |
431 scanner.scanUntil(tagRes[1]); |
240 */ |
432 type = '&'; |
241 render_tags: function (template, context, partials, in_recursion) { |
|
242 // tit for tat |
|
243 var that = this; |
|
244 |
|
245 var new_regex = function () { |
|
246 return that.getCachedRegex("render_tags", function (otag, ctag) { |
|
247 return new RegExp(otag + "(=|!|>|&|\\{|%)?([^#\\^]+?)\\1?" + ctag + "+", "g"); |
|
248 }); |
|
249 }; |
|
250 |
|
251 var regex = new_regex(); |
|
252 var tag_replace_callback = function (match, operator, name) { |
|
253 switch(operator) { |
|
254 case "!": // ignore comments |
|
255 return ""; |
|
256 case "=": // set new delimiters, rebuild the replace regexp |
|
257 that.set_delimiters(name); |
|
258 regex = new_regex(); |
|
259 return ""; |
|
260 case ">": // render partial |
|
261 return that.render_partial(name, context, partials); |
|
262 case "{": // the triple mustache is unescaped |
|
263 case "&": // & operator is an alternative unescape method |
|
264 return that.find(name, context); |
|
265 default: // escape the value |
|
266 return escapeHTML(that.find(name, context)); |
|
267 } |
|
268 }; |
|
269 var lines = template.split("\n"); |
|
270 for(var i = 0; i < lines.length; i++) { |
|
271 lines[i] = lines[i].replace(regex, tag_replace_callback, this); |
|
272 if (!in_recursion) { |
|
273 this.send(lines[i]); |
|
274 } |
|
275 } |
|
276 |
|
277 if (in_recursion) { |
|
278 return lines.join("\n"); |
|
279 } |
|
280 }, |
|
281 |
|
282 set_delimiters: function (delimiters) { |
|
283 var dels = delimiters.split(" "); |
|
284 this.otag = this.escape_regex(dels[0]); |
|
285 this.ctag = this.escape_regex(dels[1]); |
|
286 }, |
|
287 |
|
288 escape_regex: function (text) { |
|
289 // thank you Simon Willison |
|
290 if (!arguments.callee.sRE) { |
|
291 var specials = [ |
|
292 '/', '.', '*', '+', '?', '|', |
|
293 '(', ')', '[', ']', '{', '}', '\\' |
|
294 ]; |
|
295 arguments.callee.sRE = new RegExp( |
|
296 '(\\' + specials.join('|\\') + ')', 'g' |
|
297 ); |
|
298 } |
|
299 return text.replace(arguments.callee.sRE, '\\$1'); |
|
300 }, |
|
301 |
|
302 /* |
|
303 find `name` in current `context`. That is find me a value |
|
304 from the view object |
|
305 */ |
|
306 find: function (name, context) { |
|
307 name = trim(name); |
|
308 |
|
309 // Checks whether a value is thruthy or false or 0 |
|
310 function is_kinda_truthy(bool) { |
|
311 return bool === false || bool === 0 || bool; |
|
312 } |
|
313 |
|
314 var value; |
|
315 |
|
316 // check for dot notation eg. foo.bar |
|
317 if (name.match(/([a-z_]+)\./ig)) { |
|
318 var childValue = this.walk_context(name, context); |
|
319 if (is_kinda_truthy(childValue)) { |
|
320 value = childValue; |
|
321 } |
|
322 } else { |
433 } else { |
323 if (is_kinda_truthy(context[name])) { |
434 value = scanner.scanUntil(tagRes[1]); |
324 value = context[name]; |
435 } |
325 } else if (is_kinda_truthy(this.context[name])) { |
436 |
326 value = this.context[name]; |
437 // Match the closing tag. |
327 } |
438 if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos); |
328 } |
439 |
329 |
440 token = [type, value, start, scanner.pos]; |
330 if (typeof value == "function") { |
441 tokens.push(token); |
331 return value.apply(context); |
442 |
332 } |
443 if (type === '#' || type === '^') { |
333 if (value !== undefined) { |
444 sections.push(token); |
334 return value; |
445 } else if (type === '/') { |
335 } |
446 // Check section nesting. |
336 // silently ignore unkown variables |
447 if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start); |
337 return ""; |
448 var openSection = sections.pop(); |
338 }, |
449 if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); |
339 |
450 } else if (type === 'name' || type === '{' || type === '&') { |
340 walk_context: function (name, context) { |
451 nonSpace = true; |
341 var path = name.split('.'); |
452 } else if (type === '=') { |
342 // if the var doesn't exist in current context, check the top level context |
453 // Set the tags for the next time around. |
343 var value_context = (context[path[0]] != undefined) ? context : this.context; |
454 tags = value.split(spaceRe); |
344 var value = value_context[path.shift()]; |
455 if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); |
345 while (value != undefined && path.length > 0) { |
456 tagRes = escapeTags(tags); |
346 value_context = value; |
457 } |
347 value = value[path.shift()]; |
458 } |
348 } |
459 |
349 // if the value is a function, call it, binding the correct context |
460 // Make sure there are no open sections when we're done. |
350 if (typeof value == "function") { |
461 var openSection = sections.pop(); |
351 return value.apply(value_context); |
462 if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); |
352 } |
463 |
353 return value; |
464 tokens = squashTokens(tokens); |
354 }, |
465 |
355 |
466 return nestTokens(tokens); |
356 // Utility methods |
467 } |
357 |
468 |
358 /* includes tag */ |
469 mustache.name = "mustache.js"; |
359 includes: function (needle, haystack) { |
470 mustache.version = "0.7.2"; |
360 return haystack.indexOf(this.otag + needle) != -1; |
471 mustache.tags = ["{{", "}}"]; |
361 }, |
472 |
362 |
473 mustache.Scanner = Scanner; |
363 // by @langalex, support for arrays of strings |
474 mustache.Context = Context; |
364 create_context: function (_context) { |
475 mustache.Writer = Writer; |
365 if (this.is_object(_context)) { |
476 |
366 return _context; |
477 mustache.parse = parseTemplate; |
367 } else { |
478 |
368 var iterator = "."; |
479 // Export the escaping function so that the user may override it. |
369 if (this.pragmas["IMPLICIT-ITERATOR"]) { |
480 // See https://github.com/janl/mustache.js/issues/244 |
370 iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; |
481 mustache.escape = escapeHtml; |
371 } |
482 |
372 var ctx = {}; |
483 // All Mustache.* functions use this writer. |
373 ctx[iterator] = _context; |
484 var defaultWriter = new Writer(); |
374 return ctx; |
485 |
375 } |
486 /** |
376 }, |
487 * Clears all cached templates and partials in the default writer. |
377 |
488 */ |
378 is_object: function (a) { |
489 mustache.clearCache = function () { |
379 return a && typeof a == "object"; |
490 return defaultWriter.clearCache(); |
380 }, |
491 }; |
381 |
492 |
382 /* |
493 /** |
383 Why, why, why? Because IE. Cry, cry cry. |
494 * Compiles the given `template` to a reusable function using the default |
384 */ |
495 * writer. |
385 map: function (array, fn) { |
496 */ |
386 if (typeof array.map == "function") { |
497 mustache.compile = function (template, tags) { |
387 return array.map(fn); |
498 return defaultWriter.compile(template, tags); |
388 } else { |
499 }; |
389 var r = []; |
500 |
390 var l = array.length; |
501 /** |
391 for(var i = 0; i < l; i++) { |
502 * Compiles the partial with the given `name` and `template` to a reusable |
392 r.push(fn(array[i])); |
503 * function using the default writer. |
393 } |
504 */ |
394 return r; |
505 mustache.compilePartial = function (name, template, tags) { |
395 } |
506 return defaultWriter.compilePartial(name, template, tags); |
396 }, |
507 }; |
397 |
508 |
398 getCachedRegex: function (name, generator) { |
509 /** |
399 var byOtag = regexCache[this.otag]; |
510 * Compiles the given array of tokens (the output of a parse) to a reusable |
400 if (!byOtag) { |
511 * function using the default writer. |
401 byOtag = regexCache[this.otag] = {}; |
512 */ |
402 } |
513 mustache.compileTokens = function (tokens, template) { |
403 |
514 return defaultWriter.compileTokens(tokens, template); |
404 var byCtag = byOtag[this.ctag]; |
515 }; |
405 if (!byCtag) { |
516 |
406 byCtag = byOtag[this.ctag] = {}; |
517 /** |
407 } |
518 * Renders the `template` with the given `view` and `partials` using the |
408 |
519 * default writer. |
409 var regex = byCtag[name]; |
520 */ |
410 if (!regex) { |
521 mustache.render = function (template, view, partials) { |
411 regex = byCtag[name] = generator(this.otag, this.ctag); |
522 return defaultWriter.render(template, view, partials); |
412 } |
523 }; |
413 |
524 |
414 return regex; |
525 // This is here for backwards compatibility with 0.4.x. |
415 } |
526 mustache.to_html = function (template, view, partials, send) { |
416 }; |
527 var result = mustache.render(template, view, partials); |
417 |
528 |
418 return({ |
529 if (typeof send === "function") { |
419 name: "mustache.js", |
530 send(result); |
420 version: "0.5.0-dev", |
531 } else { |
421 |
532 return result; |
422 /* |
533 } |
423 Turns a template and view into HTML |
534 }; |
424 */ |
535 |
425 to_html: function (template, view, partials, send_fun) { |
536 })); |
426 var renderer = new Renderer(); |
|
427 if (send_fun) { |
|
428 renderer.send = send_fun; |
|
429 } |
|
430 renderer.render(template, view || {}, partials); |
|
431 if (!send_fun) { |
|
432 return renderer.buffer.join("\n"); |
|
433 } |
|
434 } |
|
435 }); |
|
436 }(); |
|