groups, $this->internalEscape((string)$expression), $out); # treat only strings $replacement if( is_string($replacement) ) { # does the pattern deal with sub-expressions? if( preg_match($this->subreplace, $replacement) ) { # a simple lookup? (e.g. "$2") if( preg_match($this->indexed, $replacement) ) { # store the index (used for fast retrieval of matched strings) $replacement = (int)(substr($replacement, 1)) - 1; } else { # a complicated lookup (e.g. "Hello $2 $1") # build a function to do the lookup $quote = preg_match($this->quote, $this->internalEscape($replacement)) ? '"' : "'"; $replacement = [ 'fn' => 'backReferences', 'data' => [ 'replacement' => $replacement, 'length' => $length, 'quote' => $quote ] ]; } } } # pass the modified arguments $this->adding($expression ?: '/^$/', $replacement, $length); } /** * Exec * * @param string * * @return string */ public function exec($string) { # execute the global replacement $this->escaped = []; # simulate the _patterns.toSTring of Dean $regexp = '/'; foreach( $this->patterns as $reg ) { $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|'; } $regexp = substr($regexp, 0, -1) . '/'; $regexp.= ($this->ignoreCase) ? 'i' : ''; $string = $this->escape($string, $this->escapeChar); $string = preg_replace_callback($regexp, [&$this, 'replacement'], $string); $string = $this->unescape($string, $this->escapeChar); return preg_replace($this->deleted, '', $string); } /** * reset * * @codeCoverageIgnore */ public function reset() { # clear the patterns collection so that this object may be re-used $this->patterns = []; } /** * Protected adding * * create and add a new pattern to the patterns collection */ protected function adding() { $arguments = func_get_args(); $this->patterns[] = $arguments; } /** * Protected replacemenet * * this is the global replace function (it's quite complicated) */ protected function replacement($arguments) { if( empty($arguments) ) { return ''; // @codeCoverageIgnore } $i = 1; $j = 0; # loop through the patterns while( isset($this->patterns[$j]) ) { $pattern = $this->patterns[$j++]; # do we have a result? if( isset($arguments[$i]) && ($arguments[$i] != '') ) { $replacement = $pattern[self::REPLACEMENT]; if( is_array($replacement) && isset($replacement['fn']) ) { if( isset($replacement['data']) ) { $this->buffer = $replacement['data']; } return call_user_func([&$this, $replacement['fn']], $arguments, $i); } elseif( is_int($replacement) ) { return $arguments[$replacement + $i]; } $delete = ($this->escapeChar == '' || strpos($arguments[$i], $this->escapeChar) === false) ? '' : "\x01" . $arguments[$i] . "\x01"; return $delete . $replacement; # skip over references to sub-expressions } else { $i += $pattern[self::LENGTH]; } } return ''; // @codeCoverageIgnore } /** * Protected back references */ protected function backReferences($match, $offset) { $replacement = $this->buffer['replacement']; $quote = $this->buffer['quote']; $i = $this->buffer['length']; while( $i ) { $replacement = str_replace('$'.$i--, $match[$offset + $i] ?? '', $replacement ?? ''); } return $replacement; } /** * Protected replace name */ protected function replaceName($match, $offset) { $length = strlen($match[$offset + 2] ?? ''); $start = $length - max($length - strlen($match[$offset + 3] ?? ''), 0); return substr($match[$offset + 1] ?? '', $start, $length) . ($match[$offset + 4] ?? ''); } /** * Protected replace encoded */ protected function replaceEncoded($match, $offset) { return $this->buffer[$match[$offset]]; } /** * Protected escape * * encode escaped characters */ protected function escape($string, $escapeChar) { if( $escapeChar ) { $this->buffer = $escapeChar; return preg_replace_callback('/\\' . $escapeChar . '(.)' .'/', [&$this, 'escapeBis'], $string); } return $string; } /** * Protected escape bis */ protected function escapeBis($match) { $this->escaped[] = $match[1]; return $this->buffer; } /** * Protected unescape * * decode escaped characters */ protected function unescape($string, $escapeChar) { if( $escapeChar ) { $regexp = '/'.'\\'.$escapeChar.'/'; $this->buffer = ['escapeChar'=> $escapeChar, 'i' => 0]; return preg_replace_callback($regexp, [&$this, 'unescapeBis'], $string); } return $string; } /** * Protected unescape bis */ protected function unescapeBis() { if( isset($this->escaped[$this->buffer['i']]) && $this->escaped[$this->buffer['i']] != '' ) { $temp = $this->escaped[$this->buffer['i']]; } else { $temp = ''; // @codeCoverageIgnore } $this->buffer['i']++; return $this->buffer['escapeChar'] . $temp; } /** * Protected internal escape */ protected function internalEscape($string) { return preg_replace($this->escape, '', $string); } }