From 92d96e2eab7a9c266a7fec953ffd847b4e1e0b3e Mon Sep 17 00:00:00 2001 From: fabien Date: Tue, 22 Dec 2009 14:13:11 +0000 Subject: [PATCH] refactored node transformers to node visitors git-svn-id: http://svn.twig-project.org/trunk@201 93ef8e89-cb99-4229-a87c-7fa0fa45744b --- CHANGELOG | 5 ++ lib/Twig/Environment.php | 12 ++-- lib/Twig/Extension.php | 6 +- lib/Twig/Extension/Core.php | 10 ++- lib/Twig/Extension/Escaper.php | 10 ++-- lib/Twig/Extension/Sandbox.php | 8 +- lib/Twig/ExtensionInterface.php | 6 +- lib/Twig/NodeTransformer.php | 42 ------------ lib/Twig/NodeTransformer/Chain.php | 39 ----------- lib/Twig/NodeTransformer/Escaper.php | 119 -------------------------------- lib/Twig/NodeTransformer/Filter.php | 68 ------------------ lib/Twig/NodeTransformer/Sandbox.php | 58 ---------------- lib/Twig/NodeTraverser.php | 39 +++++++++++ lib/Twig/NodeVisitor/Escaper.php | 124 ++++++++++++++++++++++++++++++++++ lib/Twig/NodeVisitor/Filter.php | 75 ++++++++++++++++++++ lib/Twig/NodeVisitor/Sandbox.php | 59 ++++++++++++++++ lib/Twig/Parser.php | 20 +++--- 17 files changed, 340 insertions(+), 360 deletions(-) delete mode 100644 lib/Twig/NodeTransformer.php delete mode 100644 lib/Twig/NodeTransformer/Chain.php delete mode 100644 lib/Twig/NodeTransformer/Escaper.php delete mode 100644 lib/Twig/NodeTransformer/Filter.php delete mode 100644 lib/Twig/NodeTransformer/Sandbox.php create mode 100644 lib/Twig/NodeTraverser.php create mode 100644 lib/Twig/NodeVisitor/Escaper.php create mode 100644 lib/Twig/NodeVisitor/Filter.php create mode 100644 lib/Twig/NodeVisitor/Sandbox.php diff --git a/CHANGELOG b/CHANGELOG index a1c83f3..c455abc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,11 @@ environment constant by the "needs_environment" option: 'even' => new Twig_Filter_Function('twig_is_even_filter'), 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)), +If you have used the undocumented NodeTransformer classes, you will need to +upgrade to the new interface (please not that the interface is not yet stable). + + * refactored node transformers to node visitors + * fixed automatic-escaping for blocks * added a way to specify variables to pass to an included template * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules) * improved the filter system to allow object methods to be used as filters diff --git a/lib/Twig/Environment.php b/lib/Twig/Environment.php index daabe84..c691d97 100644 --- a/lib/Twig/Environment.php +++ b/lib/Twig/Environment.php @@ -25,7 +25,7 @@ class Twig_Environment protected $baseTemplateClass; protected $extensions; protected $parsers; - protected $transformers; + protected $visitors; protected $filters; protected $runtimeInitialized; protected $loadedTemplates; @@ -353,18 +353,18 @@ class Twig_Environment return $this->parsers; } - public function getNodeTransformers() + public function getNodeVisitors() { - if (null === $this->transformers) + if (null === $this->visitors) { - $this->transformers = array(); + $this->visitors = array(); foreach ($this->getExtensions() as $extension) { - $this->transformers = array_merge($this->transformers, $extension->getNodeTransformers()); + $this->visitors = array_merge($this->visitors, $extension->getNodeVisitors()); } } - return $this->transformers; + return $this->visitors; } public function getFilters() diff --git a/lib/Twig/Extension.php b/lib/Twig/Extension.php index 82046e9..a3c8156 100644 --- a/lib/Twig/Extension.php +++ b/lib/Twig/Extension.php @@ -30,11 +30,11 @@ abstract class Twig_Extension implements Twig_ExtensionInterface } /** - * Returns the node transformer instances to add to the existing list. + * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeTransformer instances + * @return array An array of Twig_NodeVisitorInterface instances */ - public function getNodeTransformers() + public function getNodeVisitors() { return array(); } diff --git a/lib/Twig/Extension/Core.php b/lib/Twig/Extension/Core.php index b8154d2..9c2ed90 100644 --- a/lib/Twig/Extension/Core.php +++ b/lib/Twig/Extension/Core.php @@ -43,13 +43,15 @@ class Twig_Extension_Core extends Twig_Extension } /** - * Returns the node transformer instances to add to the existing list. + * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeTransformer instances + * @return array An array of Twig_NodeVisitorInterface instances */ - public function getNodeTransformers() + public function getNodeVisitors() { - return array(new Twig_NodeTransformer_Filter()); + return array( + new Twig_NodeVisitor_Filter(), + ); } /** diff --git a/lib/Twig/Extension/Escaper.php b/lib/Twig/Extension/Escaper.php index 05eaec0..4a8a7cc 100644 --- a/lib/Twig/Extension/Escaper.php +++ b/lib/Twig/Extension/Escaper.php @@ -37,13 +37,13 @@ class Twig_Extension_Escaper extends Twig_Extension } /** - * Returns the node transformer instances to add to the existing list. + * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeTransformer instances + * @return array An array of Twig_NodeVisitorInterface instances */ - public function getNodeTransformers() + public function getNodeVisitors() { - return array(new Twig_NodeTransformer_Escaper()); + return array(new Twig_NodeVisitor_Escaper()); } /** @@ -74,7 +74,7 @@ class Twig_Extension_Escaper extends Twig_Extension } } -// tells the escaper node transformer that the string is safe +// tells the escaper node visitor that the string is safe function twig_safe_filter($string) { return $string; diff --git a/lib/Twig/Extension/Sandbox.php b/lib/Twig/Extension/Sandbox.php index 0baee4b..ddca886 100644 --- a/lib/Twig/Extension/Sandbox.php +++ b/lib/Twig/Extension/Sandbox.php @@ -21,13 +21,13 @@ class Twig_Extension_Sandbox extends Twig_Extension } /** - * Returns the node transformer instances to add to the existing list. + * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeTransformer instances + * @return array An array of Twig_NodeVisitorInterface instances */ - public function getNodeTransformers() + public function getNodeVisitors() { - return array(new Twig_NodeTransformer_Sandbox()); + return array(new Twig_NodeVisitor_Sandbox()); } public function enableSandbox() diff --git a/lib/Twig/ExtensionInterface.php b/lib/Twig/ExtensionInterface.php index cbfb96c..d18c3c3 100644 --- a/lib/Twig/ExtensionInterface.php +++ b/lib/Twig/ExtensionInterface.php @@ -33,11 +33,11 @@ interface Twig_ExtensionInterface public function getTokenParsers(); /** - * Returns the node transformer instances to add to the existing list. + * Returns the node visitor instances to add to the existing list. * - * @return array An array of Twig_NodeTransformer instances + * @return array An array of Twig_NodeVisitorInterface instances */ - public function getNodeTransformers(); + public function getNodeVisitors(); /** * Returns a list of filters to add to the existing list. diff --git a/lib/Twig/NodeTransformer.php b/lib/Twig/NodeTransformer.php deleted file mode 100644 index 82c4fbc..0000000 --- a/lib/Twig/NodeTransformer.php +++ /dev/null @@ -1,42 +0,0 @@ -env = $env; - } - - abstract public function visit(Twig_Node $node); - - protected function visitDeep(Twig_Node $node) - { - if (!$node instanceof Twig_NodeListInterface) - { - return $node; - } - - $newNodes = array(); - foreach ($nodes = $node->getNodes() as $k => $n) - { - if (null !== $n = $this->visit($n)) - { - $newNodes[$k] = $n; - } - } - - $node->setNodes($newNodes); - - return $node; - } -} diff --git a/lib/Twig/NodeTransformer/Chain.php b/lib/Twig/NodeTransformer/Chain.php deleted file mode 100644 index 6fd6fa0..0000000 --- a/lib/Twig/NodeTransformer/Chain.php +++ /dev/null @@ -1,39 +0,0 @@ -transformers = $transformers; - } - - public function setEnvironment(Twig_Environment $env) - { - parent::setEnvironment($env); - - foreach ($this->transformers as $transformer) - { - $transformer->setEnvironment($env); - } - } - - public function visit(Twig_Node $node) - { - foreach ($this->transformers as $transformer) - { - $node = $transformer->visit($node); - } - - return $node; - } -} diff --git a/lib/Twig/NodeTransformer/Escaper.php b/lib/Twig/NodeTransformer/Escaper.php deleted file mode 100644 index 646a04d..0000000 --- a/lib/Twig/NodeTransformer/Escaper.php +++ /dev/null @@ -1,119 +0,0 @@ -statusStack[] = $node->getValue(); - - $node = $this->visitDeep($node); - - array_pop($this->statusStack); - - // remove the node - return $node; - } - - if (!$node instanceof Twig_Node_Print) - { - return $this->visitDeep($node); - } - - if (false === $this->needEscaping()) - { - return $node; - } - - return $this->escapeNode($node); - } - - protected function escapeNode($node) - { - $expression = $node instanceof Twig_Node_Print ? $node->getExpression() : $node; - - if ($expression instanceof Twig_Node_Expression_Filter) - { - // don't escape if the primary node of the filter is not a variable - $nodes = $expression->getNodes(); - if (!$nodes[0] instanceof Twig_Node_Expression_Name) - { - return $node; - } - - // don't escape if there is already an "escaper" in the filter chain - $filterMap = $this->env->getFilters(); - foreach ($expression->getFilters() as $filter) - { - if (isset($filterMap[$filter[0]]) && $filterMap[$filter[0]]->isEscaper()) - { - return $node; - } - } - } - elseif (!$expression instanceof Twig_Node_Expression_GetAttr && !$expression instanceof Twig_Node_Expression_Name) - { - // don't escape if the node is not a variable - return $node; - } - - // escape - if ($expression instanceof Twig_Node_Expression_Filter) - { - // escape all variables in filters arguments - $filters = $expression->getFilters(); - foreach ($filters as $i => $filter) - { - foreach ($filter[1] as $j => $argument) - { - $filters[$i][1][$j] = $this->escapeNode($argument); - } - } - - $expression->setFilters($filters); - $expression->prependFilter($this->getEscaperFilter()); - - return $node; - } - elseif ($node instanceof Twig_Node_Print) - { - return new Twig_Node_Print( - new Twig_Node_Expression_Filter($expression, array($this->getEscaperFilter()), $node->getLine()) - , $node->getLine() - ); - } - else - { - return new Twig_Node_Expression_Filter($node, array($this->getEscaperFilter()), $node->getLine()); - } - } - - protected function needEscaping() - { - if (count($this->statusStack)) - { - return $this->statusStack[count($this->statusStack) - 1]; - } - else - { - return $this->env->hasExtension('escaper') ? $this->env->getExtension('escaper')->isGlobal() : false; - } - } - - protected function getEscaperFilter() - { - return array('escape', array()); - } -} diff --git a/lib/Twig/NodeTransformer/Filter.php b/lib/Twig/NodeTransformer/Filter.php deleted file mode 100644 index 105456d..0000000 --- a/lib/Twig/NodeTransformer/Filter.php +++ /dev/null @@ -1,68 +0,0 @@ -statusStack[] = $node->getFilters(); - - $node = $this->visitDeep($node); - - array_pop($this->statusStack); - - return $node; - } - - if (!$node instanceof Twig_Node_Print && !$node instanceof Twig_Node_Text) - { - return $this->visitDeep($node); - } - - if (false === $filters = $this->getCurrentFilters()) - { - return $node; - } - - if ($node instanceof Twig_Node_Text) - { - $expression = new Twig_Node_Expression_Constant($node->getData(), $node->getLine()); - } - else - { - $expression = $node->getExpression(); - } - - // filters - if ($expression instanceof Twig_Node_Expression_Filter) - { - $expression->appendFilters($filters); - - return $node; - } - else - { - return new Twig_Node_Print( - new Twig_Node_Expression_Filter($expression, $filters, $node->getLine()) - , $node->getLine() - ); - } - } - - protected function getCurrentFilters() - { - return count($this->statusStack) ? $this->statusStack[count($this->statusStack) - 1] : false; - } -} diff --git a/lib/Twig/NodeTransformer/Sandbox.php b/lib/Twig/NodeTransformer/Sandbox.php deleted file mode 100644 index 0b08819..0000000 --- a/lib/Twig/NodeTransformer/Sandbox.php +++ /dev/null @@ -1,58 +0,0 @@ -inAModule = true; - $this->tags = array(); - $this->filters = array(); - - $node = $this->visitDeep($node); - - $node->setUsedFilters(array_keys($this->filters)); - $node->setUsedTags(array_keys($this->tags)); - $this->inAModule = false; - - return $node; - } - - if (!$this->inAModule) - { - return $node; - } - - // look for tags - if ($node->getTag()) - { - $this->tags[$node->getTag()] = true; - } - - // look for filters - if ($node instanceof Twig_Node_Expression_Filter) - { - foreach ($node->getFilters() as $filter) - { - $this->filters[$filter[0]] = true; - } - } - - $this->visitDeep($node); - - return $node; - } -} diff --git a/lib/Twig/NodeTraverser.php b/lib/Twig/NodeTraverser.php new file mode 100644 index 0000000..a781504 --- /dev/null +++ b/lib/Twig/NodeTraverser.php @@ -0,0 +1,39 @@ +env = $env; + } + + public function traverse(Twig_Node $node, Twig_NodeVisitorInterface $visitor) + { + $node = $visitor->enterNode($node, $this->env); + + if ($node instanceof Twig_NodeListInterface) + { + $newNodes = array(); + foreach ($nodes = $node->getNodes() as $k => $n) + { + if (null !== $n = $this->traverse($n, $visitor)) + { + $newNodes[$k] = $n; + } + } + $node->setNodes($newNodes); + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/lib/Twig/NodeVisitor/Escaper.php b/lib/Twig/NodeVisitor/Escaper.php new file mode 100644 index 0000000..098345a --- /dev/null +++ b/lib/Twig/NodeVisitor/Escaper.php @@ -0,0 +1,124 @@ +statusStack[] = $node->getValue(); + } + elseif ($node instanceof Twig_Node_Print && true === $this->needEscaping($env)) + { + return $this->escapeNode($node, $env); + } + elseif ($node instanceof Twig_Node_Block) + { + $this->statusStack[] = isset($this->blocks[$node->getName()]) ? $this->blocks[$node->getName()] : $this->needEscaping($env); + } + + return $node; + } + + public function leaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) + { + array_pop($this->statusStack); + } + elseif ($node instanceof Twig_Node_BlockReference) + { + $this->blocks[$node->getName()] = $this->needEscaping($env); + } + + return $node; + } + + protected function escapeNode(Twig_Node $node, Twig_Environment $env) + { + $expression = $node instanceof Twig_Node_Print ? $node->getExpression() : $node; + + if ($expression instanceof Twig_Node_Expression_Filter) + { + // don't escape if the primary node of the filter is not a variable + $nodes = $expression->getNodes(); + if (!$nodes[0] instanceof Twig_Node_Expression_Name) + { + return $node; + } + + // don't escape if there is already an "escaper" in the filter chain + $filterMap = $env->getFilters(); + foreach ($expression->getFilters() as $filter) + { + if (isset($filterMap[$filter[0]]) && $filterMap[$filter[0]]->isEscaper()) + { + return $node; + } + } + } + elseif (!$expression instanceof Twig_Node_Expression_GetAttr && !$expression instanceof Twig_Node_Expression_Name) + { + // don't escape if the node is not a variable + return $node; + } + + // escape + if ($expression instanceof Twig_Node_Expression_Filter) + { + // escape all variables in filters arguments + $filters = $expression->getFilters(); + foreach ($filters as $i => $filter) + { + foreach ($filter[1] as $j => $argument) + { + $filters[$i][1][$j] = $this->escapeNode($argument, $env); + } + } + + $expression->setFilters($filters); + $expression->prependFilter($this->getEscaperFilter()); + + return $node; + } + elseif ($node instanceof Twig_Node_Print) + { + return new Twig_Node_Print( + new Twig_Node_Expression_Filter($expression, array($this->getEscaperFilter()), $node->getLine()) + , $node->getLine() + ); + } + else + { + return new Twig_Node_Expression_Filter($node, array($this->getEscaperFilter()), $node->getLine()); + } + } + + protected function needEscaping(Twig_Environment $env) + { + if (count($this->statusStack)) + { + return $this->statusStack[count($this->statusStack) - 1]; + } + else + { + return $env->hasExtension('escaper') ? $env->getExtension('escaper')->isGlobal() : false; + } + } + + protected function getEscaperFilter() + { + return array('escape', array()); + } +} diff --git a/lib/Twig/NodeVisitor/Filter.php b/lib/Twig/NodeVisitor/Filter.php new file mode 100644 index 0000000..02a27eb --- /dev/null +++ b/lib/Twig/NodeVisitor/Filter.php @@ -0,0 +1,75 @@ +statusStack[] = $node->getFilters(); + } + elseif ($node instanceof Twig_Node_Print || $node instanceof Twig_Node_Text) + { + return $this->applyFilters($node); + } + + return $node; + } + + public function leaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Filter) + { + array_pop($this->statusStack); + } + + return $node; + } + + protected function applyFilters(Twig_Node $node) + { + if (false === $filters = $this->getCurrentFilters()) + { + return $node; + } + + if ($node instanceof Twig_Node_Text) + { + $expression = new Twig_Node_Expression_Constant($node->getData(), $node->getLine()); + } + else + { + $expression = $node->getExpression(); + } + + // filters + if ($expression instanceof Twig_Node_Expression_Filter) + { + $expression->appendFilters($filters); + + return $node; + } + else + { + return new Twig_Node_Print( + new Twig_Node_Expression_Filter($expression, $filters, $node->getLine()) + , $node->getLine() + ); + } + } + + protected function getCurrentFilters() + { + return count($this->statusStack) ? $this->statusStack[count($this->statusStack) - 1] : false; + } +} diff --git a/lib/Twig/NodeVisitor/Sandbox.php b/lib/Twig/NodeVisitor/Sandbox.php new file mode 100644 index 0000000..3739376 --- /dev/null +++ b/lib/Twig/NodeVisitor/Sandbox.php @@ -0,0 +1,59 @@ +inAModule = true; + $this->tags = array(); + $this->filters = array(); + + return $node; + } + elseif ($this->inAModule) + { + // look for tags + if ($node->getTag()) + { + $this->tags[$node->getTag()] = true; + } + + // look for filters + if ($node instanceof Twig_Node_Expression_Filter) + { + foreach ($node->getFilters() as $filter) + { + $this->filters[$filter[0]] = true; + } + } + } + + return $node; + } + + public function leaveNode(Twig_Node $node, Twig_Environment $env) + { + if ($node instanceof Twig_Node_Module) + { + $node->setUsedFilters(array_keys($this->filters)); + $node->setUsedTags(array_keys($this->tags)); + $this->inAModule = false; + } + + return $node; + } +} diff --git a/lib/Twig/Parser.php b/lib/Twig/Parser.php index 4eea96d..29d60ef 100644 --- a/lib/Twig/Parser.php +++ b/lib/Twig/Parser.php @@ -14,7 +14,7 @@ class Twig_Parser protected $stream; protected $extends; protected $handlers; - protected $transformers; + protected $visitors; protected $expressionParser; protected $blocks; protected $currentBlock; @@ -31,7 +31,7 @@ class Twig_Parser $this->env = $env; $this->handlers = array(); - $this->transformers = array(); + $this->visitors = array(); // tag handlers foreach ($this->env->getTokenParsers() as $handler) @@ -41,8 +41,8 @@ class Twig_Parser $this->handlers[$handler->getTag()] = $handler; } - // node transformers - $this->transformers = $env->getNodeTransformers(); + // node visitors + $this->visitors = $env->getNodeVisitors(); } /** @@ -89,9 +89,11 @@ class Twig_Parser $node = new Twig_Node_Module($body, $this->extends, $this->blocks, $this->macros, $this->stream->getFilename()); - $transformer = new Twig_NodeTransformer_Chain($this->transformers); - $transformer->setEnvironment($this->env); - $node = $transformer->visit($node); + $t = new Twig_NodeTraverser($this->env); + foreach ($this->visitors as $visitor) + { + $node = $t->traverse($node, $visitor); + } return $node; } @@ -163,9 +165,9 @@ class Twig_Parser $this->handlers[$name] = $class; } - public function addTransformer(Twig_NodeTransformer $transformer) + public function addNodeVisitor(Twig_NodeVisitorInterface $visitor) { - $this->transformers[] = $transformer; + $this->visitors[] = $visitor; } public function getCurrentBlock() -- 1.7.2.5