diff options
author | Nicolas LÅ“uillet <nicolas.loeuillet@gmail.com> | 2013-08-02 22:40:51 +0200 |
---|---|---|
committer | Nicolas LÅ“uillet <nicolas.loeuillet@gmail.com> | 2013-08-02 22:40:51 +0200 |
commit | a4565e88edbc8e3bd092a475469769c86a4c350c (patch) | |
tree | a6a3c935b03a23ff87575c8c315cf8ba78fe68c2 /inc/Twig/Parser.php | |
parent | f6c9baab3efeec1d0efa151e276fc08d5b58f9e9 (diff) | |
download | wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.gz wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.tar.zst wallabag-a4565e88edbc8e3bd092a475469769c86a4c350c.zip |
add Twig & refactor poche
Diffstat (limited to 'inc/Twig/Parser.php')
-rw-r--r-- | inc/Twig/Parser.php | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/inc/Twig/Parser.php b/inc/Twig/Parser.php new file mode 100644 index 00000000..958e46b3 --- /dev/null +++ b/inc/Twig/Parser.php | |||
@@ -0,0 +1,394 @@ | |||
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 | * Default parser implementation. | ||
15 | * | ||
16 | * @author Fabien Potencier <fabien@symfony.com> | ||
17 | */ | ||
18 | class Twig_Parser implements Twig_ParserInterface | ||
19 | { | ||
20 | protected $stack = array(); | ||
21 | protected $stream; | ||
22 | protected $parent; | ||
23 | protected $handlers; | ||
24 | protected $visitors; | ||
25 | protected $expressionParser; | ||
26 | protected $blocks; | ||
27 | protected $blockStack; | ||
28 | protected $macros; | ||
29 | protected $env; | ||
30 | protected $reservedMacroNames; | ||
31 | protected $importedSymbols; | ||
32 | protected $traits; | ||
33 | protected $embeddedTemplates = array(); | ||
34 | |||
35 | /** | ||
36 | * Constructor. | ||
37 | * | ||
38 | * @param Twig_Environment $env A Twig_Environment instance | ||
39 | */ | ||
40 | public function __construct(Twig_Environment $env) | ||
41 | { | ||
42 | $this->env = $env; | ||
43 | } | ||
44 | |||
45 | public function getEnvironment() | ||
46 | { | ||
47 | return $this->env; | ||
48 | } | ||
49 | |||
50 | public function getVarName() | ||
51 | { | ||
52 | return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false)); | ||
53 | } | ||
54 | |||
55 | public function getFilename() | ||
56 | { | ||
57 | return $this->stream->getFilename(); | ||
58 | } | ||
59 | |||
60 | /** | ||
61 | * Converts a token stream to a node tree. | ||
62 | * | ||
63 | * @param Twig_TokenStream $stream A token stream instance | ||
64 | * | ||
65 | * @return Twig_Node_Module A node tree | ||
66 | */ | ||
67 | public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false) | ||
68 | { | ||
69 | // push all variables into the stack to keep the current state of the parser | ||
70 | $vars = get_object_vars($this); | ||
71 | unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']); | ||
72 | $this->stack[] = $vars; | ||
73 | |||
74 | // tag handlers | ||
75 | if (null === $this->handlers) { | ||
76 | $this->handlers = $this->env->getTokenParsers(); | ||
77 | $this->handlers->setParser($this); | ||
78 | } | ||
79 | |||
80 | // node visitors | ||
81 | if (null === $this->visitors) { | ||
82 | $this->visitors = $this->env->getNodeVisitors(); | ||
83 | } | ||
84 | |||
85 | if (null === $this->expressionParser) { | ||
86 | $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators()); | ||
87 | } | ||
88 | |||
89 | $this->stream = $stream; | ||
90 | $this->parent = null; | ||
91 | $this->blocks = array(); | ||
92 | $this->macros = array(); | ||
93 | $this->traits = array(); | ||
94 | $this->blockStack = array(); | ||
95 | $this->importedSymbols = array(array()); | ||
96 | $this->embeddedTemplates = array(); | ||
97 | |||
98 | try { | ||
99 | $body = $this->subparse($test, $dropNeedle); | ||
100 | |||
101 | if (null !== $this->parent) { | ||
102 | if (null === $body = $this->filterBodyNodes($body)) { | ||
103 | $body = new Twig_Node(); | ||
104 | } | ||
105 | } | ||
106 | } catch (Twig_Error_Syntax $e) { | ||
107 | if (!$e->getTemplateFile()) { | ||
108 | $e->setTemplateFile($this->getFilename()); | ||
109 | } | ||
110 | |||
111 | if (!$e->getTemplateLine()) { | ||
112 | $e->setTemplateLine($this->stream->getCurrent()->getLine()); | ||
113 | } | ||
114 | |||
115 | throw $e; | ||
116 | } | ||
117 | |||
118 | $node = new Twig_Node_Module(new Twig_Node_Body(array($body)), $this->parent, new Twig_Node($this->blocks), new Twig_Node($this->macros), new Twig_Node($this->traits), $this->embeddedTemplates, $this->getFilename()); | ||
119 | |||
120 | $traverser = new Twig_NodeTraverser($this->env, $this->visitors); | ||
121 | |||
122 | $node = $traverser->traverse($node); | ||
123 | |||
124 | // restore previous stack so previous parse() call can resume working | ||
125 | foreach (array_pop($this->stack) as $key => $val) { | ||
126 | $this->$key = $val; | ||
127 | } | ||
128 | |||
129 | return $node; | ||
130 | } | ||
131 | |||
132 | public function subparse($test, $dropNeedle = false) | ||
133 | { | ||
134 | $lineno = $this->getCurrentToken()->getLine(); | ||
135 | $rv = array(); | ||
136 | while (!$this->stream->isEOF()) { | ||
137 | switch ($this->getCurrentToken()->getType()) { | ||
138 | case Twig_Token::TEXT_TYPE: | ||
139 | $token = $this->stream->next(); | ||
140 | $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine()); | ||
141 | break; | ||
142 | |||
143 | case Twig_Token::VAR_START_TYPE: | ||
144 | $token = $this->stream->next(); | ||
145 | $expr = $this->expressionParser->parseExpression(); | ||
146 | $this->stream->expect(Twig_Token::VAR_END_TYPE); | ||
147 | $rv[] = new Twig_Node_Print($expr, $token->getLine()); | ||
148 | break; | ||
149 | |||
150 | case Twig_Token::BLOCK_START_TYPE: | ||
151 | $this->stream->next(); | ||
152 | $token = $this->getCurrentToken(); | ||
153 | |||
154 | if ($token->getType() !== Twig_Token::NAME_TYPE) { | ||
155 | throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename()); | ||
156 | } | ||
157 | |||
158 | if (null !== $test && call_user_func($test, $token)) { | ||
159 | if ($dropNeedle) { | ||
160 | $this->stream->next(); | ||
161 | } | ||
162 | |||
163 | if (1 === count($rv)) { | ||
164 | return $rv[0]; | ||
165 | } | ||
166 | |||
167 | return new Twig_Node($rv, array(), $lineno); | ||
168 | } | ||
169 | |||
170 | $subparser = $this->handlers->getTokenParser($token->getValue()); | ||
171 | if (null === $subparser) { | ||
172 | if (null !== $test) { | ||
173 | $error = sprintf('Unexpected tag name "%s"', $token->getValue()); | ||
174 | if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) { | ||
175 | $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno); | ||
176 | } | ||
177 | |||
178 | throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename()); | ||
179 | } | ||
180 | |||
181 | $message = sprintf('Unknown tag name "%s"', $token->getValue()); | ||
182 | if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) { | ||
183 | $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives)); | ||
184 | } | ||
185 | |||
186 | throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename()); | ||
187 | } | ||
188 | |||
189 | $this->stream->next(); | ||
190 | |||
191 | $node = $subparser->parse($token); | ||
192 | if (null !== $node) { | ||
193 | $rv[] = $node; | ||
194 | } | ||
195 | break; | ||
196 | |||
197 | default: | ||
198 | throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename()); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | if (1 === count($rv)) { | ||
203 | return $rv[0]; | ||
204 | } | ||
205 | |||
206 | return new Twig_Node($rv, array(), $lineno); | ||
207 | } | ||
208 | |||
209 | public function addHandler($name, $class) | ||
210 | { | ||
211 | $this->handlers[$name] = $class; | ||
212 | } | ||
213 | |||
214 | public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) | ||
215 | { | ||
216 | $this->visitors[] = $visitor; | ||
217 | } | ||
218 | |||
219 | public function getBlockStack() | ||
220 | { | ||
221 | return $this->blockStack; | ||
222 | } | ||
223 | |||
224 | public function peekBlockStack() | ||
225 | { | ||
226 | return $this->blockStack[count($this->blockStack) - 1]; | ||
227 | } | ||
228 | |||
229 | public function popBlockStack() | ||
230 | { | ||
231 | array_pop($this->blockStack); | ||
232 | } | ||
233 | |||
234 | public function pushBlockStack($name) | ||
235 | { | ||
236 | $this->blockStack[] = $name; | ||
237 | } | ||
238 | |||
239 | public function hasBlock($name) | ||
240 | { | ||
241 | return isset($this->blocks[$name]); | ||
242 | } | ||
243 | |||
244 | public function getBlock($name) | ||
245 | { | ||
246 | return $this->blocks[$name]; | ||
247 | } | ||
248 | |||
249 | public function setBlock($name, $value) | ||
250 | { | ||
251 | $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine()); | ||
252 | } | ||
253 | |||
254 | public function hasMacro($name) | ||
255 | { | ||
256 | return isset($this->macros[$name]); | ||
257 | } | ||
258 | |||
259 | public function setMacro($name, Twig_Node_Macro $node) | ||
260 | { | ||
261 | if (null === $this->reservedMacroNames) { | ||
262 | $this->reservedMacroNames = array(); | ||
263 | $r = new ReflectionClass($this->env->getBaseTemplateClass()); | ||
264 | foreach ($r->getMethods() as $method) { | ||
265 | $this->reservedMacroNames[] = $method->getName(); | ||
266 | } | ||
267 | } | ||
268 | |||
269 | if (in_array($name, $this->reservedMacroNames)) { | ||
270 | throw new Twig_Error_Syntax(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword', $name), $node->getLine(), $this->getFilename()); | ||
271 | } | ||
272 | |||
273 | $this->macros[$name] = $node; | ||
274 | } | ||
275 | |||
276 | public function addTrait($trait) | ||
277 | { | ||
278 | $this->traits[] = $trait; | ||
279 | } | ||
280 | |||
281 | public function hasTraits() | ||
282 | { | ||
283 | return count($this->traits) > 0; | ||
284 | } | ||
285 | |||
286 | public function embedTemplate(Twig_Node_Module $template) | ||
287 | { | ||
288 | $template->setIndex(mt_rand()); | ||
289 | |||
290 | $this->embeddedTemplates[] = $template; | ||
291 | } | ||
292 | |||
293 | public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null) | ||
294 | { | ||
295 | $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node); | ||
296 | } | ||
297 | |||
298 | public function getImportedSymbol($type, $alias) | ||
299 | { | ||
300 | foreach ($this->importedSymbols as $functions) { | ||
301 | if (isset($functions[$type][$alias])) { | ||
302 | return $functions[$type][$alias]; | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | public function isMainScope() | ||
308 | { | ||
309 | return 1 === count($this->importedSymbols); | ||
310 | } | ||
311 | |||
312 | public function pushLocalScope() | ||
313 | { | ||
314 | array_unshift($this->importedSymbols, array()); | ||
315 | } | ||
316 | |||
317 | public function popLocalScope() | ||
318 | { | ||
319 | array_shift($this->importedSymbols); | ||
320 | } | ||
321 | |||
322 | /** | ||
323 | * Gets the expression parser. | ||
324 | * | ||
325 | * @return Twig_ExpressionParser The expression parser | ||
326 | */ | ||
327 | public function getExpressionParser() | ||
328 | { | ||
329 | return $this->expressionParser; | ||
330 | } | ||
331 | |||
332 | public function getParent() | ||
333 | { | ||
334 | return $this->parent; | ||
335 | } | ||
336 | |||
337 | public function setParent($parent) | ||
338 | { | ||
339 | $this->parent = $parent; | ||
340 | } | ||
341 | |||
342 | /** | ||
343 | * Gets the token stream. | ||
344 | * | ||
345 | * @return Twig_TokenStream The token stream | ||
346 | */ | ||
347 | public function getStream() | ||
348 | { | ||
349 | return $this->stream; | ||
350 | } | ||
351 | |||
352 | /** | ||
353 | * Gets the current token. | ||
354 | * | ||
355 | * @return Twig_Token The current token | ||
356 | */ | ||
357 | public function getCurrentToken() | ||
358 | { | ||
359 | return $this->stream->getCurrent(); | ||
360 | } | ||
361 | |||
362 | protected function filterBodyNodes(Twig_NodeInterface $node) | ||
363 | { | ||
364 | // check that the body does not contain non-empty output nodes | ||
365 | if ( | ||
366 | ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data'))) | ||
367 | || | ||
368 | (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface) | ||
369 | ) { | ||
370 | if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) { | ||
371 | throw new Twig_Error_Syntax('A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed.', $node->getLine(), $this->getFilename()); | ||
372 | } | ||
373 | |||
374 | throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename()); | ||
375 | } | ||
376 | |||
377 | // bypass "set" nodes as they "capture" the output | ||
378 | if ($node instanceof Twig_Node_Set) { | ||
379 | return $node; | ||
380 | } | ||
381 | |||
382 | if ($node instanceof Twig_NodeOutputInterface) { | ||
383 | return; | ||
384 | } | ||
385 | |||
386 | foreach ($node as $k => $n) { | ||
387 | if (null !== $n && null === $n = $this->filterBodyNodes($n)) { | ||
388 | $node->removeNode($k); | ||
389 | } | ||
390 | } | ||
391 | |||
392 | return $node; | ||
393 | } | ||
394 | } | ||