PageSourceSearch

https://medicine.lv/assets/plugins/twig.js/twig.js

js medicine.lv collected 2026-10-02 11:06:20 UTC 200,431 bytes, 5,592 lines download raw bytes

1/**
2 * Twig.js 0.8.3
3 *
4 * @copyright 2011-2015 John Roepke and the Twig.js Contributors
5 * @license   Available under the BSD 2-Clause License
6 * @link      https://github.com/justjohn/twig.js
7 */
8
9var Twig = (function (Twig) {
10
11    Twig.VERSION = "0.8.4";
12
13    return Twig;
14})(Twig || {});
15//     Twig.js
16//     Available under the BSD 2-Clause License
17//     https://github.com/justjohn/twig.js
18
19var Twig = (function (Twig) {
20    "use strict";
21    // ## twig.core.js
22    //
23    // This file handles template level tokenizing, compiling and parsing.
24
25    Twig.trace = false;
26    Twig.debug = false;
27
28    // Default caching to true for the improved performance it offers
29    Twig.cache = true;
30
31    Twig.placeholders = {
32        parent: "{{|PARENT|}}"
33    };
34
35    /**
36     * Fallback for Array.indexOf for IE8 et al
37     */
38    Twig.indexOf = function (arr, searchElement /*, fromIndex */ ) {
39        if (Array.prototype.hasOwnProperty("indexOf")) {
40            return arr.indexOf(searchElement);
41        }
42        if (arr === void 0 || arr === null) {
43            throw new TypeError();
44        }
45        var t = Object(arr);
46        var len = t.length >>> 0;
47        if (len === 0) {
48            return -1;
49        }
50        var n = 0;
51        if (arguments.length > 0) {
52            n = Number(arguments[1]);
53            if (n !== n) { // shortcut for verifying if it's NaN
54                n = 0;
55            } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
56                n = (n > 0 || -1) * Math.floor(Math.abs(n));
57            }
58        }
59        if (n >= len) {
60            // console.log("indexOf not found1 ", JSON.stringify(searchElement), JSON.stringify(arr));
61            return -1;
62        }
63        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
64        for (; k < len; k++) {
65            if (k in t && t[k] === searchElement) {
66                return k;
67            }
68        }
69        if (arr == searchElement) {
70            return 0;
71        }
72        // console.log("indexOf not found2 ", JSON.stringify(searchElement), JSON.stringify(arr));
73
74        return -1;
75    }
76
77    Twig.forEach = function (arr, callback, thisArg) {
78        if (Array.prototype.forEach ) {
79            return arr.forEach(callback, thisArg);
80        }
81
82        var T, k;
83
84        if ( arr == null ) {
85          throw new TypeError( " this is null or not defined" );
86        }
87
88        // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
89        var O = Object(arr);
90
91        // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
92        // 3. Let len be ToUint32(lenValue).
93        var len = O.length >>> 0; // Hack to convert O.length to a UInt32
94
95        // 4. If IsCallable(callback) is false, throw a TypeError exception.
96        // See: http://es5.github.com/#x9.11
97        if ( {}.toString.call(callback) != "[object Function]" ) {
98          throw new TypeError( callback + " is not a function" );
99        }
100
101        // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
102        if ( thisArg ) {
103          T = thisArg;
104        }
105
106        // 6. Let k be 0
107        k = 0;
108
109        // 7. Repeat, while k < len
110        while( k < len ) {
111
112          var kValue;
113
114          // a. Let Pk be ToString(k).
115          //   This is implicit for LHS operands of the in operator
116          // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
117          //   This step can be combined with c
118          // c. If kPresent is true, then
119          if ( k in O ) {
120
121            // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
122            kValue = O[ k ];
123
124            // ii. Call the Call internal method of callback with T as the this value and
125            // argument list containing kValue, k, and O.
126            callback.call( T, kValue, k, O );
127          }
128          // d. Increase k by 1.
129          k++;
130        }
131        // 8. return undefined
132    };
133
134    Twig.merge = function(target, source, onlyChanged) {
135        Twig.forEach(Object.keys(source), function (key) {
136            if (onlyChanged && !(key in target)) {
137                return;
138            }
139
140            target[key] = source[key]
141        });
142
143        return target;
144    };
145
146    /**
147     * Exception thrown by twig.js.
148     */
149    Twig.Error = function(message) {
150       this.message = message;
151       this.name = "TwigException";
152       this.type = "TwigException";
153    };
154
155    /**
156     * Get the string representation of a Twig error.
157     */
158    Twig.Error.prototype.toString = function() {
159        var output = this.name + ": " + this.message;
160
161        return output;
162    };
163
164    /**
165     * Wrapper for logging to the console.
166     */
167    Twig.log = {
168        trace: function() {if (Twig.trace && console) {console.log(Array.prototype.slice.call(arguments));}},
169        debug: function() {if (Twig.debug && console) {console.log(Array.prototype.slice.call(arguments));}}
170    };
171
172    if (typeof console !== "undefined") {
173        if (typeof console.error !== "undefined") {
174            Twig.log.error = function() {
175                console.error.apply(console, arguments);
176            }
177        } else if (typeof console.log !== "undefined") {
178            Twig.log.error = function() {
179                console.log.apply(console, arguments);
180            }
181        }
182    } else {
183        Twig.log.error = function(){};
184    }
185
186    /**
187     * Wrapper for child context objects in Twig.
188     *
189     * @param {Object} context Values to initialize the context with.
190     */
191    Twig.ChildContext = function(context) {
192        var ChildContext = function ChildContext() {};
193        ChildContext.prototype = context;
194        return new ChildContext();
195    };
196
197    /**
198     * Container for methods related to handling high level template tokens
199     *      (for example: {{ expression }}, {% logic %}, {# comment #}, raw data)
200     */
201    Twig.token = {};
202
203    /**
204     * Token types.
205     */
206    Twig.token.type = {
207        output:  'output',
208        logic:   'logic',
209        comment: 'comment',
210        raw:     'raw'
211    };
212
213    /**
214     * Token syntax definitions.
215     */
216    Twig.token.definitions = [
217        {
218            type: Twig.token.type.raw,
219            open: '{% raw %}',
220            close: '{% endraw %}'
221        },
222        {
223            type: Twig.token.type.raw,
224            open: '{% verbatim %}',
225            close: '{% endverbatim %}'
226        },
227        // *Output type tokens*
228        //
229        // These typically take the form `{{ expression }}`.
230        {
231            type: Twig.token.type.output,
232            open: '{{',
233            close: '}}'
234        },
235        // *Logic type tokens*
236        //
237        // These typically take a form like `{% if expression %}` or `{% endif %}`
238        {
239            type: Twig.token.type.logic,
240            open: '{%',
241            close: '%}'
242        },
243        // *Comment type tokens*
244        //
245        // These take the form `{# anything #}`
246        {
247            type: Twig.token.type.comment,
248            open: '{#',
249            close: '#}'
250        }
251    ];
252
253
254    /**
255     * What characters start "strings" in token definitions. We need this to ignore token close
256     * strings inside an expression.
257     */
258    Twig.token.strings = ['"', "'"];
259
260    Twig.token.findStart = function (template) {
261        var output = {
262                position: null,
263                def: null
264            },
265            i,
266            token_template,
267            first_key_position;
268
269        for (i=0;i<Twig.token.definitions.length;i++) {
270            token_template = Twig.token.definitions[i];
271            first_key_position = template.indexOf(token_template.open);
272
273            Twig.log.trace("Twig.token.findStart: ", "Searching for ", token_template.open, " found at ", first_key_position);
274
275            // Does this token occur before any other types?
276            if (first_key_position >= 0 && (output.position === null || first_key_position < output.position)) {
277                output.position = first_key_position;
278                output.def = token_template;
279            }
280        }
281
282        return output;
283    };
284
285    Twig.token.findEnd = function (template, token_def, start) {
286        var end = null,
287            found = false,
288            offset = 0,
289
290            // String position variables
291            str_pos = null,
292            str_found = null,
293            pos = null,
294            end_offset = null,
295            this_str_pos = null,
296            end_str_pos = null,
297
298            // For loop variables
299            i,
300            l;
301
302        while (!found) {
303            str_pos = null;
304            str_found = null;
305            pos = template.indexOf(token_def.close, offset);
306
307            if (pos >= 0) {
308                end = pos;
309                found = true;
310            } else {
311                // throw an exception
312                throw new Twig.Error("Unable to find closing bracket '" + token_def.close +
313                                "'" + " opened near template position " + start);
314            }
315
316            // Ignore quotes within comments; just look for the next comment close sequence,
317            // regardless of what comes before it. https://github.com/justjohn/twig.js/issues/95
318            if (token_def.type === Twig.token.type.comment) {
319              break;
320            }
321
322            l = Twig.token.strings.length;
323            for (i = 0; i < l; i += 1) {
324                this_str_pos = template.indexOf(Twig.token.strings[i], offset);
325
326                if (this_str_pos > 0 && this_str_pos < pos &&
327                        (str_pos === null || this_str_pos < str_pos)) {
328                    str_pos = this_str_pos;
329                    str_found = Twig.token.strings[i];
330                }
331            }
332
333            // We found a string before the end of the token, now find the string's end and set the search offset to it
334            if (str_pos !== null) {
335                end_offset = str_pos + 1;
336                end = null;
337                found = false;
338                while (true) {
339                    end_str_pos = template.indexOf(str_found, end_offset);
340                    if (end_str_pos < 0) {
341                        throw "Unclosed string in template";
342                    }
343                    // Ignore escaped quotes
344                    if (template.substr(end_str_pos - 1, 1) !== "\\") {
345                        offset = end_str_pos + 1;
346                        break;
347                    } else {
348                        end_offset = end_str_pos + 1;
349                    }
350                }
351            }
352        }
353        return end;
354    };
355
356    /**
357     * Convert a template into high-level tokens.
358     */
359    Twig.tokenize = function (template) {
360        var tokens = [],
361            // An offset for reporting errors locations in the template.
362            error_offset = 0,
363
364            // The start and type of the first token found in the template.
365            found_token = null,
366            // The end position of the matched token.
367            end = null;
368
369        while (template.length > 0) {
370            // Find the first occurance of any token type in the template
371            found_token = Twig.token.findStart(template);
372
373            Twig.log.trace("Twig.tokenize: ", "Found token: ", found_token);
374
375            if (found_token.position !== null) {
376                // Add a raw type token for anything before the start of the token
377                if (found_token.position > 0) {
378                    tokens.push({
379                        type: Twig.token.type.raw,
380                        value: template.substring(0, found_token.position)
381                    });
382                }
383                template = template.substr(found_token.position + found_token.def.open.length);
384                error_offset += found_token.position + found_token.def.open.length;
385
386                // Find the end of the token
387                end = Twig.token.findEnd(template, found_token.def, error_offset);
388
389                Twig.log.trace("Twig.tokenize: ", "Token ends at ", end);
390
391                tokens.push({
392                    type:  found_token.def.type,
393                    value: template.substring(0, end).trim()
394                });
395
396                if ( found_token.def.type === "logic" && template.substr( end + found_token.def.close.length, 1 ) === "\n" ) {
397                    // Newlines directly after logic tokens are ignored
398                    end += 1;
399                }
400
401                template = template.substr(end + found_token.def.close.length);
402
403                // Increment the position in the template
404                error_offset += end + found_token.def.close.length;
405
406            } else {
407                // No more tokens -> add the rest of the template as a raw-type token
408                tokens.push({
409                    type: Twig.token.type.raw,
410                    value: template
411                });
412                template = '';
413            }
414        }
415
416        return tokens;
417    };
418
419
420    Twig.compile = function (tokens) {
421        try {
422
423            // Output and intermediate stacks
424            var output = [],
425                stack = [],
426                // The tokens between open and close tags
427                intermediate_output = [],
428
429                token = null,
430                logic_token = null,
431                unclosed_token = null,
432                // Temporary previous token.
433                prev_token = null,
434                // The previous token's template
435                prev_template = null,
436                // The output token
437                tok_output = null,
438
439                // Logic Token values
440                type = null,
441                open = null,
442                next = null;
443
444            while (tokens.length > 0) {
445                token = tokens.shift();
446                Twig.log.trace("Compiling token ", token);
447                switch (token.type) {
448                    case Twig.token.type.raw:
449                        if (stack.length > 0) {
450                            intermediate_output.push(token);
451                        } else {
452                            output.push(token);
453                        }
454                        break;
455
456                    case Twig.token.type.logic:
457                        // Compile the logic token
458                        logic_token = Twig.logic.compile.apply(this, [token]);
459
460                        type = logic_token.type;
461                        open = Twig.logic.handler[type].open;
462                        next = Twig.logic.handler[type].next;
463
464                        Twig.log.trace("Twig.compile: ", "Compiled logic token to ", logic_token,
465                                                         " next is: ", next, " open is : ", open);
466
467                        // Not a standalone token, check logic stack to see if this is expected
468                        if (open !== undefined && !open) {
469                            prev_token = stack.pop();
470                            prev_template = Twig.logic.handler[prev_token.type];
471
472                            if (Twig.indexOf(prev_template.next, type) < 0) {
473                                throw new Error(type + " not expected after a " + prev_token.type);
474                            }
475
476                            prev_token.output = prev_token.output || [];
477
478                            prev_token.output = prev_token.output.concat(intermediate_output);
479                            intermediate_output = [];
480
481                            tok_output = {
482                                type: Twig.token.type.logic,
483                                token: prev_token
484                            };
485                            if (stack.length > 0) {
486                                intermediate_output.push(tok_output);
487                            } else {
488                                output.push(tok_output);
489                            }
490                        }
491
492                        // This token requires additional tokens to complete the logic structure.
493                        if (next !== undefined && next.length > 0) {
494                            Twig.log.trace("Twig.compile: ", "Pushing ", logic_token, " to logic stack.");
495
496                            if (stack.length > 0) {
497                                // Put any currently held output into the output list of the logic operator
498                                // currently at the head of the stack before we push a new one on.
499                                prev_token = stack.pop();
500                                prev_token.output = prev_token.output || [];
501                                prev_token.output = prev_token.output.concat(intermediate_output);
502                                stack.push(prev_token);
503                                intermediate_output = [];
504                            }
505
506                            // Push the new logic token onto the logic stack
507                            stack.push(logic_token);
508
509                        } else if (open !== undefined && open) {
510                            tok_output = {
511                                type: Twig.token.type.logic,
512                                token: logic_token
513                            };
514                            // Standalone token (like {% set ... %}
515                            if (stack.length > 0) {
516                                intermediate_output.push(tok_output);
517                            } else {
518                                output.push(tok_output);
519                            }
520                        }
521                        break;
522
523                    // Do nothing, comments should be ignored
524                    case Twig.token.type.comment:
525                        break;
526
527                    case Twig.token.type.output:
528                        Twig.expression.compile.apply(this, [token]);
529                        if (stack.length > 0) {
530                            intermediate_output.push(token);
531                        } else {
532                            output.push(token);
533                        }
534                        break;
535                }
536
537                Twig.log.trace("Twig.compile: ", " Output: ", output,
538                                                 " Logic Stack: ", stack,
539                                                 " Pending Output: ", intermediate_output );
540            }
541
542            // Verify that there are no logic tokens left in the stack.
543            if (stack.length > 0) {
544                unclosed_token = stack.pop();
545                throw new Error("Unable to find an end tag for " + unclosed_token.type +
546                                ", expecting one of " + unclosed_token.next);
547            }
548            return output;
549        } catch (ex) {
550            Twig.log.error("Error compiling twig template " + this.id + ": ");
551            if (ex.stack) {
552                Twig.log.error(ex.stack);
553            } else {
554                Twig.log.error(ex.toString());
555            }
556
557            if (this.options.rethrow) throw ex;
558        }
559    };
560
561    /**
562     * Parse a compiled template.
563     *
564     * @param {Array} tokens The compiled tokens.
565     * @param {Object} context The render context.
566     *
567     * @return {string} The parsed template.
568     */
569    Twig.parse = function (tokens, context) {
570        try {
571            var output = [],
572                // Track logic chains
573                chain = true,
574                that = this;
575
576            Twig.forEach(tokens, function parseToken(token) {
577                Twig.log.debug("Twig.parse: ", "Parsing token: ", token);
578
579                switch (token.type) {
580                    case Twig.token.type.raw:
581                        output.push(Twig.filters.raw(token.value));
582                        break;
583
584                    case Twig.token.type.logic:
585                        var logic_token = token.token,
586                            logic = Twig.logic.parse.apply(that, [logic_token, context, chain]);
587
588                        if (logic.chain !== undefined) {
589                            chain = logic.chain;
590                        }
591                        if (logic.context !== undefined) {
592                            context = logic.context;
593                        }
594                        if (logic.output !== undefined) {
595                            output.push(logic.output);
596                        }
597                        break;
598
599                    case Twig.token.type.comment:
600                        // Do nothing, comments should be ignored
601                        break;
602
603                    case Twig.token.type.output:
604                        Twig.log.debug("Twig.parse: ", "Output token: ", token.stack);
605                        // Parse the given expression in the given context
606                        output.push(Twig.expression.parse.apply(that, [token.stack, context]));
607                        break;
608                }
609            });
610            return Twig.output.apply(this, [output]);
611        } catch (ex) {
612            Twig.log.error("Error parsing twig template " + this.id + ": ");
613            if (ex.stack) {
614                Twig.log.error(ex.stack);
615            } else {
616                Twig.log.error(ex.toString());
617            }
618
619            if (this.options.rethrow) throw ex;
620
621            if (Twig.debug) {
622                return ex.toString();
623            }
624        }
625    };
626
627    /**
628     * Tokenize and compile a string template.
629     *
630     * @param {string} data The template.
631     *
632     * @return {Array} The compiled tokens.
633     */
634    Twig.prepare = function(data) {
635        var tokens, raw_tokens;
636
637        // Tokenize
638        Twig.log.debug("Twig.prepare: ", "Tokenizing ", data);
639        raw_tokens = Twig.tokenize.apply(this, [data]);
640
641        // Compile
642        Twig.log.debug("Twig.prepare: ", "Compiling ", raw_tokens);
643        tokens = Twig.compile.apply(this, [raw_tokens]);
644
645        Twig.log.debug("Twig.prepare: ", "Compiled ", tokens);
646
647        return tokens;
648    };
649
650    /**
651     * Join the output token's stack and escape it if needed
652     *
653     * @param {Array} Output token's stack
654     *
655     * @return {string|String} Autoescaped output
656     */
657    Twig.output = function(output) {
658        if (!this.options.autoescape) {
659            return output.join("");
660        }
661
662        // [].map would be better but it's not supported by IE8-
663        var escaped_output = [];
664        Twig.forEach(output, function (str) {
665            if (str && !str.twig_markup) {
666                str = Twig.filters.escape(str);
667            }
668            escaped_output.push(str);
669        });
670        return Twig.Markup(escaped_output.join(""));
671    }
672
673    // Namespace for template storage and retrieval
674    Twig.Templates = {
675        registry: {}
676    };
677
678    /**
679     * Is this id valid for a twig template?
680     *
681     * @param {string} id The ID to check.
682     *
683     * @throws {Twig.Error}
683 If the ID is invalid or used.
684     * @return {boolean} True if the ID is valid.
685     */
686    Twig.validateId = function(id) {
687        if (id === "prototype") {
688            throw new Twig.Error(id + " is not a valid twig identifier");
689        } else if (Twig.Templates.registry.hasOwnProperty(id)) {
690            throw new Twig.Error("There is already a template with the ID " + id);
691        }
692        return true;
693    }
694
695    /**
696     * Save a template object to the store.
697     *
698     * @param {Twig.Template} template   The twig.js template to store.
699     */
700    Twig.Templates.save = function(template) {
701        if (template.id === undefined) {
702            throw new Twig.Error("Unable to save template with no id");
703        }
704        Twig.Templates.registry[template.id] = template;
705    };
706
707    /**
708     * Load a previously saved template from the store.
709     *
710     * @param {string} id   The ID of the template to load.
711     *
712     * @return {Twig.Template} A twig.js template stored with the provided ID.
713     */
714    Twig.Templates.load = function(id) {
715        if (!Twig.Templates.registry.hasOwnProperty(id)) {
716            return null;
717        }
718        return Twig.Templates.registry[id];
719    };
720
721    /**
722     * Load a template from a remote location using AJAX and saves in with the given ID.
723     *
724     * Available parameters:
725     *
726     *      async:       Should the HTTP request be performed asynchronously.
727     *                      Defaults to true.
728     *      method:      What method should be used to load the template
729     *                      (fs or ajax)
730     *      precompiled: Has the template already been compiled.
731     *
732     * @param {string} location  The remote URL to load as a template.
733     * @param {Object} params The template parameters.
734     * @param {function} callback  A callback triggered when the template finishes loading.
735     * @param {function} error_callback  A callback triggered if an error occurs loading the template.
736     *
737     *
738     */
739    Twig.Templates.loadRemote = function(location, params, callback, error_callback) {
740        var id          = params.id,
741            method      = params.method,
742            async       = params.async,
743            precompiled = params.precompiled,
744            template    = null;
745
746        // Default to async
747        if (async === undefined) async = true;
748
749        // Default to the URL so the template is cached.
750        if (id === undefined) {
751            id = location;
752        }
753        params.id = id;
754
755        // Check for existing template
756        if (Twig.cache && Twig.Templates.registry.hasOwnProperty(id)) {
757            // A template is already saved with the given id.
758            if (callback) {
759                callback(Twig.Templates.registry[id]);
760            }
761            return Twig.Templates.registry[id];
762        }
763
764        if (method == 'ajax') {
765            if (typeof XMLHttpRequest == "undefined") {
766                throw new Twig.Error("Unsupported platform: Unable to do remote requests " +
767                                     "because there is no XMLHTTPRequest implementation");
768            }
769
770            var xmlhttp = new XMLHttpRequest();
771            xmlhttp.onreadystatechange = function() {
772                var data = null;
773
774                if(xmlhttp.readyState == 4) {
775                    if (xmlhttp.status == 200) {
776                        Twig.log.debug("Got template ", xmlhttp.responseText);
777
778                        if (precompiled === true) {
779                            data = JSON.parse(xmlhttp.responseText);
780                        } else {
781                            data = xmlhttp.responseText;
782                        }
783
784                        params.url = location;
785                        params.data = data;
786
787                        template = new Twig.Template(params);
788
789                        if (callback) {
790                            callback(template);
791                        }
792                    } else {
793                        if (error_callback) {
794                            error_callback(xmlhttp);
795                        }
796                    }
797                }
798            };
799            xmlhttp.open("GET", location, async);
800            xmlhttp.send();
801
802        } else { // if method = 'fs'
803            // Create local scope
804            (function() {
805                var fs = require('fs'),
806                    path = require('path'),
807                    data = null,
808                    loadTemplateFn = function(err, data) {
809                        if (err) {
810                            if (error_callback) {
811                                error_callback(err);
812                            }
813                            return;
814                        }
815
816                        if (precompiled === true) {
817                            data = JSON.parse(data);
818                        }
819
820                        params.data = data;
821                        params.path = location;
822
823                        // template is in data
824                        template = new Twig.Template(params);
825
826                        if (callback) {
827                            callback(template);
828                        }
829                    };
830
831                if (async === true) {
832                    fs.stat(location, function (err, stats) {
833                        if (err || !stats.isFile())
834                            throw new Twig.Error("Unable to find template file " + location);
835
836                        fs.readFile(location, 'utf8', loadTemplateFn);
837                    });
838                } else {
839                    if (!fs.statSync(location).isFile())
840                        throw new Twig.Error("Unable to find template file " + location);
841
842                    data = fs.readFileSync(location, 'utf8');
843                    loadTemplateFn(undefined, data);
844                }
845            })();
846        }
847        if (async === false) {
848            return template;
849        } else {
850            // placeholder for now, should eventually return a deferred object.
851            return true;
852        }
853    };
854
855    // Determine object type
856    function is(type, obj) {
857        var clas = Object.prototype.toString.call(obj).slice(8, -1);
858        return obj !== undefined && obj !== null && clas === type;
859    }
860
861    /**
862     * Create a new twig.js template.
863     *
864     * Parameters: {
865     *      data:   The template, either pre-compiled tokens or a string template
866     *      id:     The name of this template
867     *      blocks: Any pre-existing block from a child template
868     * }
869     *
870     * @param {Object} params The template parameters.
871     */
872    Twig.Template = function ( params ) {
873        var data = params.data,
874            id = params.id,
875            blocks = params.blocks,
876            macros = params.macros || {},
877            base = params.base,
878            path = params.path,
879            url = params.url,
880            // parser options
881            options = params.options;
882
883        // # What is stored in a Twig.Template
884        //
885        // The Twig Template hold several chucks of data.
886        //
887        //     {
888        //          id:     The token ID (if any)
889        //          tokens: The list of tokens that makes up this template.
890        //          blocks: The list of block this template contains.
891        //          base:   The base template (if any)
892        //            options:  {
893        //                Compiler/parser options
894        //
895        //                strict_variables: true/false
896        //                    Should missing variable/keys emit an error message. If false, they default to null.
897        //            }
898        //     }
899        //
900
901        this.id     = id;
902        this.base   = base;
903        this.path   = path;
904        this.url    = url;
905        this.macros = macros;
906        this.options = options;
907
908        this.reset(blocks);
909
910        if (is('String', data)) {
911            this.tokens = Twig.prepare.apply(this, [data]);
912        } else {
913            this.tokens = data;
914        }
915
916        if (id !== undefined) {
917            Twig.Templates.save(this);
918        }
919    };
920
921    Twig.Template.prototype.reset = function(blocks) {
922        Twig.log.debug("Twig.Template.reset", "Reseting template " + this.id);
923        this.blocks = {};
924        this.importedBlocks = [];
925        this.originalBlockTokens = {};
926        this.child = {
927            blocks: blocks || {}
928        };
929        this.extend = null;
930    };
931
932    Twig.Template.prototype.render = function (context, params) {
933        params = params || {};
934
935        var output,
936            url;
937
938        this.context = context || {};
939
940        // Clear any previous state
941        this.reset();
942        if (params.blocks) {
943            this.blocks = params.blocks;
944        }
945        if (params.macros) {
946            this.macros = params.macros;
947        }
948
949        output = Twig.parse.apply(this, [this.tokens, this.context]);
950
951        // Does this template extend another
952        if (this.extend) {
953            var ext_template;
954
955            // check if the template is provided inline
956            if ( this.options.allowInlineIncludes ) {
957                ext_template = Twig.Templates.load(this.extend);
958                if ( ext_template ) {
959                    ext_template.options = this.options;
960                }
961            }
962
963            // check for the template file via include
964            if (!ext_template) {
965                url = relativePath(this, this.extend);
966
967                ext_template = Twig.Templates.loadRemote(url, {
968                    method: this.url?'ajax':'fs',
969                    base: this.base,
970                    async:  false,
971                    id:     url,
972                    options: this.options
973                });
974            }
975
976            this.parent = ext_template;
977
978            return this.parent.render(this.context, {
979                blocks: this.blocks
980            });
981        }
982
983        if (params.output == 'blocks') {
984            return this.blocks;
985        } else if (params.output == 'macros') {
986            return this.macros;
987        } else {
988            return output;
989        }
990    };
991
992    Twig.Template.prototype.importFile = function(file) {
993        var url, sub_template;
994        if ( !this.url && !this.path && this.options.allowInlineIncludes ) {
995            sub_template = Twig.Templates.load(file);
996            sub_template.options = this.options;
997            if ( sub_template ) {
998                return sub_template;
999            }
1000
1001            throw new Twig.Error("Didn't find the inline template by id");
1002        }
1003
1004        url = relativePath(this, file);
1005
1006        // Load blocks from an external file
1007        sub_template = Twig.Templates.loadRemote(url, {
1008            method: this.url?'ajax':'fs',
1009            base: this.base,
1010            async: false,
1011            options: this.options,
1012            id: url
1013        });
1014
1015        return sub_template;
1016    };
1017
1018    Twig.Template.prototype.importBlocks = function(file, override) {
1019        var sub_template = this.importFile(file),
1020            context = this.context,
1021            that = this,
1022            key;
1023
1024        override = override || false;
1025
1026        sub_template.render(context);
1027
1028        // Mixin blocks
1029        Twig.forEach(Object.keys(sub_template.blocks), function(key) {
1030            if (override || that.blocks[key] === undefined) {
1031                that.blocks[key] = sub_template.blocks[key];
1032                that.importedBlocks.push(key);
1033            }
1034        });
1035    };
1036
1037    Twig.Template.prototype.compile = function(options) {
1038        // compile the template into raw JS
1039        return Twig.compiler.compile(this, options);
1040    };
1041
1042    /**
1043     * Create safe output
1044     *
1045     * @param {string} Content safe to output
1046     *
1047     * @return {String} Content wrapped into a String
1048     */
1049
1050    Twig.Markup = function(content) {
1051        if (typeof content === 'string' && content.length > 0) {
1052            content = new String(content);
1053            content.twig_markup = true;
1054        }
1055        return content;
1056    }
1057
1058    /**
1059     * Generate the relative canonical version of a url based on the given base path and file path.
1060     *
1061     * @param {string} template The Twig.Template.
1062     * @param {string} file The file path, relative to the base path.
1063     *
1064     * @return {string} The canonical version of the path.
1065     */
1066    function relativePath(template, file) {
1067        var base,
1068            base_path,
1069            sep_chr = "/",
1070            new_path = [],
1071            val;
1072
1073        if (template.url) {
1074            if (typeof template.base !== 'undefined') {
1075                base = template.base + ((template.base.charAt(template.base.length-1) === '/') ? '' : '/');
1076            } else {
1077                base = template.url;
1078            }
1079        } else if (template.path) {
1080            // Get the system-specific path separator
1081            var path = require("path"),
1082                sep = path.sep || sep_chr,
1083                relative = new RegExp("^\\.{1,2}" + sep.replace("\\", "\\\\"));
1084            file = file.replace(/\//g, sep);
1085
1086            if (template.base !== undefined && file.match(relative) == null) {
1087                file = file.replace(template.base, '');
1088                base = template.base + sep;
1089            } else {
1090                base = template.path;
1091            }
1092
1093            base = base.replace(sep+sep, sep);
1094            sep_chr = sep;
1095        } else {
1096            throw new Twig.Error("Cannot extend an inline template.");
1097        }
1098
1099        base_path = base.split(sep_chr);
1100
1101        // Remove file from url
1102        base_path.pop();
1103        base_path = base_path.concat(file.split(sep_chr));
1104
1105        while (base_path.length > 0) {
1106            val = base_path.shift();
1107            if (val == ".") {
1108                // Ignore
1109            } else if (val == ".." && new_path.length > 0 && new_path[new_path.length-1] != "..") {
1110                new_path.pop();
1111            } else {
1112                new_path.push(val);
1113            }
1114        }
1115
1116        return new_path.join(sep_chr);
1117    }
1118
1119    return Twig;
1120
1121}) (Twig || { });
1122
1123// The following methods are from MDN and are available under a
1124// [MIT License](http://www.opensource.org/licenses/mit-license.php) or are
1125// [Public Domain](https://developer.mozilla.org/Project:Copyrights).
1126//
1127// See:
1128// * [Object.keys - MDN](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys)
1129
1130// ## twig.fills.js
1131//
1132// This file contains fills for backwards compatability.
1133(function() {
1134    "use strict";
1135    // Handle methods that don't yet exist in every browser
1136
1137    if (!String.prototype.trim) {
1138        String.prototype.trim = function() {
1139            return this.replace(/^\s+|\s+$/g,'');
1140        }
1141    };
1142
1143    if(!Object.keys) Object.keys = function(o){
1144        if (o !== Object(o)) {
1145            throw new TypeError('Object.keys called on non-object');
1146        }
1147        var ret = [], p;
1148        for (p in o) if (Object.prototype.hasOwnProperty.call(o, p)) ret.push(p);
1149        return ret;
1150    }
1151
1152})();
1153// ## twig.lib.js
1154//
1155// This file contains 3rd party libraries used within twig.
1156//
1157// Copies of the licenses for the code included here can be found in the
1158// LICENSES.md file.
1159//
1160
1161var Twig = (function(Twig) {
1162
1163    // Namespace for libraries
1164    Twig.lib = { };
1165
1166    /**
1167    sprintf() for JavaScript 1.0.3
1168    https://github.com/alexei/sprintf.js
1169    **/
1170    var sprintfLib = (function() {
1171        var re = {
1172            not_string: /[^s]/,
1173            number: /[diefg]/,
1174            json: /[j]/,
1175            not_json: /[^j]/,
1176            text: /^[^\x25]+/,
1177            modulo: /^\x25{2}/,
1178            placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosuxX])
1178/,
1179            key: /^([a-z_][a-z_\d]*)/i,
1180            key_access: /^\.([a-z_][a-z_\d]*)/i,
1181            index_access: /^\[(\d+)\]/,
1182            sign: /^[\+\-]/
1183        }
1184
1185        function sprintf() {
1186            var key = arguments[0], cache = sprintf.cache
1187            if (!(cache[key] && cache.hasOwnProperty(key))) {
1188                cache[key] = sprintf.parse(key)
1189            }
1190            return sprintf.format.call(null, cache[key], arguments)
1191        }
1192
1193        sprintf.format = function(parse_tree, argv) {
1194            var cursor = 1, tree_length = parse_tree.length, node_type = "", arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ""
1195            for (i = 0; i < tree_length; i++) {
1196                node_type = get_type(parse_tree[i])
1197                if (node_type === "string") {
1198                    output[output.length] = parse_tree[i]
1199                }
1200                else if (node_type === "array") {
1201                    match = parse_tree[i] // convenience purposes only
1202                    if (match[2]) { // keyword argument
1203                        arg = argv[cursor]
1204                        for (k = 0; k < match[2].length; k++) {
1205                            if (!arg.hasOwnProperty(match[2][k])) {
1206                                throw new Error(sprintf("[sprintf] property '%s' does not exist", match[2][k]))
1207                            }
1208                            arg = arg[match[2][k]]
1209                        }
1210                    }
1211                    else if (match[1]) { // positional argument (explicit)
1212                        arg = argv[match[1]]
1213                    }
1214                    else { // positional argument (implicit)
1215                        arg = argv[cursor++]
1216                    }
1217
1218                    if (get_type(arg) == "function") {
1219                        arg = arg()
1220                    }
1221
1222                    if (re.not_string.test(match[8]) && re.not_json.test(match[8]) && (get_type(arg) != "number" && isNaN(arg))) {
1223                        throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
1224                    }
1225
1226                    if (re.number.test(match[8])) {
1227                        is_positive = arg >= 0
1228                    }
1229
1230                    switch (match[8]) {
1231                        case "b":
1232                            arg = arg.toString(2)
1233                            break
1234                        case "c":
1235                            arg = String.fromCharCode(arg)
1236                            break
1237                        case "d":
1238                        case "i":
1239                            arg = parseInt(arg, 10)
1240                            break
1241                        case "j":
1242                            arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
1243                            break
1244                        case "e":
1245                            arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential()
1246                            break
1247                        case "f":
1248                            arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
1249                            break
1250                        case "g":
1251                            arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
1252                            break
1253                        case "o":
1254                            arg = arg.toString(8)
1255                            break
1256                        case "s":
1257                            arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg)
1258                            break
1259                        case "u":
1260                            arg = arg >>> 0
1261                            break
1262                        case "x":
1263                            arg = arg.toString(16)
1264                            break
1265                        case "X":
1266                            arg = arg.toString(16).toUpperCase()
1267                            break
1268                    }
1269                    if (re.json.test(match[8])) {
1270                        output[output.length] = arg
1271                    }
1272                    else {
1273                        if (re.number.test(match[8]) && (!is_positive || match[3])) {
1274                            sign = is_positive ? "+" : "-"
1275                            arg = arg.toString().replace(re.sign, "")
1276                        }
1277                        else {
1278                            sign = ""
1279                        }
1280                        pad_character = match[4] ? match[4] === "0" ? "0" : match[4].charAt(1) : " "
1281                        pad_length = match[6] - (sign + arg).length
1282                        pad = match[6] ? (pad_length >
1282 0 ? str_repeat(pad_character, pad_length) : "") : ""
1283                        output[output.length] = match[5] ? sign + arg + pad : (pad_character === "0" ? sign + pad + arg : pad + sign + arg)
1284                    }
1285                }
1286            }
1287            return output.join("")
1288        }
1289
1290        sprintf.cache = {}
1291
1292        sprintf.parse = function(fmt) {
1293            var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
1294            while (_fmt) {
1295                if ((match = re.text.exec(_fmt)) !== null) {
1296                    parse_tree[parse_tree.length] = match[0]
1297                }
1298                else if ((match = re.modulo.exec(_fmt)) !== null) {
1299                    parse_tree[parse_tree.length] = "%"
1300                }
1301                else if ((match = re.placeholder.exec(_fmt)) !== null) {
1302                    if (match[2]) {
1303                        arg_names |= 1
1304                        var field_list = [], replacement_field = match[2], field_match = []
1305                        if ((field_match = re.key.exec(replacement_field)) !== null) {
1306                            field_list[field_list.length] = field_match[1]
1307                            while ((replacement_field = replacement_field.substring(field_match[0].length)) !== "") {
1308                                if ((field_match = re.key_access.exec(replacement_field)) !== null) {
1309                                    field_list[field_list.length] = field_match[1]
1310                                }
1311                                else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
1312                                    field_list[field_list.length] = field_match[1]
1313                                }
1314                                else {
1315                                    throw new SyntaxError("[sprintf] failed to parse named argument key")
1316                                }
1317                            }
1318                        }
1319                        else {
1320                            throw new SyntaxError("[sprintf] failed to parse named argument key")
1321                        }
1322                        match[2] = field_list
1323                    }
1324                    else {
1325                        arg_names |= 2
1326                    }
1327                    if (arg_names === 3) {
1328                        throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
1329                    }
1330                    parse_tree[parse_tree.length] = match
1331                }
1332                else {
1333                    throw new SyntaxError("[sprintf] unexpected placeholder")
1334                }
1335                _fmt = _fmt.substring(match[0].length)
1336            }
1337            return parse_tree
1338        }
1339
1340        var vsprintf = function(fmt, argv, _argv) {
1341            _argv = (argv || []).slice(0)
1342            _argv.splice(0, 0, fmt)
1343            return sprintf.apply(null, _argv)
1344        }
1345
1346        /**
1347         * helpers
1348         */
1349        function get_type(variable) {
1350            return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
1351        }
1352
1353        function str_repeat(input, multiplier) {
1354            return Array(multiplier + 1).join(input)
1355        }
1356
1357        /**
1358         * export
1359         */
1360        return {
1361            sprintf: sprintf,
1362            vsprintf: vsprintf
1363        }
1364    })();
1365
1366    var sprintf = sprintfLib.sprintf;
1367    var vsprintf = sprintfLib.vsprintf;
1368
1369    // Expose to Twig
1370    Twig.lib.sprintf = sprintf;
1371    Twig.lib.vsprintf = vsprintf;
1372
1373
1374    /**
1375     * jPaq - A fully customizable JavaScript/JScript library
1376     * http://jpaq.org/
1377     *
1378     * Copyright (c) 2011 Christopher West
1379     * Licensed under the MIT license.
1380     * http://jpaq.org/license/
1381     *
1382     * Version: 1.0.6.0000W
1383     * Revised: April 6, 2011
1384     */
1385    ; (function() {
1386        var shortDays = "Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(",");
1387        var fullDays = "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(",");
1388        var shortMonths = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(",");
1389        var fullMonths = "January,February,March,April,May,June,July,August,September,October,November,December".split(",");
1390        function getOrdinalFor(intNum) {
1391                return (((intNum = Math.abs(intNum) % 100) % 10 == 1 && intNum != 11) ? "st"
1392                        : (intNum % 10 == 2 && intNum != 12) ? "nd" : (intNum % 10 == 3
1393                        && intNum != 13) ? "rd" : "th");
1394        }
1395        function getISO8601Year(aDate) {
1396                var d = new Date(aDate.getFullYear() + 1, 0, 4);
1397                if((d - aDate) / 86400000 < 7 && (aDate.getDay() + 6) % 7 < (d.getDay() + 6) % 7)
1398                        return d.getFullYear();
1399                if(aDate.getMonth() > 0 || aDate.getDate() >= 4)
1400                        return aDate.getFullYear();
1401                return aDate.getFullYear() - (((aDate.getDay() + 6) % 7 - aDate.getDate() > 2) ? 1 : 0);
1402        }
1403        function getISO8601Week(aDate) {
1404                // Get a day during the first week of the year.
1405                var d = new Date(getISO8601Year(aDate), 0, 4);
1406                // Get the first monday of the year.
1407                d.setDate(d.getDate() - (d.getDay() + 6) % 7);
1408                return parseInt((aDate - d) / 604800000) + 1;
1409        }
1410        Twig.lib.formatDate = function(date, format) {
1411            /// <summary>
1412            ///   Gets a string for this date, formatted according to the given format
1413            ///   string.
1414            /// </summary>
1415            /// <param name="format" type="String">
1416            ///   The format of the output date string.  The format str
1416ing works in a
1417            ///   nearly identical way to the PHP date function which is highlighted here:
1418            ///   http://php.net/manual/en/function.date.php.
1419            ///   The only difference is the fact that "u" signifies milliseconds
1420            ///   instead of microseconds.  The following characters are recognized in
1421            ///   the format parameter string:
1422            ///     d - Day of the month, 2 digits with leading zeros
1423            ///     D - A textual representation of a day, three letters
1424            ///     j - Day of the month without leading zeros
1425            ///     l (lowercase 'L') - A full textual representation of the day of the week
1426            ///     N - ISO-8601 numeric representation of the day of the week (starting from 1)
1427            ///     S - English ordinal suffix for the day of the month, 2 characters st,
1428            ///         nd, rd or th. Works well with j.
1429            ///     w - Numeric representation of the day of the week (starting from 0)
1430            ///     z - The day of the year (starting from 0)
1431            ///     W - ISO-8601 week number of year, weeks starting on Monday
1432            ///     F - A full textual representation of a month, such as January or March
1433            ///     m - Numeric representation of a month, with leading zeros
1434            ///     M - A short textual representation of a month, three letters
1435            ///     n - Numeric representation of a month, without leading zeros
1436            ///     t - Number of days in the given month
1437            ///     L - Whether it's a leap year
1438            ///     o - ISO-8601 year number. This has the same value as Y, except that if
1439            ///         the ISO week number (W) belongs to the previous or next year, that
1440            ///         year is used instead.
1441            ///     Y - A full numeric representation of a year, 4 digits
1442            ///     y - A two digit representation of a year
1443            ///     a - Lowercase Ante meridiem and Post meridiem
1444            ///     A - Uppercase Ante meridiem and Post meridiem
1445            ///     B - Swatch Internet time
1446            ///     g - 12-hour format of an hour without leading zeros
1447            ///     G - 24-hour format of an hour without leading zeros
1448            ///     h - 12-hour format of an hour with leading zeros
1449            ///     H - 24-hour format of an hour with leading zeros
1450            ///     i - Minutes with leading zeros
1451            ///     s - Seconds, with leading zeros
1452            ///     u - Milliseconds
1453            ///     U - Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
1454            /// </param>
1455            /// <returns type="String">
1456            ///   Returns the string for this date, formatted according to the given
1457            ///   format string.
1458            /// </returns>
1459            // If the format was not passed, use the default toString method.
1460            if(typeof format !== "string" || /^\s*$/.test(format))
1461                    return date + "";
1462            var jan1st = new Date(date.getFullYear(), 0, 1);
1463            var me = date;
1464            return format.replace(/[dDjlNSwzWFmMntLoYyaABgGhHisuU]/g, function(option) {
1465                switch(option) {
1466                    // Day of the month, 2 digits with leading zeros
1467                    case "d": return ("0" + me.getDate()).replace(/^.+(..)$/, "$1");
1468                    // A textual representation of a day, three letters
1469                    case "D": return shortDays[me.getDay()];
1470                    // Day of the month without leading zeros
1471                    case "j": return me.getDate();
1472                    // A full textual representation of the day of the week
1473                    case "l": return fullDays[me.getDay()];
1474                    // ISO-8601 numeric representation of the day of the week
1475                    case "N": return (me.getDay() + 6) % 7 + 1;
1476                    // English ordinal suffix for the day of the month, 2 characters
1477                    case "S": return getOrdinalFor(me.getDate());
1478                    // Numeric representation of the day of the week
1479                    case "w": return me.getDay();
1480                    // The day of the year (starting from 0)
1481                    case "z": return Math.ceil((jan1st - me) / 86400000);
1482                    // ISO-8601 week number of year, weeks starting on Monday
1483                    case "W": return ("0" + getISO8601Week(me)).replace(/^.(..)$/, "$1");
1484                    // A full textual representation of a month, such as January or March
1485                    case "F": return fullMonths[me.getMonth()];
1486                    // Numeric representation of a month, with leading zeros
1487                    case "m": return ("0" + (me.getMonth() + 1)).replace(/^.+(..)$/, "$1");
1488                    // A short textual representation of a month, three letters
1489                    case "M": return shortMonths[me.getMonth()];
1490                    // Numeric representation of a month, without leading zeros
1491                    case "n": return me.getMonth() + 1;
1492                    // Number of days in the given month
1493                    case "t": return new Date(me.getFullYear(), me.getMonth() + 1, -1).getDate();
1494                    // Whether it's a leap year
1495                    case "L": return new Date(me.getFullYear(), 1, 29).getDate() == 29 ? 1 : 0;
1496                    // ISO-8601 year number. This has the same value as Y, except that if the
1497                    // ISO week number (W) belongs to the previous or next year, that year is
1498                    // used instead.
1499                    case "o": return getISO8601Year(me);
1500                    // A full numeric representation of a year, 4 digits
1501                    case "Y": return me.getFullYear();
1502                    // A two digit representation of a year
1503                    case "y": return (me.getFullYear() + "").replace(/^.+(..)$/, "$1");
1504                    // Lowercase Ante meridiem and Post meridiem
1505                    case "a": return me.getHours() < 12 ? "am" : "pm";
1506                    // Uppercase Ante meridiem and Post meridiem
1507                    case "A": return me.getHours() < 12 ? "AM" : "PM";
1508                    // Swatch Internet time
1509                    case "B": return Math.floor((((me.getUTCHours() + 1) % 24) + me.getUTCMinutes() / 60 + me.getUTCSeconds() / 3600) * 1000 / 24);
1510                    // 12-hour format of an hour without leading zeros
1511                    case "g": return me.getHours() % 12 != 0 ? me.getHours() % 12 : 12;
1512                    // 24-hour format of an hour without leading zeros
1513                    case "G": return me.getHours();
1514                    // 12-hour format of an hour with leading zeros
1515                    case "h": return ("0" + (me.getHours() % 12 != 0 ? me.getHours() % 12 : 12)).replace(/^.+(..)$/, "$1");
1516                    // 24-hour format of an hour with leading zeros
1517                    case "H": return ("0" + me.getHours()).replace(/^.+(..)$/, "$1");
1518                    // Minutes with leading zeros
1519                    case "i": return ("0" + me.getMinutes()).replace(/^.+(..)$/, "$1");
1520                    // Seconds, with leading zeros
1521                    case "s": return ("0" + me.getSeconds()).replace(/^.+(..)$/, "$1");
1522                    // Milliseconds
1523                    case "u": return me.getMilliseconds();
1524                    // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT)
1525                    case "U": return me.getTime() / 1000;
1526                }
1527            });
1528        };
1529    })();
1530
1531    Twig.lib.strip_tags = function(input, allowed) {
1532        // Strips HTML and PHP tags from a string
1533        //
1534        // version: 1109.2015
1535        // discuss at: http://phpjs.org/functions/strip_tags
1536        // +   original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1537        // +   improved by: Luke Godfrey
1538        // +      input by: Pul
1539        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1540        // +   bugfixed by: Onno Marsman
1541        // +      input by: Alex
1542        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1543        // +      input by: Marc Palau
1544        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1545        // +      input by: Brett Zamir (http://brett-zamir.me)
1546        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1547        // +   bugfixed by: Eric Nagel
1548        // +      input by: Bobby Drake
1549        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1550        // +   bugfixed by: Tomasz Wesolowski
1551        // +      input by: Evertjan Garretsen
1552        // +    revised by: Rafał Kukawski (http://blog.kukawski.pl/)
1553        // *     example 1: strip_tags('<p>Kevin</p> <b>van</b> <i>Zonneveld</i>', '<i><b>');
1554        // *     returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
1555        // *     example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>');
1556        // *     returns 2: '<p>Kevin van Zonneveld</p>'
1557        // *     example 3: strip_tags("<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>", "<a>");
1558        // *     returns 3: '<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>'
1559        // *     example 4: strip_tags('1 < 5 5 > 1');
1560        // *     returns 4: '1 < 5 5 > 1'
1561        // *     example 5: strip_tags('1 <br/> 1');
1562        // *     returns 5: '1  1'
1563        // *     example 6: strip_tags('1 <br/> 1', '<br>');
1564        // *     returns 6: '1  1'
1565        // *     example 7: strip_tags('1 <br/> 1', '<br><br/>');
1566        // *     returns 7: '1 <br/> 1'
1567        allowed = (((allowed || "") + "").toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join(''); // making sure the allowed arg is a string containing only tags in lowercase (<a><b><c>)
1568        var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
1569            commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
1570        return input.replace(commentsAndPhpTags, '').replace(tags, function ($0, $1) {
1571            return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
1572        });
1573    }
1574
1575    Twig.lib.parseISO8601Date = function (s){
1576        // Taken from http://n8v.enteuxis.org/2010/12/parsing-iso-8601-dates-in-javascript/
1577        // parenthese matches:
1578        // year month day    hours minutes seconds  
1579        // dotmilliseconds 
1580        // tzstring plusminus hours minutes
1581        var re = /(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?(Z|([+-])(\d\d):(\d\d))/;
1582
1583        var d = [];
1584        d = s.match(re);
1585
1586        // "2010-12-07T11:00:00.000-09:00" parses to:
1587        //  ["2010-12-07T11:00:00.000-09:00", "2010", "12", "07", "11",
1588        //     "00", "00", ".000", "-09:00", "-", "09", "00"]
1589        // "2010-12-07T11:00:00.000Z" parses to:
1590        //  ["2010-12-07T11:00:00.000Z",      "2010", "12", "07", "11", 
1591        //     "00", "00", ".000", "Z", undefined, undefined, undefined]
1592
1593        if (! d) {
1594            throw "Couldn't parse ISO 8601 date string '" + s + "'";
1595        }
1596
1597        // parse strings, leading zeros into proper ints
1598        var a = [1,2,3,4,5,6,10,11];
1599        for (var i in a) {
1600            d[a[i]] = parseInt(d[a[i]], 10);
1601        }
1602        d[7] = parseFloat(d[7]);
1603
1604        // Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
1605        // note that month is 0-11, not 1-12
1606        // see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Date/UTC
1607        var ms = Date.UTC(d[1], d[2] - 1, d[3], d[4], d[5], d[6]);
1608
1609        // if there are milliseconds, add them
1610        if (d[7] > 0) {  
1611            ms += Math.round(d[7] * 1000);
1612        }
1613
1614        // if there's a timezone, calculate it
1615        if (d[8] != "Z" && d[10]) {
1616            var offset = d[10] * 60 * 60 * 1000;
1617            if (d[11]) {
1618                offset += d[11] * 60 * 1000;
1619            }
1620            if (d[9] == "-") {
1621                ms -= offset;
1622            }
1623            else {
1624                ms += offset;
1625            }
1626        }
1627
1628        return new Date(ms);
1629    };
1630
1631    Twig.lib.strtotime = function (text, now) {
1632        //  discuss at: http://phpjs.org/functions/strtotime/
1633        //     version: 1109.2016
1634        // original by: Caio Ariede (http://caioariede.com)
1635        // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1636        // improved by: Caio Ariede (http://caioariede.com)
1637        // improved by: A. Matías Quezada (http://amatiasq.com)
1638        // improved by: preuter
1639        // improved by: Brett Zamir (http://brett-zamir.me)
1640        // improved by: Mirko Faber
1641        //    input by: David
1642        // bugfixed by: Wagner B. Soares
1643        // bugfixed by: Artur Tchernychev
1644        //        note: Examples all have a fixed timestamp to prevent tests to fail because of variable time(zones)
1645        //   example 1: strtotime('+1 day', 1129633200);
1646        //   returns 1: 1129719600
1647        //   example 2: strtotime('+1 week 2 days 4 hours 2 seconds', 1129633200);
1648        //   returns 2: 1130425202
1649        //   example 3: strtotime('last month', 1129633200);
1650        //   returns 3: 1127041200
1651        //   example 4: strtotime('2009-05-04 08:30:00 GMT');
1652        //   returns 4: 1241425800
1653
1654        var parsed, match, today, year, date, days, ranges, len, times, regex, i, fail = false;
1655
1656        if (!text) {
1657            return fail;
1658        }
1659
1660        // Unecessary spaces
1661        text = text.replace(/^\s+|\s+$/g, '')
1662            .replace(/\s{2,}/g, ' ')
1663            .replace(/[\t\r\n]/g, '')
1664            .toLowerCase();
1665
1666        // in contrast to php, js Date.parse function interprets:
1667        // dates given as yyyy-mm-dd as in timezone: UTC,
1668        // dates with "." or "-" as MDY instead of DMY
1669        // dates with two-digit years differently
1670        // etc...etc...
1671        // ...therefore we manually parse lots of common date formats
1672        match = text.match(
1673            /^(\d{1,4})([\-\.\/\:])(\d{1,2})([\-\.\/\:])(\d{1,4})(?:\s(\d{1,2}):(\d{2})?:?(\d{2})?)?(?:\s([A-Z]+)?)?$/);
1674
1675        if (match && match[2] === match[4]) {
1676            if (match[1] > 1901) {
1677                switch (match[2]) {
1678                case '-':
1679                    {
1680                        // YYYY-M-D
1681                        if (match[3] > 12 || match[5] > 31) {
1682                            return fail;
1683                        }
1684
1685                        return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
1686                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1687                    }
1688                case '.':
1689                    {
1690                        // YYYY.M.D is not parsed by strtotime()
1691                        return fail;
1692                    }
1693                case '/':
1694                    {
1695                        // YYYY/M/D
1696                        if (match[3] > 12 || match[5] > 31) {
1697                            return fail;
1698                        }
1699
1700                        return new Date(match[1], parseInt(match[3], 10) - 1, match[5],
1701                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1702                    }
1703                }
1704            } else if (match[5] > 1901) {
1705                switch (match[2]) {
1706                case '-':
1707                    {
1708                        // D-M-YYYY
1709                        if (match[3] > 12 || match[1] > 31) {
1710                            return fail;
1711                        }
1712
1713                        return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
1714                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1715                    }
1716                case '.':
1717                    {
1718                        // D.M.YYYY
1719                        if (match[3] > 12 || match[1] > 31) {
1720                            return fail;
1721                        }
1722
1723                        return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
1724                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1725                    }
1726                case '/':
1727                    {
1728                        // M/D/YYYY
1729                        if (match[1] > 12 || match[3] > 31) {
1730                            return fail;
1731                        }
1732
1733                        return new Date(match[5], parseInt(match[1], 10) - 1, match[3],
1734                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1735                    }
1736                }
1737            } else {
1738                switch (match[2]) {
1739                case '-':
1740                    {
1741                        // YY-M-D
1742                        if (match[3] > 12 || match[5] > 31 || (match[1] < 70 && match[1] > 38)) {
1743                            return fail;
1744                        }
1745
1746                        year = match[1] >= 0 && match[1] <= 38 ? +match[1] + 2000 : match[1];
1747                        return new Date(year, parseInt(match[3], 10) - 1, match[5],
1748                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1749                    }
1750                case '.':
1751                    {
1752                        // D.M.YY or H.MM.SS
1753                        if (match[5] >= 70) {
1754                            // D.M.YY
1755                            if (match[3] > 12 || match[1] > 31) {
1756                                return fail;
1757                            }
1758
1759                            return new Date(match[5], parseInt(match[3], 10) - 1, match[1],
1760                                match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1761                        }
1762                        if (match[5] < 60 && !match[6]) {
1763                            // H.MM.SS
1764                            if (match[1] > 23 || match[3] > 59) {
1765                                return fail;
1766                            }
1767
1768                            today = new Date();
1769                            return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
1770                                match[1] || 0, match[3] || 0, match[5] || 0, match[9] || 0) / 1000;
1771                        }
1772
1773                        // invalid format, cannot be parsed
1774                        return fail;
1775                    }
1776                case '/':
1777                    {
1778                        // M/D/YY
1779                        if (match[1] > 12 || match[3] > 31 || (match[5] < 70 && match[5] > 38)) {
1780                            return fail;
1781                        }
1782
1783                        year = match[5] >= 0 && match[5] <= 38 ? +match[5] + 2000 : match[5];
1784                        return new Date(year, parseInt(match[1], 10) - 1, match[3],
1785                            match[6] || 0, match[7] || 0, match[8] || 0, match[9] || 0) / 1000;
1786                    }
1787                case ':':
1788                    {
1789                        // HH:MM:SS
1790                        if (match[1] > 23 || match[3] > 59 || match[5] > 59) {
1791                            return fail;
1792                        }
1793
1794                        today = new Date();
1795                        return new Date(today.getFullYear(), today.getMonth(), today.getDate(),
1796                            match[1] || 0, match[3] || 0, match[5] || 0) / 1000;
1797                    }
1798                }
1799            }
1800        }
1801
1802        // other formats and "now" should be parsed by Date.parse()
1803        if (text === 'now') {
1804            return now === null || isNaN(now) ? new Date()
1805                .getTime() / 1000 | 0 : now | 0;
1806        }
1807        if (!isNaN(parsed = Date.parse(text))) {
1808            return parsed / 1000 | 0;
1809        }
1810
1811        date = now ? new Date(now * 1000) : new Date();
1812        days = {
1813            'sun': 0,
1814            'mon': 1,
1815            'tue': 2,
1816            'wed': 3,
1817            'thu': 4,
1818            'fri': 5,
1819            'sat': 6
1820        };
1821        ranges = {
1822            'yea': 'FullYear',
1823            'mon': 'Month',
1824            'day': 'Date',
1825            'hou': 'Hours',
1826            'min': 'Minutes',
1827            'sec': 'Seconds'
1828        };
1829
1830        function lastNext(type, range, modifier) {
1831            var diff, day = days[range];
1832
1833            if (typeof day !== 'undefined') {
1834                diff = day - date.getDay();
1835
1836                if (diff === 0) {
1837                    diff = 7 * modifier;
1838                } else if (diff > 0 && type === 'last') {
1839                    diff -= 7;
1840                } else if (diff < 0 && type === 'next') {
1841                    diff += 7;
1842                }
1843
1844                date.setDate(date.getDate() + diff);
1845            }
1846        }
1847
1848        function process(val) {
1849            var splt = val.split(' '), // Todo: Reconcile this with regex using \s, taking into account browser issues with split and regexes
1850                type = splt[0],
1851                range = splt[1].substring(0, 3),
1852                typeIsNumber = /\d+/.test(type),
1853                ago = splt[2] === 'ago',
1854                num = (type === 'last' ? -1 : 1) * (ago ? -1 : 1);
1855
1856            if (typeIsNumber) {
1857                num *= parseInt(type, 10);
1858            }
1859
1860            if (ranges.hasOwnProperty(range) && !splt[1].match(/^mon(day|\.)?$/i)) {
1861                return date['set' + ranges[range]](date['get' + ranges[range]]() + num);
1862            }
1863
1864            if (range === 'wee') {
1865                return date.setDate(date.getDate() + (num * 7));
1866            }
1867
1868            if (type === 'next' || type === 'last') {
1869                lastNext(type, range, num);
1870            } else if (!typeIsNumber) {
1871                return false;
1872            }
1873
1874            return true;
1875        }
1876
1877        times = '(years?|months?|weeks?|days?|hours?|minutes?|min|seconds?|sec' +
1878            '|sunday|sun\\.?|monday|mon\\.?|tuesday|tue\\.?|wednesday|wed\\.?' +
1879            '|thursday|thu\\.?|friday|fri\\.?|saturday|sat\\.?)';
1880        regex = '([+-]?\\d+\\s' + times + '|' + '(last|next)\\s' + times + ')(\\sago)?';
1881
1882        match = text.match(new RegExp(regex, 'gi'));
1883        if (!match) {
1884            return fail;
1885        }
1886
1887        for (i = 0, len = match.length; i < len; i++) {
1888            if (!process(match[i])) {
1889                return fail;
1890            }
1891        }
1892
1893        // ECMAScript 5 only
1894        // if (!match.every(process))
1895        //    return false;
1896
1897        return (date.getTime() / 1000);
1898    };
1899
1900    Twig.lib.is = function(type, obj) {
1901        var clas = Object.prototype.toString.call(obj).slice(8, -1);
1902        return obj !== undefined && obj !== null && clas === type;
1903    };
1904
1905    // shallow-copy an object
1906    Twig.lib.copy = function(src) {
1907        var target = {},
1908            key;
1909        for (key in src)
1910            target[key] = src[key];
1911
1912        return target;
1913    };
1914
1915    Twig.lib.replaceAll = function(string, search, replace) {
1916        return string.split(search).join(replace);
1917    };
1918
1919    // chunk an array (arr) into arrays of (size) items, returns an array of arrays, or an empty array on invalid input
1920    Twig.lib.chunkArray = function (arr, size) {
1921        var returnVal = [],
1922            x = 0,
1923            len = arr.length;
1924
1925        if (size < 1 || !Twig.lib.is("Array", arr)) {
1926            return [];
1927        }
1928
1929        while (x < len) {
1930            returnVal.push(arr.slice(x, x += size));
1931        }
1932
1933        return returnVal;
1934    };
1935
1936    Twig.lib.round = function round(value, precision, mode) {
1937        //  discuss at: http://phpjs.org/functions/round/
1938        // original by: Philip Peterson
1939        //  revised by: Onno Marsman
1940        //  revised by: T.Wild
1941        //  revised by: Rafał Kukawski (http://blog.kukawski.pl/)
1942        //    input by: Greenseed
1943        //    input by: meo
1944        //    input by: William
1945        //    input by: Josep Sanz (http://www.ws3.es/)
1946        // bugfixed by: Brett Zamir (http://brett-zamir.me)
1947        //        note: Great work. Ideas for improvement:
1948        //        note: - code more compliant with developer guidelines
1949        //        note: - for implementing PHP constant arguments look at
1950        //        note: the pathinfo() function, it offers the greatest
1951        //        note: flexibility & compatibility possible
1952        //   example 1: round(1241757, -3);
1953        //   returns 1: 1242000
1954        //   example 2: round(3.6);
1955        //   returns 2: 4
1956        //   example 3: round(2.835, 2);
1957        //   returns 3: 2.84
1958        //   example 4: round(1.1749999999999, 2);
1959        //   returns 4: 1.17
1960        //   example 5: round(58551.799999999996, 2);
1961        //   returns 5: 58551.8
1962
1963        var m, f, isHalf, sgn; // helper variables
1964        precision |= 0; // making sure precision is integer
1965        m = Math.pow(10, precision);
1966        value *= m;
1967        sgn = (value > 0) | -(value < 0); // sign of the number
1968        isHalf = value % 1 === 0.5 * sgn;
1969        f = Math.floor(value);
1970
1971        if (isHalf) {
1972            switch (mode) {
1973                case 'PHP_ROUND_HALF_DOWN':
1974                    value = f + (sgn < 0); // rounds .5 toward zero
1975                    break;
1976                case 'PHP_ROUND_HALF_EVEN':
1977                    value = f + (f % 2 * sgn); // rouds .5 towards the next even integer
1978                    break;
1979                case 'PHP_ROUND_HALF_ODD':
1980                    value = f + !(f % 2); // rounds .5 towards the next odd integer
1981                    break;
1982                default:
1983                    value = f + (sgn > 0); // rounds .5 away from zero
1984            }
1985        }
1986
1987        return (isHalf ? value : Math.round(value)) / m;
1988    }
1989
1990    return Twig;
1991
1992})(Twig || { });
1993//     Twig.js
1994//     Available under the BSD 2-Clause License
1995//     https://github.com/justjohn/twig.js
1996
1997// ## twig.logic.js
1998//
1999// This file handles tokenizing, compiling and parsing logic tokens. {% ... %}
2000var Twig = (function (Twig) {
2001    "use strict";
2002
2003    /**
2004     * Namespace for logic handling.
2005     */
2006    Twig.logic = {};
2007
2008    /**
2009     * Logic token types.
2010     */
2011    Twig.logic.type = {
2012        if_:       'Twig.logic.type.if',
2013        endif:     'Twig.logic.type.endif',
2014        for_:      'Twig.logic.type.for',
2015        endfor:    'Twig.logic.type.endfor',
2016        else_:     'Twig.logic.type.else',
2017        elseif:    'Twig.logic.type.elseif',
2018        set:       'Twig.logic.type.set',
2019        setcapture:'Twig.logic.type.setcapture',
2020        endset:    'Twig.logic.type.endset',
2021        filter:    'Twig.logic.type.filter',
2022        endfilter: 'Twig.logic.type.endfilter',
2023        block:     'Twig.logic.type.block',
2024        endblock:  'Twig.logic.type.endblock',
2025        extends_:  'Twig.logic.type.extends',
2026        use:       'Twig.logic.type.use',
2027        include:   'Twig.logic.type.include',
2028        spaceless: 'Twig.logic.type.spaceless',
2029        endspaceless: 'Twig.logic.type.endspaceless',
2030        macro:     'Twig.logic.type.macro',
2031        endmacro:  'Twig.logic.type.endmacro',
2032        import_:   'Twig.logic.type.import',
2033        from:      'Twig.logic.type.from',
2034        embed:     'Twig.logic.type.embed',
2035        endembed:  'Twig.logic.type.endembed'
2036    };
2037
2038
2039    // Regular expressions for handling logic tokens.
2040    //
2041    // Properties:
2042    //
2043    //      type:  The type of expression this matches
2044    //
2045    //      regex: A regular expression that matches the format of the token
2046    //
2047    //      next:  What logic tokens (if any) pop this token off the logic stack. If empty, the
2048    //             logic token is assumed to not require an end tag and isn't push onto the stack.
2049    //
2050    //      open:  Does this tag open a logic expression or is it standalone. For example,
2051    //             {% endif %} cannot exist without an opening {% if ... %} tag, so open = false.
2052    //
2053    //  Functions:
2054    //
2055    //      compile: A function that handles compiling the token into an output token ready for
2056    //               parsing with the parse function.
2057    //
2058    //      parse:   A function that parses the compiled token into output (HTML / whatever the
2059    //               template represents).
2060    Twig.logic.definitions = [
2061        {
2062            /**
2063             * If type logic tokens.
2064             *
2065             *  Format: {% if expression %}
2066             */
2067            type: Twig.logic.type.if_,
2068            regex: /^if\s+([\s\S]+)$/,
2069            next: [
2070                Twig.logic.type.else_,
2071                Twig.logic.type.elseif,
2072                Twig.logic.type.endif
2073            ],
2074            open: true,
2075            compile: function (token) {
2076                var expression = token.match[1];
2077                // Compile the expression.
2078                token.stack = Twig.expression.compile.apply(this, [{
2079                    type:  Twig.expression.type.expression,
2080                    value: expression
2081                }]).stack;
2082                delete token.match;
2083                return token;
2084            },
2085            parse: function (token, context, chain) {
2086                var output = '',
2087                    // Parse the expression
2088                    result = Twig.expression.parse.apply(this, [token.stack, context]);
2089
2090                // Start a new logic chain
2091                chain = true;
2092
2093                if (result) {
2094                    chain = false;
2095                    // parse if output
2096                    output = Twig.parse.apply(this, [token.output, context]);
2097                }
2098                return {
2099                    chain: chain,
2100                    output: output
2101                };
2102            }
2103        },
2104        {
2105            /**
2106             * Else if type logic tokens.
2107             *
2108             *  Format: {% elseif expression %}
2109             */
2110            type: Twig.logic.type.elseif,
2111            regex: /^elseif\s+([^\s].*)$/,
2112            next: [
2113                Twig.logic.type.else_,
2114                Twig.logic.type.elseif,
2115                Twig.logic.type.endif
2116            ],
2117            open: false,
2118            compile: function (token) {
2119                var expression = token.match[1];
2120                // Compile the expression.
2121                token.stack = Twig.expression.compile.apply(this, [{
2122                    type:  Twig.expression.type.expression,
2123                    value: expression
2124                }]).stack;
2125                delete token.match;
2126                return token;
2127            },
2128            parse: function (token, context, chain) {
2129                var output = '';
2130
2131                if (chain && Twig.expression.parse.apply(this, [token.stack, context]) === true) {
2132                    chain = false;
2133                    // parse if output
2134                    output = Twig.parse.apply(this, [token.output, context]);
2135                }
2136
2137                return {
2138                    chain: chain,
2139                    output: output
2140                };
2141            }
2142        },
2143        {
2144            /**
2145             * Else if type logic tokens.
2146             *
2147             *  Format: {% elseif expression %}
2148             */
2149            type: Twig.logic.type.else_,
2150            regex: /^else$/,
2151            next: [
2152                Twig.logic.type.endif,
2153                Twig.logic.type.endfor
2154            ],
2155            open: false,
2156            parse: function (token, context, chain) {
2157                var output = '';
2158                if (chain) {
2159                    output = Twig.parse.apply(this, [token.output, context]);
2160                }
2161                return {
2162                    chain: chain,
2163                    output: output
2164                };
2165            }
2166        },
2167        {
2168            /**
2169             * End if type logic tokens.
2170             *
2171             *  Format: {% endif %}
2172             */
2173            type: Twig.logic.type.endif,
2174            regex: /^endif$/,
2175            next: [ ],
2176            open: false
2177        },
2178        {
2179            /**
2180             * For type logic tokens.
2181             *
2182             *  Format: {% for expression %}
2183             */
2184            type: Twig.logic.type.for_,
2185            regex: /^for\s+([a-zA-Z0-9_,\s]+)\s+in\s+([^\s].*?)(?:\s+if\s+([^\s].*))?$/,
2186            next: [
2187                Twig.logic.type.else_,
2188                Twig.logic.type.endfor
2189            ],
2190            open: true,
2191            compile: function (token) {
2192                var key_value = token.match[1],
2193                    expression = token.match[2],
2194                    conditional = token.match[3],
2195                    kv_split = null;
2196
2197                token.key_var = null;
2198                token.value_var = null;
2199
2200                if (key_value.indexOf(",") >= 0) {
2201                    kv_split = key_value.split(',');
2202                    if (kv_split.length === 2) {
2203                        token.key_var = kv_split[0].trim();
2204                        token.value_var = kv_split[1].trim();
2205                    } else {
2206                        throw new Twig.Error("Invalid expression in for loop: " + key_value);
2207                    }
2208                } else {
2209                    token.value_var = key_value;
2210                }
2211
2212                // Valid expressions for a for loop
2213                //   for item     in expression
2214                //   for key,item in expression
2215
2216                // Compile the expression.
2217                token.expression = Twig.expression.compile.apply(this, [{
2218                    type:  Twig.expression.type.expression,
2219                    value: expression
2220                }]).stack;
2221
2222                // Compile the conditional (if available)
2223                if (conditional) {
2224                    token.conditional = Twig.expression.compile.apply(this, [{
2225                        type:  Twig.expression.type.expression,
2226                        value: conditional
2227                    }]).stack;
2228                }
2229
2230                delete token.match;
2231                return token;
2232            },
2233            parse: function (token, context, continue_chain) {
2234                // Parse expression
2235                var result = Twig.expression.parse.apply(this, [token.expression, context]),
2236                    output = [],
2237                    len,
2238                    index = 0,
2239                    keyset,
2240                    that = this,
2241                    conditional = token.conditional,
2242                    buildLoop = function(index, len) {
2243                        var isConditional = conditional !== undefined;
2244                        return {
2245                            index: index+1,
2246                            index0: index,
2247                            revindex: isConditional?undefined:len-index,
2248                            revindex0: isConditional?undefined:len-index-1,
2249                            first: (index === 0),
2250                            last: isConditional?undefined:(index === len-1),
2251                            length: isConditional?undefined:len,
2252                            parent: context
2253                        };
2254                    },
2255                    // run once for each iteration of the loop
2256                    loop = function(key, value) {
2257                        var inner_context = Twig.ChildContext(context);
2258
2259                        inner_context[token.value_var] = value;
2260
2261                        if (token.key_var) {
2262                            inner_context[token.key_var] = key;
2263                        }
2264
2265                        // Loop object
2266                        inner_context.loop = buildLoop(index, len);
2267
2268                        if (conditional === undefined ||
2269                            Twig.expression.parse.apply(that, [conditional, inner_context]))
2270                        {
2271                            output.push(Twig.parse.apply(that, [token.output, inner_context]));
2272                            index += 1;
2273                        }
2274
2275                        // Delete loop-related variables from the context
2276                        delete inner_context['loop'];
2277                        delete inner_context[token.value_var];
2278                        delete inner_context[token.key_var];
2279
2280                        // Merge in values that exist in context but have changed
2281                        // in inner_context.
2282                        Twig.merge(context, inner_context, true);
2283                    };
2284
2285
2286                if (Twig.lib.is('Array', result)) {
2287                    len = result.length;
2288                    Twig.forEach(result, function (value) {
2289                        var key = index;
2290
2291                        loop(key, value);
2292                    });
2293                } else if (Twig.lib.is('Object', result)) {
2294                    if (result._keys !== undefined) {
2295                        keyset = result._keys;
2296                    } else {
2297                        keyset = Object.keys(result);
2298                    }
2299                    len = keyset.length;
2300                    Twig.forEach(keyset, function(key) {
2301                        // Ignore the _keys property, it's internal to twig.js
2302                        if (key === "_keys") return;
2303
2304                        loop(key,  result[key]);
2305                    });
2306                }
2307
2308                // Only allow else statements if no output was generated
2309                continue_chain = (output.length === 0);
2310
2311                return {
2312                    chain: continue_chain,
2313                    output: Twig.output.apply(this, [output])
2314                };
2315            }
2316        },
2317        {
2318            /**
2319             * End if type logic tokens.
2320             *
2321             *  Format: {% endif %}
2322             */
2323            type: Twig.logic.type.endfor,
2324            regex: /^endfor$/,
2325            next: [ ],
2326            open: false
2327        },
2328        {
2329            /**
2330             * Set type logic tokens.
2331             *
2332             *  Format: {% set key = expression %}
2333             */
2334            type: Twig.logic.type.set,
2335            regex: /^set\s+([a-zA-Z0-9_,\s]+)\s*=\s*([\s\S]+)$/,
2336            next: [ ],
2337            open: true,
2338            compile: function (token) {
2339                var key = token.match[1].trim(),
2340                    expression = token.match[2],
2341                    // Compile the expression.
2342                    expression_stack  = Twig.expression.compile.apply(this, [{
2343                        type:  Twig.expression.type.expression,
2344                        value: expression
2345                    }]).stack;
2346
2347                token.key = key;
2348                token.expression = expression_stack;
2349
2350                delete token.match;
2351                return token;
2352            },
2353            parse: function (token, context, continue_chain) {
2354                var value = Twig.expression.parse.apply(this, [token.expression, context]),
2355                    key = token.key;
2356
2357                context[key] = value;
2358
2359                return {
2360                    chain: continue_chain,
2361                    context: context
2362                };
2363            }
2364        },
2365        {
2366            /**
2367             * Set capture type logic tokens.
2368             *
2369             *  Format: {% set key %}
2370             */
2371            type: Twig.logic.type.setcapture,
2372            regex: /^set\s+([a-zA-Z0-9_,\s]+)$/,
2373            next: [
2374                Twig.logic.type.endset
2375            ],
2376            open: true,
2377            compile: function (token) {
2378                var key = token.match[1].trim();
2379
2380                token.key = key;
2381
2382                delete token.match;
2383                return token;
2384            },
2385            parse: function (token, context, continue_chain) {
2386
2387                var value = Twig.parse.apply(this, [token.output, context]),
2388                    key = token.key;
2389
2390                // set on both the global and local context
2391                this.context[key] = value;
2392                context[key] = value;
2393
2394                return {
2395                    chain: continue_chain,
2396                    context: context
2397                };
2398            }
2399        },
2400        {
2401            /**
2402             * End set type block logic tokens.
2403             *
2404             *  Format: {% endset %}
2405             */
2406            type: Twig.logic.type.endset,
2407            regex: /^endset$/,
2408            next: [ ],
2409            open: false
2410        },
2411        {
2412            /**
2413             * Filter logic tokens.
2414             *
2415             *  Format: {% filter upper %} or {% filter lower|escape %}
2416             */
2417            type: Twig.logic.type.filter,
2418            regex: /^filter\s+(.+)$/,
2419            next: [
2420                Twig.logic.type.endfilter
2421            ],
2422            open: true,
2423            compile: function (token) {
2424                var expression = "|" + token.match[1].trim();
2425                // Compile the expression.
2426                token.stack = Twig.expression.compile.apply(this, [{
2427                    type:  Twig.expression.type.expression,
2428                    value: expression
2429                }]).stack;
2430                delete token.match;
2431                return token;
2432            },
2433            parse: function (token, context, chain) {
2434                var unfiltered = Twig.parse.apply(this, [token.output, context]),
2435                    stack = [{
2436                        type: Twig.expression.type.string,
2437                        value: unfiltered
2438                    }].concat(token.stack);
2439
2440                var output = Twig.expression.parse.apply(this, [stack, context]);
2441
2442                return {
2443                    chain: chain,
2444                    output: output
2445                };
2446            }
2447        },
2448        {
2449            /**
2450             * End filter logic tokens.
2451             *
2452             *  Format: {% endfilter %}
2453             */
2454            type: Twig.logic.type.endfilter,
2455            regex: /^endfilter$/,
2456            next: [ ],
2457            open: false
2458        },
2459        {
2460            /**
2461             * Block logic tokens.
2462             *
2463             *  Format: {% block title %}
2464             */
2465            type: Twig.logic.type.block,
2466            regex: /^block\s+([a-zA-Z0-9_]+)$/,
2467            next: [
2468                Twig.logic.type.endblock
2469            ],
2470            open: true,
2471            compile: function (token) {
2472                token.block = token.match[1].trim();
2473                delete token.match;
2474                return token;
2475            },
2476            parse: function (token, context, chain) {
2477                var block_output = "",
2478                    output = "",
2479                    isImported = this.importedBlocks.indexOf(token.block) > -1,
2480                    hasParent = this.blocks[token.block] && this.blocks[token.block].indexOf(Twig.placeholders.parent) > -1;
2481
2482                // Don't override previous blocks unless they're imported with "use"
2483                // Loops should be exempted as well.
2484                if (this.blocks[token.block] === undefined || isImported || hasParent || context.loop || token.overwrite) {
2485                    block_output = Twig.expression.parse.apply(this, [{
2486                        type: Twig.expression.type.string,
2487                        value: Twig.parse.apply(this, [token.output, context])
2488                    }, context]);
2489
2490                    if (isImported) {
2491                        // once the block is overridden, remove it from the list of imported blocks
2492                        this.importedBlocks.splice(this.importedBlocks.indexOf(token.block), 1);
2493                    }
2494
2495                    if (hasParent) {
2496                        this.blocks[token.block] = Twig.Markup(this.blocks[token.block].replace(Twig.placeholders.parent, block_output));
2497                    } else {
2498                        this.blocks[token.block] = block_output;
2499                    }
2500
2501                    this.originalBlockTokens[token.block] = {
2502                        type: token.type,
2503                        block: token.block,
2504                        output: token.output,
2505                        overwrite: true
2506                    };
2507                }
2508
2509                // Check if a child block has been set from a template extending this one.
2510                if (this.child.blocks[token.block]) {
2511                    output = this.child.blocks[token.block];
2512                } else {
2513                    output = this.blocks[token.block];
2514                }
2515
2516                return {
2517                    chain: chain,
2518                    output: output
2519                };
2520            }
2521        },
2522        {
2523            /**
2524             * End block logic tokens.
2525             *
2526             *  Format: {% endblock %}
2527             */
2528            type: Twig.logic.type.endblock,
2529            regex: /^endblock(?:\s+([a-zA-Z0-9_]+))?$/,
2530            next: [ ],
2531            open: false
2532        },
2533        {
2534            /**
2535             * Block logic tokens.
2536             *
2537             *  Format: {% extends "template.twig" %}
2538             */
2539            type: Twig.logic.type.extends_,
2540            regex: /^extends\s+(.+)$/,
2541            next: [ ],
2542            open: true,
2543            compile: function (token) {
2544                var expression = token.match[1].trim();
2545                delete token.match;
2546
2547                token.stack   = Twig.expression.compile.apply(this, [{
2548                    type:  Twig.expression.type.expression,
2549                    value: expression
2550                }]).stack;
2551
2552                return token;
2553            },
2554            parse: function (token, context, chain) {
2555                // Resolve filename
2556                var file = Twig.expression.parse.apply(this, [token.stack, context]);
2557
2558                // Set parent template
2559                this.extend = file;
2560
2561                return {
2562                    chain: chain,
2563                    output: ''
2564          
2564      };
2565            }
2566        },
2567        {
2568            /**
2569             * Block logic tokens.
2570             *
2571             *  Format: {% use "template.twig" %}
2572             */
2573            type: Twig.logic.type.use,
2574            regex: /^use\s+(.+)$/,
2575            next: [ ],
2576            open: true,
2577            compile: function (token) {
2578                var expression = token.match[1].trim();
2579                delete token.match;
2580
2581                token.stack = Twig.expression.compile.apply(this, [{
2582                    type:  Twig.expression.type.expression,
2583                    value: expression
2584                }]).stack;
2585
2586                return token;
2587            },
2588            parse: function (token, context, chain) {
2589                // Resolve filename
2590                var file = Twig.expression.parse.apply(this, [token.stack, context]);
2591
2592                // Import blocks
2593                this.importBlocks(file);
2594
2595                return {
2596                    chain: chain,
2597                    output: ''
2598                };
2599            }
2600        },
2601        {
2602            /**
2603             * Block logic tokens.
2604             *
2605             *  Format: {% includes "template.twig" [with {some: 'values'} only] %}
2606             */
2607            type: Twig.logic.type.include,
2608            regex: /^include\s+(ignore missing\s+)?(.+?)\s*(?:with\s+([\S\s]+?))?\s*(only)?$/,
2609            next: [ ],
2610            open: true,
2611            compile: function (token) {
2612                var match = token.match,
2613                    includeMissing = match[1] !== undefined,
2614                    expression = match[2].trim(),
2615                    withContext = match[3],
2616                    only = ((match[4] !== undefined) && match[4].length);
2617
2618                delete token.match;
2619
2620                token.only = only;
2621                token.includeMissing = includeMissing;
2622
2623                token.stack = Twig.expression.compile.apply(this, [{
2624                    type:  Twig.expression.type.expression,
2625                    value: expression
2626                }]).stack;
2627
2628                if (withContext !== undefined) {
2629                    token.withStack = Twig.expression.compile.apply(this, [{
2630                        type:  Twig.expression.type.expression,
2631                        value: withContext.trim()
2632                    }]).stack;
2633                }
2634
2635                return token;
2636            },
2637            parse: function (token, context, chain) {
2638                // Resolve filename
2639                var innerContext = {},
2640                    withContext,
2641                    i,
2642                    template;
2643
2644                if (!token.only) {
2645                    innerContext = Twig.ChildContext(context);
2646                }
2647
2648                if (token.withStack !== undefined) {
2649                    withContext = Twig.expression.parse.apply(this, [token.withStack, context]);
2650
2651                    for (i in withContext) {
2652                        if (withContext.hasOwnProperty(i))
2653                            innerContext[i] = withContext[i];
2654                    }
2655                }
2656
2657                var file = Twig.expression.parse.apply(this, [token.stack, innerContext]);
2658
2659                if (file instanceof Twig.Template) {
2660                    template = file;
2661                } else {
2662                    // Import file
2663                    template = this.importFile(file);
2664                }
2665
2666                return {
2667                    chain: chain,
2668                    output: template.render(innerContext)
2669                };
2670            }
2671        },
2672        {
2673            type: Twig.logic.type.spaceless,
2674            regex: /^spaceless$/,
2675            next: [
2676                Twig.logic.type.endspaceless
2677            ],
2678            open: true,
2679
2680            // Parse the html and return it without any spaces between tags
2681            parse: function (token, context, chain) {
2682                var // Parse the output without any filter
2683                    unfiltered = Twig.parse.apply(this, [token.output, context]),
2684                    // A regular expression to find closing and opening tags with spaces between them
2685                    rBetweenTagSpaces = />\s+</g,
2686                    // Replace all space between closing and opening html tags
2687                    output = unfiltered.replace(rBetweenTagSpaces,'><').trim();
2688
2689                return {
2690                    chain: chain,
2691                    output: output
2692                };
2693            }
2694        },
2695
2696        // Add the {% endspaceless %} token
2697        {
2698            type: Twig.logic.type.endspaceless,
2699            regex: /^endspaceless$/,
2700            next: [ ],
2701            open: false
2702        },
2703        {
2704            /**
2705             * Macro logic tokens.
2706             *
2707             * Format: {% maro input(name, value, type, size) %}
2708             *
2709             */
2710            type: Twig.logic.type.macro,
2711            regex: /^macro\s+([a-zA-Z0-9_]+)\s*\(\s*((?:[a-zA-Z0-9_]+(?:,\s*)?)*)\s*\)$/,
2712            next: [
2713                Twig.logic.type.endmacro
2714            ],
2715            open: true,
2716            compile: function (token) {
2717                var macroName = token.match[1],
2718                    parameters = token.match[2].split(/[\s,]+/);
2719
2720                //TODO: Clean up duplicate check
2721                for (var i=0; i<parameters.length; i++) {
2722                    for (var j=0; j<parameters.length; j++){
2723                        if (parameters[i] === parameters[j] && i !== j) {
2724                            throw new Twig.Error("Duplicate arguments for parameter: "+ parameters[i]);
2725                        }
2726                    }
2727                }
2728
2729                token.macroName = macroName;
2730                token.parameters = parameters;
2731
2732                delete token.match;
2733                return token;
2734            },
2735            parse: function (token, context, chain) {
2736                var template = this;
2737                this.macros[token.macroName] = function() {
2738                    // Pass global context and other macros
2739                    var macroContext = {
2740                        _self: template.macros
2741                    }
2742                    // Add parameters from context to macroContext
2743                    for (var i=0; i<token.parameters.length; i++) {
2744                        var prop = token.parameters[i];
2745                        if(typeof arguments[i] !== 'undefined') {
2746                            macroContext[prop] = arguments[i];
2747                        } else {
2748                            macroContext[prop] = undefined;
2749                        }
2750                    }
2751                    // Render
2752                    return Twig.parse.apply(template, [token.output, macroContext])
2753                };
2754
2755                return {
2756                    chain: chain,
2757                    output: ''
2758                };
2759
2760            }
2761        },
2762        {
2763            /**
2764             * End macro logic tokens.
2765             *
2766             * Format: {% endmacro %}
2767             */
2768             type: Twig.logic.type.endmacro,
2769             regex: /^endmacro$/,
2770             next: [ ],
2771             open: false
2772        },
2773        {
2774            /*
2775            * import logic tokens.
2776            *
2777            * Format: {% import "template.twig" as form %}
2778            */
2779            type: Twig.logic.type.import_,
2780            regex: /^import\s+(.+)\s+as\s+([a-zA-Z0-9_]+)$/,
2781            next: [ ],
2782            open: true,
2783            compile: function (token) {
2784                var expression = token.match[1].trim(),
2785                    contextName = token.match[2].trim();
2786                delete token.match;
2787
2788                token.expression = expression;
2789                token.contextName = contextName;
2790
2791                token.stack = Twig.expression.compile.apply(this, [{
2792                    type: Twig.expression.type.expression,
2793                    value: expression
2794                }]).stack;
2795
2796                return token;
2797            },
2798            parse: function (token, context, chain) {
2799                if (token.expression !== "_self") {
2800                    var file = Twig.expression.parse.apply(this, [token.stack, context]);
2801                    var template = this.importFile(file || token.expression);
2802                    context[token.contextName] = template.render({}, {output: 'macros'});
2803                }
2804                else {
2805                    context[token.contextName] = this.macros;
2806                }
2807
2808                return {
2809                    chain: chain,
2810                    output: ''
2811                }
2812
2813            }
2814        },
2815        {
2816            /*
2817            * from logic tokens.
2818            *
2819            * Format: {% from "template.twig" import func as form %}
2820            */
2821            type: Twig.logic.type.from,
2822            regex: /^from\s+(.+)\s+import\s+([a-zA-Z0-9_, ]+)$/,
2823            next: [ ],
2824            open: true,
2825            compile: function (token) {
2826                var expression = token.match[1].trim(),
2827                    macroExpressions = token.match[2].trim().split(/[ ,]+/),
2828                    macroNames = {};
2829
2830                for (var i=0; i<macroExpressions.length; i++) {
2831                    var res = macroExpressions[i];
2832
2833                    // match function as variable
2834                    var macroMatch = res.match(/^([a-zA-Z0-9_]+)\s+(.+)\s+as\s+([a-zA-Z0-9_]+)$/);
2835                    if (macroMatch) {
2836                        macroNames[macroMatch[1].trim()] = macroMatch[2].trim();
2837                    }
2838                    else if (res.match(/^([a-zA-Z0-9_]+)$/)) {
2839                        macroNames[res] = res;
2840                    }
2841                    else {
2842                        // ignore import
2843                    }
2844
2845                }
2846
2847                delete token.match;
2848
2849                token.expression = expression;
2850                token.macroNames = macroNames;
2851
2852                token.stack = Twig.expression.compile.apply(this, [{
2853                    type: Twig.expression.type.expression,
2854                    value: expression
2855                }]).stack;
2856
2857                return token;
2858            },
2859            parse: function (token, context, chain) {
2860                var macros;
2861
2862                if (token.expression !== "_self") {
2863                    var file = Twig.expression.parse.apply(this, [token.stack, context]);
2864                    var template = this.importFile(file || token.expression);
2865                    macros = template.render({}, {output: 'macros'});
2866                }
2867                else {
2868                    macros = this.macros;
2869                }
2870
2871                for (var macroName in token.macroNames) {
2872                    if (macros.hasOwnProperty(macroName)) {
2873                        context[token.macroNames[macroName]] = macros[macroName];
2874                    }
2875                }
2876
2877                return {
2878                    chain: chain,
2879                    output: ''
2880                }
2881
2882            }
2883        },
2884        {
2885            /**
2886             * The embed tag combines the behaviour of include and extends.
2887             * It allows you to include another template's contents, just like include does.
2888             *
2889             *  Format: {% embed "template.twig" [with {some: 'values'} only] %}
2890             */
2891            type: Twig.logic.type.embed,
2892            regex: /^embed\s+(ignore missing\s+)?(.+?)\s*(?:with\s+(.+?))?\s*(only)?$/,
2893            next: [
2894                Twig.logic.type.endembed
2895            ],
2896            open: true,
2897            compile: function (token) {
2898                var match = token.match,
2899                    includeMissing = match[1] !== undefined,
2900                    expression = match[2].trim(),
2901                    withContext = match[3],
2902                    only = ((match[4] !== undefined) && match[4].length);
2903
2904                delete token.match;
2905
2906                token.only = only;
2907                token.includeMissing = includeMissing;
2908
2909                token.stack = Twig.expression.compile.apply(this, [{
2910                    type:  Twig.expression.type.expression,
2911                    value: expression
2912                }]).stack;
2913
2914                if (withContext !== undefined) {
2915                    token.withStack = Twig.expression.compile.apply(this, [{
2916                        type:  Twig.expression.type.expression,
2917                        value: withContext.trim()
2918                    }]).stack;
2919                }
2920
2921                return token;
2922            },
2923            parse: function (token, context, chain) {
2924                // Resolve filename
2925                var innerContext = {},
2926                    withContext,
2927                    i,
2928                    template;
2929
2930                if (!token.only) {
2931                    for (i in context) {
2932                        if (context.hasOwnProperty(i))
2933                            innerContext[i] = context[i];
2934                    }
2935                }
2936
2937                if (token.withStack !== undefined) {
2938                    withContext = Twig.expression.parse.apply(this, [token.withStack, context]);
2939
2940                    for (i in withContext) {
2941                        if (withContext.hasOwnProperty(i))
2942                            innerContext[i] = withContext[i];
2943                    }
2944                }
2945
2946                var file = Twig.expression.parse.apply(this, [token.stack, innerContext]);
2947
2948                if (file instanceof Twig.Template) {
2949                    template = file;
2950                } else {
2951                    // Import file
2952                    template = this.importFile(file);
2953                }
2954
2955                // reset previous blocks
2956                this.blocks = {};
2957
2958                // parse tokens. output will be not used
2959                var output = Twig.parse.apply(this, [token.output, innerContext]);
2960
2961                // render tempalte with blocks defined in embed block
2962                return {
2963                    chain: chain,
2964                    output: template.render(innerContext, {'blocks':this.blocks}
2964)
2965                };
2966            }
2967        },
2968        /* Add the {% endembed %} token
2969         *
2970         */
2971        {
2972            type: Twig.logic.type.endembed,
2973            regex: /^endembed$/,
2974            next: [ ],
2975            open: false
2976        }
2977
2978    ];
2979
2980
2981    /**
2982     * Registry for logic handlers.
2983     */
2984    Twig.logic.handler = {};
2985
2986    /**
2987     * Define a new token type, available at Twig.logic.type.{type}
2988     */
2989    Twig.logic.extendType = function (type, value) {
2990        value = value || ("Twig.logic.type" + type);
2991        Twig.logic.type[type] = value;
2992    };
2993
2994    /**
2995     * Extend the logic parsing functionality with a new token definition.
2996     *
2997     * // Define a new tag
2998     * Twig.logic.extend({
2999     *     type: Twig.logic.type.{type},
3000     *     // The pattern to match for this token
3001     *     regex: ...,
3002     *     // What token types can follow this token, leave blank if any.
3003     *     next: [ ... ]
3004     *     // Create and return compiled version of the token
3005     *     compile: function(token) { ... }
3006     *     // Parse the compiled token with the context provided by the render call
3007     *     //   and whether this token chain is complete.
3008     *     parse: function(token, context, chain) { ... }
3009     * });
3010     *
3011     * @param {Object} definition The new logic expression.
3012     */
3013    Twig.logic.extend = function (definition) {
3014
3015        if (!definition.type) {
3016            throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
3017        } else {
3018            Twig.logic.extendType(definition.type);
3019        }
3020        Twig.logic.handler[definition.type] = definition;
3021    };
3022
3023    // Extend with built-in expressions
3024    while (Twig.logic.definitions.length > 0) {
3025        Twig.logic.extend(Twig.logic.definitions.shift());
3026    }
3027
3028    /**
3029     * Compile a logic token into an object ready for parsing.
3030     *
3031     * @param {Object} raw_token An uncompiled logic token.
3032     *
3033     * @return {Object} A compiled logic token, ready for parsing.
3034     */
3035    Twig.logic.compile = function (raw_token) {
3036        var expression = raw_token.value.trim(),
3037            token = Twig.logic.tokenize.apply(this, [expression]),
3038            token_template = Twig.logic.handler[token.type];
3039
3040        // Check if the token needs compiling
3041        if (token_template.compile) {
3042            token = token_template.compile.apply(this, [token]);
3043            Twig.log.trace("Twig.logic.compile: ", "Compiled logic token to ", token);
3044        }
3045
3046        return token;
3047    };
3048
3049    /**
3050     * Tokenize logic expressions. This function matches token expressions against regular
3051     * expressions provided in token definitions provided with Twig.logic.extend.
3052     *
3053     * @param {string} expression the logic token expression to tokenize
3054     *                (i.e. what's between {% and %})
3055     *
3056     * @return {Object} The matched token with type set to the token type and match to the regex match.
3057     */
3058    Twig.logic.tokenize = function (expression) {
3059        var token = {},
3060            token_template_type = null,
3061            token_type = null,
3062            token_regex = null,
3063            regex_array = null,
3064            regex = null,
3065            match = null;
3066
3067        // Ignore whitespace around expressions.
3068        expression = expression.trim();
3069
3070        for (token_template_type in Twig.logic.handler) {
3071            if (Twig.logic.handler.hasOwnProperty(token_template_type)) {
3072                // Get the type and regex for this template type
3073                token_type = Twig.logic.handler[token_template_type].type;
3074                token_regex = Twig.logic.handler[token_template_type].regex;
3075
3076                // Handle multiple regular expressions per type.
3077                regex_array = [];
3078                if (token_regex instanceof Array) {
3079                    regex_array = token_regex;
3080                } else {
3081                    regex_array.push(token_regex);
3082                }
3083
3084                // Check regular expressions in the order they were specified in the definition.
3085                while (regex_array.length > 0) {
3086                    regex = regex_array.shift();
3087                    match = regex.exec(expression.trim());
3088                    if (match !== null) {
3089                        token.type  = token_type;
3090                        token.match = match;
3091                        Twig.log.trace("Twig.logic.tokenize: ", "Matched a ", token_type, " regular expression of ", match);
3092                        return token;
3093                    }
3094                }
3095            }
3096        }
3097
3098        // No regex matches
3099        throw new Twig.Error("Unable to parse '" + expression.trim() + "'");
3100    };
3101
3102    /**
3103     * Parse a logic token within a given context.
3104     *
3105     * What are logic chains?
3106     *      Logic chains represent a series of tokens that are connected,
3107     *          for example:
3108     *          {% if ... %} {% else %} {% endif %}
3109     *
3110     *      The chain parameter is used to signify if a chain is open of closed.
3111     *      open:
3112     *          More tokens in this chain should be parsed.
3113     *      closed:
3114     *          This token chain has completed parsing and any additional
3115     *          tokens (else, elseif, etc...) should be ignored.
3116     *
3117     * @param {Object} token The compiled token.
3118     * @param {Object} context The render context.
3119     * @param {boolean} chain Is this an open logic chain. If false, that means a
3120     *                        chain is closed and no further cases should be parsed.
3121     */
3122    Twig.logic.parse = function (token, context, chain) {
3123        var output = '',
3124            token_template;
3125
3126        context = context || { };
3127
3128        Twig.log.debug("Twig.logic.parse: ", "Parsing logic token ", token);
3129
3130        token_template = Twig.logic.handler[token.type];
3131
3132        if (token_template.parse) {
3133            output = token_template.parse.apply(this, [token, context, chain]);
3134        }
3135        return output;
3136    };
3137
3138    return Twig;
3139
3140})(Twig || { });
3141//     Twig.js
3142//     Available under the BSD 2-Clause License
3143//     https://github.com/justjohn/twig.js
3144
3145// ## twig.expression.js
3146//
3147// This file handles tokenizing, compiling and parsing expressions.
3148var Twig = (function (Twig) {
3149    "use strict";
3150
3151    /**
3152     * Namespace for expression handling.
3153     */
3154    Twig.expression = { };
3155
3156    /**
3157     * Reserved word that can't be used as variable names.
3158     */
3159    Twig.expression.reservedWords = [
3160        "true", "false", "null", "TRUE", "FALSE", "NULL", "_context"
3161    ];
3162
3163    /**
3164     * The type of tokens used in expressions.
3165     */
3166    Twig.expression.type = {
3167        comma:      'Twig.expression.type.comma',
3168        operator: {
3169            unary:  'Twig.expression.type.operator.unary',
3170            binary: 'Twig.expression.type.operator.binary'
3171        },
3172        string:     'Twig.expression.type.string',
3173        bool:       'Twig.expression.type.bool',
3174        array: {
3175            start:  'Twig.expression.type.array.start',
3176            end:    'Twig.expression.type.array.end'
3177        },
3178        object: {
3179            start:  'Twig.expression.type.object.start',
3180            end:    'Twig.expression.type.object.end'
3181        },
3182        parameter: {
3183            start:  'Twig.expression.type.parameter.start',
3184            end:    'Twig.expression.type.parameter.end'
3185        },
3186        key: {
3187            period:   'Twig.expression.type.key.period',
3188            brackets: 'Twig.expression.type.key.brackets'
3189        },
3190        filter:     'Twig.expression.type.filter',
3191        _function:  'Twig.expression.type._function',
3192        variable:   'Twig.expression.type.variable',
3193        number:     'Twig.expression.type.number',
3194        _null:     'Twig.expression.type.null',
3195        context:    'Twig.expression.type.context',
3196        test:       'Twig.expression.type.test'
3197    };
3198
3199    Twig.expression.set = {
3200        // What can follow an expression (in general)
3201        operations: [
3202            Twig.expression.type.filter,
3203            Twig.expression.type.operator.unary,
3204            Twig.expression.type.operator.binary,
3205            Twig.expression.type.array.end,
3206            Twig.expression.type.object.end,
3207            Twig.expression.type.parameter.end,
3208            Twig.expression.type.comma,
3209            Twig.expression.type.test
3210        ],
3211        expressions: [
3212            Twig.expression.type._function,
3213            Twig.expression.type.bool,
3214            Twig.expression.type.string,
3215            Twig.expression.type.variable,
3216            Twig.expression.type.number,
3217            Twig.expression.type._null,
3218            Twig.expression.type.context,
3219            Twig.expression.type.parameter.start,
3220            Twig.expression.type.array.start,
3221            Twig.expression.type.object.start
3222        ]
3223    };
3224
3225    // Most expressions allow a '.' or '[' after them, so we provide a convenience set
3226    Twig.expression.set.operations_extended = Twig.expression.set.operations.concat([
3227                    Twig.expression.type.key.period,
3228                    Twig.expression.type.key.brackets]);
3229
3230    // Some commonly used compile and parse functions.
3231    Twig.expression.fn = {
3232        compile: {
3233            push: function(token, stack, output) {
3234                output.push(token);
3235            },
3236            push_both: function(token, stack, output) {
3237                output.push(token);
3238                stack.push(token);
3239            }
3240        },
3241        parse: {
3242            push: function(token, stack, context) {
3243                stack.push(token);
3244            },
3245            push_value: function(token, stack, context) {
3246                stack.push(token.value);
3247            }
3248        }
3249    };
3250
3251    // The regular expressions and compile/parse logic used to match tokens in expressions.
3252    //
3253    // Properties:
3254    //
3255    //      type:  The type of expression this matches
3256    //
3257    //      regex: One or more regular expressions that matche the format of the token.
3258    //
3259    //      next:  Valid tokens that can occur next in the expression.
3260    //
3261    // Functions:
3262    //
3263    //      compile: A function that compiles the raw regular expression match into a token.
3264    //
3265    //      parse:   A function that parses the compiled token into output.
3266    //
3267    Twig.expression.definitions = [
3268        {
3269            type: Twig.expression.type.test,
3270            regex: /^is\s+(not)?\s*([a-zA-Z_][a-zA-Z0-9_]*)/,
3271            next: Twig.expression.set.operations.concat([Twig.expression.type.parameter.start]),
3272            compile: function(token, stack, output) {
3273                token.filter   = token.match[2];
3274                token.modifier = token.match[1];
3275                delete token.match;
3276                delete token.value;
3277                output.push(token);
3278            },
3279            parse: function(token, stack, context) {
3280                var value = stack.pop(),
3281                    params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
3282                    result = Twig.test(token.filter, value, params);
3283
3284                if (token.modifier == 'not') {
3285                    stack.push(!result);
3286                } else {
3287                    stack.push(result);
3288                }
3289            }
3290        },
3291        {
3292            type: Twig.expression.type.comma,
3293            // Match a comma
3294            regex: /^,/,
3295            next: Twig.expression.set.expressions.concat([Twig.expression.type.array.end, Twig.expression.type.object.end]),
3296            compile: function(token, stack, output) {
3297                var i = stack.length - 1,
3298                    stack_token;
3299
3300                delete token.match;
3301                delete token.value;
3302
3303                // pop tokens off the stack until the start of the object
3304                for(;i >= 0; i--) {
3305                    stack_token = stack.pop();
3306                    if (stack_token.type === Twig.expression.type.object.start
3307                            || stack_token.type === Twig.expression.type.parameter.start
3308                            || stack_token.type === Twig.expression.type.array.start) {
3309                        stack.push(stack_token);
3310                        break;
3311                    }
3312                    output.push(stack_token);
3313                }
3314                output.push(token);
3315            }
3316        },
3317        {
3318            type: Twig.expression.type.operator.binary,
3319            // Match any of +, *, /, -, %, ~, <, <=, >, >=, !=, ==, **, ?, :, and, or, not
3320            regex: /(^[\+\-~%\?\:]|^[!=]==?|^[!<>]=?|^\*\*?|^\/\/?|^and\s+|^or\s+|^in\s+|^not in\s+|^\.\.)/,
3321            next: Twig.expression.set.expressions.concat([Twig.expression.type.operator.unary]),
3322            compile: function(token, stack, output) {
3323                delete token.match;
3324
3325                token.value = token.value.trim();
3326                var value = token.value,
3327                    operator = Twig.expression.operator.lookup(value, token);
3328
3329                Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
3330
3331                while (stack.length > 0 &&
3332                       (stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
3333                            (
3334                                (operator.associativity === Twig.expression.operator.leftToRight &&
3335                                 operator.precidence    >= stack[stack.length-1].precidence) ||
3336
3337                                (operator.associativity === Twig.expression.operator.rightToLeft &&
3338                                 operator.precidence    >  stack[stack.length-1].precidence)
3339                            )
3340                       ) {
3341                     var temp = stack.pop();
3342                     output.push(temp);
3343                }
3344
3345                if (value === ":") {
3346                    // Check if this is a ternary or object key being set
3347                    if (stack[stack.length - 1] && stack[stack.length-1].value === "?") {
3348                        // Continue as normal for a ternary
3349                    } else {
3350                        // This is not a ternary so we push the token to the output where it can be handled
3351                        //   when the assocated object is closed.
3352                        var key_token = output.pop();
3353
3354                        if (key_token.type === Twig.expression.type.string ||
3355                                key_token.type === Twig.expression.type.variable ||
3356                                key_token.type === Twig.expression.type.number) {
3357                            token.key = key_token.value;
3358
3359                        } else {
3360                            throw new Twig.Error("Unexpected value before ':' of " + key_token.type + " = " + key_token.value);
3361                        }
3362
3363                        output.push(token);
3364                        return;
3365                    }
3366                } else {
3367                    stack.push(operator);
3368                }
3369            },
3370            parse: function(token, stack, context) {
3371                if (token.key) {
3372                    // handle ternary ':' operator
3373                    stack.push(token);
3374                } else {
3375                    Twig.expression.operator.parse(token.value, stack);
3376                }
3377            }
3378        },
3379        {
3380            type: Twig.expression.type.operator.unary,
3381            // Match any of not
3382            regex: /(^not\s+)/,
3383            next: Twig.expression.set.expressions,
3384            compile: function(token, stack, output) {
3385                delete token.match;
3386
3387                token.value = token.value.trim();
3388                var value = token.value,
3389                    operator = Twig.expression.operator.lookup(value, token);
3390
3391                Twig.log.trace("Twig.expression.compile: ", "Operator: ", operator, " from ", value);
3392
3393                while (stack.length > 0 &&
3394                       (stack[stack.length-1].type == Twig.expression.type.operator.unary || stack[stack.length-1].type == Twig.expression.type.operator.binary) &&
3395                            (
3396                                (operator.associativity === Twig.expression.operator.leftToRight &&
3397                                 operator.precidence    >= stack[stack.length-1].precidence) ||
3398
3399                                (operator.associativity === Twig.expression.operator.rightToLeft &&
3400                                 operator.precidence    >  stack[stack.length-1].precidence)
3401                            )
3402                       ) {
3403                     var temp = stack.pop();
3404                     output.push(temp);
3405                }
3406
3407                stack.push(operator);
3408            },
3409            parse: function(token, stack, context) {
3410                Twig.expression.operator.parse(token.value, stack);
3411            }
3412        },
3413        {
3414            /**
3415             * Match a string. This is anything between a pair of single or double quotes.
3416             */
3417            type: Twig.expression.type.string,
3418            // See: http://blog.stevenlevithan.com/archives/match-quoted-string
3419            regex: /^(["'])(?:(?=(\\?))\2[\s\S])*?\1/,
3420            next: Twig.expression.set.operations,
3421            compile: function(token, stack, output) {
3422                var value = token.value;
3423                delete token.match
3424
3425                // Remove the quotes from the string
3426                if (value.substring(0, 1) === '"') {
3427                    value = value.replace('\\"', '"');
3428                } else {
3429                    value = value.replace("\\'", "'");
3430                }
3431                token.value = value.substring(1, value.length-1).replace( /\\n/g, "\n" ).replace( /\\r/g, "\r" );
3432                Twig.log.trace("Twig.expression.compile: ", "String value: ", token.value);
3433                output.push(token);
3434            },
3435            parse: Twig.expression.fn.parse.push_value
3436        },
3437        {
3438            /**
3439             * Match a parameter set start.
3440             */
3441            type: Twig.expression.type.parameter.start,
3442            regex: /^\(/,
3443            next: Twig.expression.set.expressions.concat([Twig.expression.type.parameter.end]),
3444            compile: Twig.expression.fn.compile.push_both,
3445            parse: Twig.expression.fn.parse.push
3446        },
3447        {
3448            /**
3449             * Match a parameter set end.
3450             */
3451            type: Twig.expression.type.parameter.end,
3452            regex: /^\)/,
3453            next: Twig.expression.set.operations_extended,
3454            compile: function(token, stack, output) {
3455                var stack_token,
3456                    end_token = token;
3457
3458                stack_token = stack.pop();
3459                while(stack.length > 0 && stack_token.type != Twig.expression.type.parameter.start) {
3460                    output.push(stack_token);
3461                    stack_token = stack.pop();
3462                }
3463
3464                // Move contents of parens into preceding filter
3465                var param_stack = [];
3466                while(token.type !== Twig.expression.type.parameter.start) {
3467                    // Add token to arguments stack
3468                    param_stack.unshift(token);
3469                    token = output.pop();
3470                }
3471                param_stack.unshift(token);
3472
3473                var is_expression = false;
3474
3475                // Get the token preceding the parameters
3476                token = output[output.length-1];
3477
3478                if (token === undefined ||
3479                    (token.type !== Twig.expression.type._function &&
3480                    token.type !== Twig.expression.type.filter &&
3481                    token.type !== Twig.expression.type.test &&
3482                    token.type !== Twig.expression.type.key.brackets &&
3483                    token.type !== Twig.expression.type.key.period)) {
3484
3485                    end_token.expression = true;
3486
3487                    // remove start and end token from stack
3488                    param_stack.pop();
3489                    param_stack.shift();
3490
3491                    end_token.params = param_stack;
3492
3493                    output.push(end_token);
3494
3495                } else {
3496                    end_token.expression = false;
3497                    token.params = param_stack;
3498                }
3499            },
3500            parse: function(token, stack, context) {
3501                var new_array = [],
3502                    array_ended = false,
3503                    value = null;
3504
3505                if (token.expression) {
3506                    value = Twig.expression.parse.apply(this, [token.params, context])
3507                    stack.push(value);
3508
3509                } else {
3510
3511                    while (stack.length > 0) {
3512                        value = stack.pop();
3513                        // Push values into the array until the start of the array
3514                        if (value && value.type && value.type == Twig.expression.type.parameter.start) {
3515                            array_ended = true;
3516                            break;
3517                        }
3518                        new_array.unshift(value);
3519                    }
3520
3521                    if (!array_ended) {
3522                        throw new Twig.Error("Expected end of parameter set.");
3523                    }
3524
3525                    stack.push(new_array);
3526                }
3527            }
3528        },
3529        {
3530            /**
3531             * Match an array start.
3532             */
3533            type: Twig.expression.type.array.start,
3534            regex: /^\[/,
3535            next: Twig.expression.set.expressions.concat([Twig.expression.type.array.end]),
3536            compile: Twig.expression.fn.compile.push_both,
3537            parse: Twig.expression.fn.parse.push
3538        },
3539        {
3540            /**
3541             * Match an array end.
3542             */
3543            type: Twig.expression.type.array.end,
3544            regex: /^\]/,
3545            next: Twig.expression.set.operations_extended,
3546            compile: function(token, stack, output) {
3547                var i = stack.length - 1,
3548                    stack_token;
3549                // pop tokens off the stack until the start of the object
3550                for(;i >= 0; i--) {
3551                    stack_token = stack.pop();
3552                    if (stack_token.type === Twig.expression.type.array.start) {
3553                        break;
3554                    }
3555                    output.push(stack_token);
3556                }
3557                output.push(token);
3558            },
3559            parse: function(token, stack, context) {
3560                var new_array = [],
3561                    array_ended = false,
3562                    value = null;
3563
3564                while (stack.length > 0) {
3565                    value = stack.pop();
3566                    // Push values into the array until the start of the array
3567                    if (value.type && value.type == Twig.expression.type.array.start) {
3568                        array_ended = true;
3569                        break;
3570                    }
3571                    new_array.unshift(value);
3572                }
3573                if (!array_ended) {
3574                    throw new Twig.Error("Expected end of array.");
3575                }
3576
3577                stack.push(new_array);
3578            }
3579        },
3580        // Token that represents the start of a hash map '}'
3581        //
3582        // Hash maps take the form:
3583        //    { "key": 'value', "another_key": item }
3584        //
3585        // Keys must be quoted (either single or double) and values can be any expression.
3586        {
3587            type: Twig.expression.type.object.start,
3588            regex: /^\{/,
3589            next: Twig.expression.set.expressions.concat([Twig.expression.type.object.end]),
3590            compile: Twig.expression.fn.compile.push_both,
3591            parse: Twig.expression.fn.parse.push
3592        },
3593
3594        // Token that represents the end of a Hash Map '}'
3595        //
3596        // This is where the logic for building the internal
3597        // representation of a hash map is defined.
3598        {
3599            type: Twig.expression.type.object.end,
3600            regex: /^\}/,
3601            next: Twig.expression.set.operations_extended,
3602            compile: function(token, stack, output) {
3603                var i = stack.length-1,
3604                    stack_token;
3605
3606                // pop tokens off the stack until the start of the object
3607                for(;i >= 0; i--) {
3608                    stack_token = stack.pop();
3609                    if (stack_token && stack_token.type === Twig.expression.type.object.start) {
3610                        break;
3611                    }
3612                    output.push(stack_token);
3613                }
3614                output.push(token);
3615            },
3616            parse: function(end_token, stack, context) {
3617                var new_object = {},
3618                    object_ended = false,
3619                    token = null,
3620                    token_key = null,
3621                    has_value = false,
3622                    value = null;
3623
3624                while (stack.length > 0) {
3625                    token = stack.pop();
3626                    // Push values into the array until the start of the object
3627                    if (token && token.type && token.type === Twig.expression.type.object.start) {
3628                        object_ended = true;
3629                        break;
3630                    }
3631                    if (token && token.type && (token.type === Twig.expression.type.operator.binary || token.type === Twig.expression.type.operator.unary) && token.key) {
3632                        if (!has_value) {
3633                            throw new Twig.Error("Missing value for key '" + token.key + "' in object definition.");
3634                        }
3635                        new_object[token.key] = value;
3636
3637                        // Preserve the order that elements are added to the map
3638                        // This is necessary since JavaScript objects don't
3639                        // guarantee the order of keys
3640                        if (new_object._keys === undefined) new_object._keys = [];
3641                        new_object._keys.unshift(token.key);
3642
3643                        // reset value check
3644                        value = null;
3645                        has_value = false;
3646
3647                    } else {
3648                        has_value = true;
3649                        value = token;
3650                    }
3651                }
3652                if (!object_ended) {
3653                    throw new Twig.Error("Unexpected end of object.");
3654                }
3655
3656                stack.push(new_object);
3657            }
3658        },
3659
3660        // Token representing a filter
3661        //
3662        // Filters can follow any expression and take the form:
3663        //    expression|filter(optional, args)
3664        //
3665        // Filter parsing is done in the Twig.filters namespace.
3666        {
3667            type: Twig.expression.type.filter,
3668            // match a | then a letter or _, then any number of letters, numbers, _ or -
3669            regex: /^\|\s?([a-zA-Z_][a-zA-Z0-9_\-]*)/,
3670            next: Twig.expression.set.operations_extended.concat([
3671                    Twig.expression.type.parameter.start]),
3672            compile: function(token, stack, output) {
3673                token.value = token.match[1];
3674                output.push(token);
3675            },
3676            parse: function(token, stack, context) {
3677                var input = stack.pop(),
3678                    params = token.params && Twig.expression.parse.apply(this, [token.params, context]);
3679
3680                stack.push(Twig.filter.apply(this, [token.value, input, params]));
3681            }
3682        },
3683        {
3684            type: Twig.expression.type._function,
3685            // match any letter or _, then any number of letters, numbers, _ or - followed by (
3686            regex: /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/,
3687            next: Twig.expression.type.parameter.start,
3688            transform: function(match, tokens) {
3689                return '(';
3690            },
3691            compile: function(token, stack, output) {
3692                var fn = token.match[1];
3693                token.fn = fn;
3694                // cleanup token
3695                delete token.match;
3696                delete token.value;
3697
3698                output.push(token);
3699            },
3700            parse: function(token, stack, context) {
3701                var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
3702                    fn     = token.fn,
3703                    value;
3704
3705                if (Twig.functions[fn]) {
3706                    // Get the function from the built-in functions
3707                    value = Twig.functions[fn].apply(this, params);
3708
3709                } else if (typeof context[fn] == 'function') {
3710                    // Get the function from the user/context defined functions
3711                    value = context[fn].apply(context, params);
3712
3713                } else {
3714                    throw new Twig.Error(fn + ' function does not exist and is not defined in the context');
3715                }
3716
3717                stack.push(value);
3718            }
3719        },
3720
3721        // Token representing a variable.
3722        //
3723        // Variables can contain letters, numbers, underscores and
3724        // dashes, but must start with a letter or underscore.
3725        //
3726        // Variables are retrieved from the render context and take
3727        // the value of 'undefined' if the given variable doesn't
3728        // exist in the context.
3729        {
3730            type: Twig.expression.type.variable,
3731            // match any letter or _, then any number of letters, numbers, _ or -
3732            regex: /^[a-zA-Z_][a-zA-Z0-9_]*/,
3733            next: Twig.expression.set.operations_extended.concat([
3734                    Twig.expression.type.parameter.start]),
3735            compile: Twig.expression.fn.compile.push,
3736            validate: function(match, tokens) {
3737                return (Twig.indexOf(Twig.expression.reservedWords, match[0]) < 0);
3738            },
3739            parse: function(token, stack, context) {
3740                // Get the variable from the context
3741                var value = Twig.expression.resolve(context[token.value], context);
3742                stack.push(value);
3743            }
3744        },
3745        {
3746            type: Twig.expression.type.key.period,
3747            regex: /^\.([a-zA-Z0-9_]+)/,
3748            next: Twig.expression.set.operations_extended.concat([
3749                    Twig.expression.type.parameter.start]),
3750            compile: function(token, stack, output) {
3751                token.key = token.match[1];
3752                delete token.match;
3753                delete token.value;
3754
3755                output.push(token);
3756            },
3757            parse: function(token, stack, context) {
3758                var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
3759                    key = token.key,
3760                    object = stack.pop(),
3761                    value;
3762
3763                if (object === null || object === undefined) {
3764                    if (this.options.strict_variables) {
3765                        throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
3766                    } else {
3767                        return null;
3768                    }
3769                }
3770
3771                var capitalize = function(value) {return value.substr(0, 1).toUpperCase() + value.substr(1);};
3772
3773                // Get the variable from the context
3774                if (typeof object === 'object' && key in object) {
3775                    value = object[key];
3776                } else if (object["get"+capitalize(key)] !== undefined) {
3777                    value = object["get"+capitalize(key)];
3778                } else if (object["is"+capitalize(key)] !== undefined) {
3779                    value = object["is"+capitalize(key)];
3780                } else {
3781                    value = null;
3782                }
3783                stack.push(Twig.expression.resolve(value, object, params));
3784            }
3785        },
3786        {
3787            type: Twig.expression.type.key.brackets,
3788            regex: /^\[([^\]]*)\]/,
3789            next: Twig.expression.set.operations_extended.concat([
3790                    Twig.expression.type.parameter.start]),
3791            compile: function(token, stack, output) {
3792                var match = token.match[1];
3793                delete token.value;
3794                delete token.match;
3795
3796                // The expression stack for the key
3797                token.stack = Twig.expression.compile({
3798                    value: match
3799                }).stack;
3800
3801                output.push(token);
3802            },
3803            parse: function(token, stack, context) {
3804                // Evaluate key
3805                var params = token.params && Twig.expression.parse.apply(this, [token.params, context]),
3806                    key = Twig.expression.parse.apply(this, [token.stack, context]),
3807                    object = stack.pop(),
3808                    value;
3809
3810                if (object === null || object === undefined) {
3811                    if (this.options.strict_variables) {
3812                        throw new Twig.Error("Can't access a key " + key + " on an null or undefined object.");
3813                    } else {
3814                        return null;
3815                    }
3816                }
3817
3818                // Get the variable from the context
3819                if (typeof object === 'object' && key in object) {
3820                    value = object[key];
3821                } else {
3822                    value = null;
3823                }
3824                stack.push(Twig.expression.resolve(value, object, params));
3825            }
3826        },
3827        {
3828            /**
3829             * Match a null value.
3830             */
3831            type: Twig.expression.type._null,
3832            // match a number
3833            regex: /^(null|NULL|none|NONE)/,
3834            next: Twig.expression.set.operations,
3835            compile: function(token, stack, output) {
3836                delete token.match;
3837                token.value = null;
3838                output.push(token);
3839            },
3840            parse: Twig.expression.fn.parse.push_value
3841        },
3842        {
3843            /**
3844             * Match the context
3845             */
3846            type: Twig.expression.type.context,
3847            regex: /^_context/,
3848            next: Twig.expression.set.operations_extended.concat([
3849                    Twig.expression.type.parameter.start]),
3850            compile: Twig.expression.fn.compile.push,
3851            parse: function(token, stack, context) {
3852                stack.push(context);
3853            }
3854        },
3855        {
3856            /**
3857             * Match a number (integer or decimal)
3858             */
3859            type: Twig.expression.type.number,
3860            // match a number
3861            regex: /^\-?\d+(\.\d+)?/,
3862            next: Twig.expression.set.operations,
3863            compile: function(token, stack, output) {
3864                token.value = Number(token.value);
3865                output.push(token);
3866            },
3867            parse: Twig.expression.fn.parse.push_value
3868        },
3869        {
3870            /**
3871             * Match a boolean
3872             */
3873            type: Twig.expression.type.bool,
3874            regex: /^(true|TRUE|false|FALSE)/,
3875            next: Twig.expression.set.operations,
3876            compile: function(token, stack, output) {
3877                token.value = (token.match[0].toLowerCase( ) === "true");
3878                delete token.match;
3879                output.push(token);
3880            },
3881            parse: Twig.expression.fn.parse.push_value
3882        }
3883    ];
3884
3885    /**
3886     * Resolve a context value.
3887     *
3888     * If the value is a function, it is executed with a context parameter.
3889     *
3890     * @param {string} key The context object key.
3891     * @param {Object} context The render context.
3892     */
3893    Twig.expression.resolve = function(value, context, params) {
3894        if (typeof value == 'function') {
3895            return value.apply(context, params || []);
3896        } else {
3897            return value;
3898        }
3899    };
3900
3901    /**
3902     * Registry for logic handlers.
3903     */
3904    Twig.expression.handler = {};
3905
3906    /**
3907     * Define a new expression type, available at Twig.logic.type.{type}
3908     *
3909     * @param {string} type The name of the new type.
3910     */
3911    Twig.expression.extendType = function (type) {
3912        Twig.expression.type[type] = "Twig.expression.type." + type;
3913    };
3914
3915    /**
3916     * Extend the expression parsing functionality with a new definition.
3917     *
3918     * Token definitions follow this format:
3919     *  {
3920     *      type:     One of Twig.expression.type.[type], either pre-defined or added using
3921     *                    Twig.expression.extendType
3922     *
3923     *      next:     Array of types from Twig.expression.type that can follow this token,
3924     *
3925     *      regex:    A regex or array of regex's that should match the token.
3926     *
3927     *      compile: function(token, stack, output) called when this token is being compiled.
3928     *                   Should return an object with stack and output set.
3929     *
3930     *      parse:   function(token, stack, context) called when this token is being parsed.
3931     *                   Should return an object with stack and context set.
3932     *  }
3933     *
3934     * @param {Object} definition A token definition.
3935     */
3936    Twig.expression.extend = function (definition) {
3937        if (!definition.type) {
3938            throw new Twig.Error("Unable to extend logic definition. No type provided for " + definition);
3939        }
3940        Twig.expression.handler[definition.type] = definition;
3941    };
3942
3943    // Extend with built-in expressions
3944    while (Twig.expression.definitions.length > 0) {
3945        Twig.expression.extend(Twig.expression.definitions.shift());
3946    }
3947
3948    /**
3949     * Break an expression into tokens defined in Twig.expression.definitions.
3950     *
3951     * @param {string} expression The string to tokenize.
3952     *
3953     * @return {Array} An array of tokens.
3954     */
3955    Twig.expression.tokenize = function (expression) {
3956        var tokens = [],
3957            // Keep an offset of the location in the expression for error messages.
3958            exp_offset = 0,
3959            // The valid next tokens of the previous token
3960            next = null,
3961            // Match information
3962            type, regex, regex_array,
3963            // The possible next token for the match
3964            token_next,
3965            // Has a match been found from the definitions
3966            match_found, invalid_matches = [], match_function;
3967
3968        match_function = function () {
3969            var match = Array.prototype.slice.apply(arguments),
3970                string = match.pop(),
3971                offset = match.pop();
3972
3973            Twig.log.trace("Twig.expression.tokenize",
3974                           "Matched a ", type, " regular expression of ", match);
3975
3976            if (next && Twig.indexOf(next, type) < 0) {
3977                invalid_matches.push(
3978                    type + " cannot follow a " + tokens[tokens.length - 1].type +
3979                           " at template:" + exp_offset + " near '" + match[0].substring(0, 20) +
3980                           "...'"
3981                );
3982                // Not a match, don't change the expression
3983                return match[0];
3984            }
3985
3986            // Validate the token if a validation function is provided
3987            if (Twig.expression.handler[type].validate &&
3988                    !Twig.expression.handler[type].validate(match, tokens)) {
3989                return match[0];
3990            }
3991
3992            invalid_matches = [];
3993
3994            tokens.push({
3995                type:  type,
3996                value: match[0],
3997                match: match
3998            });
3999
4000            match_found = true;
4001            next = token_next;
4002            exp_offset += match[0].length;
4003
4004            // Does the token need to return output back to the expression string
4005            // e.g. a function match of cycle( might return the '(' back to the expression
4006            // This allows look-ahead to differentiate between token types (e.g. functions and variable names)
4007            if (Twig.expression.handler[type].transform) {
4008                return Twig.expression.handler[type].transform(match, tokens);
4009            }
4010            return '';
4011        };
4012
4013        Twig.log.debug("Twig.expression.tokenize", "Tokenizing expression ", expression);
4014
4015        while (expression.length > 0) {
4016            expression = expression.trim();
4017            for (type in Twig.expression.handler) {
4018                if (Twig.expression.handler.hasOwnProperty(type)) {
4019                    token_next = Twig.expression.handler[type].next;
4020                    regex = Twig.expression.handler[type].regex;
4021                    // Twig.log.trace("Checking type ", type, " on ", expression);
4022                    if (regex instanceof Array) {
4023                        regex_array = regex;
4024                    } else {
4025                        regex_array = [regex];
4026                    }
4027
4028                    match_found = false;
4029                    while (regex_array.length > 0) {
4030                        regex = regex_array.pop();
4031                        expression = expression.replace(regex, match_function);
4032                    }
4033                    // An expression token has been matched. Break the for loop and start trying to
4034                    //  match the next template (if expression isn't empty.)
4035                    if (match_found) {
4036                        break;
4037                    }
4038                }
4039            }
4040            if (!match_found) {
4041                if (invalid_matches.length > 0) {
4042                    throw new Twig.Error(invalid_matches.join(" OR "));
4043                } else {
4044                    throw new Twig.Error("Unable to parse '" + expression + "' at template position" + exp_offset);
4045                }
4046            }
4047        }
4048
4049        Twig.log.trace("Twig.expression.tokenize", "Tokenized to ", tokens);
4050        return tokens;
4051    };
4052
4053    /**
4054     * Compile an expression token.
4055     *
4056     * @param {Object} raw_token The uncompiled token.
4057     *
4058     * @return {Object} The compiled token.
4059     */
4060    Twig.expression.compile = function (raw_token) {
4061        var expression = raw_token.value,
4062            // Tokenize expression
4063            tokens = Twig.expression.tokenize(expression),
4064            token = null,
4065            output = [],
4066            stack = [],
4067            token_template = null;
4068
4069        Twig.log.trace("Twig.expression.compile: ", "Compiling ", expression);
4070
4071        // Push tokens into RPN stack using the Sunting-yard algorithm
4072        // See http://en.wikipedia.org/wiki/Shunting_yard_algorithm
4073
4074        while (tokens.length > 0) {
4075            token = tokens.shift();
4076            token_template = Twig.expression.handler[token.type];
4077
4078            Twig.log.trace("Twig.expression.compile: ", "Compiling ", token);
4079
4080            // Compile the template
4081            token_template.compile && token_template.compile(token, stack, output);
4082
4083            Twig.log.trace("Twig.expression.compile: ", "Stack is", stack);
4084            Twig.log.trace("Twig.expression.compile: ", "Output is", output);
4085        }
4086
4087        while(stack.length > 0) {
4088            output.push(stack.pop());
4089        }
4090
4091        Twig.log.trace("Twig.expression.compile: ", "Final output is", output);
4092
4093        raw_token.stack = output;
4094        delete raw_token.value;
4095
4096        return raw_token;
4097    };
4098
4099
4100    /**
4101     * Parse an RPN expression stack within a context.
4102     *
4103     * @param {Array} tokens An array of compiled expression tokens.
4104     * @param {Object} context The render context to parse the tokens with.
4105     *
4106     * @return {Object} The result of parsing all the tokens. The result
4107     *                  can be anything, String, Array, Object, etc... based on
4108     *                  the given expression.
4109     */
4110    Twig.expression.parse = function (tokens, context) {
4111        var that = this;
4112
4113        // If the token isn't an array, make it one.
4114        if (!(tokens instanceof Array)) {
4115            tokens = [tokens];
4116        }
4117
4118        // The output stack
4119        var stack = [],
4120            token_template = null;
4121
4122        Twig.forEach(tokens, function (token) {
4123            token_template = Twig.expression.handler[token.type];
4124
4125            token_template.parse && token_template.parse.apply(that, [token, stack, context]);
4126        });
4127
4128        // Pop the final value off the stack
4129        return stack.pop();
4130    };
4131
4132    return Twig;
4133
4134})( Twig || { } );
4135//     Twig.js
4136//     Available under the BSD 2-Clause License
4137//     https://github.com/justjohn/twig.js
4138
4139// ## twig.expression.operator.js
4140//
4141// This file handles operator lookups and parsing.
4142var Twig = (function (Twig) {
4143    "use strict";
4144
4145    /**
4146     * Operator associativity constants.
4147     */
4148    Twig.expression.operator = {
4149        leftToRight: 'leftToRight',
4150        rightToLeft: 'rightToLeft'
4151    };
4152
4153    var containment = function(a, b) {
4154        if (b.indexOf !== undefined) {
4155            // String
4156            return a === b || a !== '' && b.indexOf(a) > -1;
4157
4158        } else {
4159            var el;
4160            for (el in b) {
4161                if (b.hasOwnProperty(el) && b[el] === a) {
4162                    return true;
4163                }
4164            }
4165            return false;
4166        }
4167    };
4168
4169    /**
4170     * Get the precidence and associativity of an operator. These follow the order that C/C++ use.
4171     * See http://en.wikipedia.org/wiki/Operators_in_C_and_C++ for the table of values.
4172     */
4173    Twig.expression.operator.lookup = function (operator, token) {
4174        switch (operator) {
4175            case "..":
4176            case 'not in':
4177            case 'in':
4178                token.precidence = 20;
4179                token.associativity = Twig.expression.operator.leftToRight;
4180                break;
4181
4182            case ',':
4183                token.precidence = 18;
4184                token.associativity = Twig.expression.operator.leftToRight;
4185                break;
4186
4187            // Ternary
4188            case '?':
4189            case ':':
4190                token.precidence = 16;
4191                token.associativity = Twig.expression.operator.rightToLeft;
4192                break;
4193
4194            case 'or':
4195                token.precidence = 14;
4196                token.associativity = Twig.expression.operator.leftToRight;
4197                break;
4198
4199            case 'and':
4200                token.precidence = 13;
4201                token.associativity = Twig.expression.operator.leftToRight;
4202                break;
4203
4204            case '==':
4205            case '!=':
4206                token.precidence = 9;
4207                token.associativity = Twig.expression.operator.leftToRight;
4208                break;
4209
4210            case '<':
4211            case '<=':
4212            case '>':
4213            case '>=':
4214                token.precidence = 8;
4215                token.associativity = Twig.expression.operator.leftToRight;
4216                break;
4217
4218
4219            case '~': // String concatination
4220            case '+':
4221            case '-':
4222                token.precidence = 6;
4223                token.associativity = Twig.expression.operator.leftToRight;
4224                break;
4225
4226            case '//':
4227            case '**':
4228            case '*':
4229            case '/':
4230            case '%':
4231                token.precidence = 5;
4232                token.associativity = Twig.expression.operator.leftToRight;
4233                break;
4234
4235            case 'not':
4236                token.precidence = 3;
4237                token.associativity = Twig.expression.operator.rightToLeft;
4238                break;
4239
4240            default:
4241                throw new Twig.Error(operator + " is an unknown operator.");
4242        }
4243        token.operator = operator;
4244        return token;
4245    };
4246
4247    /**
4248     * Handle operations on the RPN stack.
4249     *
4250     * Returns the updated stack.
4251     */
4252    Twig.expression.operator.parse = function (operator, stack) {
4253        Twig.log.trace("Twig.expression.operator.parse: ", "Handling ", operator);
4254        var a, b, c;
4255        switch (operator) {
4256            case ':':
4257                // Ignore
4258                break;
4259
4260            case '?':
4261                c = stack.pop(); // false expr
4262                b = stack.pop(); // true expr
4263                a = stack.pop(); // conditional
4264                if (a) {
4265                    stack.push(b);
4266                } else {
4267                    stack.push(c);
4268                }
4269                break;
4270
4271            case '+':
4272                b = parseFloat(stack.pop());
4273                a = parseFloat(stack.pop());
4274                stack.push(a + b);
4275                break;
4276
4277            case '-':
4278                b = parseFloat(stack.pop());
4279                a = parseFloat(stack.pop());
4280                stack.push(a - b);
4281                break;
4282
4283            case '*':
4284                b = parseFloat(stack.pop());
4285                a = parseFloat(stack.pop());
4286                stack.push(a * b);
4287                break;
4288
4289            case '/':
4290                b = parseFloat(stack.pop());
4291                a = parseFloat(stack.pop());
4292                stack.push(a / b);
4293                break;
4294
4295            case '//':
4296                b = parseFloat(stack.pop());
4297                a = parseFloat(stack.pop());
4298                stack.push(parseInt(a / b));
4299                break;
4300
4301            case '%':
4302                b = parseFloat(stack.pop());
4303                a = parseFloat(stack.pop());
4304                stack.push(a % b);
4305                break;
4306
4307            case '~':
4308                b = stack.pop();
4309                a = stack.pop();
4310                stack.push( (a != null ? a.toString() : "")
4311                          + (b != null ? b.toString() : "") );
4312                break;
4313
4314            case 'not':
4315            case '!':
4316                stack.push(!stack.pop());
4317                break;
4318
4319            case '<':
4320                b = stack.pop();
4321                a = stack.pop();
4322                stack.push(a < b);
4323                break;
4324
4325            case '<=':
4326                b = stack.pop();
4327                a = stack.pop();
4328                stack.push(a <= b);
4329                break;
4330
4331            case '>':
4332                b = stack.pop();
4333                a = stack.pop();
4334                stack.push(a > b);
4335                break;
4336
4337            case '>=':
4338                b = stack.pop();
4339                a = stack.pop();
4340                stack.push(a >= b);
4341                break;
4342
4343            case '===':
4344                b = stack.pop();
4345                a = stack.pop();
4346                stack.push(a === b);
4347                break;
4348
4349            case '==':
4350                b = stack.pop();
4351                a = stack.pop();
4352                stack.push(a == b);
4353                break;
4354
4355            case '!==':
4356                b = stack.pop();
4357                a = stack.pop();
4358                stack.push(a !== b);
4359                break;
4360
4361            case '!=':
4362                b = stack.pop();
4363                a = stack.pop();
4364                stack.push(a != b);
4365                break;
4366
4367            case 'or':
4368                b = stack.pop();
4369                a = stack.pop();
4370                stack.push(a || b);
4371                break;
4372
4373            case 'and':
4374                b = stack.pop();
4375                a = stack.pop();
4376                stack.push(a && b);
4377                break;
4378
4379            case '**':
4380                b = stack.pop();
4381                a = stack.pop();
4382                stack.push(Math.pow(a, b));
4383                break;
4384
4385
4386            case 'not in':
4387                b = stack.pop();
4388                a = stack.pop();
4389                stack.push( !containment(a, b) );
4390                break;
4391
4392            case 'in':
4393                b = stack.pop();
4394                a = stack.pop();
4395                stack.push( containment(a, b) );
4396                break;
4397
4398            case '..':
4399                b = stack.pop();
4400                a = stack.pop();
4401                stack.push( Twig.functions.range(a, b) );
4402                break;
4403
4404            default:
4405                throw new Twig.Error(operator + " is an unknown operator.");
4406        }
4407    };
4408
4409    return Twig;
4410
4411})( Twig || { } );
4412//     Twig.js
4413//     Available under the BSD 2-Clause License
4414//     https://github.com/justjohn/twig.js
4415
4416// ## twig.filters.js
4417//
4418// This file handles parsing filters.
4419var Twig = (function (Twig) {
4420
4421    // Determine object type
4422    function is(type, obj) {
4423        var clas = Object.prototype.toString.call(obj).slice(8, -1);
4424        return obj !== undefined && obj !== null && clas === type;
4425    }
4426
4427    Twig.filters = {
4428        // String Filters
4429        upper:  function(value) {
4430            if ( typeof value !== "string" ) {
4431               return value;
4432            }
4433
4434            return value.toUpperCase();
4435        },
4436        lower: function(value) {
4437            if ( typeof value !== "string" ) {
4438               return value;
4439            }
4440
4441            return value.toLowerCase();
4442        },
4443        capitalize: function(value) {
4444            if ( typeof value !== "string" ) {
4445                 return value;
4446            }
4447
4448            return value.substr(0, 1).toUpperCase() + value.toLowerCase().substr(1);
4449        },
4450        title: function(value) {
4451            if ( typeof value !== "string" ) {
4452               return value;
4453            }
4454
4455            return value.toLowerCase().replace( /(^|\s)([a-z])/g , function(m, p1, p2){
4456                return p1 + p2.toUpperCase();
4457            });
4458        },
4459        length: function(value) {
4460            if (Twig.lib.is("Array", value) || typeof value === "string") {
4461                return value.length;
4462            } else if (Twig.lib.is("Object", value)) {
4463                if (value._keys === undefined) {
4464                    return Object.keys(value).length;
4465                } else {
4466                    return value._keys.length;
4467                }
4468            } else {
4469                return 0;
4470            }
4471        },
4472
4473        // Array/Object Filters
4474        reverse: function(value) {
4475            if (is("Array", value)) {
4476                return value.reverse();
4477            } else if (is("String", value)) {
4478                return value.split("").reverse().join("");
4479            } else if (is("Object", value)) {
4480                var keys = value._keys || Object.keys(value).reverse();
4481                value._keys = keys;
4482                return value;
4483            }
4484        },
4485        sort: function(value) {
4486            if (is("Array", value)) {
4487                return value.sort();
4488            } else if (is('Object', value)) {
4489                // Sorting objects isn't obvious since the order of
4490                // returned keys isn't guaranteedin JavaScript.
4491                // Because of this we use a "hidden" key called _keys to
4492                // store the keys in the order we want to return them.
4493
4494                delete value._keys;
4495                var keys = Object.keys(value),
4496                    sorted_keys = keys.sort(function(a, b) {
4497                        return value[a] > value[b];
4498                    });
4499                value._keys = sorted_keys;
4500                return value;
4501            }
4502        },
4503        keys: function(value) {
4504            if (value === undefined || value === null){
4505                return;
4506           }
4507
4508            var keyset = value._keys || Object.keys(value),
4509                output = [];
4510
4511            Twig.forEach(keyset, function(key) {
4512                if (key === "_keys") return; // Ignore the _keys property
4513                if (value.hasOwnProperty(key)) {
4514                    output.push(key);
4515                }
4516            });
4517            return output;
4518        },
4519        url_encode: function(value) {
4520            if (value === undefined || value === null){
4521                return;
4522            }
4523
4524            return encodeURIComponent(value);
4525        },
4526        join: function(value, params) {
4527            if (value === undefined || value === null){
4528                return;
4529            }
4530
4531            var join_str = "",
4532                output = [],
4533                keyset = null;
4534
4535            if (params && params[0]) {
4536                join_str = params[0];
4537            }
4538            if (is("Array", value)) {
4539                output = value;
4540            } else {
4541                keyset = value._keys || Object.keys(value);
4542                Twig.forEach(keyset, function(key) {
4543                    if (key === "_keys") return; // Ignore the _keys property
4544                    if (value.hasOwnProperty(key)) {
4545                        output.push(value[key]);
4546                    }
4547                });
4548            }
4549            return output.join(join_str);
4550        },
4551        "default": function(value, params) {
4552            if (params === undefined || params.length !== 1) {
4553                throw new Twig.Error("default filter expects one argument");
4554            }
4555            if (value === undefined || value === null || value === '' ) {
4556                return params[0];
4557            } else {
4558                return value;
4559            }
4560        },
4561        json_encode: function(value) {
4562            if (value && value.hasOwnProperty( "_keys" ) ) {
4563                delete value._keys;
4564            }
4565            if(value === undefined || value === null) {
4566                return "null";
4567            }
4568            return JSON.stringify(value);
4569        },
4570        merge: function(value, params) {
4571            var obj = [],
4572                arr_index = 0,
4573                keyset = [];
4574
4575            // Check to see if all the objects being merged are arrays
4576            if (!is("Array", value)) {
4577                // Create obj as an Object
4578                obj = { };
4579            } else {
4580                Twig.forEach(params, function(param) {
4581                    if (!is("Array", param)) {
4582                        obj = { };
4583                    }
4584                });
4585            }
4586            if (!is("Array", obj)) {
4587                obj._keys = [];
4588            }
4589
4590            if (is("Array", value)) {
4591                Twig.forEach(value, function(val) {
4592                    if (obj._keys) obj._keys.push(arr_index);
4593                    obj[arr_index] = val;
4594                    arr_index++;
4595                });
4596            } else {
4597                keyset = value._keys || Object.keys(value);
4598                Twig.forEach(keyset, function(key) {
4599                    obj[key] = value[key];
4600                    obj._keys.push(key);
4601
4602                    // Handle edge case where a number index in an object is greater than
4603                    //   the array counter. In such a case, the array counter is increased
4604                    //   one past the index.
4605                    //
4606                    // Example {{ ["a", "b"]|merge({"4":"value"}, ["c", "d"])
4607                    // Without this, d would have an index of "4" and overwrite the value
4608                    //   of "value"
4609                    var int_key = parseInt(key, 10);
4610                    if (!isNaN(int_key) && int_key >= arr_index) {
4611                        arr_index = int_key + 1;
4612                    }
4613                });
4614            }
4615
4616            // mixin the merge arrays
4617            Twig.forEach(params, function(param) {
4618                if (is("Array", param)) {
4619                    Twig.forEach(param, function(val) {
4620                        if (obj._keys) obj._keys.push(arr_index);
4621                        obj[arr_index] = val;
4622                        arr_index++;
4623                    });
4624                } else {
4625                    keyset = param._keys || Object.keys(param);
4626                    Twig.forEach(keyset, function(key) {
4627                        if (!obj[key]) obj._keys.push(key);
4628                        obj[key] = param[key];
4629
4630                        var int_key = parseInt(key, 10);
4631                        if (!isNaN(int_key) && int_key >= arr_index) {
4632                            arr_index = int_key + 1;
4633                        }
4634                    });
4635                }
4636            });
4637            if (params.length === 0) {
4638                throw new Twig.Error("Filter merge expects at least one parameter");
4639            }
4640
4641            return obj;
4642        },
4643        date: function(value, params) {
4644            var date = Twig.functions.date(value);
4645            var format = params && params.length ? params[0] : 'F j, Y H:i';
4646            return Twig.lib.formatDate(date, format);
4647        },
4648
4649        date_modify: function(value, params) {
4650            if (value === undefined || value === null) {
4651                return;
4652            }
4653            if (params === undefined || params.length !== 1) {
4654                throw new Twig.Error("date_modify filter expects 1 argument");
4655            }
4656
4657            var modifyText = params[0], time;
4658
4659            if (Twig.lib.is("Date", value)) {
4660                time = Twig.lib.strtotime(modifyText, value.getTime() / 1000);
4661            }
4662            if (Twig.lib.is("String", value)) {
4663                time = Twig.lib.strtotime(modifyText, Twig.lib.strtotime(value));
4664            }
4665            if (Twig.lib.is("Number", value)) {
4666                time = Twig.lib.strtotime(modifyText, value);
4667            }
4668
4669            return new Date(time * 1000);
4670        },
4671
4672        replace: function(value, params) {
4673            if (value === undefined||value === null){
4674                return;
4675            }
4676
4677            var pairs = params[0],
4678                tag;
4679            for (tag in pairs) {
4680                if (pairs.hasOwnProperty(tag) && tag !== "_keys") {
4681                    value = Twig.lib.replaceAll(value, tag, pairs[tag]);
4682                }
4683            }
4684            return value;
4685        },
4686
4687        format: function(value, params) {
4688            if (value === undefined || value === null){
4689                return;
4690            }
4691
4692            return Twig.lib.vsprintf(value, params);
4693        },
4694
4695        striptags: function(value) {
4696            if (value === undefined || value === null){
4697                return;
4698            }
4699
4700            return Twig.lib.strip_tags(value);
4701        },
4702
4703        escape: function(value) {
4704            if (value === undefined|| value === null){
4705                return;
4706            }
4707            var raw_value = value.toString().replace(/&/g, "&amp;")
4708                        .replace(/</g, "&lt;")
4709                        .replace(/>/g, "&gt;")
4710                        .replace(/"/g, "&quot;")
4711                        .replace(/'/g, "&#039;");
4712            return Twig.Markup(raw_value);
4713        },
4714
4715        /* Alias of escape */
4716        "e": function(value) {
4717            return Twig.filters.escape(value);
4718        },
4719
4720        nl2br: function(value) {
4721            if (value === undefined || value === null){
4722                return;
4723            }
4724            var linebreak_tag = "BACKSLASH_n_replace",
4725                br = "<br />" + linebreak_tag;
4726
4727            value = Twig.filters.escape(value)
4728                        .replace(/\r\n/g, br)
4729                        .replace(/\r/g, br)
4730                        .replace(/\n/g, br);
4731
4732            value = Twig.lib.replaceAll(value, linebreak_tag, "\n");
4733
4734            return Twig.Markup(value);
4735        },
4736
4737        /**
4738         * Adapted from: http://phpjs.org/functions/number_format:481
4739         */
4740        number_format: function(value, params) {
4741            var number = value,
4742                decimals = (params && params[0]) ? params[0] : undefined,
4743                dec      = (params && params[1] !== undefined) ? params[1] : ".",
4744                sep      = (params && params[2] !== undefined) ? params[2] : ",";
4745
4746            number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
4747            var n = !isFinite(+number) ? 0 : +number,
4748                prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
4749                s = '',
4750                toFixedFix = function (n, prec) {
4751                    var k = Math.pow(10, prec);
4752                    return '' + Math.round(n * k) / k;
4753                };
4754            // Fix for IE parseFloat(0.55).toFixed(0) = 0;
4755            s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
4756            if (s[0].length > 3) {
4757                s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
4758            }
4759            if ((s[1] || '').length < prec) {
4760                s[1] = s[1] || '';
4761                s[1] += new Array(prec - s[1].length + 1).join('0');
4762            }
4763            return s.join(dec);
4764        },
4765
4766        trim: function(value, params) {
4767            if (value === undefined|| value === null){
4768                return;
4769            }
4770
4771            var str = Twig.filters.escape( '' + value ),
4772                whitespace;
4773            if ( params && params[0] ) {
4774                whitespace = '' + params[0];
4775            } else {
4776                whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
4777            }
4778            for (var i = 0; i < str.length; i++) {
4779                if (whitespace.indexOf(str.charAt(i)) === -1) {
4780                    str = str.substring(i);
4781                    break;
4782                }
4783            }
4784            for (i = str.length - 1; i >= 0; i--) {
4785                if (whitespace.indexOf(str.charAt(i)) === -1) {
4786                    str = str.substring(0, i + 1);
4787                    break;
4788                }
4789            }
4790            return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
4791        },
4792
4793        truncate: function (value, params) {
4794            var length = 30,
4795                preserve = false,
4796                separator = '...';
4797
4798            value =  value + '';
4799            if (params) {
4800                if (params[0]) {
4801                    length = params[0];
4802                }
4803                if (params[1]) {
4804                    preserve = params[1];
4805                }
4806                if (params[2]) {
4807                    separator = params[2];
4808                }
4809            }
4810
4811            if (value.length > length) {
4812
4813                if (preserve) {
4814                    length = value.indexOf(' ', length);
4815                    if (length === -1) {
4816                        return value;
4817                    }
4818                }
4819
4820                value =  value.substr(0, length) + separator;
4821            }
4822
4823            return value;
4824        },
4825
4826        slice: function(value, params) {
4827            if (value === undefined || value === null) {
4828                return;
4829            }
4830            if (params === undefined || params.length < 1) {
4831                throw new Twig.Error("slice filter expects at least 1 argument");
4832            }
4833
4834            // default to start of string
4835            var start = params[0] || 0;
4836            // default to length of string
4837            var length = params.length > 1 ? params[1] : value.length;
4838            // handle negative start values
4839            var startIndex = start >= 0 ? start : Math.max( value.length + start, 0 );
4840
4841            if (Twig.lib.is("Array", value)) {
4842                var output = [];
4843                for (var i = startIndex; i < startIndex + length && i < value.length; i++) {
4844                    output.push(value[i]);
4845                }
4846                return output;
4847            } else if (Twig.lib.is("String", value)) {
4848                return value.substr(startIndex, length);
4849            } else {
4850                throw new Twig.Error("slice filter expects value to be an array or string");
4851            }
4852        },
4853
4854        abs: function(value) {
4855            if (value === undefined || value === null) {
4856                return;
4857            }
4858
4859            return Math.abs(value);
4860        },
4861
4862        first: function(value) {
4863            if (is("Array", value)) {
4864                return value[0];
4865            } else if (is("Object", value)) {
4866                if ('_keys' in value) {
4867                    return value[value._keys[0]];
4868                }
4869            } else if ( typeof value === "string" ) {
4870                return value.substr(0, 1);
4871            }
4872
4873            return;
4874        },
4875
4876        split: function(value, params) {
4877            if (value === undefined || value === null) {
4878                return;
4879            }
4880            if (params === undefined || params.length < 1 || params.length > 2) {
4881                throw new Twig.Error("split filter expects 1 or 2 argument");
4882            }
4883            if (Twig.lib.is("String", value)) {
4884                var delimiter = params[0],
4885                    limit = params[1],
4886                    split = value.split(delimiter);
4887
4888                if (limit === undefined) {
4889
4890                    return split;
4891
4892                } else if (limit < 0) {
4893
4894                    return value.split(delimiter, split.length + limit);
4895
4896                } else {
4897
4898                    var limitedSplit = [];
4899
4900                    if (delimiter == '') {
4901                        // empty delimiter
4902                        // "aabbcc"|split('', 2)
4903                        //     -> ['aa', 'bb', 'cc']
4904
4905                        while(split.length > 0) {
4906                            var temp = "";
4907                            for (var i=0; i<limit && split.length > 0; i++) {
4908                                temp += split.shift();
4909                            }
4910                            limitedSplit.push(temp);
4911                        }
4912
4913                    } else {
4914                        // non-empty delimiter
4915                        // "one,two,three,four,five"|split(',', 3)
4916                        //     -> ['one', 'two', 'three,four,five']
4917
4918                        for (var i=0; i<limit-1 && split.length > 0; i++) {
4919                            limitedSplit.push(split.shift());
4920                        }
4921
4922                        if (split.length > 0) {
4923                            limitedSplit.push(split.join(delimiter));
4924                        }
4925                    }
4926
4927                    return limitedSplit;
4928                }
4929
4930            } else {
4931                throw new Twig.Error("split filter expects value to be a string");
4932            }
4933        },
4934        last: function(value) {
4935            if (Twig.lib.is('Object', value)) {
4936                var keys;
4937
4938                if (value._keys === undefined) {
4939                    keys = Object.keys(value);
4940                } else {
4941                    keys = value._keys;
4942                }
4943
4944                return value[keys[keys.length - 1]];
4945            }
4946
4947            // string|array
4948            return value[value.length - 1];
4949        },
4950        raw: function(value) {
4951            return Twig.Markup(value);
4952        },
4953        batch: function(items, params) {
4954            var size = params.shift(),
4955                fill = params.shift(),
4956                result,
4957                last,
4958                missing;
4959
4960            if (!Twig.lib.is("Array", items)) {
4961                throw new Twig.Error("batch filter expects items to be an array");
4962            }
4963
4964            if (!Twig.lib.is("Number", size)) {
4965                throw new Twig.Error("batch filter expects size to be a number");
4966            }
4967
4968            size = Math.ceil(size);
4969
4970            result = Twig.lib.chunkArray(items, size);
4971
4972            if (fill && items.length % size != 0) {
4973                last = result.pop();
4974                missing = size - last.length;
4975
4976                while (missing--) {
4977                    last.push(fill);
4978                }
4979
4980                result.push(last);
4981            }
4982
4983            return result;
4984        },
4985        round: function(value, params) {
4986            params = params || [];
4987
4988            var precision = params.length > 0 ? params[0] : 0,
4989                method = params.length > 1 ? params[1] : "common";
4990
4991            value = parseFloat(value);
4992
4993            if(precision && !Twig.lib.is("Number", precision)) {
4994                throw new Twig.Error("round filter expects precision to be a number");
4995            }
4996
4997            if (method === "common") {
4998                return Twig.lib.round(value, precision);
4999            }
5000
5001            if(!Twig.lib.is("Function", Math[method])) {
5002                throw new Twig.Error("round filter expects method to be 'floor', 'ceil', or 'common'");
5003            }
5004
5005            return Math[method](value * Math.pow(10, precision)) / Math.pow(10, precision);
5006        }
5007    };
5008
5009    Twig.filter = function(filter, value, params) {
5010        if (!Twig.filters[filter]) {
5011            throw "Unable to find filter " + filter;
5012        }
5013        return Twig.filters[filter].apply(this, [value, params]);
5014    };
5015
5016    Twig.filter.extend = function(filter, definition) {
5017        Twig.filters[filter] = definition;
5018    };
5019
5020    return Twig;
5021
5022})(Twig || { });
5023//     Twig.js
5024//                   2012 Hadrien Lanneau
5025//     Available under the BSD 2-Clause License
5026//     https://github.com/justjohn/twig.js
5027
5028// ## twig.functions.js
5029//
5030// This file handles parsing filters.
5031var Twig = (function (Twig) {
5032
5033    // Determine object type
5034    function is(type, obj) {
5035        var clas = Object.prototype.toString.call(obj).slice(8, -1);
5036        return obj !== undefined && obj !== null && clas === type;
5037    }
5038
5039    Twig.functions = {
5040        //  attribute, block, constant, date, dump, parent, random,.
5041
5042        // Range function from http://phpjs.org/functions/range:499
5043        // Used under an MIT License
5044        range: function (low, high, step) {
5045            // http://kevin.vanzonneveld.net
5046            // +   original by: Waldo Malqui Silva
5047            // *     example 1: range ( 0, 12 );
5048            // *     returns 1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
5049            // *     example 2: range( 0, 100, 10 );
5050            // *     returns 2: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
5051            // *     example 3: range( 'a', 'i' );
5052            // *     returns 3: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']
5053            // *     example 4: range( 'c', 'a' );
5054            // *     returns 4: ['c', 'b', 'a']
5055            var matrix = [];
5056            var inival, endval, plus;
5057            var walker = step || 1;
5058            var chars = false;
5059
5060            if (!isNaN(low) && !isNaN(high)) {
5061                inival = parseInt(low, 10);
5062                endval = parseInt(high, 10);
5063            } else if (isNaN(low) && isNaN(high)) {
5064                chars = true;
5065                inival = low.charCodeAt(0);
5066                endval = high.charCodeAt(0);
5067            } else {
5068                inival = (isNaN(low) ? 0 : low);
5069                endval = (isNaN(high) ? 0 : high);
5070            }
5071
5072            plus = ((inival > endval) ? false : true);
5073            if (plus) {
5074                while (inival <= endval) {
5075                    matrix.push(((chars) ? String.fromCharCode(inival) : inival));
5076                    inival += walker;
5077                }
5078            } else {
5079                while (inival >= endval) {
5080                    matrix.push(((chars) ? String.fromCharCode(inival) : inival));
5081                    inival -= walker;
5082                }
5083            }
5084
5085            return matrix;
5086        },
5087        cycle: function(arr, i) {
5088            var pos = i % arr.length;
5089            return arr[pos];
5090        },
5091        dump: function() {
5092            var EOL = '\n',
5093                indentChar = '  ',
5094                indentTimes = 0,
5095                out = '',
5096                args = Array.prototype.slice.call(arguments),
5097                indent = function(times) {
5098                    var ind  = '';
5099                    while (times > 0) {
5100                        times--;
5101                        ind += indentChar;
5102                    }
5103                    return ind;
5104                },
5105                displayVar = function(variable) {
5106                    out += indent(indentTimes);
5107                    if (typeof(variable) === 'object') {
5108                        dumpVar(variable);
5109                    } else if (typeof(variable) === 'function') {
5110                        out += 'function()' + EOL;
5111                    } else if (typeof(variable) === 'string') {
5112                        out += 'string(' + variable.length + ') "' + variable + '"' + EOL;
5113                    } else if (typeof(variable) === 'number') {
5114                        out += 'number(' + variable + ')' + EOL;
5115                    } else if (typeof(variable) === 'boolean') {
5116                        out += 'bool(' + variable + ')' + EOL;
5117                    }
5118                },
5119                dumpVar = function(variable) {
5120                    var i;
5121                    if (variable === null) {
5122                        out += 'NULL' + EOL;
5123                    } else if (variable === undefined) {
5124                        out += 'undefined' + EOL;
5125                    } else if (typeof variable === 'object') {
5126                        out += indent(indentTimes) + typeof(variable);
5127                        indentTimes++;
5128                        out += '(' + (function(obj) {
5129                            var size = 0, key;
5130                            for (key in obj) {
5131                                if (obj.hasOwnProperty(key)) {
5132                                    size++;
5133                                }
5134                            }
5135                            return size;
5136                        })(variable) + ') {' + EOL;
5137                        for (i in variable) {
5138                            out += indent(indentTimes) + '[' + i + ']=> ' + EOL;
5139                            displayVar(variable[i]);
5140                        }
5141                        indentTimes--;
5142                        out += indent(indentTimes) + '}' + EOL;
5143                    } else {
5144                        displayVar(variable);
5145                    }
5146                };
5147
5148            // handle no argument case by dumping the entire render context
5149            if (args.length == 0) args.push(this.context);
5150
5151            Twig.forEach(args, function(variable) {
5152                dumpVar(variable);
5153            });
5154
5155            return out;
5156        },
5157        date: function(date, time) {
5158            var dateObj;
5159            if (date === undefined) {
5160                dateObj = new Date();
5161            } else if (Twig.lib.is("Date", date)) {
5162                dateObj = date;
5163            } else if (Twig.lib.is("String", date)) {
5164                dateObj = new Date(Twig.lib.strtotime(date) * 1000);
5165            } else if (Twig.lib.is("Number", date)) {
5166                // timestamp
5167                dateObj = new Date(date * 1000);
5168            } else {
5169                throw new Twig.Error("Unable to parse date " + date);
5170            }
5171            return dateObj;
5172        },
5173        block: function(block) {
5174            if (this.originalBlockTokens[block]) {
5175                return Twig.logic.parse.apply(this, [this.originalBlockTokens[block], this.context]).output;
5176            } else {
5177                return this.blocks[block];
5178            }
5179        },
5180        parent: function() {
5181            // Add a placeholder
5182            return Twig.placeholders.parent;
5183        },
5184        attribute: function(object, method, params) {
5185            if (Twig.lib.is('Object', object)) {
5186                if (object.hasOwnProperty(method)) {
5187                    if (typeof object[method] === "function") {
5188                        return object[method].apply(undefined, params);
5189                    }
5190                    else {
5191                        return object[method];
5192                    }
5193                }
5194            }
5195            // Array will return element 0-index
5196            return object[method] || undefined;
5197        },
5198        template_from_string: function(template) {
5199            if (template === undefined) {
5200                template = '';
5201            }
5202            return new Twig.Template({
5203                options: this.options,
5204                data: template
5205            });
5206        },
5207        random: function(value) {
5208            var LIMIT_INT31 = 0x80000000;
5209
5210            function getRandomNumber(n) {
5211                var random = Math.floor(Math.random() * LIMIT_INT31);
5212                var limits = [0, n];
5213                var min = Math.min.apply(null, limits),
5214                    max = Math.max.apply(null, limits);
5215                return min + Math.floor((max - min + 1) * random / LIMIT_INT31);
5216            }
5217
5218            if(Twig.lib.is("Number", value)) {
5219                return getRandomNumber(value);
5220            }
5221
5222            if(Twig.lib.is("String", value)) {
5223                return value.charAt(getRandomNumber(value.length-1));
5224            }
5225
5226            if(Twig.lib.is("Array", value)) {
5227                return value[getRandomNumber(value.length-1)];
5228            }
5229
5230            if(Twig.lib.is("Object", value)) {
5231                var keys = Object.keys(value);
5232                return value[keys[getRandomNumber(keys.length-1)]];
5233            }
5234
5235            return getRandomNumber(LIMIT_INT31-1);
5236        }
5237    };
5238
5239    Twig._function = function(_function, value, params) {
5240        if (!Twig.functions[_function]) {
5241            throw "Unable to find function " + _function;
5242        }
5243        return Twig.functions[_function](value, params);
5244    };
5245
5246    Twig._function.extend = function(_function, definition) {
5247        Twig.functions[_function] = definition;
5248    };
5249
5250    return Twig;
5251
5252})(Twig || { });
5253//     Twig.js
5254//     Available under the BSD 2-Clause License
5255//     https://github.com/justjohn/twig.js
5256
5257// ## twig.tests.js
5258//
5259// This file handles expression tests. (is empty, is not defined, etc...)
5260var Twig = (function (Twig) {
5261    "use strict";
5262    Twig.tests = {
5263        empty: function(value) {
5264            if (value === null || value === undefined) return true;
5265            // Handler numbers
5266            if (typeof value === "number") return false; // numbers are never "empty"
5267            // Handle strings and arrays
5268            if (value.length && value.length > 0) return false;
5269            // Handle objects
5270            for (var key in value) {
5271                if (value.hasOwnProperty(key)) return false;
5272            }
5273            return true;
5274        },
5275        odd: function(value) {
5276            return value % 2 === 1;
5277        },
5278        even: function(value) {
5279            return value % 2 === 0;
5280        },
5281        divisibleby: function(value, params) {
5282            return value % params[0] === 0;
5283        },
5284        defined: function(value) {
5285            return value !== undefined;
5286        },
5287        none: function(value) {
5288            return value === null;
5289        },
5290        'null': function(value) {
5291            return this.none(value); // Alias of none
5292        },
5293        sameas: function(value, params) {
5294            return value === params[0];
5295        },
5296        iterable: function(value) {
5297            return value && (Twig.lib.is("Array", value) || Twig.lib.is("Object", value));
5298        }
5299        /*
5300        constant ?
5301         */
5302    };
5303
5304    Twig.test = function(test, value, params) {
5305        if (!Twig.tests[test]) {
5306            throw "Test " + test + " is not defined.";
5307        }
5308        return Twig.tests[test](value, params);
5309    };
5310
5311    Twig.test.extend = function(test, definition) {
5312        Twig.tests[test] = definition;
5313    };
5314
5315    return Twig;
5316})( Twig || { } );
5317//     Twig.js
5318//     Available under the BSD 2-Clause License
5319//     https://github.com/justjohn/twig.js
5320
5321// ## twig.exports.js
5322//
5323// This file provides extension points and other hooks into the twig functionality.
5324
5325var Twig = (function (Twig) {
5326    "use strict";
5327    Twig.exports = {
5328        VERSION: Twig.VERSION
5329    };
5330
5331    /**
5332     * Create and compile a twig.js template.
5333     *
5334     * @param {Object} param Paramteres for creating a Twig template.
5335     *
5336     * @return {Twig.Template} A Twig template ready for rendering.
5337     */
5338    Twig.exports.twig = function twig(params) {
5339        'use strict';
5340        var id = params.id,
5341            options = {
5342                strict_variables: params.strict_variables || false,
5343                // TODO: turn autoscape on in the next major version
5344                autoescape: params.autoescape != null && params.autoescape || false,
5345                allowInlineIncludes: params.allowInlineIncludes || false,
5346                rethrow: params.rethrow || false
5347            };
5348
5349        if (id) {
5350            Twig.validateId(id);
5351        }
5352
5353        if (params.debug !== undefined) {
5354            Twig.debug = params.debug;
5355        }
5356        if (params.trace !== undefined) {
5357            Twig.trace = params.trace;
5358        }
5359
5360        if (params.data !== undefined) {
5361            return new Twig.Template({
5362                data: params.data,
5363                module: params.module,
5364                id:   id,
5365                options: options
5366            });
5367
5368        } else if (params.ref !== undefined) {
5369            if (params.id !== undefined) {
5370                throw new Twig.Error("Both ref and id cannot be set on a twig.js template.");
5371            }
5372            return Twig.Templates.load(params.ref);
5373
5374        } else if (params.href !== undefined) {
5375            return Twig.Templates.loadRemote(params.href, {
5376                id: id,
5377                method: 'ajax',
5378                base: params.base,
5379                module: params.module,
5380                precompiled: params.precompiled,
5381                async: params.async,
5382                options: options
5383
5384            }, params.load, params.error);
5385
5386        } else if (params.path !== undefined) {
5387            return Twig.Templates.loadRemote(params.path, {
5388                id: id,
5389                method: 'fs',
5390                base: params.base,
5391                module: params.module,
5392                precompiled: params.precompiled,
5393                async: params.async,
5394                options: options
5395
5396            }, params.load, params.error);
5397        }
5398    };
5399
5400    // Extend Twig with a new filter.
5401    Twig.exports.extendFilter = function(filter, definition) {
5402        Twig.filter.extend(filter, definition);
5403    };
5404
5405    // Extend Twig with a new function.
5406    Twig.exports.extendFunction = function(fn, definition) {
5407        Twig._function.extend(fn, definition);
5408    };
5409
5410    // Extend Twig with a new test.
5411    Twig.exports.extendTest = function(test, definition) {
5412        Twig.test.extend(test, definition);
5413    };
5414
5415    // Extend Twig with a new definition.
5416    Twig.exports.extendTag = function(definition) {
5417        Twig.logic.extend(definition);
5418    };
5419
5420    // Provide an environment for extending Twig core.
5421    // Calls fn with the internal Twig object.
5422    Twig.exports.extend = function(fn) {
5423        fn(Twig);
5424    };
5425
5426
5427    /**
5428     * Provide an extension for use with express 2.
5429     *
5430     * @param {string} markup The template markup.
5431     * @param {array} options The express options.
5432     *
5433     * @return {string} The rendered template.
5434     */
5435    Twig.exports.compile = function(markup, options) {
5436        var id = options.filename,
5437            path = options.filename,
5438            template;
5439
5440        // Try to load the template from the cache
5441        template = new Twig.Template({
5442            data: markup,
5443            path: path,
5444            id: id,
5445            options: options.settings['twig options']
5446        }); // Twig.Templates.load(id) ||
5447
5448        return function(context) {
5449            return template.render(context);
5450        };
5451    };
5452
5453    /**
5454     * Provide an extension for use with express 3.
5455     *
5456     * @param {string} path The location of the template file on disk.
5457     * @param {Object|Function} The options or callback.
5458     * @param {Function} fn callback.
5459     */
5460
5461    Twig.exports.renderFile = function(path, options, fn) {
5462        // handle callback in options
5463        if ('function' == typeof options) {
5464            fn = options;
5465            options = {};
5466        }
5467
5468        options = options || {};
5469
5470        var params = {
5471                path: path,
5472                base: options.settings['views'],
5473                load: function(template) {
5474                    // render and return template
5475                    fn(null, template.render(options));
5476                }
5477            };
5478
5479        // mixin any options provided to the express app.
5480        var view_options = options.settings['twig options'];
5481
5482        if (view_options) {
5483            for (var option in view_options) if (view_options.hasOwnProperty(option)) {
5484                params[option] = view_options[option];
5485            }
5486        }
5487
5488        Twig.exports.twig(params);
5489    };
5490
5491    // Express 3 handler
5492    Twig.exports.__express = Twig.exports.renderFile;
5493
5494    /**
5495     * Shoud Twig.js cache templates.
5496     * Disable during development to see changes to templates without
5497     * reloading, and disable in production to improve performance.
5498     *
5499     * @param {boolean} cache
5500     */
5501    Twig.exports.cache = function(cache) {
5502        Twig.cache = cache;
5503    }
5504
5505    return Twig;
5506}) (Twig || { });
5507
5508//     Twig.js
5509//     Available under the BSD 2-Clause License
5510//     https://github.com/justjohn/twig.js
5511
5512// ## twig.compiler.js
5513//
5514// This file handles compiling templates into JS
5515var Twig = (function (Twig) {
5516    /**
5517     * Namespace for compilation.
5518     */
5519    Twig.compiler = {
5520        module: {}
5521    };
5522
5523    // Compile a Twig Template to output.
5524    Twig.compiler.compile = function(template, options) {
5525        // Get tokens
5526        var tokens = JSON.stringify(template.tokens)
5527            , id = template.id
5528            , output;
5529
5530        if (options.module) {
5531            if (Twig.compiler.module[options.module] === undefined) {
5532                throw new Twig.Error("Unable to find module type " + options.module);
5533            }
5534            output = Twig.compiler.module[options.module](id, tokens, options.twig);
5535        } else {
5536            output = Twig.compiler.wrap(id, tokens);
5537        }
5538        return output;
5539    };
5540
5541    Twig.compiler.module = {
5542        amd: function(id, tokens, pathToTwig) {
5543            return 'define(["' + pathToTwig + '"], function (Twig) {\n\tvar twig, templates;\ntwig = Twig.twig;\ntemplates = ' + Twig.compiler.wrap(id, tokens) + '\n\treturn templates;\n});';
5544        }
5545        , node: function(id, tokens) {
5546            return 'var twig = require("twig").twig;\n'
5547                + 'exports.template = ' + Twig.compiler.wrap(id, tokens)
5548        }
5549        , cjs2: function(id, tokens, pathToTwig) {
5550            return 'module.declare([{ twig: "' + pathToTwig + '" }], function (require, exports, module) {\n'
5551                        + '\tvar twig = require("twig").twig;\n'
5552                        + '\texports.template = ' + Twig.compiler.wrap(id, tokens)
5553                    + '\n});'
5554        }
5555    };
5556
5557    Twig.compiler.wrap = function(id, tokens) {
5558        return 'twig({id:"'+id.replace('"', '\\"')+'", data:'+tokens+', precompiled: true});\n';
5559    };
5560
5561    return Twig;
5562})(Twig || {});
5563//     Twig.js
5564//     Available under the BSD 2-Clause License
5565//     https://github.com/justjohn/twig.js
5566
5567// ## twig.module.js
5568// Provide a CommonJS/AMD/Node module export.
5569
5570if (typeof module !== 'undefined' && module.declare) {
5571    // Provide a CommonJS Modules/2.0 draft 8 module
5572    module.declare([], function(require, exports, module) {
5573        // Add exports from the Twig exports
5574        for (key in Twig.exports) {
5575            if (Twig.exports.hasOwnProperty(key)) {
5576                exports[key] = Twig.exports[key];
5577            }
5578        }
5579    });
5580} else if (typeof define == 'function' && define.amd) {
5581    define(function() {
5582        return Twig.exports;
5583    });
5584} else if (typeof module !== 'undefined' && module.exports) {
5585    // Provide a CommonJS Modules/1.1 module
5586    module.exports = Twig.exports;
5587} else {
5588    // Export for browser use
5589    window.twig = Twig.exports.twig;
5590    window.Twig = Twig.exports;
5591}
5592

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.