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

ba2f5a3f

Преобразование ftp и http ссылок в теги HTML


Возьмем такой текст:

Зайдите на www.intuit.ru и посмотрите список курсов

Здесь текст www.intuit.ru является ссылкой, несмотря на отсутствие протокола http://, который подразумевается по умолчанию. В итоге наше регулярное выражение должно преобразовать этот текст к такому виду:

Зайдите на <a href="http://www.intuit.ru" target="_blank"> www.intuit.ru</a> и посмотрите список курсов

Может быть и так, что ссылка не отделена пробелом от окружающих слов или после нее идет знак препинания (точка, запятая и т.д.) Желательно, чтобы регулярное выражение это учитывало и не включало такой знак в ссылку. И конечно, оно не должно совпадать там, где ему совпадать не следует. Неплохо было бы, если бы оно также форматировало текст ссылки href: протокол, домен и субдомены должны быть записаны строчными буквами. А сам текст, который будет виден на странице, должен оставаться таким, каким его ввел участник форума. Задача эта непростая и не формализуется. Критерием успешности регулярного выражения является то, как оно справляется с набором тестов, которые провоцируют его к несовпадению или совпадению не в тех местах.

Это регулярное выражение достаточно сложное и громоздкое, и мы будем создавать его по частям. Начнем с протокола.

Протокол может быть http, https и ftp. Для его обнаружения создадим строковую переменную $protocol:

my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';

Если в тексте следующий символ F, f, H или h, то этот подшаблон делает проверку следующих за ним символов и, если это протокол, поглощает его вместе с префиксом. Я взял весь шаблон для протокола в скобки, потому что в общем регулярном выражении у этого подшаблона может стоять квантификатор, который должен относиться ко всему этому подшаблону, а не к последнему его символу /.

Результирующий оператор подстановки у нас будет иметь модификатор x, поэтому для имени хоста запишем такое регулярное выражение в свободном формате:

my $host=<<HOST; (?>[A-Za-z0-9]{1,63}\\.) (?>[A-Za-z0-9] (?>[-A-Za-z0-9]{0,62})\\. )* HOST


После имени хоста через двоеточие может идти порт:

my $port="(?::\\d{1,5}$wb)";

А после зоны может идти хвост, который содержит множество всяких параметров, передаваемых с URL:

