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, factory) { 9 if (typeof exports === "object" && exports) { 10 factory(exports); // CommonJS 11 } else { 12 var mustache = {}; 13 factory(mustache); 14 if (typeof define === "function" && define.amd) { 15 define(mustache); // AMD 16 } else { 17 root.Mustache = mustache; // <script> 18 } 19 } 20}(this, function (mustache) { 21 22 var whiteRe = /\s*/; 23 var spaceRe = /\s+/; 24 var nonSpaceRe = /\S/; 25 var eqRe = /\s*=/; 26 var curlyRe = /\s*\}/; 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 = { 50 "&": "&", 51 "<": "<", 52 ">": ">", 53 '"': '"', 54 "'": ''', 55 "/": '/' 56 }; 57 58 function escapeHtml(string) { 59 return String(string).replace(/[&<>"'\/]/g, function (s) { 60 return entityMap[s]; 61 }); 62 } 63 64 function Scanner(string) { 65 this.string = string; 66 this.tail = string; 67 this.pos = 0; 68 } 69 70 /** 71 * Returns `true` if the tail is empty (end of string). 72 */ 73 Scanner.prototype.eos = function () { 74 return this.tail === ""; 75 }; 76 77 /** 78 * Tries to match the given regular expression at the current position. 79 * Returns the matched text if it can match, the empty string otherwise. 80 */ 81 Scanner.prototype.scan = function (re) { 82 var match = this.tail.match(re); 83 84 if (match && match.index === 0) { 85 this.tail = this.tail.substring(match[0].length); 86 this.pos += match[0].length; 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; 206 } else { 207 for (var name in partials) { 208 self.compilePartial(name, partials[name]); 209 } 210 } 211 } 212 213 return renderTokens(tokens, self, Context.make(view), template); 214 }; 215 }; 216 217 Writer.prototype.render = function (template, view, partials) { 218 return this.compile(template)(view, partials); 219 }; 220 221 /** 222 * Low-level function that renders the given `tokens` using the given `writer` 223 * and `context`. The `template` string is only needed for templates that use 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 } 383 } else { 384 spaces = []; 385 } 386 387 hasTag = false; 388 nonSpace = false; 389 } 390 391 var start, type, value, chr, token; 392 while (!scanner.eos()) { 393 start = scanner.pos; 394 395 // Match any text between tags. 396 value = scanner.scanUntil(tagRes[0]); 397 if (value) { 398 for (var i = 0, len = value.length; i < len; ++i) { 399 chr = value.charAt(i); 400 401 if (isWhitespace(chr)) { 402 spaces.push(tokens.length); 403 } else { 404 nonSpace = true; 405 } 406 407 tokens.push(['text', chr, start, start + 1]); 408 start += 1; 409 410 // Check for whitespace on the current line. 411 if (chr == '\n') stripSpace(); 412 } 413 } 414 415 // Match the opening tag. 416 if (!scanner.scan(tagRes[0])) break; 417 hasTag = true; 418 419 // Get the tag type. 420 type = scanner.scan(tagRe) || 'name'; 421 scanner.scan(whiteRe); 422 423 // Get the tag value. 424 if (type === '=') { 425 value = scanner.scanUntil(eqRe); 426 scanner.scan(eqRe); 427 scanner.scanUntil(tagRes[1]); 428 } else if (type === '{') { 429 value = scanner.scanUntil(new RegExp('\\s*' + escapeRegExp('}' + tags[1]))); 430 scanner.scan(curlyRe); 431 scanner.scanUntil(tagRes[1]); 432 type = '&'; 433 } else { 434 value = scanner.scanUntil(tagRes[1]); 435 } 436 437 // Match the closing tag. 438 if (!scanner.scan(tagRes[1])) throw new Error('Unclosed tag at ' + scanner.pos); 439 440 token = [type, value, start, scanner.pos]; 441 tokens.push(token); 442 443 if (type === '#' || type === '^') { 444 sections.push(token); 445 } else if (type === '/') { 446 // Check section nesting. 447 if (sections.length === 0) throw new Error('Unopened section "' + value + '" at ' + start); 448 var openSection = sections.pop(); 449 if (openSection[1] !== value) throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); 450 } else if (type === 'name' || type === '{' || type === '&') { 451 nonSpace = true; 452 } else if (type === '=') { 453 // Set the tags for the next time around. 454 tags = value.split(spaceRe); 455 if (tags.length !== 2) throw new Error('Invalid tags at ' + start + ': ' + tags.join(', ')); 456 tagRes = escapeTags(tags); 457 } 458 } 459 460 // Make sure there are no open sections when we're done. 461 var openSection = sections.pop(); 462 if (openSection) throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); 463 464 tokens = squashTokens(tokens); 465 466 return nestTokens(tokens); 467 } 468 469 mustache.name = "mustache.js"; 470 mustache.version = "0.7.2"; 471 mustache.tags = ["{{", "}}"]; 472 473 mustache.Scanner = Scanner; 474 mustache.Context = Context; 475 mustache.Writer = Writer; 476 477 mustache.parse = parseTemplate; 478 479 // Export the escaping function so that the user may override it. 480 // See https://github.com/janl/mustache.js/issues/244 481 mustache.escape = escapeHtml; 482 483 // All Mustache.* functions use this writer. 484 var defaultWriter = new Writer(); 485 486 /** 487 * Clears all cached templates and partials in the default writer. 488 */ 489 mustache.clearCache = function () { 490 return defaultWriter.clearCache(); 491 }; 492 493 /** 494 * Compiles the given `template` to a reusable function using the default 495 * writer. 496 */ 497 mustache.compile = function (template, tags) { 498 return defaultWriter.compile(template, tags); 499 }; 500 501 /** 502 * Compiles the partial with the given `name` and `template` to a reusable 503 * function using the default writer. 504 */ 505 mustache.compilePartial = function (name, template, tags) { 506 return defaultWriter.compilePartial(name, template, tags); 507 }; 508 509 /** 510 * Compiles the given array of tokens (the output of a parse) to a reusable 511 * function using the default writer. 512 */ 513 mustache.compileTokens = function (tokens, template) { 514 return defaultWriter.compileTokens(tokens, template); 515 }; 516 517 /** 518 * Renders the `template` with the given `view` and `partials` using the 519 * default writer. 520 */ 521 mustache.render = function (template, view, partials) { 522 return defaultWriter.render(template, view, partials); 523 }; 524 525 // This is here for backwards compatibility with 0.4.x. 526 mustache.to_html = function (template, view, partials, send) { 527 var result = mustache.render(template, view, partials); 528 529 if (typeof send === "function") { 530 send(result); 531 } else { 532 return result; 533 } 534 }; 535 536}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.