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

brick / geo / 13766209603

09 Mar 2025 10:35PM UTC coverage: 87.414% (+3.3%) from 84.117%
13766209603

push

github

BenMorel
Add Point::isEqualTo() (WIP: finish? keep?)

8 of 8 new or added lines in 2 files covered. (100.0%)

73 existing lines in 16 files now uncovered.

1653 of 1891 relevant lines covered (87.41%)

1946.79 hits per line

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

83.13
/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, with support for EWKT.
11
 *
12
 * @internal
13
 */
14
final class WKTParser
15
{
16
    private const string REGEX_CAPTURE_WORD   = '([a-z]+)';
17
    private const string REGEX_CAPTURE_NUMBER = '(\-?[0-9]+(?:\.[0-9]+)?(?:e[\+\-]?[0-9]+)?)';
18
    private const string REGEX_WHITESPACE = '\s+';
19
    private const string REGEX_CAPTURE_OTHER = '(.+?)';
20
    private const string REGEX_CAPTURE_SRID = 'SRID\=([0-9]+)\s*;'; // EWKT
21

22
    /**
23
     * The list of tokens.
24
     *
25
     * The first element of each token is the token type, the second element is the token value.
26
     *
27
     * @var list<array{WKTTokenType, string}>
28
     */
29
    private array $tokens = [];
30

31
    /**
32
     * The current token pointer.
33
     */
34
    private int $current = 0;
35

36
    public function __construct(
37
        string $wkt,
38
        private bool $ewkt = false,
39
    ) {
40
        $this->scan($wkt);
15,578✔
41
    }
42

43
    private function scan(string $wkt) : void
44
    {
45
        $regexPatterns = [
15,578✔
46
            self::REGEX_CAPTURE_WORD,
15,578✔
47
            self::REGEX_CAPTURE_NUMBER,
15,578✔
48
            self::REGEX_WHITESPACE,
15,578✔
49
            self::REGEX_CAPTURE_OTHER,
15,578✔
50
        ];
15,578✔
51

52
        if ($this->ewkt) {
15,578✔
53
            array_unshift($regexPatterns, self::REGEX_CAPTURE_SRID);
4,760✔
54
        }
55

56
        $matchKeyToType = $this->ewkt ? [
15,578✔
57
            1 => WKTTokenType::SRID,
15,578✔
58
            2 => WKTTokenType::Word,
15,578✔
59
            3 => WKTTokenType::Number,
15,578✔
60
            4 => WKTTokenType::Other,
15,578✔
61
        ] : [
10,818✔
62
            1 => WKTTokenType::Word,
10,818✔
63
            2 => WKTTokenType::Number,
10,818✔
64
            3 => WKTTokenType::Other,
10,818✔
65
        ];
10,818✔
66

67
        $regex = '/' . implode('|', $regexPatterns) . '/i';
15,578✔
68

69
        preg_match_all($regex, $wkt, $matches, PREG_SET_ORDER);
15,578✔
70

71
        foreach ($matches as $match) {
15,578✔
72
            foreach ($match as $key => $value) {
15,578✔
73
                /** @var int $key */
74

75
                if ($key === 0) {
15,578✔
76
                    continue;
15,578✔
77
                }
78

79
                if ($value !== '') {
15,578✔
80
                    $this->tokens[] = [$matchKeyToType[$key], $value];
15,578✔
81
                }
82
            }
83
        }
84
    }
85

86
    /**
87
     * @return array{WKTTokenType, string}|null The next token, or null if there are no more tokens.
88
     */
89
    private function nextToken() : ?array
90
    {
91
        $token = $this->tokens[$this->current] ?? null;
15,578✔
92

93
        if ($token === null) {
15,578✔
94
            return null;
15,529✔
95
        }
96

97
        $this->current++;
15,578✔
98

99
        return $token;
15,578✔
100
    }
101

102
    /**
103
     * @throws GeometryIOException
104
     */
105
    public function matchOpener() : void
106
    {
107
        $token = $this->nextToken();
10,112✔
108

109
        if ($token === null) {
10,112✔
UNCOV
110
            throw new GeometryIOException("Expected '(' but encountered end of stream");
×
111
        }
112
        if ($token[1] !== '(') {
10,112✔
UNCOV
113
            throw new GeometryIOException("Expected '(' but encountered '" . $token[1] . "'");
×
114
        }
115
    }
116

117
    /**
118
     * @throws GeometryIOException
119
     */
120
    public function matchCloser() : void
121
    {
122
        $token = $this->nextToken();
1,953✔
123

124
        if ($token === null) {
1,953✔
UNCOV
125
            throw new GeometryIOException("Expected ')' but encountered end of stream");
×
126
        }
127
        if ($token[1] !== ')') {
1,953✔
UNCOV
128
            throw new GeometryIOException("Expected ')' but encountered '" . $token[1] . "'");
×
129
        }
130
    }
131

132
    /**
133
     * @throws GeometryIOException
134
     */
135
    public function getNextWord() : string
136
    {
137
        $token = $this->nextToken();
15,578✔
138

139
        if ($token === null) {
15,578✔
UNCOV
140
            throw new GeometryIOException("Expected word but encountered end of stream");
×
141
        }
142
        if ($token[0] !== WKTTokenType::Word) {
15,578✔
UNCOV
143
            throw new GeometryIOException("Expected word but encountered '" . $token[1] . "'");
×
144
        }
145

146
        return $token[1];
15,578✔
147
    }
148

149
    /**
150
     * @return string|null The next word, or NULL if the next token is not a word, or there are no more tokens.
151
     */
152
    public function getOptionalNextWord() : ?string
153
    {
154
        $token = $this->tokens[$this->current] ?? null;
15,578✔
155

156
        if ($token === null) {
15,578✔
UNCOV
157
            return null;
×
158
        }
159

160
        if ($token[0] !== WKTTokenType::Word) {
15,578✔
161
            return null;
10,112✔
162
        }
163

164
        $this->current++;
11,502✔
165

166
        return $token[1];
11,502✔
167
    }
168

169
    /**
170
     * Returns whether the next token is an opener or a word.
171
     *
172
     * @return bool True if the next token is an opener, false if it is a word.
173
     *
174
     * @throws GeometryIOException If the next token is not an opener or a word, or if there is no next token.
175
     */
176
    public function isNextOpenerOrWord() : bool
177
    {
178
        $token = $this->tokens[$this->current] ?? null;
1,351✔
179

180
        if ($token === null) {
1,351✔
UNCOV
181
            throw new GeometryIOException("Expected '(' or word but encountered end of stream");
×
182
        }
183

184
        if ($token[1] === '(') {
1,351✔
185
            return true;
1,323✔
186
        }
187

188
        if ($token[0] === WKTTokenType::Word) {
1,155✔
189
            return false;
1,155✔
190
        }
191

UNCOV
192
        throw new GeometryIOException("Expected '(' or word but encountered '" . $token[1] . "'");
×
193
    }
194

195
    /**
196
     * @throws GeometryIOException
197
     */
198
    public function getNextNumber() : float
199
    {
200
        $token = $this->nextToken();
10,035✔
201

202
        if ($token === null) {
10,035✔
UNCOV
203
            throw new GeometryIOException("Expected number but encountered end of stream");
×
204
        }
205

206
        if ($token[0] !== WKTTokenType::Number) {
10,035✔
UNCOV
207
            throw new GeometryIOException("Expected number but encountered '" . $token[1] . "'");
×
208
        }
209

210
        return (float) $token[1];
10,035✔
211
    }
212

213
    /**
214
     * @throws GeometryIOException
215
     */
216
    public function getNextCloserOrComma() : string
217
    {
218
        $token = $this->nextToken();
8,810✔
219

220
        if ($token === null) {
8,810✔
UNCOV
221
            throw new GeometryIOException("Expected ')' or ',' but encountered end of stream");
×
222
        }
223
        if ($token[1] !== ')' && $token[1] !== ',') {
8,810✔
UNCOV
224
            throw new GeometryIOException("Expected ')' or ',' but encountered '" . $token[1] . "'");
×
225
        }
226

227
        return $token[1];
8,810✔
228
    }
229

230
    public function getOptionalSRID() : int
231
    {
232
        $token = $this->tokens[$this->current] ?? null;
4,760✔
233

234
        if ($token === null) {
4,760✔
UNCOV
235
            return 0;
×
236
        }
237

238
        if ($token[0] !== WKTTokenType::SRID) {
4,760✔
239
            return 0;
2,380✔
240
        }
241

242
        $this->current++;
2,380✔
243

244
        return (int) $token[1];
2,380✔
245
    }
246

247
    public function isEndOfStream() : bool
248
    {
249
        return $this->nextToken() === null;
15,529✔
250
    }
251
}
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