my $tail=<<TAIL; (?:[/?] (?>[^.,"'<>()[\\]{}\\s\\x7F-\\xFF]*) (?:(?>[.,?]+) (?:[^"'<>()[\\]{}\\s\\x7F-\\xFF]+) )* (?<![,.?!-]) ) TAIL

В конце стоит заглядывание назад

(?<![,.?!-])

которое учитывает, что после URL могут стоять знаки препинания, которые в него не входят.

Все регулярное выражение для поиска URL в тексте выглядит немного страшновато:

my $re=<<RE; ( (?>($protocol)(?(2)(?>$host$zone)|$host$zone) (?![A-Za-z0-9])| (?<![A-Za-z0-9_\\\@-]) (?<!\\.(?!(?i:www))) $subdom$zone(?![A-Za-z0-9_.-]*\\\@) ) (?>(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?) ) ($tail?) RE

Это выражение учитывает заход через прокси-сервер вида

http://proxy.com@site.com/

Часть URL от начала до хвоста, который может идти после символов / или ?, мы берем в нумерованную переменную $1. Эту часть URL внутри тега <a мы будем выводить маленькими буквами, а для отображения на странице будем выводить в том виде, в котором ее ввел автор сообщения. Протокол мы возьмем в переменную $2. Если протокола нет, то при форматировании ссылки мы подставим на его место текст http://. Хвост $tail мы захватываем в переменную $3.

Внутри переменной $re встречается эскейп-последовательности \@. Но т.к. внутри текста here doc \ и @ являются метасимволами, то, чтобы в результате получить последовательность \@, надо написать \\\@. Тогда при обработке такого текста \\ превратится в \, а \@ превратится в @, и в конце получится нужная последовательность \@. Для проверки напечатайте переменную $re.

Программа должна "подсвечивать" ссылки в тексте. Например, имеем текст

Look at:aaa.Museum.

Это должно превратиться в

Look at:<a href="aaa.museum" target="_blank">aaa.Museum</a>.

Здесь возможный URL отделяется от прилипших справа и слева символов и оформляется в тег <a. Внутри строки href имя хоста и зона записываются строчными буквами, а в тексте, который будет виден на странице, форматирования не производится. Еще программа должна учесть, что в хвосте URL (имена подкаталогов и параметры) форматировать текст нельзя, т. к. эти слова чувствительны к регистру символов.

Возьмем в качестве тестового такой текст:

my $text=<<TEXT; URLs: Ftp://a.com/AAa Look at:aaa.Museum. http://www.proxy.com:80\@www.site.com/ http://proxy.com:80\@site.com/ http://proxy.com\@site.com/ aAaa.com.au.rr.ggg Zwww.Yahoo.co.uk Фforum.abcd.de www.Abc.eu П123.123.123.1234.com/?q=aaa http://Abc.Tk Ahttp://www.Abc.pt/AAa http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.old-avto.tk



my $re=<<RE; ( (?>($protocol)(?(2)(?>$host$zone)|$host$zone) (?![A-Za-z0-9])| (?<![A-Za-z0-9_\\\@-]) (?<!\\.(?!(?i:www))) $subdom$zone(?![A-Za-z0-9_.-]*\\\@) ) (?>(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?) ) ($tail?) RE

my $text=<<TEXT; URLs: Ftp://a.com/AAa Look at:aaa.Museum. http://www.proxy.com:80\@www.site.com/ http://proxy.com:80\@site.com/ http://proxy.com\@site.com/ aAaa.com.au.rr.ggg Zwww.Yabcd.co.uk Фforum.abcd.de www.Abc.eu П123.123.123.1234.com/?q=aaa http://Abc.Tk Ahttp://www.Abc.pt/AAa http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.old-avto.tk

NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com TEXT

$text =~ s!$re!<a href="${\($2 ? '' : 'http://')}\L$1\E$3" target="_blank">$1$3</a>!gx; print $text;

Листинг 8.1.

А вот текст, который она печатает:

URLs: <a href="ftp://a.com/AAa" target="_blank">Ftp://a.com/AAa</a> Look at:<a href="http://aaa.museum" target="_blank">aaa.Museum</a>. <a href="http://www.proxy.com:80@www.site.com/" target="_blank">http://www.proxy.com:80@www.site.com/</a> <a href="http://proxy.com:80@site.com/" target="_blank">http://proxy.com:80@site.com/</a> <a href="http://proxy.com@site.com/" target="_blank">http://proxy.com@site.com/</a> <a href="http://aaaa.com.au.rr" target="_blank">aAaa.com.au.rr</a>.ggg <a href="http://zwww.yabcd.co.uk" target="_blank">Zwww.Yabcd.co.uk</a> Ф<a href="http://forum.abcd.de" target="_blank">forum.abcd.de</a> <a href="http://www.abc.eu" target="_blank">www.Abc.eu</a> П<a href="http://123.123.123.1234.com/?q=aaa" target="_blank">123.123.123.1234.com/?q=aaa</a> <a href="http://abc.tk" target="_blank">http://Abc.Tk</a> A<a href="http://www.abc.pt/AAa" target="_blank">http://www.Abc.pt/AAa</a> <a href="http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image" target="_blank">http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image</a> Ф.<a href="http://www.old-avto.tk" target="_blank">Www.old-avto.tk</a>

NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com

Листинг 8.2.



3-я, 10-я и 13-я строка не уместились по ширине страницы.

Обратите внимание, как преобразуется в URL строка

aAaa.com.au.rr.ggg

Получается

<a href="http://aaaa.com.au.rr" target="_blank">aAaa.com.au.rr</a>.ggg

.ggg не считается частью URL. Количество последовательностей символов через точку ограничено, чтобы не захватить в URL следующий за ним текст. Это интуитивное ограничение.


Содержание раздела