PHP регулярное выражение заменить несколько шаблонов с обратным вызовом - PullRequest
1 голос
/ 23 мая 2019

Я пытаюсь выполнить простую замену некоторых входных данных, которые можно описать следующим образом:

  • взять регулярное выражение
  • принять входной поток данных
  • в каждом совпадении, заменить совпадение с помощью обратного вызова

К сожалению, preg_replace_callback () не работает так, как я ожидал. Это дает мне все совпадения по всей линии, а не отдельные совпадения. Поэтому мне нужно снова соединить линию после замены, но у меня нет информации, чтобы сделать это. Показательный пример:

<?php
echo replace("/^\d+,(.*),(.*),.*$/", "12,LOWERME,ANDME,ButNotMe")."\n";
echo replace("/^\d+-\d+-(.*) .* (.*)$/", "13-007-THISLOWER ThisNot THISAGAIN")."\n";


function replace($pattern, $data) {
    return preg_replace_callback(
        $pattern, 
        function($match) {
            return strtolower($match[0]);
        }, $data
    );
}

https://www.tehplayground.com/hE1ZBuJNtFiHbdHO

дает мне 12,lowerme,andme,butnotme, но я хочу 12,lowerme,andme,ButNotMe.

Я знаю, что использование $ match [0] неправильно. Это просто для иллюстрации здесь. Внутри замыкания мне нужно запустить что-то вроде

foreach ($match as $m) { /* do something */ }

Но, как я уже сказал, у меня нет информации о положении совпадений во входной строке, что делает невозможным повторное соединение строки.

Я просмотрел документацию по PHP, а также несколько поисков и не смог найти решение.


Разъяснения:

Я знаю, что $ match [1], $ match [2] ... и т. Д. Содержат совпадения. Но только строка, а не позиция. Представьте, что в моем примере последняя строка также ANDME вместо ButNotMe - согласно регулярному выражению она должна соответствовать , а не , и к ней должен применяться обратный вызов , а не . Вот почему я использую регулярные выражения вместо замены строк.

Кроме того, причина, по которой я использую группы захвата таким образом, заключается в том, что мне нужно, чтобы процесс замены был настраиваемым. Поэтому я не могу жестко закодировать что-то вроде «заменить № 1 и № 2, но не № 3». В другом входном файле позиции могут отличаться, или может потребоваться больше замен, и должно измениться только используемое регулярное выражение.

Так что, если мой ввод "15,LOWER,ME,NotThis,AND,ME,AGAIN", я хочу иметь возможность просто изменить регулярное выражение, а не код и получить желаемый результат. Как правило, $ pattern и $ data являются переменными.

Ответы [ 2 ]

1 голос
/ 23 мая 2019

Это будет работать:

function replaceGroups(string $pattern, string $string, callable $callback)
{
    preg_match($pattern, $string, $matches, PREG_OFFSET_CAPTURE);
    array_shift($matches);

    foreach (array_reverse($matches) as $match) {
        $string = substr_replace($string, $callback($match[0]), $match[1], mb_strlen($match[0]));
    }

    return $string;
}

echo replaceGroups("/^\d+-\d+-(.*) .* (.*)$/", "13-007-THISLOWER ThisNot THISAGAIN", 'strtolower');
1 голос
/ 23 мая 2019

Используются preg_match() и PREG_OFFSET_CAPTURE для возврата групп захвата и смещения в исходной строке, где она найдена. Затем он использует substr_replace() с каждой группой захвата для замены только той части строки, которая должна быть изменена - это исключает любую возможность замены аналогичного текста, который вы не хотите изменять ...

function lowerParts (string $input, string $regex ) {
    preg_match($regex, $input, $matches, PREG_OFFSET_CAPTURE);
    array_shift($matches);
    foreach ( $matches as $match )  {
        $input = substr_replace($input, strtolower($match[0]),
            $match[1], strlen($match[0]));
    }
    return $input;
}
echo lowerParts ("12,LOWERME,ANDME,ButNotMe", "/^\d+,(.*),(.*),.*$/");

дает ...

12,lowerme,andme,ButNotMe

Но также с

echo lowerParts ("12,LOWERME,ANDME,LOWERME", "/^\d+,(.*),(.*),.*$/");

это дает

12,lowerme,andme,LOWERME

Edit:

Если данные для замены имеют разную длину, вам нужно будет разбить строку на части и заменить каждую. Сложность состоит в том, что каждое изменение в длине изменяет относительное положение смещений, поэтому необходимо отслеживать, что это за смещение. В этой версии также есть параметр, который представляет собой процесс, который вы хотите применить к строкам (этот пример просто передает "strtolower") ...

function processParts (string $input, string $regex, callable $process ) {
    preg_match($regex, $input, $matches, PREG_OFFSET_CAPTURE);
    array_shift($matches);
    $offset = 0;
    foreach ( $matches as $match )  {
        $replacement = $process($match[0]);
        $input = substr($input, 0, $match[1]+$offset)
                 .$replacement.
                 substr($input, $match[1]+$offset+strlen($match[0]));
        $offset += strlen($replacement) - strlen($match[0]);
    }
    return $input;
}
echo processParts ("12,LOWERME,ANDME,LOWERME", "/^\d+,.*,(.*),(.*)$/", "strtolower");
...