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

dg / texy / 22262589061

21 Feb 2026 07:04PM UTC coverage: 92.991% (+1.8%) from 91.178%
22262589061

push

github

dg
LinkModule: deprecated label and modifiers in link definitions

3 of 3 new or added lines in 1 file covered. (100.0%)

126 existing lines in 22 files now uncovered.

2083 of 2240 relevant lines covered (92.99%)

0.93 hits per line

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

89.19
/src/Texy/Regexp.php
1
<?php declare(strict_types=1);
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
namespace Texy;
9

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

14

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

39

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

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

65

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

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

90

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

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

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

123

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

132

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

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