3 use wcf\system\exception\SystemException
;
6 * Represents a regular expression.
8 * @author Tim Duesterhus
9 * @copyright 2001-2018 WoltLab GmbH
10 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11 * @package WoltLabSuite\Core\System
15 * delimiter used internally
18 const REGEX_DELIMITER
= '/';
21 * indicates that no modifier is applied
24 const MODIFIER_NONE
= 0;
27 * indicates case insensitive matching
30 const CASE_INSENSITIVE
= 1;
33 * indicates ungreedy matching
39 * indicates that no extra time is spent on analysing
45 * indicates that whitespaces are ignored in regex
48 const IGNORE_WHITESPACE
= 16;
51 * indicates that a dot matches every char
57 * indicates that ^/$ match start and end of a line instead of the whole string
63 * indicates that pattern string is treated as UTF-8.
69 * indicates that no flags are set
75 * indicates that default flags are set
78 const FLAGS_DEFAULT
= 1;
81 * captures the offset of an match (all excluding replace)
84 const CAPTURE_OFFSET
= 2;
87 * indicates default pattern ordering (match all only)
90 const ORDER_MATCH_BY_PATTERN
= 4;
93 * indicates alternative set ordering (match all only)
96 const ORDER_MATCH_BY_SET
= 8;
99 * indicates that only non-empty pieces will be splitted (split only)
102 const SPLIT_NON_EMPTY_ONLY
= 16;
105 * indicates that the split delimiter is returned as well (split only)
108 const CAPTURE_SPLIT_DELIMITER
= 32;
120 private $matches = [];
125 * @param string $regex
126 * @param integer $modifier
128 public function __construct($regex, $modifier = self
::MODIFIER_NONE
) {
130 $regex = str_replace(self
::REGEX_DELIMITER
, '\\'.self
::REGEX_DELIMITER
, $regex);
133 $this->regex
= self
::REGEX_DELIMITER
.$regex.self
::REGEX_DELIMITER
;
136 if ($modifier & self
::CASE_INSENSITIVE
) $this->regex
.= 'i';
137 if ($modifier & self
::UNGREEDY
) $this->regex
.= 'U';
138 if (!($modifier & self
::NO_ANALYSE
)) $this->regex
.= 'S';
139 if ($modifier & self
::IGNORE_WHITESPACE
) $this->regex
.= 'x';
140 if ($modifier & self
::DOT_ALL
) $this->regex
.= 's';
141 if ($modifier & self
::MULTILINE
) $this->regex
.= 'm';
142 if ($modifier & self
::UTF_8
) $this->regex
.= 'u';
148 public static function compile($regex, $modifier = self
::MODIFIER_NONE
) {
149 return new self($regex, $modifier);
155 public function __invoke($string) {
156 return $this->match($string);
160 * Checks whether the regex is syntactically correct.
164 public function isValid() {
166 $this->match(''); // we don't care about the result, we only care about the exception
169 catch (SystemException
$e) {
170 // we have a syntax error now
175 // @codingStandardsIgnoreStart
177 * Checks whether the regex matches the given string.
179 * @param string $string string to match
180 * @param boolean $all indicates if all matches are collected
181 * @param integer $flags match flags
182 * @return integer return value of preg_match(_all)
184 public function match($string, $all = false, $flags = self
::FLAGS_DEFAULT
) {
186 if ($flags & self
::CAPTURE_OFFSET
) $matchFlags |
= PREG_OFFSET_CAPTURE
;
189 if ($flags & self
::FLAGS_DEFAULT
) $matchFlags |
= PREG_PATTERN_ORDER
;
190 if (($flags & self
::ORDER_MATCH_BY_PATTERN
) && !($flags & self
::ORDER_MATCH_BY_SET
)) $matchFlags |
= PREG_PATTERN_ORDER
;
191 if (($flags & self
::ORDER_MATCH_BY_SET
) && !($flags & self
::ORDER_MATCH_BY_PATTERN
)) $matchFlags |
= PREG_SET_ORDER
;
193 return $this->checkResult(preg_match_all($this->regex
, $string, $this->matches
, $matchFlags), 'match');
196 return $this->checkResult(preg_match($this->regex
, $string, $this->matches
, $matchFlags), 'match');
200 * Replaces part of the string with the regex.
202 * @param string $string
203 * @param mixed $replacement replacement-string or closure
206 public function replace($string, $replacement) {
207 if ($replacement instanceof Callback ||
$replacement instanceof \Closure
) {
208 return $this->checkResult(preg_replace_callback($this->regex
, $replacement, $string), 'replace');
211 return $this->checkResult(preg_replace($this->regex
, $replacement, $string), 'replace');
215 * Splits the string with the regex.
217 * @param string $string
218 * @param integer $flags
221 public function split($string, $flags = self
::FLAGS_DEFAULT
) {
223 if ($flags & self
::CAPTURE_OFFSET
) $splitFlags |
= PREG_SPLIT_OFFSET_CAPTURE
;
224 if ($flags & self
::SPLIT_NON_EMPTY_ONLY
) $splitFlags |
= PREG_SPLIT_NO_EMPTY
;
225 if ($flags & self
::CAPTURE_SPLIT_DELIMITER
) $splitFlags |
= PREG_SPLIT_DELIM_CAPTURE
;
227 return $this->checkResult(preg_split($this->regex
, $string, null, $splitFlags), 'split');
229 // @codingStandardsIgnoreEnd
232 * Checks whether there was success.
234 * @param mixed $result
235 * @param string $method
237 * @throws SystemException
239 private function checkResult($result, $method = '') {
240 if ($result === false ||
$result === null) {
241 switch (preg_last_error()) {
242 case PREG_INTERNAL_ERROR
:
243 $error = 'Internal error';
245 case PREG_BACKTRACK_LIMIT_ERROR
:
246 $error = 'Backtrack limit was exhausted';
248 case PREG_RECURSION_LIMIT_ERROR
:
249 $error = 'Recursion limit was exhausted';
251 case PREG_BAD_UTF8_ERROR
:
258 $error = 'Unknown error';
262 throw new SystemException('Could not execute '.($method ?
$method.' on ' : '').$this->regex
.': '.$error);
268 * Returns the matches of the last string.
272 public function getMatches() {
273 return $this->matches
;
277 * Returns the compiled regex.
281 public function getRegex() {