shop.balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

Parser.php (12410B)


      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('sha256', uniqid(mt_rand(), true), false));
     53     }
     54 
     55     public function getFilename()
     56     {
     57         return $this->stream->getFilename();
     58     }
     59 
     60     /**
     61      * {@inheritdoc}
     62      */
     63     public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
     64     {
     65         // push all variables into the stack to keep the current state of the parser
     66         // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336
     67         $vars = array();
     68         foreach ($this as $k => $v) {
     69             $vars[$k] = $v;
     70         }
     71 
     72         unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']);
     73         $this->stack[] = $vars;
     74 
     75         // tag handlers
     76         if (null === $this->handlers) {
     77             $this->handlers = $this->env->getTokenParsers();
     78             $this->handlers->setParser($this);
     79         }
     80 
     81         // node visitors
     82         if (null === $this->visitors) {
     83             $this->visitors = $this->env->getNodeVisitors();
     84         }
     85 
     86         if (null === $this->expressionParser) {
     87             $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators());
     88         }
     89 
     90         $this->stream = $stream;
     91         $this->parent = null;
     92         $this->blocks = array();
     93         $this->macros = array();
     94         $this->traits = array();
     95         $this->blockStack = array();
     96         $this->importedSymbols = array(array());
     97         $this->embeddedTemplates = array();
     98 
     99         try {
    100             $body = $this->subparse($test, $dropNeedle);
    101 
    102             if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) {
    103                 $body = new Twig_Node();
    104             }
    105         } catch (Twig_Error_Syntax $e) {
    106             if (!$e->getTemplateFile()) {
    107                 $e->setTemplateFile($this->getFilename());
    108             }
    109 
    110             if (!$e->getTemplateLine()) {
    111                 $e->setTemplateLine($this->stream->getCurrent()->getLine());
    112             }
    113 
    114             throw $e;
    115         }
    116 
    117         $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());
    118 
    119         $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
    120 
    121         $node = $traverser->traverse($node);
    122 
    123         // restore previous stack so previous parse() call can resume working
    124         foreach (array_pop($this->stack) as $key => $val) {
    125             $this->$key = $val;
    126         }
    127 
    128         return $node;
    129     }
    130 
    131     public function subparse($test, $dropNeedle = false)
    132     {
    133         $lineno = $this->getCurrentToken()->getLine();
    134         $rv = array();
    135         while (!$this->stream->isEOF()) {
    136             switch ($this->getCurrentToken()->getType()) {
    137                 case Twig_Token::TEXT_TYPE:
    138                     $token = $this->stream->next();
    139                     $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
    140                     break;
    141 
    142                 case Twig_Token::VAR_START_TYPE:
    143                     $token = $this->stream->next();
    144                     $expr = $this->expressionParser->parseExpression();
    145                     $this->stream->expect(Twig_Token::VAR_END_TYPE);
    146                     $rv[] = new Twig_Node_Print($expr, $token->getLine());
    147                     break;
    148 
    149                 case Twig_Token::BLOCK_START_TYPE:
    150                     $this->stream->next();
    151                     $token = $this->getCurrentToken();
    152 
    153                     if ($token->getType() !== Twig_Token::NAME_TYPE) {
    154                         throw new Twig_Error_Syntax('A block must start with a tag name.', $token->getLine(), $this->getFilename());
    155                     }
    156 
    157                     if (null !== $test && call_user_func($test, $token)) {
    158                         if ($dropNeedle) {
    159                             $this->stream->next();
    160                         }
    161 
    162                         if (1 === count($rv)) {
    163                             return $rv[0];
    164                         }
    165 
    166                         return new Twig_Node($rv, array(), $lineno);
    167                     }
    168 
    169                     $subparser = $this->handlers->getTokenParser($token->getValue());
    170                     if (null === $subparser) {
    171                         if (null !== $test) {
    172                             $e = new Twig_Error_Syntax(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->getFilename());
    173 
    174                             if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
    175                                 $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
    176                             }
    177                         } else {
    178                             $e = new Twig_Error_Syntax(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->getFilename());
    179                             $e->addSuggestions($token->getValue(), array_keys($this->env->getTags()));
    180                         }
    181 
    182                         throw $e;
    183                     }
    184 
    185                     $this->stream->next();
    186 
    187                     $node = $subparser->parse($token);
    188                     if (null !== $node) {
    189                         $rv[] = $node;
    190                     }
    191                     break;
    192 
    193                 default:
    194                     throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename());
    195             }
    196         }
    197 
    198         if (1 === count($rv)) {
    199             return $rv[0];
    200         }
    201 
    202         return new Twig_Node($rv, array(), $lineno);
    203     }
    204 
    205     public function addHandler($name, $class)
    206     {
    207         $this->handlers[$name] = $class;
    208     }
    209 
    210     public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
    211     {
    212         $this->visitors[] = $visitor;
    213     }
    214 
    215     public function getBlockStack()
    216     {
    217         return $this->blockStack;
    218     }
    219 
    220     public function peekBlockStack()
    221     {
    222         return $this->blockStack[count($this->blockStack) - 1];
    223     }
    224 
    225     public function popBlockStack()
    226     {
    227         array_pop($this->blockStack);
    228     }
    229 
    230     public function pushBlockStack($name)
    231     {
    232         $this->blockStack[] = $name;
    233     }
    234 
    235     public function hasBlock($name)
    236     {
    237         return isset($this->blocks[$name]);
    238     }
    239 
    240     public function getBlock($name)
    241     {
    242         return $this->blocks[$name];
    243     }
    244 
    245     public function setBlock($name, Twig_Node_Block $value)
    246     {
    247         $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine());
    248     }
    249 
    250     public function hasMacro($name)
    251     {
    252         return isset($this->macros[$name]);
    253     }
    254 
    255     public function setMacro($name, Twig_Node_Macro $node)
    256     {
    257         if ($this->isReservedMacroName($name)) {
    258             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());
    259         }
    260 
    261         $this->macros[$name] = $node;
    262     }
    263 
    264     public function isReservedMacroName($name)
    265     {
    266         if (null === $this->reservedMacroNames) {
    267             $this->reservedMacroNames = array();
    268             $r = new ReflectionClass($this->env->getBaseTemplateClass());
    269             foreach ($r->getMethods() as $method) {
    270                 $methodName = strtolower($method->getName());
    271 
    272                 if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) {
    273                     $this->reservedMacroNames[] = substr($methodName, 3);
    274                 }
    275             }
    276         }
    277 
    278         return in_array(strtolower($name), $this->reservedMacroNames);
    279     }
    280 
    281     public function addTrait($trait)
    282     {
    283         $this->traits[] = $trait;
    284     }
    285 
    286     public function hasTraits()
    287     {
    288         return count($this->traits) > 0;
    289     }
    290 
    291     public function embedTemplate(Twig_Node_Module $template)
    292     {
    293         $template->setIndex(mt_rand());
    294 
    295         $this->embeddedTemplates[] = $template;
    296     }
    297 
    298     public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null)
    299     {
    300         $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node);
    301     }
    302 
    303     public function getImportedSymbol($type, $alias)
    304     {
    305         foreach ($this->importedSymbols as $functions) {
    306             if (isset($functions[$type][$alias])) {
    307                 return $functions[$type][$alias];
    308             }
    309         }
    310     }
    311 
    312     public function isMainScope()
    313     {
    314         return 1 === count($this->importedSymbols);
    315     }
    316 
    317     public function pushLocalScope()
    318     {
    319         array_unshift($this->importedSymbols, array());
    320     }
    321 
    322     public function popLocalScope()
    323     {
    324         array_shift($this->importedSymbols);
    325     }
    326 
    327     /**
    328      * Gets the expression parser.
    329      *
    330      * @return Twig_ExpressionParser The expression parser
    331      */
    332     public function getExpressionParser()
    333     {
    334         return $this->expressionParser;
    335     }
    336 
    337     public function getParent()
    338     {
    339         return $this->parent;
    340     }
    341 
    342     public function setParent($parent)
    343     {
    344         $this->parent = $parent;
    345     }
    346 
    347     /**
    348      * Gets the token stream.
    349      *
    350      * @return Twig_TokenStream The token stream
    351      */
    352     public function getStream()
    353     {
    354         return $this->stream;
    355     }
    356 
    357     /**
    358      * Gets the current token.
    359      *
    360      * @return Twig_Token The current token
    361      */
    362     public function getCurrentToken()
    363     {
    364         return $this->stream->getCurrent();
    365     }
    366 
    367     protected function filterBodyNodes(Twig_NodeInterface $node)
    368     {
    369         // check that the body does not contain non-empty output nodes
    370         if (
    371             ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
    372             ||
    373             (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
    374         ) {
    375             if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
    376                 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());
    377             }
    378 
    379             throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename());
    380         }
    381 
    382         // bypass "set" nodes as they "capture" the output
    383         if ($node instanceof Twig_Node_Set) {
    384             return $node;
    385         }
    386 
    387         if ($node instanceof Twig_NodeOutputInterface) {
    388             return;
    389         }
    390 
    391         foreach ($node as $k => $n) {
    392             if (null !== $n && null === $this->filterBodyNodes($n)) {
    393                 $node->removeNode($k);
    394             }
    395         }
    396 
    397         return $node;
    398     }
    399 }