Заменить каждый экземпляр между двумя символами

У меня есть следующие данные ниже, где {n} представляет местозаполнитель.

{n}{n}A{n}{n}A{n}
{n}A{n}{n}{n}{n}A
{n}{n}A{n}A{n}{n}
{n}{n}{n}A{n}A{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

Я хотел бы заменить каждый экземпляр заполнителя между двумя символами А, например, буквой C. Я написал для него следующее регулярное выражение, и я использую функцию preg_replace.

$str = preg_replace('~(?<=A)(\{n\})*(?=A)~', 'C', $str);

Проблема заключается в том, что он заменяет все экземпляры между двумя A одним C. Как я могу исправить свое регулярное выражение или вызов preg_replace для замены каждого отдельного экземпляра заполнителей на C?

Это должен быть мой вывод.

{n}{n}ACCA{n}
{n}ACCCCA
{n}{n}ACA{n}{n}
{n}{n}{n}ACA{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

Но в настоящее время он выводит это.

{n}{n}ACA{n}
{n}ACA
{n}{n}ACA{n}{n}
{n}{n}{n}ACA{n}B
{n}A{n}{n}B{n}{n}
A{n}B{n}{n}{n}{n}

Ответ 1

Вы можете решить проблему, привязавшись к \G.

$str = preg_replace('~(?:\G(?!\A)|({n})*A(?=(?1)++A))\K{n}~', 'C', $str);

Функция \G - это привязка, которая может совпадать в одной из двух позиций; начало позиции строки или положение в конце последнего совпадения. Управляющая последовательность \K сбрасывает исходную точку сообщенного соответствия, и все ранее использованные символы больше не включаются.

Чтобы уменьшить количество обратных следов, вы можете использовать более сложное выражение:

$str = preg_replace('~\G(?!\A)(?:{n}
                      |A(?:[^A]*A)+?((?=(?:{n})++A)\K{n}
                      |(*COMMIT)(*F)))
                      |[^A]*A(?:[^A]*A)*?(?1)~x', 'C', $str);

Ответ 2

Несколько более подробное, но более простое решение - использовать начальное выражение, чтобы разбить текст на группы; затем примените индивидуальное преобразование внутри каждой группы:

$text = preg_replace_callback('~(?<=A)(?:\{n\})*(?=A)~', function($match) {
    // simple replacement inside
    return str_replace('{n}', 'C', $match[0]);
}, $text);

Я сделал небольшую настройку для выражения, чтобы избавиться от захвата памяти, что необязательно, используя (?:...).

Ответ 3

(?<=A){n}(?=(?:{n})*A)|\G(?!^){n}

Вы можете попробовать это. Заменить на C. Здесь вы должны использовать \G для утверждения позиции в конце предыдущего совпадения или начала строки для первого совпадения.

Итак, вы можете совпадение после первого матча. См. Демонстрацию.

https://regex101.com/r/wU4xK1/7

Здесь сначала вы сопоставляете {n}, у которого A позади него и A после него, у которого может быть {n} между ними. После захвата вы используете \G до reset до конца предыдущего совпадения и впоследствии сохраняете замену найденного {n}.

$re = "/(?<=A){n}(?=(?:{n})*A)|\\G(?!^){n}/";
$str = "{n}{n}A{n}{n}A{n}\n{n}A{n}{n}{n}{n}A\n{n}{n}A{n}A{n}{n}\n{n}{n}{n}A{n}A{n}B\n{n}A{n}{n}B{n}{n}\nA{n}B{n}{n}{n}{n}";
$subst = "C";

$result = preg_replace($re, $subst, $str);