Test Setup Failed
Pull Request — latest (#3)
by Mark
34:22
created

EmojiParser::parseEmoji()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 27
rs 8.8333
cc 7
nc 7
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UnicornFail\Emoji\Parser;
6
7
use UnicornFail\Emoji\Environment\EnvironmentInterface;
8
use UnicornFail\Emoji\Event\DocumentParsedEvent;
9
use UnicornFail\Emoji\Event\DocumentPreParsedEvent;
10
use UnicornFail\Emoji\Input\Input;
11
use UnicornFail\Emoji\Node\Block\Document;
12
use UnicornFail\Emoji\Node\Inline\Emoticon;
13
use UnicornFail\Emoji\Node\Inline\HtmlEntity;
14
use UnicornFail\Emoji\Node\Inline\Shortcode;
15
use UnicornFail\Emoji\Node\Inline\Text;
16
use UnicornFail\Emoji\Node\Inline\Unicode;
17
18
class EmojiParser implements EmojiParserInterface
19
{
20
    public const T_DATASETS = [
21
        Lexer::T_EMOTICON    => 'emoticon',
22
        Lexer::T_HTML_ENTITY => 'htmlEntity',
23
        Lexer::T_SHORTCODE   => 'shortcodes',
24
        Lexer::T_UNICODE     => 'unicode',
25
    ];
26
27
    /** @var EnvironmentInterface */
28
    private $environment;
29
30
    /** @var Lexer */
31
    private $lexer;
32
33
    public function __construct(EnvironmentInterface $environment, ?Lexer $lexer = null)
34
    {
35
        $this->environment = $environment;
36
        $this->lexer       = $lexer ?? new Lexer($environment);
37
    }
38
39
    public function parse(string $input): Document
40
    {
41
        $preParsedEvent = new DocumentPreParsedEvent(new Document(), new Input($input));
42
        $this->environment->dispatch($preParsedEvent);
43
44
        $document = $preParsedEvent->getDocument();
45
        $input    = $preParsedEvent->getInput();
46
47
        $lineCount = $input->getLineCount();
48
        foreach ($input->getLines() as $lineNumber => $line) {
49
            $this->parseLine($line, $document);
50
51
            if ($lineNumber < $lineCount) {
52
                $document->appendChild(new Text("\n"));
53
            }
54
        }
55
56
        // If the original content ended with a new line, mimic the same.
57
        if (\preg_match_all('/.*\n|\r\n$/sm', $input->getContent()) === 1) {
58
            $document->appendChild(new Text("\n"));
59
        }
60
61
        $this->environment->dispatch(new DocumentParsedEvent($document));
62
63
        return $document;
64
    }
65
66
    protected function parseLine(string $line, Document $document): void
67
    {
68
        $this->lexer->setInput($line);
69
        $this->lexer->moveNext();
70
71
        while (true) {
72
            if (! $this->lexer->lookahead) {
73
                break;
74
            }
75
76
            $this->lexer->moveNext();
77
78
            $type  = (int) ($this->lexer->token['type'] ?? Lexer::T_TEXT);
79
            $value = (string) ($this->lexer->token['value'] ?? '');
80
81
            if ($node = $type === Lexer::T_TEXT ? $this->parseText($value) : $this->parseEmoji($type, $value)) {
82
                $document->appendChild($node);
83
            }
84
        }
85
    }
86
87
    /**
88
     * @return Emoticon|HtmlEntity|Shortcode|Unicode|null
89
     */
90
    protected function parseEmoji(int $type, string $value)
91
    {
92
        // Immediately return if not a valid type.
93
        if (! isset(self::T_DATASETS[$type])) {
94
            return null;
95
        }
96
97
        $dataset = $this->environment->getRuntimeDataset()->indexBy(self::T_DATASETS[$type]);
98
        $emoji   = $dataset->offsetGet($value);
99
100
        // Return if not an emoji.
101
        if (! $emoji) {
102
            return null;
103
        }
104
105
        switch ($type) {
106
            case Lexer::T_EMOTICON:
107
                return new Emoticon($value, $emoji);
108
            case Lexer::T_HTML_ENTITY:
109
                return new HtmlEntity($value, $emoji);
110
            case Lexer::T_SHORTCODE:
111
                return new Shortcode($value, $emoji);
112
            case Lexer::T_UNICODE:
113
                return new Unicode($value, $emoji);
114
        }
115
116
        return null;
117
    }
118
119
    protected function parseText(string $value): ?Text
120
    {
121
        $text = '';
122
        while (true) {
123
            $text .= $value;
124
            if ($this->lexer->lookahead === null || $this->lexer->lookahead['type'] !== Lexer::T_TEXT) {
125
                break;
126
            }
127
128
            $value = (string) ($this->lexer->lookahead['value'] ?? '');
129
130
            $this->lexer->moveNext();
131
        }
132
133
        return $text
134
            ? new Text($text)
135
            : null;
136
    }
137
}
138