summaryrefslogtreecommitdiff
path: root/node_modules/twig/twig.js
diff options
context:
space:
mode:
authorMarvin Borner2018-11-07 18:02:36 +0100
committerMarvin Borner2018-11-07 18:02:36 +0100
commit824a2d9f587ca017fc71b84d835e72f54f9c87c4 (patch)
tree765267ea4686f752aad1f69930cfee5680cc494a /node_modules/twig/twig.js
parentfe75612e86b493a4e66c4e104e22658679cc014f (diff)
Began rewrite
Diffstat (limited to 'node_modules/twig/twig.js')
-rw-r--r--node_modules/twig/twig.js7223
1 files changed, 7223 insertions, 0 deletions
diff --git a/node_modules/twig/twig.js b/node_modules/twig/twig.js
new file mode 100644
index 0000000..d860b59
--- /dev/null
+++ b/node_modules/twig/twig.js
@@ -0,0 +1,7223 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory((function webpackLoadOptionalExternalModule() { try { return require("fs"); } catch(e) {} }()), require("path"));
+ else if(typeof define === 'function' && define.amd)
+ define(["fs", "path"], factory);
+ else if(typeof exports === 'object')
+ exports["Twig"] = factory((function webpackLoadOptionalExternalModule() { try { return require("fs"); } catch(e) {} }()), require("path"));
+ else
+ root["Twig"] = factory(root["fs"], root["path"]);
+})(this, function(__WEBPACK_EXTERNAL_MODULE_19__, __WEBPACK_EXTERNAL_MODULE_20__) {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * Twig.js
+ *
+ * @copyright 2011-2016 John Roepke and the Twig.js Contributors
+ * @license Available under the BSD 2-Clause License
+ * @link https://github.com/twigjs/twig.js
+ */
+
+ var Twig = {
+ VERSION: '0.10.2'
+ };
+
+ __webpack_require__(1)(Twig);
+ __webpack_require__(2)(Twig);
+ __webpack_require__(3)(Twig);
+ __webpack_require__(5)(Twig);
+ __webpack_require__(6)(Twig);
+ __webpack_require__(7)(Twig);
+ __webpack_require__(17)(Twig);
+ __webpack_require__(18)(Twig);
+ __webpack_require__(21)(Twig);
+ __webpack_require__(22)(Twig);
+ __webpack_require__(23)(Twig);
+ __webpack_require__(24)(Twig);
+ __webpack_require__(25)(Twig);
+ __webpack_require__(26)(Twig);
+
+ module.exports = Twig.exports;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ // ## twig.core.js
+ //
+ // This file handles template level tokenizing, compiling and parsing.
+ module.exports = function (Twig) {
+ "use strict";
+
+ Twig.trace = false;
+ Twig.debug = false;
+
+ // Default caching to true for the improved performance it offers
+ Twig.cache = true;
+
+ Twig.placeholders = {
+ parent: "{{|PARENT|}}"
+ };
+
+ /**
+ * Fallback for Array.indexOf for IE8 et al
+ */
+ Twig.indexOf = function (arr, searchElement /*, fromIndex */ ) {
+ if (Array.prototype.hasOwnProperty("indexOf")) {
+ return arr.indexOf(searchElement);
+ }
+ if (arr === void 0 || arr === null) {
+ throw new TypeError();
+ }
+ var t = Object(arr);
+ var len = t.length >>> 0;
+ if (len === 0) {
+ return -1;
+ }
+ var n = 0;
+ if (arguments.length > 0) {
+ n = Number(arguments[1]);
+ if (n !== n) { // shortcut for verifying if it's NaN
+ n = 0;
+ } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
+ n = (n > 0 || -1) * Math.floor(Math.abs(n));
+ }
+ }
+ if (n >= len) {
+ // console.log("indexOf not found1 ", JSON.stringify(searchElement), JSON.stringify(arr));
+ return -1;
+ }
+ var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
+ for (; k < len; k++) {
+ if (k in t && t[k] === searchElement) {
+ return k;
+ }
+ }
+ if (arr == searchElement) {
+ return 0;
+ }
+ // console.log("indexOf not found2 ", JSON.stringify(searchElement), JSON.stringify(arr));
+
+ return -1;
+ }
+
+ Twig.forEach = function (arr, callback, thisArg) {
+ if (Array.prototype.forEach ) {
+ return arr.forEach(callback, thisArg);
+ }
+
+ var T, k;
+
+ if ( arr == null ) {
+ throw new TypeError( " this is null or not defined" );
+ }
+
+ // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
+ var O = Object(arr);
+
+ // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
+ // 3. Let len be ToUint32(lenValue).
+ var len = O.length >>> 0; // Hack to convert O.length to a UInt32
+
+ // 4. If IsCallable(callback) is false, throw a TypeError exception.
+ // See: http://es5.github.com/#x9.11
+ if ( {}.toString.call(callback) != "[object Function]" ) {
+ throw new TypeError( callback + " is not a function" );
+ }
+
+ // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
+ if ( thisArg ) {
+ T = thisArg;
+ }
+
+ // 6. Let k be 0
+ k = 0;
+
+ // 7. Repeat, while k < len
+ while( k < len ) {
+
+ var kValue;
+
+ // a. Let Pk be ToString(k).
+ // This is implicit for LHS operands of the in operator
+ // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
+ // This step can be combined with c
+ // c. If kPresent is true, then
+ if ( k in O ) {
+
+ // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
+ kValue = O[ k ];
+
+ // ii. Call the Call internal method of callback with T as the this value and
+ // argument list containing kValue, k, and O.
+ callback.call( T, kValue, k, O );
+ }
+ // d. Increase k by 1.
+ k++;
+ }
+ // 8. return undefined
+ };
+
+ Twig.merge = function(target, source, onlyChanged) {
+ Twig.forEach(Object.keys(source), function (key) {
+ if (onlyChanged && !(key in target)) {
+ return;
+ }
+
+ target[key] = source[key]
+ });
+
+ return target;
+ };
+
+ /**
+ * Exception thrown by twig.js.
+ */
+ Twig.Error = function(message) {
+ this.message = message;
+ this.name = "TwigException";
+ this.type = "TwigException";
+ };
+
+ /**
+ * Get the string representation of a Twig error.
+ */
+ Twig.Error.prototype.toString = function() {
+ var output = this.name + ": " + this.message;
+
+ return output;
+ };
+
+ /**
+ * Wrapper for logging to the console.
+ */
+ Twig.log = {
+ trace: function() {if (Twig.trace && console) {console.log(Array.prototype.slice.call(arguments));}},
+ debug: function() {if (Twig.debug && console) {console.log(Array.prototype.slice.call(arguments));}}
+ };
+
+
+ if (typeof console !== "undefined") {
+ if (typeof console.error !== "undefined") {
+ Twig.log.error = function() {
+ console.error.apply(console, arguments);
+ }
+ } else if (typeof console.log !== "undefined") {
+ Twig.log.error = function() {
+ console.log.apply(console, arguments);
+ }
+ }
+ } else {
+ Twig.log.error = function(){};
+ }
+
+ /**
+ * Wrapper for child context objects in Twig.
+ *
+ * @param {Object} context Values to initialize the context with.
+ */
+ Twig.ChildContext = function(context) {
+ var ChildContext = function ChildContext() {};
+ ChildContext.prototype = context;
+ return new ChildContext();
+ };
+
+ /**
+ * Container for methods related to handling high level template tokens
+ * (for example: {{ expression }}, {% logic %}, {# comment #}, raw data)
+ */
+ Twig.token = {};
+
+ /**
+ * Token types.
+ */
+ Twig.token.type = {
+ output: 'output',
+ logic: 'logic',
+ comment: 'comment',
+ raw: 'raw',
+ output_whitespace_pre: 'output_whitespace_pre',
+ output_whitespace_post: 'output_whitespace_post',
+ output_whitespace_both: 'output_whitespace_both',
+ logic_whitespace_pre: 'logic_whitespace_pre',
+ logic_whitespace_post: 'logic_whitespace_post',
+ logic_whitespace_both: 'logic_whitespace_both'
+ };
+
+ /**
+ * Token syntax definitions.
+ */
+ Twig.token.definitions = [
+ {
+ type: Twig.token.type.raw,
+ open: '{% raw %}',
+ close: '{% endraw %}'
+ },
+ {
+ type: Twig.token.type.raw,
+ open: '{% verbatim %}',
+ close: '{% endverbatim %}'
+ },
+ // *Whitespace type tokens*
+ //
+ // These typically take the form `{{- expression -}}` or `{{- expression }}` or `{{ expression -}}`.
+ {
+ type: Twig.token.type.output_whitespace_pre,
+ open: '{{-',
+ close: '}}'
+ },
+ {
+ type: Twig.token.type.output_whitespace_post,
+ open: '{{',
+ close: '-}}'
+ },
+ {
+ type: Twig.token.type.output_whitespace_both,
+ open: '{{-',
+ close: '-}}'
+ },
+ {
+ type: Twig.token.type.logic_whitespace_pre,
+ open: '{%-',
+ close: '%}'
+ },
+ {
+ type: Twig.token.type.logic_whitespace_post,
+ open: '{%',
+ close: '-%}'
+ },
+ {
+ type: Twig.token.type.logic_whitespace_both,
+ open: '{%-',
+ close: '-%}'
+ },
+ // *Output type tokens*
+ //
+ // These typically take the form `{{ expression }}`.
+ {
+ type: Twig.token.type.output,
+ open: '{{',
+ close: '}}'
+ },
+ // *Logic type tokens*
+ //
+ // These typically take a form like `{% if expression %}` or `{% endif %}`
+ {
+ type: Twig.token.type.logic,
+ open: '{%',
+ close: '%}'
+ },
+ // *Comment type tokens*
+ //
+ // These take the form `{# anything #}`
+ {
+ type: Twig.token.type.comment,
+ open: '{#',
+ close: '#}'
+ }
+ ];
+
+
+ /**
+ * What characters start "strings" in token definitions. We need this to ignore token close
+ * strings inside an expression.
+ */
+ Twig.token.strings = ['"', "'"];
+
+ Twig.token.findStart = function (template) {
+ var output = {
+ position: null,
+ close_position: null,
+ def: null
+ },
+ i,
+ token_template,
+ first_key_position,
+ close_key_position;
+
+ for (i=0;i<Twig.token.definitions.length;i++) {
+ token_template = Twig.token.definitions[i];
+ first_key_position = template.indexOf(token_template.open);
+ close_key_position = template.indexOf(token_template.close);
+
+ Twig.log.trace("Twig.token.findStart: ", "Searching for ", token_template.open, " found at ", first_key_position);
+
+ //Special handling for mismatched tokens
+ if (first_key_position >= 0) {
+ //This token matches the template
+ if (token_template.open.length !== token_template.close.length) {
+ //This token has mismatched closing and opening tags
+ if (close_key_position < 0) {
+ //This token's closing tag does not match the template
+ continue;
+ }
+ }
+ }
+ // Does this token occur before any other types?
+ if (first_key_position >= 0 && (output.position === null || first_key_position < output.position)) {
+ output.position = first_key_position;
+ output.def = token_template;
+ output.close_position = close_key_position;
+ } else if (first_key_position >= 0 && output.position !== null && first_key_position === output.position) {
+ /*This token exactly matches another token,
+ greedily match to check if this token has a greater specificity*/
+ if (token_template.open.length > output.def.open.length) {
+ //This token's opening tag is more specific than the previous match
+ output.position = first_key_position;
+ output.def = token_template;
+ output.close_position = close_key_position;
+ } else if (token_template.open.length === output.def.open.length) {
+ if (token_template.close.length > output.def.close.length) {
+ //This token's opening tag is as specific as the previous match,
+ //but the closing tag has greater specificity
+ if (close_key_position >= 0 && close_key_position < output.close_position) {
+ //This token's closing tag exists in the template,
+ //and it occurs sooner than the previous match
+ output.position = first_key_position;
+ output.def = token_template;
+ output.close_position = close_key_position;
+ }
+ } else if (close_key_position >= 0 && close_key_position < output.close_position) {
+ //This token's closing tag is not more specific than the previous match,
+ //but it occurs sooner than the previous match
+ output.position = first_key_position;
+ output.def = token_template;
+ output.close_position = close_key_position;
+ }
+ }
+ }
+ }
+
+ delete output['close_position'];
+
+ return output;
+ };
+
+ Twig.token.findEnd = function (template, token_def, start) {
+ var end = null,
+ found = false,
+ offset = 0,
+
+ // String position variables
+ str_pos = null,
+ str_found = null,
+ pos = null,
+ end_offset = null,
+ this_str_pos = null,
+ end_str_pos = null,
+
+ // For loop variables
+ i,
+ l;
+
+ while (!found) {
+ str_pos = null;
+ str_found = null;
+ pos = template.indexOf(token_def.close, offset);
+
+ if (pos >= 0) {
+ end = pos;
+ found = true;
+ } else {
+ // throw an exception
+ throw new Twig.Error("Unable to find closing bracket '" + token_def.close +
+ "'" + " opened near template position " + start);
+ }
+
+ // Ignore quotes within comments; just look for the next comment close sequence,
+ // regardless of what comes before it. https://github.com/justjohn/twig.js/issues/95
+ if (token_def.type === Twig.token.type.comment) {
+ break;
+ }
+ // Ignore quotes within raw tag
+ // Fixes #283
+ if (token_def.type === Twig.token.type.raw) {
+ break;
+ }
+
+ l = Twig.token.strings.length;
+ for (i = 0; i < l; i += 1) {
+ this_str_pos = template.indexOf(Twig.token.strings[i], offset);
+
+ if (this_str_pos > 0 && this_str_pos < pos &&
+ (str_pos === null || this_str_pos < str_pos)) {
+ str_pos = this_str_pos;
+ str_found = Twig.token.strings[i];
+ }
+ }
+
+ // We found a string before the end of the token, now find the string's end and set the search offset to it
+ if (str_pos !== null) {
+ end_offset = str_pos + 1;
+ end = null;
+ found = false;
+ while (true) {
+ end_str_pos = template.indexOf(str_found, end_offset);
+ if (end_str_pos < 0) {
+ throw "Unclosed string in template";
+ }
+ // Ignore escaped quotes
+ if (template.substr(end_str_pos - 1, 1) !== "\\") {
+ offset = end_str_pos + 1;
+ break;
+ } else {
+ end_offset = end_str_pos + 1;
+ }
+ }
+ }
+ }
+ return end;
+ };
+
+ /**
+ * Convert a template into high-level tokens.
+ */
+ Twig.tokenize = function (template) {
+ var tokens = [],
+ // An offset for reporting errors locations in the template.
+ error_offset = 0,
+
+ // The start and type of the first token found in the template.
+ found_token = null,
+ // The end position of the matched token.
+ end = null;
+
+ while (template.length > 0) {
+ // Find the first occurance of any token type in the template
+ found_token = Twig.token.findStart(template);
+
+ Twig.log.trace("Twig.tokenize: ", "Found token: ", found_token);
+
+ if (found_token.position !== null) {
+ // Add a raw type token for anything before the start of the token
+ if (found_token.position > 0) {
+ tokens.push({
+ type: Twig.token.type.raw,
+ value: template.substring(0, found_token.position)
+ });
+ }
+ template = template.substr(found_token.position + found_token.def.open.length);
+ error_offset += found_token.position + found_token.def.open.length;
+
+ // Find the end of the token
+ end = Twig.token.findEnd(template, found_token.def, error_offset);
+
+ Twig.log.trace("Twig.tokenize: ", "Token ends at ", end);
+
+ tokens.push({
+ type: found_token.def.type,
+ value: template.substring(0, end).trim()
+ });
+
+ if (template.substr( end + found_token.def.close.length, 1 ) === "\n") {
+ switch (found_token.def.type) {
+ case "logic_whitespace_pre":
+ case "logic_whitespace_post":
+ case "logic_whitespace_both":
+ case "logic":
+ // Newlines directly after logic tokens are ignored
+ end += 1;
+ break;
+ }
+ }
+
+ template = template.substr(end + found_token.def.close.length);
+
+ // Increment the position in the template
+ error_offset += end + found_token.def.close.length;
+
+ } else {
+ // No more tokens -> add the rest of the template as a raw-type token
+ tokens.push({
+ type: Twig.token.type.raw,
+ value: template
+ });
+ template = '';
+ }
+ }
+
+ return tokens;
+ };
+
+
+ Twig.compile = function (tokens) {
+ try {
+
+ // Output and intermediate stacks
+ var output = [],
+ stack = [],
+ // The tokens between open and close tags
+ intermediate_output = [],
+
+ token = null,
+ logic_token = null,
+ unclosed_token = null,
+ // Temporary previous token.
+ prev_token = null,
+ // Temporary previous output.
+ prev_output = null,
+ // Temporary previous intermediate output.
+ prev_intermediate_output = null,
+ // The previous token's template
+ prev_template = null,
+ // Token lookahead
+ next_token = null,
+ // The output token
+ tok_output = null,
+
+ // Logic Token values
+ type = null,
+ open = null,
+ next = null;
+
+ var compile_output = function(token) {
+ Twig.expression.compile.apply(this, [token]);
+ if (stack.length > 0) {
+ intermediate_output.push(token);
+ } else {
+ output.push(token);
+ }
+ };
+
+ var compile_logic = function(token) {
+ // Compile the logic token
+ logic_token = Twig.logic.compile.apply(this, [token]);
+
+ type = logic_token.type;
+ open = Twig.logic.handler[type].open;
+ next = Twig.logic.handler[type].next;
+
+ Twig.log.trace("Twig.compile: ", "Compiled logic token to ", logic_token,
+ " next is: ", next, " open is : ", open);
+
+ // Not a standalone token, check logic stack to see if this is expected
+ if (open !== undefined && !open) {
+ prev_token = stack.pop();
+ prev_template = Twig.logic.handler[prev_token.type];
+
+ if (Twig.indexOf(prev_template.next, type) < 0) {
+ throw new Error(type + " not expected after a " + prev_token.type);
+ }
+
+ prev_token.output = prev_token.output || [];
+
+ prev_token.output = prev_token.output.concat(intermediate_output);
+ intermediate_output = [];
+
+ tok_output = {
+ type: Twig.token.type.logic,
+ token: prev_token
+ };
+ if (stack.length > 0) {
+ intermediate_output.push(tok_output);
+ } else {
+ output.push(tok_output);
+ }
+ }
+
+ // This token requires additional tokens to complete the logic structure.
+ if (next !== undefined && next.length > 0) {
+ Twig.log.trace("Twig.compile: ", "Pushing ", logic_token, " to logic stack.");
+
+ if (stack.length > 0) {
+ // Put any currently held output into the output list of the logic operator
+ // currently at the head of the stack before we push a new one on.
+ prev_token = stack.pop();
+ prev_token.output = prev_token.output || [];
+ prev_token.output = prev_token.output.concat(intermediate_output);
+ stack.push(prev_token);
+ intermediate_output = [];
+ }
+
+ // Push the new logic token onto the logic stack
+ stack.push(logic_token);
+
+ } else if (open !== undefined && open) {
+ tok_output = {
+ type: Twig.token.type.logic,
+ token: logic_token
+ };
+ // Standalone token (like {% set ... %}
+ if (stack.length > 0) {
+ intermediate_output.push(tok_output);
+ } else {
+ output.push(tok_output);
+ }
+ }
+ };
+
+ while (tokens.length > 0) {
+ token = tokens.shift();
+ prev_output = output[output.length - 1];
+ prev_intermediate_output = intermediate_output[intermediate_output.length - 1];
+ next_token = tokens[0];
+ Twig.log.trace("Compiling token ", token);
+ switch (token.type) {
+ case Twig.token.type.raw:
+ if (stack.length > 0) {
+ intermediate_output.push(token);
+ } else {
+ output.push(token);
+ }
+ break;
+
+ case Twig.token.type.logic:
+ compile_logic.call(this, token);
+ break;
+
+ // Do nothing, comments should be ignored
+ case Twig.token.type.comment:
+ break;
+
+ case Twig.token.type.output:
+ compile_output.call(this, token);
+ break;
+
+ //Kill whitespace ahead and behind this token
+ case Twig.token.type.logic_whitespace_pre:
+ case Twig.token.type.logic_whitespace_post:
+ case Twig.token.type.logic_whitespace_both:
+ case Twig.token.type.output_whitespace_pre:
+ case Twig.token.type.output_whitespace_post:
+ case Twig.token.type.output_whitespace_both:
+ if (token.type !== Twig.token.type.output_whitespace_post && token.type !== Twig.token.type.logic_whitespace_post) {
+ if (prev_output) {
+ //If the previous output is raw, pop it off
+ if (prev_output.type === Twig.token.type.raw) {
+ output.pop();
+
+ //If the previous output is not just whitespace, trim it
+ if (prev_output.value.match(/^\s*$/) === null) {
+ prev_output.value = prev_output.value.trim();
+ //Repush the previous output
+ output.push(prev_output);
+ }
+ }
+ }
+
+ if (prev_intermediate_output) {
+ //If the previous intermediate output is raw, pop it off
+ if (prev_intermediate_output.type === Twig.token.type.raw) {
+ intermediate_output.pop();
+
+ //If the previous output is not just whitespace, trim it
+ if (prev_intermediate_output.value.match(/^\s*$/) === null) {
+ prev_intermediate_output.value = prev_intermediate_output.value.trim();
+ //Repush the previous intermediate output
+ intermediate_output.push(prev_intermediate_output);
+ }
+ }
+ }
+ }
+
+ //Compile this token
+ switch (token.type) {
+ case Twig.token.type.output_whitespace_pre:
+ case Twig.token.type.output_whitespace_post:
+ case Twig.token.type.output_whitespace_both:
+ compile_output.call(this, token);
+ break;
+ case Twig.token.type.logic_whitespace_pre:
+ case Twig.token.type.logic_whitespace_post:
+ case Twig.token.type.logic_whitespace_both:
+ compile_logic.call(this, token);
+ break;
+ }
+
+ if (token.type !== Twig.token.type.output_whitespace_pre && token.type !== Twig.token.type.logic_whitespace_pre) {
+ if (next_token) {
+ //If the next token is raw, shift it out
+ if (next_token.type === Twig.token.type.raw) {
+ tokens.shift();
+
+ //If the next token is not just whitespace, trim it
+ if (next_token.value.match(/^\s*$/) === null) {
+ next_token.value = next_token.value.trim();
+ //Unshift the next token
+ tokens.unshift(next_token);
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ Twig.log.trace("Twig.compile: ", " Output: ", output,
+ " Logic Stack: ", stack,
+ " Pending Output: ", intermediate_output );
+ }
+
+ // Verify that there are no logic tokens left in the stack.
+ if (stack.length > 0) {
+ unclosed_token = stack.pop();
+ throw new Error("Unable to find an end tag for " + unclosed_token.type +
+ ", expecting one of " + unclosed_token.next);
+ }
+ return output;
+ } catch (ex) {
+ if (this.options.rethrow) {
+ throw ex
+ }
+ else {
+ Twig.log.error("Error compiling twig template " + this.id + ": ");
+ if (ex.stack) {
+ Twig.log.error(ex.stack);
+ } else {
+ Twig.log.error(ex.toString());
+ }
+ }
+ }
+ };
+
+ /**
+ * Parse a compiled template.
+ *
+ * @param {Array} tokens The compiled tokens.
+ * @param {Object} context The render context.
+ *
+ * @return {string} The parsed template.
+ */
+ Twig.parse = function (tokens, context) {
+ try {
+ var output = [],
+ // Track logic chains
+ chain = true,
+ that = this;
+
+ Twig.forEach(tokens, function parseToken(token) {
+ Twig.log.debug("Twig.parse: ", "Parsing token: ", token);
+
+ switch (token.type) {
+ case Twig.token.type.raw:
+ output.push(Twig.filters.raw(token.value));
+ break;
+
+ case Twig.token.type.logic:
+ var logic_token = token.token,
+ logic = Twig.logic.parse.apply(that, [logic_token, context, chain]);
+
+ if (logic.chain !== undefined) {
+ chain = logic.chain;
+ }
+ if (logic.context !== undefined) {
+ context = logic.context;
+ }
+ if (logic.output !== undefined) {
+ output.push(logic.output);
+ }
+ break;
+
+ case Twig.token.type.comment:
+ // Do nothing, comments should be ignored
+ break;
+
+ //Fall through whitespace to output
+ case Twig.token.type.output_whitespace_pre:
+ case Twig.token.type.output_whitespace_post:
+ case Twig.token.type.output_whitespace_both:
+ case Twig.token.type.output:
+ Twig.log.debug("Twig.parse: ", "Output token: ", token.stack);
+ // Parse the given expression in the given context
+ output.push(Twig.expression.parse.apply(that, [token.stack, context]));
+ break;
+ }
+ });
+ return Twig.output.apply(this, [output]);
+ } catch (ex) {
+ if (this.options.rethrow) {
+ throw ex;
+ }
+ else {
+ Twig.log.error("Error parsing twig template " + this.id + ": ");
+ if (ex.stack) {
+ Twig.log.error(ex.stack);
+ } else {
+ Twig.log.error(ex.toString());
+ }
+
+ if (Twig.debug) {
+ return ex.toString();
+ }
+ }
+ }
+ };
+
+ /**
+ * Tokenize and compile a string template.
+ *
+ * @param {string} data The template.
+ *
+ * @return {Array} The compiled tokens.
+ */
+ Twig.prepare = function(data) {
+ var tokens, raw_tokens;
+
+ // Tokenize
+ Twig.log.debug("Twig.prepare: ", "Tokenizing ", data);
+ raw_tokens = Twig.tokenize.apply(this, [data]);
+
+ // Compile
+ Twig.log.debug("Twig.prepare: ", "Compiling ", raw_tokens);
+ tokens = Twig.compile.apply(this, [raw_tokens]);
+
+ Twig.log.debug("Twig.prepare: ", "Compiled ", tokens);
+
+ return tokens;
+ };
+
+ /**
+ * Join the output token's stack and escape it if needed
+ *
+ * @param {Array} Output token's stack
+ *
+ * @return {string|String} Autoescaped output
+ */
+ Twig.output = function(output) {
+ if (!this.options.autoescape) {
+ return output.join("");
+ }
+
+ var strategy = 'html';
+ if(typeof this.options.autoescape == 'string')
+ strategy = this.options.autoescape;
+
+ // [].map would be better but it's not supported by IE8-
+ var escaped_output = [];
+ Twig.forEach(output, function (str) {
+ if (str && (str.twig_markup !== true && str.twig_markup != strategy)) {
+ str = Twig.filters.escape(str, [ strategy ]);
+ }
+ escaped_output.push(str);
+ });
+ return Twig.Markup(escaped_output.join(""));
+ }
+
+ // Namespace for template storage and retrieval
+ Twig.Templates = {
+ /**
+ * Registered template loaders - use Twig.Templates.registerLoader to add supported loaders
+ * @type {Object}
+ */
+ loaders: {},
+
+ /**
+ * Registered template parsers - use Twig.Templates.registerParser to add supported parsers
+ * @type {Object}
+ */
+ parsers: {},
+
+ /**
+ * Cached / loaded templates
+ * @type {Object}
+ */
+ registry: {}
+ };
+
+ /**
+ * Is this id valid for a twig template?
+ *
+ * @param {string} id The ID to check.
+ *
+ * @throws {Twig.Error} If the ID is invalid or used.
+ * @return {boolean} True if the ID is valid.
+ */
+ Twig.validateId = function(id) {
+ if (id === "prototype") {
+ throw new Twig.Error(id + " is not a valid twig identifier");
+ } else if (Twig.cache && Twig.Templates.registry.hasOwnProperty(id)) {
+ throw new Twig.Error("There is already a template with the ID " + id);
+ }
+ return true;
+ }
+
+ /**
+ * Register a template loader
+ *
+ * @example
+ * Twig.extend(function(Twig) {
+ * Twig.Templates.registerLoader('custom_loader', function(location, params, callback, error_callback) {
+ * // ... load the template ...
+ * params.data = loadedTemplateData;
+ * // create and return the template
+ * var template = new Twig.Template(params);
+ * if (typeof callback === 'function') {
+ * callback(template);
+ * }
+ * return template;
+ * });
+ * });
+ *
+ * @param {String} method_name The method this loader is intended for (ajax, fs)
+ * @param {Function} func The function to execute when loading the template
+ * @param {Object|undefined} scope Optional scope parameter to bind func to
+ *
+ * @throws Twig.Error
+ *
+ * @return {void}
+ */
+ Twig.Templates.registerLoader = function(method_name, func, scope) {
+ if (typeof func !== 'function') {
+ throw new Twig.Error('Unable to add loader for ' + method_name + ': Invalid function reference given.');
+ }
+ if (scope) {
+ func = func.bind(scope);
+ }
+ this.loaders[method_name] = func;
+ };
+
+ /**
+ * Remove a registered loader
+ *
+ * @param {String} method_name The method name for the loader you wish to remove
+ *
+ * @return {void}
+ */
+ Twig.Templates.unRegisterLoader = function(method_name) {
+ if (this.isRegisteredLoader(method_name)) {
+ delete this.loaders[method_name];
+ }
+ };
+
+ /**
+ * See if a loader is registered by its method name
+ *
+ * @param {String} method_name The name of the loader you are looking for
+ *
+ * @return {boolean}
+ */
+ Twig.Templates.isRegisteredLoader = function(method_name) {
+ return this.loaders.hasOwnProperty(method_name);
+ };
+
+ /**
+ * Register a template parser
+ *
+ * @example
+ * Twig.extend(function(Twig) {
+ * Twig.Templates.registerParser('custom_parser', function(params) {
+ * // this template source can be accessed in params.data
+ * var template = params.data
+ *
+ * // ... custom process that modifies the template
+ *
+ * // return the parsed template
+ * return template;
+ * });
+ * });
+ *
+ * @param {String} method_name The method this parser is intended for (twig, source)
+ * @param {Function} func The function to execute when parsing the template
+ * @param {Object|undefined} scope Optional scope parameter to bind func to
+ *
+ * @throws Twig.Error
+ *
+ * @return {void}
+ */
+ Twig.Templates.registerParser = function(method_name, func, scope) {
+ if (typeof func !== 'function') {
+ throw new Twig.Error('Unable to add parser for ' + method_name + ': Invalid function regerence given.');
+ }
+
+ if (scope) {
+ func = func.bind(scope);
+ }
+
+ this.parsers[method_name] = func;
+ };
+
+ /**
+ * Remove a registered parser
+ *
+ * @param {String} method_name The method name for the parser you wish to remove
+ *
+ * @return {void}
+ */
+ Twig.Templates.unRegisterParser = function(method_name) {
+ if (this.isRegisteredParser(method_name)) {
+ delete this.parsers[method_name];
+ }
+ };
+
+ /**
+ * See if a parser is registered by its method name
+ *
+ * @param {String} method_name The name of the parser you are looking for
+ *
+ * @return {boolean}
+ */
+ Twig.Templates.isRegisteredParser = function(method_name) {
+ return this.parsers.hasOwnProperty(method_name);
+ };
+
+ /**
+ * Save a template object to the store.
+ *
+ * @param {Twig.Template} template The twig.js template to store.
+ */
+ Twig.Templates.save = function(template) {
+ if (template.id === undefined) {
+ throw new Twig.Error("Unable to save template with no id");
+ }
+ Twig.Templates.registry[template.id] = template;
+ };
+
+ /**
+ * Load a previously saved template from the store.
+ *
+ * @param {string} id The ID of the template to load.
+ *
+ * @return {Twig.Template} A twig.js template stored with the provided ID.
+ */
+ Twig.Templates.load = function(id) {
+ if (!Twig.Templates.registry.hasOwnProperty(id)) {
+ return null;
+ }
+ return Twig.Templates.registry[id];
+ };
+
+ /**
+ * Load a template from a remote location using AJAX and saves in with the given ID.
+ *
+ * Available parameters:
+ *
+ * async: Should the HTTP request be performed asynchronously.
+ * Defaults to true.
+ * method: What method should be used to load the template
+ * (fs or ajax)
+ * parser: What method should be used to parse the template
+ * (twig or source)
+ * precompiled: Has the template already been compiled.
+ *
+ * @param {string} location The remote URL to load as a template.
+ * @param {Object} params The template parameters.
+ * @param {function} callback A callback triggered when the template finishes loading.
+ * @param {function} error_callback A callback triggered if an error occurs loading the template.
+ *
+ *
+ */
+ Twig.Templates.loadRemote = function(location, params, callback, error_callback) {
+ var loader;
+
+ // Default to async
+ if (params.async === undefined) {
+ params.async = true;
+ }
+
+ // Default to the URL so the template is cached.
+ if (params.id === undefined) {
+ params.id = location;
+ }
+
+ // Check for existing template
+ if (Twig.cache && Twig.Templates.registry.hasOwnProperty(params.id)) {
+ // A template is already saved with the given id.
+ if (typeof callback === 'function') {
+ callback(Twig.Templates.registry[params.id]);
+ }
+ // TODO: if async, return deferred promise
+ return Twig.Templates.registry[params.id];
+ }
+
+ //if the parser name hasn't been set, default it to twig
+ params.parser = params.parser || 'twig';
+
+ // Assume 'fs' if the loader is not defined
+ loader = this.loaders[params.method] || this.loaders.fs;
+ return loader.apply(this, arguments);
+ };
+
+ // Determine object type
+ function is(type, obj) {
+ var clas = Object.prototype.toString.call(obj).slice(8, -1);
+ return obj !== undefined && obj !== null && clas === type;
+ }
+
+ /**
+ * Create a new twig.js template.
+ *
+ * Parameters: {
+ * data: The template, either pre-compiled tokens or a string template
+ * id: The name of this template
+ * blocks: Any pre-existing block from a child template
+ * }
+ *
+ * @param {Object} params The template parameters.
+ */
+ Twig.Template = function ( params ) {
+ var data = params.data,
+ id = params.id,
+ blocks = params.blocks,
+ macros = params.macros || {},
+ base = params.base,
+ path = params.path,
+ url = params.url,
+ name = params.name,
+ method = params.method,
+ // parser options
+ options = params.options;
+
+ // # What is stored in a Twig.Template
+ //
+ // The Twig Template hold several chucks of data.
+ //
+ // {
+ // id: The token ID (if any)
+ // tokens: The list of tokens that makes up this template.
+ // blocks: The list of block this template contains.
+ // base: The base template (if any)
+ // options: {
+ // Compiler/parser options
+ //
+ // strict_variables: true/false
+ // Should missing variable/keys emit an error message. If false, they default to null.
+ // }
+ // }
+ //
+
+ this.id = id;
+ this.method = method;
+ this.base = base;
+ this.path = path;
+ this.url = url;
+ this.name = name;
+ this.macros = macros;
+ this.options = options;
+
+ this.reset(blocks);
+
+ if (is('String', data)) {
+ this.tokens = Twig.prepare.apply(this, [data]);
+ } else {
+ this.tokens = data;
+ }
+
+ if (id !== undefined) {
+ Twig.Templates.save(this);
+ }
+ };
+
+ Twig.Template.prototype.reset = function(blocks) {
+ Twig.log.debug("Twig.Template.reset", "Reseting template " + this.id);
+ this.blocks = {};
+ this.importedBlocks = [];
+ this.originalBlockTokens = {};
+ this.child = {
+ blocks: blocks || {}
+ };
+ this.extend = null;
+ };
+
+ Twig.Template.prototype.render = function (context, params) {
+ params = params || {};
+
+ var output,
+ url;
+
+ this.context = context || {};
+
+ // Clear any previous state
+ this.reset();
+ if (params.blocks) {
+ this.blocks = params.blocks;
+ }
+ if (params.macros) {
+ this.macros = params.macros;
+ }
+
+ output = Twig.parse.apply(this, [this.tokens, this.context]);
+
+ // Does this template extend another
+ if (this.extend) {
+ var ext_template;
+
+ // check if the template is provided inline
+ if ( this.options.allowInlineIncludes ) {
+ ext_template = Twig.Templates.load(this.extend);
+ if ( ext_template ) {
+ ext_template.options = this.options;
+ }
+ }
+
+ // check for the template file via include
+ if (!ext_template) {
+ url = Twig.path.parsePath(this, this.extend);
+
+ ext_template = Twig.Templates.loadRemote(url, {
+ method: this.getLoaderMethod(),
+ base: this.base,
+ async: false,
+ id: url,
+ options: this.options
+ });
+ }
+
+ this.parent = ext_template;
+
+ return this.parent.render(this.context, {
+ blocks: this.blocks
+ });
+ }
+
+ if (params.output == 'blocks') {
+ return this.blocks;
+ } else if (params.output == 'macros') {
+ return this.macros;
+ } else {
+ return output;
+ }
+ };
+
+ Twig.Template.prototype.importFile = function(file) {
+ var url, sub_template;
+ if (!this.url && this.options.allowInlineIncludes) {
+ file = this.path ? this.path + '/' + file : file;
+ sub_template = Twig.Templates.load(file);
+
+ if (!sub_template) {
+ sub_template = Twig.Templates.loadRemote(url, {
+ id: file,
+ method: this.getLoaderMethod(),
+ async: false,
+ path: file,
+ options: this.options
+ });
+
+ if (!sub_template) {
+ throw new Twig.Error("Unable to find the template " + file);
+ }
+ }
+
+ sub_template.options = this.options;
+
+ return sub_template;
+ }
+
+ url = Twig.path.parsePath(this, file);
+
+ // Load blocks from an external file
+ sub_template = Twig.Templates.loadRemote(url, {
+ method: this.getLoaderMethod(),
+ base: this.base,
+ async: false,
+ options: this.options,
+ id: url
+ });
+
+ return sub_template;
+ };
+
+ Twig.Template.prototype.importBlocks = function(file, override) {
+ var sub_template = this.importFile(file),
+ context = this.context,
+ that = this,
+ key;
+
+ override = override || false;
+
+ sub_template.render(context);
+
+ // Mixin blocks
+ Twig.forEach(Object.keys(sub_template.blocks), function(key) {
+ if (override || that.blocks[key] === undefined) {
+ that.blocks[key] = sub_template.blocks[key];
+ that.importedBlocks.push(key);
+ }
+ });
+ };
+
+ Twig.Template.prototype.importMacros = function(file) {
+ var url = Twig.path.parsePath(this, file);
+
+ // load remote template
+ var remoteTemplate = Twig.Templates.loadRemote(url, {
+ method: this.getLoaderMethod(),
+ async: false,
+ id: url
+ });
+
+ return remoteTemplate;
+ };
+
+ Twig.Template.prototype.getLoaderMethod = function() {
+ if (this.path) {
+ return 'fs';
+ }
+ if (this.url) {
+ return 'ajax';
+ }
+ return this.method || 'fs';
+ };
+
+ Twig.Template.prototype.compile = function(options) {
+ // compile the template into raw JS
+ return Twig.compiler.compile(this, options);
+ };
+
+ /**
+ * Create safe output
+ *
+ * @param {string} Content safe to output
+ *
+ * @return {String} Content wrapped into a String
+ */
+
+ Twig.Markup = function(content, strategy) {
+ if(typeof strategy == 'undefined') {
+ strategy = true;
+ }
+
+ if (typeof content === 'string' && content.length > 0) {
+ content = new String(content);
+ content.twig_markup = strategy;
+ }
+ return content;
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ // ## twig.compiler.js
+ //
+ // This file handles compiling templates into JS
+ module.exports = function (Twig) {
+ /**
+ * Namespace for compilation.
+ */
+ Twig.compiler = {
+ module: {}
+ };
+
+ // Compile a Twig Template to output.
+ Twig.compiler.compile = function(template, options) {
+ // Get tokens
+ var tokens = JSON.stringify(template.tokens)
+ , id = template.id
+ , output;
+
+ if (options.module) {
+ if (Twig.compiler.module[options.module] === undefined) {
+ throw new Twig.Error("Unable to find module type " + options.module);
+ }
+ output = Twig.compiler.module[options.module](id, tokens, options.twig);
+ } else {
+ output = Twig.compiler.wrap(id, tokens);
+ }
+ return output;
+ };
+
+ Twig.compiler.module = {
+ amd: function(id, tokens, pathToTwig) {
+ return 'define(["' + pathToTwig + '"], function (Twig) {\n\tvar twig, templates;\ntwig = Twig.twig;\ntemplates = ' + Twig.compiler.wrap(id, tokens) + '\n\treturn templates;\n});';
+ }
+ , node: function(id, tokens) {
+ return 'var twig = require("twig").twig;\n'
+ + 'exports.template = ' + Twig.compiler.wrap(id, tokens)
+ }
+ , cjs2: function(id, tokens, pathToTwig) {
+ return 'module.declare([{ twig: "' + pathToTwig + '" }], function (require, exports, module) {\n'
+ + '\tvar twig = require("twig").twig;\n'
+ + '\texports.template = ' + Twig.compiler.wrap(id, tokens)
+ + '\n});'
+ }
+ };
+
+ Twig.compiler.wrap = function(id, tokens) {
+ return 'twig({id:"'+id.replace('"', '\\"')+'", data:'+tokens+', precompiled: true});\n';
+ };
+
+ return Twig;
+ };
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // ## twig.expression.js
+ //
+ // This file handles tokenizing, compiling and parsing expressions.
+ module.exports = function (Twig) {
+ "use strict";
+
+ /**
+ * Namespace for expression handling.
+ */
+ Twig.expression = { };
+
+ __webpack_require__(4)(Twig);
+
+ /**
+ * Reserved word that can't be used as variable names.
+ */
+ Twig.expression.reservedWords = [
+ "true", "false", "null", "TRUE", "FALSE", "NULL", "_context", "and", "or", "in", "not in", "if"
+ ];
+
+ /**
+ * The type of tokens used in expressions.
+ */
+ Twig.expression.type = {
+ comma: 'Twig.expression.type.comma',
+ operator: {
+ unary: 'Twig.expression.type.operator.unary',
+ binary: 'Twig.expression.type.operator.binary'
+ },
+ string: 'Twig.expression.type.string',
+ bool: 'Twig.expression.type.bool',
+ slice: 'Twig.expression.type.slice',
+ array: {
+ start: 'Twig.expression.type.array.start',
+ end: 'Twig.expression.type.array.end'
+ },
+ object: {
+ start: 'Twig.expression.type.object.start',
+ end: 'Twig.expression.type.object.end'
+ },
+ parameter: {
+ start: 'Twig.expression.type.parameter.start',
+ end: 'Twig.expression.type.parameter.end'
+ },
+ subexpression: {
+ start: 'Twig.expression.type.subexpression.start',
+ end: 'Twig.expression.type.subexpression.end'
+ },
+ key: {
+ period: 'Twig.expression.type.key.period',
+ brackets: 'Twig.expression.type.key.brackets'
+ },
+ filter: 'Twig.expression.type.filter',
+ _function: 'Twig.expression.type._function',
+ variable: 'Twig.expression.type.variable',
+ number: 'Twig.expression.type.number',
+ _null: 'Twig.expression.type.null',
+ context: 'Twig.expression.type.context',
+ test: 'Twig.expression.type.test'
+ };
+
+ Twig.expression.set = {
+ // What can follow an expression (in general)
+ operations: [
+ Twig.expression.type.filter,
+ Twig.expression.type.operator.unary,
+ Twig.expression.type.operator.binary,
+ Twig.expression.type.array.end,
+ Twig.expression.type.object.end,
+ Twig.expression.type.parameter.end,
+ Twig.expression.type.subexpression.end,
+ Twig.expression.type.comma,
+ Twig.expression.type.test
+ ],
+ expressions: [
+ Twig.expression.type._function,
+ Twig.expression.type.bool,
+ Twig.expression.type.string,
+ Twig.expression.type.variable,
+ Twig.expression.type.number,
+ Twig.expression.type._null,
+ Twig.expression.type.context,
+ Twig.expression.type.parameter.start,
+ Twig.expression.type.array.start,
+ Twig.expression.type.object.start,
+ Twig.expression.type.subexpression.start
+ ]
+ };
+
+ // Most expressions allow a '.' or '[' after them, so we provide a convenience set
+ Twig.expression.set.operations_extended = Twig.expression.set.operations.concat([
+ Twig.expression.type.key.period,
+ Twig.expression.type.key.brackets,
+ Twig.expression.type.slice]);
+
+ // Some commonly used compile and parse functions.
+ Twig.expression.fn = {
+ compile: {
+ push: function(token, stack, output) {
+ output.push(token);
+ },
+ push_both: function(token, stack, output) {
+ output.push(token);
+ stack.push(token);
+ }
+ },
+ parse: {
+ push: function(token, stack, context) {
+ stack.push(token);
+ },
+ push_value: function(token, stack, context) {
+ stack.push(token.value);
+ }
+ }
+ };
+
+ // The regular expressions and compile/parse logic used to match tokens in expressions.
+ //
+ // Properties:
+ //
+ // type: The type of expression this matches
+ //
+ // regex: One or more regular expressions that matche the format of the token.
+ //
+ // next: Valid tokens that can occur next in the expression.
+ //
+ // Functions:
+ //
+ // compile: A function that compiles the raw regular expression match into a token.
+ //
+ // parse: A function that parses the compiled token into output.
+ //
+ Twig.expression.definitions = [
+ {
+ type: Twig.expression.type.test,
+ regex: /^is\s+(not)?\s*([a-zA-Z_][a-zA-Z0-9_]*(\s?as)?)/,
+ next: Twig.expression.set.operations.concat([Twig.expression.type.parameter.start]),
+ compile: function(token, stack, output) {
+ token.filter = token.match[2];
+ token.modifier = token.match[1];
+ delete token.match;
+ delete token.value;
+ output.push(token);
+ },
+ parse: function(token, stack, context) {
+ var value = stack.pop(),
+ params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
+ result = Twig.test(token.filter, value, params);
+
+ if (token.modifier == 'not') {
+ stack.push(!result);
+ } else {
+ stack.push(result);
+ }
+ }
+ },
+ {
+ type: Twig.expression.type.comma,
+ // Match a comma
+ regex: /^,/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.array.end, Twig.expression.type.object.end]),
+ compile: function(token, stack, output) {
+ var i = stack.length - 1,
+ stack_token;
+
+ delete token.match;
+ delete token.value;
+
+ // pop tokens off the stack until the start of the object
+ for(;i >= 0; i--) {
+ stack_token = stack.pop();
+ if (stack_token.type === Twig.expression.type.object.start
+ || stack_token.type === Twig.expression.type.parameter.start
+ || stack_token.type === Twig.expression.type.array.start) {
+ stack.push(stack_token);
+ break;
+ }
+ output.push(stack_token);
+ }
+ output.push(token);
+ }
+ },
+ {
+ /**
+ * Match a number (integer or decimal)
+ */
+ type: Twig.expression.type.number,
+ // match a number
+ regex: /^\-?\d+(\.\d+)?/,
+ next: Twig.expression.set.operations,
+ compile: function(token, stack, output) {
+ token.value = Number(token.value);
+ output.push(token);
+ },
+ parse: Twig.expression.fn.parse.push_value
+ },
+ {
+ type: Twig.expression.type.operator.binary,
+ // Match any of ?:, +, *, /, -, %, ~, <, <=, >, >=, !=, ==, **, ?, :, and, or, in, not in
+ // and, or, in, not in can be followed by a space or parenthesis
+ regex: /(^\?\:|^[\+\-~%\?]|^[\:](?!\d\])|^[!=]==?|^[!<>]=?|^\*\*?|^\/\/?|^(and)[\(|\s+]|^(or)[\(|\s+]|^(in)[\(|\s+]|^(not in)[\(|\s+]|^\.\.)/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.operator.unary]),
+ transform: function(match, tokens) {
+ switch(match[0]) {
+ case 'and(':
+ case 'or(':
+ case 'in(':
+ case 'not in(':
+ //Strip off the ( if it exists
+ tokens[tokens.length - 1].value = match[2];
+ return match[0];
+ break;
+ default:
+ return '';
+ }
+ },
+ compile: function(token, stack, output) {
+ delete token.match;
+
+ token.value = token.value.trim();
+ var value = token.value,
+ operator = Twig.expression.operator.lookup(value, token);
+
+ Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
+
+ while (stack.length > 0 &&
+ (stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
+ (
+ (operator.associativity === Twig.expression.operator.leftToRight &&
+ operator.precidence >= stack[stack.length-1].precidence) ||
+
+ (operator.associativity === Twig.expression.operator.rightToLeft &&
+ operator.precidence > stack[stack.length-1].precidence)
+ )
+ ) {
+ var temp = stack.pop();
+ output.push(temp);
+ }
+
+ if (value === ":") {
+ // Check if this is a ternary or object key being set
+ if (stack[stack.length - 1] && stack[stack.length-1].value === "?") {
+ // Continue as normal for a ternary
+ } else {
+ // This is not a ternary so we push the token to the output where it can be handled
+ // when the assocated object is closed.
+ var key_token = output.pop();
+
+ if (key_token.type === Twig.expression.type.string ||
+ key_token.type === Twig.expression.type.variable) {
+ token.key = key_token.value;
+ } else if (key_token.type === Twig.expression.type.number) {
+ // Convert integer keys into string keys
+ token.key = key_token.value.toString();
+ } else if (key_token.expression &&
+ (key_token.type === Twig.expression.type.parameter.end ||
+ key_token.type == Twig.expression.type.subexpression.end)) {
+ token.params = key_token.params;
+ } else {
+ throw new Twig.Error("Unexpected value before ':' of " + key_token.type + " = " + key_token.value);
+ }
+
+ output.push(token);
+ return;
+ }
+ } else {
+ stack.push(operator);
+ }
+ },
+ parse: function(token, stack, context) {
+ if (token.key) {
+ // handle ternary ':' operator
+ stack.push(token);
+ } else if (token.params) {
+ // handle "{(expression):value}"
+ token.key = Twig.expression.parse.apply(this, [token.params, context]);
+ stack.push(token);
+
+ //If we're in a loop, we might need token.params later, especially in this form of "(expression):value"
+ if (!context.loop) {
+ delete(token.params);
+ }
+ } else {
+ Twig.expression.operator.parse(token.value, stack);
+ }
+ }
+ },
+ {
+ type: Twig.expression.type.operator.unary,
+ // Match any of not
+ regex: /(^not\s+)/,
+ next: Twig.expression.set.expressions,
+ compile: function(token, stack, output) {
+ delete token.match;
+
+ token.value = token.value.trim();
+ var value = token.value,
+ operator = Twig.expression.operator.lookup(value, token);
+
+ Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
+
+ while (stack.length > 0 &&
+ (stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
+ (
+ (operator.associativity === Twig.expression.operator.leftToRight &&
+ operator.precidence >= stack[stack.length-1].precidence) ||
+
+ (operator.associativity === Twig.expression.operator.rightToLeft &&
+ operator.precidence > stack[stack.length-1].precidence)
+ )
+ ) {
+ var temp = stack.pop();
+ output.push(temp);
+ }
+
+ stack.push(operator);
+ },
+ parse: function(token, stack, context) {
+ Twig.expression.operator.parse(token.value, stack);
+ }
+ },
+ {
+ /**
+ * Match a string. This is anything between a pair of single or double quotes.
+ */
+ type: Twig.expression.type.string,
+ // See: http://blog.stevenlevithan.com/archives/match-quoted-string
+ regex: /^(["'])(?:(?=(\\?))\2[\s\S])*?\1/,
+ next: Twig.expression.set.operations_extended,
+ compile: function(token, stack, output) {
+ var value = token.value;
+ delete token.match
+
+ // Remove the quotes from the string
+ if (value.substring(0, 1) === '"') {
+ value = value.replace('\\"', '"');
+ } else {
+ value = value.replace("\\'", "'");
+ }
+ token.value = value.substring(1, value.length-1).replace( /\\n/g, "\n" ).replace( /\\r/g, "\r" );
+ Twig.log.trace("Twig.expression.compile: ", "String value: ", token.value);
+ output.push(token);
+ },
+ parse: Twig.expression.fn.parse.push_value
+ },
+ {
+ /**
+ * Match a subexpression set start.
+ */
+ type: Twig.expression.type.subexpression.start,
+ regex: /^\(/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.subexpression.end]),
+ compile: function(token, stack, output) {
+ token.value = '(';
+ output.push(token);
+ stack.push(token);
+ },
+ parse: Twig.expression.fn.parse.push
+ },
+ {
+ /**
+ * Match a subexpression set end.
+ */
+ type: Twig.expression.type.subexpression.end,
+ regex: /^\)/,
+ next: Twig.expression.set.operations_extended,
+ validate: function(match, tokens) {
+ // Iterate back through previous tokens to ensure we follow a subexpression start
+ var i = tokens.length - 1,
+ found_subexpression_start = false,
+ next_subexpression_start_invalid = false,
+ unclosed_parameter_count = 0;
+
+ while(!found_subexpression_start && i >= 0) {
+ var token = tokens[i];
+
+ found_subexpression_start = token.type === Twig.expression.type.subexpression.start;
+
+ // If we have previously found a subexpression end, then this subexpression start is the start of
+ // that subexpression, not the subexpression we are searching for
+ if (found_subexpression_start && next_subexpression_start_invalid) {
+ next_subexpression_start_invalid = false;
+ found_subexpression_start = false;
+ }
+
+ // Count parameter tokens to ensure we dont return truthy for a parameter opener
+ if (token.type === Twig.expression.type.parameter.start) {
+ unclosed_parameter_count++;
+ } else if (token.type === Twig.expression.type.parameter.end) {
+ unclosed_parameter_count--;
+ } else if (token.type === Twig.expression.type.subexpression.end) {
+ next_subexpression_start_invalid = true;
+ }
+
+ i--;
+ }
+
+ // If we found unclosed parameters, return false
+ // If we didnt find subexpression start, return false
+ // Otherwise return true
+
+ return (found_subexpression_start && (unclosed_parameter_count === 0));
+ },
+ compile: function(token, stack, output) {
+ // This is basically a copy of parameter end compilation
+ var stack_token,
+ end_token = token;
+
+ stack_token = stack.pop();
+ while(stack.length > 0 && stack_token.type != Twig.expression.type.subexpression.start) {
+ output.push(stack_token);
+ stack_token = stack.pop();
+ }
+
+ // Move contents of parens into preceding filter
+ var param_stack = [];
+ while(token.type !== Twig.expression.type.subexpression.start) {
+ // Add token to arguments stack
+ param_stack.unshift(token);
+ token = output.pop();
+ }
+
+ param_stack.unshift(token);
+
+ var is_expression = false;
+
+ //If the token at the top of the *stack* is a function token, pop it onto the output queue.
+ // Get the token preceding the parameters
+ stack_token = stack[stack.length-1];
+
+ if (stack_token === undefined ||
+ (stack_token.type !== Twig.expression.type._function &&
+ stack_token.type !== Twig.expression.type.filter &&
+ stack_token.type !== Twig.expression.type.test &&
+ stack_token.type !== Twig.expression.type.key.brackets)) {
+
+ end_token.expression = true;
+
+ // remove start and end token from stack
+ param_stack.pop();
+ param_stack.shift();
+
+ end_token.params = param_stack;
+
+ output.push(end_token);
+ } else {
+ // This should never be hit
+ end_token.expression = false;
+ stack_token.params = param_stack;
+ }
+ },
+ parse: function(token, stack, context) {
+ var new_array = [],
+ array_ended = false,
+ value = null;
+
+ if (token.expression) {
+ value = Twig.expression.parse.apply(this, [token.params, context]);
+ stack.push(value);
+ } else {
+ throw new Twig.Error("Unexpected subexpression end when token is not marked as an expression");
+ }
+ }
+ },
+ {
+ /**
+ * Match a parameter set start.
+ */
+ type: Twig.expression.type.parameter.start,
+ regex: /^\(/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.parameter.end]),
+ validate: function(match, tokens) {
+ var last_token = tokens[tokens.length - 1];
+ // We can't use the regex to test if we follow a space because expression is trimmed
+ return last_token && (Twig.indexOf(Twig.expression.reservedWords, last_token.value.trim()) < 0);
+ },
+ compile: Twig.expression.fn.compile.push_both,
+ parse: Twig.expression.fn.parse.push
+ },
+ {
+ /**
+ * Match a parameter set end.
+ */
+ type: Twig.expression.type.parameter.end,
+ regex: /^\)/,
+ next: Twig.expression.set.operations_extended,
+ compile: function(token, stack, output) {
+ var stack_token,
+ end_token = token;
+
+ stack_token = stack.pop();
+ while(stack.length > 0 && stack_token.type != Twig.expression.type.parameter.start) {
+ output.push(stack_token);
+ stack_token = stack.pop();
+ }
+
+ // Move contents of parens into preceding filter
+ var param_stack = [];
+ while(token.type !== Twig.expression.type.parameter.start) {
+ // Add token to arguments stack
+ param_stack.unshift(token);
+ token = output.pop();
+ }
+ param_stack.unshift(token);
+
+ var is_expression = false;
+
+ // Get the token preceding the parameters
+ token = output[output.length-1];
+
+ if (token === undefined ||
+ (token.type !== Twig.expression.type._function &&
+ token.type !== Twig.expression.type.filter &&
+ token.type !== Twig.expression.type.test &&
+ token.type !== Twig.expression.type.key.brackets)) {
+
+ end_token.expression = true;
+
+ // remove start and end token from stack
+ param_stack.pop();
+ param_stack.shift();
+
+ end_token.params = param_stack;
+
+ output.push(end_token);
+
+ } else {
+ end_token.expression = false;
+ token.params = param_stack;
+ }
+ },
+ parse: function(token, stack, context) {
+ var new_array = [],
+ array_ended = false,
+ value = null;
+
+ if (token.expression) {
+ value = Twig.expression.parse.apply(this, [token.params, context])
+ stack.push(value);
+
+ } else {
+
+ while (stack.length > 0) {
+ value = stack.pop();
+ // Push values into the array until the start of the array
+ if (value && value.type && value.type == Twig.expression.type.parameter.start) {
+ array_ended = true;
+ break;
+ }
+ new_array.unshift(value);
+ }
+
+ if (!array_ended) {
+ throw new Twig.Error("Expected end of parameter set.");
+ }
+
+ stack.push(new_array);
+ }
+ }
+ },
+ {
+ type: Twig.expression.type.slice,
+ regex: /^\[(\d*\:\d*)\]/,
+ next: Twig.expression.set.operations_extended,
+ compile: function(token, stack, output) {
+ var sliceRange = token.match[1].split(':');
+
+ //sliceStart can be undefined when we pass parameters to the slice filter later
+ var sliceStart = (sliceRange[0]) ? parseInt(sliceRange[0]) : undefined;
+ var sliceEnd = (sliceRange[1]) ? parseInt(sliceRange[1]) : undefined;
+
+ token.value = 'slice';
+ token.params = [sliceStart, sliceEnd];
+
+ //sliceEnd can't be undefined as the slice filter doesn't check for this, but it does check the length
+ //of the params array, so just shorten it.
+ if (!sliceEnd) {
+ token.params = [sliceStart];
+ }
+
+ output.push(token);
+ },
+ parse: function(token, stack, context) {
+ var input = stack.pop(),
+ params = token.params;
+
+ stack.push(Twig.filter.apply(this, [token.value, input, params]));
+ }
+ },
+ {
+ /**
+ * Match an array start.
+ */
+ type: Twig.expression.type.array.start,
+ regex: /^\[/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.array.end]),
+ compile: Twig.expression.fn.compile.push_both,
+ parse: Twig.expression.fn.parse.push
+ },
+ {
+ /**
+ * Match an array end.
+ */
+ type: Twig.expression.type.array.end,
+ regex: /^\]/,
+ next: Twig.expression.set.operations_extended,
+ compile: function(token, stack, output) {
+ var i = stack.length - 1,
+ stack_token;
+ // pop tokens off the stack until the start of the object
+ for(;i >= 0; i--) {
+ stack_token = stack.pop();
+ if (stack_token.type === Twig.expression.type.array.start) {
+ break;
+ }
+ output.push(stack_token);
+ }
+ output.push(token);
+ },
+ parse: function(token, stack, context) {
+ var new_array = [],
+ array_ended = false,
+ value = null;
+
+ while (stack.length > 0) {
+ value = stack.pop();
+ // Push values into the array until the start of the array
+ if (value.type && value.type == Twig.expression.type.array.start) {
+ array_ended = true;
+ break;
+ }
+ new_array.unshift(value);
+ }
+ if (!array_ended) {
+ throw new Twig.Error("Expected end of array.");
+ }
+
+ stack.push(new_array);
+ }
+ },
+ // Token that represents the start of a hash map '}'
+ //
+ // Hash maps take the form:
+ // { "key": 'value', "another_key": item }
+ //
+ // Keys must be quoted (either single or double) and values can be any expression.
+ {
+ type: Twig.expression.type.object.start,
+ regex: /^\{/,
+ next: Twig.expression.set.expressions.concat([Twig.expression.type.object.end]),
+ compile: Twig.expression.fn.compile.push_both,
+ parse: Twig.expression.fn.parse.push
+ },
+
+ // Token that represents the end of a Hash Map '}'
+ //
+ // This is where the logic for building the internal
+ // representation of a hash map is defined.
+ {
+ type: Twig.expression.type.object.end,
+ regex: /^\}/,
+ next: Twig.expression.set.operations_extended,
+ compile: function(token, stack, output) {
+ var i = stack.length-1,
+ stack_token;
+
+ // pop tokens off the stack until the start of the object
+ for(;i >= 0; i--) {
+ stack_token = stack.pop();
+ if (stack_token && stack_token.type === Twig.expression.type.object.start) {
+ break;
+ }
+ output.push(stack_token);
+ }
+ output.push(token);
+ },
+ parse: function(end_token, stack, context) {
+ var new_object = {},
+ object_ended = false,
+ token = null,
+ token_key = null,
+ has_value = false,
+ value = null;
+
+ while (stack.length > 0) {
+ token = stack.pop();
+ // Push values into the array until the start of the object
+ if (token && token.type && token.type === Twig.expression.type.object.start) {
+ object_ended = true;
+ break;
+ }
+ if (token && token.type && (token.type === Twig.expression.type.operator.binary || token.type === Twig.expression.type.operator.unary) && token.key) {
+ if (!has_value) {
+ throw new Twig.Error("Missing value for key '" + token.key + "' in object definition.");
+ }
+ new_object[token.key] = value;
+
+ // Preserve the order that elements are added to the map
+ // This is necessary since JavaScript objects don't
+ // guarantee the order of keys
+ if (new_object._keys === undefined) new_object._keys = [];
+ new_object._keys.unshift(token.key);
+
+ // reset value check
+ value = null;
+ has_value = false;
+
+ } else {
+ has_value = true;
+ value = token;
+ }
+ }
+ if (!object_ended) {
+ throw new Twig.Error("Unexpected end of object.");
+ }
+
+ stack.push(new_object);
+ }
+ },
+
+ // Token representing a filter
+ //
+ // Filters can follow any expression and take the form:
+ // expression|filter(optional, args)
+ //
+ // Filter parsing is done in the Twig.filters namespace.
+ {
+ type: Twig.expression.type.filter,
+ // match a | then a letter or _, then any number of letters, numbers, _ or -
+ regex: /^\|\s?([a-zA-Z_][a-zA-Z0-9_\-]*)/,
+ next: Twig.expression.set.operations_extended.concat([
+ Twig.expression.type.parameter.start]),
+ compile: function(token, stack, output) {
+ token.value = token.match[1];
+ output.push(token);
+ },
+ parse: function(token, stack, context) {
+ var input = stack.pop(),
+ params = token.params && Twig.expression.parse.apply(this, [token.params, context]);
+
+ stack.push(Twig.filter.apply(this, [token.value, input, params]));
+ }
+ },
+ {
+ type: Twig.expression.type._function,
+ // match any letter or _, then any number of letters, numbers, _ or - followed by (
+ regex: /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/,
+ next: Twig.expression.type.parameter.start,
+ validate: function(match, tokens) {
+ // Make sure this function is not a reserved word
+ return match[1] && (Twig.indexOf(Twig.expression.reservedWords, match[1]) < 0);
+ },
+ transform: function(match, tokens) {
+ return '(';
+ },
+ compile: function(token, stack, output) {
+ var fn = token.match[1];
+ token.fn = fn;
+ // cleanup token
+ delete token.match;
+ delete token.value;
+
+ output.push(token);
+ },
+ parse: function(token, stack, context) {
+ var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
+ fn = token.fn,
+ value;
+
+ if (Twig.functions[fn]) {
+ // Get the function from the built-in functions
+ value = Twig.functions[fn].apply(this, params);
+
+ } else if (typeof context[fn] == 'function') {
+ // Get the function from the user/context defined functions
+ value = context[fn].apply(context, params);
+
+ } else {
+ throw new Twig.Error(fn + ' function does not exist and is not defined in the context');
+ }
+
+ stack.push(value);
+ }
+ },
+
+ // Token representing a variable.
+ //
+ // Variables can contain letters, numbers, underscores and
+ // dashes, but must start with a letter or underscore.
+ //
+ // Variables are retrieved from the render context and take
+ // the value of 'undefined' if the given variable doesn't
+ // exist in the context.
+ {
+ type: Twig.expression.type.variable,
+ // match any letter or _, then any number of letters, numbers, _ or -
+ regex: /^[a-zA-Z_][a-zA-Z0-9_]*/,
+ next: Twig.expression.set.operations_extended.concat([
+ Twig.expression.type.parameter.start]),
+ compile: Twig.expression.fn.compile.push,
+ validate: function(match, tokens) {
+ return (Twig.indexOf(Twig.expression.reservedWords, match[0]) < 0);
+ },
+ parse: function(token, stack, context) {
+ // Get the variable from the context
+ var value = Twig.expression.resolve.apply(this, [context[token.value], context]);
+ stack.push(value);
+ }
+ },
+ {
+ type: Twig.expression.type.key.period,
+ regex: /^\.([a-zA-Z0-9_]+)/,
+ next: Twig.expression.set.operations_extended.concat([
+ Twig.expression.type.parameter.start]),
+ compile: function(token, stack, output) {
+ token.key = token.match[1];
+ delete token.match;
+ delete token.value;
+
+ output.push(token);
+ },
+ parse: function(token, stack, context, next_token) {
+ var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
+ key = token.key,
+ object = stack.pop(),
+ value;
+
+ if (object === null || object === undefined) {
+ if (this.options.strict_variables) {
+ throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
+ } else {
+ value = undefined;
+ }
+ } else {
+ var capitalize = function (value) {
+ return value.substr(0, 1).toUpperCase() + value.substr(1);
+ };
+
+ // Get the variable from the context
+ if (typeof object === 'object' && key in object) {
+ value = object[key];
+ } else if (object["get" + capitalize(key)] !== undefined) {
+ value = object["get" + capitalize(key)];
+ } else if (object["is" + capitalize(key)] !== undefined) {
+ value = object["is" + capitalize(key)];
+ } else {
+ value = undefined;
+ }
+ }
+
+ // When resolving an expression we need to pass next_token in case the expression is a function
+ stack.push(Twig.expression.resolve.apply(this, [value, context, params, next_token]));
+ }
+ },
+ {
+ type: Twig.expression.type.key.brackets,
+ regex: /^\[([^\]\:]*)\]/,
+ next: Twig.expression.set.operations_extended.concat([
+ Twig.expression.type.parameter.start]),
+ compile: function(token, stack, output) {
+ var match = token.match[1];
+ delete token.value;
+ delete token.match;
+
+ // The expression stack for the key
+ token.stack = Twig.expression.compile({
+ value: match
+ }).stack;
+
+ output.push(token);
+ },
+ parse: function(token, stack, context, next_token) {
+ // Evaluate key
+ var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
+ key = Twig.expression.parse.apply(this, [token.stack, context]),
+ object = stack.pop(),
+ value;
+
+ if (object === null || object === undefined) {
+ if (this.options.strict_variables) {
+ throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
+ } else {
+ return null;
+ }
+ }
+
+ // Get the variable from the context
+ if (typeof object === 'object' && key in object) {
+ value = object[key];
+ } else {
+ value = null;
+ }
+
+ // When resolving an expression we need to pass next_token in case the expression is a function
+ stack.push(Twig.expression.resolve.apply(this, [value, object, params, next_token]));
+ }
+ },
+ {
+ /**
+ * Match a null value.
+ */
+ type: Twig.expression.type._null,
+ // match a number
+ regex: /^(null|NULL|none|NONE)/,
+ next: Twig.expression.set.operations,
+ compile: function(token, stack, output) {
+ delete token.match;
+ token.value = null;
+ output.push(token);
+ },
+ parse: Twig.expression.fn.parse.push_value
+ },
+ {
+ /**
+ * Match the context
+ */
+ type: Twig.expression.type.context,
+ regex: /^_context/,
+ next: Twig.expression.set.operations_extended.concat([
+ Twig.expression.type.parameter.start]),
+ compile: Twig.expression.fn.compile.push,
+ parse: function(token, stack, context) {
+ stack.push(context);
+ }
+ },
+ {
+ /**
+ * Match a boolean
+ */
+ type: Twig.expression.type.bool,
+ regex: /^(true|TRUE|false|FALSE)/,
+ next: Twig.expression.set.operations,
+ compile: function(token, stack, output) {
+ token.value = (token.match[0].toLowerCase( ) === "true");
+ delete token.match;
+ output.push(token);
+ },
+ parse: Twig.expression.fn.parse.push_value
+ }
+ ];
+
+ /**
+ * Resolve a context value.
+ *
+ * If the value is a function, it is executed with a context parameter.
+ *
+ * @param {string} key The context object key.
+ * @param {Object} context The render context.
+ */
+ Twig.expression.resolve = function(value, context, params, next_token) {
+ if (typeof value == 'function') {
+ /*
+ If value is a function, it will have been impossible during the compile stage to determine that a following
+ set of parentheses were parameters for this function.
+
+ Those parentheses will have therefore been marked as an expression, with their own parameters, which really
+ belong to this function.
+
+ Those parameters will also need parsing in case they are actually an expression to pass as parameters.
+ */
+ if (next_token && next_token.type === Twig.expression.type.parameter.end) {
+ //When parsing these parameters, we need to get them all back, not just the last item on the stack.
+ var tokens_are_parameters = true;
+
+ params = next_token.params && Twig.expression.parse.apply(this, [next_token.params, context, tokens_are_parameters]);
+
+ //Clean up the parentheses tokens on the next loop
+ next_token.cleanup = true;
+ }
+ return value.apply(context, params || []);
+ } else {
+ return value;
+ }
+ };
+
+ /**
+ * Registry for logic handlers.
+ */
+ Twig.expression.handler = {};
+
+ /**
+ * Define a new expression type, available at Twig.logic.type.{type}
+ *
+ * @param {string} type The name of the new type.
+ */
+ Twig.expression.extendType = function (type) {
+ Twig.expression.type[type] = "Twig.expression.type." + type;
+ };
+
+ /**
+ * Extend the expression parsing functionality with a new definition.
+ *
+ * Token definitions follow this format:
+ * {
+ * type: One of Twig.expression.type.[type], either pre-defined or added using
+ * Twig.expression.extendType
+ *
+ * next: Array of types from Twig.expression.type that can follow this token,
+ *
+ * regex: A regex or array of regex's that should match the token.
+ *
+ * compile: function(token, stack, output) called when this token is being compiled.
+ * Should return an object with stack and output set.
+ *
+ * parse: function(token, stack, context) called when this token is being parsed.
+ * Should return an object with stack and context set.
+ * }
+ *
+ * @param {Object} definition A token definition.
+ */
+ Twig.expression.extend = function (definition) {
+ if (!definition.type) {
+ throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
+ }
+ Twig.expression.handler[definition.type] = definition;
+ };
+
+ // Extend with built-in expressions
+ while (Twig.expression.definitions.length > 0) {
+ Twig.expression.extend(Twig.expression.definitions.shift());
+ }
+
+ /**
+ * Break an expression into tokens defined in Twig.expression.definitions.
+ *
+ * @param {string} expression The string to tokenize.
+ *
+ * @return {Array} An array of tokens.
+ */
+ Twig.expression.tokenize = function (expression) {
+ var tokens = [],
+ // Keep an offset of the location in the expression for error messages.
+ exp_offset = 0,
+ // The valid next tokens of the previous token
+ next = null,
+ // Match information
+ type, regex, regex_array,
+ // The possible next token for the match
+ token_next,
+ // Has a match been found from the definitions
+ match_found, invalid_matches = [], match_function;
+
+ match_function = function () {
+ var match = Array.prototype.slice.apply(arguments),
+ string = match.pop(),
+ offset = match.pop();
+
+ Twig.log.trace("Twig.expression.tokenize",
+ "Matched a ", type, " regular expression of ", match);
+
+ if (next && Twig.indexOf(next, type) < 0) {
+ invalid_matches.push(
+ type + " cannot follow a " + tokens[tokens.length - 1].type +
+ " at template:" + exp_offset + " near '" + match[0].substring(0, 20) +
+ "...'"
+ );
+ // Not a match, don't change the expression
+ return match[0];
+ }
+
+ // Validate the token if a validation function is provided
+ if (Twig.expression.handler[type].validate &&
+ !Twig.expression.handler[type].validate(match, tokens)) {
+ return match[0];
+ }
+
+ invalid_matches = [];
+
+ tokens.push({
+ type: type,
+ value: match[0],
+ match: match
+ });
+
+ match_found = true;
+ next = token_next;
+ exp_offset += match[0].length;
+
+ // Does the token need to return output back to the expression string
+ // e.g. a function match of cycle( might return the '(' back to the expression
+ // This allows look-ahead to differentiate between token types (e.g. functions and variable names)
+ if (Twig.expression.handler[type].transform) {
+ return Twig.expression.handler[type].transform(match, tokens);
+ }
+ return '';
+ };
+
+ Twig.log.debug("Twig.expression.tokenize", "Tokenizing expression ", expression);
+
+ while (expression.length > 0) {
+ expression = expression.trim();
+ for (type in Twig.expression.handler) {
+ if (Twig.expression.handler.hasOwnProperty(type)) {
+ token_next = Twig.expression.handler[type].next;
+ regex = Twig.expression.handler[type].regex;
+ Twig.log.trace("Checking type ", type, " on ", expression);
+ if (regex instanceof Array) {
+ regex_array = regex;
+ } else {
+ regex_array = [regex];
+ }
+
+ match_found = false;
+ while (regex_array.length > 0) {
+ regex = regex_array.pop();
+ expression = expression.replace(regex, match_function);
+ }
+ // An expression token has been matched. Break the for loop and start trying to
+ // match the next template (if expression isn't empty.)
+ if (match_found) {
+ break;
+ }
+ }
+ }
+ if (!match_found) {
+ if (invalid_matches.length > 0) {
+ throw new Twig.Error(invalid_matches.join(" OR "));
+ } else {
+ throw new Twig.Error("Unable to parse '" + expression + "' at template position" + exp_offset);
+ }
+ }
+ }
+
+ Twig.log.trace("Twig.expression.tokenize", "Tokenized to ", tokens);
+ return tokens;
+ };
+
+ /**
+ * Compile an expression token.
+ *
+ * @param {Object} raw_token The uncompiled token.
+ *
+ * @return {Object} The compiled token.
+ */
+ Twig.expression.compile = function (raw_token) {
+ var expression = raw_token.value,
+ // Tokenize expression
+ tokens = Twig.expression.tokenize(expression),
+ token = null,
+ output = [],
+ stack = [],
+ token_template = null;
+
+ Twig.log.trace("Twig.expression.compile: ", "Compiling ", expression);
+
+ // Push tokens into RPN stack using the Shunting-yard algorithm
+ // See http://en.wikipedia.org/wiki/Shunting_yard_algorithm
+
+ while (tokens.length > 0) {
+ token = tokens.shift();
+ token_template = Twig.expression.handler[token.type];
+
+ Twig.log.trace("Twig.expression.compile: ", "Compiling ", token);
+
+ // Compile the template
+ token_template.compile && token_template.compile(token, stack, output);
+
+ Twig.log.trace("Twig.expression.compile: ", "Stack is", stack);
+ Twig.log.trace("Twig.expression.compile: ", "Output is", output);
+ }
+
+ while(stack.length > 0) {
+ output.push(stack.pop());
+ }
+
+ Twig.log.trace("Twig.expression.compile: ", "Final output is", output);
+
+ raw_token.stack = output;
+ delete raw_token.value;
+
+ return raw_token;
+ };
+
+
+ /**
+ * Parse an RPN expression stack within a context.
+ *
+ * @param {Array} tokens An array of compiled expression tokens.
+ * @param {Object} context The render context to parse the tokens with.
+ *
+ * @return {Object} The result of parsing all the tokens. The result
+ * can be anything, String, Array, Object, etc... based on
+ * the given expression.
+ */
+ Twig.expression.parse = function (tokens, context, tokens_are_parameters) {
+ var that = this;
+
+ // If the token isn't an array, make it one.
+ if (!(tokens instanceof Array)) {
+ tokens = [tokens];
+ }
+
+ // The output stack
+ var stack = [],
+ next_token,
+ token_template = null,
+ loop_token_fixups = [];
+
+ Twig.forEach(tokens, function (token, index) {
+ //If the token is marked for cleanup, we don't need to parse it
+ if (token.cleanup) {
+ return;
+ }
+
+ //Determine the token that follows this one so that we can pass it to the parser
+ if (tokens.length > index + 1) {
+ next_token = tokens[index + 1];
+ }
+
+ token_template = Twig.expression.handler[token.type];
+
+ token_template.parse && token_template.parse.apply(that, [token, stack, context, next_token]);
+
+ //Store any binary tokens for later if we are in a loop.
+ if (context.loop && token.type === Twig.expression.type.operator.binary) {
+ loop_token_fixups.push(token);
+ }
+ });
+
+ //Check every fixup and remove "key" as long as they still have "params". This covers the use case where
+ //a ":" operator is used in a loop with a "(expression):" statement. We need to be able to evaluate the expression
+ Twig.forEach(loop_token_fixups, function (loop_token_fixup) {
+ if (loop_token_fixup.params && loop_token_fixup.key) {
+ delete loop_token_fixup["key"];
+ }
+ });
+
+ //If parse has been called with a set of tokens that are parameters, we need to return the whole stack,
+ //wrapped in an Array.
+ if (tokens_are_parameters) {
+ var params = [];
+ while (stack.length > 0) {
+ params.unshift(stack.pop());
+ }
+
+ stack.push(params);
+ }
+
+ // Pop the final value off the stack
+ return stack.pop();
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ // ## twig.expression.operator.js
+ //
+ // This file handles operator lookups and parsing.
+ module.exports = function (Twig) {
+ "use strict";
+
+ /**
+ * Operator associativity constants.
+ */
+ Twig.expression.operator = {
+ leftToRight: 'leftToRight',
+ rightToLeft: 'rightToLeft'
+ };
+
+ var containment = function(a, b) {
+ if (b === undefined || b === null) {
+ return null;
+ } else if (b.indexOf !== undefined) {
+ // String
+ return a === b || a !== '' && b.indexOf(a) > -1;
+ } else {
+ var el;
+ for (el in b) {
+ if (b.hasOwnProperty(el) && b[el] === a) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ /**
+ * Get the precidence and associativity of an operator. These follow the order that C/C++ use.
+ * See http://en.wikipedia.org/wiki/Operators_in_C_and_C++ for the table of values.
+ */
+ Twig.expression.operator.lookup = function (operator, token) {
+ switch (operator) {
+ case "..":
+ token.precidence = 20;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case ',':
+ token.precidence = 18;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ // Ternary
+ case '?:':
+ case '?':
+ case ':':
+ token.precidence = 16;
+ token.associativity = Twig.expression.operator.rightToLeft;
+ break;
+
+ case 'or':
+ token.precidence = 14;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case 'and':
+ token.precidence = 13;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case '==':
+ case '!=':
+ token.precidence = 9;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case '<':
+ case '<=':
+ case '>':
+ case '>=':
+ case 'not in':
+ case 'in':
+ token.precidence = 8;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case '~': // String concatination
+ case '+':
+ case '-':
+ token.precidence = 6;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case '//':
+ case '**':
+ case '*':
+ case '/':
+ case '%':
+ token.precidence = 5;
+ token.associativity = Twig.expression.operator.leftToRight;
+ break;
+
+ case 'not':
+ token.precidence = 3;
+ token.associativity = Twig.expression.operator.rightToLeft;
+ break;
+
+ default:
+ throw new Twig.Error("Failed to lookup operator: " + operator + " is an unknown operator.");
+ }
+ token.operator = operator;
+ return token;
+ };
+
+ /**
+ * Handle operations on the RPN stack.
+ *
+ * Returns the updated stack.
+ */
+ Twig.expression.operator.parse = function (operator, stack) {
+ Twig.log.trace("Twig.expression.operator.parse: ", "Handling ", operator);
+ var a, b, c;
+
+ if (operator === '?') {
+ c = stack.pop();
+ }
+
+ b = stack.pop();
+ if (operator !== 'not') {
+ a = stack.pop();
+ }
+
+ if (operator !== 'in' && operator !== 'not in') {
+ if (a && Array.isArray(a)) {
+ a = a.length;
+ }
+
+ if (b && Array.isArray(b)) {
+ b = b.length;
+ }
+ }
+
+ switch (operator) {
+ case ':':
+ // Ignore
+ break;
+
+ case '?:':
+ if (Twig.lib.boolval(a)) {
+ stack.push(a);
+ } else {
+ stack.push(b);
+ }
+ break;
+ case '?':
+ if (a === undefined) {
+ //An extended ternary.
+ a = b;
+ b = c;
+ c = undefined;
+ }
+
+ if (Twig.lib.boolval(a)) {
+ stack.push(b);
+ } else {
+ stack.push(c);
+ }
+ break;
+
+ case '+':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(a + b);
+ break;
+
+ case '-':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(a - b);
+ break;
+
+ case '*':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(a * b);
+ break;
+
+ case '/':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(a / b);
+ break;
+
+ case '//':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(Math.floor(a / b));
+ break;
+
+ case '%':
+ b = parseFloat(b);
+ a = parseFloat(a);
+ stack.push(a % b);
+ break;
+
+ case '~':
+ stack.push( (a != null ? a.toString() : "")
+ + (b != null ? b.toString() : "") );
+ break;
+
+ case 'not':
+ case '!':
+ stack.push(!Twig.lib.boolval(b));
+ break;
+
+ case '<':
+ stack.push(a < b);
+ break;
+
+ case '<=':
+ stack.push(a <= b);
+ break;
+
+ case '>':
+ stack.push(a > b);
+ break;
+
+ case '>=':
+ stack.push(a >= b);
+ break;
+
+ case '===':
+ stack.push(a === b);
+ break;
+
+ case '==':
+ stack.push(a == b);
+ break;
+
+ case '!==':
+ stack.push(a !== b);
+ break;
+
+ case '!=':
+ stack.push(a != b);
+ break;
+
+ case 'or':
+ stack.push(a || b);
+ break;
+
+ case 'and':
+ stack.push(a && b);
+ break;
+
+ case '**':
+ stack.push(Math.pow(a, b));
+ break;
+
+ case 'not in':
+ stack.push( !containment(a, b) );
+ break;
+
+ case 'in':
+ stack.push( containment(a, b) );
+ break;
+
+ case '..':
+ stack.push( Twig.functions.range(a, b) );
+ break;
+
+ default:
+ debugger;
+ throw new Twig.Error("Failed to parse operator: " + operator + " is an unknown operator.");
+ }
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+ // ## twig.filters.js
+ //
+ // This file handles parsing filters.
+ module.exports = function (Twig) {
+
+ // Determine object type
+ function is(type, obj) {
+ var clas = Object.prototype.toString.call(obj).slice(8, -1);
+ return obj !== undefined && obj !== null && clas === type;
+ }
+
+ Twig.filters = {
+ // String Filters
+ upper: function(value) {
+ if ( typeof value !== "string" ) {
+ return value;
+ }
+
+ return value.toUpperCase();
+ },
+ lower: function(value) {
+ if ( typeof value !== "string" ) {
+ return value;
+ }
+
+ return value.toLowerCase();
+ },
+ capitalize: function(value) {
+ if ( typeof value !== "string" ) {
+ return value;
+ }
+
+ return value.substr(0, 1).toUpperCase() + value.toLowerCase().substr(1);
+ },
+ title: function(value) {
+ if ( typeof value !== "string" ) {
+ return value;
+ }
+
+ return value.toLowerCase().replace( /(^|\s)([a-z])/g , function(m, p1, p2){
+ return p1 + p2.toUpperCase();
+ });
+ },
+ length: function(value) {
+ if (Twig.lib.is("Array", value) || typeof value === "string") {
+ return value.length;
+ } else if (Twig.lib.is("Object", value)) {
+ if (value._keys === undefined) {
+ return Object.keys(value).length;
+ } else {
+ return value._keys.length;
+ }
+ } else {
+ return 0;
+ }
+ },
+
+ // Array/Object Filters
+ reverse: function(value) {
+ if (is("Array", value)) {
+ return value.reverse();
+ } else if (is("String", value)) {
+ return value.split("").reverse().join("");
+ } else if (is("Object", value)) {
+ var keys = value._keys || Object.keys(value).reverse();
+ value._keys = keys;
+ return value;
+ }
+ },
+ sort: function(value) {
+ if (is("Array", value)) {
+ return value.sort();
+ } else if (is('Object', value)) {
+ // Sorting objects isn't obvious since the order of
+ // returned keys isn't guaranteed in JavaScript.
+ // Because of this we use a "hidden" key called _keys to
+ // store the keys in the order we want to return them.
+
+ delete value._keys;
+ var keys = Object.keys(value),
+ sorted_keys = keys.sort(function(a, b) {
+ var a1, a2;
+
+ // if a and b are comparable, we're fine :-)
+ if((value[a] > value[b]) == !(value[a] <= value[b])) {
+ return value[a] > value[b] ? 1 :
+ value[a] < value[b] ? -1 :
+ 0;
+ }
+ // if a and b can be parsed as numbers, we can compare
+ // their numeric value
+ else if(!isNaN(a1 = parseFloat(value[a])) &&
+ !isNaN(b1 = parseFloat(value[b]))) {
+ return a1 > b1 ? 1 :
+ a1 < b1 ? -1 :
+ 0;
+ }
+ // if one of the values is a string, we convert the
+ // other value to string as well
+ else if(typeof value[a] == 'string') {
+ return value[a] > value[b].toString() ? 1 :
+ value[a] < value[b].toString() ? -1 :
+ 0;
+ }
+ else if(typeof value[b] == 'string') {
+ return value[a].toString() > value[b] ? 1 :
+ value[a].toString() < value[b] ? -1 :
+ 0;
+ }
+ // everything failed - return 'null' as sign, that
+ // the values are not comparable
+ else {
+ return null;
+ }
+ });
+ value._keys = sorted_keys;
+ return value;
+ }
+ },
+ keys: function(value) {
+ if (value === undefined || value === null){
+ return;
+ }
+
+ var keyset = value._keys || Object.keys(value),
+ output = [];
+
+ Twig.forEach(keyset, function(key) {
+ if (key === "_keys") return; // Ignore the _keys property
+ if (value.hasOwnProperty(key)) {
+ output.push(key);
+ }
+ });
+ return output;
+ },
+ url_encode: function(value) {
+ if (value === undefined || value === null){
+ return;
+ }
+
+ var result = encodeURIComponent(value);
+ result = result.replace("'", "%27");
+ return result;
+ },
+ join: function(value, params) {
+ if (value === undefined || value === null){
+ return;
+ }
+
+ var join_str = "",
+ output = [],
+ keyset = null;
+
+ if (params && params[0]) {
+ join_str = params[0];
+ }
+ if (is("Array", value)) {
+ output = value;
+ } else {
+ keyset = value._keys || Object.keys(value);
+ Twig.forEach(keyset, function(key) {
+ if (key === "_keys") return; // Ignore the _keys property
+ if (value.hasOwnProperty(key)) {
+ output.push(value[key]);
+ }
+ });
+ }
+ return output.join(join_str);
+ },
+ "default": function(value, params) {
+ if (params !== undefined && params.length > 1) {
+ throw new Twig.Error("default filter expects one argument");
+ }
+ if (value === undefined || value === null || value === '' ) {
+ if (params === undefined) {
+ return '';
+ }
+
+ return params[0];
+ } else {
+ return value;
+ }
+ },
+ json_encode: function(value) {
+ if(value === undefined || value === null) {
+ return "null";
+ }
+ else if ((typeof value == 'object') && (is("Array", value))) {
+ output = [];
+
+ Twig.forEach(value, function(v) {
+ output.push(Twig.filters.json_encode(v));
+ });
+
+ return "[" + output.join(",") + "]";
+ }
+ else if (typeof value == 'object') {
+ var keyset = value._keys || Object.keys(value),
+ output = [];
+
+ Twig.forEach(keyset, function(key) {
+ output.push(JSON.stringify(key) + ":" + Twig.filters.json_encode(value[key]));
+ });
+
+ return "{" + output.join(",") + "}";
+ }
+ else {
+ return JSON.stringify(value);
+ }
+ },
+ merge: function(value, params) {
+ var obj = [],
+ arr_index = 0,
+ keyset = [];
+
+ // Check to see if all the objects being merged are arrays
+ if (!is("Array", value)) {
+ // Create obj as an Object
+ obj = { };
+ } else {
+ Twig.forEach(params, function(param) {
+ if (!is("Array", param)) {
+ obj = { };
+ }
+ });
+ }
+ if (!is("Array", obj)) {
+ obj._keys = [];
+ }
+
+ if (is("Array", value)) {
+ Twig.forEach(value, function(val) {
+ if (obj._keys) obj._keys.push(arr_index);
+ obj[arr_index] = val;
+ arr_index++;
+ });
+ } else {
+ keyset = value._keys || Object.keys(value);
+ Twig.forEach(keyset, function(key) {
+ obj[key] = value[key];
+ obj._keys.push(key);
+
+ // Handle edge case where a number index in an object is greater than
+ // the array counter. In such a case, the array counter is increased
+ // one past the index.
+ //
+ // Example {{ ["a", "b"]|merge({"4":"value"}, ["c", "d"])
+ // Without this, d would have an index of "4" and overwrite the value
+ // of "value"
+ var int_key = parseInt(key, 10);
+ if (!isNaN(int_key) && int_key >= arr_index) {
+ arr_index = int_key + 1;
+ }
+ });
+ }
+
+ // mixin the merge arrays
+ Twig.forEach(params, function(param) {
+ if (is("Array", param)) {
+ Twig.forEach(param, function(val) {
+ if (obj._keys) obj._keys.push(arr_index);
+ obj[arr_index] = val;
+ arr_index++;
+ });
+ } else {
+ keyset = param._keys || Object.keys(param);
+ Twig.forEach(keyset, function(key) {
+ if (!obj[key]) obj._keys.push(key);
+ obj[key] = param[key];
+
+ var int_key = parseInt(key, 10);
+ if (!isNaN(int_key) && int_key >= arr_index) {
+ arr_index = int_key + 1;
+ }
+ });
+ }
+ });
+ if (params.length === 0) {
+ throw new Twig.Error("Filter merge expects at least one parameter");
+ }
+
+ return obj;
+ },
+ date: function(value, params) {
+ var date = Twig.functions.date(value);
+ var format = params && params.length ? params[0] : 'F j, Y H:i';
+ return Twig.lib.date(format, date);
+ },
+
+ date_modify: function(value, params) {
+ if (value === undefined || value === null) {
+ return;
+ }
+ if (params === undefined || params.length !== 1) {
+ throw new Twig.Error("date_modify filter expects 1 argument");
+ }
+
+ var modifyText = params[0], time;
+
+ if (Twig.lib.is("Date", value)) {
+ time = Twig.lib.strtotime(modifyText, value.getTime() / 1000);
+ }
+ if (Twig.lib.is("String", value)) {
+ time = Twig.lib.strtotime(modifyText, Twig.lib.strtotime(value));
+ }
+ if (Twig.lib.is("Number", value)) {
+ time = Twig.lib.strtotime(modifyText, value);
+ }
+
+ return new Date(time * 1000);
+ },
+
+ replace: function(value, params) {
+ if (value === undefined||value === null){
+ return;
+ }
+
+ var pairs = params[0],
+ tag;
+ for (tag in pairs) {
+ if (pairs.hasOwnProperty(tag) && tag !== "_keys") {
+ value = Twig.lib.replaceAll(value, tag, pairs[tag]);
+ }
+ }
+ return value;
+ },
+
+ format: function(value, params) {
+ if (value === undefined || value === null){
+ return;
+ }
+
+ return Twig.lib.vsprintf(value, params);
+ },
+
+ striptags: function(value) {
+ if (value === undefined || value === null){
+ return;
+ }
+
+ return Twig.lib.strip_tags(value);
+ },
+
+ escape: function(value, params) {
+ if (value === undefined|| value === null){
+ return;
+ }
+
+ var strategy = "html";
+ if(params && params.length && params[0] !== true)
+ strategy = params[0];
+
+ if(strategy == "html") {
+ var raw_value = value.toString().replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;")
+ .replace(/'/g, "&#039;");
+ return Twig.Markup(raw_value, 'html');
+ } else if(strategy == "js") {
+ var raw_value = value.toString();
+ var result = "";
+
+ for(var i = 0; i < raw_value.length; i++) {
+ if(raw_value[i].match(/^[a-zA-Z0-9,\._]$/))
+ result += raw_value[i];
+ else {
+ var char_code = raw_value.charCodeAt(i);
+
+ if(char_code < 0x80)
+ result += "\\x" + char_code.toString(16).toUpperCase();
+ else
+ result += Twig.lib.sprintf("\\u%04s", char_code.toString(16).toUpperCase());
+ }
+ }
+
+ return Twig.Markup(result, 'js');
+ } else if(strategy == "css") {
+ var raw_value = value.toString();
+ var result = "";
+
+ for(var i = 0; i < raw_value.length; i++) {
+ if(raw_value[i].match(/^[a-zA-Z0-9]$/))
+ result += raw_value[i];
+ else {
+ var char_code = raw_value.charCodeAt(i);
+ result += "\\" + char_code.toString(16).toUpperCase() + " ";
+ }
+ }
+
+ return Twig.Markup(result, 'css');
+ } else if(strategy == "url") {
+ var result = Twig.filters.url_encode(value);
+ return Twig.Markup(result, 'url');
+ } else if(strategy == "html_attr") {
+ var raw_value = value.toString();
+ var result = "";
+
+ for(var i = 0; i < raw_value.length; i++) {
+ if(raw_value[i].match(/^[a-zA-Z0-9,\.\-_]$/))
+ result += raw_value[i];
+ else if(raw_value[i].match(/^[&<>"]$/))
+ result += raw_value[i].replace(/&/g, "&amp;")
+ .replace(/</g, "&lt;")
+ .replace(/>/g, "&gt;")
+ .replace(/"/g, "&quot;");
+ else {
+ var char_code = raw_value.charCodeAt(i);
+
+ // The following replaces characters undefined in HTML with
+ // the hex entity for the Unicode replacement character.
+ if(char_code <= 0x1f && char_code != 0x09 && char_code != 0x0a && char_code != 0x0d)
+ result += "&#xFFFD;";
+ else if(char_code < 0x80)
+ result += Twig.lib.sprintf("&#x%02s;", char_code.toString(16).toUpperCase());
+ else
+ result += Twig.lib.sprintf("&#x%04s;", char_code.toString(16).toUpperCase());
+ }
+ }
+
+ return Twig.Markup(result, 'html_attr');
+ } else {
+ throw new Twig.Error("escape strategy unsupported");
+ }
+ },
+
+ /* Alias of escape */
+ "e": function(value, params) {
+ return Twig.filters.escape(value, params);
+ },
+
+ nl2br: function(value) {
+ if (value === undefined || value === null){
+ return;
+ }
+ var linebreak_tag = "BACKSLASH_n_replace",
+ br = "<br />" + linebreak_tag;
+
+ value = Twig.filters.escape(value)
+ .replace(/\r\n/g, br)
+ .replace(/\r/g, br)
+ .replace(/\n/g, br);
+
+ value = Twig.lib.replaceAll(value, linebreak_tag, "\n");
+
+ return Twig.Markup(value);
+ },
+
+ /**
+ * Adapted from: http://phpjs.org/functions/number_format:481
+ */
+ number_format: function(value, params) {
+ var number = value,
+ decimals = (params && params[0]) ? params[0] : undefined,
+ dec = (params && params[1] !== undefined) ? params[1] : ".",
+ sep = (params && params[2] !== undefined) ? params[2] : ",";
+
+ number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
+ var n = !isFinite(+number) ? 0 : +number,
+ prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
+ s = '',
+ toFixedFix = function (n, prec) {
+ var k = Math.pow(10, prec);
+ return '' + Math.round(n * k) / k;
+ };
+ // Fix for IE parseFloat(0.55).toFixed(0) = 0;
+ s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
+ if (s[0].length > 3) {
+ s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
+ }
+ if ((s[1] || '').length < prec) {
+ s[1] = s[1] || '';
+ s[1] += new Array(prec - s[1].length + 1).join('0');
+ }
+ return s.join(dec);
+ },
+
+ trim: function(value, params) {
+ if (value === undefined|| value === null){
+ return;
+ }
+
+ var str = Twig.filters.escape( '' + value ),
+ whitespace;
+ if ( params && params[0] ) {
+ whitespace = '' + params[0];
+ } else {
+ whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
+ }
+ for (var i = 0; i < str.length; i++) {
+ if (whitespace.indexOf(str.charAt(i)) === -1) {
+ str = str.substring(i);
+ break;
+ }
+ }
+ for (i = str.length - 1; i >= 0; i--) {
+ if (whitespace.indexOf(str.charAt(i)) === -1) {
+ str = str.substring(0, i + 1);
+ break;
+ }
+ }
+ return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
+ },
+
+ truncate: function (value, params) {
+ var length = 30,
+ preserve = false,
+ separator = '...';
+
+ value = value + '';
+ if (params) {
+ if (params[0]) {
+ length = params[0];
+ }
+ if (params[1]) {
+ preserve = params[1];
+ }
+ if (params[2]) {
+ separator = params[2];
+ }
+ }
+
+ if (value.length > length) {
+
+ if (preserve) {
+ length = value.indexOf(' ', length);
+ if (length === -1) {
+ return value;
+ }
+ }
+
+ value = value.substr(0, length) + separator;
+ }
+
+ return value;
+ },
+
+ slice: function(value, params) {
+ if (value === undefined || value === null) {
+ return;
+ }
+ if (params === undefined || params.length < 1) {
+ throw new Twig.Error("slice filter expects at least 1 argument");
+ }
+
+ // default to start of string
+ var start = params[0] || 0;
+ // default to length of string
+ var length = params.length > 1 ? params[1] : value.length;
+ // handle negative start values
+ var startIndex = start >= 0 ? start : Math.max( value.length + start, 0 );
+
+ if (Twig.lib.is("Array", value)) {
+ var output = [];
+ for (var i = startIndex; i < startIndex + length && i < value.length; i++) {
+ output.push(value[i]);
+ }
+ return output;
+ } else if (Twig.lib.is("String", value)) {
+ return value.substr(startIndex, length);
+ } else {
+ throw new Twig.Error("slice filter expects value to be an array or string");
+ }
+ },
+
+ abs: function(value) {
+ if (value === undefined || value === null) {
+ return;
+ }
+
+ return Math.abs(value);
+ },
+
+ first: function(value) {
+ if (is("Array", value)) {
+ return value[0];
+ } else if (is("Object", value)) {
+ if ('_keys' in value) {
+ return value[value._keys[0]];
+ }
+ } else if ( typeof value === "string" ) {
+ return value.substr(0, 1);
+ }
+
+ return;
+ },
+
+ split: function(value, params) {
+ if (value === undefined || value === null) {
+ return;
+ }
+ if (params === undefined || params.length < 1 || params.length > 2) {
+ throw new Twig.Error("split filter expects 1 or 2 argument");
+ }
+ if (Twig.lib.is("String", value)) {
+ var delimiter = params[0],
+ limit = params[1],
+ split = value.split(delimiter);
+
+ if (limit === undefined) {
+
+ return split;
+
+ } else if (limit < 0) {
+
+ return value.split(delimiter, split.length + limit);
+
+ } else {
+
+ var limitedSplit = [];
+
+ if (delimiter == '') {
+ // empty delimiter
+ // "aabbcc"|split('', 2)
+ // -> ['aa', 'bb', 'cc']
+
+ while(split.length > 0) {
+ var temp = "";
+ for (var i=0; i<limit && split.length > 0; i++) {
+ temp += split.shift();
+ }
+ limitedSplit.push(temp);
+ }
+
+ } else {
+ // non-empty delimiter
+ // "one,two,three,four,five"|split(',', 3)
+ // -> ['one', 'two', 'three,four,five']
+
+ for (var i=0; i<limit-1 && split.length > 0; i++) {
+ limitedSplit.push(split.shift());
+ }
+
+ if (split.length > 0) {
+ limitedSplit.push(split.join(delimiter));
+ }
+ }
+
+ return limitedSplit;
+ }
+
+ } else {
+ throw new Twig.Error("split filter expects value to be a string");
+ }
+ },
+ last: function(value) {
+ if (Twig.lib.is('Object', value)) {
+ var keys;
+
+ if (value._keys === undefined) {
+ keys = Object.keys(value);
+ } else {
+ keys = value._keys;
+ }
+
+ return value[keys[keys.length - 1]];
+ }
+
+ // string|array
+ return value[value.length - 1];
+ },
+ raw: function(value) {
+ return Twig.Markup(value);
+ },
+ batch: function(items, params) {
+ var size = params.shift(),
+ fill = params.shift(),
+ result,
+ last,
+ missing;
+
+ if (!Twig.lib.is("Array", items)) {
+ throw new Twig.Error("batch filter expects items to be an array");
+ }
+
+ if (!Twig.lib.is("Number", size)) {
+ throw new Twig.Error("batch filter expects size to be a number");
+ }
+
+ size = Math.ceil(size);
+
+ result = Twig.lib.chunkArray(items, size);
+
+ if (fill && items.length % size != 0) {
+ last = result.pop();
+ missing = size - last.length;
+
+ while (missing--) {
+ last.push(fill);
+ }
+
+ result.push(last);
+ }
+
+ return result;
+ },
+ round: function(value, params) {
+ params = params || [];
+
+ var precision = params.length > 0 ? params[0] : 0,
+ method = params.length > 1 ? params[1] : "common";
+
+ value = parseFloat(value);
+
+ if(precision && !Twig.lib.is("Number", precision)) {
+ throw new Twig.Error("round filter expects precision to be a number");
+ }
+
+ if (method === "common") {
+ return Twig.lib.round(value, precision);
+ }
+
+ if(!Twig.lib.is("Function", Math[method])) {
+ throw new Twig.Error("round filter expects method to be 'floor', 'ceil', or 'common'");
+ }
+
+ return Math[method](value * Math.pow(10, precision)) / Math.pow(10, precision);
+ }
+ };
+
+ Twig.filter = function(filter, value, params) {
+ if (!Twig.filters[filter]) {
+ throw "Unable to find filter " + filter;
+ }
+ return Twig.filters[filter].apply(this, [value, params]);
+ };
+
+ Twig.filter.extend = function(filter, definition) {
+ Twig.filters[filter] = definition;
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports) {
+
+ // ## twig.functions.js
+ //
+ // This file handles parsing filters.
+ module.exports = function (Twig) {
+ /**
+ * @constant
+ * @type {string}
+ */
+ var TEMPLATE_NOT_FOUND_MESSAGE = 'Template "{name}" is not defined.';
+
+ // Determine object type
+ function is(type, obj) {
+ var clas = Object.prototype.toString.call(obj).slice(8, -1);
+ return obj !== undefined && obj !== null && clas === type;
+ }
+
+ Twig.functions = {
+ // attribute, block, constant, date, dump, parent, random,.
+
+ // Range function from http://phpjs.org/functions/range:499
+ // Used under an MIT License
+ range: function (low, high, step) {
+ // http://kevin.vanzonneveld.net
+ // + original by: Waldo Malqui Silva
+ // * example 1: range ( 0, 12 );
+ // * returns 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+ // * example 2: range( 0, 100, 10 );
+ // * returns 2: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
+ // * example 3: range( 'a', 'i' );
+ // * returns 3: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
+ // * example 4: range( 'c', 'a' );
+ // * returns 4: ['c', 'b', 'a']
+ var matrix = [];
+ var inival, endval, plus;
+ var walker = step || 1;
+ var chars = false;
+
+ if (!isNaN(low) && !isNaN(high)) {
+ inival = parseInt(low, 10);
+ endval = parseInt(high, 10);
+ } else if (isNaN(low) && isNaN(high)) {
+ chars = true;
+ inival = low.charCodeAt(0);
+ endval = high.charCodeAt(0);
+ } else {
+ inival = (isNaN(low) ? 0 : low);
+ endval = (isNaN(high) ? 0 : high);
+ }
+
+ plus = ((inival > endval) ? false : true);
+ if (plus) {
+ while (inival <= endval) {
+ matrix.push(((chars) ? String.fromCharCode(inival) : inival));
+ inival += walker;
+ }
+ } else {
+ while (inival >= endval) {
+ matrix.push(((chars) ? String.fromCharCode(inival) : inival));
+ inival -= walker;
+ }
+ }
+
+ return matrix;
+ },
+ cycle: function(arr, i) {
+ var pos = i % arr.length;
+ return arr[pos];
+ },
+ dump: function() {
+ var EOL = '\n',
+ indentChar = ' ',
+ indentTimes = 0,
+ out = '',
+ args = Array.prototype.slice.call(arguments),
+ indent = function(times) {
+ var ind = '';
+ while (times > 0) {
+ times--;
+ ind += indentChar;
+ }
+ return ind;
+ },
+ displayVar = function(variable) {
+ out += indent(indentTimes);
+ if (typeof(variable) === 'object') {
+ dumpVar(variable);
+ } else if (typeof(variable) === 'function') {
+ out += 'function()' + EOL;
+ } else if (typeof(variable) === 'string') {
+ out += 'string(' + variable.length + ') "' + variable + '"' + EOL;
+ } else if (typeof(variable) === 'number') {
+ out += 'number(' + variable + ')' + EOL;
+ } else if (typeof(variable) === 'boolean') {
+ out += 'bool(' + variable + ')' + EOL;
+ }
+ },
+ dumpVar = function(variable) {
+ var i;
+ if (variable === null) {
+ out += 'NULL' + EOL;
+ } else if (variable === undefined) {
+ out += 'undefined' + EOL;
+ } else if (typeof variable === 'object') {
+ out += indent(indentTimes) + typeof(variable);
+ indentTimes++;
+ out += '(' + (function(obj) {
+ var size = 0, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ size++;
+ }
+ }
+ return size;
+ })(variable) + ') {' + EOL;
+ for (i in variable) {
+ out += indent(indentTimes) + '[' + i + ']=> ' + EOL;
+ displayVar(variable[i]);
+ }
+ indentTimes--;
+ out += indent(indentTimes) + '}' + EOL;
+ } else {
+ displayVar(variable);
+ }
+ };
+
+ // handle no argument case by dumping the entire render context
+ if (args.length == 0) args.push(this.context);
+
+ Twig.forEach(args, function(variable) {
+ dumpVar(variable);
+ });
+
+ return out;
+ },
+ date: function(date, time) {
+ var dateObj;
+ if (date === undefined || date === null || date === "") {
+ dateObj = new Date();
+ } else if (Twig.lib.is("Date", date)) {
+ dateObj = date;
+ } else if (Twig.lib.is("String", date)) {
+ if (date.match(/^[0-9]+$/)) {
+ dateObj = new Date(date * 1000);
+ }
+ else {
+ dateObj = new Date(Twig.lib.strtotime(date) * 1000);
+ }
+ } else if (Twig.lib.is("Number", date)) {
+ // timestamp
+ dateObj = new Date(date * 1000);
+ } else {
+ throw new Twig.Error("Unable to parse date " + date);
+ }
+ return dateObj;
+ },
+ block: function(block) {
+ if (this.originalBlockTokens[block]) {
+ return Twig.logic.parse.apply(this, [this.originalBlockTokens[block], this.context]).output;
+ } else {
+ return this.blocks[block];
+ }
+ },
+ parent: function() {
+ // Add a placeholder
+ return Twig.placeholders.parent;
+ },
+ attribute: function(object, method, params) {
+ if (Twig.lib.is('Object', object)) {
+ if (object.hasOwnProperty(method)) {
+ if (typeof object[method] === "function") {
+ return object[method].apply(undefined, params);
+ }
+ else {
+ return object[method];
+ }
+ }
+ }
+ // Array will return element 0-index
+ return object[method] || undefined;
+ },
+ max: function(values) {
+ if(Twig.lib.is("Object", values)) {
+ delete values["_keys"];
+ return Twig.lib.max(values);
+ }
+
+ return Twig.lib.max.apply(null, arguments);
+ },
+ min: function(values) {
+ if(Twig.lib.is("Object", values)) {
+ delete values["_keys"];
+ return Twig.lib.min(values);
+ }
+
+ return Twig.lib.min.apply(null, arguments);
+ },
+ template_from_string: function(template) {
+ if (template === undefined) {
+ template = '';
+ }
+ return Twig.Templates.parsers.twig({
+ options: this.options,
+ data: template
+ });
+ },
+ random: function(value) {
+ var LIMIT_INT31 = 0x80000000;
+
+ function getRandomNumber(n) {
+ var random = Math.floor(Math.random() * LIMIT_INT31);
+ var limits = [0, n];
+ var min = Math.min.apply(null, limits),
+ max = Math.max.apply(null, limits);
+ return min + Math.floor((max - min + 1) * random / LIMIT_INT31);
+ }
+
+ if(Twig.lib.is("Number", value)) {
+ return getRandomNumber(value);
+ }
+
+ if(Twig.lib.is("String", value)) {
+ return value.charAt(getRandomNumber(value.length-1));
+ }
+
+ if(Twig.lib.is("Array", value)) {
+ return value[getRandomNumber(value.length-1)];
+ }
+
+ if(Twig.lib.is("Object", value)) {
+ var keys = Object.keys(value);
+ return value[keys[getRandomNumber(keys.length-1)]];
+ }
+
+ return getRandomNumber(LIMIT_INT31-1);
+ },
+
+ /**
+ * Returns the content of a template without rendering it
+ * @param {string} name
+ * @param {boolean} [ignore_missing=false]
+ * @returns {string}
+ */
+ source: function(name, ignore_missing) {
+ var templateSource;
+ var templateFound = false;
+ var isNodeEnvironment = typeof module !== 'undefined' && typeof module.exports !== 'undefined' && typeof window === 'undefined';
+ var loader;
+ var path;
+
+ //if we are running in a node.js environment, set the loader to 'fs' and ensure the
+ // path is relative to the CWD of the running script
+ //else, set the loader to 'ajax' and set the path to the value of name
+ if (isNodeEnvironment) {
+ loader = 'fs';
+ path = __dirname + '/' + name;
+ } else {
+ loader = 'ajax';
+ path = name;
+ }
+
+ //build the params object
+ var params = {
+ id: name,
+ path: path,
+ method: loader,
+ parser: 'source',
+ async: false,
+ fetchTemplateSource: true
+ };
+
+ //default ignore_missing to false
+ if (typeof ignore_missing === 'undefined') {
+ ignore_missing = false;
+ }
+
+ //try to load the remote template
+ //
+ //on exception, log it
+ try {
+ templateSource = Twig.Templates.loadRemote(name, params);
+
+ //if the template is undefined or null, set the template to an empty string and do NOT flip the
+ // boolean indicating we found the template
+ //
+ //else, all is good! flip the boolean indicating we found the template
+ if (typeof templateSource === 'undefined' || templateSource === null) {
+ templateSource = '';
+ } else {
+ templateFound = true;
+ }
+ } catch (e) {
+ Twig.log.debug('Twig.functions.source: ', 'Problem loading template ', e);
+ }
+
+ //if the template was NOT found AND we are not ignoring missing templates, return the same message
+ // that is returned by the PHP implementation of the twig source() function
+ //
+ //else, return the template source
+ if (!templateFound && !ignore_missing) {
+ return TEMPLATE_NOT_FOUND_MESSAGE.replace('{name}', name);
+ } else {
+ return templateSource;
+ }
+ }
+ };
+
+ Twig._function = function(_function, value, params) {
+ if (!Twig.functions[_function]) {
+ throw "Unable to find function " + _function;
+ }
+ return Twig.functions[_function](value, params);
+ };
+
+ Twig._function.extend = function(_function, definition) {
+ Twig.functions[_function] = definition;
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // ## twig.lib.js
+ //
+ // This file contains 3rd party libraries used within twig.
+ //
+ // Copies of the licenses for the code included here can be found in the
+ // LICENSES.md file.
+ //
+
+ module.exports = function(Twig) {
+
+ // Namespace for libraries
+ Twig.lib = { };
+
+ Twig.lib.sprintf = __webpack_require__(8);
+ Twig.lib.vsprintf = __webpack_require__(9);
+ Twig.lib.round = __webpack_require__(10);
+ Twig.lib.max = __webpack_require__(11);
+ Twig.lib.min = __webpack_require__(12);
+ Twig.lib.strip_tags = __webpack_require__(13);
+ Twig.lib.strtotime = __webpack_require__(14);
+ Twig.lib.date = __webpack_require__(15);
+ Twig.lib.boolval = __webpack_require__(16);
+
+ Twig.lib.is = function(type, obj) {
+ var clas = Object.prototype.toString.call(obj).slice(8, -1);
+ return obj !== undefined && obj !== null && clas === type;
+ };
+
+ // shallow-copy an object
+ Twig.lib.copy = function(src) {
+ var target = {},
+ key;
+ for (key in src)
+ target[key] = src[key];
+
+ return target;
+ };
+
+ Twig.lib.extend = function (src, add) {
+ var keys = Object.keys(add),
+ i;
+
+ i = keys.length;
+
+ while (i--) {
+ src[keys[i]] = add[keys[i]];
+ }
+
+ return src;
+ };
+
+ Twig.lib.replaceAll = function(string, search, replace) {
+ return string.split(search).join(replace);
+ };
+
+ // chunk an array (arr) into arrays of (size) items, returns an array of arrays, or an empty array on invalid input
+ Twig.lib.chunkArray = function (arr, size) {
+ var returnVal = [],
+ x = 0,
+ len = arr.length;
+
+ if (size < 1 || !Twig.lib.is("Array", arr)) {
+ return [];
+ }
+
+ while (x < len) {
+ returnVal.push(arr.slice(x, x += size));
+ }
+
+ return returnVal;
+ };
+
+ return Twig;
+ };
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function sprintf() {
+ // discuss at: http://locutus.io/php/sprintf/
+ // original by: Ash Searle (http://hexmen.com/blog/)
+ // improved by: Michael White (http://getsprink.com)
+ // improved by: Jack
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Dj
+ // improved by: Allidylls
+ // input by: Paulo Freitas
+ // input by: Brett Zamir (http://brett-zamir.me)
+ // example 1: sprintf("%01.2f", 123.1)
+ // returns 1: '123.10'
+ // example 2: sprintf("[%10s]", 'monkey')
+ // returns 2: '[ monkey]'
+ // example 3: sprintf("[%'#10s]", 'monkey')
+ // returns 3: '[####monkey]'
+ // example 4: sprintf("%d", 123456789012345)
+ // returns 4: '123456789012345'
+ // example 5: sprintf('%-03s', 'E')
+ // returns 5: 'E00'
+
+ var regex = /%%|%(\d+\$)?([\-+'#0 ]*)(\*\d+\$|\*|\d+)?(?:\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g;
+ var a = arguments;
+ var i = 0;
+ var format = a[i++];
+
+ var _pad = function _pad(str, len, chr, leftJustify) {
+ if (!chr) {
+ chr = ' ';
+ }
+ var padding = str.length >= len ? '' : new Array(1 + len - str.length >>> 0).join(chr);
+ return leftJustify ? str + padding : padding + str;
+ };
+
+ var justify = function justify(value, prefix, leftJustify, minWidth, zeroPad, customPadChar) {
+ var diff = minWidth - value.length;
+ if (diff > 0) {
+ if (leftJustify || !zeroPad) {
+ value = _pad(value, minWidth, customPadChar, leftJustify);
+ } else {
+ value = [value.slice(0, prefix.length), _pad('', diff, '0', true), value.slice(prefix.length)].join('');
+ }
+ }
+ return value;
+ };
+
+ var _formatBaseX = function _formatBaseX(value, base, prefix, leftJustify, minWidth, precision, zeroPad) {
+ // Note: casts negative numbers to positive ones
+ var number = value >>> 0;
+ prefix = prefix && number && {
+ '2': '0b',
+ '8': '0',
+ '16': '0x'
+ }[base] || '';
+ value = prefix + _pad(number.toString(base), precision || 0, '0', false);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad);
+ };
+
+ // _formatString()
+ var _formatString = function _formatString(value, leftJustify, minWidth, precision, zeroPad, customPadChar) {
+ if (precision !== null && precision !== undefined) {
+ value = value.slice(0, precision);
+ }
+ return justify(value, '', leftJustify, minWidth, zeroPad, customPadChar);
+ };
+
+ // doFormat()
+ var doFormat = function doFormat(substring, valueIndex, flags, minWidth, precision, type) {
+ var number, prefix, method, textTransform, value;
+
+ if (substring === '%%') {
+ return '%';
+ }
+
+ // parse flags
+ var leftJustify = false;
+ var positivePrefix = '';
+ var zeroPad = false;
+ var prefixBaseX = false;
+ var customPadChar = ' ';
+ var flagsl = flags.length;
+ var j;
+ for (j = 0; j < flagsl; j++) {
+ switch (flags.charAt(j)) {
+ case ' ':
+ positivePrefix = ' ';
+ break;
+ case '+':
+ positivePrefix = '+';
+ break;
+ case '-':
+ leftJustify = true;
+ break;
+ case "'":
+ customPadChar = flags.charAt(j + 1);
+ break;
+ case '0':
+ zeroPad = true;
+ customPadChar = '0';
+ break;
+ case '#':
+ prefixBaseX = true;
+ break;
+ }
+ }
+
+ // parameters may be null, undefined, empty-string or real valued
+ // we want to ignore null, undefined and empty-string values
+ if (!minWidth) {
+ minWidth = 0;
+ } else if (minWidth === '*') {
+ minWidth = +a[i++];
+ } else if (minWidth.charAt(0) === '*') {
+ minWidth = +a[minWidth.slice(1, -1)];
+ } else {
+ minWidth = +minWidth;
+ }
+
+ // Note: undocumented perl feature:
+ if (minWidth < 0) {
+ minWidth = -minWidth;
+ leftJustify = true;
+ }
+
+ if (!isFinite(minWidth)) {
+ throw new Error('sprintf: (minimum-)width must be finite');
+ }
+
+ if (!precision) {
+ precision = 'fFeE'.indexOf(type) > -1 ? 6 : type === 'd' ? 0 : undefined;
+ } else if (precision === '*') {
+ precision = +a[i++];
+ } else if (precision.charAt(0) === '*') {
+ precision = +a[precision.slice(1, -1)];
+ } else {
+ precision = +precision;
+ }
+
+ // grab value using valueIndex if required?
+ value = valueIndex ? a[valueIndex.slice(0, -1)] : a[i++];
+
+ switch (type) {
+ case 's':
+ return _formatString(value + '', leftJustify, minWidth, precision, zeroPad, customPadChar);
+ case 'c':
+ return _formatString(String.fromCharCode(+value), leftJustify, minWidth, precision, zeroPad);
+ case 'b':
+ return _formatBaseX(value, 2, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
+ case 'o':
+ return _formatBaseX(value, 8, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
+ case 'x':
+ return _formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
+ case 'X':
+ return _formatBaseX(value, 16, prefixBaseX, leftJustify, minWidth, precision, zeroPad).toUpperCase();
+ case 'u':
+ return _formatBaseX(value, 10, prefixBaseX, leftJustify, minWidth, precision, zeroPad);
+ case 'i':
+ case 'd':
+ number = +value || 0;
+ // Plain Math.round doesn't just truncate
+ number = Math.round(number - number % 1);
+ prefix = number < 0 ? '-' : positivePrefix;
+ value = prefix + _pad(String(Math.abs(number)), precision, '0', false);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad);
+ case 'e':
+ case 'E':
+ case 'f': // @todo: Should handle locales (as per setlocale)
+ case 'F':
+ case 'g':
+ case 'G':
+ number = +value;
+ prefix = number < 0 ? '-' : positivePrefix;
+ method = ['toExponential', 'toFixed', 'toPrecision']['efg'.indexOf(type.toLowerCase())];
+ textTransform = ['toString', 'toUpperCase']['eEfFgG'.indexOf(type) % 2];
+ value = prefix + Math.abs(number)[method](precision);
+ return justify(value, prefix, leftJustify, minWidth, zeroPad)[textTransform]();
+ default:
+ return substring;
+ }
+ };
+
+ return format.replace(regex, doFormat);
+ };
+ //# sourceMappingURL=sprintf.js.map
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+ 'use strict';
+
+ module.exports = function vsprintf(format, args) {
+ // discuss at: http://locutus.io/php/vsprintf/
+ // original by: ejsanders
+ // example 1: vsprintf('%04d-%02d-%02d', [1988, 8, 1])
+ // returns 1: '1988-08-01'
+
+ var sprintf = __webpack_require__(8);
+
+ return sprintf.apply(this, [format].concat(args));
+ };
+ //# sourceMappingURL=vsprintf.js.map
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function round(value, precision, mode) {
+ // discuss at: http://locutus.io/php/round/
+ // original by: Philip Peterson
+ // revised by: Onno Marsman (https://twitter.com/onnomarsman)
+ // revised by: T.Wild
+ // revised by: Rafał Kukawski (http://blog.kukawski.pl)
+ // input by: Greenseed
+ // input by: meo
+ // input by: William
+ // input by: Josep Sanz (http://www.ws3.es/)
+ // bugfixed by: Brett Zamir (http://brett-zamir.me)
+ // note 1: Great work. Ideas for improvement:
+ // note 1: - code more compliant with developer guidelines
+ // note 1: - for implementing PHP constant arguments look at
+ // note 1: the pathinfo() function, it offers the greatest
+ // note 1: flexibility & compatibility possible
+ // example 1: round(1241757, -3)
+ // returns 1: 1242000
+ // example 2: round(3.6)
+ // returns 2: 4
+ // example 3: round(2.835, 2)
+ // returns 3: 2.84
+ // example 4: round(1.1749999999999, 2)
+ // returns 4: 1.17
+ // example 5: round(58551.799999999996, 2)
+ // returns 5: 58551.8
+
+ var m, f, isHalf, sgn; // helper variables
+ // making sure precision is integer
+ precision |= 0;
+ m = Math.pow(10, precision);
+ value *= m;
+ // sign of the number
+ sgn = value > 0 | -(value < 0);
+ isHalf = value % 1 === 0.5 * sgn;
+ f = Math.floor(value);
+
+ if (isHalf) {
+ switch (mode) {
+ case 'PHP_ROUND_HALF_DOWN':
+ // rounds .5 toward zero
+ value = f + (sgn < 0);
+ break;
+ case 'PHP_ROUND_HALF_EVEN':
+ // rouds .5 towards the next even integer
+ value = f + f % 2 * sgn;
+ break;
+ case 'PHP_ROUND_HALF_ODD':
+ // rounds .5 towards the next odd integer
+ value = f + !(f % 2);
+ break;
+ default:
+ // rounds .5 away from zero
+ value = f + (sgn > 0);
+ }
+ }
+
+ return (isHalf ? value : Math.round(value)) / m;
+ };
+ //# sourceMappingURL=round.js.map
+
+/***/ },
+/* 11 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+ module.exports = function max() {
+ // discuss at: http://locutus.io/php/max/
+ // original by: Onno Marsman (https://twitter.com/onnomarsman)
+ // revised by: Onno Marsman (https://twitter.com/onnomarsman)
+ // improved by: Jack
+ // note 1: Long code cause we're aiming for maximum PHP compatibility
+ // example 1: max(1, 3, 5, 6, 7)
+ // returns 1: 7
+ // example 2: max([2, 4, 5])
+ // returns 2: 5
+ // example 3: max(0, 'hello')
+ // returns 3: 0
+ // example 4: max('hello', 0)
+ // returns 4: 'hello'
+ // example 5: max(-1, 'hello')
+ // returns 5: 'hello'
+ // example 6: max([2, 4, 8], [2, 5, 7])
+ // returns 6: [2, 5, 7]
+
+ var ar;
+ var retVal;
+ var i = 0;
+ var n = 0;
+ var argv = arguments;
+ var argc = argv.length;
+ var _obj2Array = function _obj2Array(obj) {
+ if (Object.prototype.toString.call(obj) === '[object Array]') {
+ return obj;
+ } else {
+ var ar = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ ar.push(obj[i]);
+ }
+ }
+ return ar;
+ }
+ };
+ var _compare = function _compare(current, next) {
+ var i = 0;
+ var n = 0;
+ var tmp = 0;
+ var nl = 0;
+ var cl = 0;
+
+ if (current === next) {
+ return 0;
+ } else if ((typeof current === 'undefined' ? 'undefined' : _typeof(current)) === 'object') {
+ if ((typeof next === 'undefined' ? 'undefined' : _typeof(next)) === 'object') {
+ current = _obj2Array(current);
+ next = _obj2Array(next);
+ cl = current.length;
+ nl = next.length;
+ if (nl > cl) {
+ return 1;
+ } else if (nl < cl) {
+ return -1;
+ }
+ for (i = 0, n = cl; i < n; ++i) {
+ tmp = _compare(current[i], next[i]);
+ if (tmp === 1) {
+ return 1;
+ } else if (tmp === -1) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ return -1;
+ } else if ((typeof next === 'undefined' ? 'undefined' : _typeof(next)) === 'object') {
+ return 1;
+ } else if (isNaN(next) && !isNaN(current)) {
+ if (current === 0) {
+ return 0;
+ }
+ return current < 0 ? 1 : -1;
+ } else if (isNaN(current) && !isNaN(next)) {
+ if (next === 0) {
+ return 0;
+ }
+ return next > 0 ? 1 : -1;
+ }
+
+ if (next === current) {
+ return 0;
+ }
+
+ return next > current ? 1 : -1;
+ };
+
+ if (argc === 0) {
+ throw new Error('At least one value should be passed to max()');
+ } else if (argc === 1) {
+ if (_typeof(argv[0]) === 'object') {
+ ar = _obj2Array(argv[0]);
+ } else {
+ throw new Error('Wrong parameter count for max()');
+ }
+ if (ar.length === 0) {
+ throw new Error('Array must contain at least one element for max()');
+ }
+ } else {
+ ar = argv;
+ }
+
+ retVal = ar[0];
+ for (i = 1, n = ar.length; i < n; ++i) {
+ if (_compare(retVal, ar[i]) === 1) {
+ retVal = ar[i];
+ }
+ }
+
+ return retVal;
+ };
+ //# sourceMappingURL=max.js.map
+
+/***/ },
+/* 12 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+ module.exports = function min() {
+ // discuss at: http://locutus.io/php/min/
+ // original by: Onno Marsman (https://twitter.com/onnomarsman)
+ // revised by: Onno Marsman (https://twitter.com/onnomarsman)
+ // improved by: Jack
+ // note 1: Long code cause we're aiming for maximum PHP compatibility
+ // example 1: min(1, 3, 5, 6, 7)
+ // returns 1: 1
+ // example 2: min([2, 4, 5])
+ // returns 2: 2
+ // example 3: min(0, 'hello')
+ // returns 3: 0
+ // example 4: min('hello', 0)
+ // returns 4: 'hello'
+ // example 5: min(-1, 'hello')
+ // returns 5: -1
+ // example 6: min([2, 4, 8], [2, 5, 7])
+ // returns 6: [2, 4, 8]
+
+ var ar;
+ var retVal;
+ var i = 0;
+ var n = 0;
+ var argv = arguments;
+ var argc = argv.length;
+ var _obj2Array = function _obj2Array(obj) {
+ if (Object.prototype.toString.call(obj) === '[object Array]') {
+ return obj;
+ }
+ var ar = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ ar.push(obj[i]);
+ }
+ }
+ return ar;
+ };
+
+ var _compare = function _compare(current, next) {
+ var i = 0;
+ var n = 0;
+ var tmp = 0;
+ var nl = 0;
+ var cl = 0;
+
+ if (current === next) {
+ return 0;
+ } else if ((typeof current === 'undefined' ? 'undefined' : _typeof(current)) === 'object') {
+ if ((typeof next === 'undefined' ? 'undefined' : _typeof(next)) === 'object') {
+ current = _obj2Array(current);
+ next = _obj2Array(next);
+ cl = current.length;
+ nl = next.length;
+ if (nl > cl) {
+ return 1;
+ } else if (nl < cl) {
+ return -1;
+ }
+ for (i = 0, n = cl; i < n; ++i) {
+ tmp = _compare(current[i], next[i]);
+ if (tmp === 1) {
+ return 1;
+ } else if (tmp === -1) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ return -1;
+ } else if ((typeof next === 'undefined' ? 'undefined' : _typeof(next)) === 'object') {
+ return 1;
+ } else if (isNaN(next) && !isNaN(current)) {
+ if (current === 0) {
+ return 0;
+ }
+ return current < 0 ? 1 : -1;
+ } else if (isNaN(current) && !isNaN(next)) {
+ if (next === 0) {
+ return 0;
+ }
+ return next > 0 ? 1 : -1;
+ }
+
+ if (next === current) {
+ return 0;
+ }
+
+ return next > current ? 1 : -1;
+ };
+
+ if (argc === 0) {
+ throw new Error('At least one value should be passed to min()');
+ } else if (argc === 1) {
+ if (_typeof(argv[0]) === 'object') {
+ ar = _obj2Array(argv[0]);
+ } else {
+ throw new Error('Wrong parameter count for min()');
+ }
+
+ if (ar.length === 0) {
+ throw new Error('Array must contain at least one element for min()');
+ }
+ } else {
+ ar = argv;
+ }
+
+ retVal = ar[0];
+
+ for (i = 1, n = ar.length; i < n; ++i) {
+ if (_compare(retVal, ar[i]) === -1) {
+ retVal = ar[i];
+ }
+ }
+
+ return retVal;
+ };
+ //# sourceMappingURL=min.js.map
+
+/***/ },
+/* 13 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function strip_tags(input, allowed) {
+ // eslint-disable-line camelcase
+ // discuss at: http://locutus.io/php/strip_tags/
+ // original by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Luke Godfrey
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // input by: Pul
+ // input by: Alex
+ // input by: Marc Palau
+ // input by: Brett Zamir (http://brett-zamir.me)
+ // input by: Bobby Drake
+ // input by: Evertjan Garretsen
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: Eric Nagel
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: Tomasz Wesolowski
+ // revised by: Rafał Kukawski (http://blog.kukawski.pl)
+ // example 1: strip_tags('<p>Kevin</p> <br /><b>van</b> <i>Zonneveld</i>', '<i><b>')
+ // returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
+ // example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>')
+ // returns 2: '<p>Kevin van Zonneveld</p>'
+ // example 3: strip_tags("<a href='http://kvz.io'>Kevin van Zonneveld</a>", "<a>")
+ // returns 3: "<a href='http://kvz.io'>Kevin van Zonneveld</a>"
+ // example 4: strip_tags('1 < 5 5 > 1')
+ // returns 4: '1 < 5 5 > 1'
+ // example 5: strip_tags('1 <br/> 1')
+ // returns 5: '1 1'
+ // example 6: strip_tags('1 <br/> 1', '<br>')
+ // returns 6: '1 <br/> 1'
+ // example 7: strip_tags('1 <br/> 1', '<br><br/>')
+ // returns 7: '1 <br/> 1'
+
+ // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
+ allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
+
+ var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
+ var commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
+
+ return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
+ return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
+ });
+ };
+ //# sourceMappingURL=strip_tags.js.map
+
+/***/ },
+/* 14 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function strtotime(text, now) {
+ // discuss at: http://locutus.io/php/strtotime/
+ // original by: Caio Ariede (http://caioariede.com)
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: Caio Ariede (http://caioariede.com)
+ // improved by: A. Matías Quezada (http://amatiasq.com)
+ // improved by: preuter
+ // improved by: Brett Zamir (http://brett-zamir.me)
+ // improved by: Mirko Faber
+ // input by: David
+ // bugfixed by: Wagner B. Soares
+ // bugfixed by: Artur Tchernychev
+ // bugfixed by: Stephan Bösch-Plepelits (http://github.com/plepe)
+ // note 1: Examples all have a fixed timestamp to prevent
+ // note 1: tests to fail because of variable time(zones)
+ // example 1: strtotime('+1 day', 1129633200)
+ // returns 1: 1129719600
+ // example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200)
+ // returns 2: 1130425202
+ // example 3: strtotime('last month', 1129633200)
+ // returns 3: 1127041200
+ // example 4: strtotime('2009-05-04 08:30:00 GMT')
+ // returns 4: 1241425800
+ // example 5: strtotime('2009-05-04 08:30:00+00')
+ // returns 5: 1241425800
+ // example 6: strtotime('2009-05-04 08:30:00+02:00')
+ // returns 6: 1241418600
+ // example 7: strtotime('2009-05-04T08:30:00Z')
+ // returns 7: 1241425800
+
+ var parsed;
+ var match;
+ var today;
+ var year;
+ var date;
+ var days;
+ var ranges;
+ var len;
+ var times;
+ var regex;
+ var i;
+ var fail = false;
+
+ if (!text) {
+ return fail;
+ }
+
+ // Unecessary spaces
+ text = text.replace(/^\s+|\s+$/g, '').replace(/\s{2,}/g, ' ').replace(/[\t\r\n]/g, '').toLowerCase();
+
+ // in contrast to php, js Date.parse function interprets:
+ // dates given as yyyy-mm-dd as in timezone: UTC,
+ // dates with "." or "-" as MDY instead of DMY
+ // dates with two-digit years differently
+ // etc...etc...
+ // ...therefore we manually parse lots of common date formats
+ var pattern = new RegExp(['^(\\d{1,4})', '([\\-\\.\\/:])', '(\\d{1,2})', '([\\-\\.\\/:])', '(\\d{1,4})', '(?:\\s(\\d{1,2}):(\\d{2})?:?(\\d{2})?)?', '(?:\\s([A-Z]+)?)?$'].join(''));
+ match = text.match(pattern);
+
+ if (match && match[2] === match[4]) {
+ if (match[1] > 1901) {
+ switch (match[2]) {
+ case '-':
+ // YYYY-M-D
+ if (match[3] > 12 || match[5] > 31) {
+ return fail;
+ }
+
+ return new Date(match[1], parseInt(match[3], 10) - 1, match[5], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ case '.':
+ // YYYY.M.D is not parsed by strtotime()
+ return fail;
+ case '/':
+ // YYYY/M/D
+ if (match[3] > 12 || match[5] > 31) {
+ return fail;
+ }
+
+ return new Date(match[1], parseInt(match[3], 10) - 1, match[5], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ }
+ } else if (match[5] > 1901) {
+ switch (match[2]) {
+ case '-':
+ // D-M-YYYY
+ if (match[3] > 12 || match[1] > 31) {
+ return fail;
+ }
+
+ return new Date(match[5], parseInt(match[3], 10) - 1, match[1], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ case '.':
+ // D.M.YYYY
+ if (match[3] > 12 || match[1] > 31) {
+ return fail;
+ }
+
+ return new Date(match[5], parseInt(match[3], 10) - 1, match[1], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ case '/':
+ // M/D/YYYY
+ if (match[1] > 12 || match[3] > 31) {
+ return fail;
+ }
+
+ return new Date(match[5], parseInt(match[1], 10) - 1, match[3], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ }
+ } else {
+ switch (match[2]) {
+ case '-':
+ // YY-M-D
+ if (match[3] > 12 || match[5] > 31 || match[1] < 70 && match[1] > 38) {
+ return fail;
+ }
+
+ year = match[1] >= 0 && match[1] <= 38 ? +match[1] + 2000 : match[1];
+ return new Date(year, parseInt(match[3], 10) - 1, match[5], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ case '.':
+ // D.M.YY or H.MM.SS
+ if (match[5] >= 70) {
+ // D.M.YY
+ if (match[3] > 12 || match[1] > 31) {
+ return fail;
+ }
+
+ return new Date(match[5], parseInt(match[3], 10) - 1, match[1], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ }
+ if (match[5] < 60 && !match[6]) {
+ // H.MM.SS
+ if (match[1] > 23 || match[3] > 59) {
+ return fail;
+ }
+
+ today = new Date();
+ return new Date(today.getFullYear(), today.getMonth(), today.getDate(), match[1] || 0, match[3] || 0, match[5] || 0, match[9] || 0) / 1000;
+ }
+
+ // invalid format, cannot be parsed
+ return fail;
+ case '/':
+ // M/D/YY
+ if (match[1] > 12 || match[3] > 31 || match[5] < 70 && match[5] > 38) {
+ return fail;
+ }
+
+ year = match[5] >= 0 && match[5] <= 38 ? +match[5] + 2000 : match[5];
+ return new Date(year, parseInt(match[1], 10) - 1, match[3], match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
+ case ':':
+ // HH:MM:SS
+ if (match[1] > 23 || match[3] > 59 || match[5] > 59) {
+ return fail;
+ }
+
+ today = new Date();
+ return new Date(today.getFullYear(), today.getMonth(), today.getDate(), match[1] || 0, match[3] || 0, match[5] || 0) / 1000;
+ }
+ }
+ }
+
+ // other formats and "now" should be parsed by Date.parse()
+ if (text === 'now') {
+ return now === null || isNaN(now) ? new Date().getTime() / 1000 | 0 : now | 0;
+ }
+ if (!isNaN(parsed = Date.parse(text))) {
+ return parsed / 1000 | 0;
+ }
+ // Browsers !== Chrome have problems parsing ISO 8601 date strings, as they do
+ // not accept lower case characters, space, or shortened time zones.
+ // Therefore, fix these problems and try again.
+ // Examples:
+ // 2015-04-15 20:33:59+02
+ // 2015-04-15 20:33:59z
+ // 2015-04-15t20:33:59+02:00
+ pattern = new RegExp(['^([0-9]{4}-[0-9]{2}-[0-9]{2})', '[ t]', '([0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?)', '([\\+-][0-9]{2}(:[0-9]{2})?|z)'].join(''));
+ match = text.match(pattern);
+ if (match) {
+ // @todo: time zone information
+ if (match[4] === 'z') {
+ match[4] = 'Z';
+ } else if (match[4].match(/^([\+-][0-9]{2})$/)) {
+ match[4] = match[4] + ':00';
+ }
+
+ if (!isNaN(parsed = Date.parse(match[1] + 'T' + match[2] + match[4]))) {
+ return parsed / 1000 | 0;
+ }
+ }
+
+ date = now ? new Date(now * 1000) : new Date();
+ days = {
+ 'sun': 0,
+ 'mon': 1,
+ 'tue': 2,
+ 'wed': 3,
+ 'thu': 4,
+ 'fri': 5,
+ 'sat': 6
+ };
+ ranges = {
+ 'yea': 'FullYear',
+ 'mon': 'Month',
+ 'day': 'Date',
+ 'hou': 'Hours',
+ 'min': 'Minutes',
+ 'sec': 'Seconds'
+ };
+
+ function lastNext(type, range, modifier) {
+ var diff;
+ var day = days[range];
+
+ if (typeof day !== 'undefined') {
+ diff = day - date.getDay();
+
+ if (diff === 0) {
+ diff = 7 * modifier;
+ } else if (diff > 0 && type === 'last') {
+ diff -= 7;
+ } else if (diff < 0 && type === 'next') {
+ diff += 7;
+ }
+
+ date.setDate(date.getDate() + diff);
+ }
+ }
+
+ function process(val) {
+ // @todo: Reconcile this with regex using \s, taking into account
+ // browser issues with split and regexes
+ var splt = val.split(' ');
+ var type = splt[0];
+ var range = splt[1].substring(0, 3);
+ var typeIsNumber = /\d+/.test(type);
+ var ago = splt[2] === 'ago';
+ var num = (type === 'last' ? -1 : 1) * (ago ? -1 : 1);
+
+ if (typeIsNumber) {
+ num *= parseInt(type, 10);
+ }
+
+ if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
+ return date['set' + ranges[range]](date['get' + ranges[range]]() + num);
+ }
+
+ if (range === 'wee') {
+ return date.setDate(date.getDate() + num * 7);
+ }
+
+ if (type === 'next' || type === 'last') {
+ lastNext(type, range, num);
+ } else if (!typeIsNumber) {
+ return false;
+ }
+
+ return true;
+ }
+
+ times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' + '|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' + '|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)';
+ regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?';
+
+ match = text.match(new RegExp(regex, 'gi'));
+ if (!match) {
+ return fail;
+ }
+
+ for (i = 0, len = match.length; i < len; i++) {
+ if (!process(match[i])) {
+ return fail;
+ }
+ }
+
+ return date.getTime() / 1000;
+ };
+ //# sourceMappingURL=strtotime.js.map
+
+/***/ },
+/* 15 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function date(format, timestamp) {
+ // discuss at: http://locutus.io/php/date/
+ // original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
+ // original by: gettimeofday
+ // parts by: Peter-Paul Koch (http://www.quirksmode.org/js/beat.html)
+ // improved by: Kevin van Zonneveld (http://kvz.io)
+ // improved by: MeEtc (http://yass.meetcweb.com)
+ // improved by: Brad Touesnard
+ // improved by: Tim Wiel
+ // improved by: Bryan Elliott
+ // improved by: David Randall
+ // improved by: Theriault (https://github.com/Theriault)
+ // improved by: Theriault (https://github.com/Theriault)
+ // improved by: Brett Zamir (http://brett-zamir.me)
+ // improved by: Theriault (https://github.com/Theriault)
+ // improved by: Thomas Beaucourt (http://www.webapp.fr)
+ // improved by: JT
+ // improved by: Theriault (https://github.com/Theriault)
+ // improved by: Rafał Kukawski (http://blog.kukawski.pl)
+ // improved by: Theriault (https://github.com/Theriault)
+ // input by: Brett Zamir (http://brett-zamir.me)
+ // input by: majak
+ // input by: Alex
+ // input by: Martin
+ // input by: Alex Wilson
+ // input by: Haravikk
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: majak
+ // bugfixed by: Kevin van Zonneveld (http://kvz.io)
+ // bugfixed by: Brett Zamir (http://brett-zamir.me)
+ // bugfixed by: omid (http://locutus.io/php/380:380#comment_137122)
+ // bugfixed by: Chris (http://www.devotis.nl/)
+ // note 1: Uses global: locutus to store the default timezone
+ // note 1: Although the function potentially allows timezone info
+ // note 1: (see notes), it currently does not set
+ // note 1: per a timezone specified by date_default_timezone_set(). Implementers might use
+ // note 1: $locutus.currentTimezoneOffset and
+ // note 1: $locutus.currentTimezoneDST set by that function
+ // note 1: in order to adjust the dates in this function
+ // note 1: (or our other date functions!) accordingly
+ // example 1: date('H:m:s \\m \\i\\s \\m\\o\\n\\t\\h', 1062402400)
+ // returns 1: '07:09:40 m is month'
+ // example 2: date('F j, Y, g:i a', 1062462400)
+ // returns 2: 'September 2, 2003, 12:26 am'
+ // example 3: date('Y W o', 1062462400)
+ // returns 3: '2003 36 2003'
+ // example 4: var $x = date('Y m d', (new Date()).getTime() / 1000)
+ // example 4: $x = $x + ''
+ // example 4: var $result = $x.length // 2009 01 09
+ // returns 4: 10
+ // example 5: date('W', 1104534000)
+ // returns 5: '52'
+ // example 6: date('B t', 1104534000)
+ // returns 6: '999 31'
+ // example 7: date('W U', 1293750000.82); // 2010-12-31
+ // returns 7: '52 1293750000'
+ // example 8: date('W', 1293836400); // 2011-01-01
+ // returns 8: '52'
+ // example 9: date('W Y-m-d', 1293974054); // 2011-01-02
+ // returns 9: '52 2011-01-02'
+ // test: skip-1 skip-2 skip-5
+
+ var jsdate, f;
+ // Keep this here (works, but for code commented-out below for file size reasons)
+ // var tal= [];
+ var txtWords = ['Sun', 'Mon', 'Tues', 'Wednes', 'Thurs', 'Fri', 'Satur', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+ // trailing backslash -> (dropped)
+ // a backslash followed by any character (including backslash) -> the character
+ // empty string -> empty string
+ var formatChr = /\\?(.?)/gi;
+ var formatChrCb = function formatChrCb(t, s) {
+ return f[t] ? f[t]() : s;
+ };
+ var _pad = function _pad(n, c) {
+ n = String(n);
+ while (n.length < c) {
+ n = '0' + n;
+ }
+ return n;
+ };
+ f = {
+ // Day
+ d: function d() {
+ // Day of month w/leading 0; 01..31
+ return _pad(f.j(), 2);
+ },
+ D: function D() {
+ // Shorthand day name; Mon...Sun
+ return f.l().slice(0, 3);
+ },
+ j: function j() {
+ // Day of month; 1..31
+ return jsdate.getDate();
+ },
+ l: function l() {
+ // Full day name; Monday...Sunday
+ return txtWords[f.w()] + 'day';
+ },
+ N: function N() {
+ // ISO-8601 day of week; 1[Mon]..7[Sun]
+ return f.w() || 7;
+ },
+ S: function S() {
+ // Ordinal suffix for day of month; st, nd, rd, th
+ var j = f.j();
+ var i = j % 10;
+ if (i <= 3 && parseInt(j % 100 / 10, 10) === 1) {
+ i = 0;
+ }
+ return ['st', 'nd', 'rd'][i - 1] || 'th';
+ },
+ w: function w() {
+ // Day of week; 0[Sun]..6[Sat]
+ return jsdate.getDay();
+ },
+ z: function z() {
+ // Day of year; 0..365
+ var a = new Date(f.Y(), f.n() - 1, f.j());
+ var b = new Date(f.Y(), 0, 1);
+ return Math.round((a - b) / 864e5);
+ },
+
+ // Week
+ W: function W() {
+ // ISO-8601 week number
+ var a = new Date(f.Y(), f.n() - 1, f.j() - f.N() + 3);
+ var b = new Date(a.getFullYear(), 0, 4);
+ return _pad(1 + Math.round((a - b) / 864e5 / 7), 2);
+ },
+
+ // Month
+ F: function F() {
+ // Full month name; January...December
+ return txtWords[6 + f.n()];
+ },
+ m: function m() {
+ // Month w/leading 0; 01...12
+ return _pad(f.n(), 2);
+ },
+ M: function M() {
+ // Shorthand month name; Jan...Dec
+ return f.F().slice(0, 3);
+ },
+ n: function n() {
+ // Month; 1...12
+ return jsdate.getMonth() + 1;
+ },
+ t: function t() {
+ // Days in month; 28...31
+ return new Date(f.Y(), f.n(), 0).getDate();
+ },
+
+ // Year
+ L: function L() {
+ // Is leap year?; 0 or 1
+ var j = f.Y();
+ return j % 4 === 0 & j % 100 !== 0 | j % 400 === 0;
+ },
+ o: function o() {
+ // ISO-8601 year
+ var n = f.n();
+ var W = f.W();
+ var Y = f.Y();
+ return Y + (n === 12 && W < 9 ? 1 : n === 1 && W > 9 ? -1 : 0);
+ },
+ Y: function Y() {
+ // Full year; e.g. 1980...2010
+ return jsdate.getFullYear();
+ },
+ y: function y() {
+ // Last two digits of year; 00...99
+ return f.Y().toString().slice(-2);
+ },
+
+ // Time
+ a: function a() {
+ // am or pm
+ return jsdate.getHours() > 11 ? 'pm' : 'am';
+ },
+ A: function A() {
+ // AM or PM
+ return f.a().toUpperCase();
+ },
+ B: function B() {
+ // Swatch Internet time; 000..999
+ var H = jsdate.getUTCHours() * 36e2;
+ // Hours
+ var i = jsdate.getUTCMinutes() * 60;
+ // Minutes
+ // Seconds
+ var s = jsdate.getUTCSeconds();
+ return _pad(Math.floor((H + i + s + 36e2) / 86.4) % 1e3, 3);
+ },
+ g: function g() {
+ // 12-Hours; 1..12
+ return f.G() % 12 || 12;
+ },
+ G: function G() {
+ // 24-Hours; 0..23
+ return jsdate.getHours();
+ },
+ h: function h() {
+ // 12-Hours w/leading 0; 01..12
+ return _pad(f.g(), 2);
+ },
+ H: function H() {
+ // 24-Hours w/leading 0; 00..23
+ return _pad(f.G(), 2);
+ },
+ i: function i() {
+ // Minutes w/leading 0; 00..59
+ return _pad(jsdate.getMinutes(), 2);
+ },
+ s: function s() {
+ // Seconds w/leading 0; 00..59
+ return _pad(jsdate.getSeconds(), 2);
+ },
+ u: function u() {
+ // Microseconds; 000000-999000
+ return _pad(jsdate.getMilliseconds() * 1000, 6);
+ },
+
+ // Timezone
+ e: function e() {
+ // Timezone identifier; e.g. Atlantic/Azores, ...
+ // The following works, but requires inclusion of the very large
+ // timezone_abbreviations_list() function.
+ /* return that.date_default_timezone_get();
+ */
+ var msg = 'Not supported (see source code of date() for timezone on how to add support)';
+ throw new Error(msg);
+ },
+ I: function I() {
+ // DST observed?; 0 or 1
+ // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
+ // If they are not equal, then DST is observed.
+ var a = new Date(f.Y(), 0);
+ // Jan 1
+ var c = Date.UTC(f.Y(), 0);
+ // Jan 1 UTC
+ var b = new Date(f.Y(), 6);
+ // Jul 1
+ // Jul 1 UTC
+ var d = Date.UTC(f.Y(), 6);
+ return a - c !== b - d ? 1 : 0;
+ },
+ O: function O() {
+ // Difference to GMT in hour format; e.g. +0200
+ var tzo = jsdate.getTimezoneOffset();
+ var a = Math.abs(tzo);
+ return (tzo > 0 ? '-' : '+') + _pad(Math.floor(a / 60) * 100 + a % 60, 4);
+ },
+ P: function P() {
+ // Difference to GMT w/colon; e.g. +02:00
+ var O = f.O();
+ return O.substr(0, 3) + ':' + O.substr(3, 2);
+ },
+ T: function T() {
+ // The following works, but requires inclusion of the very
+ // large timezone_abbreviations_list() function.
+ /* var abbr, i, os, _default;
+ if (!tal.length) {
+ tal = that.timezone_abbreviations_list();
+ }
+ if ($locutus && $locutus.default_timezone) {
+ _default = $locutus.default_timezone;
+ for (abbr in tal) {
+ for (i = 0; i < tal[abbr].length; i++) {
+ if (tal[abbr][i].timezone_id === _default) {
+ return abbr.toUpperCase();
+ }
+ }
+ }
+ }
+ for (abbr in tal) {
+ for (i = 0; i < tal[abbr].length; i++) {
+ os = -jsdate.getTimezoneOffset() * 60;
+ if (tal[abbr][i].offset === os) {
+ return abbr.toUpperCase();
+ }
+ }
+ }
+ */
+ return 'UTC';
+ },
+ Z: function Z() {
+ // Timezone offset in seconds (-43200...50400)
+ return -jsdate.getTimezoneOffset() * 60;
+ },
+
+ // Full Date/Time
+ c: function c() {
+ // ISO-8601 date.
+ return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb);
+ },
+ r: function r() {
+ // RFC 2822
+ return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb);
+ },
+ U: function U() {
+ // Seconds since UNIX epoch
+ return jsdate / 1000 | 0;
+ }
+ };
+
+ var _date = function _date(format, timestamp) {
+ jsdate = timestamp === undefined ? new Date() // Not provided
+ : timestamp instanceof Date ? new Date(timestamp) // JS Date()
+ : new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
+ ;
+ return format.replace(formatChr, formatChrCb);
+ };
+
+ return _date(format, timestamp);
+ };
+ //# sourceMappingURL=date.js.map
+
+/***/ },
+/* 16 */
+/***/ function(module, exports) {
+
+ 'use strict';
+
+ module.exports = function boolval(mixedVar) {
+ // original by: Will Rowe
+ // example 1: boolval(true)
+ // returns 1: true
+ // example 2: boolval(false)
+ // returns 2: false
+ // example 3: boolval(0)
+ // returns 3: false
+ // example 4: boolval(0.0)
+ // returns 4: false
+ // example 5: boolval('')
+ // returns 5: false
+ // example 6: boolval('0')
+ // returns 6: false
+ // example 7: boolval([])
+ // returns 7: false
+ // example 8: boolval('')
+ // returns 8: false
+ // example 9: boolval(null)
+ // returns 9: false
+ // example 10: boolval(undefined)
+ // returns 10: false
+ // example 11: boolval('true')
+ // returns 11: true
+
+ if (mixedVar === false) {
+ return false;
+ }
+
+ if (mixedVar === 0 || mixedVar === 0.0) {
+ return false;
+ }
+
+ if (mixedVar === '' || mixedVar === '0') {
+ return false;
+ }
+
+ if (Array.isArray(mixedVar) && mixedVar.length === 0) {
+ return false;
+ }
+
+ if (mixedVar === null || mixedVar === undefined) {
+ return false;
+ }
+
+ return true;
+ };
+ //# sourceMappingURL=boolval.js.map
+
+/***/ },
+/* 17 */
+/***/ function(module, exports) {
+
+ module.exports = function(Twig) {
+ 'use strict';
+
+ Twig.Templates.registerLoader('ajax', function(location, params, callback, error_callback) {
+ var template,
+ xmlhttp,
+ precompiled = params.precompiled,
+ parser = this.parsers[params.parser] || this.parser.twig;
+
+ if (typeof XMLHttpRequest === "undefined") {
+ throw new Twig.Error('Unsupported platform: Unable to do ajax requests ' +
+ 'because there is no "XMLHTTPRequest" implementation');
+ }
+
+ xmlhttp = new XMLHttpRequest();
+ xmlhttp.onreadystatechange = function() {
+ var data = null;
+
+ if(xmlhttp.readyState === 4) {
+ if (xmlhttp.status === 200 || (window.cordova && xmlhttp.status == 0)) {
+ Twig.log.debug("Got template ", xmlhttp.responseText);
+
+ if (precompiled === true) {
+ data = JSON.parse(xmlhttp.responseText);
+ } else {
+ data = xmlhttp.responseText;
+ }
+
+ params.url = location;
+ params.data = data;
+
+ template = parser.call(this, params);
+
+ if (typeof callback === 'function') {
+ callback(template);
+ }
+ } else {
+ if (typeof error_callback === 'function') {
+ error_callback(xmlhttp);
+ }
+ }
+ }
+ };
+ xmlhttp.open("GET", location, !!params.async);
+ xmlhttp.send();
+
+ if (params.async) {
+ // TODO: return deferred promise
+ return true;
+ } else {
+ return template;
+ }
+ });
+
+ };
+
+
+/***/ },
+/* 18 */
+/***/ function(module, exports, __webpack_require__) {
+
+ module.exports = function(Twig) {
+ 'use strict';
+
+ var fs, path;
+
+ try {
+ // require lib dependencies at runtime
+ fs = __webpack_require__(19);
+ path = __webpack_require__(20);
+ } catch (e) {
+ // NOTE: this is in a try/catch to avoid errors cross platform
+ }
+
+ Twig.Templates.registerLoader('fs', function(location, params, callback, error_callback) {
+ var template,
+ data = null,
+ precompiled = params.precompiled,
+ parser = this.parsers[params.parser] || this.parser.twig;
+
+ if (!fs || !path) {
+ throw new Twig.Error('Unsupported platform: Unable to load from file ' +
+ 'because there is no "fs" or "path" implementation');
+ }
+
+ var loadTemplateFn = function(err, data) {
+ if (err) {
+ if (typeof error_callback === 'function') {
+ error_callback(err);
+ }
+ return;
+ }
+
+ if (precompiled === true) {
+ data = JSON.parse(data);
+ }
+
+ params.data = data;
+ params.path = params.path || location;
+
+ // template is in data
+ template = parser.call(this, params);
+
+ if (typeof callback === 'function') {
+ callback(template);
+ }
+ };
+ params.path = params.path || location;
+
+ if (params.async) {
+ fs.stat(params.path, function (err, stats) {
+ if (err || !stats.isFile()) {
+ if (typeof error_callback === 'function') {
+ error_callback(new Twig.Error('Unable to find template file ' + params.path));
+ }
+ return;
+ }
+ fs.readFile(params.path, 'utf8', loadTemplateFn);
+ });
+ // TODO: return deferred promise
+ return true;
+ } else {
+ try {
+ if (!fs.statSync(params.path).isFile()) {
+ throw new Twig.Error('Unable to find template file ' + params.path);
+ }
+ } catch (err) {
+ throw new Twig.Error('Unable to find template file ' + params.path);
+ }
+ data = fs.readFileSync(params.path, 'utf8');
+ loadTemplateFn(undefined, data);
+ return template
+ }
+ });
+
+ };
+
+
+/***/ },
+/* 19 */
+/***/ function(module, exports) {
+
+ module.exports = require("fs");
+
+/***/ },
+/* 20 */
+/***/ function(module, exports) {
+
+ module.exports = require("path");
+
+/***/ },
+/* 21 */
+/***/ function(module, exports) {
+
+ // ## twig.logic.js
+ //
+ // This file handles tokenizing, compiling and parsing logic tokens. {% ... %}
+ module.exports = function (Twig) {
+ "use strict";
+
+ /**
+ * Namespace for logic handling.
+ */
+ Twig.logic = {};
+
+ /**
+ * Logic token types.
+ */
+ Twig.logic.type = {
+ if_: 'Twig.logic.type.if',
+ endif: 'Twig.logic.type.endif',
+ for_: 'Twig.logic.type.for',
+ endfor: 'Twig.logic.type.endfor',
+ else_: 'Twig.logic.type.else',
+ elseif: 'Twig.logic.type.elseif',
+ set: 'Twig.logic.type.set',
+ setcapture:'Twig.logic.type.setcapture',
+ endset: 'Twig.logic.type.endset',
+ filter: 'Twig.logic.type.filter',
+ endfilter: 'Twig.logic.type.endfilter',
+ shortblock: 'Twig.logic.type.shortblock',
+ block: 'Twig.logic.type.block',
+ endblock: 'Twig.logic.type.endblock',
+ extends_: 'Twig.logic.type.extends',
+ use: 'Twig.logic.type.use',
+ include: 'Twig.logic.type.include',
+ spaceless: 'Twig.logic.type.spaceless',
+ endspaceless: 'Twig.logic.type.endspaceless',
+ macro: 'Twig.logic.type.macro',
+ endmacro: 'Twig.logic.type.endmacro',
+ import_: 'Twig.logic.type.import',
+ from: 'Twig.logic.type.from',
+ embed: 'Twig.logic.type.embed',
+ endembed: 'Twig.logic.type.endembed'
+ };
+
+
+ // Regular expressions for handling logic tokens.
+ //
+ // Properties:
+ //
+ // type: The type of expression this matches
+ //
+ // regex: A regular expression that matches the format of the token
+ //
+ // next: What logic tokens (if any) pop this token off the logic stack. If empty, the
+ // logic token is assumed to not require an end tag and isn't push onto the stack.
+ //
+ // open: Does this tag open a logic expression or is it standalone. For example,
+ // {% endif %} cannot exist without an opening {% if ... %} tag, so open = false.
+ //
+ // Functions:
+ //
+ // compile: A function that handles compiling the token into an output token ready for
+ // parsing with the parse function.
+ //
+ // parse: A function that parses the compiled token into output (HTML / whatever the
+ // template represents).
+ Twig.logic.definitions = [
+ {
+ /**
+ * If type logic tokens.
+ *
+ * Format: {% if expression %}
+ */
+ type: Twig.logic.type.if_,
+ regex: /^if\s+([\s\S]+)$/,
+ next: [
+ Twig.logic.type.else_,
+ Twig.logic.type.elseif,
+ Twig.logic.type.endif
+ ],
+ open: true,
+ compile: function (token) {
+ var expression = token.match[1];
+ // Compile the expression.
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var output = '',
+ // Parse the expression
+ result = Twig.expression.parse.apply(this, [token.stack, context]);
+
+ // Start a new logic chain
+ chain = true;
+
+ if (Twig.lib.boolval(result)) {
+ chain = false;
+ // parse if output
+ output = Twig.parse.apply(this, [token.output, context]);
+ }
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+ {
+ /**
+ * Else if type logic tokens.
+ *
+ * Format: {% elseif expression %}
+ */
+ type: Twig.logic.type.elseif,
+ regex: /^elseif\s+([^\s].*)$/,
+ next: [
+ Twig.logic.type.else_,
+ Twig.logic.type.elseif,
+ Twig.logic.type.endif
+ ],
+ open: false,
+ compile: function (token) {
+ var expression = token.match[1];
+ // Compile the expression.
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var output = '',
+ result = Twig.expression.parse.apply(this, [token.stack, context]);
+
+ if (chain && Twig.lib.boolval(result)) {
+ chain = false;
+ // parse if output
+ output = Twig.parse.apply(this, [token.output, context]);
+ }
+
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+ {
+ /**
+ * Else if type logic tokens.
+ *
+ * Format: {% elseif expression %}
+ */
+ type: Twig.logic.type.else_,
+ regex: /^else$/,
+ next: [
+ Twig.logic.type.endif,
+ Twig.logic.type.endfor
+ ],
+ open: false,
+ parse: function (token, context, chain) {
+ var output = '';
+ if (chain) {
+ output = Twig.parse.apply(this, [token.output, context]);
+ }
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+ {
+ /**
+ * End if type logic tokens.
+ *
+ * Format: {% endif %}
+ */
+ type: Twig.logic.type.endif,
+ regex: /^endif$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * For type logic tokens.
+ *
+ * Format: {% for expression %}
+ */
+ type: Twig.logic.type.for_,
+ regex: /^for\s+([a-zA-Z0-9_,\s]+)\s+in\s+([^\s].*?)(?:\s+if\s+([^\s].*))?$/,
+ next: [
+ Twig.logic.type.else_,
+ Twig.logic.type.endfor
+ ],
+ open: true,
+ compile: function (token) {
+ var key_value = token.match[1],
+ expression = token.match[2],
+ conditional = token.match[3],
+ kv_split = null;
+
+ token.key_var = null;
+ token.value_var = null;
+
+ if (key_value.indexOf(",") >= 0) {
+ kv_split = key_value.split(',');
+ if (kv_split.length === 2) {
+ token.key_var = kv_split[0].trim();
+ token.value_var = kv_split[1].trim();
+ } else {
+ throw new Twig.Error("Invalid expression in for loop: " + key_value);
+ }
+ } else {
+ token.value_var = key_value;
+ }
+
+ // Valid expressions for a for loop
+ // for item in expression
+ // for key,item in expression
+
+ // Compile the expression.
+ token.expression = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ // Compile the conditional (if available)
+ if (conditional) {
+ token.conditional = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: conditional
+ }]).stack;
+ }
+
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, continue_chain) {
+ // Parse expression
+ var result = Twig.expression.parse.apply(this, [token.expression, context]),
+ output = [],
+ len,
+ index = 0,
+ keyset,
+ that = this,
+ conditional = token.conditional,
+ buildLoop = function(index, len) {
+ var isConditional = conditional !== undefined;
+ return {
+ index: index+1,
+ index0: index,
+ revindex: isConditional?undefined:len-index,
+ revindex0: isConditional?undefined:len-index-1,
+ first: (index === 0),
+ last: isConditional?undefined:(index === len-1),
+ length: isConditional?undefined:len,
+ parent: context
+ };
+ },
+ // run once for each iteration of the loop
+ loop = function(key, value) {
+ var inner_context = Twig.ChildContext(context);
+
+ inner_context[token.value_var] = value;
+
+ if (token.key_var) {
+ inner_context[token.key_var] = key;
+ }
+
+ // Loop object
+ inner_context.loop = buildLoop(index, len);
+
+ if (conditional === undefined ||
+ Twig.expression.parse.apply(that, [conditional, inner_context]))
+ {
+ output.push(Twig.parse.apply(that, [token.output, inner_context]));
+ index += 1;
+ }
+
+ // Delete loop-related variables from the context
+ delete inner_context['loop'];
+ delete inner_context[token.value_var];
+ delete inner_context[token.key_var];
+
+ // Merge in values that exist in context but have changed
+ // in inner_context.
+ Twig.merge(context, inner_context, true);
+ };
+
+
+ if (Twig.lib.is('Array', result)) {
+ len = result.length;
+ Twig.forEach(result, function (value) {
+ var key = index;
+
+ loop(key, value);
+ });
+ } else if (Twig.lib.is('Object', result)) {
+ if (result._keys !== undefined) {
+ keyset = result._keys;
+ } else {
+ keyset = Object.keys(result);
+ }
+ len = keyset.length;
+ Twig.forEach(keyset, function(key) {
+ // Ignore the _keys property, it's internal to twig.js
+ if (key === "_keys") return;
+
+ loop(key, result[key]);
+ });
+ }
+
+ // Only allow else statements if no output was generated
+ continue_chain = (output.length === 0);
+
+ return {
+ chain: continue_chain,
+ output: Twig.output.apply(this, [output])
+ };
+ }
+ },
+ {
+ /**
+ * End if type logic tokens.
+ *
+ * Format: {% endif %}
+ */
+ type: Twig.logic.type.endfor,
+ regex: /^endfor$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * Set type logic tokens.
+ *
+ * Format: {% set key = expression %}
+ */
+ type: Twig.logic.type.set,
+ regex: /^set\s+([a-zA-Z0-9_,\s]+)\s*=\s*([\s\S]+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var key = token.match[1].trim(),
+ expression = token.match[2],
+ // Compile the expression.
+ expression_stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ token.key = key;
+ token.expression = expression_stack;
+
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, continue_chain) {
+ var value = Twig.expression.parse.apply(this, [token.expression, context]),
+ key = token.key;
+
+ if (value === context) {
+ /* If storing the context in a variable, it needs to be a clone of the current state of context.
+ Otherwise we have a context with infinite recursion.
+ Fixes #341
+ */
+ value = Twig.lib.copy(value);
+ }
+
+ context[key] = value;
+
+ return {
+ chain: continue_chain,
+ context: context
+ };
+ }
+ },
+ {
+ /**
+ * Set capture type logic tokens.
+ *
+ * Format: {% set key %}
+ */
+ type: Twig.logic.type.setcapture,
+ regex: /^set\s+([a-zA-Z0-9_,\s]+)$/,
+ next: [
+ Twig.logic.type.endset
+ ],
+ open: true,
+ compile: function (token) {
+ var key = token.match[1].trim();
+
+ token.key = key;
+
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, continue_chain) {
+
+ var value = Twig.parse.apply(this, [token.output, context]),
+ key = token.key;
+
+ // set on both the global and local context
+ this.context[key] = value;
+ context[key] = value;
+
+ return {
+ chain: continue_chain,
+ context: context
+ };
+ }
+ },
+ {
+ /**
+ * End set type block logic tokens.
+ *
+ * Format: {% endset %}
+ */
+ type: Twig.logic.type.endset,
+ regex: /^endset$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * Filter logic tokens.
+ *
+ * Format: {% filter upper %} or {% filter lower|escape %}
+ */
+ type: Twig.logic.type.filter,
+ regex: /^filter\s+(.+)$/,
+ next: [
+ Twig.logic.type.endfilter
+ ],
+ open: true,
+ compile: function (token) {
+ var expression = "|" + token.match[1].trim();
+ // Compile the expression.
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var unfiltered = Twig.parse.apply(this, [token.output, context]),
+ stack = [{
+ type: Twig.expression.type.string,
+ value: unfiltered
+ }].concat(token.stack);
+
+ var output = Twig.expression.parse.apply(this, [stack, context]);
+
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+ {
+ /**
+ * End filter logic tokens.
+ *
+ * Format: {% endfilter %}
+ */
+ type: Twig.logic.type.endfilter,
+ regex: /^endfilter$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * Block logic tokens.
+ *
+ * Format: {% block title %}
+ */
+ type: Twig.logic.type.block,
+ regex: /^block\s+([a-zA-Z0-9_]+)$/,
+ next: [
+ Twig.logic.type.endblock
+ ],
+ open: true,
+ compile: function (token) {
+ token.block = token.match[1].trim();
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var block_output,
+ output,
+ isImported = Twig.indexOf(this.importedBlocks, token.block) > -1,
+ hasParent = this.blocks[token.block] && Twig.indexOf(this.blocks[token.block], Twig.placeholders.parent) > -1;
+
+ // Don't override previous blocks unless they're imported with "use"
+ // Loops should be exempted as well.
+ if (this.blocks[token.block] === undefined || isImported || hasParent || context.loop || token.overwrite) {
+ if (token.expression) {
+ // Short blocks have output as an expression on the open tag (no body)
+ block_output = Twig.expression.parse.apply(this, [{
+ type: Twig.expression.type.string,
+ value: Twig.expression.parse.apply(this, [token.output, context])
+ }, context]);
+ } else {
+ block_output = Twig.expression.parse.apply(this, [{
+ type: Twig.expression.type.string,
+ value: Twig.parse.apply(this, [token.output, context])
+ }, context]);
+ }
+
+ if (isImported) {
+ // once the block is overridden, remove it from the list of imported blocks
+ this.importedBlocks.splice(this.importedBlocks.indexOf(token.block), 1);
+ }
+
+ if (hasParent) {
+ this.blocks[token.block] = Twig.Markup(this.blocks[token.block].replace(Twig.placeholders.parent, block_output));
+ } else {
+ this.blocks[token.block] = block_output;
+ }
+
+ this.originalBlockTokens[token.block] = {
+ type: token.type,
+ block: token.block,
+ output: token.output,
+ overwrite: true
+ };
+ }
+
+ // Check if a child block has been set from a template extending this one.
+ if (this.child.blocks[token.block]) {
+ output = this.child.blocks[token.block];
+ } else {
+ output = this.blocks[token.block];
+ }
+
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+ {
+ /**
+ * Block shorthand logic tokens.
+ *
+ * Format: {% block title expression %}
+ */
+ type: Twig.logic.type.shortblock,
+ regex: /^block\s+([a-zA-Z0-9_]+)\s+(.+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ token.expression = token.match[2].trim();
+
+ token.output = Twig.expression.compile({
+ type: Twig.expression.type.expression,
+ value: token.expression
+ }).stack;
+
+ token.block = token.match[1].trim();
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ return Twig.logic.handler[Twig.logic.type.block].parse.apply(this, arguments);
+ }
+ },
+ {
+ /**
+ * End block logic tokens.
+ *
+ * Format: {% endblock %}
+ */
+ type: Twig.logic.type.endblock,
+ regex: /^endblock(?:\s+([a-zA-Z0-9_]+))?$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * Block logic tokens.
+ *
+ * Format: {% extends "template.twig" %}
+ */
+ type: Twig.logic.type.extends_,
+ regex: /^extends\s+(.+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var expression = token.match[1].trim();
+ delete token.match;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var template,
+ innerContext = Twig.ChildContext(context);
+ // Resolve filename
+ var file = Twig.expression.parse.apply(this, [token.stack, context]);
+
+ // Set parent template
+ this.extend = file;
+
+ if (file instanceof Twig.Template) {
+ template = file;
+ } else {
+ // Import file
+ template = this.importFile(file);
+ }
+
+ // Render the template in case it puts anything in its context
+ template.render(innerContext);
+
+ // Extend the parent context with the extended context
+ Twig.lib.extend(context, innerContext);
+
+ return {
+ chain: chain,
+ output: ''
+ };
+ }
+ },
+ {
+ /**
+ * Block logic tokens.
+ *
+ * Format: {% use "template.twig" %}
+ */
+ type: Twig.logic.type.use,
+ regex: /^use\s+(.+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var expression = token.match[1].trim();
+ delete token.match;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ // Resolve filename
+ var file = Twig.expression.parse.apply(this, [token.stack, context]);
+
+ // Import blocks
+ this.importBlocks(file);
+
+ return {
+ chain: chain,
+ output: ''
+ };
+ }
+ },
+ {
+ /**
+ * Block logic tokens.
+ *
+ * Format: {% includes "template.twig" [with {some: 'values'} only] %}
+ */
+ type: Twig.logic.type.include,
+ regex: /^include\s+(.+?)(?:\s|$)(ignore missing(?:\s|$))?(?:with\s+([\S\s]+?))?(?:\s|$)(only)?$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var match = token.match,
+ expression = match[1].trim(),
+ ignoreMissing = match[2] !== undefined,
+ withContext = match[3],
+ only = ((match[4] !== undefined) && match[4].length);
+
+ delete token.match;
+
+ token.only = only;
+ token.ignoreMissing = ignoreMissing;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ if (withContext !== undefined) {
+ token.withStack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: withContext.trim()
+ }]).stack;
+ }
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ // Resolve filename
+ var innerContext = {},
+ withContext,
+ i,
+ template;
+
+ if (!token.only) {
+ innerContext = Twig.ChildContext(context);
+ }
+
+ if (token.withStack !== undefined) {
+ withContext = Twig.expression.parse.apply(this, [token.withStack, context]);
+
+ for (i in withContext) {
+ if (withContext.hasOwnProperty(i))
+ innerContext[i] = withContext[i];
+ }
+ }
+
+ var file = Twig.expression.parse.apply(this, [token.stack, context]);
+
+ if (file instanceof Twig.Template) {
+ template = file;
+ } else {
+ // Import file
+ try {
+ template = this.importFile(file);
+ } catch (err) {
+ if (token.ignoreMissing) {
+ return {
+ chain: chain,
+ output: ''
+ }
+ }
+
+ throw err;
+ }
+ }
+
+ return {
+ chain: chain,
+ output: template.render(innerContext)
+ };
+ }
+ },
+ {
+ type: Twig.logic.type.spaceless,
+ regex: /^spaceless$/,
+ next: [
+ Twig.logic.type.endspaceless
+ ],
+ open: true,
+
+ // Parse the html and return it without any spaces between tags
+ parse: function (token, context, chain) {
+ var // Parse the output without any filter
+ unfiltered = Twig.parse.apply(this, [token.output, context]),
+ // A regular expression to find closing and opening tags with spaces between them
+ rBetweenTagSpaces = />\s+</g,
+ // Replace all space between closing and opening html tags
+ output = unfiltered.replace(rBetweenTagSpaces,'><').trim();
+ // Rewrap output as a Twig.Markup
+ output = Twig.Markup(output);
+ return {
+ chain: chain,
+ output: output
+ };
+ }
+ },
+
+ // Add the {% endspaceless %} token
+ {
+ type: Twig.logic.type.endspaceless,
+ regex: /^endspaceless$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /**
+ * Macro logic tokens.
+ *
+ * Format: {% maro input(name, value, type, size) %}
+ *
+ */
+ type: Twig.logic.type.macro,
+ regex: /^macro\s+([a-zA-Z0-9_]+)\s*\(\s*((?:[a-zA-Z0-9_]+(?:,\s*)?)*)\s*\)$/,
+ next: [
+ Twig.logic.type.endmacro
+ ],
+ open: true,
+ compile: function (token) {
+ var macroName = token.match[1],
+ parameters = token.match[2].split(/[\s,]+/);
+
+ //TODO: Clean up duplicate check
+ for (var i=0; i<parameters.length; i++) {
+ for (var j=0; j<parameters.length; j++){
+ if (parameters[i] === parameters[j] && i !== j) {
+ throw new Twig.Error("Duplicate arguments for parameter: "+ parameters[i]);
+ }
+ }
+ }
+
+ token.macroName = macroName;
+ token.parameters = parameters;
+
+ delete token.match;
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var template = this;
+ this.macros[token.macroName] = function() {
+ // Pass global context and other macros
+ var macroContext = {
+ _self: template.macros
+ }
+ // Add parameters from context to macroContext
+ for (var i=0; i<token.parameters.length; i++) {
+ var prop = token.parameters[i];
+ if(typeof arguments[i] !== 'undefined') {
+ macroContext[prop] = arguments[i];
+ } else {
+ macroContext[prop] = undefined;
+ }
+ }
+ // Render
+ return Twig.parse.apply(template, [token.output, macroContext])
+ };
+
+ return {
+ chain: chain,
+ output: ''
+ };
+
+ }
+ },
+ {
+ /**
+ * End macro logic tokens.
+ *
+ * Format: {% endmacro %}
+ */
+ type: Twig.logic.type.endmacro,
+ regex: /^endmacro$/,
+ next: [ ],
+ open: false
+ },
+ {
+ /*
+ * import logic tokens.
+ *
+ * Format: {% import "template.twig" as form %}
+ */
+ type: Twig.logic.type.import_,
+ regex: /^import\s+(.+)\s+as\s+([a-zA-Z0-9_]+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var expression = token.match[1].trim(),
+ contextName = token.match[2].trim();
+ delete token.match;
+
+ token.expression = expression;
+ token.contextName = contextName;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ if (token.expression !== "_self") {
+ var file = Twig.expression.parse.apply(this, [token.stack, context]);
+ var template = this.importFile(file || token.expression);
+ context[token.contextName] = template.render({}, {output: 'macros'});
+ }
+ else {
+ context[token.contextName] = this.macros;
+ }
+
+ return {
+ chain: chain,
+ output: ''
+ }
+
+ }
+ },
+ {
+ /*
+ * from logic tokens.
+ *
+ * Format: {% from "template.twig" import func as form %}
+ */
+ type: Twig.logic.type.from,
+ regex: /^from\s+(.+)\s+import\s+([a-zA-Z0-9_, ]+)$/,
+ next: [ ],
+ open: true,
+ compile: function (token) {
+ var expression = token.match[1].trim(),
+ macroExpressions = token.match[2].trim().split(/[ ,]+/),
+ macroNames = {};
+
+ for (var i=0; i<macroExpressions.length; i++) {
+ var res = macroExpressions[i];
+
+ // match function as variable
+ var macroMatch = res.match(/^([a-zA-Z0-9_]+)\s+(.+)\s+as\s+([a-zA-Z0-9_]+)$/);
+ if (macroMatch) {
+ macroNames[macroMatch[1].trim()] = macroMatch[2].trim();
+ }
+ else if (res.match(/^([a-zA-Z0-9_]+)$/)) {
+ macroNames[res] = res;
+ }
+ else {
+ // ignore import
+ }
+
+ }
+
+ delete token.match;
+
+ token.expression = expression;
+ token.macroNames = macroNames;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ var macros;
+
+ if (token.expression !== "_self") {
+ var file = Twig.expression.parse.apply(this, [token.stack, context]);
+ var template = this.importFile(file || token.expression);
+ macros = template.render({}, {output: 'macros'});
+ }
+ else {
+ macros = this.macros;
+ }
+
+ for (var macroName in token.macroNames) {
+ if (macros.hasOwnProperty(macroName)) {
+ context[token.macroNames[macroName]] = macros[macroName];
+ }
+ }
+
+ return {
+ chain: chain,
+ output: ''
+ }
+
+ }
+ },
+ {
+ /**
+ * The embed tag combines the behaviour of include and extends.
+ * It allows you to include another template's contents, just like include does.
+ *
+ * Format: {% embed "template.twig" [with {some: 'values'} only] %}
+ */
+ type: Twig.logic.type.embed,
+ regex: /^embed\s+(.+?)(?:\s|$)(ignore missing(?:\s|$))?(?:with\s+([\S\s]+?))?(?:\s|$)(only)?$/,
+ next: [
+ Twig.logic.type.endembed
+ ],
+ open: true,
+ compile: function (token) {
+ var match = token.match,
+ expression = match[1].trim(),
+ ignoreMissing = match[2] !== undefined,
+ withContext = match[3],
+ only = ((match[4] !== undefined) && match[4].length);
+
+ delete token.match;
+
+ token.only = only;
+ token.ignoreMissing = ignoreMissing;
+
+ token.stack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: expression
+ }]).stack;
+
+ if (withContext !== undefined) {
+ token.withStack = Twig.expression.compile.apply(this, [{
+ type: Twig.expression.type.expression,
+ value: withContext.trim()
+ }]).stack;
+ }
+
+ return token;
+ },
+ parse: function (token, context, chain) {
+ // Resolve filename
+ var innerContext = {},
+ withContext,
+ i,
+ template;
+
+ if (!token.only) {
+ for (i in context) {
+ if (context.hasOwnProperty(i))
+ innerContext[i] = context[i];
+ }
+ }
+
+ if (token.withStack !== undefined) {
+ withContext = Twig.expression.parse.apply(this, [token.withStack, context]);
+
+ for (i in withContext) {
+ if (withContext.hasOwnProperty(i))
+ innerContext[i] = withContext[i];
+ }
+ }
+
+ var file = Twig.expression.parse.apply(this, [token.stack, innerContext]);
+
+ if (file instanceof Twig.Template) {
+ template = file;
+ } else {
+ // Import file
+ try {
+ template = this.importFile(file);
+ } catch (err) {
+ if (token.ignoreMissing) {
+ return {
+ chain: chain,
+ output: ''
+ }
+ }
+
+ throw err;
+ }
+ }
+
+ // reset previous blocks
+ this.blocks = {};
+
+ // parse tokens. output will be not used
+ var output = Twig.parse.apply(this, [token.output, innerContext]);
+
+ // render tempalte with blocks defined in embed block
+ return {
+ chain: chain,
+ output: template.render(innerContext, {'blocks':this.blocks})
+ };
+ }
+ },
+ /* Add the {% endembed %} token
+ *
+ */
+ {
+ type: Twig.logic.type.endembed,
+ regex: /^endembed$/,
+ next: [ ],
+ open: false
+ }
+
+ ];
+
+
+ /**
+ * Registry for logic handlers.
+ */
+ Twig.logic.handler = {};
+
+ /**
+ * Define a new token type, available at Twig.logic.type.{type}
+ */
+ Twig.logic.extendType = function (type, value) {
+ value = value || ("Twig.logic.type" + type);
+ Twig.logic.type[type] = value;
+ };
+
+ /**
+ * Extend the logic parsing functionality with a new token definition.
+ *
+ * // Define a new tag
+ * Twig.logic.extend({
+ * type: Twig.logic.type.{type},
+ * // The pattern to match for this token
+ * regex: ...,
+ * // What token types can follow this token, leave blank if any.
+ * next: [ ... ]
+ * // Create and return compiled version of the token
+ * compile: function(token) { ... }
+ * // Parse the compiled token with the context provided by the render call
+ * // and whether this token chain is complete.
+ * parse: function(token, context, chain) { ... }
+ * });
+ *
+ * @param {Object} definition The new logic expression.
+ */
+ Twig.logic.extend = function (definition) {
+
+ if (!definition.type) {
+ throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
+ } else {
+ Twig.logic.extendType(definition.type);
+ }
+ Twig.logic.handler[definition.type] = definition;
+ };
+
+ // Extend with built-in expressions
+ while (Twig.logic.definitions.length > 0) {
+ Twig.logic.extend(Twig.logic.definitions.shift());
+ }
+
+ /**
+ * Compile a logic token into an object ready for parsing.
+ *
+ * @param {Object} raw_token An uncompiled logic token.
+ *
+ * @return {Object} A compiled logic token, ready for parsing.
+ */
+ Twig.logic.compile = function (raw_token) {
+ var expression = raw_token.value.trim(),
+ token = Twig.logic.tokenize.apply(this, [expression]),
+ token_template = Twig.logic.handler[token.type];
+
+ // Check if the token needs compiling
+ if (token_template.compile) {
+ token = token_template.compile.apply(this, [token]);
+ Twig.log.trace("Twig.logic.compile: ", "Compiled logic token to ", token);
+ }
+
+ return token;
+ };
+
+ /**
+ * Tokenize logic expressions. This function matches token expressions against regular
+ * expressions provided in token definitions provided with Twig.logic.extend.
+ *
+ * @param {string} expression the logic token expression to tokenize
+ * (i.e. what's between {% and %})
+ *
+ * @return {Object} The matched token with type set to the token type and match to the regex match.
+ */
+ Twig.logic.tokenize = function (expression) {
+ var token = {},
+ token_template_type = null,
+ token_type = null,
+ token_regex = null,
+ regex_array = null,
+ regex = null,
+ match = null;
+
+ // Ignore whitespace around expressions.
+ expression = expression.trim();
+
+ for (token_template_type in Twig.logic.handler) {
+ if (Twig.logic.handler.hasOwnProperty(token_template_type)) {
+ // Get the type and regex for this template type
+ token_type = Twig.logic.handler[token_template_type].type;
+ token_regex = Twig.logic.handler[token_template_type].regex;
+
+ // Handle multiple regular expressions per type.
+ regex_array = [];
+ if (token_regex instanceof Array) {
+ regex_array = token_regex;
+ } else {
+ regex_array.push(token_regex);
+ }
+
+ // Check regular expressions in the order they were specified in the definition.
+ while (regex_array.length > 0) {
+ regex = regex_array.shift();
+ match = regex.exec(expression.trim());
+ if (match !== null) {
+ token.type = token_type;
+ token.match = match;
+ Twig.log.trace("Twig.logic.tokenize: ", "Matched a ", token_type, " regular expression of ", match);
+ return token;
+ }
+ }
+ }
+ }
+
+ // No regex matches
+ throw new Twig.Error("Unable to parse '" + expression.trim() + "'");
+ };
+
+ /**
+ * Parse a logic token within a given context.
+ *
+ * What are logic chains?
+ * Logic chains represent a series of tokens that are connected,
+ * for example:
+ * {% if ... %} {% else %} {% endif %}
+ *
+ * The chain parameter is used to signify if a chain is open of closed.
+ * open:
+ * More tokens in this chain should be parsed.
+ * closed:
+ * This token chain has completed parsing and any additional
+ * tokens (else, elseif, etc...) should be ignored.
+ *
+ * @param {Object} token The compiled token.
+ * @param {Object} context The render context.
+ * @param {boolean} chain Is this an open logic chain. If false, that means a
+ * chain is closed and no further cases should be parsed.
+ */
+ Twig.logic.parse = function (token, context, chain) {
+ var output = '',
+ token_template;
+
+ context = context || { };
+
+ Twig.log.debug("Twig.logic.parse: ", "Parsing logic token ", token);
+
+ token_template = Twig.logic.handler[token.type];
+
+ if (token_template.parse) {
+ output = token_template.parse.apply(this, [token, context, chain]);
+ }
+ return output;
+ };
+
+ return Twig;
+
+ };
+
+
+/***/ },
+/* 22 */
+/***/ function(module, exports) {
+
+ module.exports = function(Twig) {
+ 'use strict';
+
+ Twig.Templates.registerParser('source', function(params) {
+ return params.data || '';
+ });
+ };
+
+
+/***/ },
+/* 23 */
+/***/ function(module, exports) {
+
+ module.exports = function(Twig) {
+ 'use strict';
+
+ Twig.Templates.registerParser('twig', function(params) {
+ return new Twig.Template(params);
+ });
+ };
+
+
+/***/ },
+/* 24 */
+/***/ function(module, exports, __webpack_require__) {
+
+ // ## twig.path.js
+ //
+ // This file handles path parsing
+ module.exports = function (Twig) {
+ "use strict";
+
+ /**
+ * Namespace for path handling.
+ */
+ Twig.path = {};
+
+ /**
+ * Generate the canonical version of a url based on the given base path and file path and in
+ * the previously registered namespaces.
+ *
+ * @param {string} template The Twig Template
+ * @param {string} file The file path, may be relative and may contain namespaces.
+ *
+ * @return {string} The canonical version of the path
+ */
+ Twig.path.parsePath = function(template, file) {
+ var namespaces = null,
+ file = file || "";
+
+ if (typeof template === 'object' && typeof template.options === 'object') {
+ namespaces = template.options.namespaces;
+ }
+
+ if (typeof namespaces === 'object' && (file.indexOf('::') > 0) || file.indexOf('@') >= 0){
+ for (var k in namespaces){
+ if (namespaces.hasOwnProperty(k)) {
+ file = file.replace(k + '::', namespaces[k]);
+ file = file.replace('@' + k, namespaces[k]);
+ }
+ }
+
+ return file;
+ }
+
+ return Twig.path.relativePath(template, file);
+ };
+
+ /**
+ * Generate the relative canonical version of a url based on the given base path and file path.
+ *
+ * @param {Twig.Template} template The Twig.Template.
+ * @param {string} file The file path, relative to the base path.
+ *
+ * @return {string} The canonical version of the path.
+ */
+ Twig.path.relativePath = function(template, file) {
+ var base,
+ base_path,
+ sep_chr = "/",
+ new_path = [],
+ file = file || "",
+ val;
+
+ if (template.url) {
+ if (typeof template.base !== 'undefined') {
+ base = template.base + ((template.base.charAt(template.base.length-1) === '/') ? '' : '/');
+ } else {
+ base = template.url;
+ }
+ } else if (template.path) {
+ // Get the system-specific path separator
+ var path = __webpack_require__(20),
+ sep = path.sep || sep_chr,
+ relative = new RegExp("^\\.{1,2}" + sep.replace("\\", "\\\\"));
+ file = file.replace(/\//g, sep);
+
+ if (template.base !== undefined && file.match(relative) == null) {
+ file = file.replace(template.base, '');
+ base = template.base + sep;
+ } else {
+ base = path.normalize(template.path);
+ }
+
+ base = base.replace(sep+sep, sep);
+ sep_chr = sep;
+ } else if ((template.name || template.id) && template.method && template.method !== 'fs' && template.method !== 'ajax') {
+ // Custom registered loader
+ base = template.base || template.name || template.id;
+ } else {
+ throw new Twig.Error("Cannot extend an inline template.");
+ }
+
+ base_path = base.split(sep_chr);
+
+ // Remove file from url
+ base_path.pop();
+ base_path = base_path.concat(file.split(sep_chr));
+
+ while (base_path.length > 0) {
+ val = base_path.shift();
+ if (val == ".") {
+ // Ignore
+ } else if (val == ".." && new_path.length > 0 && new_path[new_path.length-1] != "..") {
+ new_path.pop();
+ } else {
+ new_path.push(val);
+ }
+ }
+
+ return new_path.join(sep_chr);
+ };
+
+ return Twig;
+ };
+
+
+/***/ },
+/* 25 */
+/***/ function(module, exports) {
+
+ // ## twig.tests.js
+ //
+ // This file handles expression tests. (is empty, is not defined, etc...)
+ module.exports = function (Twig) {
+ "use strict";
+ Twig.tests = {
+ empty: function(value) {
+ if (value === null || value === undefined) return true;
+ // Handler numbers
+ if (typeof value === "number") return false; // numbers are never "empty"
+ // Handle strings and arrays
+ if (value.length && value.length > 0) return false;
+ // Handle objects
+ for (var key in value) {
+ if (value.hasOwnProperty(key)) return false;
+ }
+ return true;
+ },
+ odd: function(value) {
+ return value % 2 === 1;
+ },
+ even: function(value) {
+ return value % 2 === 0;
+ },
+ divisibleby: function(value, params) {
+ return value % params[0] === 0;
+ },
+ defined: function(value) {
+ return value !== undefined;
+ },
+ none: function(value) {
+ return value === null;
+ },
+ 'null': function(value) {
+ return this.none(value); // Alias of none
+ },
+ 'same as': function(value, params) {
+ return value === params[0];
+ },
+ sameas: function(value, params) {
+ console.warn('`sameas` is deprecated use `same as`');
+ return Twig.tests['same as'](value, params);
+ },
+ iterable: function(value) {
+ return value && (Twig.lib.is("Array", value) || Twig.lib.is("Object", value));
+ }
+ /*
+ constant ?
+ */
+ };
+
+ Twig.test = function(test, value, params) {
+ if (!Twig.tests[test]) {
+ throw "Test " + test + " is not defined.";
+ }
+ return Twig.tests[test](value, params);
+ };
+
+ Twig.test.extend = function(test, definition) {
+ Twig.tests[test] = definition;
+ };
+
+ return Twig;
+ };
+
+
+/***/ },
+/* 26 */
+/***/ function(module, exports) {
+
+ // ## twig.exports.js
+ //
+ // This file provides extension points and other hooks into the twig functionality.
+
+ module.exports = function (Twig) {
+ "use strict";
+ Twig.exports = {
+ VERSION: Twig.VERSION
+ };
+
+ /**
+ * Create and compile a twig.js template.
+ *
+ * @param {Object} param Paramteres for creating a Twig template.
+ *
+ * @return {Twig.Template} A Twig template ready for rendering.
+ */
+ Twig.exports.twig = function twig(params) {
+ 'use strict';
+ var id = params.id,
+ options = {
+ strict_variables: params.strict_variables || false,
+ // TODO: turn autoscape on in the next major version
+ autoescape: params.autoescape != null && params.autoescape || false,
+ allowInlineIncludes: params.allowInlineIncludes || false,
+ rethrow: params.rethrow || false,
+ namespaces: params.namespaces
+ };
+
+ if (Twig.cache && id) {
+ Twig.validateId(id);
+ }
+
+ if (params.debug !== undefined) {
+ Twig.debug = params.debug;
+ }
+ if (params.trace !== undefined) {
+ Twig.trace = params.trace;
+ }
+
+ if (params.data !== undefined) {
+ return Twig.Templates.parsers.twig({
+ data: params.data,
+ path: params.hasOwnProperty('path') ? params.path : undefined,
+ module: params.module,
+ id: id,
+ options: options
+ });
+
+ } else if (params.ref !== undefined) {
+ if (params.id !== undefined) {
+ throw new Twig.Error("Both ref and id cannot be set on a twig.js template.");
+ }
+ return Twig.Templates.load(params.ref);
+
+ } else if (params.method !== undefined) {
+ if (!Twig.Templates.isRegisteredLoader(params.method)) {
+ throw new Twig.Error('Loader for "' + params.method + '" is not defined.');
+ }
+ return Twig.Templates.loadRemote(params.name || params.href || params.path || id || undefined, {
+ id: id,
+ method: params.method,
+ parser: params.parser || 'twig',
+ base: params.base,
+ module: params.module,
+ precompiled: params.precompiled,
+ async: params.async,
+ options: options
+
+ }, params.load, params.error);
+
+ } else if (params.href !== undefined) {
+ return Twig.Templates.loadRemote(params.href, {
+ id: id,
+ method: 'ajax',
+ parser: params.parser || 'twig',
+ base: params.base,
+ module: params.module,
+ precompiled: params.precompiled,
+ async: params.async,
+ options: options
+
+ }, params.load, params.error);
+
+ } else if (params.path !== undefined) {
+ return Twig.Templates.loadRemote(params.path, {
+ id: id,
+ method: 'fs',
+ parser: params.parser || 'twig',
+ base: params.base,
+ module: params.module,
+ precompiled: params.precompiled,
+ async: params.async,
+ options: options
+
+ }, params.load, params.error);
+ }
+ };
+
+ // Extend Twig with a new filter.
+ Twig.exports.extendFilter = function(filter, definition) {
+ Twig.filter.extend(filter, definition);
+ };
+
+ // Extend Twig with a new function.
+ Twig.exports.extendFunction = function(fn, definition) {
+ Twig._function.extend(fn, definition);
+ };
+
+ // Extend Twig with a new test.
+ Twig.exports.extendTest = function(test, definition) {
+ Twig.test.extend(test, definition);
+ };
+
+ // Extend Twig with a new definition.
+ Twig.exports.extendTag = function(definition) {
+ Twig.logic.extend(definition);
+ };
+
+ // Provide an environment for extending Twig core.
+ // Calls fn with the internal Twig object.
+ Twig.exports.extend = function(fn) {
+ fn(Twig);
+ };
+
+
+ /**
+ * Provide an extension for use with express 2.
+ *
+ * @param {string} markup The template markup.
+ * @param {array} options The express options.
+ *
+ * @return {string} The rendered template.
+ */
+ Twig.exports.compile = function(markup, options) {
+ var id = options.filename,
+ path = options.filename,
+ template;
+
+ // Try to load the template from the cache
+ template = new Twig.Template({
+ data: markup,
+ path: path,
+ id: id,
+ options: options.settings['twig options']
+ }); // Twig.Templates.load(id) ||
+
+ return function(context) {
+ return template.render(context);
+ };
+ };
+
+ /**
+ * Provide an extension for use with express 3.
+ *
+ * @param {string} path The location of the template file on disk.
+ * @param {Object|Function} The options or callback.
+ * @param {Function} fn callback.
+ *
+ * @throws Twig.Error
+ */
+ Twig.exports.renderFile = function(path, options, fn) {
+ // handle callback in options
+ if (typeof options === 'function') {
+ fn = options;
+ options = {};
+ }
+
+ options = options || {};
+
+ var settings = options.settings || {};
+
+ var params = {
+ path: path,
+ base: settings.views,
+ load: function(template) {
+ // render and return template as a simple string, see https://github.com/twigjs/twig.js/pull/348 for more information
+ fn(null, '' + template.render(options));
+ }
+ };
+
+ // mixin any options provided to the express app.
+ var view_options = settings['twig options'];
+
+ if (view_options) {
+ for (var option in view_options) {
+ if (view_options.hasOwnProperty(option)) {
+ params[option] = view_options[option];
+ }
+ }
+ }
+
+ Twig.exports.twig(params);
+ };
+
+ // Express 3 handler
+ Twig.exports.__express = Twig.exports.renderFile;
+
+ /**
+ * Shoud Twig.js cache templates.
+ * Disable during development to see changes to templates without
+ * reloading, and disable in production to improve performance.
+ *
+ * @param {boolean} cache
+ */
+ Twig.exports.cache = function(cache) {
+ Twig.cache = cache;
+ };
+
+ //We need to export the path module so we can effectively test it
+ Twig.exports.path = Twig.path;
+
+ //Export our filters.
+ //Resolves #307
+ Twig.exports.filters = Twig.filters;
+
+ return Twig;
+ };
+
+
+/***/ }
+/******/ ])
+});
+; \ No newline at end of file