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