Statistics
| Branch: | Tag: | Revision:

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
    "&": "&amp;",
48
    "<": "&lt;",
49
    ">": "&gt;",
50
    '"': '&quot;',
51
    "'": '&#39;',
52
    "/": '&#x2F;'
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
}())));