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