src/FeedIo/Rule/OptionalField.php
<?php

declare(strict_types=1);

namespace FeedIo\Rule;

use DomDocument;
use DOMElement;
use DOMNode;
use DOMNodeList;
use DOMText;
use FeedIo\Feed\ElementsAwareInterface;
use FeedIo\Feed\NodeInterface;
use FeedIo\Feed\Node\ElementInterface;
use FeedIo\RuleAbstract;

class OptionalField extends RuleAbstract
{
    public const NODE_NAME = 'default';

    public function setProperty(NodeInterface $node, DOMElement $element): void
    {
        if ($node instanceof ElementsAwareInterface) {
            $newElement = $this->createElementFromDomNode($node, $element);
            $node->addElement($newElement);
        }
    }

    private function addSubElements(ElementsAwareInterface $node, ElementInterface $element, DOMNode $domNode): void
    {
        if (!$domNode->hasChildNodes() || !$this->hasSubElements($domNode)) {
            return;
        }

        $this->addElementsFromNodeList($node, $element, $domNode->childNodes);
    }

    private function addElementsFromNodeList(ElementsAwareInterface $node, ElementInterface $element, DOMNodeList $childNodeList): void
    {
        foreach ($childNodeList as $childNode) {
            if ($childNode instanceof DOMText) {
                continue;
            }

            if ($element instanceof ElementsAwareInterface) {
                $element->addElement($this->createElementFromDomNode($node, $childNode));
            }
        }
    }

    private function hasSubElements(DOMNode $domNode): bool
    {
        foreach ($domNode->childNodes as $childDomNode) {
            if (!$childDomNode instanceof \DOMText) {
                return true;
            }
        }

        return false;
    }

    private function createElementFromDomNode(ElementsAwareInterface $node, DOMNode $domNode): ElementInterface
    {
        $element = $node->newElement();
        $element->setName($domNode->nodeName);
        $element->setValue($domNode->nodeValue);

        foreach ($domNode->attributes as $attribute) {
            $element->setAttribute($attribute->name, $attribute->value);
        }
        $this->addSubElements($node, $element, $domNode);

        return $element;
    }

    public function buildDomElement(DomElement $domElement, ElementInterface $element): DOMElement
    {
        $domElement->nodeValue = $element->getValue();

        foreach ($element->getAttributes() as $name => $value) {
            $domElement->setAttribute($name, $value);
        }

        if ($element instanceof ElementsAwareInterface) {
            /** @var ElementInterface $subElement */
            foreach ($element->getAllElements() as $subElement) {
                $subDomElement = $domElement->ownerDocument->createElement($subElement->getName());
                $this->buildDomElement($subDomElement, $subElement);
                $domElement->appendChild($subDomElement);
            }
        }

        return $domElement;
    }

    /**
     * @inheritDoc
     */
    protected function hasValue(NodeInterface $node): bool
    {
        return $node instanceof ElementsAwareInterface;
    }

    /**
     * @inheritDoc
     */
    protected function addElement(DomDocument $document, DOMElement $rootElement, NodeInterface $node): void
    {
        $addedElementsCount = 0;

        if ($node instanceof ElementsAwareInterface) {
            foreach ($node->getElementIterator($this->getNodeName()) as $element) {
                $domElement = $document->createElement($this->getNodeName());
                $this->buildDomElement($domElement, $element);
                $rootElement->appendChild($domElement);
                $addedElementsCount++;
            }
        }

        if (! $addedElementsCount) {
            // add an implicit empty element if the node had no elements matching this rule
            $rootElement->appendChild($document->createElement($this->getNodeName()));
        }
    }
}
