Решил вчера написать плагин для своего блога на WordPress. Он заключается в замене содержимого, которое в коде выделено тегом <var>
.
Как только дело доходит до использования функции preg_replace()
я испытываю неопытность, несмотря на большой опыт программирования. Речь конечно идет о регулярных выражениях.
Сначала я решил, что функция должна вернуть строку, в которой будут заменены все символы на звездочки "*". Рассмотрим следующий код:
preg_replace('/<var>(.*?)<\/var>/', '*', $content);
Содержимое переменной $content
:
Строка для замены с секретным кодом <var>836-72</var> и секретной строкой <var>"секрет 2"</var>.
После обработки содержимого переменной $content
все найденные совпадения будут заменены на один символ "*", а мне нужно, чтобы звезд было столько же, сколько символов между тегами <var></var>
:
Строка для замены с секретным кодом * и секретной строкой *.
Я сразу догадался, что нужно воспользоваться функцией str_repeat()
совместно с strlen()
с использованием 2-го вхождения $1
. Код получился следующий:
preg_replace('/<var>(.*?)<\/var>/', str_repeat("*", strlen("$1")), $content);
Естественно ничего не стало работать и тогда я стал искать решение в Интернете. Приведу одну из страниц, которая мне помогла:
Теперь я понял, что 2-й аргумент функции preg_replace()
необходимо брать в кавычки:
preg_replace('/<var>(.*?)<\/var>/e', 'str_repeat("*", strlen("$1"))', $content);
Данный код работает. Но если 1-й момент с кавычками понятен, то почему используется в маске символ "e
" после косой черты – нет.
Оказывается добавление данного символа позволяет исполнить код PHP, который содержится в строке. Это та же функция eval()
.
В процессе изучения вопроса я нашел в официальной документации PHP дополнительную информацию по регулярным выражениям:
Отлично, символы прекрасно заменяются:
Строка для замены с секретным кодом ****** и секретной строкой ****************.
Но мне этого стало мало. Я захотел, чтобы теперь не все символы заменялись, а только цифры и буквы. Я понял, что нужно использовать функцию preg_replace()
в preg_replace()
.
Сначала у меня ничего не работало (видимо путался в двойных и одинарных кавычках) и тогда я наткнулся на функцию preg_replace_callback()
. Данная функция вторым аргументом может принять create_function()
. Таким образом, мне просто было легче прописать еще один вызов preg_replace()
.
В итоге, код стал следующий:
preg_replace_callback('/<var>(.*?)<\/var>/', create_function('$matches', '$matches[1] = preg_replace("/[\w]/", "*", $matches[1]); return "<span class=\"stars\">" . $matches[1] . "</span>";'), $content);
2-й выглядит довольно просто:
preg_replace("/[\w*?]/", "*", $matches[1]);
Несмотря на видимую правильность кода, он не работает с русскими символами. Простая задача превратилась в большую для меня сложность. Текст стал следующим:
Строка для замены с секретным кодом ***-** и секретной строкой "секрет *".
Русские буквы попросту не заменились.
Стал искать ответ в Интернете. Очень помог данный ресурс:
Особенно помогли комментарии под записью. Оказалось, что можно вместо "\w
", который в UTF-8 не распознает русские символы воспользоваться "\p{L}
".
Но и с "\p{L}
" проблема – на локальном компьютере у меня все заработало, в на хостинге нет. Вместо русских символов в некоторых местах появились знаки вопросов и количество звезд увеличилось.
Пошел далее искать в Интернете. По очереди быстро просмотрел 2 следующие страницы и на последней нашел решение:
На всякий случай копирую в запись код, который привел автор статьи:
<? print "Локаль: " . setLocale(LC_ALL, 0) . "\n"; /** * Выводит результаты функции preg_match_all * @param string $comment Комментарий * @param string $pattern Паттерн для preg_match_all * @param bool $usePatch Использовать ли патч * @return void */ function preg_test($comment, $pattern, $usePatch = false) { $test = "one два два three"; print "\n<strong>{$comment}:</strong> <u>{$pattern}</u>\n"; if ($usePatch) mb_preg_match_all($pattern, $test, $matches, PREG_OFFSET_CAPTURE); else preg_match_all($pattern, $test, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $v) print " Подстрока: «{$v[0]}», смещение: {$v[1]}\n"; } /** * Патч для устранения проблемы с оффсетами, осуществляет только их пересчет */ function mb_preg_match_all( $ps_pattern, $ps_subject, &$pa_matches, $pn_flags = PREG_PATTERN_ORDER, $pn_offset = 0, $ps_encoding = NULL ) { // WARNING! - All this function does is to correct offsets, nothing else: //(code is independent of PREG_PATTER_ORDER / PREG_SET_ORDER) if (is_null($ps_encoding)) $ps_encoding = mb_internal_encoding(); $pn_offset = strlen(mb_substr($ps_subject, 0, $pn_offset, $ps_encoding)); $ret = preg_match_all($ps_pattern, $ps_subject, $pa_matches, $pn_flags, $pn_offset); if ($ret && ($pn_flags & PREG_OFFSET_CAPTURE)) foreach($pa_matches as &$ha_match) foreach($ha_match as &$ha_match) $ha_match[1] = mb_strlen(substr($ps_subject, 0, $ha_match[1]), $ps_encoding); return $ret; } preg_test("«В лоб» — не ловятся русские буквы", "/[\w]+/i"); preg_test("Character range — ловится все, что нужно, но кривые offset'ы", "/[а-яa-z]+/i"); preg_test("«В лоб» с ключем «/u» — снова не ловятся русские буквы", "/[\w]+/ui"); preg_test("Character range с ключем «/u» — ловится все, что нужно, но кривые offset'ы", "/[а-яa-z]+/ui"); preg_test("Модификатор «\pL», можно даже без «/u» — ловится все, что нужно, но кривые offset'ы", "/[\pL]+/i"); preg_test("Модификатор «\p{Cyrillic}», можно тоже без «/u» — ловится все, что нужно, но кривые offset'ы", "/[\p{Cyrillic}]+/i"); preg_test("(!) Модификатор «\pL» с патчем — ловится все, правильные offset'ы — ЭТО ПРАВИЛЬНЫЙ СПОСОБ", "/[\pL]+/i", true); $source = highlight_file(__FILE__, true); ?>
На хостинге у меня заработал вариант "/[а-яa-z0-9]/ui
". Однако, автор пишет, что правильный способ: "/[\pL]+/i
", который мне выдал знаки вопросов. И тогда я понял, что решение заключается в модификаторе "u
". Смотрим в документацию:
и читаем:
u (PCRE_UTF8) – этот модификатор включает дополнительную функциональность PCRE, которая не совместима с Perl: шаблоны обрабатываются как UTF-8 строки. Модификатор u доступен в PHP 4.1.0 и выше для Unix-платформ, и в PHP 4.2.3 и выше для Windows платформ. Валидность UTF-8 в шаблоне проверяется начиная с PHP 4.3.5.
Именно это и было нужно для моего хостинга. Итоговый и работающий вариант выглядит следующим образом:
preg_replace_callback('/<var>(.*?)<\/var>/', create_function('$matches', '$matches[1] = preg_replace("/[\pL]/ui", "*", $matches[1]); return "<span class=\"stars\">" . $matches[1] . "</span>";'), $content);
На экран выводится следующая строчка:
Строка для замены с секретным кодом ***-** и секретной строкой "****** *".
Спасибо тем, кто до меня копал в этой теме, я знаю, как это сложно и сколько отнимает времени.
Кстати также рабочий вариант прописать полностью русский алфавит в квадратных скобках. Замена произойдет без знаков вопросов, русские символы будут обработаны. Только одна исключение – звезд вновь будет больше, чем должно быть.
Переставил последнюю строчку кода в 1-й preg_replace()
и все также заработало:
preg_replace('/<var>(.*?)<\/var>/e', 'preg_replace("/[\pL]/ui", "*", "$1")', $content);