edde-framework/edde-framework

View on GitHub
src/Edde/Common/Node/NodeUtils.php

Summary

Maintainability
D
1 day
Test Coverage
<?php
    declare(strict_types=1);

    namespace Edde\Common\Node;

    use Edde\Api\Node\INode;
    use Edde\Api\Node\NodeException;
    use Edde\Common\Object;
    use Edde\Common\Strings\StringException;
    use Edde\Common\Strings\StringUtils;

    /**
     * Set of tools for work with nodes.
     */
    class NodeUtils extends Object {
        /**
         * @param INode                        $root
         * @param \Traversable|\Iterator|array $source
         *
         * @return INode
         * @throws NodeException
         */
        static public function node(INode $root, $source): INode {
            $callback = null;
            if (is_array($source) === false && is_object($source) === false) {
                throw new NodeException('Source must be array or stdClass object.');
            }
            /** @noinspection UnnecessaryParenthesesInspection */
            return ($callback = function (callable $callback, INode $root, $source) {
                $attributeList = $root->getAttributeList();
                /** @noinspection ForeachSourceInspection */
                foreach ($source as $key => $value) {
                    switch ($key) {
                        case 'name':
                            $root->setName($value);
                            continue 2;
                        case 'value':
                            $root->setValue($value);
                            continue 2;
                        case 'attribute-list':
                            $attributeList->put((array)$value);
                            continue 2;
                        case 'meta-list':
                            $root->getMetaList()->put((array)$value);
                            continue 2;
                        case 'node-list':
                            /** @noinspection ForeachSourceInspection */
                            foreach ($value as $item) {
                                /** @noinspection DisconnectedForeachInstructionInspection */
                                $root->addNode($node = new Node());
                                if (is_object($item) || is_array($item)) {
                                    $callback($callback, $node, $item);
                                    continue;
                                }
                                $node->setValue($item);
                            }
                            continue 2;
                    }
                    if (is_object($value)) {
                        $value = [
                            $value,
                        ];
                    }
                    if (is_array($value)) {
                        $root->addNode($itemList = new Node($key));
                        foreach ($value as $item) {
                            /** @noinspection DisconnectedForeachInstructionInspection */
                            $itemList->addNode($node = new Node());
                            if (is_object($item) || is_array($item)) {
                                $callback($callback, $node, $item);
                                continue;
                            }
                            $node->setValue($item);
                        }
                        continue;
                    }
                    $attributeList->set($key, $value);
                }
                return $root;
            })($callback, $root, $source);
        }

        /**
         * convert input of stdClass to node tree
         *
         * @param \stdClass   $stdClass
         * @param INode       $node
         * @param string|null $class
         *
         * @return INode
         * @throws NodeException
         */
        static public function toNode(\stdClass $stdClass, INode $node = null, string $class = null): INode {
            $createNode = function (string $class, string $name = null): INode {
                /** @var $node INode */
                if (($node = new $class()) instanceof INode === false) {
                    throw new ClassMismatchException(sprintf('Class specified [%s] is not instance of [%s].', $class, INode::class));
                }
                $name ? $node->setName($name) : null;
                return $node;
            };
            $node = $node ?: $createNode($class = $class ?: Node::class);
            foreach ($stdClass as $k => $v) {
                if ($k === '::name') {
                    $node->setName($v);
                    continue;
                } else if ($k === '::value') {
                    $node->setValue($v);
                    continue;
                } else if ($k === '::meta') {
                    $node->putMeta((array)$v);
                    continue;
                } else if ($v instanceof \stdClass) {
                    $node->addNode(self::toNode($v, $createNode($class, $k), $class));
                    continue;
                } else if (is_array($v)) {
                    foreach ($v as $vv) {
                        $node->addNode(self::toNode($vv, $createNode($class, $k), $class));
                    }
                    continue;
                }
                $node->setAttribute($k, $v);
            }
            /** @var $node INode */
            if ($node->getName() === null && $node->getNodeCount() === 1) {
                $node = $node->getNodeList()[0];
                $node->setParent(null);
                return $node;
            }
            return $node;
        }

        /**
         * convert the given node to stdClass; output of this method should be convertible 1:1 by self::toNode()
         *
         * @param INode $root
         *
         * @return \stdClass
         */
        static public function fromNode(INode $root): \stdClass {
            $object = new \stdClass();
            $attributeList = $root->getAttributeList();
            if (($value = $root->getValue()) !== null) {
                $object->{'::value'} = $value;
            }
            if ($attributeList->isEmpty() === false) {
                $object = (object)array_merge((array)$object, $attributeList->array());
            }
            $metaList = $root->getMetaList();
            if ($metaList->isEmpty() === false) {
                $object->{'::meta'} = $metaList->array();
            }
            if ($value = $root->getValue()) {
                $object->value = $value;
            }
            $nodeList = [];
            foreach ($root->getNodeList() as $node) {
                $nodeList[$node->getName()][] = self::fromNode($node);
            }
            foreach ($nodeList as $name => $list) {
                $object->{$name} = count($list) === 1 ? reset($list) : $list;
            }
            return $root->isRoot() ? (object)[$root->getName() => $object] : $object;
        }

        /**
         * namespecize the given node tree; attributes matching the given preg will be converted to namespace structure
         *
         * @param INode  $root
         * @param string $preg
         *
         * @throws NodeException
         * @throws StringException
         */
        static public function namespace(INode $root, string $preg) {
            foreach (NodeIterator::recursive($root, true) as $node) {
                $attributeList = $node->getAttributeList();
                foreach ($attributeList as $k => $value) {
                    if (($match = StringUtils::match($k, $preg, true)) !== null) {
                        $attributeList->set($match['namespace'], $namespace = $attributeList->get($match['namespace'], new AttributeList()));
                        $namespace->set($match['name'], $value);
                        $attributeList->remove($k);
                    }
                }
            }
        }
    }