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

dg / texy / 21501721037

30 Jan 2026 02:00AM UTC coverage: 91.159% (-1.3%) from 92.426%
21501721037

push

github

dg
wip

2681 of 2941 relevant lines covered (91.16%)

0.91 hits per line

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

89.19
/src/Texy/Regexp.php
1
<?php
2

3
/**
4
 * This file is part of the Texy! (https://texy.nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Texy;
11

12
use JetBrains\PhpStorm\Language;
13
use function array_keys, array_values, in_array, is_array, is_string, preg_last_error, preg_last_error_msg, strlen;
14
use const PREG_OFFSET_CAPTURE, PREG_SET_ORDER, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY, PREG_SPLIT_OFFSET_CAPTURE, PREG_UNMATCHED_AS_NULL;
15

16

17
/**
18
 * Regular expression utilities with error handling.
19
 * Patterns run with 'ux' flags. Use Regexp::quote() instead of preg_quote().
20
 */
21
class Regexp
22
{
23
        /**
24
         * Splits string by a regular expression. Subpatterns in parentheses will be captured and returned as well.
25
         * Pattern runs with 'ux' flags.
26
         * @return ($captureOffset is true ? list<array{string, int}> : list<string>)
27
         */
28
        public static function split(
1✔
29
                string $subject,
30
                #[Language('PhpRegExpXTCommentMode')]
31
                string $pattern,
32
                bool $captureOffset = false,
33
                bool $skipEmpty = false,
34
                int $limit = -1,
35
        ): array
36
        {
37
                $flags = ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0);
1✔
38
                return self::pcre('preg_split', [$pattern . 'ux', $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]);
1✔
39
        }
40

41

42
        /**
43
         * Searches the string for the part matching the regular expression and returns
44
         * an array with the found expression and individual subexpressions, or null.
45
         * Pattern runs with 'ux' flags. Unmatched groups return null.
46
         * @return ($captureOffset is true ? array<int|string, array{string|null, int}> : array<int|string, string|null>)|null
47
         */
48
        public static function match(
1✔
49
                string $subject,
50
                #[Language('PhpRegExpXTCommentMode')]
51
                string $pattern,
52
                bool $captureOffset = false,
53
                int $offset = 0,
54
        ): ?array
55
        {
56
                $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | PREG_UNMATCHED_AS_NULL;
1✔
57
                if ($offset > strlen($subject)) {
1✔
58
                        return null;
×
59
                }
60

61
                $m = [];
1✔
62
                return self::pcre('preg_match', [$pattern . 'ux', $subject, &$m, $flags, $offset])
1✔
63
                        ? $m
1✔
64
                        : null;
1✔
65
        }
66

67

68
        /**
69
         * Searches the string for all occurrences matching the regular expression and
70
         * returns an array of arrays containing the found expression and each subexpression.
71
         * Pattern runs with 'ux' flags. Unmatched groups return null.
72
         * @return ($captureOffset is true ? list<array<int|string, array{string|null, int}>> : list<array<int|string, string|null>>)
73
         */
74
        public static function matchAll(
1✔
75
                string $subject,
76
                #[Language('PhpRegExpXTCommentMode')]
77
                string $pattern,
78
                bool $captureOffset = false,
79
                int $offset = 0,
80
        ): array
81
        {
82
                if ($offset > strlen($subject)) {
1✔
83
                        return [];
×
84
                }
85

86
                $m = [];
1✔
87
                $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | PREG_UNMATCHED_AS_NULL | PREG_SET_ORDER;
1✔
88
                self::pcre('preg_match_all', [$pattern . 'ux', $subject, &$m, $flags, $offset]);
1✔
89
                return $m;
1✔
90
        }
91

92

93
        /**
94
         * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
95
         * Pattern runs with 'ux' flags. Closure receives null for unmatched groups.
96
         * @param  string|string[]  $pattern
97
         * @param  string|\Closure(string[]): string  $replacement
98
         */
99
        public static function replace(
1✔
100
                string $subject,
101
                #[Language('PhpRegExpXTCommentMode')]
102
                string|array $pattern,
103
                string|\Closure $replacement = '',
104
                int $limit = -1,
105
                bool $captureOffset = false,
106
        ): string
107
        {
108
                if (is_array($pattern) && is_string(key($pattern))) {
1✔
109
                        $patterns = array_map(static fn($p) => $p . 'ux', array_keys($pattern));
1✔
110
                        return self::pcre('preg_replace', [$patterns, array_values($pattern), $subject, $limit]);
1✔
111
                }
112

113
                $pattern = is_array($pattern)
1✔
114
                        ? array_map(static fn($p) => $p . 'ux', $pattern)
×
115
                        : $pattern . 'ux';
1✔
116

117
                if ($replacement instanceof \Closure) {
1✔
118
                        $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | PREG_UNMATCHED_AS_NULL;
1✔
119
                        return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]);
1✔
120
                } else {
121
                        return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]);
1✔
122
                }
123
        }
124

125

126
        /**
127
         * Escapes string for use in a regular expression. Unlike preg_quote(), also escapes spaces and '#' for extended mode.
128
         */
129
        public static function quote(string $s): string
1✔
130
        {
131
                return addcslashes($s, "\x00..\x20-.\\+*?[^]$(){}=!<>|:-#");
1✔
132
        }
133

134

135
        /**
136
         * @internal
137
         * @param  array<mixed>  $args
138
         */
139
        public static function pcre(string $func, array $args): mixed
1✔
140
        {
141
                assert(is_callable($func));
142
                $res = @$func(...$args);
1✔
143
                if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
1✔
144
                        && ($res === null || !in_array($func, ['preg_replace_callback', 'preg_replace'], true))
1✔
145
                ) {
146
                        throw new RegexpException(preg_last_error_msg() . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
×
147
                }
148

149
                return $res;
1✔
150
        }
151
}
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