]>
Commit | Line | Data |
---|---|---|
a4565e88 NL |
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 | } |