1

I have this situation where i have a string of which i have to replace certain parts. I know where these parts are and what they look like (in other words i can match them through a regex) but not only i don't know the exact content but there might be similar substrings inside a string. What i want to achieve is to replace only the particular occurrence at that offset while leaving the rest intact.

An Example:

Test String (this) Test (not this) Test Test (this) Test Test (and this) Test (this maybe)

I know i have to replace strings that match this regex: \(.*?\) but only if they start in positions 12, 62 and 78 (in other words, the first (this) but not the second, (and this) and (this maybe). the substrings (not this) and the second (this) must not be replaced)

I know i should be posting my attempts at the problem but i'm staring at the code since half an hour and no idea came out of it (i've been staring at code for longer time before giving up and ask in the past, but today i'm a bit in a hurry) but i have this feeling the solution is simpler than i think. The only thing i have realized is that i should replace strings in reverse order so i don't modify the positions

valepu
  • 3,136
  • 7
  • 36
  • 67

1 Answers1

2

Here's what I came up with:

$string = 'Test String (this) Test (not this) Test Test (this) Test Test (and this) Test (this maybe)';

$offsets = [12, 62, 78];
$replace = "REPLACED";

// work backwards so we don't have to keep track of changing offsets
rsort($offsets);

foreach ($offsets as $offset) {
    $string = preg_replace('/^(.{' . $offset . '})\([^\)]*\)/', '$1'.$replace, $string);
}

echo $string;
// Test String REPLACED Test (not this) Test Test (this) Test Test REPLACED Test REPLACED

Note that the second (this) was not replaced, because it doesn't start at offset 12, 62 or 78. That one is actually at offset 45, if you add it to the $offsets array it will be replaced as well.

I noticed in my tests that \(.*\) is too broad for the way I solved it, and I changed it to \([^\)*\) instead (essentially: match anything within brackets as long as it's not a closing bracket itself). I had to do this, because \(.*\) also matched (this) Test (not this).

rickdenhaan
  • 10,857
  • 28
  • 37
  • Thanks! it worked. It also works using \\(.*?\\) Using .* without ? makes it greedy and will match until the last occurrence of the next character while adding ? makes it non-greedy and will match only until the first. I know there won't be brackets inside brackets so i'm ok with this regex – valepu Oct 14 '18 at 19:54