Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
 
3
/*
4
 * This file is part of composer/pcre.
5
 *
6
 * (c) Composer <https://github.com/composer>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
 
12
namespace Composer\Pcre;
13
 
14
class Preg
15
{
16
    /** @internal */
17
    public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.';
18
    /** @internal */
19
    public const INVALID_TYPE_MSG = '$subject must be a string, %s given.';
20
 
21
    /**
22
     * @param non-empty-string   $pattern
23
     * @param array<mixed> $matches Set by method
24
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
25
     * @return 0|1
26
     *
27
     * @param-out array<int|string, string|null> $matches
28
     */
29
    public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
30
    {
31
        self::checkOffsetCapture($flags, 'matchWithOffsets');
32
 
33
        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
34
        if ($result === false) {
35
            throw PcreException::fromFunction('preg_match', $pattern);
36
        }
37
 
38
        return $result;
39
    }
40
 
41
    /**
42
     * Variant of `match()` which outputs non-null matches (or throws)
43
     *
44
     * @param non-empty-string $pattern
45
     * @param array<mixed> $matches Set by method
46
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
47
     * @return 0|1
48
     * @throws UnexpectedNullMatchException
49
     *
50
     * @param-out array<int|string, string> $matches
51
     */
52
    public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
53
    {
54
        $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset);
55
        $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match');
56
 
57
        return $result;
58
    }
59
 
60
    /**
61
     * Runs preg_match with PREG_OFFSET_CAPTURE
62
     *
63
     * @param non-empty-string   $pattern
64
     * @param array<mixed> $matches Set by method
65
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported
66
     * @return 0|1
67
     *
68
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
69
     */
70
    public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
71
    {
72
        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
73
        if ($result === false) {
74
            throw PcreException::fromFunction('preg_match', $pattern);
75
        }
76
 
77
        return $result;
78
    }
79
 
80
    /**
81
     * @param non-empty-string   $pattern
82
     * @param array<mixed> $matches Set by method
83
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
84
     * @return 0|positive-int
85
     *
86
     * @param-out array<int|string, list<string|null>> $matches
87
     */
88
    public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
89
    {
90
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
91
        self::checkSetOrder($flags);
92
 
93
        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
94
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
95
            throw PcreException::fromFunction('preg_match_all', $pattern);
96
        }
97
 
98
        return $result;
99
    }
100
 
101
    /**
102
     * Variant of `match()` which outputs non-null matches (or throws)
103
     *
104
     * @param non-empty-string   $pattern
105
     * @param array<mixed> $matches Set by method
106
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
107
     * @return 0|positive-int
108
     * @throws UnexpectedNullMatchException
109
     *
110
     * @param-out array<int|string, list<string>> $matches
111
     */
112
    public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
113
    {
114
        $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset);
115
        $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll');
116
 
117
        return $result;
118
    }
119
 
120
    /**
121
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
122
     *
123
     * @param non-empty-string   $pattern
124
     * @param array<mixed> $matches Set by method
125
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
126
     * @return 0|positive-int
127
     *
128
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
129
     */
130
    public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
131
    {
132
        self::checkSetOrder($flags);
133
 
134
        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
135
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
136
            throw PcreException::fromFunction('preg_match_all', $pattern);
137
        }
138
 
139
        return $result;
140
    }
141
 
142
    /**
143
     * @param string|string[] $pattern
144
     * @param string|string[] $replacement
145
     * @param string $subject
146
     * @param int             $count Set by method
147
     *
148
     * @param-out int<0, max> $count
149
     */
150
    public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string
151
    {
152
        if (!is_scalar($subject)) {
153
            if (is_array($subject)) {
154
                throw new \InvalidArgumentException(static::ARRAY_MSG);
155
            }
156
 
157
            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
158
        }
159
 
160
        $result = preg_replace($pattern, $replacement, $subject, $limit, $count);
161
        if ($result === null) {
162
            throw PcreException::fromFunction('preg_replace', $pattern);
163
        }
164
 
165
        return $result;
166
    }
167
 
168
    /**
169
     * @param string|string[] $pattern
170
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
171
     * @param string $subject
172
     * @param int             $count Set by method
173
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
174
     *
175
     * @param-out int<0, max> $count
176
     */
177
    public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
178
    {
179
        if (!is_scalar($subject)) {
180
            if (is_array($subject)) {
181
                throw new \InvalidArgumentException(static::ARRAY_MSG);
182
            }
183
 
184
            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
185
        }
186
 
187
        $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
188
        if ($result === null) {
189
            throw PcreException::fromFunction('preg_replace_callback', $pattern);
190
        }
191
 
192
        return $result;
193
    }
194
 
195
    /**
196
     * Variant of `replaceCallback()` which outputs non-null matches (or throws)
197
     *
198
     * @param string $pattern
199
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
200
     * @param string $subject
201
     * @param int $count Set by method
202
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
203
     *
204
     * @param-out int<0, max> $count
205
     */
206
    public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
207
    {
208
        return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) {
209
            return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback'));
210
        }, $subject, $limit, $count, $flags);
211
    }
212
 
213
    /**
214
     * @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
215
     * @param string $subject
216
     * @param int    $count Set by method
217
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
218
     *
219
     * @param-out int<0, max> $count
220
     */
221
    public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
