visitors = array(); } /** * Adds a visitor. * * @param NodeVisitor $visitor Visitor to add */ public function addVisitor(NodeVisitor $visitor) { $this->visitors[] = $visitor; } /** * Removes an added visitor. * * @param NodeVisitor $visitor */ public function removeVisitor(NodeVisitor $visitor) { foreach ($this->visitors as $index => $storedVisitor) { if ($storedVisitor === $visitor) { unset($this->visitors[$index]); break; } } } /** * Traverses an array of nodes using the registered visitors. * * @param Node[] $nodes Array of nodes * * @return Node[] Traversed array of nodes */ public function traverse(array $nodes) { $this->stopTraversal = false; foreach ($this->visitors as $visitor) { if (null !== $return = $visitor->beforeTraverse($nodes)) { $nodes = $return; } } $nodes = $this->traverseArray($nodes); foreach ($this->visitors as $visitor) { if (null !== $return = $visitor->afterTraverse($nodes)) { $nodes = $return; } } return $nodes; } /** * Recursively traverse a node. * * @param Node $node Node to traverse. * * @return Node Result of traversal (may be original node or new one) */ protected function traverseNode(Node $node) { foreach ($node->getSubNodeNames() as $name) { $subNode =& $node->$name; if (is_array($subNode)) { $subNode = $this->traverseArray($subNode); if ($this->stopTraversal) { break; } } elseif ($subNode instanceof Node) { $traverseChildren = true; foreach ($this->visitors as $visitor) { $return = $visitor->enterNode($subNode); if (self::DONT_TRAVERSE_CHILDREN === $return) { $traverseChildren = false; } else if (self::STOP_TRAVERSAL === $return) { $this->stopTraversal = true; break 2; } else if (null !== $return) { $subNode = $return; } } if ($traverseChildren) { $subNode = $this->traverseNode($subNode); if ($this->stopTraversal) { break; } } foreach ($this->visitors as $visitor) { $return = $visitor->leaveNode($subNode); if (self::STOP_TRAVERSAL === $return) { $this->stopTraversal = true; break 2; } else if (null !== $return) { if (is_array($return)) { throw new \LogicException( 'leaveNode() may only return an array ' . 'if the parent structure is an array' ); } $subNode = $return; } } } } return $node; } /** * Recursively traverse array (usually of nodes). * * @param array $nodes Array to traverse * * @return array Result of traversal (may be original array or changed one) */ protected function traverseArray(array $nodes) { $doNodes = array(); foreach ($nodes as $i => &$node) { if ($node instanceof Node) { $traverseChildren = true; foreach ($this->visitors as $visitor) { $return = $visitor->enterNode($node); if (self::DONT_TRAVERSE_CHILDREN === $return) { $traverseChildren = false; } else if (self::STOP_TRAVERSAL === $return) { $this->stopTraversal = true; break 2; } else if (null !== $return) { $node = $return; } } if ($traverseChildren) { $node = $this->traverseNode($node); if ($this->stopTraversal) { break; } } foreach ($this->visitors as $visitor) { $return = $visitor->leaveNode($node); if (self::REMOVE_NODE === $return) { $doNodes[] = array($i, array()); break; } else if (self::STOP_TRAVERSAL === $return) { $this->stopTraversal = true; break 2; } elseif (is_array($return)) { $doNodes[] = array($i, $return); break; } elseif (null !== $return) { $node = $return; } } } else if (is_array($node)) { throw new \LogicException('Invalid node structure: Contains nested arrays'); } } if (!empty($doNodes)) { while (list($i, $replace) = array_pop($doNodes)) { array_splice($nodes, $i, 1, $replace); } } return $nodes; } }