diff options
Diffstat (limited to 'inc/Twig/TokenParser/For.php')
-rw-r--r-- | inc/Twig/TokenParser/For.php | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/inc/Twig/TokenParser/For.php b/inc/Twig/TokenParser/For.php new file mode 100644 index 00000000..98a6d079 --- /dev/null +++ b/inc/Twig/TokenParser/For.php | |||
@@ -0,0 +1,136 @@ | |||
1 | <?php | ||
2 | |||
3 | /* | ||
4 | * This file is part of Twig. | ||
5 | * | ||
6 | * (c) 2009 Fabien Potencier | ||
7 | * (c) 2009 Armin Ronacher | ||
8 | * | ||
9 | * For the full copyright and license information, please view the LICENSE | ||
10 | * file that was distributed with this source code. | ||
11 | */ | ||
12 | |||
13 | /** | ||
14 | * Loops over each item of a sequence. | ||
15 | * | ||
16 | * <pre> | ||
17 | * <ul> | ||
18 | * {% for user in users %} | ||
19 | * <li>{{ user.username|e }}</li> | ||
20 | * {% endfor %} | ||
21 | * </ul> | ||
22 | * </pre> | ||
23 | */ | ||
24 | class Twig_TokenParser_For extends Twig_TokenParser | ||
25 | { | ||
26 | /** | ||
27 | * Parses a token and returns a node. | ||
28 | * | ||
29 | * @param Twig_Token $token A Twig_Token instance | ||
30 | * | ||
31 | * @return Twig_NodeInterface A Twig_NodeInterface instance | ||
32 | */ | ||
33 | public function parse(Twig_Token $token) | ||
34 | { | ||
35 | $lineno = $token->getLine(); | ||
36 | $stream = $this->parser->getStream(); | ||
37 | $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); | ||
38 | $stream->expect(Twig_Token::OPERATOR_TYPE, 'in'); | ||
39 | $seq = $this->parser->getExpressionParser()->parseExpression(); | ||
40 | |||
41 | $ifexpr = null; | ||
42 | if ($stream->test(Twig_Token::NAME_TYPE, 'if')) { | ||
43 | $stream->next(); | ||
44 | $ifexpr = $this->parser->getExpressionParser()->parseExpression(); | ||
45 | } | ||
46 | |||
47 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
48 | $body = $this->parser->subparse(array($this, 'decideForFork')); | ||
49 | if ($stream->next()->getValue() == 'else') { | ||
50 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
51 | $else = $this->parser->subparse(array($this, 'decideForEnd'), true); | ||
52 | } else { | ||
53 | $else = null; | ||
54 | } | ||
55 | $stream->expect(Twig_Token::BLOCK_END_TYPE); | ||
56 | |||
57 | if (count($targets) > 1) { | ||
58 | $keyTarget = $targets->getNode(0); | ||
59 | $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine()); | ||
60 | $valueTarget = $targets->getNode(1); | ||
61 | $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); | ||
62 | } else { | ||
63 | $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno); | ||
64 | $valueTarget = $targets->getNode(0); | ||
65 | $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine()); | ||
66 | } | ||
67 | |||
68 | if ($ifexpr) { | ||
69 | $this->checkLoopUsageCondition($stream, $ifexpr); | ||
70 | $this->checkLoopUsageBody($stream, $body); | ||
71 | } | ||
72 | |||
73 | return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); | ||
74 | } | ||
75 | |||
76 | public function decideForFork(Twig_Token $token) | ||
77 | { | ||
78 | return $token->test(array('else', 'endfor')); | ||
79 | } | ||
80 | |||
81 | public function decideForEnd(Twig_Token $token) | ||
82 | { | ||
83 | return $token->test('endfor'); | ||
84 | } | ||
85 | |||
86 | // the loop variable cannot be used in the condition | ||
87 | protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node) | ||
88 | { | ||
89 | if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { | ||
90 | throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition', $node->getLine(), $stream->getFilename()); | ||
91 | } | ||
92 | |||
93 | foreach ($node as $n) { | ||
94 | if (!$n) { | ||
95 | continue; | ||
96 | } | ||
97 | |||
98 | $this->checkLoopUsageCondition($stream, $n); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | // check usage of non-defined loop-items | ||
103 | // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) | ||
104 | protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterface $node) | ||
105 | { | ||
106 | if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) { | ||
107 | $attribute = $node->getNode('attribute'); | ||
108 | if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) { | ||
109 | throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition', $attribute->getAttribute('value')), $node->getLine(), $stream->getFilename()); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | // should check for parent.loop.XXX usage | ||
114 | if ($node instanceof Twig_Node_For) { | ||
115 | return; | ||
116 | } | ||
117 | |||
118 | foreach ($node as $n) { | ||
119 | if (!$n) { | ||
120 | continue; | ||
121 | } | ||
122 | |||
123 | $this->checkLoopUsageBody($stream, $n); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * Gets the tag name associated with this token parser. | ||
129 | * | ||
130 | * @return string The tag name | ||
131 | */ | ||
132 | public function getTag() | ||
133 | { | ||
134 | return 'for'; | ||
135 | } | ||
136 | } | ||