cloudformation-plus-plus: cfn template preprocessor
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

361 lines
6.0 KiB

<?php
declare(strict_types=1);
namespace App\Dom;
/**
* Node in a document.
*
* @author Adam Pippin <hello@adampippin.ca>
*/
class Node implements \ArrayAccess, \Iterator
{
/**
* Index for Iterator implementation.
* @var int
*/
private $_idx;
/**
* Name of this node, or null if none (e.g., array elements).
* @var ?string
*/
protected $name;
/**
* Parent node of this node, should be set on every node in the
* document besides the document itself.
* @var ?Node
*/
protected $parent;
/**
* Children nested under this node.
* @var Node[]
*/
protected $children;
/**
* Create a new DOM node.
*
* @param ?Node $parent the parent this node is nested under
* @param ?string $name the name of the node, if one exists
*/
public function __construct(?Node $parent, ?string $name)
{
$this->parent = $parent;
$this->children = [];
$this->name = $name;
}
/**
* Set the node's name.
*
* @param string $name
* @return void
*/
public function setName(string $name): void
{
$this->name = $name;
}
/**
* Get the node's name.
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Check whether the node has a name set.
*
* @return bool
*/
public function hasName(): bool
{
return isset($this->name);
}
/**
* Get this node's parent node.
*
* @return Node
*/
public function getParent(): Node
{
if (!isset($this->parent))
{
throw new \Exception('Cannot fetch parent -- none set');
}
return $this->parent;
}
/**
* Set this node's parent.
*
* @param Node $parent
* @return void
*/
public function setParent(Node $parent): void
{
$this->parent = $parent;
}
/**
* Check whether this node has any child nodes.
*
* @return bool
*/
public function hasChildren(): bool
{
return sizeof($this->children) > 0;
}
/**
* How many children this node has.
*
* @return int
*/
public function countChildren(): int
{
return sizeof($this->children);
}
/**
* Fetch this node's children.
*
* @return Node[]
*/
public function getChildren(): array
{
return $this->children;
}
/**
* Replace all of this node's children.
*
* @param Node[] $nodes
* @return void
*/
public function setChildren(array $nodes): void
{
$this->children = $nodes;
}
/**
* Attempt to find a child node by name.
*
* @param string $name
* @return ?Node
*/
public function getChildByName(string $name): ?Node
{
foreach ($this->children as $child)
{
if ($child->name == $name)
{
return $child;
}
}
return null;
}
/**
* Retrieve a child of this node by a dot-separated path.
*
* Each element of the path can either be a node name or a numeric
* index representing the offset into an array
*
* @param string $path
* @return ?Node the requested node, or null if it wasn't found
*/
public function getChildByPath(string $path): ?Node
{
$path = explode('.', $path);
return $this->findChild($path, $this);
}
/**
* Recursively walk nodes to find a node based on a path.
*
* @param string[] $path
* @param Node $current
* @return ?Node
*/
protected function findChild(array $path, Node $current): ?Node
{
$next = array_shift($path);
if (is_numeric($next))
{
$next = $current[$next];
}
else
{
$next = $current->getChildByName($next);
}
if (empty($path))
{
return $next;
}
if (!isset($next))
{
return null;
}
return $this->findChild($path, $next);
}
/**
* Add a node to this node's children.
*
* @param Node $child
* @return void
*/
public function addChild(Node $child): void
{
$this->children[] = $child;
}
/**
* Remove a child node.
*
* @param Node $child
* @return void
*/
public function removeChild(Node $child): void
{
for ($i = 0; $i < sizeof($this->children); $i++)
{
if ($this->children[$i] === $child)
{
array_splice($this->children, $i, 1);
return;
}
}
}
/**
* Try and determine whether this node is a map (named sub-properties).
*
* Checks that this node is not a "FunctionParent" (see isFunctionParent)
* and whether it contains any children that don't have names.
*
* @return bool
*/
public function isMap(): bool
{
if ($this->isFunctionParent())
{
return false;
}
foreach ($this->children as $child)
{
if (!$child->hasName())
{
return false;
}
}
return true;
}
/**
* Try and determine wether this node is an array.
*
* Checks that this node is not a "FunctionParent" (see isFunctionParent)
* and whether it contains any children that have names.
*
* @return bool
*/
public function isArray(): bool
{
foreach ($this->children as $child)
{
if ($child->hasName())
{
return false;
}
}
return true;
}
/**
* Check whether this node is a 'function parent', that is, whether it contains
* a single child node and that node is a function node.
*
* When we come across, in YAML, something like:
* Map:
* Value: !Func arg
* That's parsed as:
* Map (Node):
* Value (Node): !Func arg (FunctionNode)
*
* @return bool
*/
public function isFunctionParent(): bool
{
return sizeof($this->children) == 1 && $this->children[0] instanceof NodeFunction;
}
/**
* Remove this node from its parent.
*
* @return void
*/
public function remove(): void
{
$this->getParent()->removeChild($this);
}
// ArrayAccess
public function offsetExists($offset): bool
{
return isset($this->children[$offset]);
}
public function offsetGet($offset)
{
return $this->children[$offset];
}
public function offsetSet($offset, $value)
{
$this->children[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->children[$offset]);
}
// Iterator
public function current()
{
return $this->children[$this->_idx];
}
public function key()
{
return $this->_idx;
}
public function next()
{
$this->_idx++;
}
public function rewind()
{
$this->_idx = 0;
}
public function valid()
{
return isset($this->children[$this->_idx]);
}
}