[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 // The MIT License 2 // 3 // Copyright (c) 2009 Chris Wanstrath (Ruby) 4 // Copyright (c) 2010-2014 Jan Lehnardt (JavaScript) 5 // Copyright (c) 2010-2015 The mustache.js community 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining 8 // a copy of this software and associated documentation files (the 9 // "Software"), to deal in the Software without restriction, including 10 // without limitation the rights to use, copy, modify, merge, publish, 11 // distribute, sublicense, and/or sell copies of the Software, and to 12 // permit persons to whom the Software is furnished to do so, subject to 13 // the following conditions: 14 // 15 // The above copyright notice and this permission notice shall be 16 // included in all copies or substantial portions of the Software. 17 // 18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 // 26 27 // Description of import into Moodle: 28 // Checkout from https://github.com/moodle/custom-mustache.js 29 // Rebase onto latest release tag from https://github.com/janl/mustache.js 30 // Copy mustache.js into lib/amd/src/ in Moodle folder. 31 // Add the license as a comment to the file and these instructions. 32 // Add jshint tags so this file is not linted. 33 // Remove the "global define:" comment (hint for linter) 34 // Make sure that you have not removed the custom code for '$' and '<'. 35 36 /*! 37 * mustache.js - Logic-less {{mustache}} templates with JavaScript 38 * http://github.com/janl/mustache.js 39 */ 40 41 /* jshint ignore:start */ 42 43 (function defineMustache (global, factory) { 44 if (typeof exports === 'object' && exports && typeof exports.nodeName !== 'string') { 45 factory(exports); // CommonJS 46 } else if (typeof define === 'function' && define.amd) { 47 define(['exports'], factory); // AMD 48 } else { 49 global.Mustache = {}; 50 factory(global.Mustache); // script, wsh, asp 51 } 52 }(this, function mustacheFactory (mustache) { 53 54 var objectToString = Object.prototype.toString; 55 var isArray = Array.isArray || function isArrayPolyfill (object) { 56 return objectToString.call(object) === '[object Array]'; 57 }; 58 59 function isFunction (object) { 60 return typeof object === 'function'; 61 } 62 63 /** 64 * More correct typeof string handling array 65 * which normally returns typeof 'object' 66 */ 67 function typeStr (obj) { 68 return isArray(obj) ? 'array' : typeof obj; 69 } 70 71 function escapeRegExp (string) { 72 return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'); 73 } 74 75 /** 76 * Null safe way of checking whether or not an object, 77 * including its prototype, has a given property 78 */ 79 function hasProperty (obj, propName) { 80 return obj != null && typeof obj === 'object' && (propName in obj); 81 } 82 83 // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577 84 // See https://github.com/janl/mustache.js/issues/189 85 var regExpTest = RegExp.prototype.test; 86 function testRegExp (re, string) { 87 return regExpTest.call(re, string); 88 } 89 90 var nonSpaceRe = /\S/; 91 function isWhitespace (string) { 92 return !testRegExp(nonSpaceRe, string); 93 } 94 95 var entityMap = { 96 '&': '&', 97 '<': '<', 98 '>': '>', 99 '"': '"', 100 "'": ''', 101 '/': '/', 102 '`': '`', 103 '=': '=' 104 }; 105 106 function escapeHtml (string) { 107 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) { 108 return entityMap[s]; 109 }); 110 } 111 112 var whiteRe = /\s*/; 113 var spaceRe = /\s+/; 114 var equalsRe = /\s*=/; 115 var curlyRe = /\s*\}/; 116 var tagRe = /#|\^|\/|>|\{|&|=|!|\$|</; 117 118 /** 119 * Breaks up the given `template` string into a tree of tokens. If the `tags` 120 * argument is given here it must be an array with two string values: the 121 * opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of 122 * course, the default is to use mustaches (i.e. mustache.tags). 123 * 124 * A token is an array with at least 4 elements. The first element is the 125 * mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag 126 * did not contain a symbol (i.e. {{myValue}}) this element is "name". For 127 * all text that appears outside a symbol this element is "text". 128 * 129 * The second element of a token is its "value". For mustache tags this is 130 * whatever else was inside the tag besides the opening symbol. For text tokens 131 * this is the text itself. 132 * 133 * The third and fourth elements of the token are the start and end indices, 134 * respectively, of the token in the original template. 135 * 136 * Tokens that are the root node of a subtree contain two more elements: 1) an 137 * array of tokens in the subtree and 2) the index in the original template at 138 * which the closing tag for that section begins. 139 */ 140 function parseTemplate (template, tags) { 141 if (!template) 142 return []; 143 144 var sections = []; // Stack to hold section tokens 145 var tokens = []; // Buffer to hold the tokens 146 var spaces = []; // Indices of whitespace tokens on the current line 147 var hasTag = false; // Is there a {{tag}} on the current line? 148 var nonSpace = false; // Is there a non-space char on the current line? 149 150 // Strips all whitespace tokens array for the current line 151 // if there was a {{#tag}} on it and otherwise only space. 152 function stripSpace () { 153 if (hasTag && !nonSpace) { 154 while (spaces.length) 155 delete tokens[spaces.pop()]; 156 } else { 157 spaces = []; 158 } 159 160 hasTag = false; 161 nonSpace = false; 162 } 163 164 var openingTagRe, closingTagRe, closingCurlyRe; 165 function compileTags (tagsToCompile) { 166 if (typeof tagsToCompile === 'string') 167 tagsToCompile = tagsToCompile.split(spaceRe, 2); 168 169 if (!isArray(tagsToCompile) || tagsToCompile.length !== 2) 170 throw new Error('Invalid tags: ' + tagsToCompile); 171 172 openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*'); 173 closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1])); 174 closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1])); 175 } 176 177 compileTags(tags || mustache.tags); 178 179 var scanner = new Scanner(template); 180 181 var start, type, value, chr, token, openSection; 182 while (!scanner.eos()) { 183 start = scanner.pos; 184 185 // Match any text between tags. 186 value = scanner.scanUntil(openingTagRe); 187 188 if (value) { 189 for (var i = 0, valueLength = value.length; i < valueLength; ++i) { 190 chr = value.charAt(i); 191 192 if (isWhitespace(chr)) { 193 spaces.push(tokens.length); 194 } else { 195 nonSpace = true; 196 } 197 198 tokens.push([ 'text', chr, start, start + 1 ]); 199 start += 1; 200 201 // Check for whitespace on the current line. 202 if (chr === '\n') 203 stripSpace(); 204 } 205 } 206 207 // Match the opening tag. 208 if (!scanner.scan(openingTagRe)) 209 break; 210 211 hasTag = true; 212 213 // Get the tag type. 214 type = scanner.scan(tagRe) || 'name'; 215 scanner.scan(whiteRe); 216 217 // Get the tag value. 218 if (type === '=') { 219 value = scanner.scanUntil(equalsRe); 220 scanner.scan(equalsRe); 221 scanner.scanUntil(closingTagRe); 222 } else if (type === '{') { 223 value = scanner.scanUntil(closingCurlyRe); 224 scanner.scan(curlyRe); 225 scanner.scanUntil(closingTagRe); 226 type = '&'; 227 } else { 228 value = scanner.scanUntil(closingTagRe); 229 } 230 231 // Match the closing tag. 232 if (!scanner.scan(closingTagRe)) 233 throw new Error('Unclosed tag at ' + scanner.pos); 234 235 token = [ type, value, start, scanner.pos ]; 236 tokens.push(token); 237 238 if (type === '#' || type === '^' || type === '$' || type === '<') { 239 sections.push(token); 240 } else if (type === '/') { 241 // Check section nesting. 242 openSection = sections.pop(); 243 244 if (!openSection) 245 throw new Error('Unopened section "' + value + '" at ' + start); 246 247 if (openSection[1] !== value) 248 throw new Error('Unclosed section "' + openSection[1] + '" at ' + start); 249 } else if (type === 'name' || type === '{' || type === '&') { 250 nonSpace = true; 251 } else if (type === '=') { 252 // Set the tags for the next time around. 253 compileTags(value); 254 } 255 } 256 257 // Make sure there are no open sections when we're done. 258 openSection = sections.pop(); 259 260 if (openSection) 261 throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos); 262 263 return nestTokens(squashTokens(tokens)); 264 } 265 266 /** 267 * Combines the values of consecutive text tokens in the given `tokens` array 268 * to a single token. 269 */ 270 function squashTokens (tokens) { 271 var squashedTokens = []; 272 273 var token, lastToken; 274 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 275 token = tokens[i]; 276 277 if (token) { 278 if (token[0] === 'text' && lastToken && lastToken[0] === 'text') { 279 lastToken[1] += token[1]; 280 lastToken[3] = token[3]; 281 } else { 282 squashedTokens.push(token); 283 lastToken = token; 284 } 285 } 286 } 287 288 return squashedTokens; 289 } 290 291 /** 292 * Forms the given array of `tokens` into a nested tree structure where 293 * tokens that represent a section have two additional items: 1) an array of 294 * all tokens that appear in that section and 2) the index in the original 295 * template that represents the end of that section. 296 */ 297 function nestTokens (tokens) { 298 var nestedTokens = []; 299 var collector = nestedTokens; 300 var sections = []; 301 302 var token, section; 303 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 304 token = tokens[i]; 305 306 switch (token[0]) { 307 case '$': 308 case '<': 309 case '#': 310 case '^': 311 collector.push(token); 312 sections.push(token); 313 collector = token[4] = []; 314 break; 315 case '/': 316 section = sections.pop(); 317 section[5] = token[2]; 318 collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens; 319 break; 320 default: 321 collector.push(token); 322 } 323 } 324 325 return nestedTokens; 326 } 327 328 /** 329 * A simple string scanner that is used by the template parser to find 330 * tokens in template strings. 331 */ 332 function Scanner (string) { 333 this.string = string; 334 this.tail = string; 335 this.pos = 0; 336 } 337 338 /** 339 * Returns `true` if the tail is empty (end of string). 340 */ 341 Scanner.prototype.eos = function eos () { 342 return this.tail === ''; 343 }; 344 345 /** 346 * Tries to match the given regular expression at the current position. 347 * Returns the matched text if it can match, the empty string otherwise. 348 */ 349 Scanner.prototype.scan = function scan (re) { 350 var match = this.tail.match(re); 351 352 if (!match || match.index !== 0) 353 return ''; 354 355 var string = match[0]; 356 357 this.tail = this.tail.substring(string.length); 358 this.pos += string.length; 359 360 return string; 361 }; 362 363 /** 364 * Skips all text until the given regular expression can be matched. Returns 365 * the skipped string, which is the entire tail if no match can be made. 366 */ 367 Scanner.prototype.scanUntil = function scanUntil (re) { 368 var index = this.tail.search(re), match; 369 370 switch (index) { 371 case -1: 372 match = this.tail; 373 this.tail = ''; 374 break; 375 case 0: 376 match = ''; 377 break; 378 default: 379 match = this.tail.substring(0, index); 380 this.tail = this.tail.substring(index); 381 } 382 383 this.pos += match.length; 384 385 return match; 386 }; 387 388 /** 389 * Represents a rendering context by wrapping a view object and 390 * maintaining a reference to the parent context. 391 */ 392 function Context (view, parentContext) { 393 this.view = view; 394 this.blocks = {}; 395 this.cache = { '.': this.view }; 396 this.parent = parentContext; 397 } 398 399 /** 400 * Creates a new context using the given view with this context 401 * as the parent. 402 */ 403 Context.prototype.push = function push (view) { 404 return new Context(view, this); 405 }; 406 407 /** 408 * Set a value in the current block context. 409 */ 410 Context.prototype.setBlockVar = function set (name, value) { 411 var blocks = this.blocks; 412 413 blocks[name] = value; 414 415 return value; 416 }; 417 418 /** 419 * Clear all current block vars. 420 */ 421 Context.prototype.clearBlockVars = function clearBlockVars () { 422 this.blocks = {}; 423 }; 424 425 /** 426 * Get a value only from the current block context. 427 */ 428 Context.prototype.getBlockVar = function getBlockVar (name) { 429 var blocks = this.blocks; 430 431 var value; 432 if (blocks.hasOwnProperty(name)) { 433 value = blocks[name]; 434 } else { 435 if (this.parent) { 436 value = this.parent.getBlockVar(name); 437 } 438 } 439 // Can return undefined. 440 return value; 441 }; 442 443 /** 444 * Returns the value of the given name in this context, traversing 445 * up the context hierarchy if the value is absent in this context's view. 446 */ 447 Context.prototype.lookup = function lookup (name) { 448 var cache = this.cache; 449 450 var value; 451 if (cache.hasOwnProperty(name)) { 452 value = cache[name]; 453 } else { 454 var context = this, names, index, lookupHit = false; 455 456 while (context) { 457 if (name.indexOf('.') > 0) { 458 value = context.view; 459 names = name.split('.'); 460 index = 0; 461 462 /** 463 * Using the dot notion path in `name`, we descend through the 464 * nested objects. 465 * 466 * To be certain that the lookup has been successful, we have to 467 * check if the last object in the path actually has the property 468 * we are looking for. We store the result in `lookupHit`. 469 * 470 * This is specially necessary for when the value has been set to 471 * `undefined` and we want to avoid looking up parent contexts. 472 **/ 473 while (value != null && index < names.length) { 474 if (index === names.length - 1) 475 lookupHit = hasProperty(value, names[index]); 476 477 value = value[names[index++]]; 478 } 479 } else { 480 value = context.view[name]; 481 lookupHit = hasProperty(context.view, name); 482 } 483 484 if (lookupHit) 485 break; 486 487 context = context.parent; 488 } 489 490 cache[name] = value; 491 } 492 493 if (isFunction(value)) 494 value = value.call(this.view); 495 496 return value; 497 }; 498 499 /** 500 * A Writer knows how to take a stream of tokens and render them to a 501 * string, given a context. It also maintains a cache of templates to 502 * avoid the need to parse the same template twice. 503 */ 504 function Writer () { 505 this.cache = {}; 506 } 507 508 /** 509 * Clears all cached templates in this writer. 510 */ 511 Writer.prototype.clearCache = function clearCache () { 512 this.cache = {}; 513 }; 514 515 /** 516 * Parses and caches the given `template` and returns the array of tokens 517 * that is generated from the parse. 518 */ 519 Writer.prototype.parse = function parse (template, tags) { 520 var cache = this.cache; 521 var tokens = cache[template]; 522 523 if (tokens == null) 524 tokens = cache[template] = parseTemplate(template, tags); 525 526 return tokens; 527 }; 528 529 /** 530 * High-level method that is used to render the given `template` with 531 * the given `view`. 532 * 533 * The optional `partials` argument may be an object that contains the 534 * names and templates of partials that are used in the template. It may 535 * also be a function that is used to load partial templates on the fly 536 * that takes a single argument: the name of the partial. 537 */ 538 Writer.prototype.render = function render (template, view, partials) { 539 var tokens = this.parse(template); 540 var context = (view instanceof Context) ? view : new Context(view); 541 return this.renderTokens(tokens, context, partials, template); 542 }; 543 544 /** 545 * Low-level method that renders the given array of `tokens` using 546 * the given `context` and `partials`. 547 * 548 * Note: The `originalTemplate` is only ever used to extract the portion 549 * of the original template that was contained in a higher-order section. 550 * If the template doesn't use higher-order sections, this argument may 551 * be omitted. 552 */ 553 Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) { 554 var buffer = ''; 555 556 var token, symbol, value; 557 for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) { 558 value = undefined; 559 token = tokens[i]; 560 symbol = token[0]; 561 562 if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate); 563 else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate); 564 else if (symbol === '>') value = this.renderPartial(token, context, partials, originalTemplate); 565 else if (symbol === '<') value = this.renderBlock(token, context, partials, originalTemplate); 566 else if (symbol === '$') value = this.renderBlockVariable(token, context, partials, originalTemplate); 567 else if (symbol === '&') value = this.unescapedValue(token, context); 568 else if (symbol === 'name') value = this.escapedValue(token, context); 569 else if (symbol === 'text') value = this.rawValue(token); 570 571 if (value !== undefined) 572 buffer += value; 573 } 574 575 return buffer; 576 }; 577 578 Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) { 579 var self = this; 580 var buffer = ''; 581 var value = context.lookup(token[1]); 582 583 // This function is used to render an arbitrary template 584 // in the current context by higher-order sections. 585 function subRender (template) { 586 return self.render(template, context, partials); 587 } 588 589 if (!value) return; 590 591 if (isArray(value)) { 592 for (var j = 0, valueLength = value.length; j < valueLength; ++j) { 593 buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate); 594 } 595 } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') { 596 buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate); 597 } else if (isFunction(value)) { 598 if (typeof originalTemplate !== 'string') 599 throw new Error('Cannot use higher-order sections without the original template'); 600 601 // Extract the portion of the original template that the section contains. 602 value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender); 603 604 if (value != null) 605 buffer += value; 606 } else { 607 buffer += this.renderTokens(token[4], context, partials, originalTemplate); 608 } 609 return buffer; 610 }; 611 612 Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) { 613 var value = context.lookup(token[1]); 614 615 // Use JavaScript's definition of falsy. Include empty arrays. 616 // See https://github.com/janl/mustache.js/issues/186 617 if (!value || (isArray(value) && value.length === 0)) 618 return this.renderTokens(token[4], context, partials, originalTemplate); 619 }; 620 621 Writer.prototype.renderPartial = function renderPartial (token, context, partials) { 622 if (!partials) return; 623 624 var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; 625 if (value != null) 626 return this.renderTokens(this.parse(value), context, partials, value); 627 }; 628 629 Writer.prototype.renderBlock = function renderBlock (token, context, partials, originalTemplate) { 630 if (!partials) return; 631 632 var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; 633 if (value != null) 634 // Ignore any wrongly set block vars before we started. 635 context.clearBlockVars(); 636 // We are only rendering to record the default block variables. 637 this.renderTokens(token[4], context, partials, originalTemplate); 638 // Now we render and return the result. 639 var result = this.renderTokens(this.parse(value), context, partials, value); 640 // Don't leak the block variables outside this include. 641 context.clearBlockVars(); 642 return result; 643 }; 644 645 Writer.prototype.renderBlockVariable = function renderBlockVariable (token, context, partials, originalTemplate) { 646 var value = token[1]; 647 648 var exists = context.getBlockVar(value); 649 if (!exists) { 650 context.setBlockVar(value, originalTemplate.slice(token[3], token[5])); 651 return this.renderTokens(token[4], context, partials, originalTemplate); 652 } else { 653 return this.renderTokens(this.parse(exists), context, partials, exists); 654 } 655 }; 656 657 Writer.prototype.unescapedValue = function unescapedValue (token, context) { 658 var value = context.lookup(token[1]); 659 if (value != null) 660 return value; 661 }; 662 663 Writer.prototype.escapedValue = function escapedValue (token, context) { 664 var value = context.lookup(token[1]); 665 if (value != null) 666 return mustache.escape(value); 667 }; 668 669 Writer.prototype.rawValue = function rawValue (token) { 670 return token[1]; 671 }; 672 673 mustache.name = 'mustache.js'; 674 mustache.version = '2.2.1'; 675 mustache.tags = [ '{{', '}}' ]; 676 677 // All high-level mustache.* functions use this writer. 678 var defaultWriter = new Writer(); 679 680 /** 681 * Clears all cached templates in the default writer. 682 */ 683 mustache.clearCache = function clearCache () { 684 return defaultWriter.clearCache(); 685 }; 686 687 /** 688 * Parses and caches the given template in the default writer and returns the 689 * array of tokens it contains. Doing this ahead of time avoids the need to 690 * parse templates on the fly as they are rendered. 691 */ 692 mustache.parse = function parse (template, tags) { 693 return defaultWriter.parse(template, tags); 694 }; 695 696 /** 697 * Renders the `template` with the given `view` and `partials` using the 698 * default writer. 699 */ 700 mustache.render = function render (template, view, partials) { 701 if (typeof template !== 'string') { 702 throw new TypeError('Invalid template! Template should be a "string" ' + 703 'but "' + typeStr(template) + '" was given as the first ' + 704 'argument for mustache#render(template, view, partials)'); 705 } 706 707 return defaultWriter.render(template, view, partials); 708 }; 709 710 // This is here for backwards compatibility with 0.4.x., 711 /*eslint-disable */ // eslint wants camel cased function name 712 mustache.to_html = function to_html (template, view, partials, send) { 713 /*eslint-enable*/ 714 715 var result = mustache.render(template, view, partials); 716 717 if (isFunction(send)) { 718 send(result); 719 } else { 720 return result; 721 } 722 }; 723 724 // Export the escaping function so that the user may override it. 725 // See https://github.com/janl/mustache.js/issues/244 726 mustache.escape = escapeHtml; 727 728 // Export these mainly for testing, but also for advanced usage. 729 mustache.Scanner = Scanner; 730 mustache.Context = Context; 731 mustache.Writer = Writer; 732 733 })); 734 /* jshint ignore:end */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |