5 class NodeTraverser implements NodeTraverserInterface
8 * If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
9 * of the current node will not be traversed for any visitors.
11 * For subsequent visitors enterNode() will still be called on the current
12 * node and leaveNode() will also be invoked for the current node.
14 const DONT_TRAVERSE_CHILDREN = 1;
17 * If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
18 * STOP_TRAVERSAL, traversal is aborted.
20 * The afterTraverse() method will still be invoked.
22 const STOP_TRAVERSAL = 2;
25 * If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
26 * in an array, it will be removed from the array.
28 * For subsequent visitors leaveNode() will still be invoked for the
31 const REMOVE_NODE = false;
33 /** @var NodeVisitor[] Visitors */
36 /** @var bool Whether traversal should be stopped */
37 protected $stopTraversal;
40 * Constructs a node traverser.
42 public function __construct() {
43 $this->visitors = array();
49 * @param NodeVisitor $visitor Visitor to add
51 public function addVisitor(NodeVisitor $visitor) {
52 $this->visitors[] = $visitor;
56 * Removes an added visitor.
58 * @param NodeVisitor $visitor
60 public function removeVisitor(NodeVisitor $visitor) {
61 foreach ($this->visitors as $index => $storedVisitor) {
62 if ($storedVisitor === $visitor) {
63 unset($this->visitors[$index]);
70 * Traverses an array of nodes using the registered visitors.
72 * @param Node[] $nodes Array of nodes
74 * @return Node[] Traversed array of nodes
76 public function traverse(array $nodes) {
77 $this->stopTraversal = false;
79 foreach ($this->visitors as $visitor) {
80 if (null !== $return = $visitor->beforeTraverse($nodes)) {
85 $nodes = $this->traverseArray($nodes);
87 foreach ($this->visitors as $visitor) {
88 if (null !== $return = $visitor->afterTraverse($nodes)) {
96 protected function traverseNode(Node $node) {
97 foreach ($node->getSubNodeNames() as $name) {
98 $subNode =& $node->$name;
100 if (is_array($subNode)) {
101 $subNode = $this->traverseArray($subNode);
102 if ($this->stopTraversal) {
105 } elseif ($subNode instanceof Node) {
106 $traverseChildren = true;
107 foreach ($this->visitors as $visitor) {
108 $return = $visitor->enterNode($subNode);
109 if (self::DONT_TRAVERSE_CHILDREN === $return) {
110 $traverseChildren = false;
111 } else if (self::STOP_TRAVERSAL === $return) {
112 $this->stopTraversal = true;
114 } else if (null !== $return) {
119 if ($traverseChildren) {
120 $subNode = $this->traverseNode($subNode);
121 if ($this->stopTraversal) {
126 foreach ($this->visitors as $visitor) {
127 $return = $visitor->leaveNode($subNode);
128 if (self::STOP_TRAVERSAL === $return) {
129 $this->stopTraversal = true;
131 } else if (null !== $return) {
132 if (is_array($return)) {
133 throw new \LogicException(
134 'leaveNode() may only return an array ' .
135 'if the parent structure is an array'
147 protected function traverseArray(array $nodes) {
150 foreach ($nodes as $i => &$node) {
151 if (is_array($node)) {
152 $node = $this->traverseArray($node);
153 if ($this->stopTraversal) {
156 } elseif ($node instanceof Node) {
157 $traverseChildren = true;
158 foreach ($this->visitors as $visitor) {
159 $return = $visitor->enterNode($node);
160 if (self::DONT_TRAVERSE_CHILDREN === $return) {
161 $traverseChildren = false;
162 } else if (self::STOP_TRAVERSAL === $return) {
163 $this->stopTraversal = true;
165 } else if (null !== $return) {
170 if ($traverseChildren) {
171 $node = $this->traverseNode($node);
172 if ($this->stopTraversal) {
177 foreach ($this->visitors as $visitor) {
178 $return = $visitor->leaveNode($node);
180 if (self::REMOVE_NODE === $return) {
181 $doNodes[] = array($i, array());
183 } else if (self::STOP_TRAVERSAL === $return) {
184 $this->stopTraversal = true;
186 } elseif (is_array($return)) {
187 $doNodes[] = array($i, $return);
189 } elseif (null !== $return) {
196 if (!empty($doNodes)) {
197 while (list($i, $replace) = array_pop($doNodes)) {
198 array_splice($nodes, $i, 1, $replace);