From 15e46e598d8c64b1612cb4be102d704c63e0b604 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 15 Nov 2010 23:07:19 +0100 Subject: [PATCH] Changed Twig_Node_Expression_Filter to accept only one filter Chained filters are now represented by nested Twig_Node_Expression_Filter nodes internally. This makes it easier to analyse the tree --- lib/Twig/ExpressionParser.php | 24 +++-- lib/Twig/Node/Expression/Filter.php | 73 +++----------- lib/Twig/NodeVisitor/Escaper.php | 19 ++-- lib/Twig/NodeVisitor/SafeAnalysis.php | 8 +- lib/Twig/NodeVisitor/Sandbox.php | 4 +- lib/Twig/SimpleTokenParser.php | 3 +- lib/Twig/TokenParser/Filter.php | 7 +- test/Twig/Tests/Node/Expression/FilterTest.php | 129 +++--------------------- test/Twig/Tests/Node/TransTest.php | 7 +- 9 files changed, 60 insertions(+), 214 deletions(-) diff --git a/lib/Twig/ExpressionParser.php b/lib/Twig/ExpressionParser.php index 8a76372..e5d530a 100644 --- a/lib/Twig/ExpressionParser.php +++ b/lib/Twig/ExpressionParser.php @@ -401,9 +401,10 @@ class Twig_ExpressionParser $end = $this->parseExpression(); - $filters = new Twig_Node(array(new Twig_Node_Expression_Constant('range', $lineno), new Twig_Node(array($end)))); + $name = new Twig_Node_Expression_Constant('range', $lineno); + $arguments = new Twig_Node(array($end)); - return new Twig_Node_Expression_Filter($node, $filters, $lineno); + return new Twig_Node_Expression_Filter($node, $name, $arguments, $lineno); } public function parseSubscriptExpression($node) @@ -438,26 +439,27 @@ class Twig_ExpressionParser public function parseFilterExpression($node) { - $lineno = $this->parser->getCurrentToken()->getLine(); - $this->parser->getStream()->next(); - return new Twig_Node_Expression_Filter($node, $this->parseFilterExpressionRaw(), $lineno); + return $this->parseFilterExpressionRaw($node); } - public function parseFilterExpressionRaw() + public function parseFilterExpressionRaw($node, $tag = null) { - $filters = array(); + $lineno = $this->parser->getCurrentToken()->getLine(); + while (true) { $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE); - $filters[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); + $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine()); if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '(')) { - $filters[] = new Twig_Node(); + $arguments = new Twig_Node(); } else { - $filters[] = $this->parseArguments(); + $arguments = $this->parseArguments(); } + $node = new Twig_Node_Expression_Filter($node, $name, $arguments, $token->getLine(), $tag); + if (!$this->parser->getStream()->test(Twig_Token::OPERATOR_TYPE, '|')) { break; } @@ -465,7 +467,7 @@ class Twig_ExpressionParser $this->parser->getStream()->next(); } - return new Twig_Node($filters); + return $node; } public function parseArguments() diff --git a/lib/Twig/Node/Expression/Filter.php b/lib/Twig/Node/Expression/Filter.php index a291a05..33223d5 100644 --- a/lib/Twig/Node/Expression/Filter.php +++ b/lib/Twig/Node/Expression/Filter.php @@ -11,78 +11,33 @@ */ class Twig_Node_Expression_Filter extends Twig_Node_Expression { - public function __construct(Twig_NodeInterface $node, Twig_NodeInterface $filters, $lineno, $tag = null) + public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filter_name, Twig_NodeInterface $arguments, $lineno, $tag = null) { - parent::__construct(array('node' => $node, 'filters' => $filters), array(), $lineno, $tag); + parent::__construct(array('node' => $node, 'filter' => $filter_name, 'arguments' => $arguments), array(), $lineno, $tag); } public function compile($compiler) { $filterMap = $compiler->getEnvironment()->getFilters(); - $postponed = array(); - for ($i = count($this->getNode('filters')) - 1; $i >= 0; $i -= 2) { - $name = $this->getNode('filters')->getNode($i - 1)->getAttribute('value'); - $attrs = $this->getNode('filters')->getNode($i); - if (!isset($filterMap[$name])) { - throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine()); - } else { - $compiler->raw($filterMap[$name]->compile().($filterMap[$name]->needsEnvironment() ? '($this->env, ' : '(')); - } - $postponed[] = $attrs; + $name = $this->getNode('filter')->getAttribute('value'); + $attrs = $this->getNode('arguments'); + + if (!isset($filterMap[$name])) { + throw new Twig_Error_Syntax(sprintf('The filter "%s" does not exist', $name), $this->getLine()); + } else { + $compiler->raw($filterMap[$name]->compile().($filterMap[$name]->needsEnvironment() ? '($this->env, ' : '(')); } $this->getNode('node')->compile($compiler); - foreach (array_reverse($postponed) as $attributes) { - foreach ($attributes as $node) { - $compiler - ->raw(', ') - ->subcompile($node) + foreach ($attrs as $node) { + $compiler + ->raw(', ') + ->subcompile($node) ; - } - $compiler->raw(')'); - } - } - - public function prependFilter(Twig_Node_Expression_Constant $name, Twig_Node $end) - { - $filters = array($name, $end); - foreach ($this->getNode('filters') as $node) { - $filters[] = $node; - } - - $this->setNode('filters', new Twig_Node($filters, array(), $this->getNode('filters')->getLine())); - } - - public function appendFilter(Twig_Node_Expression_Constant $name, Twig_Node $end) - { - $filters = array(); - foreach ($this->getNode('filters') as $node) { - $filters[] = $node; - } - - $filters[] = $name; - $filters[] = $end; - - $this->setNode('filters', new Twig_Node($filters, array(), $this->getNode('filters')->getLine())); - } - - public function appendFilters(Twig_NodeInterface $filters) - { - for ($i = 0; $i < count($filters); $i += 2) { - $this->appendFilter($filters->getNode($i), $filters->getNode($i + 1)); - } - } - - public function hasFilter($name) - { - for ($i = 0; $i < count($this->getNode('filters')); $i += 2) { - if ($name == $this->getNode('filters')->getNode($i)->getAttribute('value')) { - return true; - } } - return false; + $compiler->raw(')'); } } diff --git a/lib/Twig/NodeVisitor/Escaper.php b/lib/Twig/NodeVisitor/Escaper.php index 6926d49..44e2f6b 100644 --- a/lib/Twig/NodeVisitor/Escaper.php +++ b/lib/Twig/NodeVisitor/Escaper.php @@ -90,22 +90,14 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface return $node; } - // escape - if ($expression instanceof Twig_Node_Expression_Filter) { - $filter = $this->getEscaperFilter($type, $expression->getLine()); - $expression->appendFilter($filter[0], $filter[1]); - - return $node; - } - if ($node instanceof Twig_Node_Print) { return new Twig_Node_Print( - new Twig_Node_Expression_Filter($expression, new Twig_Node($this->getEscaperFilter($type, $node->getLine())), $node->getLine()), + $this->getEscaperFilter($type, $expression), $node->getLine() ); } - return new Twig_Node_Expression_Filter($node, new Twig_Node($this->getEscaperFilter($type, $node->getLine())), $node->getLine()); + return $this->getEscaperFilter($type, $node); } protected function needEscaping(Twig_Environment $env) @@ -121,8 +113,11 @@ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface return false; } - protected function getEscaperFilter($type, $line) + protected function getEscaperFilter($type, Twig_NodeInterface $node) { - return array(new Twig_Node_Expression_Constant('escape', $line), new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line)))); + $line = $node->getLine(); + $name = new Twig_Node_Expression_Constant('escape', $line); + $args = new Twig_Node(array(new Twig_Node_Expression_Constant((string) $type, $line))); + return new Twig_Node_Expression_Filter($node, $name, $args, $line); } } diff --git a/lib/Twig/NodeVisitor/SafeAnalysis.php b/lib/Twig/NodeVisitor/SafeAnalysis.php index 79d1f06..7adfe11 100644 --- a/lib/Twig/NodeVisitor/SafeAnalysis.php +++ b/lib/Twig/NodeVisitor/SafeAnalysis.php @@ -34,12 +34,10 @@ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); $this->setSafe($node, $safe); } elseif ($node instanceof Twig_Node_Expression_Filter) { - // filter expression is safe when the last filter is safe + // filter expression is safe when the filter is safe $filterMap = $env->getFilters(); - $filters = $node->getNode('filters'); - $i = count($filters) - 2; - $name = $filters->getNode($i)->getAttribute('value'); - $args = $filters->getNode($i+1); + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); if (isset($filterMap[$name])) { $this->setSafe($node, $filterMap[$name]->getSafe($args)); } else { diff --git a/lib/Twig/NodeVisitor/Sandbox.php b/lib/Twig/NodeVisitor/Sandbox.php index 8720134..77a507b 100644 --- a/lib/Twig/NodeVisitor/Sandbox.php +++ b/lib/Twig/NodeVisitor/Sandbox.php @@ -45,9 +45,7 @@ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface // look for filters if ($node instanceof Twig_Node_Expression_Filter) { - for ($i = 0; $i < count($node->getNode('filters')); $i += 2) { - $this->filters[] = $node->getNode('filters')->getNode($i)->getAttribute('value'); - } + $this->filters[] = $node->getNode('filter')->getAttribute('value'); } // look for simple print statements ({{ article }}) diff --git a/lib/Twig/SimpleTokenParser.php b/lib/Twig/SimpleTokenParser.php index 9a681cb..40c71ba 100644 --- a/lib/Twig/SimpleTokenParser.php +++ b/lib/Twig/SimpleTokenParser.php @@ -65,7 +65,8 @@ abstract class Twig_SimpleTokenParser extends Twig_TokenParser { return new Twig_Node_Expression_Filter( $node, - new Twig_Node(array(new Twig_Node_Expression_Constant('raw', $line), new Twig_Node())), + new Twig_Node_Expression_Constant('raw', $line), + new Twig_Node(), $line ); } diff --git a/lib/Twig/TokenParser/Filter.php b/lib/Twig/TokenParser/Filter.php index 4d053b7..b800f10 100644 --- a/lib/Twig/TokenParser/Filter.php +++ b/lib/Twig/TokenParser/Filter.php @@ -19,20 +19,21 @@ class Twig_TokenParser_Filter extends Twig_TokenParser */ public function parse(Twig_Token $token) { - $filters = $this->parser->getExpressionParser()->parseFilterExpressionRaw(); + $name = '_tmp'.rand(10000, 99999); + $node = new Twig_Node_Expression_Name($name, $token->getLine()); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($node, $this->getTag()); $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true); $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE); - $name = '_tmp'.rand(10000, 99999); $ref = new Twig_Node_BlockReference($name, $token->getLine(), $this->getTag()); $block = new Twig_Node_Block($name, $body, $token->getLine()); $this->parser->setBlock($name, $block); $set = new Twig_Node_Set(true, new Twig_Node(array(new Twig_Node_Expression_AssignName($name, $token->getLine()))), new Twig_Node(array($ref)), $token->getLine(), $this->getTag()); - $filter = new Twig_Node_Expression_Filter(new Twig_Node_Expression_Name($name, $token->getLine()), $filters, $token->getLine(), $this->getTag()); $filter = new Twig_Node_Print($filter, $token->getLine(), $this->getTag()); return new Twig_Node(array($set, $filter)); diff --git a/test/Twig/Tests/Node/Expression/FilterTest.php b/test/Twig/Tests/Node/Expression/FilterTest.php index 42e58d0..e4c31d0 100644 --- a/test/Twig/Tests/Node/Expression/FilterTest.php +++ b/test/Twig/Tests/Node/Expression/FilterTest.php @@ -19,110 +19,13 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase public function testConstructor() { $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); + $name = new Twig_Node_Expression_Constant('upper', 0); + $args = new Twig_Node(); + $node = new Twig_Node_Expression_Filter($expr, $name, $args, 0); $this->assertEquals($expr, $node->getNode('node')); - $this->assertEquals($filters, $node->getNode('filters')); - } - - /** - * @covers Twig_Node_Expression_Filter::hasFilter - */ - public function testHasFilter() - { - $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); - - $this->assertTrue($node->hasFilter('upper')); - $this->assertFalse($node->hasFilter('lower')); - } - - /** - * @covers Twig_Node_Expression_Filter::prependFilter - */ - public function testPrependFilter() - { - $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); - - $a = new Twig_Node_Expression_Constant('lower', 0); - $b = new Twig_Node_Expression_Constant('foobar', 0); - $node->prependFilter($a, $b); - - $filters = new Twig_Node(array( - $a, - $b, - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - - $this->assertEquals($filters, $node->getNode('filters')); - } - - /** - * @covers Twig_Node_Expression_Filter::appendFilter - */ - public function testAppendFilter() - { - $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); - - $a = new Twig_Node_Expression_Constant('lower', 0); - $b = new Twig_Node_Expression_Constant('foobar', 0); - $node->appendFilter($a, $b); - - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - $a, - $b, - ), array(), 0); - - $this->assertEquals($filters, $node->getNode('filters')); - } - - /** - * @covers Twig_Node_Expression_Filter::appendFilters - */ - public function testAppendFilters() - { - $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); - - $others = new Twig_Node(array( - $a = new Twig_Node_Expression_Constant('lower', 0), - $b = new Twig_Node_Expression_Constant('foobar', 0), - ), array(), 0); - $node->appendFilters($others); - - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - $a, - $b, - ), array(), 0); - - $this->assertEquals($filters, $node->getNode('filters')); + $this->assertEquals($name, $node->getNode('filter')); + $this->assertEquals($args, $node->getNode('arguments')); } /** @@ -134,11 +37,7 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase parent::testCompile($node, $source, $environment); $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('foobar', 0), - new Twig_Node(array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); + $node = $this->createFilter($expr, 'foobar', array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))); $tests[] = array($node, '$this->resolveMissingFilter("foobar", array("foo", "bar", "foobar"))'); @@ -155,13 +54,8 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase $tests = array(); $expr = new Twig_Node_Expression_Constant('foo', 0); - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('upper', 0), - new Twig_Node(), - new Twig_Node_Expression_Constant('lower', 0), - new Twig_Node(array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))), - ), array(), 0); - $node = new Twig_Node_Expression_Filter($expr, $filters, 0); + $node = $this->createFilter($expr, 'upper'); + $node = $this->createFilter($node, 'lower', array(new Twig_Node_Expression_Constant('bar', 0), new Twig_Node_Expression_Constant('foobar', 0))); if (function_exists('mb_get_info')) { $tests[] = array($node, 'twig_lower_filter($this->env, twig_upper_filter($this->env, "foo"), "bar", "foobar")'); @@ -171,4 +65,11 @@ class Twig_Tests_Node_Expression_FilterTest extends Twig_Tests_Node_TestCase return $tests; } + + protected function createFilter($node, $name, array $arguments = array()) + { + $name = new Twig_Node_Expression_Constant($name, 0); + $arguments = new Twig_Node($arguments); + return new Twig_Node_Expression_Filter($node, $name, $arguments, 0); + } } diff --git a/test/Twig/Tests/Node/TransTest.php b/test/Twig/Tests/Node/TransTest.php index 962b4d3..c1495e4 100644 --- a/test/Twig/Tests/Node/TransTest.php +++ b/test/Twig/Tests/Node/TransTest.php @@ -79,14 +79,9 @@ class Twig_Tests_Node_TransTest extends Twig_Tests_Node_TestCase $tests[] = array($node, 'echo strtr(ngettext("Hey %name%, I have one apple", "Hey %name%, I have %count% apples", abs(12)), array("%name%" => (isset($context[\'name\']) ? $context[\'name\'] : null), "%name%" => (isset($context[\'name\']) ? $context[\'name\'] : null), "%count%" => abs(12), ));'); // with escaper extension set to on - $filters = new Twig_Node(array( - new Twig_Node_Expression_Constant('escape', 0), - new Twig_Node(), - ), array(), 0); - $body = new Twig_Node(array( new Twig_Node_Text('J\'ai ', 0), - new Twig_Node_Print(new Twig_Node_Expression_Filter(new Twig_Node_Expression_Name('foo', 0), $filters, 0), 0), + new Twig_Node_Print(new Twig_Node_Expression_Filter(new Twig_Node_Expression_Name('foo', 0), new Twig_Node_Expression_Constant('escape', 0), new Twig_Node(), 0), 0), new Twig_Node_Text(' pommes', 0), ), array(), 0); -- 1.7.2.5