pug-php/twig

View on GitHub
src/Phug/Formatter/Format/TwigXmlFormat.php

Summary

Maintainability
B
5 hrs
Test Coverage
A
97%
<?php

namespace Phug\Formatter\Format;

use Phug\Formatter;
use Phug\Formatter\AbstractTwigFormat;
use Phug\Formatter\Element\AbstractValueElement;
use Phug\Formatter\Element\AssignmentElement;
use Phug\Formatter\Element\AttributeElement;
use Phug\Formatter\Element\CodeElement;
use Phug\Formatter\Element\ExpressionElement;
use Phug\Formatter\Element\MarkupElement;
use Phug\Formatter\Element\TextElement;
use Phug\Formatter\ElementInterface;
use Phug\Formatter\MarkupInterface;
use Phug\Formatter\Partial\AssignmentHelpersTrait;

class TwigXmlFormat extends AbstractTwigFormat
{
    use AssignmentHelpersTrait;

    const DOCTYPE = '<?xml version="1.0" encoding="utf-8" ?>';
    const OPEN_PAIR_TAG = '<%s>';
    const CLOSE_PAIR_TAG = '</%s>';
    const SELF_CLOSING_TAG = '<%s />';
    const ATTRIBUTE_PATTERN = ' %s="%s"';
    const BOOLEAN_ATTRIBUTE_PATTERN = ' %s="%s"';
    const BUFFER_VARIABLE = '$__value';

    public function __construct(Formatter $formatter = null)
    {
        parent::__construct($formatter);

        $defaults = [];
        foreach (['attributes_mapping', 'assignment_handlers', 'attribute_assignments'] as $option) {
            $defaults[$option] = ($formatter->hasOption($option) ? $formatter->getOption($option) : null) ?: [];
        }
        $this
            ->setOptionsDefaults($defaults)
            ->registerHelper('available_attribute_assignments', [])
            ->addPatterns([
                'open_pair_tag'             => static::OPEN_PAIR_TAG,
                'close_pair_tag'            => static::CLOSE_PAIR_TAG,
                'self_closing_tag'          => static::SELF_CLOSING_TAG,
                'attribute_pattern'         => static::ATTRIBUTE_PATTERN,
                'boolean_attribute_pattern' => static::BOOLEAN_ATTRIBUTE_PATTERN,
                'save_value'                => static::SAVE_VALUE,
                'buffer_variable'           => static::BUFFER_VARIABLE,
            ])
            ->provideAttributeAssignments()
            ->provideAttributeAssignment()
            ->provideStandAloneAttributeAssignment()
            ->provideMergeAttributes()
            ->provideTwigArrayEscape()
            ->provideAttributesAssignment()
            ->provideClassAttributeAssignment()
            ->provideStandAloneClassAttributeAssignment()
            ->provideStyleAttributeAssignment()
            ->provideStandAloneStyleAttributeAssignment();

        $handlers = $this->getOption('attribute_assignments');
        foreach ($handlers as $name => $handler) {
            $this->addAttributeAssignment($name, $handler);
        }
    }

    /**
     * @codeCoverageIgnore
     */
    private function provideTwigArrayEscape()
    {
        if (!method_exists($this, 'provideArrayEscape')) {
            return $this;
        }

        return $this->provideArrayEscape();
    }

    protected function addAttributeAssignment($name, $handler)
    {
        $availableAssignments = $this->getHelper('available_attribute_assignments');
        $this->registerHelper($name.'_attribute_assignment', $handler);
        $availableAssignments[] = $name;

        return $this->registerHelper('available_attribute_assignments', $availableAssignments);
    }

    public function __invoke(ElementInterface $element)
    {
        return $this->format($element);
    }

    public function isSelfClosingTag(MarkupInterface $element, $isSelfClosing = null)
    {
        if (is_null($isSelfClosing)) {
            $isSelfClosing = $element->isAutoClosed();
        }

        if ($isSelfClosing && $element->hasChildren()) {
            $visibleChildren = array_filter($element->getChildren(), function ($child) {
                return $child && (
                    !($child instanceof TextElement) ||
                    trim($child->getValue()) !== ''
                );
            });
            if (count($visibleChildren) > 0) {
                $this->throwException(
                    $element->getName().' is a self closing element: '.
                    '<'.$element->getName().'/> but contains nested content.',
                    $element
                );
            }
        }

        return $isSelfClosing;
    }

