Недавно я задал аналогичный вопрос, но не получил четкого ответа, потому что был слишком специфичен. Это более широкий.
Кто-нибудь знает, как заменить появление (x) в шаблоне регулярных выражений?
Пример. Предположим, я хотел заменить 5-е вхождение шаблона регулярного выражения в строке. Как мне это сделать?
Вот шаблон: preg_replace('/{(.*?)\|\:(.*?)}/', 'replacement', $this->source);
@anubhava ЗАПРЕЩЕННЫЙ КОД ОБРАЗЦА (последняя функция не работает):
$sample = 'blah asada asdas {load|:title} steve jobs {load|:css} windows apple '; $syntax = new syntax(); $syntax->parse($sample); class syntax { protected $source; protected $i; protected $r; // parse source public function parse($source) { // set source to protected class var $this->source = $source; // match all occurrences for regex and run loop $output = array(); preg_match_all('/\{(.*?)\|\:(.*?)\}/', $this->source, $output); // run loop $i = 0; foreach($output[0] as $key): // perform run function for each occurrence, send first match before |: and second match after |: $this->run($output[1][$i], $output[2][$i], $i); $i++; endforeach; echo $this->source; } // run function public function run($m, $p, $i) { // if method is load perform actions and run inject switch($m): case 'load': $this->inject($i, 'content'); break; endswitch; } // this function should inject the modified data, but I'm still working on this. private function inject($i, $r) { $output = preg_replace('/\{(.*?)\|\:(.*?)\}/', $r, $this->source); } }
$sample = 'blah asada asdas {load|:title} steve jobs {load|:css} windows apple '; $syntax = new syntax(); $syntax->parse($sample); class syntax { protected $source; protected $i; protected $r; // parse source public function parse($source) { // set source to protected class var $this->source = $source; // match all occurrences for regex and run loop $output = array(); preg_match_all('/\{(.*?)\|\:(.*?)\}/', $this->source, $output); // run loop $i = 0; foreach($output[0] as $key): // perform run function for each occurrence, send first match before |: and second match after |: $this->run($output[1][$i], $output[2][$i], $i); $i++; endforeach; echo $this->source; } // run function public function run($m, $p, $i) { // if method is load perform actions and run inject switch($m): case 'load': $this->inject($i, 'content'); break; endswitch; } // this function should inject the modified data, but I'm still working on this. private function inject($i, $r) { $output = preg_replace('/\{(.*?)\|\:(.*?)\}/', $r, $this->source); } }
Вы недопонимаете регулярные выражения: они без гражданства, не имеют памяти и не имеют возможности подсчитывать, ничего, поэтому вы не можете знать, что совпадение – это совпадение x'в строке – механизм регулярных выражений не имеет ключ. Вы не можете делать такие вещи с регулярным выражением по той же причине, что и невозможность написать регулярное выражение, чтобы увидеть, имеет ли строка сбалансированные скобки: для этой проблемы требуется память, которая, по определению, не имеет регулярных выражений.
Однако механизм регулярных выражений может рассказать вам все совпадения, поэтому вам лучше использовать preg_match()
чтобы получить список совпадений, а затем изменить строку, используя эту информацию самостоятельно.
Обновление : это ближе к тому, о чем вы думаете?
<?php class Parser { private $i; public function parse($source) { $this->i = 0; return preg_replace_callback('/\{(.*?)\|\:(.*?)\}/', array($this, 'on_match'), $source); } private function on_match($m) { $this->i++; // Do what you processing you need on the match. print_r(array('m' => $m, 'i' => $this->i)); // Return what you want the replacement to be. return $m[0] . '=>' . $this->i; } } $sample = 'blah asada asdas {load|:title} steve jobs {load|:css} windows apple '; $parse = new Parser(); $result = $parse->parse($sample); echo "Result is: [$result]\n";
Который дает…
Array ( [m] => Array ( [0] => {load|:title} [1] => load [2] => title ) [i] => 1 ) Array ( [m] => Array ( [0] => {load|:css} [1] => load [2] => css ) [i] => 2 ) Result is: [blah asada asdas {load|:title}=>1 steve jobs {load|:css}=>2 windows apple ]
Более простое и чистое решение, которое также касается обратных ссылок:
function preg_replace_nth($pattern, $replacement, $subject, $nth=1) { return preg_replace_callback($pattern, function($found) use (&$pattern, &$replacement, &$nth) { $nth--; if ($nth==0) return preg_replace($pattern, $replacement, reset($found) ); return reset($found); }, $subject,$nth ); } echo preg_replace_nth("/(\w+)\|/", '${1} is the 4th|', "|aa|b|cc|dd|e|ff|gg|kkk|", 4);
выходы | aa | b | cc | dd – 4 | e | ff | gg | kkk |
Как уже было сказано, регулярное выражение не имеет состояния, и вы не можете этого сделать, просто передав целое число, чтобы точно определить точное соответствие для замены … вы можете обернуть замену в метод, который находит все совпадения и заменяет только n-ое совпадение задан как целое число
<? function replace_nth_occurence ( &$haystack, $pattern, $replacement, $occurence) { preg_match_all($pattern, $haystack, $matches, PREG_OFFSET_CAPTURE); if(array_key_exists($occurence-1, $matches[0])) { $haystack = substr($haystack, 0, $matches[0][$occurence-1][1]). $replacement. substr($haystack, $matches[0][$occurence-1][1] + strlen($matches[0][$occurence-1][0]) ); } } $haystack = "test0|:test1|test2|:test3|:test4|test5|test6"; printf("%s \n", $haystack); replace_nth_occurence( $haystack, '/\|:/', "<=>", 2); printf("%s \n", $haystack); ?>
Это альтернативный подход:
$parts = preg_split('/\{((?:.*?)\|\:(?:.*?))\}/', $this->source, PREG_SPLIT_DELIM_CAPTURE);
$ parts будут содержать исходные части строки даже при смещениях [0] [2] [4] [6] [8] [10] …
И соответствующие разделители будут в [1] [3] [5] [7] [9]
Например, чтобы найти пятое появление, вы можете изменить элемент $n*2 - 1
который в этом случае будет элементом [9]:
$parts[5*2 - 1] = $replacement.
Затем соберите все:
$output = implode($parts);
Существует не буквальный способ сопоставить появление 5 шаблона /pat/
. Но вы могли бы сопоставить /^(.*?(?:pat.*?){4,4})pat/
и заменить на \1repl
. Это заменит первые 4 вхождения, плюс что-нибудь следующее, с тем же, и пятое с repl.
Если /pat/
содержит группы захвата, вам нужно будет использовать неконвертирующий эквивалент для первых совпадений N-1. Образец замены должен ссылаться на захваченные группы, начиная с \\2
.
Реализация выглядит так:
function replace_occurrence($pat_cap,$pat_noncap,$repl,$sample,$n) { $nmin = $n-1; return preg_replace("/^(.*?(?:$pat_noncap.*?){". "$nmin,$nmin". "})$pat_cap/",$r="\\1$repl",$sample); }
Моя первая идея заключалась в том, чтобы использовать preg_replace с обратным вызовом и делать подсчет в обратном вызове , как продемонстрировали другие пользователи (превосходно).
В качестве альтернативы вы можете использовать preg_split, сохраняя разделители, используя PREG_SPLIT_DELIM_CAPTURE , и выполняйте фактическую замену в результирующем массиве. PHP только фиксирует, что происходит между захватом parens, так что вам придется либо адаптировать регулярное выражение, либо самостоятельно позаботиться о других захватах. Если предположить, что 1 фиксирующая пара, то захваченные разделители всегда будут в нечетных индексах: 1, 3, 5, 7, 9, …. Вам понадобится индекс 9; и снова взорвать его.
Это подразумевает, что вам понадобится один захват
$sample = "blah asada asdas {load|:title} steve jobs {load|:css} windows apple\n"; $sample .= $sample . $sample; # at least 5 occurrences $parts = preg_split('/(\{.*?\|\:.*?\})/', $sample, -1, PREG_SPLIT_DELIM_CAPTURE); $parts[9] = 'replacement'; $return = implode('', $parts);