Регулярные выражения Perl и их применение


         

Замена n-го совпадения


Теперь рассмотрим замену n-го найденного фрагмента текста.

use locale;

my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $count=0; $s =~ s/(тел\.\s+)([\d-]+)/ ++$count == 3 ? "${1}9-9999" : "$1$2" /egi; print $s;

Напечатается строка

Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999

Оператор подстановки с модификатором g заменяет все найденные фрагменты текста глобально, и ему для этого не нужен списочный контекст, как оператору поиска.

Мы хотели третий номер телефона заменить на 9-9999, но после каждого найденного номера выполняется замена, поэтому то, что не нужно менять, должно быть заменено "на себя". Для этого мы взяли все перед номером телефона в переменную $1, а все остальное - в переменную $2. Если номер телефона не равен 3, то мы найденный фрагмент текста (который соответствует всему регулярному выражению!) меняем на строку $1$2, а в третьем случае в замене вместо номера телефона подставляем 9-9999. Для разделения имени переменной от цифр используем фигурные скобки. Не забываем также поставить модификатор e.

Как быть, когда нужно заменить n-й от конца найденный фрагмент текста, если заранее неизвестно, сколько таких фрагментов будет найдено? Рассмотрим такую идею.

Призываем в помощь конструкцию опережающей проверки и записываем регулярное выражение так, чтобы оно совпало только на n-м от конца искомом фрагменте текста, где n отсчитывается с нуля. Пусть это n хранится в переменной $n. Попробуем вначале такую программу для решения задачи:

#!/usr/bin/perl -w use strict; use locale;

my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $tel='тел\.\s+[\d-]+'; my $n=1; $s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер (?=(?:.*?$tel){$n} # за которым $n раз идет "тел." + номер (?!.*?$tel) # после чего не встречается "тел." +номер )/${1}9-9999/isx; print $s;

Поясню, как она должна работать по первоначальному замыслу.

Т.к. литерал тел\.\s+[\d-]+ в регулярном выражении будет встречаться несколько раз, то оформим его в виде переменной $tel, которую будем подставлять. Сформулируем задачу в терминах регулярных выражений: нам надо найти фрагмент, соответствующий шаблону тел\.\s+[\d-]+, за которым (фрагментом) в тексте встречается ровно $n таких же фрагментов. Номер телефона в таком фрагменте мы меняем на 9-9999.

Чтобы в этом фрагменте всё, кроме номера телефона, сохранилось, мы берем это остальное в скобки и получаем подшаблон (тел\.\s+)[\d-]+, с которого начинается регулярное выражение. За фрагментом текста, соответствующим этому шаблону, должен идти фрагмент (?:.*?$tel){$n}. ."*" впереди него означает, что эти $n фрагментов не обязательно должны идти сразу за первым фрагментом и друг за другом, между ними могут быть посторонние включения, которые поглощаются конструкцией ".*?". После того, как эти $n фрагментов поглощены, не должно стоять такого же фрагмента текста с любой позиции после этих $n фрагментов, это проверяет условие (?!.*?$tel). Оба этих подшаблона (?:.*?$tel){$n} и (?!.*?$tel) включены в позиционную проверку (?=…). Т.е. за найденным номером телефона должен быть фрагмент текста, который стоит внутри всей этой проверки (?=…).

Что ж, как будто бы все верно, начинаем проверять работу этой программы. Задаем $n=0 и видим результат:

Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999




Содержание  Назад  Вперед