Passed
Push — latest ( 0eb2c6...9ae518 )
by Mark
03:09
created

Parser   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 146
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 25
eloc 59
c 1
b 0
f 0
dl 0
loc 146
ccs 57
cts 57
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A loadLocalePreset() 0 15 3
A parseText() 0 14 5
A getConfiguration() 0 3 1
A parseEmoticon() 0 7 2
A __construct() 0 7 1
A parse() 0 6 1
A parseUnicode() 0 7 3
A parseShortcode() 0 7 2
A parseTokens() 0 22 5
A parseHtmlEntity() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace UnicornFail\Emoji;
6
7
use UnicornFail\Emoji\Emojibase\ShortcodeInterface;
8
use UnicornFail\Emoji\Exception\LocalePresetException;
9
use UnicornFail\Emoji\Token\AbstractToken;
10
use UnicornFail\Emoji\Token\Emoticon;
11
use UnicornFail\Emoji\Token\HtmlEntity;
12
use UnicornFail\Emoji\Token\Shortcode;
13
use UnicornFail\Emoji\Token\Text;
14
use UnicornFail\Emoji\Token\Unicode;
15
use UnicornFail\Emoji\Util\Normalize;
16
17
class Parser implements ParserInterface
18
{
19
    private const TYPE_METHODS = [
20
        Lexer::T_TEXT => 'parseText',
21
        Lexer::T_EMOTICON => 'parseEmoticon',
22
        Lexer::T_HTML_ENTITY => 'parseHtmlEntity',
23
        Lexer::T_SHORTCODE => 'parseShortcode',
24
        Lexer::T_UNICODE => 'parseUnicode',
25
    ];
26
27
    /** @var ConfigurationInterface */
28
    private $configuration;
29
30
    /** @var Dataset */
31
    private $dataset;
32
33
    /** @var Lexer */
34
    private $lexer;
35
36
    /**
37
     * @param mixed[]|\Traversable $configuration
38
     */
39 606
    public function __construct(?iterable $configuration = null, ?Dataset $dataset = null, ?Lexer $lexer = null)
40
    {
41 606
        $this->configuration = Configuration::create($configuration);
42 606
        $locale              = $this->configuration->get('locale');
43 606
        $preset              = $this->configuration->get('preset');
44 606
        $this->dataset       = $dataset ?? self::loadLocalePreset($locale, $preset);
45 192
        $this->lexer         = $lexer ?? new Lexer($this->configuration);
46 192
    }
47
48
    /**
49
     * @param string[] $presets
50
     */
51 606
    protected static function loadLocalePreset(string $locale = 'en', array $presets = ShortcodeInterface::DEFAULT_PRESETS): Dataset
52
    {
53 606
        $throwables = [];
54 606
        $presets    = \array_filter($presets);
55 606
        $remaining  = $presets;
56 606
        while (\count($remaining) > 0) {
57 606
            $preset = \array_shift($remaining);
58
            try {
59 606
                return Dataset::unarchive(\sprintf('%s/%s/%s.gz', Dataset::DIRECTORY, $locale, $preset));
60 414
            } catch (\Throwable $throwable) {
61 414
                $throwables[$preset] = $throwable;
62
            }
63
        }
64
65 414
        throw new LocalePresetException($locale, $throwables);
66
    }
67
68 81
    public function getConfiguration(): ConfigurationInterface
69
    {
70 81
        return $this->configuration;
71
    }
72
73
    /**
74
     * @return AbstractToken[]
75
     */
76 81
    public function parse(string $input): array
77
    {
78 81
        $this->lexer->setInput($input);
79 81
        $this->lexer->moveNext();
80
81 81
        return $this->parseTokens();
82
    }
83
84
    /**
85
     * @return AbstractToken[]
86
     */
87 81
    protected function parseTokens(): array
88
    {
89 81
        $tokens = [];
90 81
        while (true) {
91 81
            if (! $this->lexer->lookahead) {
92 81
                break;
93
            }
94
95 81
            $this->lexer->moveNext();
96
97 81
            $currentToken = \array_merge([
98
                'type' => Lexer::T_TEXT,
99
                'value' => '',
100 81
            ], $this->lexer->token ?? []);
101
102 81
            $method = self::TYPE_METHODS[$currentToken['type']] ?? null;
103 81
            if ($method && ($token = $this->$method($currentToken['value'])) instanceof AbstractToken) {
104 81
                $tokens[] = $token;
105
            }
106
        }
107
108 81
        return $tokens;
109
    }
110
111 18
    protected function parseEmoticon(string $value): ?Emoticon
112
    {
113 18
        $emoji = $this->dataset->indexBy('emoticon')->offsetGet($value);
114
115
        // Clone the configuration here. This is necessary so it can be passed to tokens,
116
        // which may be rendered at a later time; when the configuration may have changed.
117 18
        return $emoji ? new Emoticon(clone $this->configuration, $value, $emoji) : null;
118
    }
119
120 15
    protected function parseHtmlEntity(string $value): ?HtmlEntity
121
    {
122 15
        $emoji = $this->dataset->indexBy('htmlEntity')->offsetGet($value);
123
124
        // Clone the configuration here. This is necessary so it can be passed to tokens,
125
        // which may be rendered at a later time; when the configuration may have changed.
126 15
        $configuration = clone $this->configuration;
127
128 15
        return $emoji ? new HtmlEntity($configuration, $value, $emoji) : null;
129
    }
130
131 54
    protected function parseShortcode(string $value): ?Shortcode
132
    {
133 54
        $emoji = $this->dataset->indexBy('shortcodes')->offsetGet(\current(Normalize::shortcodes($value)));
134
135
        // Clone the configuration here. This is necessary so it can be passed to tokens,
136
        // which may be rendered at a later time; when the configuration may have changed.
137 54
        return $emoji ? new Shortcode(clone $this->configuration, $value, $emoji) : null;
138
    }
139
140 42
    protected function parseUnicode(string $value): ?Unicode
141
    {
142 42
        $emoji = $this->dataset->indexBy('emoji')->offsetGet($value) ?: $this->dataset->indexBy('text')->offsetGet($value);
143
144
        // Clone the configuration here. This is necessary so it can be passed to tokens,
145
        // which may be rendered at a later time; when the configuration may have changed.
146 42
        return $emoji ? new Unicode(clone $this->configuration, $value, $emoji) : null;
147
    }
148
149 66
    protected function parseText(string $value): ?Text
150
    {
151 66
        $text = '';
152 66
        while (true) {
153 66
            $text .= $value;
154 66
            if ($this->lexer->lookahead === null || $this->lexer->lookahead['type'] !== Lexer::T_TEXT) {
155 66
                break;
156
            }
157
158 66
            $value = $this->lexer->lookahead['value'] ?? '';
159 66
            $this->lexer->moveNext();
160
        }
161
162 66
        return $text ? new Text($text) : null;
163
    }
164
}
165