222
    {
223
        if (!is_scalar($subject)) {
224
            if (is_array($subject)) {
225
                throw new \InvalidArgumentException(static::ARRAY_MSG);
226
            }
227
 
228
            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
229
        }
230
 
231
        $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
232
        if ($result === null) {
233
            $pattern = array_keys($pattern);
234
            throw PcreException::fromFunction('preg_replace_callback_array', $pattern);
235
        }
236
 
237
        return $result;
238
    }
239
 
240
    /**
241
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
242
     * @return list<string>
243
     */
244
    public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
245
    {
246
        if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) {
247
            throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead');
248
        }
249
 
250
        $result = preg_split($pattern, $subject, $limit, $flags);
251
        if ($result === false) {
252
            throw PcreException::fromFunction('preg_split', $pattern);
253
        }
254
 
255
        return $result;
256
    }
257
 
258
    /**
259
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set
260
     * @return list<array{string, int}>
261
     * @phpstan-return list<array{string, int<0, max>}>
262
     */
263
    public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
264
    {
265
        $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE);
266
        if ($result === false) {
267
            throw PcreException::fromFunction('preg_split', $pattern);
268
        }
269
 
270
        return $result;
271
    }
272
 
273
    /**
274
     * @template T of string|\Stringable
275
     * @param string   $pattern
276
     * @param array<T> $array
277
     * @param int-mask<PREG_GREP_INVERT> $flags PREG_GREP_INVERT
278
     * @return array<T>
279
     */
280
    public static function grep(string $pattern, array $array, int $flags = 0): array
281
    {
282
        $result = preg_grep($pattern, $array, $flags);
283
        if ($result === false) {
284
            throw PcreException::fromFunction('preg_grep', $pattern);
285
        }
286
 
287
        return $result;
288
    }
289
 
290
    /**
291
     * Variant of match() which returns a bool instead of int
292
     *
293
     * @param non-empty-string   $pattern
294
     * @param array<mixed> $matches Set by method
295
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
296
     *
297
     * @param-out array<int|string, string|null> $matches
298
     */
299
    public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
300
    {
301
        return (bool) static::match($pattern, $subject, $matches, $flags, $offset);
302
    }
303
 
304
    /**
305
     * Variant of `isMatch()` which outputs non-null matches (or throws)
306
     *
307
     * @param non-empty-string $pattern
308
     * @param array<mixed> $matches Set by method
309
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
310
     * @throws UnexpectedNullMatchException
311
     *
312
     * @param-out array<int|string, string> $matches
313
     */
314
    public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
315
    {
316
        return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);
317
    }
318
 
319
    /**
320
     * Variant of matchAll() which returns a bool instead of int
321
     *
322
     * @param non-empty-string   $pattern
323
     * @param array<mixed> $matches Set by method
324
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
325
     *
326
     * @param-out array<int|string, list<string|null>> $matches
327
     */
328
    public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
329
    {
330
        return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset);
331
    }
332
 
333
    /**
334
     * Variant of `isMatchAll()` which outputs non-null matches (or throws)
335
     *
336
     * @param non-empty-string $pattern
337
     * @param array<mixed> $matches Set by method
338
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
339
     *
340
     * @param-out array<int|string, list<string>> $matches
341
     */
342
    public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
343
    {
344
        return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);
345
    }
346
 
347
    /**
348
     * Variant of matchWithOffsets() which returns a bool instead of int
349
     *
350
     * Runs preg_match with PREG_OFFSET_CAPTURE
351
     *
352
     * @param non-empty-string   $pattern
353
     * @param array<mixed> $matches Set by method
354
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
355
     *
356
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
357
     */
358
    public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
359
    {
360
        return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);
361
    }
362
 
363
    /**
364
     * Variant of matchAllWithOffsets() which returns a bool instead of int
365
     *
366
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
367
     *
368
     * @param non-empty-string   $pattern
369
     * @param array<mixed> $matches Set by method
370
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
371
     *
372
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
373
     */
374
    public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
375
    {
376
        return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);
377
    }
378
 
379
    private static function checkOffsetCapture(int $flags, string $useFunctionName): void
380
    {
381
        if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
382
            throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead');
383
        }
384
    }
385
 
386
    private static function checkSetOrder(int $flags): void
387
    {
388
        if (($flags & PREG_SET_ORDER) !== 0) {
389
            throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches');
390
        }
391
    }
392
 
393
    /**
394
     * @param array<int|string, string|null|array{string|null, int}> $matches
395
     * @return array<int|string, string>
396
     * @throws UnexpectedNullMatchException
397
     */
398
    private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod)
399
    {
400
        foreach ($matches as $group => $match) {
401
            if (is_string($match) || (is_array($match) && is_string($match[0]))) {
402
                continue;
403
            }
404
 
405
            throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
406
        }
407
 
408
        /** @var array<string> */
409
        return $matches;
410
    }
411
 
412
    /**
413
     * @param array<int|string, list<string|null>> $matches
414
     * @return array<int|string, list<string>>
415
     * @throws UnexpectedNullMatchException
416
     */
417
    private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod)
418
    {
419
        foreach ($matches as $group => $groupMatches) {
420
            foreach ($groupMatches as $match) {
421
                if (null === $match) {
422
                    throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
423
                }
424
            }
425
        }
426
 
427
        /** @var array<int|string, list<string>> */
428
        return $matches;
429
    }
430
}