nadar/quill-delta-parser

View on GitHub
src/listener/Text.php

Summary

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

namespace nadar\quill\listener;

use nadar\quill\BlockListener;
use nadar\quill\Lexer;
use nadar\quill\Line;

/**
 * Convert all the not done elements into paragraphs.
 *
 * @author Basil Suter <basil@nadar.io>
 * @since 1.0.0
 */
class Text extends BlockListener
{
    /**
     * @var string
     */
    public const CLOSEP = '</p>'.PHP_EOL;

    /**
     * @var string
     */
    public const OPENP = '<p>';

    /**
     * @var string
     */
    public const LINEBREAK = '<br>';

    /**
     * {@inheritDoc}
     */
    public function priority(): int
    {
        return self::PRIORITY_GARBAGE_COLLECTOR;
    }

    /**
     * {@inheritDoc}
     */
    public function process(Line $line)
    {
        if (!$line->isDone()) {
            $this->pick($line);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function render(Lexer $lexer)
    {
        $isOpen = false;
        foreach ($this->picks() as $pick) {
            if (!$pick->line->isDone() && !$pick->line->hasAttributes() && !$pick->line->isInline()) {
                $pick->line->setDone();

                $next = $pick->line->next();
                $prev = $pick->line->previous();

                $output = [];

                // if its close - we just open tag paragraph as we have a line here!
                if (!$isOpen) {
                    $isOpen = $this->output($output, self::OPENP, true);
                }

                // write the actuall content of the element into the output
                $output[] = $pick->line->isEmpty() ? self::LINEBREAK : $pick->line->renderPrepend() . $pick->line->getInput();

                // if its open and we have a next element, and the next element is not an inline, we close!
                if ($isOpen && ($next && !$next->isInline())) {
                    $isOpen = $this->output($output, self::CLOSEP, false);

                // if its open and we dont have a next element, its the end of the document! lets close this damn paragraph.
                } elseif ($isOpen && !$next) {
                    $isOpen = $this->output($output, self::CLOSEP, false);

                // its open, but the previous element was already an inline element, so maybe we should close and the next element
                // will take care of the "situation". But only if this current line also had an end new line element, otherwise
                // repeated inline elements will close
                } elseif ($isOpen && ($prev && $prev->isInline()) && $pick->line->hasEndNewline()) {
                    $isOpen = $this->output($output, self::CLOSEP, false);

                // If this element is empty we should maybe directly close and reopen this paragraph as it could be an empty line with
                // a next elmenet
                } elseif ($pick->line->isEmpty() && $next && !$next->isDone()) {
                    $isOpen = $this->output($output, self::CLOSEP.self::OPENP, true);

                // if its open, and it had an end newline, lets close
                } elseif ($isOpen && $pick->line->hasEndNewline()) {
                    $isOpen = $this->output($output, self::CLOSEP, false);
                }

                // we have a next element and the next elmenet is inline and its not open, and the current elemnt is not an endNewline element
                if ($next && $next->isInline() && !$isOpen && !$pick->line->hasEndNewline()) {
                    $isOpen = $this->output($output, self::OPENP, true);
                }

                $pick->line->output = implode("", $output);
            }
        }
    }

    /**
     * Helper method simplify output writer.
     *
     * @param array<string> $output
     * @param string $tag
     * @param boolean $openState
     * @return boolean
     */
    protected function output(&$output, $tag, $openState)
    {
        $output[] = $tag;
        return $openState;
    }
}