Избегать обработки специальных прег символов в замещающей строке - PullRequest
4 голосов
/ 24 сентября 2010

При использовании preg_replace () в PHP со строками, генерируемыми во время выполнения, можно защитить специальные символы регулярного выражения (такие как '$' или '+') в строке поиска с помощью preg_quote (). Но как правильно обработать это в строке замены? Возьмите этот код, например:

<?php

$haystack = '...a bit of sample text...';
$replacement = '\\HELLO WORLD$1.+-';
$replacement_quoted = preg_quote($replacement);

var_dump('--replacement', $replacement, '--replacement_quoted',
    $replacement_quoted, '--haystack', $haystack);

$result1 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement ."$3", $haystack);
$result2 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_quoted ."$3", $haystack);

$replacement_new1 = str_replace('$', '\$', $replacement);
$replacement_new2 = str_replace('\\', '\\\\', $replacement_new1);

$result3 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new1 ."$3", $haystack);
$result4 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new2 ."$3", $haystack);

var_dump('--result1 (not quoted)', $result1, '--result2 (quoted)', $result2,
    '--result3 ($ escaped)', $result3, '--result4 (\ and $ escaped)', $result3);

?>

Вот вывод:

string(13) "--replacement"
string(17) "\HELLO WORLD$1.+-"
string(20) "--replacement_quoted"
string(22) "\\HELLO WORLD\$1\.\+\-"
string(10) "--haystack"
string(26) "...a bit of sample text..."
string(22) "--result1 (not quoted)"
string(40) "...a bit\HELLO WORLDbit.+-sample text..."
string(18) "--result2 (quoted)"
string(42) "...a bit\HELLO WORLD$1\.\+\-sample text..."
string(21) "--result3 ($ escaped)"
string(39) "...a bit\HELLO WORLD$1.+-sample text..."
string(27) "--result4 (\ and $ escaped)"
string(39) "...a bit\HELLO WORLD$1.+-sample text..."

Как видите, вы не можете выиграть с помощью preg_quote (). Если вы не вызываете его и просто передаете строку в неизмененном виде (result1), все, что похоже на токен захвата ($ 1 выше), заменяется на что бы ни содержала соответствующая группа захвата. Если вы назовете его (result2), у вас не возникнет проблем с группами захвата, но любые другие специальные символы PCRE (такие как *) также будут экранированы, и экранированные символы сохранятся в выходных данных. Также интересно, что обе версии выдают один \ в выходной файл.

Вы можете заставить это работать, только указав символы в кавычках вручную, в частности, $. Это можно увидеть в result3 и result4. Однако, продолжая странность с \, оба result3, который добавляет экранирование для \, и result4 снова выдают одиночный \ в выходных данных. Добавление шести символов \ в начале строки замены приводит к получению только двух символов \ в конечном выводе для result1, result3 и result4 и трех из них для result2.

Таким образом, кажется, что большинство проблем решается путем ручного экранирования символа $. Кажется, что \ символ также должен быть экранирован, но мне нужно подумать об этом еще немного, чтобы точно выяснить, что происходит. В любом случае, все это довольно уродливо - между надоедливым синтаксисом \ $ {1} и необходимостью вручную экранировать определенные символы, код просто пахнет очень гнилым и подверженным ошибкам. Я что-то упускаю? Есть ли чистый способ сделать это?

1 Ответ

1 голос
/ 01 октября 2010

Хорошо, ну, я не думаю, что есть какой-то действительно удовлетворительный способ справиться с этим. Проблем два: символ \ и символ $. Другие специальные символы PCRE, по-видимому, не являются особенными при замене.

В случае \ все на самом деле ведет себя так, как и следовало ожидать, в этом случае вам необходимо экранировать его с помощью \ как при определении его через PHP, так и при передаче его в preg_replace(). В моем тестовом коде я просто путал себя с двумя уровнями побега. Что касается $, то он должен быть оставлен один на стороне PHP и экранироваться с \, переходящим в preg_replace(). Вот и все.

Вот код, демонстрирующий все это:

<?php

ini_set('display_errors', 1);
ini_set('error_reporting', E_ALL | E_STRICT);

//real string: "test1 $1 test2 \\1 test3 \${1}"

//real string manually \-escaped once for representing as a PHP string
$test = 'test1 $1 test2 \\\\1 test3 \\${1}';
var_dump('--test (starting PHP string - should match real string)', $test);

$test = str_replace(array('\\', '$'), array('\\\\', '\\$'), $test);
var_dump('--test (PHP string $-escaped and \-escaped again for preg_replace)', $test);

$result = preg_replace("/bar/", $test, 'foo bar baz');

var_dump('--result - bar should be replaced with original real string', $result);

?>

Выход:

string(55) "--test (starting PHP string - should match real string)"
string(30) "test1 $1 test2 \\1 test3 \${1}"
string(66) "--test (PHP string $-escaped and \-escaped again for preg_replace)"
string(35) "test1 \$1 test2 \\\\1 test3 \\\${1}"
string(59) "--result - bar should be replaced with original real string"
string(38) "foo test1 $1 test2 \\1 test3 \${1} baz"

У меня такое ощущение, что preg_quote() должно быть решением здесь, и было бы, если бы preg_replace() игнорировал экранированные символы, кроме самого \ и $ (например, +). Тем не менее, это не так, вынуждая сделать ручной выход. На самом деле, я бы сказал, что это ошибка, и буду продолжать регистрировать ее на php.net.

...