Лекция 8. Пример поиска и подсветки ссылок и e-mail в тексте
В данной лекции рассматривается построение регулярных выражении для выделения ссылок в тексте в теги HTML.
Ключевые слова: perl, шаблон, подшаблон, класс символов, диапазон символов, метасимвол, эскейп-последовательность. обратная ссылка, альтернативный шаблон, квантификатор, минимальный квантификатор, числитель, повторитель, мнимый символ, якорь, модификатор, захватывающие скобки, незахватывающие скобки, сохраняющие скобки, нумерованная переменная, регулярное выражение.
При обработке таких текстов, как письма и сообщения на форумах, возникает задача отыскания в тексте ссылок и адресов электронной почты и выделение их в теги <а href=.
Преобразование FTP- и HTTP-ссылок в теги HTML
Возьмем такой текст:
Зайдите на www.intuit.ru и посмотрите список курсов
Здесь текст www.intuit.ru является ссылкой, несмотря на отсутствие протокола http://, который подразумевается по умолчанию. В итоге наше регулярное выражение должно преобразовать этот текст к такому виду:
Зайдите на <а href-’http://ww.intuit, ru”
target='’_blank”>www.intuit.ru и посмотрите список курсов
Может быть и так. что ссылка не отделена пробелом от окружающих слов или после нее идет знак препинания (точка, запятая и т. д.) Желательно, чтобы регулярное выражение это учитывало и не включало такой знак в ссылку. И конечно, оно не должно совпадать там, где ему совпадать не следует. Неплохо было бы, если бы оно также форматировало текст ссылки href: протокол, домен и субдомены должны быть записаны строчными буквами. А сам текст, который будет виден на странице, должен оставаться таким, каким его ввел участник форума. Задача эта непростая и не формализуется. Критерием успешности регулярного выражения является то. как оно справляется с набором тестов, которые провоцируют его к несовпадению или совпадению не в тех местах.
Это регулярное выражение достаточно сложное и громоздкое, и мы будем создавать его по частям. Начнем с протокола.
Протокол может быть HTTP, HTTPS и FTP. Для его обнаружения создадим строковую переменную $protocoi.
my $protocol='(?:(?=[FfHh])(?!:http(?>s?)|ftp)://)’;
Если в тексте следующий символ Л/, Н или h. то этот подшаблон делает проверку следующих за ним символов и, если это протокол, поглощает его вместе с префиксом. Я взял весь шаблон для протокола в скобки, потому что в общем регулярном выражении у этого подшаблона может стоять квантификатор, который должен относиться ко всему этому подшаблону, а не к последнему его символу /.
Результирующий оператор подстановки у нас будет иметь модификатор л*, поэтому для имени хоста запишем такое регулярное выражение в свободном формате:
my $host=«HOST;
- (?>[A-Za-zO-9]{1,63}.)
- (?>[A-Za-zO-9]
- (?>[-A-Za-zO-9]{0,62}).
)*
HOST
Эта запись соответствует последовательности имен, разделенных точками, или ІР-адресу, она в том числе поглотит префикс www. Также учтено, что длина одного имени (от точки до точки) не может быть больше 63 символов. Обратите внимание на два обратных слэша перед точками. Подобная запись содержимого переменной (here doc) транслируется как строка в двойных кавычках. В таких строках обратный слэш является метасимволом. Поэтому, чтобы записать его в такой строке один раз, его надо повторить дважды. Если распечатать переменную $host, то мы увидим по одному обратному слэшу перед точками. По правде говоря. если бы мы поставили по одной обратной черте перед точками, то результат был бы тем же, потому что Perl игнорирует неизвестные эскейп-последователь-ности, такие как ., т. к. точка в строках не является метасимволом и оставляет обратную косую как она есть. Другое дело — сочетания $ и @. Тк. символы 5 и @ в строках, ограниченных двойными кавычками, являются префиксами имен переменных, Perl перед ними удаляет обратную косую черту, которая маскирует эти метасимволы.
Для поддоменов запишем такой шаблон: my $subdom=’(?:(?>[A-Za-zO-9](?:[-A-Za-zO-9J{0,61}[A-Za-zO-9])?).)+’;
Здесь строка ограничена апострофами. В таких строках есть только два метасимвола — обратный слэш и апостроф. Поэтому, если мы хотим вставить в строку, ограниченную апострофами, эти символы, то их надо замаскировать обратным слэшем: и ?. В результате мы вместо последовательности . получим во внутреннем представлении переменной $subdom последовательность .. что нам и нужно. Замечание относительно неизвестных эскейп-последовательностей для строк в двойных кавычках здесь также в силе.
Введем вспомогательный подшаблон, который будет повторяться в других подшаблонах:
my $wb=’(?![A-Za-zO-9])’;
Он означает, что справа не должно быть ни буквы, ни цифры.
Для зоны (это может быть сот, net, org, ru. de, name, museum, co.uk...) имеется такой подшаблон:
my $zone=«Z0NE;
- (?i:(?(?=[a-z]{3}$wb)(?>comI net IorgIeduI biz|govIintI mil)I
- (?(?=[a-z](2}$wb)[a-z]{2}|
- (?(?=[a-z]{4}$wb)(?>info|aero|name)| (?(?=[a-z]{6}$wb)museum|(?!) )
)
)
(?>.[a-z]{2}$wb)?)
ZONE
После имени хоста через двоеточие может идти порт:
my $port=”(?::d{1,5}$wb)”;
А после зоны может идти хвост, который содержит множество всяких параметров, передаваемых с URL:
my $tail=«TAIL;
- (?:[/?]
- (?>[".," 1<>()[]{}sx7F-xFF]*)
- (?:(?>[.,?]+)
(?:["»’<>()[1{}sx7F-xFF]+)
)*
(?<’Г,•?!-])
)
TAIL
В конце стоит заглядывание назад:
(?
которое учитывает, что после URL могут стоять знаки препинания, которые в него не входят.
Все регулярное выражение для поиска URL в тексте выглядит немного страшновато:
my $re=«RE;
- (?>($р гotocol)(?(2)(?>$host$zone)|$host$zone)
- (?![A-Za-zO-9])|
- (?-])
- (?
$subdom$zone(?HA-Za-zO-9_. -]*@)
)
(?>(?>$port?(?>@$host$zone(?l[A-Za-z0-9_. -]*@))?)?)
)
($tail?)
RE
Это выражение учитывает заход через прокси-сервер вида
http://proxy.com^site.сот/
Часть URL от начала до хвоста, который может идти после символов /или ?, мы берем в нумерованную переменную $1. Эту часть URL внутри тега <а мы будем выводить маленькими буквами, а для отображения на странице будем выводить в том виде, в котором ее ввел автор сообщения. Протокол мы возьмем в переменную $2. Если протокола нет, то при форматировании ссылки мы подставим на его место текст http://. Хвост $tail мы захватываем в переменную $3.
Внутри переменной $ге встречаются эскейп-последовательности @. Но т. к. внутри текста here doc и @ являются метасимволами, то чтобы в результате получить последовательность @, надо написать @. Тогда при обработке такого текста превратится в , а @ превратится в и в конце получится нужная последовательность @. Для проверки напечатайте переменную $ге.
Программа должна «подсвечивать» ссылки в тексте. Например, имеем текст
Look at:ааа.Museum.
Это должно превратиться в
Look at:aaa.Museum.
Здесь возможный URL отделяется от прилипших справа и слева символов и оформляется в тег <а. Внутри строки Лге/имя хоста и зона записываются строчными буквами, а в тексте, который будет виден на странице, форматирования не производится. Еще программа должна учесть, что в хвосте URL (имена подкаталогов и параметры) форматировать текст нельзя, т. к. эти слова чувствительны к регистру символов.
Возьмем в качестве тестового такой текст:
my $text=«TEXT;
URLs:
Ftp://а.сот/ААа
Look at:ааа.Museum.
http://www.proxy,com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http://proxy.com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http:// Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
aAaa.com.au.rr.ggg
Zwww.Yahoo.co.uk
(Pforum.abcd.de
www.Abe.eu
П123,123.123.1234.com/?q=aaa
http://Abc.Tk
Ahttp://www.Abe.pt/AAa
http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image
Ф.Www.old-avto. tk
NOT URLs:
aaa. museumin
http://aaa.museumm,
http://-aaa.com
www. aaa.com www.аааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааа.сот TEXT
Как же мы будем форматировать найденный текст? Одним оператором s///. Можно применить вариант с модификатором е, а можно обойтись без него, но тогда в выражении для замены надо будет использовать интерполяцию кода Perl в строку.
Этот оператор подстановки выглядит так:
$text =- s!$re!
ta r get=’'_blarik”>$ 1 $3! gx;
Регулярное выражение для поиска $ге уже составлено, остается сформировать строку для замещения найденного URL. Мы не можем в этой строке замещения сделать конкатенацию вида "... ”.$varl. "... ”.$Var2..., потому что в нем участвует строка без символов — ограничителей строки. Поэтому уже знакомой конструкцией ${( код Perl) мы вставляем протокол с помощью тернарного оператора
$2 ? “ : 'http://'
Если протокол в URL был задан, то мы вставляем то, что задано, если не задан, то вставляем http://. Дальше вставляем часть U RL без хвоста (57), предварительно сделав в нем все буквы строчными. За ней идет хвост $3. А в тексте, который будет виден на HTML-странице, будет фигурировать то, что вводил пользователь: $1$3.
Вот вся наша программа:
й!/usr/bin/perl -w
use strict;
my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)’;
my $host=«HOST;
- (?>[A-Za-zO-9]{1,63}.)
- (?>[A-Za-zO-9]
- (?>[-A-Za-z0-9]{0,62}).
)*
HOST
my $subdom=«SUBDOM;
(?:
- (?>[A-Za-zO-9]
- (?:С-A-Za-zO-9]{0,61}[A-Za-zO-9])?
)-
)+
SUBDOM
my $wb=’(?![A-Za-zO-9])’;
my $zone=«Z0NE;
- (?i:(?(?=[a-z]{3}$wb)(?>com|net|org|edu|biz|gov|int|mil)|
- (?(?=[a-z]{2}$wb)[a-z]{2}|
- (?(?=[a-z]{4}$wb)(?>info|aero|name)|
- (?(?=[a-z]{6}$wb)museum|(?l)
)
)
)
)
(?>.[a-z]{2}$wb)?
)
ZONE
my $port=”(?::d{1t5}$wb)”;
my $tail=«TAIL;
- (?:[/?]
- (?>["..”<>()[]{}sx7F-xFF]*)
- (?:(?>[..?]+)
- (?:["»'<>()[]{}sx7F-xFF]+)
)*
(?
)
TAIL
my $re=«RE;
- (
- (?>($protocol)(?(2)(?>$host$zone)|$host$zone)
- (?![A-Za-zO-9])|
- (?
- (?
$subdom$zone(?![A-Za-z0-9_. -]*@)
)
(?>(?>$port?(?>@$host$zone(?! [A-Za-z0-9_. -]*@))?)?)
)
(Stall?)
RE
my $text=«TEXT;
URLs:
Ftp://a.com/AAa
Look at:aaa.Museum.
http://www.proxy.com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http://proxy,com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http:// Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
aAaa.com.au.rr.ggg
Zwww.Yabcd.co.uk
(Dforum.abcd.de
www.Abc.eu
П123.123.123.1234,com/?q=aaa
http://Abc.Tk
Ahttp://www.Abc.pt/AAa
http://abc.au/que ry/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!$1$3! gx;
print $text;
А вот текст, который она печатает:
URLs:
<а hrof=”ftp://a.com/AAa" target=”_blank”>Ftp://a.com/AAa
Look at:aaa.Museum.
http://www.proxy.com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http://
proxy.com: Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
<а href=”http:// Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /’' target-’_blank”>http:// Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script /
http://aaaa.com.au.r r» ta rget="_blank”>aAaa.com.au.rr.ggg Zwww.Yabcd.co.uk Ф<а href="http://forum.abcd.de" target="_blank”>forum.abed.de www.Abe.eu
П<а href="http://123.123.123.1234.com/?q=aaa” target=”_blank»>123.
123.123.1234.com/?q=aaa
http://Abc.Tk
Ahttp://www.Abe.pt/AAa> 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
NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com 3-я, 10-я и 13-я строка не уместились по ширине страницы. Обратите внимание, как преобразуется в URL строка aAaa.com.au.rr.ggg Получается <а href=”http://aaaa.com.au.гг» target-'_Ыапк”>аАаа. com.au. rr.ggg .ggg не считается частью URL. Количество последовательностей символов через точку ограничено, чтобы не захватить в URL следующий за ним текст. Это интуитивное ограничение. Преобразование FTP-, HTTP- и e-mail ссылок в теги HTML Если в тексте могут присутствовать адреса электронной почты, то наша задача усложняется, поскольку ссылка через прокси-сервер http://www.proxy.com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ может трактоваться неоднозначно из-за наличия в ней символа Если мы сначала будем искать адреса электронной почты, то программа может «найти» такой e-mail: 80@www. site.com Конфликт также может возникнуть со ссылками вида ftp://login:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/www/ Чтобы устранить этот конфликт, перепишем регулярные выражения для поиска URL и добавим к ним регулярное выражение для поиска e-mail. Форматировать ссылки будем несколькими операторами подстановки, т.к. для одного оператора эта задача слишком сложна. Вот текст всей этой программы: #!perl -w use strict; my $wb=’(?![A-Za-zO-9])’; my $protocol='(?:(?=[FfHh])(?i:http(?>s?)I ftp)://)’; my $host=< )* HOST my $subdom=«SUBDOM; ). )+ SUBDOM my $subdom1='[A-Za-zO-9](?:[-A-Za-zO-9]{0,61}[A-Za-zO-9])?’; my $zone=«ZONE; (?1: ) ) (?>.[a-z]{2}$wb)7 ) ZONE my $port=”(?::d{1t5}$wb)”; my $tall=«TAIL; )* (?
) TAIL my $firstchr='(?:[A-Za-zO-9])'; my $namechr=’(?:[A-Za-zO-9_+.-])'; my $ip=’(?:(?d{1,3}).(?>d{1,3}).(?>d{1,3}). (?>d{1,3})(?!d))’; tl Login и passw ограничены 32 символами my $loginpasswat-'(?:(?>[A-Za-zO-9_]{1,32})(?>(?:: [A-Za-zO-9_]{1,32})?)@)'; my Ires; $_=q(http://www.proxy.com:80@www. site.com/ Ftp://a.com/AAa Ftp://Login:PasswaWww.Aaa.Com/Www/ Ftp://login:pas
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/www/ Ma1Ito:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
со. Mailto:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
eee.co.
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
biu@ddd. com bibi@ddbld. com bibisbi-sf. f f. com. com@ddd. com ыы$ы
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
Mailto:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
,
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
mblblbl aaa.Bb,
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
aaa.museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ http://proxy.com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ http://proxy,com@site,com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk 4>f о rum. abode. ru www.Eabcd. ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/que ry/vid.cam.dig/sony.dcrhc15.htmftfull_image Ф, Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? 1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h, .0.2.3.400. http://66.123.234.555/ddd
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:Olympus.flexiblesoft. com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a ); tt Оформляем ссылки без login :passw s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-zO-9])|(?(?>$port?(?>@$host$zone(?![A-Za-z0-9_.-]*@))?)?))($tail?)#$res=$2 ? ‘' : ‘http://’; qq!$1$3!#gex; # Оформляем ссылки c login:passw s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#,’_blank,’>$1$2$3$4«#gx; ft Оформляем е-мейлы. Этот оператор чувствителен к тексту, на ft который меняет предыдущие операторы! s#((?$namechr{0,39})@(?>$subdom1)(?:.$su bdoml)?.$zone)(?! (?>[As»<]*)(?:"starget=,’_blank’,>|))ff$1#gx; ft Оформляем ссылки c IP s#((?/])$ip(?>$port?))($tail?)#»http://L$1E$2” target=’'_blank”>$1 $2«#gx; print $_; А вот результат ее работы: <а href=’’http ://www.proxy.com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/” target="_blank”>http://www.proxy.com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ Ftp: //a. com/AAa Ftp://Login: Passw@Www. Aaa. Com/Www/" Ftp://login:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/www/« Mai Ito: aaa@sss. zzz. co. MaiIto:aaa@sss. zzz.eee.co.
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
ыызы
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
bibisbi
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
MaiIto:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
, aaa@sss. сота>ЫЫЫ aaa.Bb.
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
aaa.museumm Look at:aaa.museum. https://aaa.museumm,
target="_blank”>http://www. proxy. com:80@www. site.com/ http://proxy, com:
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ http://proxy.
Этот адрес e-mail защищен от спам-ботов. Чтобы увидеть его, у Вас должен быть включен Java-Script
/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Ф<а href="http://forum.abcde. ru” target=”_blank’'>forum. abode, ru <а href=”http://www.eabcd.ru” target="_blank’,>www. Eabcd. ru http://Eabcd.Ru Ahttp://www. Eabcd.ru/AAa http://abc.ru/query/vid. cam.dig/sony. dcrhc15.htm#full_ image Ф.<а href=”http://www.abedefg-avto. ru» target="_blank">Www. abedefg-avto,ru http://1.2.3.400/aaa/ddd.exe" target=,’_blank">httP://1.2. 3.400/aaa/ddd.exe? "1 .2.3.400/aaa/ddd.exe?d=c,f=t;&e=h", . “0.2.3.400”. http://66.123.234.555/ddd” target=”_blank">http://66.123. 234.555/ddd mailto:michel@ab-cdefg. ru’’>michel@>ab-cdefg. ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:Olympus. flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a” target=’'_blank">http://99.999.999.999/sea rch?q=cache:w5K8GsupwvcJ: Olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client= firefox-a Как видим, в этом тестовом тексте программа правильно отделила е-mail ссылки от остальных ссылок.