xarxaprod-wp-theme/vendor/mck89/peast/lib/Peast/Traverser.php

215 lines
6.7 KiB
PHP

<?php
/**
* This file is part of the Peast package
*
* (c) Marco Marchiò <marco.mm89@gmail.com>
*
* For the full copyright and license information refer to the LICENSE file
* distributed with this source code
*/
namespace Peast;
/**
* Nodes traverser class
*
* @author Marco Marchiò <marco.mm89@gmail.com>
*/
class Traverser
{
/**
* If a function return this value, the current node will be removed
*/
const REMOVE_NODE = 1;
/**
* If a function return this value, the current node's children won't be
* traversed
*/
const DONT_TRAVERSE_CHILD_NODES = 2;
/**
* If a function return this value, the traverser will stop running
*/
const STOP_TRAVERSING = 4;
/**
* Array of functions to call on each node
*
* @var array
*/
protected $functions = array();
/**
* Pass parent node flag
*
* @var bool
*/
protected $passParentNode = false;
/**
* Skip starting node flag
*
* @var bool
*/
protected $skipStartingNode = false;
/**
* Class constructor. Available options are:
* - skipStartingNode: if true the starting node will be skipped
* - passParentNode: if true the parent node of each node will be
* passed as second argument when the functions are called. Note
* that the parent node is calculated during traversing, so for
* the starting node it will always be null.
*
* @param array $options Options array
*/
public function __construct($options = array())
{
if (isset($options["passParentNode"])) {
$this->passParentNode = (bool) $options["passParentNode"];
}
if (isset($options["skipStartingNode"])) {
$this->skipStartingNode = (bool) $options["skipStartingNode"];
}
}
/**
* Adds a function that will be called for each node in the tree. The
* function will receive the current node as argument. The action that will
* be executed on the node by the traverser depends on the returned value
* of the function:
* - a node: it will replace the node with the returned one
* - a numeric value that is a combination of the constants defined in this
* class: it will execute the function related to each constant
* - an array where the first element is a node and the second element is a
* numeric value that is a combination of the constants defined in this
* class: it will replace the node with the returned one and it will
* execute the function related to each constant (REMOVE_NODE will be
* ignored since it does not make any sense in this case)
* - other: nothing
*
* @param callable $fn Function to add
*
* @return $this
*/
public function addFunction(callable $fn)
{
$this->functions[] = $fn;
return $this;
}
/**
* Starts the traversing
*
* @param Syntax\Node\Node $node Starting node
*
* @return Syntax\Node\Node
*/
public function traverse(Syntax\Node\Node $node)
{
if ($this->skipStartingNode) {
$this->traverseChildren($node);
} else {
$this->execFunctions($node);
}
return $node;
}
/**
* Executes all functions on the given node and, if required, starts
* traversing its children. The returned value is an array where the first
* value is the node or null if it has been removed and the second value is
* a boolean indicating if the traverser must continue the traversing or not
*
* @param Syntax\Node\Node $node Node
* @param Syntax\Node\Node|null $parent Parent node
*
* @return array
*/
protected function execFunctions($node, $parent = null)
{
$traverseChildren = true;
$continueTraversing = true;
foreach ($this->functions as $fn) {
$ret = $this->passParentNode ? $fn($node, $parent) : $fn($node);
if ($ret) {
if (is_array($ret) && $ret[0] instanceof Syntax\Node\Node) {
$node = $ret[0];
if (isset($ret[1]) && is_numeric($ret[1])) {
if ($ret[1] & self::DONT_TRAVERSE_CHILD_NODES) {
$traverseChildren = false;
}
if ($ret[1] & self::STOP_TRAVERSING) {
$continueTraversing = false;
}
}
} elseif ($ret instanceof Syntax\Node\Node) {
$node = $ret;
} elseif (is_numeric($ret)) {
if ($ret & self::DONT_TRAVERSE_CHILD_NODES) {
$traverseChildren = false;
}
if ($ret & self::STOP_TRAVERSING) {
$continueTraversing = false;
}
if ($ret & self::REMOVE_NODE) {
$node = null;
$traverseChildren = false;
break;
}
}
}
}
if ($traverseChildren && $continueTraversing) {
$continueTraversing = $this->traverseChildren($node);
}
return array($node, $continueTraversing);
}
/**
* Traverses node children. It returns a boolean indicating if the
* traversing must continue or not
*
* @param Syntax\Node\Node $node Node
*
* @return bool
*/
protected function traverseChildren(Syntax\Node\Node $node)
{
$continue = true;
foreach (Syntax\Utils::getNodeProperties($node, true) as $prop) {
$getter = $prop["getter"];
$setter = $prop["setter"];
$child = $node->$getter();
if (!$child) {
continue;
} elseif (is_array($child)) {
$newChildren = array();
foreach ($child as $c) {
if (!$c || !$continue) {
$newChildren[] = $c;
} else {
list($c, $continue) = $this->execFunctions($c, $node);
if ($c) {
$newChildren[] = $c;
}
}
}
$node->$setter($newChildren);
} else {
list($child, $continue) = $this->execFunctions($child, $node);
$node->$setter($child);
}
if (!$continue) {
break;
}
}
return $continue;
}
}