• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

brick / geo / 13753277563

09 Mar 2025 10:43PM UTC coverage: 49.787% (+2.5%) from 47.295%
13753277563

push

github

BenMorel
Prepare for release

1749 of 3513 relevant lines covered (49.79%)

975.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.0
/src/IO/WKTParser.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\Geo\IO;
6

7
use Brick\Geo\Exception\GeometryIOException;
8

9
/**
10
 * Well-Known Text parser.
11
 *
12
 * @internal
13
 */
14
class WKTParser
15
{
16
    protected const T_WORD   = 1;
17
    protected const T_NUMBER = 2;
18

19
    protected const REGEX_WORD   = '([a-z]+)';
20
    protected const REGEX_NUMBER = '(\-?[0-9]+(?:\.[0-9]+)?(?:e[\+\-]?[0-9]+)?)';
21

22
    /**
23
     * The list of tokens.
24
     *
25
     * @var list<array{int, string}>
26
     */
27
    protected array $tokens = [];
28

29
    /**
30
     * The current token pointer.
31
     */
32
    protected int $current = 0;
33

34
    public function __construct(string $wkt)
35
    {
36
        $this->scan($wkt);
15,039✔
37
    }
38

39
    /**
40
     * @return array<static::T_*, string>
41
     */
42
    protected function getRegex() : array
43
    {
44
        return [
10,279✔
45
            self::T_WORD   => self::REGEX_WORD,
10,279✔
46
            self::T_NUMBER => self::REGEX_NUMBER,
10,279✔
47
        ];
10,279✔
48
    }
49

50
    private function scan(string $wkt) : void
51
    {
52
        $regex = $this->getRegex();
15,039✔
53
        $regex[] = '\s+';
15,039✔
54
        $regex[] = '(.+?)';
15,039✔
55

56
        $regex = '/' . implode('|', $regex) . '/i';
15,039✔
57

58
        preg_match_all($regex, $wkt, $matches, PREG_SET_ORDER);
15,039✔
59

60
        foreach ($matches as $match) {
15,039✔
61
            foreach ($match as $key => $value) {
15,039✔
62
                /** @var int $key */
63

64
                if ($key === 0) {
15,039✔
65
                    continue;
15,039✔
66
                }
67

68
                if ($value !== '') {
15,039✔
69
                    $this->tokens[] = [$key, $value];
15,039✔
70
                }
71
            }
72
        }
73
    }
74

75
    /**
76
     * @return array{int, string}|null The next token, or null if there are no more tokens.
77
     */
78
    private function nextToken() : ?array
79
    {
80
        $token = $this->tokens[$this->current] ?? null;
15,039✔
81

82
        if ($token === null) {
15,039✔
83
            return null;
14,990✔
84
        }
85

86
        $this->current++;
15,039✔
87

88
        return $token;
15,039✔
89
    }
90

91
    /**
92
     * @throws GeometryIOException
93
     */
94
    public function matchOpener() : void
95
    {
96
        $token = $this->nextToken();
9,762✔
97

98
        if ($token === null) {
9,762✔
99
            throw new GeometryIOException("Expected '(' but encountered end of stream");
×
100
        }
101
        if ($token[1] !== '(') {
9,762✔
102
            throw new GeometryIOException("Expected '(' but encountered '" . $token[1] . "'");
×
103
        }
104
    }
105

106
    /**
107
     * @throws GeometryIOException
108
     */
109
    public function matchCloser() : void
110
    {
111
        $token = $this->nextToken();
1,603✔
112

113
        if ($token === null) {
1,603✔
114
            throw new GeometryIOException("Expected ')' but encountered end of stream");
×
115
        }
116
        if ($token[1] !== ')') {
1,603✔
117
            throw new GeometryIOException("Expected ')' but encountered '" . $token[1] . "'");
×
118
        }
119
    }
120

121
    /**
122
     * @throws GeometryIOException
123
     */
124
    public function getNextWord() : string
125
    {
126
        $token = $this->nextToken();
15,039✔
127

128
        if ($token === null) {
15,039✔
129
            throw new GeometryIOException("Expected word but encountered end of stream");
×
130
        }
131
        if ($token[0] !== static::T_WORD) {
15,039✔
132
            throw new GeometryIOException("Expected word but encountered '" . $token[1] . "'");
×
133
        }
134

135
        return $token[1];
15,039✔
136
    }
137

138
    /**
139
     * @return string|null The next word, or NULL if the next token is not a word, or there are no more tokens.
140
     */
141
    public function getOptionalNextWord() : ?string
142
    {
143
        $token = $this->tokens[$this->current] ?? null;
15,039✔
144

145
        if ($token === null) {
15,039✔
146
            return null;
×
147
        }
148

149
        if ($token[0] !== static::T_WORD) {
15,039✔
150
            return null;
9,762✔
151
        }
152

153
        $this->current++;
11,004✔
154

155
        return $token[1];
11,004✔
156
    }
157

158
    /**
159
     * Returns whether the next token is an opener or a word.
160
     *
161
     * @return bool True if the next token is an opener, false if it is a word.
162
     *
163
     * @throws GeometryIOException If the next token is not an opener or a word, or if there is no next token.
164
     */
165
    public function isNextOpenerOrWord() : bool
166
    {
167
        $token = $this->tokens[$this->current] ?? null;
1,351✔
168

169
        if ($token === null) {
1,351✔
170
            throw new GeometryIOException("Expected '(' or word but encountered end of stream");
×
171
        }
172

173
        if ($token[1] === '(') {
1,351✔
174
            return true;
1,323✔
175
        }
176

177
        if ($token[0] === static::T_WORD) {
1,155✔
178
            return false;
1,155✔
179
        }
180

181
        throw new GeometryIOException("Expected '(' or word but encountered '" . $token[1] . "'");
×
182
    }
183

184
    /**
185
     * @throws GeometryIOException
186
     */
187
    public function getNextNumber() : float
188
    {
189
        $token = $this->nextToken();
9,685✔
190

191
        if ($token === null) {
9,685✔
192
            throw new GeometryIOException("Expected number but encountered end of stream");
×
193
        }
194

195
        if ($token[0] !== static::T_NUMBER) {
9,685✔
196
            throw new GeometryIOException("Expected number but encountered '" . $token[1] . "'");
×
197
        }
198

199
        return (float) $token[1];
9,685✔
200
    }
201

202
    /**
203
     * @throws GeometryIOException
204
     */
205
    public function getNextCloserOrComma() : string
206
    {
207
        $token = $this->nextToken();
8,810✔
208

209
        if ($token === null) {
8,810✔
210
            throw new GeometryIOException("Expected ')' or ',' but encountered end of stream");
×
211
        }
212
        if ($token[1] !== ')' && $token[1] !== ',') {
8,810✔
213
            throw new GeometryIOException("Expected ')' or ',' but encountered '" . $token[1] . "'");
×
214
        }
215

216
        return $token[1];
8,810✔
217
    }
218

219
    public function isEndOfStream() : bool
220
    {
221
        return $this->nextToken() === null;
14,990✔
222
    }
223
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc