--- /dev/null
+Twig is written and maintained by the Twig Team:
+
+Lead Developer:
+
+- Fabien Potencier <fabien.potencier@symfony-project.org>
+
+Project Founder:
+
+- Armin Ronacher <armin.ronacher@active-4.com>
--- /dev/null
+Copyright (c) 2009 by the Twig Team, see AUTHORS for more details.
+
+Some rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ * The names of the contributors may not be used to endorse or
+ promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null
+Twig, the flexible, fast, and secure template language for PHP
+==============================================================
+
+Twig is a template language for PHP, released under the new BSD license (code
+and documentation).
+
+Twig uses a syntax similar to the Django and Jinja template languages which
+inspired the Twig runtime environment.
--- /dev/null
+Introduction
+============
+
+This is the documentation for Twig, the flexible, fast, and secure template
+language for PHP.
+
+If you have any exposure to other text-based template languages, such as
+Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
+designer and developer friendly by sticking to PHP's principles and adding
+functionality useful for templating environments.
+
+The key-features are...
+
+ * *Fast*: Twig compiles templates down to plain optimized PHP code. The
+ overhead compared to regular PHP code was reduced to the very minimum.
+
+ * *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
+ allows Twig to be used as a templating language for applications where
+ users may modify the template design.
+
+ * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
+ developer to define its own custom tags and filters, and create its own
+ DSL.
+
+Prerequisites
+-------------
+
+Twig needs at least **PHP 5.2.4** to run.
+
+Installation
+------------
+
+You have multiple ways to install Twig. If you are unsure what to do, go with
+the tarball.
+
+### From the tarball release
+
+ 1. Download the most recent tarball from the [download page](http://www.twig-project.org/installation)
+ 2. Unpack the tarball
+ 3. Move the files somewhere in your project
+
+### Installing the development version
+
+ 1. Install Subversion
+ 2. `svn co http://svn.twig-project.org/trunk/ twig`
+
+Basic API Usage
+---------------
+
+This section gives you a brief introduction to the PHP API for Twig.
+
+The first step to use Twig is to register its autoloader:
+
+ [php]
+ require_once '/path/to/lib/Twig/Autoloader.php';
+ Twig_Autoloader::register();
+
+Replace the `/path/to/lib/` path with the path you used for Twig installation.
+
+>**NOTE**
+>Twig follows the PEAR convention names for its classes, which means you can
+>easily integrate Twig classes loading in your own autoloader.
+
+ [php]
+ $loader = new Twig_Loader_String();
+ $twig = new Twig_Environment($loader);
+
+ $template = $twig->loadTemplate('Hello {{ name }}!');
+
+ $template->display(array('name' => 'Fabien'));
+
+Twig uses a loader (`Twig_Loader_String`) to locate templates, and an
+environment (`Twig_Environment`) to store the configuration.
+
+The `loadTemplate()` method uses the loader to locate and load the template
+and returns a template object (`Twig_Template`) which is suitable for
+rendering with the `display()` method.
+
+Twig also comes with a filesystem loader:
+
+ [php]
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
+ $twig = new Twig_Environment($loader);
+
+ $template = $twig->loadTemplate('index.html');
--- /dev/null
+Twig for Template Designers
+===========================
+
+This document describes the syntax and semantics of the template engine and
+will be most useful as reference to those creating Twig templates.
+
+Synopsis
+--------
+
+A template is simply a text file. It can generate any text-based format (HTML,
+XML, CSV, LaTeX, etc.). It doesn't have a specific extension, `.html` or
+`.xml` are just fine.
+
+A template contains **variables** or **expressions**, which get replaced with
+values when the template is evaluated, and tags, which control the logic of
+the template.
+
+Below is a minimal template that illustrates a few basics. We will cover the
+details later in that document:
+
+ [twig]
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+ <html lang="en">
+ <head>
+ <title>My Webpage</title>
+ </head>
+ <body>
+ <ul id="navigation">
+ {% for item in navigation %}
+ <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
+ {% endfor %}
+ </ul>
+
+ <h1>My Webpage</h1>
+ {{ a_variable }}
+ </body>
+ </html>
+
+There are two kinds of delimiters: `{% ... %}` and `{{ ... }}`. The first one
+is used to execute statements such as for-loops, the latter prints the result
+of an expression to the template.
+
+Variables
+---------
+
+The application passes variables to the templates you can mess around in the
+template. Variables may have attributes or elements on them you can access
+too. How a variable looks like, heavily depends on the application providing
+those.
+
+You can use a dot (`.`) to access attributes of a variable, alternative the
+so-called "subscript" syntax (`[]`) can be used. The following lines do the
+same::
+
+ [twig]
+ {{ foo.bar }}
+ {{ foo['bar'] }}
+
+>**NOTE**
+>It's important to know that the curly braces are *not* part of the variable
+>but the print statement. If you access variables inside tags don't put the
+>braces around.
+
+If a variable or attribute does not exist you will get back a `null` value.
+
+>**SIDEBAR**
+>Implementation
+>
+>For convenience sake `foo.bar` does the following things on
+>the PHP layer:
+>
+> * check if `foo` is an array and `bar` a valid element;
+> * if not, and if `foo` is an object, check that `bar` is a valid method;
+> * if not, and if `foo` is an object, check that `getBar` is a valid method;
+> * if not, return a `null` value.
+>
+>`foo['bar']` on the other hand works mostly the same with the a small
+>difference in the order:
+>
+> * check if `foo` is an array and `bar` a valid element;
+> * if not, return a `null` value.
+>
+>Using the alternative syntax is also useful to dynamically get attributes
+>from arrays:
+>
+> [twig]
+> foo[bar]
+
+Filters
+-------
+
+Variables can by modified by **filters**. Filters are separated from the
+variable by a pipe symbol (`|`) and may have optional arguments in
+parentheses. Multiple filters can be chained. The output of one filter is
+applied to the next.
+
+`{{ name|striptags|title }}` for example will remove all HTML tags from the
+`name` and title-cases it. Filters that accept arguments have parentheses
+around the arguments, like a function call. This example will join a list by
+commas: `{{ list|join(', ') }}`.
+
+The builtin filters section below describes all the builtin filters.
+
+Comments
+--------
+
+To comment-out part of a line in a template, use the comment syntax `{# ... #}`.
+This is useful to comment out parts of the template for debugging or to
+add information for other template designers or yourself:
+
+ [twig]
+ {# note: disabled template because we no longer use this
+ {% for user in users %}
+ ...
+ {% endfor %}
+ #}
+
+Whitespace Control
+------------------
+
+In the default configuration whitespace is not further modified by the
+template engine, so each whitespace (spaces, tabs, newlines etc.) is returned
+unchanged. If the application configures Twig to `trim_blocks` the first
+newline after a template tag is removed automatically (like in PHP).
+
+Escaping
+--------
+
+It is sometimes desirable or even necessary to have Twig ignore parts it would
+otherwise handle as variables or blocks. For example if the default syntax is
+used and you want to use `{{` as raw string in the template and not start a
+variable you have to use a trick.
+
+The easiest way is to output the variable delimiter (`{{`) by using a variable
+expression:
+
+ [twig]
+ {{ '{{' }}
+
+For bigger sections it makes sense to mark a block `raw`. For example to put
+Twig syntax as example into a template you can use this snippet:
+
+ [twig]
+ {% raw %}
+ <ul>
+ {% for item in seq %}
+ <li>{{ item }}</li>
+ {% endfor %}
+ </ul>
+ {% endraw %}
+
+Template Inheritance
+--------------------
+
+The most powerful part of Twig is template inheritance. Template inheritance
+allows you to build a base "skeleton" template that contains all the common
+elements of your site and defines **blocks** that child templates can
+override.
+
+Sounds complicated but is very basic. It's easiest to understand it by
+starting with an example.
+
+### Base Template
+
+This template, which we'll call `base.html`, defines a simple HTML skeleton
+document that you might use for a simple two-column page. It's the job of
+"child" templates to fill the empty blocks with content:
+
+ [twig]
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
+ <html lang="en">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ {% block head %}
+ <link rel="stylesheet" href="style.css" />
+ <title>{% block title %}{% endblock %} - My Webpage</title>
+ {% endblock %}
+ </head>
+ <body>
+ <div id="content">{% block content %}{% endblock %}</div>
+ <div id="footer">
+ {% block footer %}
+ © Copyright 2009 by <a href="http://domain.invalid/">you</a>.
+ {% endblock %}
+ </div>
+ </body>
+
+In this example, the `{% block %}` tags define four blocks that child
+templates can fill in. All the `block` tag does is to tell the template engine
+that a child template may override those portions of the template.
+
+### Child Template
+
+A child template might look like this:
+
+ [twig]
+ {% extends "base.html" %}
+
+ {% block title %}Index{% endblock %}
+ {% block head %}
+ {% parent %}
+ <style type="text/css">
+ .important { color: #336699; }
+ </style>
+ {% endblock %}
+ {% block content %}
+ <h1>Index</h1>
+ <p class="important">
+ Welcome on my awesome homepage.
+ </p>
+ {% endblock %}
+
+The `{% extends %}` tag is the key here. It tells the template engine that
+this template "extends" another template. When the template system evaluates
+this template, first it locates the parent. The extends tag should be the
+first tag in the template.
+
+The filename of the template depends on the template loader. For example the
+`Twig_Loader_Filesystem` allows you to access other templates by giving the
+filename. You can access templates in subdirectories with a slash:
+
+ [twig]
+ {% extends "layout/default.html" %}
+
+But this behavior can depend on the application embedding Twig. Note that
+since the child template doesn't define the `footer` block, the value from the
+parent template is used instead.
+
+You can't define multiple `{% block %}` tags with the same name in the same
+template. This limitation exists because a block tag works in "both"
+directions. That is, a block tag doesn't just provide a hole to fill - it also
+defines the content that fills the hole in the *parent*. If there were two
+similarly-named `{% block %}` tags in a template, that template's parent
+wouldn't know which one of the blocks' content to use.
+
+If you want to print a block multiple times you can however use the `display`
+tag:
+
+ [twig]
+ <title>{% block title %}{% endblock %}</title>
+ <h1>{% display title %}</h1>
+ {% block body %}{% endblock %}
+
+Like PHP, Twig does not support multiple inheritance. So you can only have one
+extends tag called per rendering.
+
+### Parent Blocks
+
+It's possible to render the contents of the parent block by using the `parent`
+tag. This gives back the results of the parent block:
+
+ [twig]
+ {% block sidebar %}
+ <h3>Table Of Contents</h3>
+ ...
+ {% parent %}
+ {% endblock %}
+
+### Named Block End-Tags
+
+Twig allows you to put the name of the block after the end tag for better
+readability:
+
+ [twig]
+ {% block sidebar %}
+ {% block inner_sidebar %}
+ ...
+ {% endblock inner_sidebar %}
+ {% endblock sidebar %}
+
+However the name after the `endblock` word must match the block name.
+
+### Block Nesting and Scope
+
+Blocks can be nested for more complex layouts. Per default, blocks have access
+to variables from outer scopes:
+
+ [twig]
+ {% for item in seq %}
+ <li>{% block loop_item %}{{ item }}{% endblock %}</li>
+ {% endfor %}
+
+Import Context Behavior
+-----------------------
+
+Per default included templates are passed the current context.
+
+The context that is passed to the included template includes variables defined
+in the template:
+
+ [twig]
+ {% for box in boxes %}
+ {% include "render_box.html" %}
+ {% endfor %}
+
+The included template `render_box.html` is able to access `box`.
+
+HTML Escaping
+-------------
+
+When generating HTML from templates, there's always a risk that a variable
+will include characters that affect the resulting HTML. There are two
+approaches: manually escaping each variable or automatically escaping
+everything by default.
+
+Twig supports both, but what is used depends on the application configuration.
+The default configuration is no automatic escaping for various reasons:
+
+ * Escaping everything except of safe values will also mean that Twig is
+ escaping variables known to not include HTML such as numbers which is a
+ huge performance hit.
+
+ * The information about the safety of a variable is very fragile. It could
+ happen that by coercing safe and unsafe values the return value is double
+ escaped HTML.
+
+>**NOTE**
+>Escaping is only supported if the *escaper* extension has been enabled.
+
+### Working with Manual Escaping
+
+If manual escaping is enabled it's **your** responsibility to escape variables
+if needed. What to escape? If you have a variable that *may* include any of
+the following chars (`>`, `<`, `&`, or `"`) you **have to** escape it unless
+the variable contains well-formed and trusted HTML. Escaping works by piping
+the variable through the `|e` filter: `{{ user.username|e }}`.
+
+### Working with Automatic Escaping
+
+Automatic escaping is enabled when the `escaper` extension has been enabled.
+
+Whether automatic escaping is enabled or not, you can mark a section of a
+template to be escaped or not by using the `autoescape` tag:
+
+ [twig]
+ {% autoescape on %}
+ Everything will be automatically escaped in this block
+ {% endautoescape %}
+
+ {% autoescape off %}
+ Everything will be outputed as is in this block
+ {% endautoescape %}
+
+When automatic escaping is enabled everything is escaped by default except for
+values explicitly marked as safe. Those can be marked in the template by using
+the `|safe` filter.
+
+Functions returning template data (like `parent`) return safe markup always.
+
+>**NOTE**
+>Twig is smart enough to not escape an already escaped value by the `escape`
+>filter.
+
+List of Control Structures
+--------------------------
+
+A control structure refers to all those things that control the flow of a
+program - conditionals (i.e. `if`/`elseif`/`else`), `for`-loops, as well as
+things like blocks. Control structures appear inside `{% ... %}` blocks.
+
+### For
+
+Loop over each item in a sequence. For example, to display a list of users
+provided in a variable called `users`:
+
+ [twig]
+ <h1>Members</h1>
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+ </ul>
+
+Inside of a for loop block you can access some special variables:
+
+| Variable | Description
+| --------------------- | -------------------------------------------------------------
+| `loop.index` | The current iteration of the loop. (1 indexed)
+| `loop.index0` | The current iteration of the loop. (0 indexed)
+| `loop.revindex` | The number of iterations from the end of the loop (1 indexed)
+| `loop.revindex0` | The number of iterations from the end of the loop (0 indexed)
+| `loop.first` | True if first iteration
+| `loop.last` | True if last iteration
+| `loop.length` | The number of items in the sequence
+
+Unlike in PHP it's not possible to `break` or `continue` in a loop.
+
+If no iteration took place because the sequence was empty, you can render a
+replacement block by using `else`:
+
+ [twig]
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% else %}
+ <li><em>no users found</em></li>
+ {% endif %}
+ </ul>
+
+### If
+
+The `if` statement in Twig is comparable with the if statements of PHP. In the
+simplest form you can use it to test if a variable is defined, not empty or
+not false:
+
+ [twig]
+ {% if users %}
+ <ul>
+ {% for user in users %}
+ <li>{{ user.username|e }}</li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+
+For multiple branches `elseif` and `else` can be used like in PHP. You can use
+more complex `expressions` there too:
+
+ {% if kenny.sick %}
+ Kenny is sick.
+ {% elseif kenny.dead %}
+ You killed Kenny! You bastard!!!
+ {% else %}
+ Kenny looks okay --- so far
+ {% endif %}
+
+### Filters
+
+Filter sections allow you to apply regular Twig filters on a block of template
+data. Just wrap the code in the special `filter` section:
+
+ [twig]
+ {% filter upper %}
+ This text becomes uppercase
+ {% endfilter %}
+
+### Extends
+
+The `extends` tag can be used to extend a template from another one. You can
+have multiple of them in a file but only one of them may be executed at the
+time. There is no support for multiple inheritance. See the section about
+Template inheritance above.
+
+### Block
+
+Blocks are used for inheritance and act as placeholders and replacements at
+the same time. They are documented in detail as part of the section about
+Template inheritance above.
+
+### Include
+
+The `include` statement is useful to include a template and return the
+rendered contents of that file into the current namespace:
+
+ [twig]
+ {% include 'header.html' %}
+ Body
+ {% include 'footer.html' %}
+
+Included templates have access to the variables of the active context.
+
+An included file can be evaluated in the sandbox environment by appending
+`sandboxed` at the end if the `escaper` extension has been enabled:
+
+ [twig]
+ {% include 'user.html' sandboxed %}
+
+Expressions
+-----------
+
+Twig allows basic expressions everywhere. These work very similar to regular
+PHP and even if you're not working with PHP you should feel comfortable with
+it.
+
+### Literals
+
+The simplest form of expressions are literals. Literals are representations
+for PHP types such as strings and numbers. The following literals exist:
+
+ * `"Hello World"`: Everything between two double or single quotes is a
+ string. They are useful whenever you need a string in the template (for
+ example as arguments to function calls, filters or just to extend or
+ include a template).
+
+ * `42` / `42.23`: Integers and floating point numbers are created by just
+ writing the number down. If a dot is present the number is a float,
+ otherwise an integer.
+
+### Math
+
+Twig allows you to calculate with values. This is rarely useful in templates
+but exists for completeness' sake. The following operators are supported:
+
+ * `+`: Adds two objects together. Usually the objects are numbers but if both
+ are strings or lists you can concatenate them this way. This however is not
+ the preferred way to concatenate strings! For string concatenation have a
+ look at the `~` operator. `{{ 1 + 1 }}` is `2`.
+
+ * `-`: Substract the second number from the first one. `{{ 3 - 2 }}` is `1`.
+
+ * `/`: Divide two numbers. The return value will be a floating point number.
+ `{{ 1 / 2 }}` is `{{ 0.5 }}`.
+
+ * `//`: Divide two numbers and return the truncated integer result. `{{ 20 //
+ 7 }}` is `2`.
+
+ * `%`: Calculate the remainder of an integer division. `{{ 11 % 7 }}` is `4`.
+
+ * `*`: Multiply the left operand with the right one. `{{ 2 * 2 }}` would
+ return `4`. This can also be used to repeat a string multiple times. `{{
+ '=' * 80 }}` would print a bar of 80 equal signs.
+
+ * `**`: Raise the left operand to the power of the right operand. `{{ 2**3
+ }}` would return `8`.
+
+### Logic
+
+For `if` statements, `for` filtering or `if` expressions it can be useful to
+combine multiple expressions:
+
+ * `and`: Return true if the left and the right operand is true.
+
+ * `or`: Return true if the left or the right operand is true.
+
+ * `not`: Negate a statement.
+
+ * `(expr)`: Group an expression.
+
+### Comparisons
+
+The following comparison operators are supported in any expression: `==`,
+`!=`, `<`, `>`, `>=`, and `<=`.
+
+>**TIP**
+>Besides PHP classic comparison operators, Twig also supports a shortcut
+>notation when you want to test a value in a range:
+>
+> [twig]
+> {% if 1 < foo < 4 %}foo is between 1 and 4{% endif %}
+
+### Other Operators
+
+The following operators are very useful but don't fit into any of the other
+two categories:
+
+ * `|`: Applies a filter.
+
+ * `~`: Converts all operands into strings and concatenates them. `{{ "Hello "
+ ~ name ~ "!" }}` would return (assuming `name` is `'John'`) `Hello John!`.
+
+ * `.`, `[]`: Get an attribute of an object.
+
+ * `?:`: Twig supports the PHP ternary operator:
+
+ [twig]
+ {{ foo ? 'yes' : 'no' }}
+
+List of Builtin Filters
+-----------------------
+
+### `date`
+
+The `date` filter is able to format a date to a given format:
+
+ [twig]
+ {{ post.published_at|date("m/d/Y") }}
+
+### `format`
+
+The `format` filter formats a given string by replacing the placeholders:
+
+
+ [twig]
+ {# string is a format string like: I like %s and %s. #}
+ {{ string|format(foo, "bar") }}
+ {# returns I like foo and bar. (if the foo parameter equals to the foo string) #}
+
+### `even`
+
+The `even` filter returns `true` if the given number is even, `false`
+otherwise:
+
+ [twig]
+ {{ var|even ? 'even' : 'odd' }}
+
+### `odd`
+
+The `odd` filter returns `true` if the given number is odd, `false`
+otherwise:
+
+ [twig]
+ {{ var|odd ? 'odd' : 'even' }}
+
+### `encoding`
+
+The `encoding` filter URL encode a given string.
+
+### `title`
+
+The `title` filter returns a titlecased version of the value. I.e. words will
+start with uppercase letters, all remaining characters are lowercase.
+
+### `capitalize`
+
+The `capitalize` filter capitalizes a value. The first character will be
+uppercase, all others lowercase.
+
+### `upper`
+
+The `upper` filter converts a value to uppercase.
+
+### `lower`
+
+The `lower` filter converts a value to lowercase.
+
+### `striptags`
+
+The `striptags` filter strips SGML/XML tags and replace adjacent whitespace by
+one space.
+
+### `join`
+
+The `join` filter returns a string which is the concatenation of the strings
+in the sequence. The separator between elements is an empty string per
+default, you can define it with the optional parameter:
+
+ [twig]
+ {{ [1, 2, 3]|join('|') }}
+ {# returns 1|2|3 #}
+
+ {{ [1, 2, 3]|join }}
+ {# returns 123 #}
+
+### `reverse`
+
+The `reverse` filter reverses an array or an object if it implements the
+`Iterator` interface.
+
+### `length`
+
+The `length` filters returns the number of items of a sequence or mapping, or
+the length of a string.
+
+### `sort`
+
+The `sort` filter sorts an array.
+
+### `default`
+
+The `default` filter returns the passed default value if the value is
+undefined, otherwise the value of the variable:
+
+ [twig]
+ {{ my_variable|default('my_variable is not defined') }}
+
+### `keys`
+
+The `keys` filter returns the keys of an array. It is useful when you want to
+iterate over the keys of an array:
+
+ [twig]
+ {% for key in array|keys %}
+ ...
+ {% endfor %}
+
+### `items`
+
+The `items` filter is mainly useful when using the `for` tag to iterate over
+both the keys and the values of an array:
+
+ [twig]
+ {% for key, value in array|items %}
+ ...
+ {% endfor %}
+
+### `escape`, `e`
+
+The `escape` filter converts the characters `&`, `<`, `>`, `'`, and `"` in
+strings to HTML-safe sequences. Use this if you need to display text that
+might contain such characters in HTML.
+
+>**NOTE**
+>Internally, `escape` uses the PHP `htmlspecialchars` function.
+
+### `safe`
+
+The `safe` filter marks the value as safe which means that in an environment
+with automatic escaping enabled this variable will not be escaped.
+
+ [twig]
+ {% autoescape on }
+ {{ var|safe }} {# var won't be escaped #}
+ {% autoescape off %}
--- /dev/null
+Twig for Developers
+===================
+
+This chapter describes the API to Twig and not the template language. It will
+be most useful as reference to those implementing the template interface to
+the application and not those who are creating Twig templates.
+
+Basics
+------
+
+Twig uses a central object called the **environment** (of class
+`Twig_Environment`). Instances of this class are used to store the
+configuration and extensions, and are used to load templates from the file
+system or other locations.
+
+Most applications will create one `Twig_Environment` object on application
+initialization and use that to load templates. In some cases it's however
+useful to have multiple environments side by side, if different configurations
+are in use.
+
+The simplest way to configure Twig to load templates for your application
+looks roughly like this:
+
+ [php]
+ require_once '/path/to/lib/Twig/Autoloader.php';
+ Twig_Autoloader::register();
+
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
+ $twig = new Twig_Environment($loader);
+
+This will create a template environment with the default settings and a loader
+that looks up the templates in the `/path/to/templates/` folder. Different
+loaders are available and you can also write your own if you want to load
+templates from a database or other resources.
+
+To load a template from this environment you just have to call the
+`loadTemplate()` method which then returns a `Twig_Template` instance:
+
+ [php]
+ $template = $twig->loadTemplate('index.html');
+
+To render the template with some variables, call the `render()` method:
+
+ [php]
+ echo $template->render(array('the' => 'variables', 'go' => 'here'));
+
+>**NOTE**
+>The `display()` method is a shortcut to output the template directly.
+
+Environment Options
+-------------------
+
+When creating a new `Twig_Environment` instance, you can pass an array of
+options as the constructor second argument:
+
+ [php]
+ $twig = new Twig_Environment($loader, array('debug' => true));
+
+The following options are available:
+
+ * `debug`: When set to `true`, the generated templates have a `__toString()`
+ method that you can use to display the generated nodes (default to
+ `false`).
+
+ * `trim_blocks`: Mimicks the behavior of PHP by removing the newline that
+ follows instructions if present (default to `false`).
+
+ * `charset`: The charset used by the templates (default to `utf-8`).
+
+ * `base_template_class`: The base template class to use for generated
+ templates (default to `Twig_Template`).
+
+Loaders
+-------
+
+Loaders are responsible for loading templates from a resource such as the file
+system. The environment will keep the compiled templates in memory.
+
+### Built-in Loaders
+
+Here a list of the built-in loaders Twig provides:
+
+ * `Twig_Loader_Filesystem`: Loads templates from the file system. This loader
+ can find templates in folders on the file system and is the preferred way
+ to load them.
+
+ * `Twig_Loader_String`: Loads templates from a string. It's a dummy loader as
+ you pass it the source code directly.
+
+ * `Twig_Loader_Array`: Loads a template from a PHP array. It's passed an
+ array of strings bound to template names. This loader is useful for unit
+ testing.
+
+### Create your own Loader
+
+All loaders implement the `Twig_LoaderInterface`:
+
+ [php]
+ interface Twig_LoaderInterface
+ {
+ /**
+ * Loads a template by name.
+ *
+ * @param string $name The template name
+ *
+ * @return string The class name of the compiled template
+ */
+ public function load($name);
+ }
+
+But if you want to create your own loader, you'd better inherit from the
+`Twig_Loader` class, which already provides a lot of useful features. In this
+case, you just need to implement the `getSource()` method. As an example, here
+is how the built-in `Twig_Loader_String` reads:
+
+ [php]
+ class Twig_Loader_String extends Twig_Loader
+ {
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name string The name of the template to load
+ *
+ * @return array An array consisting of the source code as the first element,
+ * and the last modification time as the second one
+ * or false if it's not relevant
+ */
+ public function getSource($name)
+ {
+ return array($name, false);
+ }
+ }
+
+Using Extensions
+----------------
+
+Twig extensions are packages that adds new features to Twig. Using an
+extension is as simple as using the `addExtension()` method:
+
+ [php]
+ $twig->addExtension('Escaper');
+
+Twig comes bundled with three extensions:
+
+ * *Core*: Defines all the core features of Twig and is automatically
+ registered when you create a new environment.
+
+ * *Escaper*: Adds automatic output-escaping and the possibility to
+ escape/unescape blocks of code.
+
+ * *Sandbox*: Adds a sandbox mode to the default Twig environment, making it
+ safe to evaluated untrusted code.
+
+Built-in Extensions
+-------------------
+
+This section describes the features added by the built-in extensions.
+
+>**TIP**
+>Read the chapter about extending Twig to learn how to create your own
+>extensions.
+
+### Core Extension
+
+The `core` extension defines all the core features of Twig:
+
+ * Tags:
+
+ * `for`
+ * `if`
+ * `extends`
+ * `include`
+ * `block`
+ * `parent`
+ * `display`
+ * `filter`
+
+ * Filters:
+
+ * `date`
+ * `format`
+ * `even`
+ * `odd`
+ * `urlencode`
+ * `title`
+ * `capitalize`
+ * `upper`
+ * `lower`
+ * `striptags`
+ * `join`
+ * `reverse`
+ * `length`
+ * `sort`
+ * `default`
+ * `keys`
+ * `items`
+ * `escape`
+ * `e`
+
+The core extension does not need to be added to the Twig environment, as it is
+registered by default.
+
+### Escaper Extension
+
+The `escaper` extension adds automatic output escaping to Twig. It defines a
+new tag, `autoescape`, and a new filter, `safe`.
+
+When creating the escaper extension, you can switch on or off the global
+output escaping strategy:
+
+ [php]
+ $escaper = new Twig_Extension_Escaper(true);
+ $twig->addExtension($escaper);
+
+If set to `true`, all variables in templates are escaped, except those using
+the `safe` filter:
+
+ [twig]
+ {{ article.to_html|safe }}
+
+You can also change the escaping mode locally by using the `autoescape` tag:
+
+ [twig]
+ {% autoescape on %}
+ {% var %}
+ {% var|safe %} {# var won't be escaped #}
+ {% var|escape %} {# var won't be doubled-escaped #}
+ {% endautoescape %}
+
+### Sandbox Extension
+
+The `sandbox` extension can be used to evaluate untrusted code. Access to
+unsafe attributes and methods is prohibited. The sandbox security is managed
+by a policy instance. By default, Twig comes with one policy class:
+`Twig_Sandbox_SecurityPolicy`. This class allows you to white-list some tags,
+filters, and methods:
+
+ [php]
+ $tags = array('if');
+ $filters = array('upper');
+ $methods = array(
+ 'Article' => array('getTitle', 'getBody'),
+ );
+ $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods);
+
+With the previous configuration, the security policy will only allow usage of
+the `if` tag, and the `upper` filter. Moreover, the templates will only be
+able to call the `getTitle()` and `getBody()` methods on `Article` objects.
+Everything else won't be allowed and will generate a
+`Twig_Sandbox_SecurityError` exception.
+
+The policy object is the first argument of the sandbox constructor:
+
+ [php]
+ $sandbox = new Twig_Extension_Sandbox($policy);
+ $twig->addExtension($sandbox);
+
+By default, the sandbox mode is disabled and should be enabled when including
+untrusted templates:
+
+ [php]
+ {% include "user.html" sandboxed %}
+
+You can sandbox all templates by passing `true` as the second argument of the
+extension constructor:
+
+ [php]
+ $sandbox = new Twig_Extension_Sandbox($policy, true);
+
+Exceptions
+----------
+
+Twig can throw exceptions:
+
+ * `Twig_Error`: The base exception for all template errors.
+
+ * `Twig_SyntaxError`: Thrown to tell the user that there is a problem with
+ the template syntax.
+
+ * `Twig_RuntimeError`: Thrown when an error occurs at runtime (when a filter
+ does not exist for instance).
+
+ * `Twig_Sandbox_SecurityError`: Thrown when an unallowed tag, filter, or
+ method is called in a sandboxed template.
--- /dev/null
+Extending Twig
+==============
+
+Twig supports extensions that can add extra tags, filters, or even extend the
+parser itself with node transformer classes. The main motivation for writing
+an extension is to move often used code into a reusable class like adding
+support for internationalization.
+
+Most of the time, it is useful to create a single extension for your project,
+to host all the specific tags and filters you want to add to Twig.
+
+Anatomy of an Extension
+-----------------------
+
+An extension is a class that implements the `Twig_ExtensionInterface`:
+
+ [php]
+ interface Twig_ExtensionInterface
+ {
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime();
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers();
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers();
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters();
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName();
+ }
+
+Instead of you implementing the whole interface, your extension class can
+inherit from the `Twig_Extension` class, which provides empty implementations
+of all the above methods to keep your extension clean.
+
+>**TIP**
+>The bundled extensions are great examples of how extensions work.
+
+Defining new Filters
+--------------------
+
+The most common element you will want to add to Twig is filters. A filter is
+just a regular PHP callable that takes the left side of the filter as first
+argument and the arguments passed to the filter as extra arguments.
+
+Let's create a filter, named `rot13`, which returns the
+[rot13](http://www.php.net/manual/en/function.str-rot13.php) transformation of
+a string:
+
+ [twig]
+ {{ "Twig"|rot13 }}
+
+ {# should displays Gjvt #}
+
+Here is the simplest extension class you can create to add this filter:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array('str_rot13', false),
+ );
+ }
+
+ public function getName()
+ {
+ return 'project';
+ }
+ }
+
+Registering the new extension is like registering core extensions:
+
+ [php]
+ $twig->addExtension(new Project_Twig_Extension());
+
+You can of course use any valid PHP callable, like a method call:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array(array($this, 'computeRot13'), false),
+ );
+ }
+
+ public function computeRot13($string)
+ {
+ return str_rot13($string);
+ }
+
+ // ...
+ }
+
+Filters can also be passed the current environment. You might have noticed
+that a filter is defined by a callable and a Boolean. If you change the
+Boolean to `true`, Twig will pass the current environment as the first
+argument to the filter call:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getFilters()
+ {
+ return array(
+ 'rot13' => array(array($this, 'computeRot13'), true),
+ );
+ }
+
+ public function computeRot13(Twig_Environment $env, $string)
+ {
+ // get the current charset for instance
+ $charset = $env->getCharset();
+
+ return str_rot13($string);
+ }
+
+ // ...
+ }
+
+Defining new Tags
+-----------------
+
+One of the most exiting feature of a template engine like Twig is the
+possibility to define new language constructs.
+
+Let's create a simple `set` tag that allows the definition of simple variables
+from within a template. The tag can be used like follows:
+
+ [twig]
+ {% set name "value" %}
+
+ {{ name }}
+
+ {# should output value #}
+
+First, we need to create a `Twig_TokenParser` class which will be able to
+parse this new language construct:
+
+ [php]
+ class Project_Twig_Set_TokenParser extends Twig_TokenParser
+ {
+ // ...
+ }
+
+Of course, we need to register this token parser in our extension class:
+
+ [php]
+ class Project_Twig_Extension extends Twig_Extension
+ {
+ public function getTokenParsers()
+ {
+ return array(new Project_Twig_Set_TokenParser());
+ }
+
+ // ...
+ }
+
+Now, let's see the actual code of the token parser class:
+
+ [php]
+ class Project_Twig_Set_TokenParser extends Twig_TokenParser
+ {
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ $value = $this->parser->getExpressionParser()->parseExpression();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Project_Twig_Set_Node($name, $value, $lineno, $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'set';
+ }
+ }
+
+The `getTag()` method must return the tag we want to parse, here `set`. The
+`parse()` method is invoked whenever the parser encounters a `set` tag. It
+should return a `Twig_Node` instance that represents the node. The parsing
+process is simplified thanks to a bunch of methods you can call from the token
+stream (`$this->parser->getStream()`):
+
+ * `test()`: Tests the type and optionally the value of the next token and
+ returns it.
+
+ * `expect()`: Expects a token and returns it (like `test()`) or throw a
+ syntax error if not found.
+
+ * `look()`: Looks a the next token. This is how you can have a look at the
+ next token without consume it.
+
+Parsing expressions is done by calling the `parseExpression()` like we did for
+the `set` tag.
+
+>**TIP**
+>Reading the existing `TokenParser` classes is the best way to learn all the
+>nitty-gritty details of the parsing process.
+
+The `Project_Twig_Set_Node` class itself is rather simple:
+
+ [php]
+ class Project_Twig_Set_Node extends Twig_Node
+ {
+ protected $name;
+ protected $value;
+
+ public function __construct($name, Twig_Node_Expression $value, $lineno)
+ {
+ parent::__construct($lineno);
+
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('$context[\''.$this->name.'\'] = ')
+ ->subcompile($this->value)
+ ->raw(";\n")
+ ;
+ }
+ }
+
+The compiler implements a fluid interface and provides methods that helps the
+developer generate beautiful and readable PHP code:
+
+ * `subcompile()`: Compiles a node.
+
+ * `raw()`: Writes the given string as is.
+
+ * `write()`: Writes the given string by adding indentation at the beginning
+ of each line.
+
+ * `string()`: Writes a quoted string.
+
+ * `repr()`: Writes a PHP representation of a given value (see `Twig_Node_For`
+ for a usage example).
+
+ * `pushContext()`: Pushes the current context on the stack (see
+ `Twig_Node_For` for a usage example).
+
+ * `popContext()`: Pops a context from the stack (see `Twig_Node_For` for a
+ usage example).
+
+ * `addDebugInfo()`: Adds the line of the original template file related to
+ the current node as a comment.
+
+ * `indent()`: Indents the generated code (see `Twig_Node_Block` for a usage
+ example).
+
+ * `outdent()`: Outdents the generated code (see `Twig_Node_Block` for a usage
+ example).
+
+Creating a Node Transformer
+---------------------------
+
+To be written...
--- /dev/null
+Hacking Twig
+============
+
+Twig is very extensible and you can easily hack it. Keep in mind that you
+should probably try to create an extension before hacking the core, as most
+features and enhancements can be done with extensions. This chapter is also
+useful for people who want to understand how Twig works under the hood.
+
+How Twig works?
+---------------
+
+The rendering of a Twig template can be summarized into four key steps:
+
+ * **Load** the template: If the template is already compiled, load it and go
+ to the *evaluation* step, otherwise:
+
+ * First, the **lexer** tokenizes the template source code into small pieces
+ for easier processing;
+
+ * Then, the **parser** converts the token stream into a meaningful tree
+ of nodes (the Abstract Syntax Tree);
+
+ * Eventually, the *compiler* transforms the AST into PHP code;
+
+ * **Evaluate** the template: It basically means calling the `display()`
+ method of the compiled template and passing it the context.
+
+The Lexer
+---------
+
+The Twig lexer goal is to tokenize a source code into a token stream (each
+token is of class `Token`, and the stream is an instance of
+`Twig_TokenStream`). The default lexer recognizes nine different token types:
+
+ * `Twig_Token::TEXT_TYPE`
+ * `Twig_Token::BLOCK_START_TYPE`
+ * `Twig_Token::VAR_START_TYPE`
+ * `Twig_Token::BLOCK_END_TYPE`
+ * `Twig_Token::VAR_END_TYPE`
+ * `Twig_Token::NAME_TYPE`
+ * `Twig_Token::NUMBER_TYPE`
+ * `Twig_Token::STRING_TYPE`
+ * `Twig_Token::OPERATOR_TYPE`
+ * `Twig_Token::EOF_TYPE`
+
+You can manually convert a source code into a token stream by calling the
+`tokenize()` of an environment:
+
+ [php]
+ $stream = $twig->tokenize($source, $identifier);
+
+As the stream has a `__toString()` method, you can have a textual
+representation of it by echoing the object:
+
+ [php]
+ echo $stream."\n";
+
+Here is the output for the `Hello {{ name }}` template:
+
+ [txt]
+ TEXT_TYPE(Hello )
+ VAR_START_TYPE()
+ NAME_TYPE(name)
+ VAR_END_TYPE()
+ EOF_TYPE()
+
+You can change the default lexer use by Twig (`Twig_Lexer`) by calling the
+`setLexer()` method:
+
+ [php]
+ $twig->setLexer($lexer);
+
+Lexer classes must implement the `Twig_LexerInterface`:
+
+ [php]
+ interface Twig_LexerInterface
+ {
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ public function tokenize($code, $filename = 'n/a');
+ }
+
+The Parser
+----------
+
+The parser converts the token stream into an AST (Abstract Syntax Tree), or a
+node tree (of class `Twig_Node_Module`). The core extension defines the basic
+nodes like: `for`, `if`, ... and the expression nodes.
+
+You can manually convert a token stream into a node tree by calling the
+`parse()` method of an environment:
+
+ [php]
+ $nodes = $twig->parse($stream);
+
+Echoing the node object gives you a nice representation of the tree:
+
+ [php]
+ echo $nodes."\n";
+
+Here is the output for the `Hello {{ name }}` template:
+
+ [txt]
+ Twig_Node_Module(
+ Twig_Node_Text(Hello )
+ Twig_Node_Print(
+ Twig_Node_Expression_Name(name)
+ )
+ )
+
+The default parser (`Twig_TokenParser`) can be also changed by calling the
+`setParser()` method:
+
+ [php]
+ $twig->setParser($parser);
+
+All Twig parsers must implement the `Twig_ParserInterface`:
+
+ [php]
+ interface Twig_ParserInterface
+ {
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ public function parser(Twig_TokenStream $code);
+ }
+
+The Compiler
+------------
+
+The last step is done by the compiler. It takes a node tree as an input and
+generates PHP code usable for runtime execution of the templates. The default
+compiler generates PHP classes to ease the implementation of the template
+inheritance feature.
+
+You can call the compiler by hand with the `compile()` method of an
+environment:
+
+ [php]
+ $php = $twig->compile($nodes);
+
+The `compile()` method returns the PHP source code representing the node.
+
+The generated template for a `Hello {{ name }}` template reads as follows:
+
+ [php]
+ /* Hello {{ name }} */
+ class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
+ {
+ public function display($context)
+ {
+ $this->env->initRuntime();
+
+ // line 1
+ echo "Hello ";
+ echo (isset($context['name']) ? $context['name'] : null);
+ }
+ }
+
+As for the lexer and the parser, the default compiler (`Twig_Compiler`) can be
+changed by calling the `setCompiler()` method:
+
+ [php]
+ $twig->setCompiler($compiler);
+
+All Twig compilers must implement the `Twig_CompilerInterface`:
+
+ [php]
+ interface Twig_CompilerInterface
+ {
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node);
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource();
+ }
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Autoloads Twig classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Autoloader
+{
+ /**
+ * Registers Twig_Autoloader as an SPL autoloader.
+ */
+ static public function register()
+ {
+ ini_set('unserialize_callback_func', 'spl_autoload_call');
+ spl_autoload_register(array(new self, 'autoload'));
+ }
+
+ /**
+ * Handles autoloading of classes.
+ *
+ * @param string $class A class name.
+ *
+ * @return boolean Returns true if the class has been loaded
+ */
+ public function autoload($class)
+ {
+ if (0 !== strpos($class, 'Twig'))
+ {
+ return false;
+ }
+
+ require dirname(__FILE__).'/../'.str_replace('_', '/', $class).'.php';
+
+ return true;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Compiles a node to PHP code.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Compiler implements Twig_CompilerInterface
+{
+ protected $lastLine;
+ protected $source;
+ protected $indentation;
+ protected $env;
+
+ /**
+ * Constructor.
+ *
+ * @param Twig_Environment $env The twig environment instance
+ */
+ public function __construct(Twig_Environment $env = null)
+ {
+ $this->env = $env;
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource()
+ {
+ return $this->source;
+ }
+
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node)
+ {
+ $this->lastLine = null;
+ $this->source = '';
+ $this->indentation = 0;
+
+ $node->compile($this);
+
+ return $this;
+ }
+
+ public function subcompile(Twig_Node $node)
+ {
+ $node->compile($this);
+
+ return $this;
+ }
+
+ /**
+ * Adds a raw string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function raw($string)
+ {
+ $this->source .= $string;
+
+ return $this;
+ }
+
+ /**
+ * Writes a string to the compiled code by adding indentation.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function write()
+ {
+ $strings = func_get_args();
+ foreach ($strings as $string)
+ {
+ $this->source .= str_repeat(' ', $this->indentation * 2).$string;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds a quoted string to the compiled code.
+ *
+ * @param string $string The string
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function string($value)
+ {
+ $this->source .= sprintf('"%s"', addcslashes($value, "\t\""));
+
+ return $this;
+ }
+
+ /**
+ * Returns a PHP representation of a given value.
+ *
+ * @param mixed $value The value to convert
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function repr($value)
+ {
+ if (is_int($value) || is_float($value))
+ {
+ $this->raw($value);
+ }
+ else if (is_null($value))
+ {
+ $this->raw('null');
+ }
+ else if (is_bool($value))
+ {
+ $this->raw($value ? 'true' : 'false');
+ }
+ else if (is_array($value))
+ {
+ $this->raw('array(');
+ $i = 0;
+ foreach ($value as $key => $value)
+ {
+ if ($i++)
+ {
+ $this->raw(', ');
+ }
+ $this->repr($key);
+ $this->raw(' => ');
+ $this->repr($value);
+ }
+ $this->raw(')');
+ }
+ else
+ {
+ $this->string($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Pushes the current context on the stack.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function pushContext()
+ {
+ $this->write('$context[\'_parent\'] = $context;'."\n");
+
+ return $this;
+ }
+
+ /**
+ * Pops a context from the stack.
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function popContext()
+ {
+ $this->write('$context = $context[\'_parent\'];'."\n");
+
+ return $this;
+ }
+
+ /**
+ * Adds debugging information.
+ *
+ * @param Twig_Node $node The related twig node
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function addDebugInfo(Twig_Node $node)
+ {
+ if ($node->getLine() != $this->lastLine)
+ {
+ $this->lastLine = $node->getLine();
+ $this->write("// line {$node->getLine()}\n");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Indents the generated code.
+ *
+ * @param integer $indent The number of indentation to add
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function indent($step = 1)
+ {
+ $this->indentation += $step;
+
+ return $this;
+ }
+
+ /**
+ * Outdents the generated code.
+ *
+ * @param integer $indent The number of indentation to remove
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function outdent($step = 1)
+ {
+ $this->indentation -= $step;
+
+ return $this;
+ }
+
+ /**
+ * Returns the environment instance related to this compiler.
+ *
+ * @return Twig_Environment The environment instance
+ */
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by compiler classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_CompilerInterface
+{
+ /**
+ * Compiles a node.
+ *
+ * @param Twig_Node $node The node to compile
+ *
+ * @return Twig_Compiler The current compiler instance
+ */
+ public function compile(Twig_Node $node);
+
+ /**
+ * Gets the current PHP code after compilation.
+ *
+ * @return string The PHP code
+ */
+ public function getSource();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Twig_Environment
+{
+ const VERSION = '0.9-DEV';
+
+ protected $charset;
+ protected $loader;
+ protected $trimBlocks;
+ protected $debug;
+ protected $lexer;
+ protected $parser;
+ protected $compiler;
+ protected $baseTemplateClass;
+ protected $extensions;
+ protected $parsers;
+ protected $transformers;
+ protected $filters;
+
+ public function __construct(Twig_LoaderInterface $loader = null, $options = array())
+ {
+ if (null !== $loader)
+ {
+ $this->setLoader($loader);
+ }
+
+ $this->debug = isset($options['debug']) ? (bool) $options['debug'] : false;
+ $this->trimBlocks = isset($options['trim_blocks']) ? (bool) $options['trim_blocks'] : false;
+ $this->charset = isset($options['charset']) ? (bool) $options['charset'] : 'UTF-8';
+ $this->baseTemplateClass = isset($options['base_template_class']) ? (bool) $options['base_template_class'] : 'Twig_Template';
+ $this->extensions = array(new Twig_Extension_Core());
+ }
+
+ public function getBaseTemplateClass()
+ {
+ return $this->baseTemplateClass;
+ }
+
+ public function setBaseTemplateClass($class)
+ {
+ $this->baseTemplateClass = $class;
+ }
+
+ public function enableDebug()
+ {
+ $this->debug = true;
+ }
+
+ public function disableDebug()
+ {
+ $this->debug = false;
+ }
+
+ public function isDebug()
+ {
+ return $this->debug;
+ }
+
+ public function getTrimBlocks()
+ {
+ return $this->trimBlocks;
+ }
+
+ public function setTrimBlocks($bool)
+ {
+ $this->trimBlocks = (bool) $bool;
+ }
+
+ public function loadTemplate($name)
+ {
+ $cls = $this->getLoader()->load($name, $this);
+
+ return new $cls($this);
+ }
+
+ public function getLexer()
+ {
+ if (null === $this->lexer)
+ {
+ $this->lexer = new Twig_Lexer($this);
+ }
+
+ return $this->lexer;
+ }
+
+ public function setLexer(Twig_LexerInterface $lexer)
+ {
+ $this->lexer = $lexer;
+ $lexer->setEnvironment($this);
+ }
+
+ public function tokenize($source, $name = null)
+ {
+ return $this->getLexer()->tokenize($source, null === $name ? $source : $name);
+ }
+
+ public function getParser()
+ {
+ if (null === $this->parser)
+ {
+ $this->parser = new Twig_Parser($this);
+ }
+
+ return $this->parser;
+ }
+
+ public function setParser(Twig_ParserInterface $parser)
+ {
+ $this->parser = $parser;
+ $parser->setEnvironment($this);
+ }
+
+ public function parse(Twig_TokenStream $tokens)
+ {
+ return $this->getParser()->parse($tokens);
+ }
+
+ public function getCompiler()
+ {
+ if (null === $this->compiler)
+ {
+ $this->compiler = new Twig_Compiler($this);
+ }
+
+ return $this->compiler;
+ }
+
+ public function setCompiler(Twig_CompilerInterface $compiler)
+ {
+ $this->compiler = $compiler;
+ $compiler->setEnvironment($this);
+ }
+
+ public function compile(Twig_Node $node)
+ {
+ return $this->getCompiler()->compile($node)->getSource();
+ }
+
+ public function setLoader(Twig_LoaderInterface $loader)
+ {
+ $this->loader = $loader;
+ $loader->setEnvironment($this);
+ }
+
+ public function getLoader()
+ {
+ return $this->loader;
+ }
+
+ public function setCharset($charset)
+ {
+ $this->charset = $charset;
+ }
+
+ public function getCharset()
+ {
+ return $this->charset;
+ }
+
+ public function initRuntime()
+ {
+ foreach ($this->getExtensions() as $extension)
+ {
+ $extension->initRuntime();
+ }
+ }
+
+ public function hasExtension($name)
+ {
+ return isset($this->extensions[$name]);
+ }
+
+ public function getExtension($name)
+ {
+ return $this->extensions[$name];
+ }
+
+ public function addExtension(Twig_ExtensionInterface $extension)
+ {
+ $this->extensions[$extension->getName()] = $extension;
+ }
+
+ public function removeExtension($name)
+ {
+ unset($this->extensions[$name]);
+ }
+
+ public function setExtensions(array $extensions)
+ {
+ foreach ($extensions as $extension)
+ {
+ $this->setExtension($extension);
+ }
+ }
+
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+ public function getTokenParsers()
+ {
+ if (null === $this->parsers)
+ {
+ $this->parsers = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->parsers = array_merge($this->parsers, $extension->getTokenParsers());
+ }
+ }
+
+ return $this->parsers;
+ }
+
+ public function getNodeTransformers()
+ {
+ if (null === $this->transformers)
+ {
+ $this->transformers = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->transformers = array_merge($this->transformers, $extension->getNodeTransformers());
+ }
+ }
+
+ return $this->transformers;
+ }
+
+ public function getFilters()
+ {
+ if (null === $this->filters)
+ {
+ $this->filters = array();
+ foreach ($this->getExtensions() as $extension)
+ {
+ $this->filters = array_merge($this->filters, $extension->getFilters());
+ }
+ }
+
+ return $this->filters;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Twig base exception.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Error extends Exception
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Parses expressions.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_ExpressionParser
+{
+ protected $parser;
+
+ public function __construct(Twig_Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ public function parseExpression()
+ {
+ return $this->parseConditionalExpression();
+ }
+
+ public function parseConditionalExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $expr1 = $this->parseOrExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '?'))
+ {
+ $this->parser->getStream()->next();
+ $expr2 = $this->parseOrExpression();
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ':');
+ $expr3 = $this->parseConditionalExpression();
+ $expr1 = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $expr1;
+ }
+
+ public function parseOrExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseAndExpression();
+ while ($this->parser->getStream()->test('or'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseAndExpression();
+ $left = new Twig_Node_Expression_Binary_Or($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseAndExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseCompareExpression();
+ while ($this->parser->getStream()->test('and'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseCompareExpression();
+ $left = new Twig_Node_Expression_Binary_And($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseCompareExpression()
+ {
+ static $operators = array('==', '!=', '<', '>', '>=', '<=');
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $expr = $this->parseAddExpression();
+ $ops = array();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, $operators))
+ {
+ $ops[] = array($this->parser->getStream()->next()->getValue(), $this->parseAddExpression());
+ }
+
+ if (empty($ops))
+ {
+ return $expr;
+ }
+
+ return new Twig_Node_Expression_Compare($expr, $ops, $lineno);
+ }
+
+ public function parseAddExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseSubExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '+'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseSubExpression();
+ $left = new Twig_Node_Expression_Binary_Add($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseSubExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseConcatExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '-'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseConcatExpression();
+ $left = new Twig_Node_Expression_Binary_Sub($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseConcatExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseMulExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '~'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseMulExpression();
+ $left = new Twig_Node_Expression_Binary_Concat($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseMulExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseDivExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '*'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseDivExpression();
+ $left = new Twig_Node_Expression_Binary_Mul($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseDivExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseModExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '/'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseModExpression();
+ $left = new Twig_Node_Expression_Binary_Div($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseModExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $left = $this->parseUnaryExpression();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '%'))
+ {
+ $this->parser->getStream()->next();
+ $right = $this->parseUnaryExpression();
+ $left = new Twig_Node_Expression_Binary_Mod($left, $right, $lineno);
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ }
+
+ return $left;
+ }
+
+ public function parseUnaryExpression()
+ {
+ if ($this->parser->getStream()->test('not'))
+ {
+ return $this->parseNotExpression();
+ }
+ if ($this->parser->getCurrentToken()->getType() == Twig_Token::OPERATOR_TYPE)
+ {
+ switch ($this->parser->getCurrentToken()->getValue())
+ {
+ case '-':
+ return $this->parseNegExpression();
+ case '+':
+ return $this->parsePosExpression();
+ }
+ }
+
+ return $this->parsePrimaryExpression();
+ }
+
+ public function parseNotExpression()
+ {
+ $token = $this->parser->getStream()->next();
+ $node = $this->parseUnaryExpression();
+
+ return new Twig_Node_Expression_Unary_Not($node, $token->getLine());
+ }
+
+ public function parseNegExpression()
+ {
+ $token = $this->parser->getStream()->next();
+ $node = $this->parseUnaryExpression();
+
+ return new Twig_Node_Expression_Unary_Neg($node, $token->getLine());
+ }
+
+ public function parsePosExpression()
+ {
+ $token = $this->parser->getStream()->next();
+ $node = $this->parseUnaryExpression();
+
+ return new Twig_Node_Expression_Unary_Pos($node, $token->getLine());
+ }
+
+ public function parsePrimaryExpression($assignment = false)
+ {
+ $token = $this->parser->getCurrentToken();
+ switch ($token->getType())
+ {
+ case Twig_Token::NAME_TYPE:
+ $this->parser->getStream()->next();
+ switch ($token->getValue())
+ {
+ case 'true':
+ $node = new Twig_Node_Expression_Constant(true, $token->getLine());
+ break;
+
+ case 'false':
+ $node = new Twig_Node_Expression_Constant(false, $token->getLine());
+ break;
+
+ case 'none':
+ $node = new Twig_Node_Expression_Constant(null, $token->getLine());
+ break;
+
+ default:
+ $cls = $assignment ? 'Twig_Node_Expression_AssignName' : 'Twig_Node_Expression_Name';
+ $node = new $cls($token->getValue(), $token->getLine());
+ }
+ break;
+
+ case Twig_Token::NUMBER_TYPE:
+ case Twig_Token::STRING_TYPE:
+ $this->parser->getStream()->next();
+ $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
+ break;
+
+ default:
+ if ($token->test(Twig_Token::OPERATOR_TYPE, '('))
+ {
+ $this->parser->getStream()->next();
+ $node = $this->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ')');
+ }
+ else
+ {
+ throw new Twig_SyntaxError('Unexpected token', $token->getLine());
+ }
+ }
+ if (!$assignment)
+ {
+ $node = $this->parsePostfixExpression($node);
+ }
+
+ return $node;
+ }
+
+ public function parsePostfixExpression($node)
+ {
+ $stop = false;
+ while (!$stop && $this->parser->getCurrentToken()->getType() == Twig_Token::OPERATOR_TYPE)
+ {
+ switch ($this->parser->getCurrentToken()->getValue())
+ {
+ case '.':
+ case '[':
+ $node = $this->parseSubscriptExpression($node);
+ break;
+
+ case '|':
+ $node = $this->parseFilterExpression($node);
+ break;
+
+ default:
+ $stop = true;
+ break;
+ }
+ }
+
+ return $node;
+ }
+
+ public function parseSubscriptExpression($node)
+ {
+ $token = $this->parser->getStream()->next();
+ $lineno = $token->getLine();
+ if ($token->getValue() == '.')
+ {
+ $token = $this->parser->getStream()->next();
+ if ($token->getType() == Twig_Token::NAME_TYPE || $token->getType() == Twig_Token::NUMBER_TYPE)
+ {
+ $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
+ }
+ else
+ {
+ throw new Twig_SyntaxError('Expected name or number', $lineno);
+ }
+ }
+ else
+ {
+ $arg = $this->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ']');
+ }
+
+ return new Twig_Node_Expression_GetAttr($node, $arg, $lineno, $token->getValue());
+ }
+
+ public function parseFilterExpression($node)
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $filters = array();
+ while ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '|'))
+ {
+ $this->parser->getStream()->next();
+ $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
+ $args = array();
+ if ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '('))
+ {
+ $this->parser->getStream()->next();
+ while (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ')'))
+ {
+ if (!empty($args))
+ {
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ',');
+ }
+ $args[] = $this->parseExpression();
+ }
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ')');
+ }
+ $filters[] = array($token->getValue(), $args);
+ }
+
+ return new Twig_Node_Expression_Filter($node, $filters, $lineno);
+ }
+
+ public function parseAssignmentExpression()
+ {
+ $lineno = $this->parser->getCurrentToken()->getLine();
+ $targets = array();
+ $is_multitarget = false;
+ while (true)
+ {
+ if (!empty($targets))
+ {
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, ',');
+ }
+ if ($this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ')') ||
+ $this->parser->getStream()->test(Twig_Token::VAR_END_TYPE) ||
+ $this->parser->getStream()->test(Twig_Token::BLOCK_END_TYPE) ||
+ $this->parser->getStream()->test('in'))
+ {
+ break;
+ }
+ $targets[] = $this->parsePrimaryExpression(true);
+ if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, ','))
+ {
+ break;
+ }
+ $is_multitarget = true;
+ }
+ if (!$is_multitarget && count($targets) == 1)
+ {
+ return array(false, $targets[0]);
+ }
+
+ return array(true, $targets);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_Extension implements Twig_ExtensionInterface
+{
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime()
+ {
+ }
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers()
+ {
+ return array();
+ }
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers()
+ {
+ return array();
+ }
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters()
+ {
+ return array();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_Core extends Twig_Extension
+{
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime()
+ {
+ require_once dirname(__FILE__).'/../runtime.php';
+ require_once dirname(__FILE__).'/../runtime_for.php';
+ }
+
+ /**
+ * Returns the token parser instance to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers()
+ {
+ return array(
+ new Twig_TokenParser_For(),
+ new Twig_TokenParser_If(),
+ new Twig_TokenParser_Extends(),
+ new Twig_TokenParser_Include(),
+ new Twig_TokenParser_Block(),
+ new Twig_TokenParser_Parent(),
+ new Twig_TokenParser_Display(),
+ new Twig_TokenParser_Filter(),
+ );
+ }
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers()
+ {
+ return array(new Twig_NodeTransformer_Filter());
+ }
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters()
+ {
+ $filters = array(
+ // formatting filters
+ 'date' => array('twig_date_format_filter', false),
+ 'format' => array('sprintf', false),
+
+ // numbers
+ 'even' => array('twig_is_even_filter', false),
+ 'odd' => array('twig_is_odd_filter', false),
+
+ // encoding
+ 'urlencode' => array('twig_urlencode_filter', false),
+
+ // string filters
+ 'title' => array('twig_title_string_filter', true),
+ 'capitalize' => array('twig_capitalize_string_filter', true),
+ 'upper' => array('strtoupper', false),
+ 'lower' => array('strtolower', false),
+ 'striptags' => array('strip_tags', false),
+
+ // array helpers
+ 'join' => array('twig_join_filter', false),
+ 'reverse' => array('twig_reverse_filter', false),
+ 'length' => array('twig_length_filter', false),
+ 'sort' => array('twig_sort_filter', false),
+
+ // iteration and runtime
+ 'default' => array('twig_default_filter', false),
+ 'keys' => array('twig_get_array_keys_filter', false),
+ 'items' => array('twig_get_array_items_filter', false),
+
+ // escaping
+ 'escape' => array('twig_escape_filter', true),
+ 'e' => array('twig_escape_filter', true),
+ );
+
+ if (function_exists('mb_get_info'))
+ {
+ $filters['upper'] = array('twig_upper_filter', true);
+ $filters['lower'] = array('twig_lower_filter', true);
+ }
+
+ return $filters;
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'core';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_Escaper extends Twig_Extension
+{
+ protected $autoescape;
+
+ public function __construct($autoescape = true)
+ {
+ $this->autoescape = $autoescape;
+ }
+
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime()
+ {
+ require_once dirname(__FILE__).'/../runtime_escaper.php';
+ }
+
+ /**
+ * Returns the token parser instance to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers()
+ {
+ return array(new Twig_TokenParser_AutoEscape());
+ }
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers()
+ {
+ return array(new Twig_NodeTransformer_Escaper());
+ }
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters()
+ {
+ return array(
+ 'safe' => array('twig_safe_filter', false),
+ );
+ }
+
+ public function isGlobal()
+ {
+ return $this->autoescape;
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'escaper';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_Macro extends Twig_Extension
+{
+ public function getTokenParsers()
+ {
+ return array(
+ new Twig_TokenParser_Macro(),
+ new Twig_TokenParser_Call(),
+ );
+ }
+
+ public function getName()
+ {
+ return 'macro';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Extension_Sandbox extends Twig_Extension
+{
+ protected $sandboxedGlobally;
+ protected $sandboxed;
+ protected $policy;
+
+ public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
+ {
+ $this->policy = $policy;
+ $this->sandboxedGlobally = $sandboxed;
+ }
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers()
+ {
+ return array(new Twig_NodeTransformer_Sandbox());
+ }
+
+ public function enableSandbox()
+ {
+ $this->sandboxed = true;
+ }
+
+ public function disableSandbox()
+ {
+ $this->sandboxed = false;
+ }
+
+ public function isSandboxed()
+ {
+ return $this->sandboxedGlobally || $this->sandboxed;
+ }
+
+ public function isSandboxedGlobally()
+ {
+ return $this->sandboxedGlobally;
+ }
+
+ public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy)
+ {
+ $this->policy = $policy;
+ }
+
+ public function getSecurityPolicy()
+ {
+ return $this->policy;
+ }
+
+ public function checkSecurity($tags, $filters)
+ {
+ if ($this->isSandboxed())
+ {
+ $this->policy->checkSecurity($tags, $filters);
+ }
+ }
+
+ public function checkMethodAllowed($obj, $method)
+ {
+ if ($this->isSandboxed())
+ {
+ $this->policy->checkMethodAllowed($obj, $method);
+ }
+ }
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName()
+ {
+ return 'sandbox';
+ }
+}
--- /dev/null
+<?php
+
+class Twig_Extension_Set extends Twig_Extension
+{
+ public function getTokenParsers()
+ {
+ return array(
+ new Twig_TokenParser_Set(),
+ );
+ }
+
+ public function getName()
+ {
+ return 'set';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by extension classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_ExtensionInterface
+{
+ /**
+ * Initializes the runtime environment.
+ *
+ * This is where you can load some file that contains filter functions for instance.
+ */
+ public function initRuntime();
+
+ /**
+ * Returns the token parser instances to add to the existing list.
+ *
+ * @return array An array of Twig_TokenParser instances
+ */
+ public function getTokenParsers();
+
+ /**
+ * Returns the node transformer instances to add to the existing list.
+ *
+ * @return array An array of Twig_NodeTransformer instances
+ */
+ public function getNodeTransformers();
+
+ /**
+ * Returns a list of filters to add to the existing list.
+ *
+ * @return array An array of filters
+ */
+ public function getFilters();
+
+ /**
+ * Returns the name of the extension.
+ *
+ * @return string The extension name
+ */
+ public function getName();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Lexes a template string.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Lexer implements Twig_LexerInterface
+{
+ protected $cursor;
+ protected $position;
+ protected $end;
+ protected $pushedBack;
+ protected $code;
+ protected $lineno;
+ protected $filename;
+ protected $env;
+
+ const TAG_COMMENT_START = '{#';
+ const TAG_COMMENT_END = '#}';
+
+ const TAG_BLOCK_START = '{%';
+ const TAG_BLOCK_END = '%}';
+
+ const TAG_VARIABLE_START = '{{';
+ const TAG_VARIABLE_END = '}}';
+
+ const POSITION_DATA = 0;
+ const POSITION_BLOCK = 1;
+ const POSITION_VAR = 2;
+
+ const REGEX_NAME = '/[A-Za-z_][A-Za-z0-9_]*/A';
+ const REGEX_NUMBER = '/[0-9]+(?:\.[0-9])?/A';
+ const REGEX_STRING = '/(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')/Asm';
+ const REGEX_OPERATOR = '/<=? | >=? | [!=]= | [(){}.,%*\/+~|-] | \[ | \] | \? | \:/Ax';
+
+ public function __construct(Twig_Environment $env = null)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ public function tokenize($code, $filename = 'n/a')
+ {
+ $this->code = preg_replace('/(\r\n|\r|\n)/', '\n', $code);
+ $this->filename = $filename;
+ $this->cursor = 0;
+ $this->lineno = 1;
+ $this->pushedBack = array();
+ $this->end = strlen($this->code);
+ $this->position = self::POSITION_DATA;
+
+ $tokens = array();
+ $end = false;
+ while (!$end)
+ {
+ $token = $this->nextToken();
+
+ $tokens[] = $token;
+
+ $end = $token->getType() === Twig_Token::EOF_TYPE;
+ }
+
+ return new Twig_TokenStream($tokens, $this->filename, $this->env->getTrimBlocks());
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * Parses the next token and returns it.
+ */
+ protected function nextToken()
+ {
+ // do we have tokens pushed back? get one
+ if (!empty($this->pushedBack))
+ {
+ return array_shift($this->pushedBack);
+ }
+
+ // have we reached the end of the code?
+ if ($this->cursor >= $this->end)
+ {
+ return new Twig_Token(Twig_Token::EOF_TYPE, '', $this->lineno);
+ }
+
+ // otherwise dispatch to the lexing functions depending
+ // on our current position in the code.
+ switch ($this->position)
+ {
+ case self::POSITION_DATA:
+ $tokens = $this->lexData();
+ break;
+
+ case self::POSITION_BLOCK:
+ $tokens = $this->lexBlock();
+ break;
+
+ case self::POSITION_VAR:
+ $tokens = $this->lexVar();
+ break;
+ }
+
+ // if the return value is not an array it's a token
+ if (!is_array($tokens))
+ {
+ return $tokens;
+ }
+ // empty array, call again
+ else if (empty($tokens))
+ {
+ return $this->nextToken();
+ }
+ // if we have multiple items we push them to the buffer
+ else if (count($tokens) > 1)
+ {
+ $first = array_shift($tokens);
+ $this->pushedBack = $tokens;
+
+ return $first;
+ }
+ // otherwise return the first item of the array.
+ else
+ {
+ return $tokens[0];
+ }
+ }
+
+ protected function lexData()
+ {
+ $match = null;
+
+ // if no matches are left we return the rest of the template
+ // as simple text token
+ if (!preg_match('/(.*?)('.self::TAG_COMMENT_START.'|'.self::TAG_BLOCK_START.'|'.self::TAG_VARIABLE_START.')/A', $this->code, $match, null, $this->cursor))
+ {
+ $rv = new Twig_Token(Twig_Token::TEXT_TYPE, substr($this->code, $this->cursor), $this->lineno);
+ $this->cursor = $this->end;
+
+ return $rv;
+ }
+
+ // update the lineno on the instance
+ $lineno = $this->lineno;
+
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+
+ // array of tokens
+ $result = array();
+
+ // push the template text first
+ $text = $match[1];
+ if (!empty($text))
+ {
+ $result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $text, $lineno);
+ $lineno += substr_count($text, '\n');
+ }
+
+ $token = $match[2];
+ switch ($token)
+ {
+ case self::TAG_COMMENT_START:
+ if (!preg_match('/(.*?)'.self::TAG_COMMENT_END.'/A', $this->code, $match, null, $this->cursor))
+ {
+ throw new Twig_SyntaxError('unclosed comment', $this->lineno, $this->filename);
+ }
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ break;
+
+ case self::TAG_BLOCK_START:
+ // raw data?
+ if (preg_match('/\s*raw\s*'.self::TAG_BLOCK_END.'(.*?)'.self::TAG_BLOCK_START.'\s*endraw\s*'.self::TAG_BLOCK_END.'/A', $this->code, $match, null, $this->cursor))
+ {
+ $result[] = new Twig_Token(Twig_Token::TEXT_TYPE, $match[1], $lineno);
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ $this->position = self::POSITION_DATA;
+ }
+ else
+ {
+ $result[] = new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $lineno);
+ $this->position = self::POSITION_BLOCK;
+ }
+ break;
+
+ case self::TAG_VARIABLE_START:
+ $result[] = new Twig_Token(Twig_Token::VAR_START_TYPE, '', $lineno);
+ $this->position = self::POSITION_VAR;
+ break;
+ }
+
+ return $result;
+ }
+
+ protected function lexBlock()
+ {
+ if (preg_match('/\s*'.self::TAG_BLOCK_END.'/A', $this->code, $match, null, $this->cursor))
+ {
+ $lineno = $this->lineno;
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ $this->position = self::POSITION_DATA;
+
+ return new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $lineno);
+ }
+
+ return $this->lexExpression();
+ }
+
+ protected function lexVar()
+ {
+ if (preg_match('/\s*'.self::TAG_VARIABLE_END.'/A', $this->code, $match, null, $this->cursor))
+ {
+ $lineno = $this->lineno;
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ $this->position = self::POSITION_DATA;
+
+ return new Twig_Token(Twig_Token::VAR_END_TYPE, '', $lineno);
+ }
+
+ return $this->lexExpression();
+ }
+
+ protected function lexExpression()
+ {
+ $match = null;
+
+ // whitespace
+ while (preg_match('/\s+/A', $this->code, $match, null, $this->cursor))
+ {
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ }
+
+ // sanity check
+ if ($this->cursor >= $this->end)
+ {
+ throw new Twig_SyntaxError('Unexpected end of stream', $this->lineno, $this->filename);
+ }
+
+ // first parse operators
+ if (preg_match(self::REGEX_OPERATOR, $this->code, $match, null, $this->cursor))
+ {
+ $this->cursor += strlen($match[0]);
+
+ return new Twig_Token(Twig_Token::OPERATOR_TYPE, $match[0], $this->lineno);
+ }
+ // now names
+ else if (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor))
+ {
+ $this->cursor += strlen($match[0]);
+
+ return new Twig_Token(Twig_Token::NAME_TYPE, $match[0], $this->lineno);
+ }
+ // then numbers
+ else if (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor))
+ {
+ $this->cursor += strlen($match[0]);
+ $value = (float)$match[0];
+ if ((int)$value === $value)
+ {
+ $value = (int)$value;
+ }
+
+ return new Twig_Token(Twig_Token::NUMBER_TYPE, $value, $this->lineno);
+ }
+ // and finally strings
+ else if (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor))
+ {
+ $this->cursor += strlen($match[0]);
+ $this->lineno += substr_count($match[0], '\n');
+ $value = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2));
+
+ return new Twig_Token(Twig_Token::STRING_TYPE, $value, $this->lineno);
+ }
+
+ // unlexable
+ throw new Twig_SyntaxError(sprintf("Unexpected character '%s'", $this->code[$this->cursor]), $this->lineno, $this->filename);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by lexer classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_LexerInterface
+{
+ /**
+ * Tokenizes a source code.
+ *
+ * @param string $code The source code
+ * @param string $filename A unique identifier for the source code
+ *
+ * @return Twig_TokenStream A token stream instance
+ */
+ public function tokenize($code, $filename = 'n/a');
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Base loader class for all builtin loaders.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+abstract class Twig_Loader implements Twig_LoaderInterface
+{
+ protected $cache;
+ protected $autoReload;
+ protected $env;
+
+ public function __construct($cache = null, $autoReload = true)
+ {
+ $this->cache = $cache;
+ $this->autoReload = $autoReload;
+ }
+
+ /**
+ * Loads a template by name.
+ *
+ * @param string $name The template name
+ *
+ * @return string The class name of the compiled template
+ */
+ public function load($name)
+ {
+ $cls = $this->getTemplateName($name);
+
+ if (class_exists($cls))
+ {
+ return $cls;
+ }
+
+ list($template, $mtime) = $this->getSource($name);
+
+ if (is_null($this->cache))
+ {
+ $this->evalString($template, $name);
+
+ return $cls;
+ }
+
+ $cache = $this->getCacheFilename($name);
+ if (!file_exists($cache) || false === $mtime || ($this->autoReload && filemtime($cache) < $mtime))
+ {
+ $fp = @fopen($cache, 'wb');
+ if (!$fp)
+ {
+ $this->evalString($template, $name);
+
+ return $cls;
+ }
+ file_put_contents($cache, $this->compile($template, $name));
+ fclose($fp);
+ }
+
+ require_once $cache;
+
+ return $cls;
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ public function getTemplateName($name)
+ {
+ return '__TwigTemplate_'.md5($name);
+ }
+
+ public function getCacheFilename($name)
+ {
+ return $this->cache.'/twig_'.md5($name).'.cache';
+ }
+
+ protected function compile($source, $name)
+ {
+ return $this->env->compile($this->env->parse($this->env->tokenize($source, $name)));
+ }
+
+ protected function evalString($source, $name)
+ {
+ eval('?>'.$this->compile($source, $name));
+ }
+
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name string The name of the template to load
+ *
+ * @return array An array consisting of the source code as the first element,
+ * and the last modification time as the second one
+ * or false if it's not relevant
+ */
+ abstract protected function getSource($name);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Loads a template from an array.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Loader_Array extends Twig_Loader
+{
+ protected $templates;
+
+ public function __construct(array $templates)
+ {
+ $this->templates = array();
+ foreach ($templates as $name => $template)
+ {
+ $this->templates[$name] = $template;
+ }
+ }
+
+ public function getSource($name)
+ {
+ if (!isset($this->templates[$name]))
+ {
+ throw new LogicException(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ return array($this->templates[$name], false);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Loads template from the filesystem.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Loader_Filesystem extends Twig_Loader
+{
+ protected $folder;
+
+ public function __construct($folder, $cache = null, $autoReload = true)
+ {
+ $this->folder = realpath($folder);
+
+ parent::__construct($cache, $autoReload);
+ }
+
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name string The name of the template to load
+ *
+ * @return array An array consisting of the source code as the first element,
+ * and the last modification time as the second one
+ * or false if it's not relevant
+ */
+ public function getSource($name)
+ {
+ $file = realpath($this->folder.DIRECTORY_SEPARATOR.$name);
+
+ if (0 !== strpos($file, $this->folder))
+ {
+ throw new RuntimeException(sprintf('Unable to find template "%s".', $name));
+ }
+
+ // simple security check
+ if (0 !== strpos($file, $this->folder))
+ {
+ throw new RuntimeException(sprintf('You cannot load a template outside the "%s" directory.', $this->folder));
+ }
+
+ return array(file_get_contents($file), filemtime($file));
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Loads a template from a string.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Loader_String extends Twig_Loader
+{
+ /**
+ * Gets the source code of a template, given its name.
+ *
+ * @param string $name string The name of the template to load
+ *
+ * @return array An array consisting of the source code as the first element,
+ * and the last modification time as the second one
+ * or false if it's not relevant
+ */
+ public function getSource($source)
+ {
+ return array($source, false);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface all loaders must implement.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_LoaderInterface
+{
+ /**
+ * Loads a template by name.
+ *
+ * @param string $name The template name
+ *
+ * @return string The class name of the compiled template
+ */
+ public function load($name);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a node in the AST.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+abstract class Twig_Node
+{
+ protected $lineno;
+ protected $tag;
+
+ public function __construct($lineno, $tag = null)
+ {
+ $this->lineno = $lineno;
+ $this->tag = $tag;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'()';
+ }
+
+ abstract public function compile($compiler);
+
+ public function getLine()
+ {
+ return $this->lineno;
+ }
+
+ public function getTag()
+ {
+ return $this->tag;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents an autoescape node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_AutoEscape extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $value;
+ protected $body;
+
+ public function __construct($value, Twig_NodeList $body, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->value = $value;
+ $this->body = $body;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->value.')';
+ }
+
+ public function getNodes()
+ {
+ return $this->body->getNodes();
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = new Twig_NodeList($nodes, $this->lineno);
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->subcompile($this->body);
+ }
+
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a block node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Block extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $name;
+ protected $body;
+ protected $parent;
+
+ public function __construct($name, Twig_NodeList $body, $lineno, $parent = null, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->name = $name;
+ $this->body = $body;
+ $this->parent = $parent;
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).' '.$this->name.'(');
+ foreach ($this->body->getNodes() as $node)
+ {
+ foreach (explode("\n", $node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+ }
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+
+ public function getNodes()
+ {
+ return $this->body->getNodes();
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = new Twig_NodeList($nodes, $this->lineno);
+ }
+
+ public function replace($other)
+ {
+ $this->body = $other->body;
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf("public function block_%s(\$context)\n", $this->name), "{\n")
+ ->indent()
+ ;
+
+ $compiler
+ ->subcompile($this->body)
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ public function setParent($parent)
+ {
+ $this->parent = $parent;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a block call node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_BlockReference extends Twig_Node
+{
+ protected $name;
+
+ public function __construct($name, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->name = $name;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->name.')';
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write(sprintf('$this->block_%s($context);'."\n", $this->name))
+ ;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a call node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Call extends Twig_Node
+{
+ protected $name;
+ protected $arguments;
+
+ public function __construct($name, Twig_NodeList $arguments, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->name = $name;
+ $this->arguments = $arguments;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->name.')';
+ }
+
+ public function compile($compiler)
+ {
+// $compiler->subcompile($this->body);
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Abstract class for all nodes that represents an expression.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+abstract class Twig_Node_Expression extends Twig_Node
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name
+{
+ public function compile($compiler)
+ {
+ $compiler->raw(sprintf('$context[\'%s\']', $this->name));
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression
+{
+ protected $left;
+ protected $right;
+
+ public function __construct(Twig_Node $left, Twig_Node $right, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->left = $left;
+ $this->right = $right;
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).'(');
+
+ foreach (explode("\n", $this->left->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+
+ $repr[] = ', ';
+
+ foreach (explode("\n", $this->right->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->left)
+ ->raw(') ')
+ ;
+ $this->operator($compiler);
+ $compiler
+ ->raw(' (')
+ ->subcompile($this->right)
+ ->raw(')')
+ ;
+ }
+
+ abstract public function operator($compiler);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Add extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('+');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_And extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('&&');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Concat extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('.');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Div extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('/');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Mod extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('%');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Mul extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('*');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Or extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('||');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Binary_Sub extends Twig_Node_Expression_Binary
+{
+ public function operator($compiler)
+ {
+ return $compiler->raw('-');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Compare extends Twig_Node_Expression
+{
+ protected $expr;
+ protected $ops;
+
+ public function __construct(Twig_Node_Expression $expr, $ops, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->expr = $expr;
+ $this->ops = $ops;
+ }
+
+ public function compile($compiler)
+ {
+ $this->expr->compile($compiler);
+ $i = 0;
+ $useTmpVars = count($this->ops) > 1;
+ foreach ($this->ops as $op)
+ {
+ if ($i)
+ {
+ $compiler->raw(' && ($tmp'.$i);
+ }
+ list($op, $node) = $op;
+ $compiler->raw(' '.$op.' ');
+
+ if ($useTmpVars)
+ {
+ $compiler
+ ->raw('($tmp'.++$i.' = ')
+ ->subcompile($node)
+ ->raw(')')
+ ;
+ }
+ else
+ {
+ $compiler->subcompile($node);
+ }
+ }
+
+ for ($j = 1; $j < $i; $j++)
+ {
+ $compiler->raw(')');
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Conditional extends Twig_Node_Expression
+{
+ protected $expr1;
+ protected $expr2;
+ protected $expr3;
+
+ public function __construct(Twig_Node_Expression $expr1, Twig_Node_Expression $expr2, Twig_Node_Expression $expr3, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->expr1 = $expr1;
+ $this->expr2 = $expr2;
+ $this->expr3 = $expr3;
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->raw('(')
+ ->subcompile($this->expr1)
+ ->raw(') ? (')
+ ->subcompile($this->expr2)
+ ->raw(') : (')
+ ->subcompile($this->expr3)
+ ->raw(')')
+ ;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Constant extends Twig_Node_Expression
+{
+ protected $value;
+
+ public function __construct($value, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->value = $value;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->value.')';
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->repr($this->value);
+ }
+
+ public function getValue()
+ {
+ return $this->value;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Filter extends Twig_Node_Expression implements Twig_NodeListInterface
+{
+ protected $node;
+ protected $filters;
+
+ public function __construct(Twig_Node $node, array $filters, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+
+ $this->node = $node;
+ $this->filters = $filters;
+ }
+
+ public function __toString()
+ {
+ $filters = array();
+ foreach ($this->filters as $filter)
+ {
+ $filters[] = $filter[0];
+ }
+
+ $repr = array(get_class($this).'(');
+
+ foreach (explode("\n", $this->node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+
+ $repr[] = ' ('.implode(', ', $filters).')';
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+
+ public function getNodes()
+ {
+ return array($this->node);
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->node = $nodes[0];
+ }
+
+ public function compile($compiler)
+ {
+ $filterMap = $compiler->getEnvironment()->getFilters();
+
+ $postponed = array();
+ for ($i = count($this->filters) - 1; $i >= 0; --$i)
+ {
+ list($name, $attrs) = $this->filters[$i];
+ if (!isset($filterMap[$name]))
+ {
+ $compiler
+ ->raw('$this->resolveMissingFilter(')
+ ->repr($name)
+ ->raw(', ')
+ ;
+ }
+ else
+ {
+ $compiler->raw($filterMap[$name][0].($filterMap[$name][1] ? '($this, ' : '('));
+ }
+ $postponed[] = $attrs;
+ }
+ $this->node->compile($compiler);
+ foreach (array_reverse($postponed) as $attributes)
+ {
+ foreach ($attributes as $node)
+ {
+ $compiler
+ ->raw(', ')
+ ->subcompile($node)
+ ;
+ }
+ $compiler->raw(')');
+ }
+ }
+
+ public function getFilters()
+ {
+ return $this->filters;
+ }
+
+ public function appendFilter($filter)
+ {
+ $this->filters[] = $filter;
+ }
+
+ public function hasFilter($name)
+ {
+ foreach ($this->filters as $filter)
+ {
+ if ($name == $filter[0])
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_GetAttr extends Twig_Node_Expression implements Twig_NodeListInterface
+{
+ protected $node;
+ protected $attr;
+
+ public function __construct(Twig_Node $node, $attr, $lineno, $token_value)
+ {
+ parent::__construct($lineno);
+ $this->node = $node;
+ $this->attr = $attr;
+ $this->token_value = $token_value;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->node.', '.$this->attr.')';
+ }
+
+ public function getNodes()
+ {
+ return array($this->node);
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->node = $nodes[0];
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->raw('$this->getAttribute(')
+ ->subcompile($this->node)
+ ->raw(', ')
+ ->subcompile($this->attr)
+ ;
+
+ if ('[' == $this->token_value) # Don't look for functions if they're using foo[bar]
+ {
+ $compiler->raw(', false');
+ }
+
+ $compiler->raw(')');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Name extends Twig_Node_Expression
+{
+ protected $name;
+
+ public function __construct($name, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->name = $name;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->name.')';
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->raw(sprintf('(isset($context[\'%s\']) ? $context[\'%s\'] : null)', $this->name, $this->name));
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression
+{
+ protected $node;
+
+ public function __construct(Twig_Node $node, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->node = $node;
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).'(');
+
+ foreach (explode("\n", $this->node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+ public function compile($compiler)
+ {
+ $compiler->raw('(');
+ $this->operator($compiler);
+ $this->node->compile($compiler);
+ $compiler->raw(')');
+ }
+
+ abstract public function operator($compiler);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Unary_Neg extends Twig_Node_Expression_Unary
+{
+ public function operator($compiler)
+ {
+ $compiler->raw('-');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Unary_Not extends Twig_Node_Expression_Unary
+{
+ public function operator($compiler)
+ {
+ $compiler->raw('!');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Node_Expression_Unary_Pos extends Twig_Node_Expression_Unary
+{
+ public function operator($compiler)
+ {
+ $compiler->raw('+');
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a filter node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Filter extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $filter;
+ protected $body;
+
+ public function __construct($filter, Twig_NodeList $body, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->filter = $filter;
+ $this->body = $body;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->filter.')';
+ }
+
+ public function getNodes()
+ {
+ return $this->body->getNodes();
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = new Twig_NodeList($nodes, $this->lineno);
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->subcompile($this->body);
+ }
+
+ public function getFilter()
+ {
+ return $this->filter;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a for node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_For extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $isMultitarget;
+ protected $item;
+ protected $seq;
+ protected $body;
+ protected $else;
+
+ public function __construct($isMultitarget, $item, $seq, Twig_NodeList $body, Twig_Node $else = null, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->isMultitarget = $isMultitarget;
+ $this->item = $item;
+ $this->seq = $seq;
+ $this->body = $body;
+ $this->else = $else;
+ $this->lineno = $lineno;
+ }
+
+ public function getNodes()
+ {
+ return $this->body->getNodes();
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = new Twig_NodeList($nodes, $this->lineno);
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->pushContext()
+ ->write("\$context['_iterated'] = false;\n")
+ ->write('foreach (twig_iterate($context, ')
+ ->subcompile($this->seq)
+ ->raw(") as \$iterator)\n")
+ ->write("{\n")
+ ->indent()
+ ->write("\$context['_iterated'] = true;\n")
+ ->write('twig_set_loop_context($context, $iterator, ');
+ ;
+
+ if ($this->isMultitarget)
+ {
+ $compiler->raw('array(');
+ foreach ($this->item as $idx => $node)
+ {
+ if ($idx)
+ {
+ $compiler->raw(', ');
+ }
+ $compiler->repr($node->getName());
+ }
+ $compiler->raw(')');
+ }
+ else
+ {
+ $compiler->repr($this->item->getName());
+ }
+
+ $compiler
+ ->raw(");\n")
+ ->subcompile($this->body)
+ ->outdent()
+ ->write("}\n")
+ ;
+
+ if (!is_null($this->else))
+ {
+ $compiler
+ ->write("if (!\$context['_iterated'])\n")
+ ->write("{\n")
+ ->indent()
+ ->subcompile($this->else)
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+ $compiler->popContext();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents an if node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_If extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $tests;
+ protected $else;
+
+ public function __construct($tests, Twig_NodeList $else = null, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->tests = $tests;
+ $this->else = $else;
+ }
+
+ public function getNodes()
+ {
+ $nodes = array();
+ foreach ($this->tests as $test)
+ {
+ $nodes[] = $test[1];
+ }
+
+ if ($this->else)
+ {
+ $nodes[] = $this->else;
+ }
+
+ return $nodes;
+ }
+
+ public function setNodes(array $nodes)
+ {
+ foreach ($this->tests as $i => $test)
+ {
+ $this->tests[$i][1] = $nodes[$i];
+ }
+
+ if ($this->else)
+ {
+ $nodes = $nodes[count($nodes) - 1];
+ }
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->addDebugInfo($this);
+ $idx = 0;
+ foreach ($this->tests as $test)
+ {
+ if ($idx++)
+ {
+ $compiler
+ ->outdent()
+ ->write("}\n", "elseif (")
+ ;
+ }
+ else
+ {
+ $compiler
+ ->write('if (')
+ ;
+ }
+
+ $compiler
+ ->subcompile($test[0])
+ ->raw(")\n")
+ ->write("{\n")
+ ->indent()
+ ->subcompile($test[1])
+ ;
+ }
+ if (!is_null($this->else))
+ {
+ $compiler
+ ->outdent()
+ ->write("}\n", "else\n", "{\n")
+ ->indent()
+ ->subcompile($this->else)
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write("}\n");
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents an include node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Include extends Twig_Node
+{
+ protected $expr;
+ protected $sandboxed;
+
+ public function __construct(Twig_Node_Expression $expr, $sandboxed, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+
+ $this->expr = $expr;
+ $this->sandboxed = $sandboxed;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->expr.')';
+ }
+
+ public function compile($compiler)
+ {
+ if (!$compiler->getEnvironment()->hasExtension('sandbox') && $this->sandboxed)
+ {
+ throw new Twig_SyntaxError('Unable to use the sanboxed attribute on an include if the sandbox extension is not enabled.');
+ }
+
+ $compiler->addDebugInfo($this);
+
+ if ($this->sandboxed)
+ {
+ $compiler
+ ->write("\$sandbox = \$this->env->getExtension('sandbox');\n")
+ ->write("\$alreadySandboxed = \$sandbox->isSandboxed();\n")
+ ->write("\$sandbox->enableSandbox();\n")
+ ;
+ }
+
+ $compiler
+ ->write('$this->env->loadTemplate(')
+ ->subcompile($this->expr)
+ ->raw(')->display($context);'."\n")
+ ;
+
+ if ($this->sandboxed)
+ {
+ $compiler
+ ->write("if (!\$alreadySandboxed)\n", "{\n")
+ ->indent()
+ ->write("\$sandbox->disableSandbox();\n")
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a macro node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Macro extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $name;
+ protected $body;
+
+ public function __construct($name, Twig_NodeList $body, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->name = $name;
+ $this->body = $body;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->name.')';
+ }
+
+ public function getNodes()
+ {
+ return $this->body->getNodes();
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = new Twig_NodeList($nodes, $this->lineno);
+ }
+
+ public function compile($compiler)
+ {
+ $compiler->subcompile($this->body);
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a module node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Module extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $body;
+ protected $extends;
+ protected $blocks;
+ protected $filename;
+ protected $usedFilters;
+ protected $usedTags;
+
+ public function __construct(Twig_NodeList $body, $extends, $blocks, $filename)
+ {
+ parent::__construct(1);
+
+ $this->body = $body;
+ $this->extends = $extends;
+ $this->blocks = array_values($blocks);
+ $this->filename = $filename;
+ $this->usedFilters = array();
+ $this->usedTags = array();
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).'(');
+ foreach ($this->body->getNodes() as $node)
+ {
+ foreach (explode("\n", $node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+ }
+
+ foreach ($this->blocks as $node)
+ {
+ foreach (explode("\n", $node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+ }
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+
+ public function getNodes()
+ {
+ return array_merge(array($this->body), $this->blocks);
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->body = array_shift($nodes);
+ $this->blocks = $nodes;
+ }
+
+ public function setUsedFilters(array $filters)
+ {
+ $this->usedFilters = $filters;
+ }
+
+ public function setUsedTags(array $tags)
+ {
+ $this->usedTags = $tags;
+ }
+
+ public function compile($compiler)
+ {
+ $sandboxed = $compiler->getEnvironment()->hasExtension('sandbox');
+
+ $compiler->write("<?php\n\n");
+
+ if (!is_null($this->extends))
+ {
+ $compiler
+ ->write('$this->load(')
+ ->repr($this->extends)
+ ->raw(");\n\n")
+ ;
+ }
+
+ $compiler
+ ->write("/* $this->filename */\n")
+ ->write('class __TwigTemplate_'.md5($this->filename))
+ ;
+
+ if (!is_null($this->extends))
+ {
+ $parent = md5($this->extends);
+ $compiler
+ ->raw(" extends __TwigTemplate_$parent\n")
+ ->write("{\n")
+ ->indent()
+ ;
+ }
+ else
+ {
+ $compiler
+ ->write(" extends ".$compiler->getEnvironment()->getBaseTemplateClass()."\n", "{\n")
+ ->indent()
+ ->write("public function display(\$context)\n", "{\n")
+ ->indent()
+ ;
+
+ if ($sandboxed)
+ {
+ $compiler->write("\$this->checkSecurity();\n");
+ }
+
+ $compiler
+ ->write("\$this->env->initRuntime();\n\n")
+ ->subcompile($this->body)
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ // blocks
+ foreach ($this->blocks as $node)
+ {
+ $compiler->subcompile($node);
+ }
+
+ if ($sandboxed)
+ {
+ // sandbox information
+ $compiler
+ ->write("protected function checkSecurity()\n", "{\n")
+ ->indent()
+ ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
+ ->indent()
+ ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n")
+ ->write(!$this->usedFilters ? "array()\n" : "array('".implode('\', \'', $this->usedFilters)."')\n")
+ ->outdent()
+ ->write(");\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ // debug information
+ if ($compiler->getEnvironment()->isDebug())
+ {
+ $compiler
+ ->write("public function __toString()\n", "{\n")
+ ->indent()
+ ->write('return ')
+ ->string($this)
+ ->raw(";\n")
+ ->outdent()
+ ->write("}\n\n")
+ ;
+ }
+
+ $compiler
+ ->outdent()
+ ->write("}\n")
+ ;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a parent node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Parent extends Twig_Node
+{
+ protected $blockName;
+
+ public function __construct($blockName, $lineno, $tag = null)
+ {
+ parent::__construct($lineno, $tag);
+ $this->blockName = $blockName;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->blockName.')';
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('parent::block_'.$this->blockName.'($context);'."\n")
+ ;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a node that outputs an expression.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Print extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $expr;
+
+ public function __construct(Twig_Node_Expression $expr, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->expr = $expr;
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).'(');
+ foreach (explode("\n", $this->expr->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+ $repr[] = ')';
+
+ return implode("\n", $repr);
+ }
+
+ public function getNodes()
+ {
+ return array($this->expr);
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->expr = $nodes[0];
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('echo ')
+ ->subcompile($this->expr)
+ ->raw(";\n")
+ ;
+ }
+
+ public function getExpression()
+ {
+ return $this->expr;
+ }
+}
--- /dev/null
+<?php
+
+class Twig_Node_Set extends Twig_Node
+{
+ protected $name;
+ protected $value;
+
+ public function __construct($name, Twig_Node_Expression $value, $lineno)
+ {
+ parent::__construct($lineno);
+
+ $this->name = $name;
+ $this->value = $value;
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('$context[')
+ ->string($this->name)
+ ->write('] = ')
+ ->subcompile($this->value)
+ ->raw(";\n")
+ ;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a text node.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Node_Text extends Twig_Node
+{
+ protected $data;
+
+ public function __construct($data, $lineno)
+ {
+ parent::__construct($lineno);
+ $this->data = $data;
+ }
+
+ public function __toString()
+ {
+ return get_class($this).'('.$this->data.')';
+ }
+
+ public function compile($compiler)
+ {
+ $compiler
+ ->addDebugInfo($this)
+ ->write('echo ')
+ ->string($this->data)
+ ->raw(";\n")
+ ;
+ }
+
+ public function getData()
+ {
+ return $this->data;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a list of nodes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_NodeList extends Twig_Node implements Twig_NodeListInterface
+{
+ protected $nodes;
+
+ public function __construct(array $nodes, $lineno = 0)
+ {
+ parent::__construct($lineno);
+
+ $this->nodes = $nodes;
+ }
+
+ public function __toString()
+ {
+ $repr = array(get_class($this).'(');
+ foreach ($this->nodes as $node)
+ {
+ foreach (explode("\n", $node->__toString()) as $line)
+ {
+ $repr[] = ' '.$line;
+ }
+ }
+
+ return implode("\n", $repr);
+ }
+
+ public function compile($compiler)
+ {
+ foreach ($this->nodes as $node)
+ {
+ $node->compile($compiler);
+ }
+ }
+
+ public function getNodes()
+ {
+ return $this->nodes;
+ }
+
+ public function setNodes(array $nodes)
+ {
+ $this->nodes = $nodes;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by node list classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_NodeListInterface
+{
+ /**
+ * Returns an array of embedded nodes
+ */
+ public function getNodes();
+
+ /**
+ * Sets the array of embedded nodes
+ */
+ public function setNodes(array $nodes);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_NodeTransformer
+{
+ protected $env;
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ abstract public function visit(Twig_Node $node);
+
+ protected function visitDeep(Twig_Node $node)
+ {
+ if (!$node instanceof Twig_NodeListInterface)
+ {
+ return $node;
+ }
+
+ $newNodes = array();
+ foreach ($nodes = $node->getNodes() as $k => $n)
+ {
+ if (null !== $n = $this->visit($n))
+ {
+ $newNodes[$k] = $n;
+ }
+ }
+
+ $node->setNodes($newNodes);
+
+ return $node;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_NodeTransformer_Chain extends Twig_NodeTransformer
+{
+ protected $transformers;
+
+ public function __construct(array $transformers)
+ {
+ $this->transformers = $transformers;
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ parent::setEnvironment($env);
+
+ foreach ($this->transformers as $transformer)
+ {
+ $transformer->setEnvironment($env);
+ }
+ }
+
+ public function visit(Twig_Node $node)
+ {
+ foreach ($this->transformers as $transformer)
+ {
+ $node = $transformer->visit($node);
+ }
+
+ return $node;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_NodeTransformer_Escaper extends Twig_NodeTransformer
+{
+ protected $statusStack = array();
+
+ public function visit(Twig_Node $node)
+ {
+ // autoescape?
+ if ($node instanceof Twig_Node_AutoEscape)
+ {
+ $this->statusStack[] = $node->getValue();
+
+ $node = $this->visitDeep($node);
+
+ array_pop($this->statusStack);
+
+ // remove the node
+ return $node;
+ }
+
+ if (!$node instanceof Twig_Node_Print)
+ {
+ return $this->visitDeep($node);
+ }
+
+ if (false === $this->needEscaping())
+ {
+ return $node;
+ }
+
+ $expression = $node->getExpression();
+
+ // don't escape if escape has already been called
+ // or if we want the safe string
+ if (
+ $expression instanceof Twig_Node_Expression_Filter
+ &&
+ (
+ $expression->hasFilter('escape')
+ ||
+ $expression->hasFilter('safe')
+ )
+ )
+ {
+ return $node;
+ }
+
+ // escape
+ if ($expression instanceof Twig_Node_Expression_Filter)
+ {
+ $expression->appendFilter(array('escape', array()));
+
+ return $node;
+ }
+ else
+ {
+ return new Twig_Node_Print(
+ new Twig_Node_Expression_Filter($expression, array(array('escape', array())), $node->getLine())
+ , $node->getLine()
+ );
+ }
+ }
+
+ protected function needEscaping()
+ {
+ if (count($this->statusStack))
+ {
+ return $this->statusStack[count($this->statusStack) - 1];
+ }
+ else
+ {
+ return $this->env->hasExtension('escaper') ? $this->env->getExtension('escaper')->isGlobal() : false;
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_NodeTransformer_Filter extends Twig_NodeTransformer
+{
+ protected $statusStack = array();
+
+ public function visit(Twig_Node $node)
+ {
+ // filter?
+ if ($node instanceof Twig_Node_Filter)
+ {
+ $this->statusStack[] = $node->getFilter();
+
+ $node = $this->visitDeep($node);
+
+ array_pop($this->statusStack);
+
+ return $node;
+ }
+
+ if (!$node instanceof Twig_Node_Print && !$node instanceof Twig_Node_Text)
+ {
+ return $this->visitDeep($node);
+ }
+
+ if (false === $filter = $this->getCurrentFilter())
+ {
+ return $node;
+ }
+
+ if ($node instanceof Twig_Node_Text)
+ {
+ $expression = new Twig_Node_Expression_Constant($node->getData(), $node->getLine());
+ }
+ else
+ {
+ $expression = $node->getExpression();
+ }
+
+ // filter
+ if ($expression instanceof Twig_Node_Expression_Filter)
+ {
+ $expression->appendFilter(array($filter, array()));
+
+ return $node;
+ }
+ else
+ {
+ return new Twig_Node_Print(
+ new Twig_Node_Expression_Filter($expression, array(array($filter, array())), $node->getLine())
+ , $node->getLine()
+ );
+ }
+ }
+
+ protected function getCurrentFilter()
+ {
+ return count($this->statusStack) ? $this->statusStack[count($this->statusStack) - 1] : false;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_NodeTransformer_Sandbox extends Twig_NodeTransformer
+{
+ protected $inAModule = false;
+ protected $tags;
+ protected $filters;
+
+ public function visit(Twig_Node $node)
+ {
+ if ($node instanceof Twig_Node_Module)
+ {
+ $this->inAModule = true;
+ $this->tags = array();
+ $this->filters = array();
+
+ $node = $this->visitDeep($node);
+
+ $node->setUsedFilters(array_keys($this->filters));
+ $node->setUsedTags(array_keys($this->tags));
+ $this->inAModule = false;
+
+ return $node;
+ }
+
+ if (!$this->inAModule)
+ {
+ return $node;
+ }
+
+ // look for tags
+ if ($node->getTag())
+ {
+ $this->tags[$node->getTag()] = true;
+ }
+
+ // look for filters
+ if ($node instanceof Twig_Node_Expression_Filter)
+ {
+ foreach ($node->getFilters() as $filter)
+ {
+ $this->filters[$filter[0]] = true;
+ }
+ }
+
+ $this->visitDeep($node);
+
+ return $node;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Parser
+{
+ protected $stream;
+ protected $extends;
+ protected $handlers;
+ protected $transformers;
+ protected $expressionParser;
+ protected $blocks;
+ protected $currentBlock;
+ protected $env;
+
+ public function __construct(Twig_Environment $env = null)
+ {
+ $this->setEnvironment($env);
+ }
+
+ public function setEnvironment(Twig_Environment $env)
+ {
+ $this->env = $env;
+
+ $this->handlers = array();
+ $this->transformers = array();
+
+ // tag handlers
+ foreach ($this->env->getTokenParsers() as $handler)
+ {
+ $handler->setParser($this);
+
+ $this->handlers[$handler->getTag()] = $handler;
+ }
+
+ // node transformers
+ $this->transformers = $env->getNodeTransformers();
+ }
+
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ public function parse(Twig_TokenStream $stream)
+ {
+ if (null === $this->expressionParser)
+ {
+ $this->expressionParser = new Twig_ExpressionParser($this);
+ }
+
+ $this->stream = $stream;
+ $this->extends = null;
+ $this->blocks = array();
+ $this->currentBlock = null;
+
+ try
+ {
+ $body = $this->subparse(null);
+ }
+ catch (Twig_SyntaxError $e)
+ {
+ if (is_null($e->getFilename()))
+ {
+ $e->setFilename($this->stream->getFilename());
+ }
+
+ throw $e;
+ }
+
+ if (!is_null($this->extends))
+ {
+ foreach ($this->blocks as $block)
+ {
+ $block->setParent($this->extends);
+ }
+ }
+
+ $node = new Twig_Node_Module($body, $this->extends, $this->blocks, $this->stream->getFilename());
+
+ $transformer = new Twig_NodeTransformer_Chain($this->transformers);
+ $transformer->setEnvironment($this->env);
+ $node = $transformer->visit($node);
+
+ return $node;
+ }
+
+ public function subparse($test, $drop_needle = false)
+ {
+ $lineno = $this->getCurrentToken()->getLine();
+ $rv = array();
+ while (!$this->stream->isEOF())
+ {
+ switch ($this->getCurrentToken()->getType())
+ {
+ case Twig_Token::TEXT_TYPE:
+ $token = $this->stream->next();
+ $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
+ break;
+
+ case Twig_Token::VAR_START_TYPE:
+ $token = $this->stream->next();
+ $expr = $this->expressionParser->parseExpression();
+ $this->stream->expect(Twig_Token::VAR_END_TYPE);
+ $rv[] = new Twig_Node_Print($expr, $token->getLine());
+ break;
+
+ case Twig_Token::BLOCK_START_TYPE:
+ $this->stream->next();
+ $token = $this->getCurrentToken();
+
+ if ($token->getType() !== Twig_Token::NAME_TYPE)
+ {
+ throw new Twig_SyntaxError('A block must start with a tag name', $token->getLine());
+ }
+
+ if (!is_null($test) && call_user_func($test, $token))
+ {
+ if ($drop_needle)
+ {
+ $this->stream->next();
+ }
+
+ return new Twig_NodeList($rv, $lineno);
+ }
+
+ if (!isset($this->handlers[$token->getValue()]))
+ {
+ throw new Twig_SyntaxError(sprintf('Unknown tag name "%s"', $token->getValue()), $token->getLine());
+ }
+
+ $this->stream->next();
+
+ $subparser = $this->handlers[$token->getValue()];
+ $node = $subparser->parse($token);
+ if (!is_null($node))
+ {
+ $rv[] = $node;
+ }
+ break;
+
+ default:
+ throw new LogicException('Lexer or parser ended up in unsupported state.');
+ }
+ }
+
+ return new Twig_NodeList($rv, $lineno);
+ }
+
+ public function addHandler($name, $class)
+ {
+ $this->handlers[$name] = $class;
+ }
+
+ public function addTransformer(Twig_NodeTransformer $transformer)
+ {
+ $this->transformers[] = $transformer;
+ }
+
+ public function getCurrentBlock()
+ {
+ return $this->currentBlock;
+ }
+
+ public function setCurrentBlock($name)
+ {
+ $this->currentBlock = $name;
+ }
+
+ public function hasBlock($name)
+ {
+ return isset($this->blocks[$name]);
+ }
+
+ public function setBlock($name, $value)
+ {
+ $this->blocks[$name] = $value;
+ }
+
+ public function getExpressionParser()
+ {
+ return $this->expressionParser;
+ }
+
+ public function getParent()
+ {
+ return $this->extends;
+ }
+
+ public function setParent($extends)
+ {
+ $this->extends = $extends;
+ }
+
+ public function getStream()
+ {
+ return $this->stream;
+ }
+
+ public function getCurrentToken()
+ {
+ return $this->stream->getCurrent();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interface implemented by parser classes.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_ParserInterface
+{
+ /**
+ * Converts a token stream to a node tree.
+ *
+ * @param Twig_TokenStream $stream A token stream instance
+ *
+ * @return Twig_Node_Module A node tree
+ */
+ public function parser(Twig_TokenStream $code);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Exception thrown when an error occurs at runtime.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_RuntimeError extends Twig_Error
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Exception thrown when a security error occurs at runtime.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Sandbox_SecurityError extends Twig_Error
+{
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Represents a security policy which need to be enforced when sandbox mode is enabled.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface
+{
+ protected $allowedTags;
+ protected $allowedFilters;
+ protected $allowedMethods;
+
+ public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array())
+ {
+ $this->allowedTags = $allowedTags;
+ $this->allowedFilters = $allowedFilters;
+ $this->allowedMethods = $allowedMethods;
+ }
+
+ public function setAllowedTags(array $tags)
+ {
+ $this->allowedTags = $tags;
+ }
+
+ public function setAllowedFilters(array $filters)
+ {
+ $this->allowedFilters = $filters;
+ }
+
+ public function setAllowedMethods(array $methods)
+ {
+ $this->allowedMethods = $methods;
+ }
+
+ public function checkSecurity($tags, $filters)
+ {
+ foreach ($tags as $tag)
+ {
+ if (!in_array($tag, $this->allowedTags))
+ {
+ throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag));
+ }
+ }
+
+ foreach ($filters as $filter)
+ {
+ if (!in_array($filter, $this->allowedFilters))
+ {
+ throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter));
+ }
+ }
+ }
+
+ public function checkMethodAllowed($obj, $method)
+ {
+ $allowed = false;
+ foreach ($this->allowedMethods as $class => $methods)
+ {
+ if ($obj instanceof $class)
+ {
+ $allowed = in_array($method, is_array($methods) ? $methods : array($methods));
+
+ break;
+ }
+ }
+
+ if (!$allowed)
+ {
+ throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj)));
+ }
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Interfaces that all security policy classes must implements.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+interface Twig_Sandbox_SecurityPolicyInterface
+{
+ public function checkSecurity($tags, $filters);
+
+ public function checkMethodAllowed($obj, $method);
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Exception thrown when a syntax error occurs during lexing or parsing of a template.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_SyntaxError extends Twig_Error
+{
+ protected $lineno;
+ protected $filename;
+ protected $rawMessage;
+
+ public function __construct($message, $lineno, $filename = null)
+ {
+ $this->lineno = $lineno;
+ $this->filename = $filename;
+ $this->rawMessage = $message;
+
+ $this->updateRepr();
+ }
+
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+
+ public function setFilename($filename)
+ {
+ $this->filename = $filename;
+
+ $this->updateRepr();
+ }
+
+ protected function updateRepr()
+ {
+ $this->message = $this->rawMessage.' in '.($this->filename ? $this->filename : 'n/a').' at line '.$this->lineno;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_Template implements Twig_TemplateInterface
+{
+ protected $env;
+
+ public function __construct(Twig_Environment $env)
+ {
+ $this->env = $env;
+ }
+
+ /**
+ * Renders the template with the given context and returns it as string.
+ */
+ public function render($context)
+ {
+ ob_start();
+ $this->display($context);
+
+ return ob_get_clean();
+ }
+
+ public function getEnvironment()
+ {
+ return $this->env;
+ }
+
+ protected function resolveMissingFilter($name)
+ {
+ throw new Twig_RuntimeError(sprintf('The filter "%s" does not exist', $name));
+ }
+
+ protected function getAttribute($object, $item)
+ {
+ $item = (string) $item;
+
+ if (is_array($object) && isset($object[$item]))
+ {
+ return $object[$item];
+ }
+
+ if (
+ !is_object($object) ||
+ (
+ !method_exists($object, $method = $item) &&
+ !method_exists($object, $method = 'get'.ucfirst($item))
+ )
+ )
+ {
+ return null;
+ }
+
+ if ($this->env->hasExtension('sandbox'))
+ {
+ $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
+ }
+
+ return $object->$method();
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+interface Twig_TemplateInterface
+{
+ public function render($context);
+
+ public function display($context);
+
+ public function getEnvironment();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_Token
+{
+ protected $value;
+ protected $type;
+ protected $lineno;
+
+ const EOF_TYPE = -1;
+ const TEXT_TYPE = 0;
+ const BLOCK_START_TYPE = 1;
+ const VAR_START_TYPE = 2;
+ const BLOCK_END_TYPE = 3;
+ const VAR_END_TYPE = 4;
+ const NAME_TYPE = 5;
+ const NUMBER_TYPE = 6;
+ const STRING_TYPE = 7;
+ const OPERATOR_TYPE = 8;
+
+ public function __construct($type, $value, $lineno)
+ {
+ $this->type = $type;
+ $this->value = $value;
+ $this->lineno = $lineno;
+ }
+
+ public function __toString()
+ {
+ return sprintf('%s(%s)', self::getTypeAsString($this->type, true), $this->value);
+ }
+
+ /**
+ * Test the current token for a type. The first argument is the type
+ * of the token (if not given Twig_Token::NAME_NAME), the second the
+ * value of the token (if not given value is not checked).
+ * the token value can be an array if multiple checks shoudl be
+ * performed.
+ */
+ public function test($type, $values = null)
+ {
+ if (is_null($values) && !is_int($type))
+ {
+ $values = $type;
+ $type = self::NAME_TYPE;
+ }
+
+ return ($this->type === $type) && (
+ is_null($values) ||
+ (is_array($values) && in_array($this->value, $values)) ||
+ $this->value == $values
+ );
+ }
+
+ public function getLine()
+ {
+ return $this->lineno;
+ }
+
+ public function getType()
+ {
+ return $this->type;
+ }
+
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ public function setValue($value)
+ {
+ $this->value = $value;
+ }
+
+ static public function getTypeAsString($type, $short = false)
+ {
+ switch ($type)
+ {
+ case 0:
+ return 'TEXT_TYPE';
+ case -1:
+ return 'EOF_TYPE';
+ case 1:
+ return 'BLOCK_START_TYPE';
+ case 2:
+ return 'VAR_START_TYPE';
+ case 3:
+ return 'BLOCK_END_TYPE';
+ case 4:
+ return 'VAR_END_TYPE';
+ case 5:
+ return 'NAME_TYPE';
+ case 6:
+ return 'NUMBER_TYPE';
+ case 7:
+ return 'STRING_TYPE';
+ case 8:
+ return 'OPERATOR_TYPE';
+ }
+
+ return $short ? $name : 'Twig_Token::'.$name;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+abstract class Twig_TokenParser
+{
+ protected $parser;
+
+ public function setParser(Twig_Parser $parser)
+ {
+ $this->parser = $parser;
+ }
+
+ abstract public function parse(Twig_Token $token);
+
+ abstract public function getTag();
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_AutoEscape extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $value = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ if (!in_array($value, array('on', 'off')))
+ {
+ throw new Twig_SyntaxError("Autoescape value must be 'on' or 'off'", $lineno);
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_AutoEscape('on' === $value ? true : false, $body, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd($token)
+ {
+ return $token->test('endautoescape');
+ }
+
+ public function getTag()
+ {
+ return 'autoescape';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Block extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ if ($this->parser->hasBlock($name))
+ {
+ throw new Twig_SyntaxError("The block '$name' has already been defined", $lineno);
+ }
+ $this->parser->setCurrentBlock($name);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE))
+ {
+ $value = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ if ($value != $name)
+ {
+ throw new Twig_SyntaxError(sprintf("Expected endblock for block '$name' (but %s given)", $value), $lineno);
+ }
+ }
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ $block = new Twig_Node_Block($name, $body, $lineno);
+ $this->parser->setBlock($name, $block);
+ $this->parser->setCurrentBlock(null);
+
+ return new Twig_Node_BlockReference($name, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd($token)
+ {
+ return $token->test('endblock');
+ }
+
+ public function getTag()
+ {
+ return 'block';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Call extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ // arguments
+ $arguments = array();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Call($name, $arguments, $lineno, $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'call';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Display extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ if (!$this->parser->hasBlock($name))
+ {
+ throw new Twig_SyntaxError("The block '$name' cannot be displayed as it has not yet been defined", $lineno);
+ }
+ $this->parser->setCurrentBlock($name);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_BlockReference($name, $lineno, $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'display';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Extends extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ if (null !== $this->parser->getParent())
+ {
+ throw new Twig_SyntaxError('Multiple extend tags are forbidden', $token->getLine());
+ }
+ $this->parser->setParent($this->parser->getStream()->expect(Twig_Token::STRING_TYPE)->getValue());
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return null;
+ }
+
+ public function getTag()
+ {
+ return 'extends';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Filter extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $filter = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Filter($filter, $body, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd($token)
+ {
+ return $token->test('endfilter');
+ }
+
+ public function getTag()
+ {
+ return 'filter';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_For extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ list($is_multitarget, $item) = $this->parser->getExpressionParser()->parseAssignmentExpression();
+ $this->parser->getStream()->expect('in');
+ $seq = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideForFork'));
+ if ($this->parser->getStream()->next()->getValue() == 'else')
+ {
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $else = $this->parser->subparse(array($this, 'decideForEnd'), true);
+ }
+ else
+ {
+ $else = null;
+ }
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_For($is_multitarget, $item, $seq, $body, $else, $lineno, $this->getTag());
+ }
+
+ public function decideForFork($token)
+ {
+ return $token->test(array('else', 'endfor'));
+ }
+
+ public function decideForEnd($token)
+ {
+ return $token->test('endfor');
+ }
+
+ public function getTag()
+ {
+ return 'for';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_If extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
+ $tests = array(array($expr, $body));
+ $else = null;
+
+ $end = false;
+ while (!$end)
+ {
+ switch ($this->parser->getStream()->next()->getValue())
+ {
+ case 'else':
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $else = $this->parser->subparse(array($this, 'decideIfEnd'));
+ break;
+
+ case 'elseif':
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
+ $tests[] = array($expr, $body);
+ break;
+
+ case 'endif':
+ $end = true;
+ break;
+ }
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_If($tests, $else, $lineno, $this->getTag());
+ }
+
+ public function decideIfFork($token)
+ {
+ return $token->test(array('elseif', 'else', 'endif'));
+ }
+
+ public function decideIfEnd($token)
+ {
+ return $token->test(array('endif'));
+ }
+
+ public function getTag()
+ {
+ return 'if';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Include extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $expr = $this->parser->getExpressionParser()->parseExpression();
+
+ $sandboxed = false;
+ if ($this->parser->getStream()->test(Twig_Token::NAME_TYPE))
+ {
+ $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
+ $sandboxed = true;
+ }
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Include($expr, $sandboxed, $token->getLine(), $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'include';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Macro extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+
+ // arguments
+
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Macro($name, $body, $lineno, $this->getTag());
+ }
+
+ public function decideBlockEnd($token)
+ {
+ return $token->test('endmacro');
+ }
+
+ public function getTag()
+ {
+ return 'macro';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenParser_Parent extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ if (null === $this->parser->getCurrentBlock())
+ {
+ throw new Twig_SyntaxError('Calling "parent" outside a block is forbidden', $token->getLine());
+ }
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Parent($this->parser->getCurrentBlock(), $token->getLine(), $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'parent';
+ }
+}
--- /dev/null
+<?php
+
+class Twig_TokenParser_Set extends Twig_TokenParser
+{
+ public function parse(Twig_Token $token)
+ {
+ $lineno = $token->getLine();
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
+ $value = $this->parser->getExpressionParser()->parseExpression();
+
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
+
+ return new Twig_Node_Set($name, $value, $lineno, $this->getTag());
+ }
+
+ public function getTag()
+ {
+ return 'set';
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+class Twig_TokenStream
+{
+ protected $pushed;
+ protected $originalTokens;
+ protected $tokens;
+ protected $eof;
+ protected $current;
+ protected $filename;
+ protected $trimBlocks;
+
+ public function __construct(array $tokens, $filename, $trimBlocks = true)
+ {
+ $this->pushed = array();
+ $this->originalTokens = $tokens;
+ $this->tokens = $tokens;
+ $this->filename = $filename;
+ $this->trimBlocks = $trimBlocks;
+ $this->next();
+ }
+
+ public function __toString()
+ {
+ $repr = '';
+ foreach ($this->originalTokens as $token)
+ {
+ $repr .= $token."\n";
+ }
+
+ return $repr;
+ }
+
+ public function push($token)
+ {
+ $this->pushed[] = $token;
+ }
+
+ /**
+ * Sets the pointer to the next token and returns the old one.
+ */
+ public function next()
+ {
+ if (!empty($this->pushed))
+ {
+ $token = array_shift($this->pushed);
+ }
+ else
+ {
+ $token = array_shift($this->tokens);
+ }
+
+ // trim blocks
+ if ($this->trimBlocks &&
+ $this->current &&
+ Twig_Token::BLOCK_END_TYPE === $this->current->getType() &&
+ Twig_Token::TEXT_TYPE === $token->getType() &&
+ $token->getValue() &&
+ '\n' === substr($token->getValue(), 0, 2)
+ )
+ {
+ $value = substr($token->getValue(), 2);
+
+ if (!$value)
+ {
+ return $this->next();
+ }
+
+ $token->setValue($value);
+ }
+
+ $old = $this->current;
+ $this->current = $token;
+
+ $this->eof = $token->getType() === Twig_Token::EOF_TYPE;
+
+ return $old;
+ }
+
+ /**
+ * Looks at the next token.
+ */
+ public function look()
+ {
+ $old = $this->next();
+ $new = $this->current;
+ $this->push($old);
+ $this->push($new);
+
+ return $new;
+ }
+
+ /**
+ * Expects a token (like $token->test()) and returns it or throw a syntax error.
+ */
+ public function expect($primary, $secondary = null)
+ {
+ $token = $this->current;
+ if (!$token->test($primary, $secondary))
+ {
+ throw new Twig_SyntaxError(sprintf('Unexpected token %s (%s expected, value: %s)', Twig_Token::getTypeAsString($token->getType()), Twig_Token::getTypeAsString($primary), $token->getValue()), $this->current->getLine());
+ }
+ $this->next();
+
+ return $token;
+ }
+
+ /**
+ * Forwards that call to the current token.
+ */
+ public function test($primary, $secondary = null)
+ {
+ return $this->current->test($primary, $secondary);
+ }
+
+ public function isEOF()
+ {
+ return $this->eof;
+ }
+
+ public function getCurrent()
+ {
+ return $this->current;
+ }
+
+ public function getFilename()
+ {
+ return $this->filename;
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+function twig_date_format_filter($timestamp, $format = 'F j, Y H:i')
+{
+ return date($format, $timestamp);
+}
+
+function twig_urlencode_filter($url, $raw = false)
+{
+ if ($raw)
+ {
+ return rawurlencode($url);
+ }
+
+ return urlencode($url);
+}
+
+function twig_join_filter($value, $glue = '')
+{
+ return implode($glue, (array) $value);
+}
+
+function twig_default_filter($value, $default = '')
+{
+ return is_null($value) ? $default : $value;
+}
+
+function twig_get_array_keys_filter($array)
+{
+ if (is_object($array) && $array instanceof Iterator)
+ {
+ $keys = array();
+ foreach ($array as $key => $value)
+ {
+ $keys[] = $key;
+ }
+
+ return $keys;
+ }
+
+ if (!is_array($array))
+ {
+ return array();
+ }
+
+ return array_keys($array);
+}
+
+function twig_reverse_filter($array)
+{
+ if (is_object($array) && $array instanceof Iterator)
+ {
+ $values = array();
+ foreach ($array as $value)
+ {
+ $values[] = $value;
+ }
+
+ return array_reverse($values);
+ }
+
+ if (!is_array($array))
+ {
+ return array();
+ }
+
+ return array_reverse($array);
+}
+
+function twig_is_even_filter($value)
+{
+ return $value % 2 == 0;
+}
+
+function twig_is_odd_filter($value)
+{
+ return $value % 2 == 1;
+}
+
+function twig_length_filter($thing)
+{
+ return is_string($thing) ? strlen($thing) : count($thing);
+}
+
+function twig_sort_filter($array)
+{
+ asort($array);
+
+ return $array;
+}
+
+// add multibyte extensions if possible
+if (function_exists('mb_get_info'))
+{
+ function twig_upper_filter(Twig_TemplateInterface $template, $string)
+ {
+ if (!is_null($template->getEnvironment()->getCharset()))
+ {
+ return mb_strtoupper($string, $template->getEnvironment()->getCharset());
+ }
+
+ return strtoupper($string);
+ }
+
+ function twig_lower_filter(Twig_TemplateInterface $template, $string)
+ {
+ if (!is_null($template->getEnvironment()->getCharset()))
+ {
+ return mb_strtolower($string, $template->getEnvironment()->getCharset());
+ }
+
+ return strtolower($string);
+ }
+
+ function twig_title_string_filter(Twig_TemplateInterface $template, $string)
+ {
+ if (is_null($template->getEnvironment()->getCharset()))
+ {
+ return ucwords(strtolower($string));
+ }
+
+ return mb_convert_case($string, MB_CASE_TITLE, $template->getEnvironment()->getCharset());
+ }
+
+ function twig_capitalize_string_filter(Twig_TemplateInterface $template, $string)
+ {
+ if (is_null($template->getEnvironment()->getCharset()))
+ {
+ return ucfirst(strtolower($string));
+ }
+
+ return mb_strtoupper(mb_substr($string, 0, 1, $template->getEnvironment()->getCharset())).
+ mb_strtolower(mb_substr($string, 1, mb_strlen($string), $template->getEnvironment()->getCharset()));
+ }
+}
+// and byte fallback
+else
+{
+ function twig_title_string_filter(Twig_TemplateInterface $template, $string)
+ {
+ return ucwords(strtolower($string));
+ }
+
+ function twig_capitalize_string_filter(Twig_TemplateInterface $template, $string)
+ {
+ return ucfirst(strtolower($string));
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+// tells the escaper node transformer that the string is safe
+function twig_safe_filter($string)
+{
+ return $string;
+}
+
+function twig_escape_filter(Twig_TemplateInterface $template, $string)
+{
+ if (!is_string($string))
+ {
+ return $string;
+ }
+
+ return htmlspecialchars($string, ENT_QUOTES, $template->getEnvironment()->getCharset());
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ * (c) 2009 Armin Ronacher
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class Twig_LoopContextIterator implements Iterator
+{
+ public $context;
+ public $seq;
+ public $idx;
+ public $length;
+ public $parent;
+
+ public function __construct(&$context, $seq, $parent)
+ {
+ $this->context = $context;
+ $this->seq = $seq;
+ $this->length = count($this->seq);
+ $this->parent = $parent;
+ }
+
+ public function rewind()
+ {
+ $this->idx = 0;
+ }
+
+ public function key()
+ {
+ }
+
+ public function valid()
+ {
+ return $this->idx < $this->length;
+ }
+
+ public function next()
+ {
+ $this->idx++;
+ }
+
+ public function current()
+ {
+ return $this;
+ }
+}
+
+function twig_iterate(&$context, $seq)
+{
+ $parent = isset($context['loop']) ? $context['loop'] : null;
+
+ // convert the sequence to an array of values
+ // we convert Iterators as an array
+ // as our iterator access the sequence as an array
+ if (is_array($seq))
+ {
+ $array = array_values($seq);
+ }
+ elseif (is_object($seq) && $seq instanceof Iterator)
+ {
+ $array = array();
+ foreach ($seq as $value)
+ {
+ $array[] = $value;
+ }
+ }
+ else
+ {
+ $array = array();
+ }
+
+ $context['loop'] = array('parent' => $parent);
+
+ return new Twig_LoopContextIterator($context, $array, $parent);
+}
+
+function twig_set_loop_context(&$context, $iterator, $target)
+{
+ if (is_array($target))
+ {
+ foreach (array_combine($target, $iterator->seq[$iterator->idx]) as $key => $value)
+ {
+ $context[$key] = $value;
+ }
+ }
+ else
+ {
+ $context[$target] = $iterator->seq[$iterator->idx];
+ }
+
+ $context['loop'] = array(
+ 'parent' => $iterator->parent,
+ 'length' => $iterator->length,
+ 'index0' => $iterator->idx,
+ 'index' => $iterator->idx + 1,
+ 'revindex0' => $iterator->length - $iterator->idx - 1,
+ 'revindex' => $iterator->length - $iterator->idx,
+ 'first' => $iterator->idx == 0,
+ 'last' => $iterator->idx + 1 == $iterator->length,
+ );
+}
+
+function twig_get_array_items_filter($array)
+{
+ if (!is_array($array) && is_object($array) && !$array instanceof Iterator)
+ {
+ return array(array(), array());
+ }
+
+ $result = array();
+ foreach ($array as $key => $value)
+ {
+ $result[] = array($key, $value);
+ }
+
+ return $result;
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once(dirname(__FILE__).'/../lib/lime/LimeAutoloader.php');
+LimeAutoloader::register();
+
+$suite = new LimeTestSuite(array(
+ 'force_colors' => isset($argv) && in_array('--color', $argv),
+ 'base_dir' => realpath(dirname(__FILE__).'/..'),
+));
+
+foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__).'/../unit'), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
+{
+ if (preg_match('/Test\.php$/', $file))
+ {
+ $suite->register($file->getRealPath());
+ }
+}
+
+$coverage = new LimeCoverage($suite, array(
+ 'base_dir' => realpath(dirname(__FILE__).'/../../lib'),
+ 'extension' => '.php',
+ 'verbose' => true,
+));
+
+$files = array();
+foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__).'/../../lib'), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
+{
+ if (preg_match('/\.php$/', $file))
+ {
+ $files[] = $file->getRealPath();
+ }
+}
+$coverage->setFiles($files);
+
+$coverage->run();
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once(dirname(__FILE__).'/../lib/lime/LimeAutoloader.php');
+LimeAutoloader::register();
+
+$suite = new LimeTestSuite(array(
+ 'force_colors' => isset($argv) && in_array('--color', $argv),
+ 'base_dir' => realpath(dirname(__FILE__).'/..'),
+));
+
+foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__).'/../unit'), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
+{
+ if (preg_match('/Test\.php$/', $file))
+ {
+ $suite->register($file->getRealPath());
+ }
+}
+
+exit($suite->run() ? 0 : 1);
--- /dev/null
+--TEST--
+Twig supports binary operations (+, -, *, /, ~, %, and, or)
+--TEMPLATE--
+{{ 1 + 1 }}
+{{ 2 - 1 }}
+{{ 2 * 2 }}
+{{ 2 / 2 }}
+{{ 3 % 2 }}
+{{ 1 and 1 }}
+{{ 1 and 0 }}
+{{ 0 and 1 }}
+{{ 0 and 0 }}
+{{ 1 or 1 }}
+{{ 1 or 0 }}
+{{ 0 or 1 }}
+{{ 0 or 0 }}
+{{ "foo" ~ "bar" }}
+{{ foo ~ "bar" }}
+{{ "foo" ~ bar }}
+{{ foo ~ bar }}
+--DATA--
+return array('foo' => 'bar', 'bar' => 'foo')
+--EXPECT--
+2
+1
+4
+1
+1
+1
+
+
+
+1
+1
+1
+
+foobar
+barbar
+foofoo
+barfoo
--- /dev/null
+--TEST--
+Twig supports comparison operators (==, !=, <, >, >=, <=)
+--TEMPLATE--
+{{ 1 > 2 }}/{{ 1 > 1 }}/{{ 1 >= 2 }}/{{ 1 >= 1 }}
+{{ 1 < 2 }}/{{ 1 < 1 }}/{{ 1 <= 2 }}/{{ 1 <= 1 }}
+{{ 1 == 1 }}/{{ 1 == 2 }}
+{{ 1 != 1 }}/{{ 1 != 2 }}
+{{ 1 < 2 < 3 }}/{{ 1 < 2 < 3 < 4 }}
+--DATA--
+return array()
+--EXPECT--
+///1
+1//1/1
+1/
+/1
+1/1
--- /dev/null
+--TEST--
+Twig supports grouping of expressions
+--TEMPLATE--
+{{ (2 + 2) / 2 }}
+--DATA--
+return array()
+--EXPECT--
+2
--- /dev/null
+--TEST--
+Twig supports the ternary operator
+--TEMPLATE--
+{{ 1 ? 'YES' : 'NO' }}
+{{ 0 ? 'YES' : 'NO' }}
+{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }}
+{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }}
+--DATA--
+return array()
+--EXPECT--
+YES
+NO
+YES1
+NO1
--- /dev/null
+--TEST--
+Twig supports unary operators (not, -, +)
+--TEMPLATE--
+{{ not 1 }}/{{ not 0 }}
+{{ +1 + 1 }}/{{ -1 - 1 }}
+--DATA--
+return array()
+--EXPECT--
+/1
+2/-2
--- /dev/null
+--TEST--
+"date" filter
+--TEMPLATE--
+{{ date1|date }}
+{{ date1|date('d/m/Y') }}
+--DATA--
+return array('date1' => mktime(13, 45, 0, 10, 4, 2010))
+--EXPECT--
+October 4, 2010 13:45
+04/10/2010
--- /dev/null
+--TEST--
+"default" filter
+--TEMPLATE--
+{{ foo|default('bar') }}
+{{ bar|default('foo') }}
+--DATA--
+return array('foo' => null, 'bar' => 'bar')
+--EXPECT--
+bar
+bar
--- /dev/null
+--TEST--
+"even" filter
+--TEMPLATE--
+{{ 1|even }}
+{{ 2|even }}
+--DATA--
+return array()
+--EXPECT--
+1
--- /dev/null
+--TEST--
+"format" filter
+--TEMPLATE--
+{{ string|format(foo, 3) }}
+--DATA--
+return array('string' => '%s/%d', 'foo' => 'bar')
+--EXPECT--
+bar/3
--- /dev/null
+--TEST--
+"length" filter
+--TEMPLATE--
+{{ array|length }}
+{{ string|length }}
+--DATA--
+return array('array' => array(1, 4), 'string' => 'foo')
+--EXPECT--
+2
+3
--- /dev/null
+--TEST--
+"odd" filter
+--TEMPLATE--
+{{ 1|odd }}
+{{ 2|odd }}
+--DATA--
+return array()
+--EXPECT--
+
+1
--- /dev/null
+--TEST--
+"sort" filter
+--TEMPLATE--
+{{ array1|sort|join }}
+{{ array2|sort|join }}
+--DATA--
+return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'))
+--EXPECT--
+14
+barfoo
--- /dev/null
+--TEST--
+"autoescape" tag applies escaping on its children
+--TEMPLATE--
+{% autoescape on %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape off %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape on %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape off %}
+{{ var }}<br />
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+<br /><br />
+<br /><br />
+<br /><br />
+<br /><br />
--- /dev/null
+--TEST--
+"autoescape" tag does not double-escape
+--TEMPLATE--
+{% autoescape on %}
+{{ var|escape }}
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+<br />
--- /dev/null
+--TEST--
+"autoescape" tags can be nested at will
+--TEMPLATE--
+{{ var }}
+{% autoescape on %}
+ {{ var }}
+ {% autoescape off %}
+ {{ var }}
+ {% autoescape on %}
+ {{ var }}
+ {% endautoescape %}
+ {{ var }}
+ {% endautoescape %}
+ {{ var }}
+{% endautoescape %}
+{{ var }}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+<br />
+ <br />
+ <br />
+ <br />
+ <br />
+ <br />
+<br />
--- /dev/null
+--TEST--
+"autoescape" tag does not escape when raw is used as a filter
+--TEMPLATE--
+{% autoescape on %}
+{{ var|safe }}
+{% endautoescape %}
+{% autoescape on %}
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+<br />
--- /dev/null
+--TEST--
+"filter" tag applies a filter on its children
+--TEMPLATE--
+{% filter upper %}
+Some text with a {{ var }}
+{% endfilter %}
+--DATA--
+return array('var' => 'var')
+--EXPECT--
+SOME TEXT WITH A VAR
--- /dev/null
+--TEST--
+"filter" tags can be nested at will
+--TEMPLATE--
+{% filter capitalize %}
+ {{ var }}
+ {% filter upper %}
+ {{ var }}
+ {% endfilter %}
+ {{ var }}
+{% endfilter %}
+--DATA--
+return array('var' => 'var')
+--EXPECT--
+ Var
+ VAR
+ Var
--- /dev/null
+--TEST--
+"filter" tag applies the filter on "for" tags
+--TEMPLATE--
+{% filter upper %}
+{% for item in items %}
+{{ item }}
+{% endfor %}
+{% endfilter %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+A
+B
--- /dev/null
+--TEST--
+"filter" tag applies the filter on "if" tags
+--TEMPLATE--
+{% filter upper %}
+{% if items %}
+{{ items|join(', ') }}
+{% endif %}
+
+{% if items.3 %}
+FOO
+{% else %}
+{{ items.1 }}
+{% endif %}
+
+{% if items.3 %}
+FOO
+{% elseif items.1 %}
+{{ items.0 }}
+{% endif %}
+
+{% endfilter %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+A, B
+
+B
+
+A
--- /dev/null
+--TEST--
+"for" tag keeps the context safe
+--TEMPLATE--
+{% for item in items %}
+ {% for item in items %}
+ * {{ item }}
+ {% endfor %}
+ * {{ item }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * a
+ * b
+ * a
+ * a
+ * b
+ * b
--- /dev/null
+--TEST--
+"for" tag can use an "else" clause
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+{% else %}
+ no item
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * a
+ * b
+--DATA--
+return array('items' => array())
+--EXPECT--
+ no item
+--DATA--
+return array()
+--EXPECT--
+ no item
--- /dev/null
+--TEST--
+"for" tag can iterate over keys
+--TEMPLATE--
+{% for key in items|keys %}
+ * {{ key }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * 0
+ * 1
--- /dev/null
+--TEST--
+"for" tag can iterate over keys and values
+--TEMPLATE--
+{% for key, item in items|items %}
+ * {{ key }}/{{ item }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * 0/a
+ * 1/b
--- /dev/null
+--TEST--
+"for" tag adds a loop variable to the context
+--TEMPLATE--
+{% for item in items %}
+ * {{ loop.index }}/{{ loop.index0 }}
+ * {{ loop.revindex }}/{{ loop.revindex0 }}
+ * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
+
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * 1/0
+ * 2/1
+ * 1//2
+
+ * 2/1
+ * 1/0
+ * /1/2
--- /dev/null
+--TEST--
+"for" tag adds a loop variable to the context locally
+--TEMPLATE--
+{% for item in items %}
+{% endfor %}
+{% if not loop %}WORKS{% endif %}
+--DATA--
+return array('items' => array())
+--EXPECT--
+WORKS
--- /dev/null
+--TEST--
+"for" tag can use an "else" clause
+--TEMPLATE--
+{% for item in items %}
+ {% for item in items1 %}
+ * {{ item }}
+ {% else %}
+ no {{ item }}
+ {% endfor %}
+{% else %}
+ no item1
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'), 'items1' => array())
+--EXPECT--
+no a
+ no b
--- /dev/null
+--TEST--
+"for" tag iterates over iterable objects
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+{% endfor %}
+
+{% for key, value in items|items %}
+ * {{ key }}/{{ value }}
+{% endfor %}
+
+{% for key in items|keys %}
+ * {{ key }}
+{% endfor %}
+--DATA--
+class ItemsIterator implements Iterator
+{
+ protected $values = array('foo' => 'bar', 'bar' => 'foo');
+ public function current() { return current($this->values); }
+ public function key() { return key($this->values); }
+ public function next() { return next($this->values); }
+ public function rewind() { return reset($this->values); }
+ public function valid() { return false !== current($this->values); }
+}
+return array('items' => new ItemsIterator())
+--EXPECT--
+ * bar
+ * foo
+
+ * foo/bar
+ * bar/foo
+
+ * foo
+ * bar
--- /dev/null
+--TEST--
+"for" tags can be nested
+--TEMPLATE--
+{% for key, item in items|items %}
+* {{ key }} ({{ loop.length }}):
+{% for value in item %}
+ * {{ value }} ({{ loop.length }})
+{% endfor %}
+{% endfor %}
+--DATA--
+return array('items' => array('a' => array('a1', 'a2', 'a3'), 'b' => array('b1')))
+--EXPECT--
+* a (2):
+ * a1 (3)
+ * a2 (3)
+ * a3 (3)
+* b (2):
+ * b1 (1)
--- /dev/null
+--TEST--
+"for" tag iterates over item values
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * a
+ * b
--- /dev/null
+--TEST--
+"if" creates a condition
+--TEMPLATE--
+{% if a %}
+ {{ a }}
+{% elseif b %}
+ {{ b }}
+{% else %}
+ NOTHING
+{% endif %}
+--DATA--
+return array('a' => 'a')
+--EXPECT--
+ a
+--DATA--
+return array('b' => 'b')
+--EXPECT--
+ b
+--DATA--
+return array()
+--EXPECT--
+ NOTHING
--- /dev/null
+--TEST--
+"if" takes an expression as a test
+--TEMPLATE--
+{% if a < 2 %}
+ A1
+{% elseif a > 10 %}
+ A2
+{% else %}
+ A3
+{% endif %}
+--DATA--
+return array('a' => 1)
+--EXPECT--
+ A1
+--DATA--
+return array('a' => 12)
+--EXPECT--
+ A2
+--DATA--
+return array('a' => 7)
+--EXPECT--
+ A3
--- /dev/null
+--TEST--
+"include" tag
+--TEMPLATE--
+FOO
+{% include "%prefix%foo.twig" %}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+FOOBAR
+BAR
--- /dev/null
+--TEST--
+"include" tag allows expressions for the template to include
+--TEMPLATE--
+FOO
+{% include "%prefix%" ~ foo %}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array('foo' => 'foo.twig')
+--EXPECT--
+FOO
+
+FOOBAR
+BAR
--- /dev/null
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "%prefix%foo.twig" %}
+
+{% block content %}
+FOO
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+FOO
--- /dev/null
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "%prefix%foo.twig" %}
+
+{% block content %}{% parent %}FOO{% parent %}{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}BAR{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+BARFOOBAR
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) 2009 Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * Loads a template from variables.
+ *
+ * @package twig
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ * @version SVN: $Id$
+ */
+class Twig_Loader_Var extends Twig_Loader
+{
+ protected $templates;
+ protected $prefix;
+
+ public function __construct(array $templates, $prefix)
+ {
+ $this->prefix = $prefix;
+ $this->templates = array();
+ foreach ($templates as $name => $template)
+ {
+ $this->templates[$this->prefix.'_'.$name] = $template;
+ }
+ }
+
+ public function getSource($name)
+ {
+ if (!isset($this->templates[$this->prefix.'_'.$name]))
+ {
+ throw new LogicException(sprintf('Template "%s" is not defined.', $name));
+ }
+
+ return array(str_replace('%prefix%', $this->prefix.'_', $this->templates[$this->prefix.'_'.$name]), false);
+ }
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once(dirname(__FILE__).'/../../lib/lime/LimeAutoloader.php');
+LimeAutoloader::register();
+
+require_once dirname(__FILE__).'/../../../lib/Twig/Autoloader.php';
+Twig_Autoloader::register();
+
+$t = new LimeTest(3);
+
+// ->autoload()
+$t->diag('->autoload()');
+
+$t->ok(!class_exists('Foo'), '->autoload() does not try to load classes that does not begin with Twig');
+
+$autoloader = new Twig_Autoloader();
+$t->is($autoloader->autoload('Twig_Parser'), true, '->autoload() returns true if it is able to load a class');
+$t->is($autoloader->autoload('Foo'), false, '->autoload() returns false if it is not able to load a class');
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once(dirname(__FILE__).'/../../../lib/lime/LimeAutoloader.php');
+LimeAutoloader::register();
+
+require_once dirname(__FILE__).'/../../../../lib/Twig/Autoloader.php';
+Twig_Autoloader::register();
+
+require_once dirname(__FILE__).'/../../../lib/Twig_Loader_Var.php';
+
+class Object
+{
+ public function foo()
+ {
+ return 'foo';
+ }
+}
+
+$params = array(
+ 'name' => 'Fabien',
+ 'obj' => new Object(),
+);
+$templates = array(
+ '1_basic1' => '{{ obj.foo }}',
+ '1_basic2' => '{{ name|upper }}',
+ '1_basic3' => '{% if name %}foo{% endif %}',
+ '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
+);
+
+$t = new LimeTest(9);
+
+$t->diag('Sandbox globally set');
+$twig = get_environment(false, $templates);
+$t->is($twig->loadTemplate('1_basic')->render($params), 'FOO', 'Sandbox does nothing if it is disabled globally');
+
+$twig = get_environment(true, $templates);
+try
+{
+ $twig->loadTemplate('1_basic1')->render($params);
+ $t->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
+}
+catch (Twig_Sandbox_SecurityError $e)
+{
+ $t->pass('Sandbox throws a SecurityError exception if an unallowed method is called');
+}
+
+$twig = get_environment(true, $templates);
+try
+{
+ $twig->loadTemplate('1_basic2')->render($params);
+ $t->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
+}
+catch (Twig_Sandbox_SecurityError $e)
+{
+ $t->pass('Sandbox throws a SecurityError exception if an unallowed filter is called');
+}
+
+$twig = get_environment(true, $templates);
+try
+{
+ $twig->loadTemplate('1_basic3')->render($params);
+ $t->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
+}
+catch (Twig_Sandbox_SecurityError $e)
+{
+ $t->pass('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
+}
+
+$twig = get_environment(true, $templates, array(), array(), array('Object' => 'foo'));
+$t->is($twig->loadTemplate('1_basic1')->render($params), 'foo', 'Sandbox allow some methods');
+
+$twig = get_environment(true, $templates, array(), array('upper'));
+$t->is($twig->loadTemplate('1_basic2')->render($params), 'FABIEN', 'Sandbox allow some filters');
+
+$twig = get_environment(true, $templates, array('if'));
+$t->is($twig->loadTemplate('1_basic3')->render($params), 'foo', 'Sandbox allow some tags');
+
+$t->diag('Sandbox locally set for an include');
+
+$templates = array(
+ '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
+ '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
+);
+
+$twig = get_environment(false, $templates);
+$t->is($twig->loadTemplate('2_basic')->render($params), 'fooFOOfoo', 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
+
+$templates = array(
+ '3_basic' => '{{ obj.foo }}{% include "3_included" sandboxed %}{{ obj.foo }}',
+ '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
+);
+
+$twig = get_environment(false, $templates);
+$twig = get_environment(true, $templates);
+try
+{
+ $twig->loadTemplate('3_basic')->render($params);
+ $t->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
+}
+catch (Twig_Sandbox_SecurityError $e)
+{
+ $t->pass('Sandbox throws a SecurityError exception when the included file is sandboxed');
+}
+
+
+function get_environment($sandboxed, $templates, $tags = array(), $filters = array(), $methods = array())
+{
+ static $prefix = 0;
+
+ ++$prefix;
+
+ $loader = new Twig_Loader_Var($templates, $prefix);
+ $twig = new Twig_Environment($loader, array('trim_blocks' => true, 'debug' => true));
+ $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods);
+ $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
+
+ return $twig;
+}
--- /dev/null
+<?php
+
+/*
+ * This file is part of Twig.
+ *
+ * (c) Fabien Potencier
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once(dirname(__FILE__).'/../lib/lime/LimeAutoloader.php');
+LimeAutoloader::register();
+
+require_once dirname(__FILE__).'/../../lib/Twig/Autoloader.php';
+Twig_Autoloader::register();
+
+require_once dirname(__FILE__).'/../lib/Twig_Loader_Var.php';
+
+$t = new LimeTest(42);
+$fixturesDir = realpath(dirname(__FILE__).'/../fixtures/');
+
+foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
+{
+ if (!preg_match('/\.test$/', $file))
+ {
+ continue;
+ }
+
+ $test = file_get_contents($file->getRealpath());
+
+ if (!preg_match('/--TEST--\s*(.*?)\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match))
+ {
+ throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file)));
+ }
+
+ $prefix = rand(1, 999999999);
+ $message = $match[1];
+ $templates = array();
+ preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $match[2], $matches, PREG_SET_ORDER);
+ foreach ($matches as $match)
+ {
+ $templates[$prefix.'_'.($match[1] ? $match[1] : 'index.twig')] = $match[2];
+ }
+
+ $loader = new Twig_Loader_Var($templates, $prefix);
+ $twig = new Twig_Environment($loader, array('trim_blocks' => true));
+ $twig->addExtension(new Twig_Extension_Escaper());
+
+ $template = $twig->loadTemplate($prefix.'_index.twig');
+
+ preg_match_all('/--DATA--(.*?)--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match)
+ {
+ $output = trim($template->render(eval($match[1].';')), "\n ");
+ $expected = trim($match[2], "\n ");
+
+ $t->is($output, $expected, $message);
+ if ($output != $expected)
+ {
+ $t->comment('Compiled template that failed:');
+
+ foreach (array_keys($templates) as $name)
+ {
+ list($source, ) = $loader->getSource($name);
+ $t->comment($twig->compile($twig->parse($twig->tokenize($source))));
+ }
+ }
+ }
+}