AutorÃa | Ultima modificación | Ver Log |
// The MIT License//// Copyright (c) 2009 Chris Wanstrath (Ruby)// Copyright (c) 2010-2014 Jan Lehnardt (JavaScript)// Copyright (c) 2010-2015 The mustache.js community//// Permission is hereby granted, free of charge, to any person obtaining// a copy of this software and associated documentation files (the// "Software"), to deal in the Software without restriction, including// without limitation the rights to use, copy, modify, merge, publish,// distribute, sublicense, and/or sell copies of the Software, and to// permit persons to whom the Software is furnished to do so, subject to// the following conditions://// The above copyright notice and this permission notice shall be// included in all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.//// Description of import into Moodle:// Checkout from https://github.com/moodle/custom-mustache.js Branch: LAMBDA_ARGS (see note below)// Rebase onto latest release tag from https://github.com/janl/mustache.js// Copy mustache.js into lib/amd/src/ in Moodle folder.// Add the license as a comment to the file and these instructions.// Make sure that you have not removed the custom code for '$' and '<'.// Run unit tests.// NOTE:// Check if pull request from branch lambdaUpgrade420 has been accepted// by moodle/custom-mustache.js repo. If not, create one and use lambdaUpgrade420// as your branch in place of LAMBDA_ARGS./*!* mustache.js - Logic-less {{mustache}} templates with JavaScript* http://github.com/janl/mustache.js*/var objectToString = Object.prototype.toString;var isArray = Array.isArray || function isArrayPolyfill (object) {return objectToString.call(object) === '[object Array]';};function isFunction (object) {return typeof object === 'function';}/*** More correct typeof string handling array* which normally returns typeof 'object'*/function typeStr (obj) {return isArray(obj) ? 'array' : typeof obj;}function escapeRegExp (string) {return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');}/*** Null safe way of checking whether or not an object,* including its prototype, has a given property*/function hasProperty (obj, propName) {return obj != null && typeof obj === 'object' && (propName in obj);}/*** Safe way of detecting whether or not the given thing is a primitive and* whether it has the given property*/function primitiveHasOwnProperty (primitive, propName) {return (primitive != null&& typeof primitive !== 'object'&& primitive.hasOwnProperty&& primitive.hasOwnProperty(propName));}// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577// See https://github.com/janl/mustache.js/issues/189var regExpTest = RegExp.prototype.test;function testRegExp (re, string) {return regExpTest.call(re, string);}var nonSpaceRe = /\S/;function isWhitespace (string) {return !testRegExp(nonSpaceRe, string);}var entityMap = {'&': '&','<': '<','>': '>','"': '"',"'": ''','/': '/','`': '`','=': '='};function escapeHtml (string) {return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {return entityMap[s];});}var whiteRe = /\s*/;var spaceRe = /\s+/;var equalsRe = /\s*=/;var curlyRe = /\s*\}/;var tagRe = /#|\^|\/|>|\{|&|=|!|\$|</;/*** Breaks up the given `template` string into a tree of tokens. If the `tags`* argument is given here it must be an array with two string values: the* opening and closing tags used in the template (e.g. [ "<%", "%>" ]). Of* course, the default is to use mustaches (i.e. mustache.tags).** A token is an array with at least 4 elements. The first element is the* mustache symbol that was used inside the tag, e.g. "#" or "&". If the tag* did not contain a symbol (i.e. {{myValue}}) this element is "name". For* all text that appears outside a symbol this element is "text".** The second element of a token is its "value". For mustache tags this is* whatever else was inside the tag besides the opening symbol. For text tokens* this is the text itself.** The third and fourth elements of the token are the start and end indices,* respectively, of the token in the original template.** Tokens that are the root node of a subtree contain two more elements: 1) an* array of tokens in the subtree and 2) the index in the original template at* which the closing tag for that section begins.** Tokens for partials also contain two more elements: 1) a string value of* indendation prior to that tag and 2) the index of that tag on that line -* eg a value of 2 indicates the partial is the third tag on this line.*/function parseTemplate (template, tags) {if (!template)return [];var lineHasNonSpace = false;var sections = []; // Stack to hold section tokensvar tokens = []; // Buffer to hold the tokensvar spaces = []; // Indices of whitespace tokens on the current linevar hasTag = false; // Is there a {{tag}} on the current line?var nonSpace = false; // Is there a non-space char on the current line?var indentation = ''; // Tracks indentation for tags that use itvar tagIndex = 0; // Stores a count of number of tags encountered on a line// Strips all whitespace tokens array for the current line// if there was a {{#tag}} on it and otherwise only space.function stripSpace () {if (hasTag && !nonSpace) {while (spaces.length)delete tokens[spaces.pop()];} else {spaces = [];}hasTag = false;nonSpace = false;}var openingTagRe, closingTagRe, closingCurlyRe;function compileTags (tagsToCompile) {if (typeof tagsToCompile === 'string')tagsToCompile = tagsToCompile.split(spaceRe, 2);if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)throw new Error('Invalid tags: ' + tagsToCompile);openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));}compileTags(tags || mustache.tags);var scanner = new Scanner(template);var start, type, value, chr, token, openSection, tagName, endTagName;while (!scanner.eos()) {start = scanner.pos;// Match any text between tags.value = scanner.scanUntil(openingTagRe);if (value) {for (var i = 0, valueLength = value.length; i < valueLength; ++i) {chr = value.charAt(i);if (isWhitespace(chr)) {spaces.push(tokens.length);indentation += chr;} else {nonSpace = true;lineHasNonSpace = true;indentation += ' ';}tokens.push([ 'text', chr, start, start + 1 ]);start += 1;// Check for whitespace on the current line.if (chr === '\n') {stripSpace();indentation = '';tagIndex = 0;lineHasNonSpace = false;}}}// Match the opening tag.if (!scanner.scan(openingTagRe))break;hasTag = true;// Get the tag type.type = scanner.scan(tagRe) || 'name';scanner.scan(whiteRe);// Get the tag value.if (type === '=') {value = scanner.scanUntil(equalsRe);scanner.scan(equalsRe);scanner.scanUntil(closingTagRe);} else if (type === '{') {value = scanner.scanUntil(closingCurlyRe);scanner.scan(curlyRe);scanner.scanUntil(closingTagRe);type = '&';} else {value = scanner.scanUntil(closingTagRe);}// Match the closing tag.if (!scanner.scan(closingTagRe))throw new Error('Unclosed tag at ' + scanner.pos);if (type == '>') {token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];} else {token = [ type, value, start, scanner.pos ];}tagIndex++;tokens.push(token);if (type === '#' || type === '^' || type === '$' || type === '<') {sections.push(token);} else if (type === '/') {// Check section nesting.openSection = sections.pop();if (!openSection)throw new Error('Unopened section "' + value + '" at ' + start);tagName = openSection[1].split(' ', 1)[0];endTagName = value.split(' ', 1)[0];if (tagName !== endTagName)throw new Error('Unclosed section "' + tagName + '" at ' + start);} else if (type === 'name' || type === '{' || type === '&') {nonSpace = true;} else if (type === '=') {// Set the tags for the next time around.compileTags(value);}}stripSpace();// Make sure there are no open sections when we're done.openSection = sections.pop();if (openSection)throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);return nestTokens(squashTokens(tokens));}/*** Combines the values of consecutive text tokens in the given `tokens` array* to a single token.*/function squashTokens (tokens) {var squashedTokens = [];var token, lastToken;for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {token = tokens[i];if (token) {if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {lastToken[1] += token[1];lastToken[3] = token[3];} else {squashedTokens.push(token);lastToken = token;}}}return squashedTokens;}/*** Forms the given array of `tokens` into a nested tree structure where* tokens that represent a section have two additional items: 1) an array of* all tokens that appear in that section and 2) the index in the original* template that represents the end of that section.*/function nestTokens (tokens) {var nestedTokens = [];var collector = nestedTokens;var sections = [];var token, section;for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {token = tokens[i];switch (token[0]) {case '$':case '<':case '#':case '^':collector.push(token);sections.push(token);collector = token[4] = [];break;case '/':section = sections.pop();section[5] = token[2];collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;break;default:collector.push(token);}}return nestedTokens;}/*** A simple string scanner that is used by the template parser to find* tokens in template strings.*/function Scanner (string) {this.string = string;this.tail = string;this.pos = 0;}/*** Returns `true` if the tail is empty (end of string).*/Scanner.prototype.eos = function eos () {return this.tail === '';};/*** Tries to match the given regular expression at the current position.* Returns the matched text if it can match, the empty string otherwise.*/Scanner.prototype.scan = function scan (re) {var match = this.tail.match(re);if (!match || match.index !== 0)return '';var string = match[0];this.tail = this.tail.substring(string.length);this.pos += string.length;return string;};/*** Skips all text until the given regular expression can be matched. Returns* the skipped string, which is the entire tail if no match can be made.*/Scanner.prototype.scanUntil = function scanUntil (re) {var index = this.tail.search(re), match;switch (index) {case -1:match = this.tail;this.tail = '';break;case 0:match = '';break;default:match = this.tail.substring(0, index);this.tail = this.tail.substring(index);}this.pos += match.length;return match;};/*** Represents a rendering context by wrapping a view object and* maintaining a reference to the parent context.*/function Context (view, parentContext) {this.view = view;this.blocks = {};this.cache = { '.': this.view };this.parent = parentContext;}/*** Creates a new context using the given view with this context* as the parent.*/Context.prototype.push = function push (view) {return new Context(view, this);};/*** Set a value in the current block context.*/Context.prototype.setBlockVar = function set (name, value) {var blocks = this.blocks;blocks[name] = value;return value;};/*** Clear all current block vars.*/Context.prototype.clearBlockVars = function clearBlockVars () {this.blocks = {};};/*** Get a value only from the current block context.*/Context.prototype.getBlockVar = function getBlockVar (name) {var blocks = this.blocks;var value;if (blocks.hasOwnProperty(name)) {value = blocks[name];} else {if (this.parent) {value = this.parent.getBlockVar(name);}}// Can return undefined.return value;};/*** Parse a tag name into an array of name and arguments (space separated, quoted strings allowed).*/Context.prototype.parseNameAndArgs = function parseNameAndArgs (name) {var parts = name.split(' ');var inString = false;var first = true;var i = 0;var arg;var unescapedArg;var argbuffer;var finalArgs = [];for (i = 0; i < parts.length; i++) {arg = parts[i];argbuffer = '';if (inString) {unescapedArg = arg.replace('\\\\', '');if (unescapedArg.search(/^"$|[^\\]"$/) !== -1) {finalArgs[finalArgs.length] = argbuffer + ' ' + arg.substr(0, arg.length - 1);argbuffer = '';inString = false;} else {argbuffer += ' ' + arg;}} else {if (arg.search(/^"/) !== -1 && !first) {unescapedArg = arg.replace('\\\\', '');if (unescapedArg.search(/^".*[^\\]"$/) !== -1) {finalArgs[finalArgs.length] = arg.substr(1, arg.length - 2);} else {inString = true;argbuffer = arg.substr(1);}} else {if (arg.search(/^\d+(\.\d*)?$/) !== -1) {finalArgs[finalArgs.length] = parseFloat(arg);} else if (arg === 'true') {finalArgs[finalArgs.length] = 1;} else if (arg === 'false') {finalArgs[finalArgs.length] = 0;} else if (first) {finalArgs[finalArgs.length] = arg;} else {finalArgs[finalArgs.length] = this.lookup(arg);}first = false;}}}return finalArgs;};/*** Returns the value of the given name in this context, traversing* up the context hierarchy if the value is absent in this context's view.*/Context.prototype.lookup = function lookup (name) {var cache = this.cache;var lambdaArgs = this.parseNameAndArgs(name);name= lambdaArgs.shift();var value;if (cache.hasOwnProperty(name)) {value = cache[name];} else {var context = this, intermediateValue, names, index, lookupHit = false;while (context) {if (name.indexOf('.') > 0) {intermediateValue = context.view;names = name.split('.');index = 0;/*** Using the dot notion path in `name`, we descend through the* nested objects.** To be certain that the lookup has been successful, we have to* check if the last object in the path actually has the property* we are looking for. We store the result in `lookupHit`.** This is specially necessary for when the value has been set to* `undefined` and we want to avoid looking up parent contexts.** In the case where dot notation is used, we consider the lookup* to be successful even if the last "object" in the path is* not actually an object but a primitive (e.g., a string, or an* integer), because it is sometimes useful to access a property* of an autoboxed primitive, such as the length of a string.**/while (intermediateValue != null && index < names.length) {if (index === names.length - 1)lookupHit = (hasProperty(intermediateValue, names[index])|| primitiveHasOwnProperty(intermediateValue, names[index]));intermediateValue = intermediateValue[names[index++]];}} else {intermediateValue = context.view[name];/*** Only checking against `hasProperty`, which always returns `false` if* `context.view` is not an object. Deliberately omitting the check* against `primitiveHasOwnProperty` if dot notation is not used.** Consider this example:* ```* Mustache.render("The length of a football field is {{#length}}{{length}}{{/length}}.", {length: "100 yards"})* ```** If we were to check also against `primitiveHasOwnProperty`, as we do* in the dot notation case, then render call would return:** "The length of a football field is 9."** rather than the expected:** "The length of a football field is 100 yards."**/lookupHit = hasProperty(context.view, name);}if (lookupHit) {value = intermediateValue;break;}context = context.parent;}cache[name] = value;}if (isFunction(value))value = value.call(this.view, lambdaArgs);return value;};/*** A Writer knows how to take a stream of tokens and render them to a* string, given a context. It also maintains a cache of templates to* avoid the need to parse the same template twice.*/function Writer () {this.templateCache = {_cache: {},set: function set (key, value) {this._cache[key] = value;},get: function get (key) {return this._cache[key];},clear: function clear () {this._cache = {};}};}/*** Clears all cached templates in this writer.*/Writer.prototype.clearCache = function clearCache () {if (typeof this.templateCache !== 'undefined') {this.templateCache.clear();}};/*** Parses and caches the given `template` according to the given `tags` or* `mustache.tags` if `tags` is omitted, and returns the array of tokens* that is generated from the parse.*/Writer.prototype.parse = function parse (template, tags) {var cache = this.templateCache;var cacheKey = template + ':' + (tags || mustache.tags).join(':');var isCacheEnabled = typeof cache !== 'undefined';var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;if (tokens == undefined) {tokens = parseTemplate(template, tags);isCacheEnabled && cache.set(cacheKey, tokens);}return tokens;};/*** High-level method that is used to render the given `template` with* the given `view`.** The optional `partials` argument may be an object that contains the* names and templates of partials that are used in the template. It may* also be a function that is used to load partial templates on the fly* that takes a single argument: the name of the partial.** If the optional `config` argument is given here, then it should be an* object with a `tags` attribute or an `escape` attribute or both.* If an array is passed, then it will be interpreted the same way as* a `tags` attribute on a `config` object.** The `tags` attribute of a `config` object must be an array with two* string values: the opening and closing tags used in the template (e.g.* [ "<%", "%>" ]). The default is to mustache.tags.** The `escape` attribute of a `config` object must be a function which* accepts a string as input and outputs a safely escaped string.* If an `escape` function is not provided, then an HTML-safe string* escaping function is used as the default.*/Writer.prototype.render = function render (template, view, partials, config) {var tags = this.getConfigTags(config);var tokens = this.parse(template, tags);var context = (view instanceof Context) ? view : new Context(view, undefined);return this.renderTokens(tokens, context, partials, template, config);};/*** Low-level method that renders the given array of `tokens` using* the given `context` and `partials`.** Note: The `originalTemplate` is only ever used to extract the portion* of the original template that was contained in a higher-order section.* If the template doesn't use higher-order sections, this argument may* be omitted.*/Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {var buffer = '';var token, symbol, value;for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {value = undefined;token = tokens[i];symbol = token[0];if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);else if (symbol === '>') value = this.renderPartial(token, context, partials, config);else if (symbol === '<') value = this.renderBlock(token, context, partials, originalTemplate, config);else if (symbol === '$') value = this.renderBlockVariable(token, context, partials, originalTemplate, config);else if (symbol === '&') value = this.unescapedValue(token, context);else if (symbol === 'name') value = this.escapedValue(token, context, config);else if (symbol === 'text') value = this.rawValue(token);if (value !== undefined)buffer += value;}return buffer;};Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {var self = this;var buffer = '';var lambdaArgs = context.parseNameAndArgs(token[1]);var name = lambdaArgs.shift();var value = context.lookup(name);// This function is used to render an arbitrary template// in the current context by higher-order sections.function subRender (template) {return self.render(template, context, partials, config);}if (!value) return;if (isArray(value)) {for (var j = 0, valueLength = value.length; j < valueLength; ++j) {buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);}} else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);} else if (isFunction(value)) {if (typeof originalTemplate !== 'string')throw new Error('Cannot use higher-order sections without the original template');// Extract the portion of the original template that the section contains.value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender, lambdaArgs);if (value != null)buffer += value;} else {buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);}return buffer;};Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {var value = context.lookup(token[1]);// Use JavaScript's definition of falsy. Include empty arrays.// See https://github.com/janl/mustache.js/issues/186if (!value || (isArray(value) && value.length === 0))return this.renderTokens(token[4], context, partials, originalTemplate, config);};Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {var filteredIndentation = indentation.replace(/[^ \t]/g, '');var partialByNl = partial.split('\n');for (var i = 0; i < partialByNl.length; i++) {if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {partialByNl[i] = filteredIndentation + partialByNl[i];}}return partialByNl.join('\n');};Writer.prototype.renderPartial = function renderPartial (token, context, partials, config) {if (!partials) return;var tags = this.getConfigTags(config);var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];if (value != null) {var lineHasNonSpace = token[6];var tagIndex = token[5];var indentation = token[4];var indentedValue = value;if (tagIndex == 0 && indentation) {indentedValue = this.indentPartial(value, indentation, lineHasNonSpace);}var tokens = this.parse(indentedValue, tags);return this.renderTokens(tokens, context, partials, indentedValue, config);}};Writer.prototype.renderBlock = function renderBlock (token, context, partials, originalTemplate, config) {if (!partials) return;var value = isFunction(partials) ? partials(token[1]) : partials[token[1]];if (value != null)// Ignore any wrongly set block vars before we started.context.clearBlockVars();// We are only rendering to record the default block variables.this.renderTokens(token[4], context, partials, originalTemplate, config);// Now we render and return the result.var result = this.renderTokens(this.parse(value), context, partials, value, config);// Don't leak the block variables outside this include.context.clearBlockVars();return result;};Writer.prototype.renderBlockVariable = function renderBlockVariable (token, context, partials, originalTemplate, config) {var value = token[1];var exists = context.getBlockVar(value);if (!exists) {context.setBlockVar(value, originalTemplate.slice(token[3], token[5]));return this.renderTokens(token[4], context, partials, originalTemplate, config);} else {return this.renderTokens(this.parse(exists), context, partials, exists, config);}};Writer.prototype.unescapedValue = function unescapedValue (token, context) {var value = context.lookup(token[1]);if (value != null)return value;};Writer.prototype.escapedValue = function escapedValue (token, context, config) {var escape = this.getConfigEscape(config) || mustache.escape;var value = context.lookup(token[1]);if (value != null)return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value);};Writer.prototype.rawValue = function rawValue (token) {return token[1];};Writer.prototype.getConfigTags = function getConfigTags (config) {if (isArray(config)) {return config;}else if (config && typeof config === 'object') {return config.tags;}else {return undefined;}};Writer.prototype.getConfigEscape = function getConfigEscape (config) {if (config && typeof config === 'object' && !isArray(config)) {return config.escape;}else {return undefined;}};var mustache = {name: 'mustache.js',version: '4.2.0',tags: [ '{{', '}}' ],clearCache: undefined,escape: undefined,parse: undefined,render: undefined,Scanner: undefined,Context: undefined,Writer: undefined,/*** Allows a user to override the default caching strategy, by providing an* object with set, get and clear methods. This can also be used to disable* the cache by setting it to the literal `undefined`.*/set templateCache (cache) {defaultWriter.templateCache = cache;},/*** Gets the default or overridden caching object from the default writer.*/get templateCache () {return defaultWriter.templateCache;}};// All high-level mustache.* functions use this writer.var defaultWriter = new Writer();/*** Clears all cached templates in the default writer.*/mustache.clearCache = function clearCache () {return defaultWriter.clearCache();};/*** Parses and caches the given template in the default writer and returns the* array of tokens it contains. Doing this ahead of time avoids the need to* parse templates on the fly as they are rendered.*/mustache.parse = function parse (template, tags) {return defaultWriter.parse(template, tags);};/*** Renders the `template` with the given `view`, `partials`, and `config`* using the default writer.*/mustache.render = function render (template, view, partials, config) {if (typeof template !== 'string') {throw new TypeError('Invalid template! Template should be a "string" ' +'but "' + typeStr(template) + '" was given as the first ' +'argument for mustache#render(template, view, partials)');}return defaultWriter.render(template, view, partials, config);};// Export the escaping function so that the user may override it.// See https://github.com/janl/mustache.js/issues/244mustache.escape = escapeHtml;// Export these mainly for testing, but also for advanced usage.mustache.Scanner = Scanner;mustache.Context = Context;mustache.Writer = Writer;export default mustache;