--- /dev/null
+/*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ */
+var Mustache = (typeof module !== "undefined" && module.exports) || {};
+
+(function (exports) {
+
+ exports.name = "mustache.js";
+ exports.version = "0.5.1-dev";
+ exports.tags = ["{{", "}}"];
+
+ exports.parse = parse;
+ exports.clearCache = clearCache;
+ exports.compile = compile;
+ exports.compilePartial = compilePartial;
+ exports.render = render;
+
+ exports.Scanner = Scanner;
+ exports.Context = Context;
+ exports.Renderer = Renderer;
+
+ // This is here for backwards compatibility with 0.4.x.
+ exports.to_html = function (template, view, partials, send) {
+ var result = render(template, view, partials);
+
+ if (typeof send === "function") {
+ send(result);
+ } else {
+ return result;
+ }
+ };
+
+ var whiteRe = /\s*/;
+ var spaceRe = /\s+/;
+ var nonSpaceRe = /\S/;
+ var eqRe = /\s*=/;
+ var curlyRe = /\s*\}/;
+ var tagRe = /#|\^|\/|>|\{|&|=|!/;
+
+ // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
+ // See https://github.com/janl/mustache.js/issues/189
+ function testRe(re, string) {
+ return RegExp.prototype.test.call(re, string);
+ }
+
+ function isWhitespace(string) {
+ return !testRe(nonSpaceRe, string);
+ }
+
+ var isArray = Array.isArray || function (obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+ };
+
+ // OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+ var jsCharsRe = /[\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\xFF\u2028\u2029]/gm;
+
+ function quote(text) {
+ var escaped = text.replace(jsCharsRe, function (c) {
+ return "\\u" + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
+ });
+
+ return '"' + escaped + '"';
+ }
+
+ function escapeRe(string) {
+ return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ }
+
+ var entityMap = {
+ "&": "&",
+ "<": "<",
+ ">": ">",
+ '"': '"',
+ "'": ''',
+ "/": '/'
+ };
+
+ function escapeHtml(string) {
+ return String(string).replace(/[&<>"'\/]/g, function (s) {
+ return entityMap[s];
+ });
+ }
+
+ // Export these utility functions.
+ exports.isWhitespace = isWhitespace;
+ exports.isArray = isArray;
+ exports.quote = quote;
+ exports.escapeRe = escapeRe;
+ exports.escapeHtml = escapeHtml;
+
+ 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 () {
+ return this.tail === "";
+ };
+
+ /**
+ * Tries to match the given regular expression at the current position.
+ * Returns the matched text if it can match, `null` otherwise.
+ */
+ Scanner.prototype.scan = function (re) {
+ var match = this.tail.match(re);
+
+ if (match && match.index === 0) {
+ this.tail = this.tail.substring(match[0].length);
+ this.pos += match[0].length;
+ return match[0];
+ }
+
+ return null;
+ };
+
+ /**
+ * Skips all text until the given regular expression can be matched. Returns
+ * the skipped string, which is the entire tail of this scanner if no match
+ * can be made.
+ */
+ Scanner.prototype.scanUntil = function (re) {
+ var match, pos = this.tail.search(re);
+
+ switch (pos) {
+ case -1:
+ match = this.tail;
+ this.pos += this.tail.length;
+ this.tail = "";
+ break;
+ case 0:
+ match = null;
+ break;
+ default:
+ match = this.tail.substring(0, pos);
+ this.tail = this.tail.substring(pos);
+ this.pos += pos;
+ }
+
+ return match;
+ };
+
+ function Context(view, parent) {
+ this.view = view;
+ this.parent = parent;
+ this.clearCache();
+ }
+
+ Context.make = function (view) {
+ return (view instanceof Context) ? view : new Context(view);
+ };
+
+ Context.prototype.clearCache = function () {
+ this._cache = {};
+ };
+
+ Context.prototype.push = function (view) {
+ return new Context(view, this);
+ };
+
+ Context.prototype.lookup = function (name) {
+ var value = this._cache[name];
+
+ if (!value) {
+ if (name === ".") {
+ value = this.view;
+ } else {
+ var context = this;
+
+ while (context) {
+ if (name.indexOf(".") > 0) {
+ var names = name.split("."), i = 0;
+
+ value = context.view;
+
+ while (value && i < names.length) {
+ value = value[names[i++]];
+ }
+ } else {
+ value = context.view[name];
+ }
+
+ if (value != null) {
+ break;
+ }
+
+ context = context.parent;
+ }
+ }
+
+ this._cache[name] = value;
+ }
+
+ if (typeof value === "function") {
+ value = value.call(this.view);
+ }
+
+ return value;
+ };
+
+ function Renderer() {
+ this.clearCache();
+ }
+
+ Renderer.prototype.clearCache = function () {
+ this._cache = {};
+ this._partialCache = {};
+ };
+
+ Renderer.prototype.compile = function (tokens, tags) {
+ var fn = compileTokens(tokens),
+ self = this;
+
+ return function (view) {
+ return fn(Context.make(view), self);
+ };
+ };
+
+ Renderer.prototype.compilePartial = function (name, tokens, tags) {
+ this._partialCache[name] = this.compile(tokens, tags);
+ return this._partialCache[name];
+ };
+
+ Renderer.prototype.render = function (template, view) {
+ var fn = this._cache[template];
+
+ if (!fn) {
+ fn = this.compile(template);
+ this._cache[template] = fn;
+ }
+
+ return fn(view);
+ };
+
+ Renderer.prototype._section = function (name, context, callback) {
+ var value = context.lookup(name);
+
+ switch (typeof value) {
+ case "object":
+ if (isArray(value)) {
+ var buffer = "";
+ for (var i = 0, len = value.length; i < len; ++i) {
+ buffer += callback(context.push(value[i]), this);
+ }
+ return buffer;
+ } else {
+ return callback(context.push(value), this);
+ }
+ break;
+ case "function":
+ var sectionText = callback(context, this), self = this;
+ var scopedRender = function (template) {
+ return self.render(template, context);
+ };
+ return value.call(context.view, sectionText, scopedRender) || "";
+ break;
+ default:
+ if (value) {
+ return callback(context, this);
+ }
+ }
+
+ return "";
+ };
+
+ Renderer.prototype._inverted = function (name, context, callback) {
+ var value = context.lookup(name);
+
+ // From the spec: inverted sections may render text once based on the
+ // inverse value of the key. That is, they will be rendered if the key
+ // doesn't exist, is false, or is an empty list.
+ if (value == null || value === false || (isArray(value) && value.length === 0)) {
+ return callback(context, this);
+ }
+
+ return "";
+ };
+
+ Renderer.prototype._partial = function (name, context) {
+ var fn = this._partialCache[name];
+
+ if (fn) {
+ return fn(context, this);
+ }
+
+ return "";
+ };
+
+ Renderer.prototype._name = function (name, context, escape) {
+ var value = context.lookup(name);
+
+ if (typeof value === "function") {
+ value = value.call(context.view);
+ }
+
+ var string = (value == null) ? "" : String(value);
+
+ if (escape) {
+ return escapeHtml(string);
+ }
+
+ return string;
+ };
+
+ /**
+ * Low-level function that compiles the given `tokens` into a
+ * function that accepts two arguments: a Context and a
+ * Renderer. Returns the body of the function as a string if
+ * `returnBody` is true.
+ */
+ function compileTokens(tokens, returnBody) {
+ if (typeof tokens === "string") {
+ tokens = parse(tokens);
+ }
+
+ var body = ['""'];
+ var token, method, escape;
+
+ for (var i = 0, len = tokens.length; i < len; ++i) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case "#":
+ case "^":
+ method = (token.type === "#") ? "_section" : "_inverted";
+ body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
+ " " + compileTokens(token.tokens, true) + "\n" +
+ "})");
+ break;
+ case "{":
+ case "&":
+ case "name":
+ escape = token.type === "name" ? "true" : "false";
+ body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
+ break;
+ case ">":
+ body.push("r._partial(" + quote(token.value) + ", c)");
+ break;
+ case "text":
+ body.push(quote(token.value));
+ break;
+ }
+ }
+
+ // Convert to a string body.
+ body = "return " + body.join(" + ") + ";";
+
+ // Good for debugging.
+ // console.log(body);
+
+ if (returnBody) {
+ return body;
+ }
+
+ // For great evil!
+ return new Function("c, r", body);
+ }
+
+ function escapeTags(tags) {
+ if (tags.length === 2) {
+ return [
+ new RegExp(escapeRe(tags[0]) + "\\s*"),
+ new RegExp("\\s*" + escapeRe(tags[1]))
+ ];
+ }
+
+ throw new Error("Invalid tags: " + tags.join(" "));
+ }
+
+ /**
+ * Forms the given linear array of `tokens` into a nested tree structure
+ * where tokens that represent a section have a "tokens" array property
+ * that contains all tokens that are in that section.
+ */
+ function nestTokens(tokens) {
+ var tree = [];
+ var collector = tree;
+ var sections = [];
+ var token, section;
+
+ for (var i = 0; i < tokens.length; ++i) {
+ token = tokens[i];
+
+ switch (token.type) {
+ case "#":
+ case "^":
+ token.tokens = [];
+ sections.push(token);
+ collector.push(token);
+ collector = token.tokens;
+ break;
+ case "/":
+ if (sections.length === 0) {
+ throw new Error("Unopened section: " + token.value);
+ }
+
+ section = sections.pop();
+
+ if (section.value !== token.value) {
+ throw new Error("Unclosed section: " + section.value);
+ }
+
+ if (sections.length > 0) {
+ collector = sections[sections.length - 1].tokens;
+ } else {
+ collector = tree;
+ }
+ break;
+ default:
+ collector.push(token);
+ }
+ }
+
+ // Make sure there were no open sections when we're done.
+ section = sections.pop();
+
+ if (section) {
+ throw new Error("Unclosed section: " + section.value);
+ }
+
+ return tree;
+ }
+
+ /**
+ * Combines the values of consecutive text tokens in the given `tokens` array
+ * to a single token.
+ */
+ function squashTokens(tokens) {
+ var lastToken;
+
+ for (var i = 0; i < tokens.length; ++i) {
+ var token = tokens[i];
+
+ if (lastToken && lastToken.type === "text" && token.type === "text") {
+ lastToken.value += token.value;
+ tokens.splice(i--, 1); // Remove this token from the array.
+ } else {
+ lastToken = token;
+ }
+ }
+ }
+
+ /**
+ * Breaks up the given `template` string into a tree of token objects. If
+ * `tags` 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).
+ */
+ function parse(template, tags) {
+ tags = tags || exports.tags;
+ var tagRes = escapeTags(tags);
+
+ var scanner = new Scanner(template);
+
+ var tokens = [], // Buffer to hold the tokens
+ spaces = [], // Indices of whitespace tokens on the current line
+ hasTag = false, // Is there a {{tag}} on the current line?
+ nonSpace = false; // Is there a non-space char on the current line?
+
+ // Strips all whitespace tokens array for the current line
+ // if there was a {{#tag}} on it and otherwise only space.
+ var stripSpace = function () {
+ if (hasTag && !nonSpace) {
+ while (spaces.length) {
+ tokens.splice(spaces.pop(), 1);
+ }
+ } else {
+ spaces = [];
+ }
+
+ hasTag = false;
+ nonSpace = false;
+ };
+
+ var type, value, chr;
+
+ while (!scanner.eos()) {
+ value = scanner.scanUntil(tagRes[0]);
+
+ if (value) {
+ for (var i = 0, len = value.length; i < len; ++i) {
+ chr = value[i];
+
+ if (isWhitespace(chr)) {
+ spaces.push(tokens.length);
+ } else {
+ nonSpace = true;
+ }
+
+ tokens.push({type: "text", value: chr});
+
+ if (chr === "\n") {
+ stripSpace(); // Check for whitespace on the current line.
+ }
+ }
+ }
+
+ // Match the opening tag.
+ if (!scanner.scan(tagRes[0])) {
+ break;
+ }
+
+ hasTag = true;
+ type = scanner.scan(tagRe) || "name";
+
+ // Skip any whitespace between tag and value.
+ scanner.scan(whiteRe);
+
+ // Extract the tag value.
+ if (type === "=") {
+ value = scanner.scanUntil(eqRe);
+ scanner.scan(eqRe);
+ scanner.scanUntil(tagRes[1]);
+ } else if (type === "{") {
+ var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1]));
+ value = scanner.scanUntil(closeRe);
+ scanner.scan(curlyRe);
+ scanner.scanUntil(tagRes[1]);
+ } else {
+ value = scanner.scanUntil(tagRes[1]);
+ }
+
+ // Match the closing tag.
+ if (!scanner.scan(tagRes[1])) {
+ throw new Error("Unclosed tag at " + scanner.pos);
+ }
+
+ tokens.push({type: type, value: value});
+
+ if (type === "name" || type === "{" || type === "&") {
+ nonSpace = true;
+ }
+
+ // Set the tags for the next time around.
+ if (type === "=") {
+ tags = value.split(spaceRe);
+ tagRes = escapeTags(tags);
+ }
+ }
+
+ squashTokens(tokens);
+
+ return nestTokens(tokens);
+ }
+
+ // The high-level clearCache, compile, compilePartial, and render functions
+ // use this default renderer.
+ var _renderer = new Renderer;
+
+ /**
+ * Clears all cached templates and partials.
+ */
+ function clearCache() {
+ _renderer.clearCache();
+ }
+
+ /**
+ * High-level API for compiling the given `tokens` down to a reusable
+ * function. If `tokens` is a string it will be parsed using the given `tags`
+ * before it is compiled.
+ */
+ function compile(tokens, tags) {
+ return _renderer.compile(tokens, tags);
+ }
+
+ /**
+ * High-level API for compiling the `tokens` for the partial with the given
+ * `name` down to a reusable function. If `tokens` is a string it will be
+ * parsed using the given `tags` before it is compiled.
+ */
+ function compilePartial(name, tokens, tags) {
+ return _renderer.compilePartial(name, tokens, tags);
+ }
+
+ /**
+ * High-level API for rendering the `template` using the given `view`. The
+ * optional `partials` object may be given here for convenience, but note that
+ * it will cause all partials to be re-compiled, thus hurting performance. Of
+ * course, this only matters if you're going to render the same template more
+ * than once. If so, it is best to call `compilePartial` before calling this
+ * function and to leave the `partials` argument blank.
+ */
+ function render(template, view, partials) {
+ if (partials) {
+ for (var name in partials) {
+ compilePartial(name, partials[name]);
+ }
+ }
+
+ return _renderer.render(template, view);
+ }
+
+})(Mustache);