summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2014-10-08 09:41:30 +0200
committerGeorg Brandl <georg@python.org>2014-10-08 09:41:30 +0200
commit1df8062fcb726c0f7e6f7985e6279d51d1190922 (patch)
tree9a1911d811102ab9d503f7dcf9b86d3340917a52
parentda727b436e7983ba042a71089848253fe0ef8a4f (diff)
downloadpygments-1df8062fcb726c0f7e6f7985e6279d51d1190922.tar.gz
Twig: add test example file, add changelog entry and regenerate mapping.
-rw-r--r--CHANGES1
-rw-r--r--pygments/lexers/_mapping.py2
-rw-r--r--pygments/lexers/templates.py47
-rw-r--r--tests/examplefiles/twig_test4612
4 files changed, 4642 insertions, 20 deletions
diff --git a/CHANGES b/CHANGES
index 85293b4a..b9a68936 100644
--- a/CHANGES
+++ b/CHANGES
@@ -68,6 +68,7 @@ Version 2.0
* Swift (PR#371)
* Swig (PR#168)
* Todo.txt todo lists
+ * Twig (PR#404)
- Added a helper to "optimize" regular expressions that match one of many
literal words; this can save 20% and more lexing time with lexers that
diff --git a/pygments/lexers/_mapping.py b/pygments/lexers/_mapping.py
index 92dc45f8..8ee280ca 100644
--- a/pygments/lexers/_mapping.py
+++ b/pygments/lexers/_mapping.py
@@ -336,6 +336,8 @@ LEXERS = {
'TextLexer': ('pygments.lexers.special', 'Text only', ('text',), ('*.txt',), ('text/plain',)),
'TodotxtLexer': ('pygments.lexers.textfmts', 'Todotxt', ('todotxt',), ('todo.txt', '*.todotxt'), ('text/x-todo',)),
'TreetopLexer': ('pygments.lexers.parsers', 'Treetop', ('treetop',), ('*.treetop', '*.tt'), ()),
+ 'TwigHtmlLexer': ('pygments.lexers.templates', 'HTML+Twig', ('html+twig',), ('*.twig',), ('text/html+twig',)),
+ 'TwigLexer': ('pygments.lexers.templates', 'Twig', ('twig',), (), ('application/x-twig',)),
'TypeScriptLexer': ('pygments.lexers.javascript', 'TypeScript', ('ts',), ('*.ts',), ('text/x-typescript',)),
'UrbiscriptLexer': ('pygments.lexers.urbi', 'UrbiScript', ('urbiscript',), ('*.u',), ('application/x-urbiscript',)),
'VCTreeStatusLexer': ('pygments.lexers.console', 'VCTreeStatus', ('vctreestatus',), (), ()),
diff --git a/pygments/lexers/templates.py b/pygments/lexers/templates.py
index 7077399d..029a7164 100644
--- a/pygments/lexers/templates.py
+++ b/pygments/lexers/templates.py
@@ -43,7 +43,8 @@ __all__ = ['HtmlPhpLexer', 'XmlPhpLexer', 'CssPhpLexer',
'VelocityHtmlLexer', 'VelocityXmlLexer', 'SspLexer',
'TeaTemplateLexer', 'LassoHtmlLexer', 'LassoXmlLexer',
'LassoCssLexer', 'LassoJavascriptLexer', 'HandlebarsLexer',
- 'HandlebarsHtmlLexer', 'YamlJinjaLexer', 'LiquidLexer', 'TwigLexer']
+ 'HandlebarsHtmlLexer', 'YamlJinjaLexer', 'LiquidLexer',
+ 'TwigLexer', 'TwigHtmlLexer']
class ErbLexer(Lexer):
@@ -2071,7 +2072,8 @@ class LiquidLexer(RegexLexer):
bygroups(Punctuation, Whitespace, Name.Tag, Whitespace,
Punctuation), '#pop'),
(r'\{', Text)
- ]
+ ],
+ }
class TwigLexer(RegexLexer):
@@ -2080,6 +2082,8 @@ class TwigLexer(RegexLexer):
It just highlights Twig code between the preprocessor directives,
other data is left untouched by the lexer.
+
+ .. versionadded:: 2.0
"""
name = 'Twig'
@@ -2094,9 +2098,6 @@ class TwigLexer(RegexLexer):
(r'\{\{', Comment.Preproc, 'var'),
# twig comments
(r'\{\#.*?\#\}', Comment),
- bygroups(Comment.Preproc, Text, Keyword, Text, Comment.Preproc,
- Comment, Comment.Preproc, Text, Keyword, Text,
- Comment.Preproc)),
# raw twig blocks
(r'(\{%)(-?\s*)(raw|verbatim)(\s*-?)(%\})(.*?)'
r'(\{%)(-?\s*)(endraw|endverbatim)(\s*-?)(%\})',
@@ -2108,7 +2109,8 @@ class TwigLexer(RegexLexer):
bygroups(Comment.Preproc, Text, Keyword, Text, Name.Function),
'block'),
(r'(\{%)(-?\s*)([a-zA-Z_]\w*)',
- bygroups(Comment.Preproc, Text, Keyword), 'block')
+ bygroups(Comment.Preproc, Text, Keyword), 'block'),
+ (r'\{', Other),
],
'varnames': [
(r'(\|)(\s*)([a-zA-Z_]\w*)',
@@ -2117,7 +2119,7 @@ class TwigLexer(RegexLexer):
bygroups(Keyword, Text, Keyword, Text, Name.Function)),
(r'(true|false|none|null)\b', Keyword.Pseudo),
(r'(in|not|and|b-and|or|b-or|b-xor|is'
- r'if|else|elseif|import'
+ r'if|elseif|else|import'
r'constant|defined|divisibleby|empty|even|iterable|odd|sameas'
r'matches|starts\s+with|ends\s+with)\b',
Keyword),
@@ -2126,7 +2128,7 @@ class TwigLexer(RegexLexer):
(r'\.\w+', Name.Variable),
(r':?"(\\\\|\\"|[^"])*"', String.Double),
(r":?'(\\\\|\\'|[^'])*'", String.Single),
- (r'([{}()\[\]+\-*/,:~%]|\.\.|\?:|\*\*|\/\/|!=|[><=]=?)', Operator),
+ (r'([{}()\[\]+\-*/,:~%]|\.\.|\?|:|\*\*|\/\/|!=|[><=]=?)', Operator),
(r"[0-9](\.[0-9]*)?(eE[+-][0-9])?[flFLdD]?|"
r"0[xX][0-9a-fA-F]+[Ll]?", Number),
],
@@ -2139,18 +2141,23 @@ class TwigLexer(RegexLexer):
(r'\s+', Text),
(r'(-?)(%\})', bygroups(Text, Comment.Preproc), '#pop'),
include('varnames'),
- (r'.', Punctuation)
- ]
+ (r'.', Punctuation),
+ ],
}
- def analyse_text(text):
- rv = 0.0
- if re.search(r'\{%\s*(block|extends)', text) is not None:
- rv += 0.4
- if re.search(r'\{%\s*if\s*.*?%\}', text) is not None:
- rv += 0.1
- if re.search(r'\{\{.*?\}\}', text) is not None:
- rv += 0.1
- return rv
-} \ No newline at end of file
+class TwigHtmlLexer(DelegatingLexer):
+ """
+ Subclass of the `TwigLexer` that highlights unlexed data with the
+ `HtmlLexer`.
+
+ .. versionadded:: 2.0
+ """
+
+ name = "HTML+Twig"
+ aliases = ["html+twig"]
+ filenames = ['*.twig']
+ mimetypes = ['text/html+twig']
+
+ def __init__(self, **options):
+ super(TwigHtmlLexer, self).__init__(HtmlLexer, TwigLexer, **options)
diff --git a/tests/examplefiles/twig_test b/tests/examplefiles/twig_test
new file mode 100644
index 00000000..0932fe90
--- /dev/null
+++ b/tests/examplefiles/twig_test
@@ -0,0 +1,4612 @@
+From the Twig test suite, https://github.com/fabpot/Twig, available under BSD license.
+
+--TEST--
+Exception for an unclosed tag
+--TEMPLATE--
+{% block foo %}
+ {% if foo %}
+
+
+
+
+ {% for i in fo %}
+
+
+
+ {% endfor %}
+
+
+
+{% endblock %}
+--EXCEPTION--
+Twig_Error_Syntax: Unexpected tag name "endblock" (expecting closing tag for the "if" tag defined near line 4) in "index.twig" at line 16
+--TEST--
+Exception for an undefined trait
+--TEMPLATE--
+{% use 'foo' with foobar as bar %}
+--TEMPLATE(foo)--
+{% block bar %}
+{% endblock %}
+--EXCEPTION--
+Twig_Error_Runtime: Block "foobar" is not defined in trait "foo" in "index.twig".
+--TEST--
+Twig supports method calls
+--TEMPLATE--
+{{ items.foo }}
+{{ items['foo'] }}
+{{ items[foo] }}
+{{ items[items[foo]] }}
+--DATA--
+return array('foo' => 'bar', 'items' => array('foo' => 'bar', 'bar' => 'foo'))
+--EXPECT--
+bar
+bar
+foo
+bar
+--TEST--
+Twig supports array notation
+--TEMPLATE--
+{# empty array #}
+{{ []|join(',') }}
+
+{{ [1, 2]|join(',') }}
+{{ ['foo', "bar"]|join(',') }}
+{{ {0: 1, 'foo': 'bar'}|join(',') }}
+{{ {0: 1, 'foo': 'bar'}|keys|join(',') }}
+
+{{ {0: 1, foo: 'bar'}|join(',') }}
+{{ {0: 1, foo: 'bar'}|keys|join(',') }}
+
+{# nested arrays #}
+{% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %}
+{{ a[2]|join(',') }}
+{{ a[3]["foo"]|join(',') }}
+
+{# works even if [] is used inside the array #}
+{{ [foo[bar]]|join(',') }}
+
+{# elements can be any expression #}
+{{ ['foo'|upper, bar|upper, bar == foo]|join(',') }}
+
+{# arrays can have a trailing , like in PHP #}
+{{
+ [
+ 1,
+ 2,
+ ]|join(',')
+}}
+
+{# keys can be any expression #}
+{% set a = 1 %}
+{% set b = "foo" %}
+{% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %}
+{{ ary|keys|join(',') }}
+{{ ary|join(',') }}
+--DATA--
+return array('bar' => 'bar', 'foo' => array('bar' => 'bar'))
+--EXPECT--
+1,2
+foo,bar
+1,bar
+0,foo
+
+1,bar
+0,foo
+
+1,2
+bar
+
+bar
+
+FOO,BAR,
+
+1,2
+
+1,foo,c,1foo
+a,b,c,d
+--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 }}
+{{ 0 or 1 and 0 }}
+{{ 1 or 0 and 1 }}
+{{ "foo" ~ "bar" }}
+{{ foo ~ "bar" }}
+{{ "foo" ~ bar }}
+{{ foo ~ bar }}
+{{ 20 // 7 }}
+--DATA--
+return array('foo' => 'bar', 'bar' => 'foo')
+--EXPECT--
+2
+1
+4
+1
+1
+1
+
+
+
+1
+1
+1
+
+
+1
+foobar
+barbar
+foofoo
+barfoo
+2
+--TEST--
+Twig supports bitwise operations
+--TEMPLATE--
+{{ 1 b-and 5 }}
+{{ 1 b-or 5 }}
+{{ 1 b-xor 5 }}
+{{ (1 and 0 b-or 0) is same as(1 and (0 b-or 0)) ? 'ok' : 'ko' }}
+--DATA--
+return array()
+--EXPECT--
+1
+5
+4
+ok
+--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 }}
+--DATA--
+return array()
+--EXPECT--
+///1
+1//1/1
+1/
+/1
+--TEST--
+Twig supports the "divisible by" operator
+--TEMPLATE--
+{{ 8 is divisible by(2) ? 'OK' }}
+{{ 8 is not divisible by(3) ? 'OK' }}
+{{ 8 is divisible by (2) ? 'OK' }}
+{{ 8 is not
+ divisible
+ by
+ (3) ? 'OK' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
+OK
+--TEST--
+Twig supports the .. operator
+--TEMPLATE--
+{% for i in 0..10 %}{{ i }} {% endfor %}
+
+{% for letter in 'a'..'z' %}{{ letter }} {% endfor %}
+
+{% for letter in 'a'|upper..'z'|upper %}{{ letter }} {% endfor %}
+
+{% for i in foo[0]..foo[1] %}{{ i }} {% endfor %}
+
+{% for i in 0 + 1 .. 10 - 1 %}{{ i }} {% endfor %}
+--DATA--
+return array('foo' => array(1, 10))
+--EXPECT--
+0 1 2 3 4 5 6 7 8 9 10
+a b c d e f g h i j k l m n o p q r s t u v w x y z
+A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+1 2 3 4 5 6 7 8 9 10
+1 2 3 4 5 6 7 8 9
+--TEST--
+Twig supports the "ends with" operator
+--TEMPLATE--
+{{ 'foo' ends with 'o' ? 'OK' : 'KO' }}
+{{ not ('foo' ends with 'f') ? 'OK' : 'KO' }}
+{{ not ('foo' ends with 'foowaytoolong') ? 'OK' : 'KO' }}
+{{ 'foo' ends with '' ? 'OK' : 'KO' }}
+{{ '1' ends with true ? 'OK' : 'KO' }}
+{{ 1 ends with true ? 'OK' : 'KO' }}
+{{ 0 ends with false ? 'OK' : 'KO' }}
+{{ '' ends with false ? 'OK' : 'KO' }}
+{{ false ends with false ? 'OK' : 'KO' }}
+{{ false ends with '' ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
+OK
+KO
+KO
+KO
+KO
+KO
+KO
+--TEST--
+Twig supports grouping of expressions
+--TEMPLATE--
+{{ (2 + 2) / 2 }}
+--DATA--
+return array()
+--EXPECT--
+2
+--TEST--
+Twig supports literals
+--TEMPLATE--
+1 {{ true }}
+2 {{ TRUE }}
+3 {{ false }}
+4 {{ FALSE }}
+5 {{ none }}
+6 {{ NONE }}
+7 {{ null }}
+8 {{ NULL }}
+--DATA--
+return array()
+--EXPECT--
+1 1
+2 1
+3
+4
+5
+6
+7
+8
+--TEST--
+Twig supports __call() for attributes
+--TEMPLATE--
+{{ foo.foo }}
+{{ foo.bar }}
+--EXPECT--
+foo_from_call
+bar_from_getbar
+--TEST--
+Twig supports the "matches" operator
+--TEMPLATE--
+{{ 'foo' matches '/o/' ? 'OK' : 'KO' }}
+{{ 'foo' matches '/^fo/' ? 'OK' : 'KO' }}
+{{ 'foo' matches '/O/i' ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
+--TEST--
+Twig supports method calls
+--TEMPLATE--
+{{ items.foo.foo }}
+{{ items.foo.getFoo() }}
+{{ items.foo.bar }}
+{{ items.foo['bar'] }}
+{{ items.foo.bar('a', 43) }}
+{{ items.foo.bar(foo) }}
+{{ items.foo.self.foo() }}
+{{ items.foo.is }}
+{{ items.foo.in }}
+{{ items.foo.not }}
+--DATA--
+return array('foo' => 'bar', 'items' => array('foo' => new TwigTestFoo(), 'bar' => 'foo'))
+--CONFIG--
+return array('strict_variables' => false)
+--EXPECT--
+foo
+foo
+bar
+
+bar_a-43
+bar_bar
+foo
+is
+in
+not
+--TEST--
+Twig allows to use named operators as variable names
+--TEMPLATE--
+{% for match in matches %}
+ {{- match }}
+{% endfor %}
+{{ in }}
+{{ is }}
+--DATA--
+return array('matches' => array(1, 2, 3), 'in' => 'in', 'is' => 'is')
+--EXPECT--
+1
+2
+3
+in
+is
+--TEST--
+Twig parses postfix expressions
+--TEMPLATE--
+{% import _self as macros %}
+
+{% macro foo() %}foo{% endmacro %}
+
+{{ 'a' }}
+{{ 'a'|upper }}
+{{ ('a')|upper }}
+{{ -1|upper }}
+{{ macros.foo() }}
+{{ (macros).foo() }}
+--DATA--
+return array();
+--EXPECT--
+a
+A
+A
+-1
+foo
+foo
+--TEST--
+Twig supports the "same as" operator
+--TEMPLATE--
+{{ 1 is same as(1) ? 'OK' }}
+{{ 1 is not same as(true) ? 'OK' }}
+{{ 1 is same as(1) ? 'OK' }}
+{{ 1 is not same as(true) ? 'OK' }}
+{{ 1 is same as (1) ? 'OK' }}
+{{ 1 is not
+ same
+ as
+ (true) ? 'OK' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
+OK
+OK
+OK
+--TEST--
+Twig supports the "starts with" operator
+--TEMPLATE--
+{{ 'foo' starts with 'f' ? 'OK' : 'KO' }}
+{{ not ('foo' starts with 'oo') ? 'OK' : 'KO' }}
+{{ not ('foo' starts with 'foowaytoolong') ? 'OK' : 'KO' }}
+{{ 'foo' starts with 'f' ? 'OK' : 'KO' }}
+{{ 'foo' starts
+with 'f' ? 'OK' : 'KO' }}
+{{ 'foo' starts with '' ? 'OK' : 'KO' }}
+{{ '1' starts with true ? 'OK' : 'KO' }}
+{{ '' starts with false ? 'OK' : 'KO' }}
+{{ 'a' starts with false ? 'OK' : 'KO' }}
+{{ false starts with '' ? 'OK' : 'KO' }}
+--DATA--
+return array()
+--EXPECT--
+OK
+OK
+OK
+OK
+OK
+OK
+KO
+KO
+KO
+KO
+--TEST--
+Twig supports string interpolation
+--TEMPLATE--
+{# "foo #{"foo #{bar} baz"} baz" #}
+{# "foo #{bar}#{bar} baz" #}
+--DATA--
+return array('bar' => 'BAR');
+--EXPECT--
+foo foo BAR baz baz
+foo BARBAR baz
+--TEST--
+Twig supports the ternary operator
+--TEMPLATE--
+{{ 1 ? 'YES' }}
+{{ 0 ? 'YES' }}
+--DATA--
+return array()
+--EXPECT--
+YES
+
+--TEST--
+Twig supports the ternary operator
+--TEMPLATE--
+{{ 'YES' ?: 'NO' }}
+{{ 0 ?: 'NO' }}
+--DATA--
+return array()
+--EXPECT--
+YES
+NO
+--TEST--
+Twig supports the ternary operator
+--TEMPLATE--
+{{ 1 ? 'YES' : 'NO' }}
+{{ 0 ? 'YES' : 'NO' }}
+{{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }}
+{{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }}
+{{ 1 == 1 ? 'foo<br />':'' }}
+{{ foo ~ (bar ? ('-' ~ bar) : '') }}
+--DATA--
+return array('foo' => 'foo', 'bar' => 'bar')
+--EXPECT--
+YES
+NO
+YES1
+NO1
+foo<br />
+foo-bar
+--TEST--
+Twig does not allow to use two-word named operators as variable names
+--TEMPLATE--
+{{ starts with }}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Error_Syntax: Unexpected token "operator" of value "starts with" in "index.twig" at line 2
+--TEST--
+Twig unary operators precedence
+--TEMPLATE--
+{{ -1 - 1 }}
+{{ -1 - -1 }}
+{{ -1 * -1 }}
+{{ 4 / -1 * 5 }}
+--DATA--
+return array()
+--EXPECT--
+-2
+0
+1
+-20
+--TEST--
+Twig supports unary operators (not, -, +)
+--TEMPLATE--
+{{ not 1 }}/{{ not 0 }}
+{{ +1 + 1 }}/{{ -1 - 1 }}
+{{ not (false or true) }}
+--DATA--
+return array()
+--EXPECT--
+/1
+2/-2
+
+--TEST--
+"abs" filter
+--TEMPLATE--
+{{ (-5.5)|abs }}
+{{ (-5)|abs }}
+{{ (-0)|abs }}
+{{ 0|abs }}
+{{ 5|abs }}
+{{ 5.5|abs }}
+{{ number1|abs }}
+{{ number2|abs }}
+{{ number3|abs }}
+{{ number4|abs }}
+{{ number5|abs }}
+{{ number6|abs }}
+--DATA--
+return array('number1' => -5.5, 'number2' => -5, 'number3' => -0, 'number4' => 0, 'number5' => 5, 'number6' => 5.5)
+--EXPECT--
+5.5
+5
+0
+0
+5
+5.5
+5.5
+5
+0
+0
+5
+5.5
+--TEST--
+"batch" filter
+--TEMPLATE--
+{% for row in items|batch(3.1) %}
+ <div class=row>
+ {% for column in row %}
+ <div class=item>{{ column }}</div>
+ {% endfor %}
+ </div>
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'))
+--EXPECT--
+<div class=row>
+ <div class=item>a</div>
+ <div class=item>b</div>
+ <div class=item>c</div>
+ <div class=item>d</div>
+ </div>
+ <div class=row>
+ <div class=item>e</div>
+ <div class=item>f</div>
+ <div class=item>g</div>
+ <div class=item>h</div>
+ </div>
+ <div class=row>
+ <div class=item>i</div>
+ <div class=item>j</div>
+ </div>
+--TEST--
+"batch" filter
+--TEMPLATE--
+{% for row in items|batch(3) %}
+ <div class=row>
+ {% for column in row %}
+ <div class=item>{{ column }}</div>
+ {% endfor %}
+ </div>
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'))
+--EXPECT--
+<div class=row>
+ <div class=item>a</div>
+ <div class=item>b</div>
+ <div class=item>c</div>
+ </div>
+ <div class=row>
+ <div class=item>d</div>
+ <div class=item>e</div>
+ <div class=item>f</div>
+ </div>
+ <div class=row>
+ <div class=item>g</div>
+ <div class=item>h</div>
+ <div class=item>i</div>
+ </div>
+ <div class=row>
+ <div class=item>j</div>
+ </div>
+--TEST--
+"batch" filter
+--TEMPLATE--
+<table>
+{% for row in items|batch(3, '') %}
+ <tr>
+ {% for column in row %}
+ <td>{{ column }}</td>
+ {% endfor %}
+ </tr>
+{% endfor %}
+</table>
+--DATA--
+return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'))
+--EXPECT--
+<table>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <td>c</td>
+ </tr>
+ <tr>
+ <td>d</td>
+ <td>e</td>
+ <td>f</td>
+ </tr>
+ <tr>
+ <td>g</td>
+ <td>h</td>
+ <td>i</td>
+ </tr>
+ <tr>
+ <td>j</td>
+ <td></td>
+ <td></td>
+ </tr>
+</table>
+--TEST--
+"batch" filter
+--TEMPLATE--
+{% for row in items|batch(3, 'fill') %}
+ <div class=row>
+ {% for column in row %}
+ <div class=item>{{ column }}</div>
+ {% endfor %}
+ </div>
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l'))
+--EXPECT--
+<div class=row>
+ <div class=item>a</div>
+ <div class=item>b</div>
+ <div class=item>c</div>
+ </div>
+ <div class=row>
+ <div class=item>d</div>
+ <div class=item>e</div>
+ <div class=item>f</div>
+ </div>
+ <div class=row>
+ <div class=item>g</div>
+ <div class=item>h</div>
+ <div class=item>i</div>
+ </div>
+ <div class=row>
+ <div class=item>j</div>
+ <div class=item>k</div>
+ <div class=item>l</div>
+ </div>
+--TEST--
+"batch" filter
+--TEMPLATE--
+<table>
+{% for row in items|batch(3, 'fill') %}
+ <tr>
+ {% for column in row %}
+ <td>{{ column }}</td>
+ {% endfor %}
+ </tr>
+{% endfor %}
+</table>
+--DATA--
+return array('items' => array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'))
+--EXPECT--
+<table>
+ <tr>
+ <td>a</td>
+ <td>b</td>
+ <td>c</td>
+ </tr>
+ <tr>
+ <td>d</td>
+ <td>e</td>
+ <td>f</td>
+ </tr>
+ <tr>
+ <td>g</td>
+ <td>h</td>
+ <td>i</td>
+ </tr>
+ <tr>
+ <td>j</td>
+ <td>fill</td>
+ <td>fill</td>
+ </tr>
+</table>
+--TEST--
+"convert_encoding" filter
+--CONDITION--
+function_exists('iconv') || function_exists('mb_convert_encoding')
+--TEMPLATE--
+{{ "愛していますか?"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}
+--DATA--
+return array()
+--EXPECT--
+愛していますか?
+--TEST--
+"date" filter (interval support as of PHP 5.3)
+--CONDITION--
+version_compare(phpversion(), '5.3.0', '>=')
+--TEMPLATE--
+{{ date2|date }}
+{{ date2|date('%d days') }}
+--DATA--
+date_default_timezone_set('UTC');
+$twig->getExtension('core')->setDateFormat('Y-m-d', '%d days %h hours');
+return array(
+ 'date2' => new DateInterval('P2D'),
+)
+--EXPECT--
+2 days 0 hours
+2 days
+--TEST--
+"date" filter
+--TEMPLATE--
+{{ date1|date }}
+{{ date1|date('d/m/Y') }}
+--DATA--
+date_default_timezone_set('UTC');
+$twig->getExtension('core')->setDateFormat('Y-m-d', '%d days %h hours');
+return array(
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
+)
+--EXPECT--
+2010-10-04
+04/10/2010
+--TEST--
+"date" filter
+--CONDITION--
+version_compare(phpversion(), '5.5.0', '>=')
+--TEMPLATE--
+{{ date1|date }}
+{{ date1|date('d/m/Y') }}
+{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}
+{{ date1|date('d/m/Y H:i:s', timezone1) }}
+{{ date1|date('d/m/Y H:i:s') }}
+
+{{ date2|date('d/m/Y H:i:s P', 'Europe/Paris') }}
+{{ date2|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}
+{{ date2|date('d/m/Y H:i:s P', false) }}
+{{ date2|date('e', 'Europe/Paris') }}
+{{ date2|date('e', false) }}
+--DATA--
+date_default_timezone_set('Europe/Paris');
+return array(
+ 'date1' => new DateTimeImmutable('2010-10-04 13:45'),
+ 'date2' => new DateTimeImmutable('2010-10-04 13:45', new DateTimeZone('America/New_York')),
+ 'timezone1' => new DateTimeZone('America/New_York'),
+)
+--EXPECT--
+October 4, 2010 13:45
+04/10/2010
+04/10/2010 19:45:00
+04/10/2010 07:45:00
+04/10/2010 13:45:00
+
+04/10/2010 19:45:00 +02:00
+05/10/2010 01:45:00 +08:00
+04/10/2010 13:45:00 -04:00
+Europe/Paris
+America/New_York
+--TEST--
+"date" filter (interval support as of PHP 5.3)
+--CONDITION--
+version_compare(phpversion(), '5.3.0', '>=')
+--TEMPLATE--
+{{ date1|date }}
+{{ date1|date('%d days %h hours') }}
+{{ date1|date('%d days %h hours', timezone1) }}
+--DATA--
+date_default_timezone_set('UTC');
+return array(
+ 'date1' => new DateInterval('P2D'),
+ // This should have no effect on DateInterval formatting
+ 'timezone1' => new DateTimeZone('America/New_York'),
+)
+--EXPECT--
+2 days
+2 days 0 hours
+2 days 0 hours
+--TEST--
+"date_modify" filter
+--TEMPLATE--
+{{ date1|date_modify('-1day')|date('Y-m-d H:i:s') }}
+{{ date2|date_modify('-1day')|date('Y-m-d H:i:s') }}
+--DATA--
+date_default_timezone_set('UTC');
+return array(
+ 'date1' => '2010-10-04 13:45',
+ 'date2' => new DateTime('2010-10-04 13:45'),
+)
+--EXPECT--
+2010-10-03 13:45:00
+2010-10-03 13:45:00
+--TEST--
+"date" filter
+--TEMPLATE--
+{{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }}
+{{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }}
+{{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }}
+--DATA--
+date_default_timezone_set('UTC');
+return array('date' => mktime(13, 45, 0, 10, 4, 2010))
+--EXPECT--
+04/10/2010 08:45:00 -05:00
+04/10/2010 08:45:00 -05:00
+04/10/2010 08:45:00 -05:00
+--TEST--
+"date" filter
+--TEMPLATE--
+{{ date1|date }}
+{{ date1|date('d/m/Y') }}
+{{ date1|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}
+{{ date1|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}
+{{ date1|date('d/m/Y H:i:s P', 'America/Chicago') }}
+{{ date1|date('e') }}
+{{ date1|date('d/m/Y H:i:s') }}
+
+{{ date2|date }}
+{{ date2|date('d/m/Y') }}
+{{ date2|date('d/m/Y H:i:s', 'Asia/Hong_Kong') }}
+{{ date2|date('d/m/Y H:i:s', timezone1) }}
+{{ date2|date('d/m/Y H:i:s') }}
+
+{{ date3|date }}
+{{ date3|date('d/m/Y') }}
+
+{{ date4|date }}
+{{ date4|date('d/m/Y') }}
+
+{{ date5|date }}
+{{ date5|date('d/m/Y') }}
+
+{{ date6|date('d/m/Y H:i:s P', 'Europe/Paris') }}
+{{ date6|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}
+{{ date6|date('d/m/Y H:i:s P', false) }}
+{{ date6|date('e', 'Europe/Paris') }}
+{{ date6|date('e', false) }}
+
+{{ date7|date }}
+--DATA--
+date_default_timezone_set('Europe/Paris');
+return array(
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
+ 'date2' => new DateTime('2010-10-04 13:45'),
+ 'date3' => '2010-10-04 13:45',
+ 'date4' => 1286199900, // DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT
+ 'date5' => -189291360, // DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new DateTimeZone('UTC'))->getTimestamp(),
+ 'date6' => new DateTime('2010-10-04 13:45', new DateTimeZone('America/New_York')),
+ 'date7' => '2010-01-28T15:00:00+05:00',
+ 'timezone1' => new DateTimeZone('America/New_York'),
+)
+--EXPECT--
+October 4, 2010 13:45
+04/10/2010
+04/10/2010 19:45:00
+04/10/2010 19:45:00 +08:00
+04/10/2010 06:45:00 -05:00
+Europe/Paris
+04/10/2010 13:45:00
+
+October 4, 2010 13:45
+04/10/2010
+04/10/2010 19:45:00
+04/10/2010 07:45:00
+04/10/2010 13:45:00
+
+October 4, 2010 13:45
+04/10/2010
+
+October 4, 2010 15:45
+04/10/2010
+
+January 2, 1964 04:04
+02/01/1964
+
+04/10/2010 19:45:00 +02:00
+05/10/2010 01:45:00 +08:00
+04/10/2010 13:45:00 -04:00
+Europe/Paris
+America/New_York
+
+January 28, 2010 11:00
+--TEST--
+"default" filter
+--TEMPLATE--
+Variable:
+{{ definedVar |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ zeroVar |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ emptyVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ nullVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ undefinedVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+Array access:
+{{ nested.definedVar |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ nested['definedVar'] |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ nested.zeroVar |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ nested.emptyVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ nested.nullVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ nested.undefinedVar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ nested['undefinedVar'] |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ undefinedVar.foo |default('default') is same as('default') ? 'ok' : 'ko' }}
+Plain values:
+{{ 'defined' |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ 0 |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ '' |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ null |default('default') is same as('default') ? 'ok' : 'ko' }}
+Precedence:
+{{ 'o' ~ nullVar |default('k') }}
+{{ 'o' ~ nested.nullVar |default('k') }}
+Object methods:
+{{ object.foo |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ object.undefinedMethod |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ object.getFoo() |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ object.getFoo('a') |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ object.undefinedMethod() |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ object.undefinedMethod('a') |default('default') is same as('default') ? 'ok' : 'ko' }}
+Deep nested:
+{{ nested.undefinedVar.foo.bar |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ nested.definedArray.0 |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ nested['definedArray'][0] |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ object.self.foo |default('default') is same as('default') ? 'ko' : 'ok' }}
+{{ object.self.undefinedMethod |default('default') is same as('default') ? 'ok' : 'ko' }}
+{{ object.undefinedMethod.self |default('default') is same as('default') ? 'ok' : 'ko' }}
+--DATA--
+return array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'emptyVar' => '',
+ 'nullVar' => null,
+ 'nested' => array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'emptyVar' => '',
+ 'nullVar' => null,
+ 'definedArray' => array(0),
+ ),
+ 'object' => new TwigTestFoo(),
+)
+--CONFIG--
+return array('strict_variables' => false)
+--EXPECT--
+Variable:
+ok
+ok
+ok
+ok
+ok
+Array access:
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+Plain values:
+ok
+ok
+ok
+ok
+Precedence:
+ok
+ok
+Object methods:
+ok
+ok
+ok
+ok
+ok
+ok
+Deep nested:
+ok
+ok
+ok
+ok
+ok
+ok
+--DATA--
+return array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'emptyVar' => '',
+ 'nullVar' => null,
+ 'nested' => array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'emptyVar' => '',
+ 'nullVar' => null,
+ 'definedArray' => array(0),
+ ),
+ 'object' => new TwigTestFoo(),
+)
+--CONFIG--
+return array('strict_variables' => true)
+--EXPECT--
+Variable:
+ok
+ok
+ok
+ok
+ok
+Array access:
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+Plain values:
+ok
+ok
+ok
+ok
+Precedence:
+ok
+ok
+Object methods:
+ok
+ok
+ok
+ok
+ok
+ok
+Deep nested:
+ok
+ok
+ok
+ok
+ok
+ok
+--TEST--
+dynamic filter
+--TEMPLATE--
+{{ 'bar'|foo_path }}
+{{ 'bar'|a_foo_b_bar }}
+--DATA--
+return array()
+--EXPECT--
+foo/bar
+a/b/bar
+--TEST--
+"escape" filter does not escape with the html strategy when using the html_attr strategy
+--TEMPLATE--
+{{ '<br />'|escape('html_attr') }}
+--DATA--
+return array()
+--EXPECT--
+&lt;br&#x20;&#x2F;&gt;
+--TEST--
+"escape" filter
+--TEMPLATE--
+{{ "愛していますか? <br />"|e }}
+--DATA--
+return array()
+--EXPECT--
+愛していますか? &lt;br /&gt;
+--TEST--
+"escape" filter
+--TEMPLATE--
+{{ "foo <br />"|e }}
+--DATA--
+return array()
+--EXPECT--
+foo &lt;br /&gt;
+--TEST--
+"first" filter
+--TEMPLATE--
+{{ [1, 2, 3, 4]|first }}
+{{ {a: 1, b: 2, c: 3, d: 4}|first }}
+{{ '1234'|first }}
+{{ arr|first }}
+{{ 'Ä€é'|first }}
+{{ ''|first }}
+--DATA--
+return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
+--EXPECT--
+1
+1
+1
+1
+--TEST--
+"escape" filter
+--TEMPLATE--
+{% set foo %}
+ foo<br />
+{% endset %}
+
+{{ foo|e('html') -}}
+{{ foo|e('js') }}
+{% autoescape true %}
+ {{ foo }}
+{% endautoescape %}
+--DATA--
+return array()
+--EXPECT--
+ foo&lt;br /&gt;
+\x20\x20\x20\x20foo\x3Cbr\x20\x2F\x3E\x0A
+ foo<br />
+--TEST--
+"format" filter
+--TEMPLATE--
+{{ string|format(foo, 3) }}
+--DATA--
+return array('string' => '%s/%d', 'foo' => 'bar')
+--EXPECT--
+bar/3
+--TEST--
+"join" filter
+--TEMPLATE--
+{{ ["foo", "bar"]|join(', ') }}
+{{ foo|join(', ') }}
+{{ bar|join(', ') }}
+--DATA--
+return array('foo' => new TwigTestFoo(), 'bar' => new ArrayObject(array(3, 4)))
+--EXPECT--
+foo, bar
+1, 2
+3, 4
+--TEST--
+"json_encode" filter
+--TEMPLATE--
+{{ "foo"|json_encode|raw }}
+{{ foo|json_encode|raw }}
+{{ [foo, "foo"]|json_encode|raw }}
+--DATA--
+return array('foo' => new Twig_Markup('foo', 'UTF-8'))
+--EXPECT--
+"foo"
+"foo"
+["foo","foo"]
+--TEST--
+"last" filter
+--TEMPLATE--
+{{ [1, 2, 3, 4]|last }}
+{{ {a: 1, b: 2, c: 3, d: 4}|last }}
+{{ '1234'|last }}
+{{ arr|last }}
+{{ 'Ä€é'|last }}
+{{ ''|last }}
+--DATA--
+return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
+--EXPECT--
+4
+4
+4
+4
+--TEST--
+"length" filter
+--TEMPLATE--
+{{ array|length }}
+{{ string|length }}
+{{ number|length }}
+{{ markup|length }}
+--DATA--
+return array('array' => array(1, 4), 'string' => 'foo', 'number' => 1000, 'markup' => new Twig_Markup('foo', 'UTF-8'))
+--EXPECT--
+2
+3
+4
+3
+--TEST--
+"length" filter
+--CONDITION--
+function_exists('mb_get_info')
+--TEMPLATE--
+{{ string|length }}
+{{ markup|length }}
+--DATA--
+return array('string' => 'été', 'markup' => new Twig_Markup('foo', 'UTF-8'))
+--EXPECT--
+3
+3
+--TEST--
+"merge" filter
+--TEMPLATE--
+{{ items|merge({'bar': 'foo'})|join }}
+{{ items|merge({'bar': 'foo'})|keys|join }}
+{{ {'bar': 'foo'}|merge(items)|join }}
+{{ {'bar': 'foo'}|merge(items)|keys|join }}
+{{ numerics|merge([4, 5, 6])|join }}
+--DATA--
+return array('items' => array('foo' => 'bar'), 'numerics' => array(1, 2, 3))
+--EXPECT--
+barfoo
+foobar
+foobar
+barfoo
+123456
+--TEST--
+"nl2br" filter
+--TEMPLATE--
+{{ "I like Twig.\nYou will like it too.\n\nEverybody like it!"|nl2br }}
+{{ text|nl2br }}
+--DATA--
+return array('text' => "If you have some <strong>HTML</strong>\nit will be escaped.")
+--EXPECT--
+I like Twig.<br />
+You will like it too.<br />
+<br />
+Everybody like it!
+If you have some &lt;strong&gt;HTML&lt;/strong&gt;<br />
+it will be escaped.
+--TEST--
+"number_format" filter with defaults.
+--TEMPLATE--
+{{ 20|number_format }}
+{{ 20.25|number_format }}
+{{ 20.25|number_format(1) }}
+{{ 20.25|number_format(2, ',') }}
+{{ 1020.25|number_format }}
+{{ 1020.25|number_format(2, ',') }}
+{{ 1020.25|number_format(2, ',', '.') }}
+--DATA--
+$twig->getExtension('core')->setNumberFormat(2, '!', '=');
+return array();
+--EXPECT--
+20!00
+20!25
+20!3
+20,25
+1=020!25
+1=020,25
+1.020,25
+--TEST--
+"number_format" filter
+--TEMPLATE--
+{{ 20|number_format }}
+{{ 20.25|number_format }}
+{{ 20.25|number_format(2) }}
+{{ 20.25|number_format(2, ',') }}
+{{ 1020.25|number_format(2, ',') }}
+{{ 1020.25|number_format(2, ',', '.') }}
+--DATA--
+return array();
+--EXPECT--
+20
+20
+20.25
+20,25
+1,020,25
+1.020,25
+--TEST--
+"replace" filter
+--TEMPLATE--
+{{ "I like %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }}
+--DATA--
+return array()
+--EXPECT--
+I like foo and bar.
+--TEST--
+"reverse" filter
+--TEMPLATE--
+{{ [1, 2, 3, 4]|reverse|join('') }}
+{{ '1234évènement'|reverse }}
+{{ arr|reverse|join('') }}
+{{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }}
+{{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }}
+{{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }}
+--DATA--
+return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
+--EXPECT--
+4321
+tnemenèvé4321
+4321
+a,c
+a,c
+a,c
+--TEST--
+"round" filter
+--TEMPLATE--
+{{ 2.7|round }}
+{{ 2.1|round }}
+{{ 2.1234|round(3, 'floor') }}
+{{ 2.1|round(0, 'ceil') }}
+
+{{ 21.3|round(-1)}}
+{{ 21.3|round(-1, 'ceil')}}
+{{ 21.3|round(-1, 'floor')}}
+--DATA--
+return array()
+--EXPECT--
+3
+2
+2.123
+3
+
+20
+30
+20
+--TEST--
+"slice" filter
+--TEMPLATE--
+{{ [1, 2, 3, 4][1:2]|join('') }}
+{{ {a: 1, b: 2, c: 3, d: 4}[1:2]|join('') }}
+{{ [1, 2, 3, 4][start:length]|join('') }}
+{{ [1, 2, 3, 4]|slice(1, 2)|join('') }}
+{{ [1, 2, 3, 4]|slice(1, 2)|keys|join('') }}
+{{ [1, 2, 3, 4]|slice(1, 2, true)|keys|join('') }}
+{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|join('') }}
+{{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|keys|join('') }}
+{{ '1234'|slice(1, 2) }}
+{{ '1234'[1:2] }}
+{{ arr|slice(1, 2)|join('') }}
+{{ arr[1:2]|join('') }}
+
+{{ [1, 2, 3, 4]|slice(1)|join('') }}
+{{ [1, 2, 3, 4][1:]|join('') }}
+{{ '1234'|slice(1) }}
+{{ '1234'[1:] }}
+{{ '1234'[:1] }}
+--DATA--
+return array('start' => 1, 'length' => 2, 'arr' => new ArrayObject(array(1, 2, 3, 4)))
+--EXPECT--
+23
+23
+23
+23
+01
+12
+23
+bc
+23
+23
+23
+23
+
+234
+234
+234
+234
+1
+--TEST--
+"sort" filter
+--TEMPLATE--
+{{ array1|sort|join }}
+{{ array2|sort|join }}
+--DATA--
+return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'))
+--EXPECT--
+14
+barfoo
+--TEST--
+"split" filter
+--TEMPLATE--
+{{ "one,two,three,four,five"|split(',')|join('-') }}
+{{ foo|split(',')|join('-') }}
+{{ foo|split(',', 3)|join('-') }}
+{{ baz|split('')|join('-') }}
+{{ baz|split('', 2)|join('-') }}
+{{ foo|split(',', -2)|join('-') }}
+--DATA--
+return array('foo' => "one,two,three,four,five", 'baz' => '12345',)
+--EXPECT--
+one-two-three-four-five
+one-two-three-four-five
+one-two-three,four,five
+1-2-3-4-5
+12-34-5
+one-two-three--TEST--
+"trim" filter
+--TEMPLATE--
+{{ " I like Twig. "|trim }}
+{{ text|trim }}
+{{ " foo/"|trim("/") }}
+--DATA--
+return array('text' => " If you have some <strong>HTML</strong> it will be escaped. ")
+--EXPECT--
+I like Twig.
+If you have some &lt;strong&gt;HTML&lt;/strong&gt; it will be escaped.
+ foo
+--TEST--
+"url_encode" filter for PHP < 5.4 and HHVM
+--CONDITION--
+defined('PHP_QUERY_RFC3986')
+--TEMPLATE--
+{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode }}
+{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode|raw }}
+{{ {}|url_encode|default("default") }}
+{{ 'spéßi%le%c0d@dspa ce'|url_encode }}
+--DATA--
+return array()
+--EXPECT--
+foo=bar&amp;number=3&amp;sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&amp;spa%20ce=
+foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce=
+default
+sp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce
+--TEST--
+"url_encode" filter
+--CONDITION--
+defined('PHP_QUERY_RFC3986')
+--TEMPLATE--
+{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode }}
+{{ {foo: "bar", number: 3, "spéßi%l": "e%c0d@d", "spa ce": ""}|url_encode|raw }}
+{{ {}|url_encode|default("default") }}
+{{ 'spéßi%le%c0d@dspa ce'|url_encode }}
+--DATA--
+return array()
+--EXPECT--
+foo=bar&amp;number=3&amp;sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&amp;spa%20ce=
+foo=bar&number=3&sp%C3%A9%C3%9Fi%25l=e%25c0d%40d&spa%20ce=
+default
+sp%C3%A9%C3%9Fi%25le%25c0d%40dspa%20ce
+--TEST--
+"attribute" function
+--TEMPLATE--
+{{ attribute(obj, method) }}
+{{ attribute(array, item) }}
+{{ attribute(obj, "bar", ["a", "b"]) }}
+{{ attribute(obj, "bar", arguments) }}
+{{ attribute(obj, method) is defined ? 'ok' : 'ko' }}
+{{ attribute(obj, nonmethod) is defined ? 'ok' : 'ko' }}
+--DATA--
+return array('obj' => new TwigTestFoo(), 'method' => 'foo', 'array' => array('foo' => 'bar'), 'item' => 'foo', 'nonmethod' => 'xxx', 'arguments' => array('a', 'b'))
+--EXPECT--
+foo
+bar
+bar_a-b
+bar_a-b
+ok
+ko
+--TEST--
+"block" function
+--TEMPLATE--
+{% extends 'base.twig' %}
+{% block bar %}BAR{% endblock %}
+--TEMPLATE(base.twig)--
+{% block foo %}{{ block('bar') }}{% endblock %}
+{% block bar %}BAR_BASE{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+BARBAR
+--TEST--
+"constant" function
+--TEMPLATE--
+{{ constant('DATE_W3C') == expect ? 'true' : 'false' }}
+{{ constant('ARRAY_AS_PROPS', object) }}
+--DATA--
+return array('expect' => DATE_W3C, 'object' => new ArrayObject(array('hi')));
+--EXPECT--
+true
+2
+--TEST--
+"cycle" function
+--TEMPLATE--
+{% for i in 0..6 %}
+{{ cycle(array1, i) }}-{{ cycle(array2, i) }}
+{% endfor %}
+--DATA--
+return array('array1' => array('odd', 'even'), 'array2' => array('apple', 'orange', 'citrus'))
+--EXPECT--
+odd-apple
+even-orange
+odd-citrus
+even-apple
+odd-orange
+even-citrus
+odd-apple
+--TEST--
+"date" function
+--TEMPLATE--
+{{ date(date, "America/New_York")|date('d/m/Y H:i:s P', false) }}
+{{ date(timezone="America/New_York", date=date)|date('d/m/Y H:i:s P', false) }}
+--DATA--
+date_default_timezone_set('UTC');
+return array('date' => mktime(13, 45, 0, 10, 4, 2010))
+--EXPECT--
+04/10/2010 09:45:00 -04:00
+04/10/2010 09:45:00 -04:00
+--TEST--
+"date" function
+--TEMPLATE--
+{{ date() == date('now') ? 'OK' : 'KO' }}
+{{ date(date1) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
+{{ date(date2) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
+{{ date(date3) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
+{{ date(date4) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
+{{ date(date5) == date('1964-01-02 03:04') ? 'OK' : 'KO' }}
+--DATA--
+date_default_timezone_set('UTC');
+return array(
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
+ 'date2' => new DateTime('2010-10-04 13:45'),
+ 'date3' => '2010-10-04 13:45',
+ 'date4' => 1286199900, // DateTime::createFromFormat('Y-m-d H:i', '2010-10-04 13:45', new DateTimeZone('UTC'))->getTimestamp() -- A unixtimestamp is always GMT
+ 'date5' => -189291360, // DateTime::createFromFormat('Y-m-d H:i', '1964-01-02 03:04', new DateTimeZone('UTC'))->getTimestamp(),
+)
+--EXPECT--
+OK
+OK
+OK
+OK
+OK
+OK
+--TEST--
+"dump" function, xdebug is not loaded or xdebug <2.2-dev is loaded
+--CONDITION--
+!extension_loaded('xdebug') || (($r = new ReflectionExtension('xdebug')) && version_compare($r->getVersion(), '2.2-dev', '<'))
+--TEMPLATE--
+{{ dump() }}
+--DATA--
+return array('foo' => 'foo', 'bar' => 'bar')
+--CONFIG--
+return array('debug' => true, 'autoescape' => false);
+--TEST--
+"dump" function
+--CONDITION--
+!extension_loaded('xdebug')
+--TEMPLATE--
+{{ dump('foo') }}
+{{ dump('foo', 'bar') }}
+--DATA--
+return array('foo' => 'foo', 'bar' => 'bar')
+--CONFIG--
+return array('debug' => true, 'autoescape' => false);
+--EXPECT--
+string(3) "foo"
+
+string(3) "foo"
+string(3) "bar"
+--TEST--
+dynamic function
+--TEMPLATE--
+{{ foo_path('bar') }}
+{{ a_foo_b_bar('bar') }}
+--DATA--
+return array()
+--EXPECT--
+foo/bar
+a/b/bar
+--TEST--
+"include" function
+--TEMPLATE--
+{% set tmp = include("foo.twig") %}
+
+FOO{{ tmp }}BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array()
+--EXPECT--
+FOO
+FOOBARBAR
+--TEST--
+"include" function is safe for auto-escaping
+--TEMPLATE--
+{{ include("foo.twig") }}
+--TEMPLATE(foo.twig)--
+<p>Test</p>
+--DATA--
+return array()
+--EXPECT--
+<p>Test</p>
+--TEST--
+"include" function
+--TEMPLATE--
+FOO
+{{ include("foo.twig") }}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+FOOBAR
+
+BAR
+--TEST--
+"include" function allows expressions for the template to include
+--TEMPLATE--
+FOO
+{{ include(foo) }}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array('foo' => 'foo.twig')
+--EXPECT--
+FOO
+
+FOOBAR
+
+BAR
+--TEST--
+"include" function
+--TEMPLATE--
+{{ include(["foo.twig", "bar.twig"], ignore_missing = true) }}
+{{ include("foo.twig", ignore_missing = true) }}
+{{ include("foo.twig", ignore_missing = true, variables = {}) }}
+{{ include("foo.twig", ignore_missing = true, variables = {}, with_context = true) }}
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+"include" function
+--TEMPLATE--
+{% extends "base.twig" %}
+
+{% block content %}
+ {{ parent() }}
+{% endblock %}
+--TEMPLATE(base.twig)--
+{% block content %}
+ {{ include("foo.twig") }}
+{% endblock %}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
+--TEST--
+"include" function
+--TEMPLATE--
+{{ include("foo.twig") }}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
+--TEST--
+"include" tag sandboxed
+--TEMPLATE--
+{{ include("foo.twig", sandboxed = true) }}
+--TEMPLATE(foo.twig)--
+{{ foo|e }}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Sandbox_SecurityError: Filter "e" is not allowed in "index.twig" at line 2.
+--TEST--
+"include" function accepts Twig_Template instance
+--TEMPLATE--
+{{ include(foo) }} FOO
+--TEMPLATE(foo.twig)--
+BAR
+--DATA--
+return array('foo' => $twig->loadTemplate('foo.twig'))
+--EXPECT--
+BAR FOO
+--TEST--
+"include" function
+--TEMPLATE--
+{{ include(["foo.twig", "bar.twig"]) }}
+{{- include(["bar.twig", "foo.twig"]) }}
+--TEMPLATE(foo.twig)--
+foo
+--DATA--
+return array()
+--EXPECT--
+foo
+foo
+--TEST--
+"include" function accept variables and with_context
+--TEMPLATE--
+{{ include("foo.twig") }}
+{{- include("foo.twig", with_context = false) }}
+{{- include("foo.twig", {'foo1': 'bar'}) }}
+{{- include("foo.twig", {'foo1': 'bar'}, with_context = false) }}
+--TEMPLATE(foo.twig)--
+{% for k, v in _context %}{{ k }},{% endfor %}
+--DATA--
+return array('foo' => 'bar')
+--EXPECT--
+foo,global,_parent,
+global,_parent,
+foo,global,foo1,_parent,
+foo1,global,_parent,
+--TEST--
+"include" function accept variables
+--TEMPLATE--
+{{ include("foo.twig", {'foo': 'bar'}) }}
+{{- include("foo.twig", vars) }}
+--TEMPLATE(foo.twig)--
+{{ foo }}
+--DATA--
+return array('vars' => array('foo' => 'bar'))
+--EXPECT--
+bar
+bar
+--TEST--
+"max" function
+--TEMPLATE--
+{{ max([2, 1, 3, 5, 4]) }}
+{{ max(2, 1, 3, 5, 4) }}
+{{ max({2:"two", 1:"one", 3:"three", 5:"five", 4:"for"}) }}
+--DATA--
+return array()
+--EXPECT--
+5
+5
+two
+--TEST--
+"min" function
+--TEMPLATE--
+{{ min(2, 1, 3, 5, 4) }}
+{{ min([2, 1, 3, 5, 4]) }}
+{{ min({2:"two", 1:"one", 3:"three", 5:"five", 4:"for"}) }}
+--DATA--
+return array()
+--EXPECT--
+1
+1
+five
+--TEST--
+"range" function
+--TEMPLATE--
+{{ range(low=0+1, high=10+0, step=2)|join(',') }}
+--DATA--
+return array()
+--EXPECT--
+1,3,5,7,9
+--TEST--
+"block" function recursively called in a parent template
+--TEMPLATE--
+{% extends "ordered_menu.twig" %}
+{% block label %}"{{ parent() }}"{% endblock %}
+{% block list %}{% set class = 'b' %}{{ parent() }}{% endblock %}
+--TEMPLATE(ordered_menu.twig)--
+{% extends "menu.twig" %}
+{% block list %}{% set class = class|default('a') %}<ol class="{{ class }}">{{ block('children') }}</ol>{% endblock %}
+--TEMPLATE(menu.twig)--
+{% extends "base.twig" %}
+{% block list %}<ul>{{ block('children') }}</ul>{% endblock %}
+{% block children %}{% set currentItem = item %}{% for item in currentItem %}{{ block('item') }}{% endfor %}{% set item = currentItem %}{% endblock %}
+{% block item %}<li>{% if item is not iterable %}{{ block('label') }}{% else %}{{ block('list') }}{% endif %}</li>{% endblock %}
+{% block label %}{{ item }}{{ block('unknown') }}{% endblock %}
+--TEMPLATE(base.twig)--
+{{ block('list') }}
+--DATA--
+return array('item' => array('1', '2', array('3.1', array('3.2.1', '3.2.2'), '3.4')))
+--EXPECT--
+<ol class="b"><li>"1"</li><li>"2"</li><li><ol class="b"><li>"3.1"</li><li><ol class="b"><li>"3.2.1"</li><li>"3.2.2"</li></ol></li><li>"3.4"</li></ol></li></ol>
+--TEST--
+"source" function
+--TEMPLATE--
+FOO
+{{ source("foo.twig") }}
+
+BAR
+--TEMPLATE(foo.twig)--
+{{ foo }}<br />
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+{{ foo }}<br />
+
+BAR
+--TEST--
+"template_from_string" function
+--TEMPLATE--
+{% include template_from_string(template) %}
+
+{% include template_from_string("Hello {{ name }}") %}
+{% include template_from_string('{% extends "parent.twig" %}{% block content %}Hello {{ name }}{% endblock %}') %}
+--TEMPLATE(parent.twig)--
+{% block content %}{% endblock %}
+--DATA--
+return array('name' => 'Fabien', 'template' => "Hello {{ name }}")
+--EXPECT--
+Hello Fabien
+Hello Fabien
+Hello Fabien
+--TEST--
+macro
+--TEMPLATE--
+{% from _self import test %}
+
+{% macro test(a, b = 'bar') -%}
+{{ a }}{{ b }}
+{%- endmacro %}
+
+{{ test('foo') }}
+{{ test('bar', 'foo') }}
+--DATA--
+return array();
+--EXPECT--
+foobar
+barfoo
+--TEST--
+macro
+--TEMPLATE--
+{% import _self as macros %}
+
+{% macro foo(data) %}
+ {{ data }}
+{% endmacro %}
+
+{% macro bar() %}
+ <br />
+{% endmacro %}
+
+{{ macros.foo(macros.bar()) }}
+--DATA--
+return array();
+--EXPECT--
+<br />
+--TEST--
+macro
+--TEMPLATE--
+{% from _self import test %}
+
+{% macro test(this) -%}
+ {{ this }}
+{%- endmacro %}
+
+{{ test(this) }}
+--DATA--
+return array('this' => 'foo');
+--EXPECT--
+foo
+--TEST--
+macro
+--TEMPLATE--
+{% import _self as test %}
+{% from _self import test %}
+
+{% macro test(a, b) -%}
+ {{ a|default('a') }}<br />
+ {{- b|default('b') }}<br />
+{%- endmacro %}
+
+{{ test.test() }}
+{{ test() }}
+{{ test.test(1, "c") }}
+{{ test(1, "c") }}
+--DATA--
+return array();
+--EXPECT--
+a<br />b<br />
+a<br />b<br />
+1<br />c<br />
+1<br />c<br />
+--TEST--
+macro with a filter
+--TEMPLATE--
+{% import _self as test %}
+
+{% macro test() %}
+ {% filter escape %}foo<br />{% endfilter %}
+{% endmacro %}
+
+{{ test.test() }}
+--DATA--
+return array();
+--EXPECT--
+foo&lt;br /&gt;
+--TEST--
+Twig outputs 0 nodes correctly
+--TEMPLATE--
+{{ foo }}0{{ foo }}
+--DATA--
+return array('foo' => 'foo')
+--EXPECT--
+foo0foo
+--TEST--
+error in twig extension
+--TEMPLATE--
+{{ object.region is not null ? object.regionChoices[object.region] }}
+--EXPECT--
+house.region.s
+--TEST--
+Twig is able to deal with SimpleXMLElement instances as variables
+--CONDITION--
+version_compare(phpversion(), '5.3.0', '>=')
+--TEMPLATE--
+Hello '{{ images.image.0.group }}'!
+{{ images.image.0.group.attributes.myattr }}
+{{ images.children().image.count() }}
+{% for image in images %}
+ - {{ image.group }}
+{% endfor %}
+--DATA--
+return array('images' => new SimpleXMLElement('<images><image><group myattr="example">foo</group></image><image><group>bar</group></image></images>'))
+--EXPECT--
+Hello 'foo'!
+example
+2
+ - foo
+ - bar
+--TEST--
+Twig does not confuse strings with integers in getAttribute()
+--TEMPLATE--
+{{ hash['2e2'] }}
+--DATA--
+return array('hash' => array('2e2' => 'works'))
+--EXPECT--
+works
+--TEST--
+"autoescape" tag applies escaping on its children
+--TEMPLATE--
+{% autoescape %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape 'html' %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape false %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape true %}
+{{ var }}<br />
+{% endautoescape %}
+{% autoescape false %}
+{{ var }}<br />
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+&lt;br /&gt;<br />
+&lt;br /&gt;<br />
+<br /><br />
+&lt;br /&gt;<br />
+<br /><br />
+--TEST--
+"autoescape" tag applies escaping on embedded blocks
+--TEMPLATE--
+{% autoescape 'html' %}
+ {% block foo %}
+ {{ var }}
+ {% endblock %}
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+&lt;br /&gt;
+--TEST--
+"autoescape" tag does not double-escape
+--TEMPLATE--
+{% autoescape 'html' %}
+{{ var|escape }}
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+&lt;br /&gt;
+--TEST--
+"autoescape" tag applies escaping after calling functions
+--TEMPLATE--
+
+autoescape false
+{% autoescape false %}
+
+safe_br
+{{ safe_br() }}
+
+unsafe_br
+{{ unsafe_br() }}
+
+{% endautoescape %}
+
+autoescape 'html'
+{% autoescape 'html' %}
+
+safe_br
+{{ safe_br() }}
+
+unsafe_br
+{{ unsafe_br() }}
+
+unsafe_br()|raw
+{{ (unsafe_br())|raw }}
+
+safe_br()|escape
+{{ (safe_br())|escape }}
+
+safe_br()|raw
+{{ (safe_br())|raw }}
+
+unsafe_br()|escape
+{{ (unsafe_br())|escape }}
+
+{% endautoescape %}
+
+autoescape js
+{% autoescape 'js' %}
+
+safe_br
+{{ safe_br() }}
+
+{% endautoescape %}
+--DATA--
+return array()
+--EXPECT--
+
+autoescape false
+
+safe_br
+<br />
+
+unsafe_br
+<br />
+
+
+autoescape 'html'
+
+safe_br
+<br />
+
+unsafe_br
+&lt;br /&gt;
+
+unsafe_br()|raw
+<br />
+
+safe_br()|escape
+&lt;br /&gt;
+
+safe_br()|raw
+<br />
+
+unsafe_br()|escape
+&lt;br /&gt;
+
+
+autoescape js
+
+safe_br
+\x3Cbr\x20\x2F\x3E
+--TEST--
+"autoescape" tag does not apply escaping on literals
+--TEMPLATE--
+{% autoescape 'html' %}
+
+1. Simple literal
+{{ "<br />" }}
+
+2. Conditional expression with only literals
+{{ true ? "<br />" : "<br>" }}
+
+3. Conditional expression with a variable
+{{ true ? "<br />" : someVar }}
+
+4. Nested conditionals with only literals
+{{ true ? (true ? "<br />" : "<br>") : "\n" }}
+
+5. Nested conditionals with a variable
+{{ true ? (true ? "<br />" : someVar) : "\n" }}
+
+6. Nested conditionals with a variable marked safe
+{{ true ? (true ? "<br />" : someVar|raw) : "\n" }}
+
+{% endautoescape %}
+--DATA--
+return array()
+--EXPECT--
+
+1. Simple literal
+<br />
+
+2. Conditional expression with only literals
+<br />
+
+3. Conditional expression with a variable
+&lt;br /&gt;
+
+4. Nested conditionals with only literals
+<br />
+
+5. Nested conditionals with a variable
+&lt;br /&gt;
+
+6. Nested conditionals with a variable marked safe
+<br />
+--TEST--
+"autoescape" tags can be nested at will
+--TEMPLATE--
+{{ var }}
+{% autoescape 'html' %}
+ {{ var }}
+ {% autoescape false %}
+ {{ var }}
+ {% autoescape 'html' %}
+ {{ var }}
+ {% endautoescape %}
+ {{ var }}
+ {% endautoescape %}
+ {{ var }}
+{% endautoescape %}
+{{ var }}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+&lt;br /&gt;
+ &lt;br /&gt;
+ <br />
+ &lt;br /&gt;
+ <br />
+ &lt;br /&gt;
+&lt;br /&gt;
+--TEST--
+"autoescape" tag applies escaping to object method calls
+--TEMPLATE--
+{% autoescape 'html' %}
+{{ user.name }}
+{{ user.name|lower }}
+{{ user }}
+{% endautoescape %}
+--EXPECT--
+Fabien&lt;br /&gt;
+fabien&lt;br /&gt;
+Fabien&lt;br /&gt;
+--TEST--
+"autoescape" tag does not escape when raw is used as a filter
+--TEMPLATE--
+{% autoescape 'html' %}
+{{ var|raw }}
+{% endautoescape %}
+--DATA--
+return array('var' => '<br />')
+--EXPECT--
+<br />
+--TEST--
+"autoescape" tag accepts an escaping strategy
+--TEMPLATE--
+{% autoescape true js %}{{ var }}{% endautoescape %}
+
+{% autoescape true html %}{{ var }}{% endautoescape %}
+
+{% autoescape 'js' %}{{ var }}{% endautoescape %}
+
+{% autoescape 'html' %}{{ var }}{% endautoescape %}
+--DATA--
+return array('var' => '<br />"')
+--EXPECT--
+\x3Cbr\x20\x2F\x3E\x22
+&lt;br /&gt;&quot;
+\x3Cbr\x20\x2F\x3E\x22
+&lt;br /&gt;&quot;
+--TEST--
+escape types
+--TEMPLATE--
+
+1. autoescape 'html' |escape('js')
+
+{% autoescape 'html' %}
+<a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
+{% endautoescape %}
+
+2. autoescape 'html' |escape('js')
+
+{% autoescape 'html' %}
+<a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
+{% endautoescape %}
+
+3. autoescape 'js' |escape('js')
+
+{% autoescape 'js' %}
+<a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
+{% endautoescape %}
+
+4. no escape
+
+{% autoescape false %}
+<a onclick="alert(&quot;{{ msg }}&quot;)"></a>
+{% endautoescape %}
+
+5. |escape('js')|escape('html')
+
+{% autoescape false %}
+<a onclick="alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)"></a>
+{% endautoescape %}
+
+6. autoescape 'html' |escape('js')|escape('html')
+
+{% autoescape 'html' %}
+<a onclick="alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)"></a>
+{% endautoescape %}
+
+--DATA--
+return array('msg' => "<>\n'\"")
+--EXPECT--
+
+1. autoescape 'html' |escape('js')
+
+<a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
+
+2. autoescape 'html' |escape('js')
+
+<a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
+
+3. autoescape 'js' |escape('js')
+
+<a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
+
+4. no escape
+
+<a onclick="alert(&quot;<>
+'"&quot;)"></a>
+
+5. |escape('js')|escape('html')
+
+<a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
+
+6. autoescape 'html' |escape('js')|escape('html')
+
+<a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
+
+--TEST--
+"autoescape" tag do not applies escaping on filter arguments
+--TEMPLATE--
+{% autoescape 'html' %}
+{{ var|nl2br("<br />") }}
+{{ var|nl2br("<br />"|escape) }}
+{{ var|nl2br(sep) }}
+{{ var|nl2br(sep|raw) }}
+{{ var|nl2br(sep|escape) }}
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig", 'sep' => '<br />')
+--EXPECT--
+&lt;Fabien&gt;<br />
+Twig
+&lt;Fabien&gt;&lt;br /&gt;
+Twig
+&lt;Fabien&gt;<br />
+Twig
+&lt;Fabien&gt;<br />
+Twig
+&lt;Fabien&gt;&lt;br /&gt;
+Twig
+--TEST--
+"autoescape" tag applies escaping after calling filters
+--TEMPLATE--
+{% autoescape 'html' %}
+
+(escape_and_nl2br is an escaper filter)
+
+1. Don't escape escaper filter output
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped )
+{{ var|escape_and_nl2br }}
+
+2. Don't escape escaper filter output
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped, |raw is redundant )
+{{ var|escape_and_nl2br|raw }}
+
+3. Explicit escape
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is explicitly escaped by |escape )
+{{ var|escape_and_nl2br|escape }}
+
+4. Escape non-escaper filter output
+( var is upper-cased by |upper,
+ the output is auto-escaped )
+{{ var|upper }}
+
+5. Escape if last filter is not an escaper
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is upper-cased by |upper,
+ the output is auto-escaped as |upper is not an escaper )
+{{ var|escape_and_nl2br|upper }}
+
+6. Don't escape escaper filter output
+( var is upper cased by upper,
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped as |escape_and_nl2br is an escaper )
+{{ var|upper|escape_and_nl2br }}
+
+7. Escape if last filter is not an escaper
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is auto-escaped )
+{{ "<b>%s</b>"|format(var) }}
+
+8. Escape if last filter is not an escaper
+( the output of |format is "<b>" ~ var ~ "</b>",
+ |raw is redundant,
+ the output is auto-escaped )
+{{ "<b>%s</b>"|raw|format(var) }}
+
+9. Don't escape escaper filter output
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is not escaped due to |raw filter at the end )
+{{ "<b>%s</b>"|format(var)|raw }}
+
+10. Don't escape escaper filter output
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is not escaped due to |raw filter at the end,
+ the |raw filter on var is redundant )
+{{ "<b>%s</b>"|format(var|raw)|raw }}
+
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig")
+--EXPECT--
+
+(escape_and_nl2br is an escaper filter)
+
+1. Don't escape escaper filter output
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped )
+&lt;Fabien&gt;<br />
+Twig
+
+2. Don't escape escaper filter output
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped, |raw is redundant )
+&lt;Fabien&gt;<br />
+Twig
+
+3. Explicit escape
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is explicitly escaped by |escape )
+&amp;lt;Fabien&amp;gt;&lt;br /&gt;
+Twig
+
+4. Escape non-escaper filter output
+( var is upper-cased by |upper,
+ the output is auto-escaped )
+&lt;FABIEN&gt;
+TWIG
+
+5. Escape if last filter is not an escaper
+( var is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is upper-cased by |upper,
+ the output is auto-escaped as |upper is not an escaper )
+&amp;LT;FABIEN&amp;GT;&lt;BR /&gt;
+TWIG
+
+6. Don't escape escaper filter output
+( var is upper cased by upper,
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
+ the output is not escaped as |escape_and_nl2br is an escaper )
+&lt;FABIEN&gt;<br />
+TWIG
+
+7. Escape if last filter is not an escaper
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is auto-escaped )
+&lt;b&gt;&lt;Fabien&gt;
+Twig&lt;/b&gt;
+
+8. Escape if last filter is not an escaper
+( the output of |format is "<b>" ~ var ~ "</b>",
+ |raw is redundant,
+ the output is auto-escaped )
+&lt;b&gt;&lt;Fabien&gt;
+Twig&lt;/b&gt;
+
+9. Don't escape escaper filter output
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is not escaped due to |raw filter at the end )
+<b><Fabien>
+Twig</b>
+
+10. Don't escape escaper filter output
+( the output of |format is "<b>" ~ var ~ "</b>",
+ the output is not escaped due to |raw filter at the end,
+ the |raw filter on var is redundant )
+<b><Fabien>
+Twig</b>
+--TEST--
+"autoescape" tag applies escaping after calling filters, and before calling pre_escape filters
+--TEMPLATE--
+{% autoescape 'html' %}
+
+(nl2br is pre_escaped for "html" and declared safe for "html")
+
+1. Pre-escape and don't post-escape
+( var|escape|nl2br )
+{{ var|nl2br }}
+
+2. Don't double-pre-escape
+( var|escape|nl2br )
+{{ var|escape|nl2br }}
+
+3. Don't escape safe values
+( var|raw|nl2br )
+{{ var|raw|nl2br }}
+
+4. Don't escape safe values
+( var|escape|nl2br|nl2br )
+{{ var|nl2br|nl2br }}
+
+5. Re-escape values that are escaped for an other contexts
+( var|escape_something|escape|nl2br )
+{{ var|escape_something|nl2br }}
+
+6. Still escape when using filters not declared safe
+( var|escape|nl2br|upper|escape )
+{{ var|nl2br|upper }}
+
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig")
+--EXPECT--
+
+(nl2br is pre_escaped for "html" and declared safe for "html")
+
+1. Pre-escape and don't post-escape
+( var|escape|nl2br )
+&lt;Fabien&gt;<br />
+Twig
+
+2. Don't double-pre-escape
+( var|escape|nl2br )
+&lt;Fabien&gt;<br />
+Twig
+
+3. Don't escape safe values
+( var|raw|nl2br )
+<Fabien><br />
+Twig
+
+4. Don't escape safe values
+( var|escape|nl2br|nl2br )
+&lt;Fabien&gt;<br /><br />
+Twig
+
+5. Re-escape values that are escaped for an other contexts
+( var|escape_something|escape|nl2br )
+&lt;FABIEN&gt;<br />
+TWIG
+
+6. Still escape when using filters not declared safe
+( var|escape|nl2br|upper|escape )
+&amp;LT;FABIEN&amp;GT;&lt;BR /&gt;
+TWIG
+
+--TEST--
+"autoescape" tag handles filters preserving the safety
+--TEMPLATE--
+{% autoescape 'html' %}
+
+(preserves_safety is preserving safety for "html")
+
+1. Unsafe values are still unsafe
+( var|preserves_safety|escape )
+{{ var|preserves_safety }}
+
+2. Safe values are still safe
+( var|escape|preserves_safety )
+{{ var|escape|preserves_safety }}
+
+3. Re-escape values that are escaped for an other contexts
+( var|escape_something|preserves_safety|escape )
+{{ var|escape_something|preserves_safety }}
+
+4. Still escape when using filters not declared safe
+( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
+{{ var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'}) }}
+
+{% endautoescape %}
+--DATA--
+return array('var' => "<Fabien>\nTwig")
+--EXPECT--
+
+(preserves_safety is preserving safety for "html")
+
+1. Unsafe values are still unsafe
+( var|preserves_safety|escape )
+&lt;FABIEN&gt;
+TWIG
+
+2. Safe values are still safe
+( var|escape|preserves_safety )
+&LT;FABIEN&GT;
+TWIG
+
+3. Re-escape values that are escaped for an other contexts
+( var|escape_something|preserves_safety|escape )
+&lt;FABIEN&gt;
+TWIG
+
+4. Still escape when using filters not declared safe
+( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
+&amp;LT;FABPOT&amp;GT;
+TWIG
+
+--TEST--
+"block" tag
+--TEMPLATE--
+{% block title1 %}FOO{% endblock %}
+{% block title2 foo|lower %}
+--TEMPLATE(foo.twig)--
+{% block content %}{% endblock %}
+--DATA--
+return array('foo' => 'bar')
+--EXPECT--
+FOObar
+--TEST--
+"block" tag
+--TEMPLATE--
+{% block content %}
+ {% block content %}
+ {% endblock %}
+{% endblock %}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Error_Syntax: The block 'content' has already been defined line 2 in "index.twig" at line 3
+--TEST--
+"§" special chars in a block name
+--TEMPLATE--
+{% block § %}
+{% endblock § %}
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+"embed" tag
+--TEMPLATE--
+FOO
+{% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ block1extended
+ {% endblock %}
+{% endembed %}
+
+BAR
+--TEMPLATE(foo.twig)--
+A
+{% block c1 %}
+ block1
+{% endblock %}
+B
+{% block c2 %}
+ block2
+{% endblock %}
+C
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+A
+ block1
+
+ block1extended
+ B
+ block2
+C
+BAR
+--TEST--
+"embed" tag
+--TEMPLATE(index.twig)--
+FOO
+{% embed "foo.twig" %}
+ {% block c1 %}
+ {{ nothing }}
+ {% endblock %}
+{% endembed %}
+BAR
+--TEMPLATE(foo.twig)--
+{% block c1 %}{% endblock %}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Error_Runtime: Variable "nothing" does not exist in "index.twig" at line 5
+--TEST--
+"embed" tag
+--TEMPLATE--
+FOO
+{% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ block1extended
+ {% endblock %}
+{% endembed %}
+
+{% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ block1extended
+ {% endblock %}
+{% endembed %}
+
+BAR
+--TEMPLATE(foo.twig)--
+A
+{% block c1 %}
+ block1
+{% endblock %}
+B
+{% block c2 %}
+ block2
+{% endblock %}
+C
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+A
+ block1
+
+ block1extended
+ B
+ block2
+C
+
+A
+ block1
+
+ block1extended
+ B
+ block2
+C
+BAR
+--TEST--
+"embed" tag
+--TEMPLATE--
+{% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ {% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ block1extended
+ {% endblock %}
+ {% endembed %}
+
+ {% endblock %}
+{% endembed %}
+--TEMPLATE(foo.twig)--
+A
+{% block c1 %}
+ block1
+{% endblock %}
+B
+{% block c2 %}
+ block2
+{% endblock %}
+C
+--DATA--
+return array()
+--EXPECT--
+A
+ block1
+
+
+A
+ block1
+
+ block1extended
+ B
+ block2
+C
+ B
+ block2
+C
+--TEST--
+"embed" tag
+--TEMPLATE--
+{% extends "base.twig" %}
+
+{% block c1 %}
+ {{ parent() }}
+ blockc1baseextended
+{% endblock %}
+
+{% block c2 %}
+ {{ parent() }}
+
+ {% embed "foo.twig" %}
+ {% block c1 %}
+ {{ parent() }}
+ block1extended
+ {% endblock %}
+ {% endembed %}
+{% endblock %}
+--TEMPLATE(base.twig)--
+A
+{% block c1 %}
+ blockc1base
+{% endblock %}
+{% block c2 %}
+ blockc2base
+{% endblock %}
+B
+--TEMPLATE(foo.twig)--
+A
+{% block c1 %}
+ block1
+{% endblock %}
+B
+{% block c2 %}
+ block2
+{% endblock %}
+C
+--DATA--
+return array()
+--EXPECT--
+A
+ blockc1base
+
+ blockc1baseextended
+ blockc2base
+
+
+
+A
+ block1
+
+ block1extended
+ B
+ block2
+CB--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
+--TEST--
+"filter" tag applies a filter on its children
+--TEMPLATE--
+{% filter json_encode|raw %}test{% endfilter %}
+--DATA--
+return array()
+--EXPECT--
+"test"
+--TEST--
+"filter" tags accept multiple chained filters
+--TEMPLATE--
+{% filter lower|title %}
+ {{ var }}
+{% endfilter %}
+--DATA--
+return array('var' => 'VAR')
+--EXPECT--
+ Var
+--TEST--
+"filter" tags can be nested at will
+--TEMPLATE--
+{% filter lower|title %}
+ {{ var }}
+ {% filter upper %}
+ {{ var }}
+ {% endfilter %}
+ {{ var }}
+{% endfilter %}
+--DATA--
+return array('var' => 'var')
+--EXPECT--
+ Var
+ Var
+ Var
+--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
+--TEST--
+"filter" tag applies the filter on "if" tags
+--TEMPLATE--
+{% filter upper %}
+{% if items %}
+{{ items|join(', ') }}
+{% endif %}
+
+{% if items.3 is defined %}
+FOO
+{% else %}
+{{ items.1 }}
+{% endif %}
+
+{% if items.3 is defined %}
+FOO
+{% elseif items.1 %}
+{{ items.0 }}
+{% endif %}
+
+{% endfilter %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+A, B
+
+B
+
+A
+--TEST--
+"for" tag takes a condition
+--TEMPLATE--
+{% for i in 1..5 if i is odd -%}
+ {{ loop.index }}.{{ i }}{{ foo.bar }}
+{% endfor %}
+--DATA--
+return array('foo' => array('bar' => 'X'))
+--CONFIG--
+return array('strict_variables' => false)
+--EXPECT--
+1.1X
+2.3X
+3.5X
+--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
+--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()
+--CONFIG--
+return array('strict_variables' => false)
+--EXPECT--
+ no item
+--TEST--
+"for" tag does not reset inner variables
+--TEMPLATE--
+{% for i in 1..2 %}
+ {% for j in 0..2 %}
+ {{k}}{% set k = k+1 %} {{ loop.parent.loop.index }}
+ {% endfor %}
+{% endfor %}
+--DATA--
+return array('k' => 0)
+--EXPECT--
+ 0 1
+ 1 1
+ 2 1
+ 3 2
+ 4 2
+ 5 2
+--TEST--
+"for" tag can iterate over keys and values
+--TEMPLATE--
+{% for key, item in items %}
+ * {{ key }}/{{ item }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * 0/a
+ * 1/b
+--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
+--TEST--
+"for" tag adds a loop variable to the context locally
+--TEMPLATE--
+{% for item in items %}
+{% endfor %}
+{% if loop is not defined %}WORKS{% endif %}
+--DATA--
+return array('items' => array())
+--EXPECT--
+WORKS
+--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
+--TEST--
+"for" tag
+--TEMPLATE--
+{% for i, item in items if loop.last > 0 %}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXCEPTION--
+Twig_Error_Syntax: The "loop" variable cannot be used in a looping condition in "index.twig" at line 2
+--TEST--
+"for" tag
+--TEMPLATE--
+{% for i, item in items if i > 0 %}
+ {{ loop.last }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXCEPTION--
+Twig_Error_Syntax: The "loop.last" variable is not defined when looping with a condition in "index.twig" at line 3
+--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
+--TEST--
+"for" tag iterates over iterable and countable objects
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+ * {{ loop.index }}/{{ loop.index0 }}
+ * {{ loop.revindex }}/{{ loop.revindex0 }}
+ * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
+
+{% endfor %}
+
+{% for key, value in items %}
+ * {{ key }}/{{ value }}
+{% endfor %}
+
+{% for key in items|keys %}
+ * {{ key }}
+{% endfor %}
+--DATA--
+class ItemsIteratorCountable implements Iterator, Countable
+{
+ 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); }
+ public function count() { return count($this->values); }
+}
+return array('items' => new ItemsIteratorCountable())
+--EXPECT--
+ * bar
+ * 1/0
+ * 2/1
+ * 1//2
+
+ * foo
+ * 2/1
+ * 1/0
+ * /1/2
+
+
+ * foo/bar
+ * bar/foo
+
+ * foo
+ * bar
+--TEST--
+"for" tag iterates over iterable objects
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+ * {{ loop.index }}/{{ loop.index0 }}
+ * {{ loop.first }}
+
+{% endfor %}
+
+{% for key, value in 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
+ * 1/0
+ * 1
+
+ * foo
+ * 2/1
+ *
+
+
+ * foo/bar
+ * bar/foo
+
+ * foo
+ * bar
+--TEST--
+"for" tags can be nested
+--TEMPLATE--
+{% for key, item in 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)
+--TEST--
+"for" tag iterates over item values
+--TEMPLATE--
+{% for item in items %}
+ * {{ item }}
+{% endfor %}
+--DATA--
+return array('items' => array('a', 'b'))
+--EXPECT--
+ * a
+ * b
+--TEST--
+global variables
+--TEMPLATE--
+{% include "included.twig" %}
+{% from "included.twig" import foobar %}
+{{ foobar() }}
+--TEMPLATE(included.twig)--
+{% macro foobar() %}
+called foobar
+{% endmacro %}
+--DATA--
+return array();
+--EXPECT--
+called foobar
+--TEST--
+"if" creates a condition
+--TEMPLATE--
+{% if a is defined %}
+ {{ a }}
+{% elseif b is defined %}
+ {{ b }}
+{% else %}
+ NOTHING
+{% endif %}
+--DATA--
+return array('a' => 'a')
+--EXPECT--
+ a
+--DATA--
+return array('b' => 'b')
+--EXPECT--
+ b
+--DATA--
+return array()
+--EXPECT--
+ NOTHING
+--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
+--TEST--
+"include" tag
+--TEMPLATE--
+FOO
+{% include "foo.twig" %}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array()
+--EXPECT--
+FOO
+
+FOOBAR
+BAR
+--TEST--
+"include" tag allows expressions for the template to include
+--TEMPLATE--
+FOO
+{% include foo %}
+
+BAR
+--TEMPLATE(foo.twig)--
+FOOBAR
+--DATA--
+return array('foo' => 'foo.twig')
+--EXPECT--
+FOO
+
+FOOBAR
+BAR
+--TEST--
+"include" tag
+--TEMPLATE--
+{% include ["foo.twig", "bar.twig"] ignore missing %}
+{% include "foo.twig" ignore missing %}
+{% include "foo.twig" ignore missing with {} %}
+{% include "foo.twig" ignore missing with {} only %}
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+"include" tag
+--TEMPLATE--
+{% extends "base.twig" %}
+
+{% block content %}
+ {{ parent() }}
+{% endblock %}
+--TEMPLATE(base.twig)--
+{% block content %}
+ {% include "foo.twig" %}
+{% endblock %}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
+--TEST--
+"include" tag
+--TEMPLATE--
+{% include "foo.twig" %}
+--DATA--
+return array();
+--EXCEPTION--
+Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
+--TEST--
+"include" tag accept variables and only
+--TEMPLATE--
+{% include "foo.twig" %}
+{% include "foo.twig" only %}
+{% include "foo.twig" with {'foo1': 'bar'} %}
+{% include "foo.twig" with {'foo1': 'bar'} only %}
+--TEMPLATE(foo.twig)--
+{% for k, v in _context %}{{ k }},{% endfor %}
+--DATA--
+return array('foo' => 'bar')
+--EXPECT--
+foo,global,_parent,
+global,_parent,
+foo,global,foo1,_parent,
+foo1,global,_parent,
+--TEST--
+"include" tag accepts Twig_Template instance
+--TEMPLATE--
+{% include foo %} FOO
+--TEMPLATE(foo.twig)--
+BAR
+--DATA--
+return array('foo' => $twig->loadTemplate('foo.twig'))
+--EXPECT--
+BAR FOO
+--TEST--
+"include" tag
+--TEMPLATE--
+{% include ["foo.twig", "bar.twig"] %}
+{% include ["bar.twig", "foo.twig"] %}
+--TEMPLATE(foo.twig)--
+foo
+--DATA--
+return array()
+--EXPECT--
+foo
+foo
+--TEST--
+"include" tag accept variables
+--TEMPLATE--
+{% include "foo.twig" with {'foo': 'bar'} %}
+{% include "foo.twig" with vars %}
+--TEMPLATE(foo.twig)--
+{{ foo }}
+--DATA--
+return array('vars' => array('foo' => 'bar'))
+--EXPECT--
+bar
+bar
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "foo.twig" %}
+
+{% block content %}
+FOO
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+FOO
+--TEST--
+block_expr2
+--TEMPLATE--
+{% extends "base2.twig" %}
+
+{% block element -%}
+ Element:
+ {{- parent() -}}
+{% endblock %}
+--TEMPLATE(base2.twig)--
+{% extends "base.twig" %}
+--TEMPLATE(base.twig)--
+{% spaceless %}
+{% block element -%}
+ <div>
+ {%- if item.children is defined %}
+ {%- for item in item.children %}
+ {{- block('element') -}}
+ {% endfor %}
+ {%- endif -%}
+ </div>
+{%- endblock %}
+{% endspaceless %}
+--DATA--
+return array(
+ 'item' => array(
+ 'children' => array(
+ null,
+ null,
+ )
+ )
+)
+--EXPECT--
+Element:<div>Element:<div></div>Element:<div></div></div>
+--TEST--
+block_expr
+--TEMPLATE--
+{% extends "base.twig" %}
+
+{% block element -%}
+ Element:
+ {{- parent() -}}
+{% endblock %}
+--TEMPLATE(base.twig)--
+{% spaceless %}
+{% block element -%}
+ <div>
+ {%- if item.children is defined %}
+ {%- for item in item.children %}
+ {{- block('element') -}}
+ {% endfor %}
+ {%- endif -%}
+ </div>
+{%- endblock %}
+{% endspaceless %}
+--DATA--
+return array(
+ 'item' => array(
+ 'children' => array(
+ null,
+ null,
+ )
+ )
+)
+--EXPECT--
+Element:<div>Element:<div></div>Element:<div></div></div>
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends standalone ? foo : 'bar.twig' %}
+
+{% block content %}{{ parent() }}FOO{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}FOO{% endblock %}
+--TEMPLATE(bar.twig)--
+{% block content %}BAR{% endblock %}
+--DATA--
+return array('foo' => 'foo.twig', 'standalone' => true)
+--EXPECT--
+FOOFOO
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends foo %}
+
+{% block content %}
+FOO
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}{% endblock %}
+--DATA--
+return array('foo' => 'foo.twig')
+--EXPECT--
+FOO
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "foo.twig" %}
+--TEMPLATE(foo.twig)--
+{% block content %}FOO{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+FOO
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends ["foo.twig", "bar.twig"] %}
+--TEMPLATE(bar.twig)--
+{% block content %}
+foo
+{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+foo
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "layout.twig" %}{% block content %}{{ parent() }}index {% endblock %}
+--TEMPLATE(layout.twig)--
+{% extends "base.twig" %}{% block content %}{{ parent() }}layout {% endblock %}
+--TEMPLATE(base.twig)--
+{% block content %}base {% endblock %}
+--DATA--
+return array()
+--EXPECT--
+base layout index
+--TEST--
+"block" tag
+--TEMPLATE--
+{% block content %}
+ CONTENT
+ {%- block subcontent -%}
+ SUBCONTENT
+ {%- endblock -%}
+ ENDCONTENT
+{% endblock %}
+--TEMPLATE(foo.twig)--
+--DATA--
+return array()
+--EXPECT--
+CONTENTSUBCONTENTENDCONTENT
+--TEST--
+"block" tag
+--TEMPLATE--
+{% extends "foo.twig" %}
+
+{% block content %}
+ {% block subcontent %}
+ {% block subsubcontent %}
+ SUBSUBCONTENT
+ {% endblock %}
+ {% endblock %}
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}
+ {% block subcontent %}
+ SUBCONTENT
+ {% endblock %}
+{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+SUBSUBCONTENT
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "layout.twig" %}
+{% block inside %}INSIDE{% endblock inside %}
+--TEMPLATE(layout.twig)--
+{% extends "base.twig" %}
+{% block body %}
+ {% block inside '' %}
+{% endblock body %}
+--TEMPLATE(base.twig)--
+{% block body '' %}
+--DATA--
+return array()
+--EXPECT--
+INSIDE
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends foo ? 'foo.twig' : 'bar.twig' %}
+--TEMPLATE(foo.twig)--
+FOO
+--TEMPLATE(bar.twig)--
+BAR
+--DATA--
+return array('foo' => true)
+--EXPECT--
+FOO
+--DATA--
+return array('foo' => false)
+--EXPECT--
+BAR
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% block content %}
+ {% extends "foo.twig" %}
+{% endblock %}
+--EXCEPTION--
+Twig_Error_Syntax: Cannot extend from a block in "index.twig" at line 3
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "base.twig" %}
+{% block content %}{% include "included.twig" %}{% endblock %}
+
+{% block footer %}Footer{% endblock %}
+--TEMPLATE(included.twig)--
+{% extends "base.twig" %}
+{% block content %}Included Content{% endblock %}
+--TEMPLATE(base.twig)--
+{% block content %}Default Content{% endblock %}
+
+{% block footer %}Default Footer{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+Included Content
+Default Footer
+Footer
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "foo.twig" %}
+
+{% block content %}
+ {% block inside %}
+ INSIDE OVERRIDDEN
+ {% endblock %}
+
+ BEFORE
+ {{ parent() }}
+ AFTER
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}
+ BAR
+{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+
+INSIDE OVERRIDDEN
+
+ BEFORE
+ BAR
+
+ AFTER
+--TEST--
+"extends" tag
+--TEMPLATE--
+{% extends "foo.twig" %}
+
+{% block content %}{{ parent() }}FOO{{ parent() }}{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}BAR{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+BARFOOBAR
+--TEST--
+"parent" tag
+--TEMPLATE--
+{% use 'foo.twig' %}
+
+{% block content %}
+ {{ parent() }}
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}BAR{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+BAR
+--TEST--
+"parent" tag
+--TEMPLATE--
+{% block content %}
+ {{ parent() }}
+{% endblock %}
+--EXCEPTION--
+Twig_Error_Syntax: Calling "parent" on a template that does not extend nor "use" another template is forbidden in "index.twig" at line 3
+--TEST--
+"extends" tag accepts Twig_Template instance
+--TEMPLATE--
+{% extends foo %}
+
+{% block content %}
+{{ parent() }}FOO
+{% endblock %}
+--TEMPLATE(foo.twig)--
+{% block content %}BAR{% endblock %}
+--DATA--
+return array('foo' => $twig->loadTemplate('foo.twig'))
+--EXPECT--
+BARFOO
+--TEST--
+"parent" function
+--TEMPLATE--
+{% extends "parent.twig" %}
+
+{% use "use1.twig" %}
+{% use "use2.twig" %}
+
+{% block content_parent %}
+ {{ parent() }}
+{% endblock %}
+
+{% block content_use1 %}
+ {{ parent() }}
+{% endblock %}
+
+{% block content_use2 %}
+ {{ parent() }}
+{% endblock %}
+
+{% block content %}
+ {{ block('content_use1_only') }}
+ {{ block('content_use2_only') }}
+{% endblock %}
+--TEMPLATE(parent.twig)--
+{% block content_parent 'content_parent' %}
+{% block content_use1 'content_parent' %}
+{% block content_use2 'content_parent' %}
+{% block content '' %}
+--TEMPLATE(use1.twig)--
+{% block content_use1 'content_use1' %}
+{% block content_use2 'content_use1' %}
+{% block content_use1_only 'content_use1_only' %}
+--TEMPLATE(use2.twig)--
+{% block content_use2 'content_use2' %}
+{% block content_use2_only 'content_use2_only' %}
+--DATA--
+return array()
+--EXPECT--
+ content_parent
+ content_use1
+ content_use2
+ content_use1_only
+ content_use2_only
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% import _self as macros %}
+
+{{ macros.input('username') }}
+{{ macros.input('password', null, 'password', 1) }}
+
+{% macro input(name, value, type, size) %}
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
+{% endmacro %}
+--DATA--
+return array()
+--EXPECT--
+ <input type="text" name="username" value="" size="20">
+
+ <input type="password" name="password" value="" size="1">
+--TEST--
+"macro" tag supports name for endmacro
+--TEMPLATE--
+{% import _self as macros %}
+
+{{ macros.foo() }}
+{{ macros.bar() }}
+
+{% macro foo() %}foo{% endmacro %}
+{% macro bar() %}bar{% endmacro bar %}
+--DATA--
+return array()
+--EXPECT--
+foo
+bar
+
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% import 'forms.twig' as forms %}
+
+{{ forms.input('username') }}
+{{ forms.input('password', null, 'password', 1) }}
+--TEMPLATE(forms.twig)--
+{% macro input(name, value, type, size) %}
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
+{% endmacro %}
+--DATA--
+return array()
+--EXPECT--
+ <input type="text" name="username" value="" size="20">
+
+ <input type="password" name="password" value="" size="1">
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% from 'forms.twig' import foo %}
+{% from 'forms.twig' import foo as foobar, bar %}
+
+{{ foo('foo') }}
+{{ foobar('foo') }}
+{{ bar('foo') }}
+--TEMPLATE(forms.twig)--
+{% macro foo(name) %}foo{{ name }}{% endmacro %}
+{% macro bar(name) %}bar{{ name }}{% endmacro %}
+--DATA--
+return array()
+--EXPECT--
+foofoo
+foofoo
+barfoo
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% from 'forms.twig' import foo %}
+
+{{ foo('foo') }}
+{{ foo() }}
+--TEMPLATE(forms.twig)--
+{% macro foo(name) %}{{ name|default('foo') }}{{ global }}{% endmacro %}
+--DATA--
+return array()
+--EXPECT--
+fooglobal
+fooglobal
+--TEST--
+"macro" tag
+--TEMPLATE--
+{% import _self as forms %}
+
+{{ forms.input('username') }}
+{{ forms.input('password', null, 'password', 1) }}
+
+{% macro input(name, value, type, size) %}
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
+{% endmacro %}
+--DATA--
+return array()
+--EXPECT--
+ <input type="text" name="username" value="" size="20">
+
+ <input type="password" name="password" value="" size="1">
+--TEST--
+"raw" tag
+--TEMPLATE--
+{% raw %}
+{{ foo }}
+{% endraw %}
+--DATA--
+return array()
+--EXPECT--
+{{ foo }}
+--TEST--
+"raw" tag
+--TEMPLATE--
+{% raw %}
+{{ foo }}
+{% endverbatim %}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Error_Syntax: Unexpected end of file: Unclosed "raw" block in "index.twig" at line 2
+--TEST--
+"raw" tag
+--TEMPLATE--
+1***
+
+{%- raw %}
+ {{ 'bla' }}
+{% endraw %}
+
+1***
+2***
+
+{%- raw -%}
+ {{ 'bla' }}
+{% endraw %}
+
+2***
+3***
+
+{%- raw -%}
+ {{ 'bla' }}
+{% endraw -%}
+
+3***
+4***
+
+{%- raw -%}
+ {{ 'bla' }}
+{%- endraw %}
+
+4***
+5***
+
+{%- raw -%}
+ {{ 'bla' }}
+{%- endraw -%}
+
+5***
+--DATA--
+return array()
+--EXPECT--
+1***
+ {{ 'bla' }}
+
+
+1***
+2***{{ 'bla' }}
+
+
+2***
+3***{{ 'bla' }}
+3***
+4***{{ 'bla' }}
+
+4***
+5***{{ 'bla' }}5***
+--TEST--
+sandbox tag
+--TEMPLATE--
+{%- sandbox %}
+ {%- include "foo.twig" %}
+ a
+{%- endsandbox %}
+--TEMPLATE(foo.twig)--
+foo
+--EXCEPTION--
+Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 4
+--TEST--
+sandbox tag
+--TEMPLATE--
+{%- sandbox %}
+ {%- include "foo.twig" %}
+
+ {% if 1 %}
+ {%- include "foo.twig" %}
+ {% endif %}
+{%- endsandbox %}
+--TEMPLATE(foo.twig)--
+foo
+--EXCEPTION--
+Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 5
+--TEST--
+sandbox tag
+--TEMPLATE--
+{%- sandbox %}
+ {%- include "foo.twig" %}
+{%- endsandbox %}
+
+{%- sandbox %}
+ {%- include "foo.twig" %}
+ {%- include "foo.twig" %}
+{%- endsandbox %}
+
+{%- sandbox %}{% include "foo.twig" %}{% endsandbox %}
+--TEMPLATE(foo.twig)--
+foo
+--DATA--
+return array()
+--EXPECT--
+foo
+foo
+foo
+foo
+--TEST--
+"set" tag
+--TEMPLATE--
+{% set foo = 'foo' %}
+{% set bar = 'foo<br />' %}
+
+{{ foo }}
+{{ bar }}
+
+{% set foo, bar = 'foo', 'bar' %}
+
+{{ foo }}{{ bar }}
+--DATA--
+return array()
+--EXPECT--
+foo
+foo&lt;br /&gt;
+
+
+foobar
+--TEST--
+"set" tag block empty capture
+--TEMPLATE--
+{% set foo %}{% endset %}
+
+{% if foo %}FAIL{% endif %}
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+"set" tag block capture
+--TEMPLATE--
+{% set foo %}f<br />o<br />o{% endset %}
+
+{{ foo }}
+--DATA--
+return array()
+--EXPECT--
+f<br />o<br />o
+--TEST--
+"set" tag
+--TEMPLATE--
+{% set foo, bar = 'foo' ~ 'bar', 'bar' ~ 'foo' %}
+
+{{ foo }}
+{{ bar }}
+--DATA--
+return array()
+--EXPECT--
+foobar
+barfoo
+--TEST--
+"spaceless" tag removes whites between HTML tags
+--TEMPLATE--
+{% spaceless %}
+
+ <div> <div> foo </div> </div>
+
+{% endspaceless %}
+--DATA--
+return array()
+--EXPECT--
+<div><div> foo </div></div>
+--TEST--
+"§" custom tag
+--TEMPLATE--
+{% § %}
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+Whitespace trimming on tags.
+--TEMPLATE--
+{{ 5 * '{#-'|length }}
+{{ '{{-'|length * 5 + '{%-'|length }}
+
+Trim on control tag:
+{% for i in range(1, 9) -%}
+ {{ i }}
+{%- endfor %}
+
+
+Trim on output tag:
+{% for i in range(1, 9) %}
+ {{- i -}}
+{% endfor %}
+
+
+Trim comments:
+
+{#- Invisible -#}
+
+After the comment.
+
+Trim leading space:
+{% if leading %}
+
+ {{- leading }}
+{% endif %}
+
+{%- if leading %}
+ {{- leading }}
+
+{%- endif %}
+
+
+Trim trailing space:
+{% if trailing -%}
+ {{ trailing -}}
+
+{% endif -%}
+
+Combined:
+
+{%- if both -%}
+<ul>
+ <li> {{- both -}} </li>
+</ul>
+
+{%- endif -%}
+
+end
+--DATA--
+return array('leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both')
+--EXPECT--
+15
+18
+
+Trim on control tag:
+123456789
+
+Trim on output tag:
+123456789
+
+Trim comments:After the comment.
+
+Trim leading space:
+leading space
+leading space
+
+Trim trailing space:
+trailing spaceCombined:<ul>
+ <li>both</li>
+</ul>end
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "blocks.twig" with content as foo %}
+
+{{ block('foo') }}
+--TEMPLATE(blocks.twig)--
+{% block content 'foo' %}
+--DATA--
+return array()
+--EXPECT--
+foo
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "blocks.twig" %}
+
+{{ block('content') }}
+--TEMPLATE(blocks.twig)--
+{% block content 'foo' %}
+--DATA--
+return array()
+--EXPECT--
+foo
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "foo.twig" %}
+--TEMPLATE(foo.twig)--
+{% use "bar.twig" %}
+--TEMPLATE(bar.twig)--
+--DATA--
+return array()
+--EXPECT--
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "foo.twig" %}
+
+{{ block('content') }}
+{{ block('foo') }}
+{{ block('bar') }}
+--TEMPLATE(foo.twig)--
+{% use "bar.twig" %}
+
+{% block content 'foo' %}
+{% block foo 'foo' %}
+--TEMPLATE(bar.twig)--
+{% block content 'bar' %}
+{% block bar 'bar' %}
+--DATA--
+return array()
+--EXPECT--
+foo
+foo
+bar
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "ancestor.twig" %}
+{% use "parent.twig" %}
+
+{{ block('container') }}
+--TEMPLATE(parent.twig)--
+{% block sub_container %}
+ <div class="overriden_sub_container">overriden sub_container</div>
+{% endblock %}
+--TEMPLATE(ancestor.twig)--
+{% block container %}
+ <div class="container">{{ block('sub_container') }}</div>
+{% endblock %}
+
+{% block sub_container %}
+ <div class="sub_container">sub_container</div>
+{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+<div class="container"> <div class="overriden_sub_container">overriden sub_container</div>
+</div>
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "parent.twig" %}
+
+{{ block('container') }}
+--TEMPLATE(parent.twig)--
+{% use "ancestor.twig" %}
+
+{% block sub_container %}
+ <div class="overriden_sub_container">overriden sub_container</div>
+{% endblock %}
+--TEMPLATE(ancestor.twig)--
+{% block container %}
+ <div class="container">{{ block('sub_container') }}</div>
+{% endblock %}
+
+{% block sub_container %}
+ <div class="sub_container">sub_container</div>
+{% endblock %}
+--DATA--
+return array()
+--EXPECT--
+<div class="container"> <div class="overriden_sub_container">overriden sub_container</div>
+</div>
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "foo.twig" with content as foo_content %}
+{% use "bar.twig" %}
+
+{{ block('content') }}
+{{ block('foo') }}
+{{ block('bar') }}
+{{ block('foo_content') }}
+--TEMPLATE(foo.twig)--
+{% block content 'foo' %}
+{% block foo 'foo' %}
+--TEMPLATE(bar.twig)--
+{% block content 'bar' %}
+{% block bar 'bar' %}
+--DATA--
+return array()
+--EXPECT--
+bar
+foo
+bar
+foo
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use "foo.twig" %}
+{% use "bar.twig" %}
+
+{{ block('content') }}
+{{ block('foo') }}
+{{ block('bar') }}
+--TEMPLATE(foo.twig)--
+{% block content 'foo' %}
+{% block foo 'foo' %}
+--TEMPLATE(bar.twig)--
+{% block content 'bar' %}
+{% block bar 'bar' %}
+--DATA--
+return array()
+--EXPECT--
+bar
+foo
+bar
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use 'file2.html.twig'%}
+{% block foobar %}
+ {{- parent() -}}
+ Content of block (second override)
+{% endblock foobar %}
+--TEMPLATE(file2.html.twig)--
+{% use 'file1.html.twig' %}
+{% block foobar %}
+ {{- parent() -}}
+ Content of block (first override)
+{% endblock foobar %}
+--TEMPLATE(file1.html.twig)--
+{% block foobar -%}
+ Content of block
+{% endblock foobar %}
+--DATA--
+return array()
+--EXPECT--
+Content of block
+Content of block (first override)
+Content of block (second override)
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use 'file2.html.twig' %}
+{% use 'file1.html.twig' with foo %}
+{% block foo %}
+ {{- parent() -}}
+ Content of foo (second override)
+{% endblock foo %}
+{% block bar %}
+ {{- parent() -}}
+ Content of bar (second override)
+{% endblock bar %}
+--TEMPLATE(file2.html.twig)--
+{% use 'file1.html.twig' %}
+{% block foo %}
+ {{- parent() -}}
+ Content of foo (first override)
+{% endblock foo %}
+{% block bar %}
+ {{- parent() -}}
+ Content of bar (first override)
+{% endblock bar %}
+--TEMPLATE(file1.html.twig)--
+{% block foo -%}
+ Content of foo
+{% endblock foo %}
+{% block bar -%}
+ Content of bar
+{% endblock bar %}
+--DATA--
+return array()
+--EXPECT--
+Content of foo
+Content of foo (first override)
+Content of foo (second override)
+Content of bar
+Content of bar (second override)
+--TEST--
+"use" tag
+--TEMPLATE--
+{% use 'file2.html.twig' with foobar as base_base_foobar %}
+{% block foobar %}
+ {{- block('base_base_foobar') -}}
+ Content of block (second override)
+{% endblock foobar %}
+--TEMPLATE(file2.html.twig)--
+{% use 'file1.html.twig' with foobar as base_foobar %}
+{% block foobar %}
+ {{- block('base_foobar') -}}
+ Content of block (first override)
+{% endblock foobar %}
+--TEMPLATE(file1.html.twig)--
+{% block foobar -%}
+ Content of block
+{% endblock foobar %}
+--DATA--
+return array()
+--EXPECT--
+Content of block
+Content of block (first override)
+Content of block (second override)
+--TEST--
+"verbatim" tag
+--TEMPLATE--
+{% verbatim %}
+{{ foo }}
+{% endverbatim %}
+--DATA--
+return array()
+--EXPECT--
+{{ foo }}
+--TEST--
+"verbatim" tag
+--TEMPLATE--
+{% verbatim %}
+{{ foo }}
+{% endraw %}
+--DATA--
+return array()
+--EXCEPTION--
+Twig_Error_Syntax: Unexpected end of file: Unclosed "verbatim" block in "index.twig" at line 2
+--TEST--
+"verbatim" tag
+--TEMPLATE--
+1***
+
+{%- verbatim %}
+ {{ 'bla' }}
+{% endverbatim %}
+
+1***
+2***
+
+{%- verbatim -%}
+ {{ 'bla' }}
+{% endverbatim %}
+
+2***
+3***
+
+{%- verbatim -%}
+ {{ 'bla' }}
+{% endverbatim -%}
+
+3***
+4***
+
+{%- verbatim -%}
+ {{ 'bla' }}
+{%- endverbatim %}
+
+4***
+5***
+
+{%- verbatim -%}
+ {{ 'bla' }}
+{%- endverbatim -%}
+
+5***
+--DATA--
+return array()
+--EXPECT--
+1***
+ {{ 'bla' }}
+
+
+1***
+2***{{ 'bla' }}
+
+
+2***
+3***{{ 'bla' }}
+3***
+4***{{ 'bla' }}
+
+4***
+5***{{ 'bla' }}5***
+--TEST--
+array index test
+--TEMPLATE--
+{% for key, value in days %}
+{{ key }}
+{% endfor %}
+--DATA--
+return array('days' => array(
+ 1 => array('money' => 9),
+ 2 => array('money' => 21),
+ 3 => array('money' => 38),
+ 4 => array('money' => 6),
+ 18 => array('money' => 6),
+ 19 => array('money' => 3),
+ 31 => array('money' => 11),
+));
+--EXPECT--
+1
+2
+3
+4
+18
+19
+31
+--TEST--
+"const" test
+--TEMPLATE--
+{{ 8 is constant('E_NOTICE') ? 'ok' : 'no' }}
+{{ 'bar' is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}
+{{ value is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}
+{{ 2 is constant('ARRAY_AS_PROPS', object) ? 'ok' : 'no' }}
+--DATA--
+return array('value' => 'bar', 'object' => new ArrayObject(array('hi')));
+--EXPECT--
+ok
+ok
+ok
+ok--TEST--
+"defined" test
+--TEMPLATE--
+{{ definedVar is defined ? 'ok' : 'ko' }}
+{{ definedVar is not defined ? 'ko' : 'ok' }}
+{{ undefinedVar is defined ? 'ko' : 'ok' }}
+{{ undefinedVar is not defined ? 'ok' : 'ko' }}
+{{ zeroVar is defined ? 'ok' : 'ko' }}
+{{ nullVar is defined ? 'ok' : 'ko' }}
+{{ nested.definedVar is defined ? 'ok' : 'ko' }}
+{{ nested['definedVar'] is defined ? 'ok' : 'ko' }}
+{{ nested.definedVar is not defined ? 'ko' : 'ok' }}
+{{ nested.undefinedVar is defined ? 'ko' : 'ok' }}
+{{ nested['undefinedVar'] is defined ? 'ko' : 'ok' }}
+{{ nested.undefinedVar is not defined ? 'ok' : 'ko' }}
+{{ nested.zeroVar is defined ? 'ok' : 'ko' }}
+{{ nested.nullVar is defined ? 'ok' : 'ko' }}
+{{ nested.definedArray.0 is defined ? 'ok' : 'ko' }}
+{{ nested['definedArray'][0] is defined ? 'ok' : 'ko' }}
+{{ object.foo is defined ? 'ok' : 'ko' }}
+{{ object.undefinedMethod is defined ? 'ko' : 'ok' }}
+{{ object.getFoo() is defined ? 'ok' : 'ko' }}
+{{ object.getFoo('a') is defined ? 'ok' : 'ko' }}
+{{ object.undefinedMethod() is defined ? 'ko' : 'ok' }}
+{{ object.undefinedMethod('a') is defined ? 'ko' : 'ok' }}
+{{ object.self.foo is defined ? 'ok' : 'ko' }}
+{{ object.self.undefinedMethod is defined ? 'ko' : 'ok' }}
+{{ object.undefinedMethod.self is defined ? 'ko' : 'ok' }}
+--DATA--
+return array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'nullVar' => null,
+ 'nested' => array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'nullVar' => null,
+ 'definedArray' => array(0),
+ ),
+ 'object' => new TwigTestFoo(),
+);
+--EXPECT--
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+--DATA--
+return array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'nullVar' => null,
+ 'nested' => array(
+ 'definedVar' => 'defined',
+ 'zeroVar' => 0,
+ 'nullVar' => null,
+ 'definedArray' => array(0),
+ ),
+ 'object' => new TwigTestFoo(),
+);
+--CONFIG--
+return array('strict_variables' => false)
+--EXPECT--
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+ok
+--TEST--
+"empty" test
+--TEMPLATE--
+{{ foo is empty ? 'ok' : 'ko' }}
+{{ bar is empty ? 'ok' : 'ko' }}
+{{ foobar is empty ? 'ok' : 'ko' }}
+{{ array is empty ? 'ok' : 'ko' }}
+{{ zero is empty ? 'ok' : 'ko' }}
+{{ string is empty ? 'ok' : 'ko' }}
+{{ countable_empty is empty ? 'ok' : 'ko' }}
+{{ countable_not_empty is empty ? 'ok' : 'ko' }}
+{{ markup_empty is empty ? 'ok' : 'ko' }}
+{{ markup_not_empty is empty ? 'ok' : 'ko' }}
+--DATA--
+
+class CountableStub implements Countable
+{
+ private $items;
+
+ public function __construct(array $items)
+ {
+ $this->items = $items;
+ }
+
+ public function count()
+ {
+ return count($this->items);
+ }
+}
+return array(
+ 'foo' => '', 'bar' => null, 'foobar' => false, 'array' => array(), 'zero' => 0, 'string' => '0',
+ 'countable_empty' => new CountableStub(array()), 'countable_not_empty' => new CountableStub(array(1, 2)),
+ 'markup_empty' => new Twig_Markup('', 'UTF-8'), 'markup_not_empty' => new Twig_Markup('test', 'UTF-8'),
+);
+--EXPECT--
+ok
+ok
+ok
+ok
+ko
+ko
+ok
+ko
+ok
+ko
+--TEST--
+"even" test
+--TEMPLATE--
+{{ 1 is even ? 'ko' : 'ok' }}
+{{ 2 is even ? 'ok' : 'ko' }}
+{{ 1 is not even ? 'ok' : 'ko' }}
+{{ 2 is not even ? 'ko' : 'ok' }}
+--DATA--
+return array()
+--EXPECT--
+ok
+ok
+ok
+ok
+--TEST--
+Twig supports the in operator
+--TEMPLATE--
+{% if bar in foo %}
+TRUE
+{% endif %}
+{% if not (bar in foo) %}
+{% else %}
+TRUE
+{% endif %}
+{% if bar not in foo %}
+{% else %}
+TRUE
+{% endif %}
+{% if 'a' in bar %}
+TRUE
+{% endif %}
+{% if 'c' not in bar %}
+TRUE
+{% endif %}
+{% if '' not in bar %}
+TRUE
+{% endif %}
+{% if '' in '' %}
+TRUE
+{% endif %}
+{% if '0' not in '' %}
+TRUE
+{% endif %}
+{% if 'a' not in '0' %}
+TRUE
+{% endif %}
+{% if '0' in '0' %}
+TRUE
+{% endif %}
+{{ false in [0, 1] ? 'TRUE' : 'FALSE' }}
+{{ true in [0, 1] ? 'TRUE' : 'FALSE' }}
+{{ '0' in [0, 1] ? 'TRUE' : 'FALSE' }}
+{{ '' in [0, 1] ? 'TRUE' : 'FALSE' }}
+{{ 0 in ['', 1] ? 'TRUE' : 'FALSE' }}
+{{ '' in 'foo' ? 'TRUE' : 'FALSE' }}
+{{ 0 in 'foo' ? 'TRUE' : 'FALSE' }}
+{{ false in 'foo' ? 'TRUE' : 'FALSE' }}
+{{ true in '100' ? 'TRUE' : 'FALSE' }}
+{{ [] in 'Array' ? 'TRUE' : 'FALSE' }}
+{{ [] in [true, false] ? 'TRUE' : 'FALSE' }}
+{{ [] in [true, ''] ? 'TRUE' : 'FALSE' }}
+{{ [] in [true, []] ? 'TRUE' : 'FALSE' }}
+{{ dir_object in 'foo'~dir_name ? 'TRUE' : 'FALSE' }}
+{{ 5 in 125 ? 'TRUE' : 'FALSE' }}
+--DATA--
+return array('bar' => 'bar', 'foo' => array('bar' => 'bar'), 'dir_name' => dirname(__FILE__), 'dir_object' => new SplFileInfo(dirname(__FILE__)))
+--EXPECT--
+TRUE
+TRUE
+TRUE
+TRUE
+TRUE
+TRUE
+TRUE
+TRUE
+TRUE
+FALSE
+FALSE
+FALSE
+FALSE
+FALSE
+TRUE
+FALSE
+FALSE
+FALSE
+FALSE
+FALSE
+FALSE
+TRUE
+FALSE
+FALSE
+--TEST--
+Twig supports the in operator when using objects
+--TEMPLATE--
+{% if object in object_list %}
+TRUE
+{% endif %}
+--DATA--
+$foo = new TwigTestFoo();
+$foo1 = new TwigTestFoo();
+
+$foo->position = $foo1;
+$foo1->position = $foo;
+
+return array(
+ 'object' => $foo,
+ 'object_list' => array($foo1, $foo),
+);
+--EXPECT--
+TRUE
+--TEST--
+"iterable" test
+--TEMPLATE--
+{{ foo is iterable ? 'ok' : 'ko' }}
+{{ traversable is iterable ? 'ok' : 'ko' }}
+{{ obj is iterable ? 'ok' : 'ko' }}
+{{ val is iterable ? 'ok' : 'ko' }}
+--DATA--
+return array(
+ 'foo' => array(),
+ 'traversable' => new ArrayIterator(array()),
+ 'obj' => new stdClass(),
+ 'val' => 'test',
+);
+--EXPECT--
+ok
+ok
+ko
+ko--TEST--
+"odd" test
+--TEMPLATE--
+{{ 1 is odd ? 'ok' : 'ko' }}
+{{ 2 is odd ? 'ko' : 'ok' }}
+--DATA--
+return array()
+--EXPECT--
+ok
+ok