    protected function isBlockTag(MarkupInterface $element)
    {
        return true;
    }

    public function isWhiteSpaceSensitive(MarkupInterface $element)
    {
        return false;
    }

    protected function formatAttributeElement(AttributeElement $element)
    {
        $value = $element->getValue();
        $name = $element->getName();
        $nonEmptyAttribute = ($name === 'class' || $name === 'id');
        if ($value instanceof TextElement && $nonEmptyAttribute && (!$value->getValue() || $value->getValue() === '')) {
            return '';
        }
        if ($nonEmptyAttribute && (!$value || (is_string($value) && in_array(trim($value), ['', '""', "''"])))) {
            return '';
        }
        if ($value instanceof ExpressionElement) {
            if ($nonEmptyAttribute && in_array(trim($value->getValue()), ['', '""', "''"])) {
                return '';
            }
            if (strtolower($value->getValue()) === 'true') {
                return $this->formatAttributeFlag($name);
            }
            if (in_array(strtolower($value->getValue()), ['false', 'null', 'undefined'])) {
                return '';
            }
        }

        return $this->pattern(
            'attribute_pattern',
            $this->format($name),
            $this->format($value)
        );
    }

    protected function formatAttributeFlag($name)
    {
        $formattedValue = null;

        if ($name instanceof ExpressionElement) {
            $bufferVariable = $this->pattern('buffer_variable');
            $name = $this->pattern(
                'php_display_code',
                $this->pattern(
                    'save_value',
                    $bufferVariable,
                    $this->formatCode($name->getValue(), $name->isChecked())
                )
            );
            $value = new ExpressionElement($bufferVariable);
            $formattedValue = $this->format($value);
        }

        $formattedName = $this->format($name);
        $formattedValue = $formattedValue || $formattedValue === '0'
            ? $formattedValue
            : $formattedName;

        return $this->pattern(
            'boolean_attribute_pattern',
            $formattedName,
            $formattedValue
        );
    }

    protected function formatPairTagChildren(MarkupElement $element)
    {
        $firstChild = $element->getChildAt(0);
        $needIndent = (
            (
                (
                    $firstChild instanceof CodeElement &&
                    $this->isBlockTag($element)
                ) || (
                    $firstChild instanceof MarkupInterface &&
                    $this->isBlockTag($firstChild)
                )
            ) &&
            !$this->isWhiteSpaceSensitive($element)
        );

        return sprintf(
            $needIndent
                ? $this->getNewLine().'%s'.$this->getIndent()
                : '%s',
            $this->formatElementChildren($element)
        );
    }

    protected function formatPairTag($open, $close, MarkupElement $element)
    {
        return $this->pattern(
            'pair_tag',
            $open,
            $element->hasChildren()
                ? $this->formatPairTagChildren($element)
                : '',
            $close
        );
    }

    protected function formatAssignmentElement(AssignmentElement $element)
    {
        $handlers = $this->getOption('assignment_handlers');
        $newElements = [];
        array_walk(
            $handlers,
            function (callable $handler) use (&$newElements, $element) {
                $iterator = $handler($element) ?: [];
                foreach ($iterator as $newElement) {
                    $newElements[] = $newElement;
                }
            }
        );

        $markup = $element->getContainer();

        $arguments = [];
        $attributes = $markup->getAssignmentsByName('attributes');
        array_walk(
            $attributes,
            function (AssignmentElement $attributesAssignment) use (&$arguments, $markup) {
                $attributes = iterator_to_array($attributesAssignment->getAttributes());
                array_walk(
                    $attributes,
                    function (AbstractValueElement $attribute) use (&$arguments) {
                        $value = $attribute;
                        $checked = method_exists($value, 'isChecked') && $value->isChecked();
                        while (method_exists($value, 'getValue')) {
                            $value = $value->getValue();
                        }
                        $arguments[] = $this->formatCode($value, $checked);
                    }
                );
                $markup->removedAssignment($attributesAssignment);
            }
        );

        $attributes = $markup->getAttributes();
        $attributesArray = iterator_to_array($attributes);
        array_walk(
            $attributesArray,
            function (AttributeElement $attribute) use (&$arguments) {
                $arguments[] = $this->formatAttributeAsArrayItem($attribute);
            }
        );
        $attributes->removeAll($attributes);

        if (count($arguments)) {
            $newElements[] = $this->attributesAssignmentsFromPairs($arguments);
        }

        return implode('', array_map([$this, 'format'], $newElements));
    }
}