Регулярные выражения в Delphi
Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: delphi | reg exp | программирование
Просмотров: 27531
Регулярные выражения – один из главных «инструментов» заядлых линуксоидов и WEB-программистов. Проверить введенные пользователем данные, быстренько и непринужденно пропарсить какую-нибудь html страницу, найти заковыристый фрагмент в большом куске текста – задачи, решаемые за несколько минут с помощью регулярных выражений. Многие программисты считают, что использовать их дано лишь гуру. Мы так не думаем, поэтому расскажем и покажем все самое необходимое, чтобы при виде вот таких наборов символов «[wd-.]+@([wd-]+(.[w-]+)+)» ты не смущался и не испытывал чувство дискомфорта в нижней части живота.
Немного истории
История регулярных выражений начинается в далеких 40-х годах. Двое нейрофизиологов, Уоррен Мак-Каллох и Уолтер Питтс, трудились в то время над моделированием работы нервной системы на нейронном уровне. Спустя несколько лет, математик Стивен Клин сумел описать эти модели с помощью алгебры и дал им имя – регулярные множества. Вот так, постепенно, регулярные выражения стали набирать популярность среди кодеров. Такие знаменитые люди как, например, Кен Томпсон написали множество статей на тему использования регулярных выражений для выполнения самых разнообразных задач. Итак, регулярные выражения – технология поиска текстовых фрагментов в электронных документах, соответствующих определенным правилам.
Основы основ
Перед тем, как начать использовать регулярные выражения стоит разобраться с некоторыми понятиями. Начнем с литералов. Литерал – это любой отдельный символ. Например: а, b, с, d – литералы. Думаю, с этим все ясно. Едем дальше. Из одних литералов кашу не сваришь, поэтому на помощь приходят метасимволы (специальные символы, которые выполняют какое-либо дополнительное действие). Наверняка тебе не раз приходилось использовать метасимволы при работе в командной строке (неважно, Windows ли эта консоль или Unix).
Например, чтобы вывести в Windows список файлов определенного каталога, в cmd можно воспользоваться командой dir. Как быть, если мне нужно отобразить все имена файлов, у которых расширение html и htm? Можно выполнить команду dir и сломать глаза, выискивая нужные файлы, а можно воспользоваться конструкцией dir *.htm? В этой записи присутствует два метасимвола – «*» и «?» Звездочкой мы указываем, на то, что имя файла может состоять из любых символов (литералов), затем мы определяем расширение и ставим знак вопроса, который указывает, что после m может ничего не быть или быть один любой символ. Для нашей задачи этого вполне достаточно.
Попробуем написать пробный пример. В поле для регулярного выражения мы напишем одно лишь слово «Пример», а в качестве пробного текста: «Это простой Пример использования регулярных выражений». Жмем на Exec и видим, как во входном тексте выделилось слово «Пример». После нажатия на пимпу «ExecNext» программой будет произведена попытка поиска следующего фрагмента текста, соответствующего шаблону. В нашем случае регулярное выражение больше не сработает, поскольку слова «Пример» больше нигде нет.
Шаблон получился очень простым. Если бы регулярные выражения предназначались только для этого, от них не было бы толку. Попробуем усложнить пример. В качестве входной строки определим: «Первый мой номер: +7-924-111-11-34, а второй +7-231-331-55-55». Наша с тобой задача будет найти в этом тексте все номера телефонов. Первое, что приходит на ум – это описать телефоны в качестве шаблона. К сожалению, этот способ не подойдет. Его нельзя назвать универсальным. Стоит изменить номер телефона и шаблон станет бесполезным. Введем в качестве шаблона: «+[0-9-]+». Попробуем запустить поиск. Работает? А почему? :). Для понимания вопроса мы для начала разобьем шаблон на части:
Остальные метасимволы, указанные в символьном классе теряют свою силу и становятся обычными литералами.
Рассмотрим такую задачку. Имеется две строки, в которой содержатся буквы и цифры. Задача состоит в том, что необходимо выбрать часть строки, в которой сначала идут подряд 3 латинские буквы, а за ними цифры в интервале от 3 до 6. Строки следующие:
abdajD345bhad5124jjdaabc3456 abdj23456kfej3456fe
Попробуй решить данную задачку самостоятельно, на основе полученных знаний. Мое решение будет выглядеть так: «[a-z]{3}[3-6]{4}». Наиболее удобным способом решения данной задачи является установка ограничений на количество находимых символов. Итак, нам нужно найти 3 латинских буквы. Можно, конечно, три раза записать класс [a-z], а можно просто в фигурных скобках указать их количество. Это я и сделал. Следующим условием является цифры от 3-6. Указываем их в классе, не забыв про количество. Вот и все. В результате поиска по данному шаблону в первой строке будет выбраны «abc3456», а во второй «fej3456».
Регулярные выражения в Delphi
В Delphi нет модуля/компонента для работы с регулярными выражениями. На мой взгляд, это существенное упущение Borland (теперь - Code Gear Embarcadero). Так считаю не только я, но и тот перец, который закодил класс, благодаря которому перед нами открываются безграничные возможности использования регепсов. Итак, перед тем как переходить к рассмотрению практических примеров, потрудись скачать с сайта разработчиков (http://www.regexpstudio.com) архив с модулем и примерами. Можешь не бегать так далеко, а просто взять наш диск и все слить с него. Подключив к своему проекту RegExpr (именно так он называется), тебе становится доступным для создания новый объект типа TRegExpr, который обладает следующими свойствами и методами .
Собираем свой антиспам-лист
В качестве первого практического примера я решил сделать что-нибудь крайне полезное. Первое, что мне пришло в голову, написать тулзу для выдирания е-mail адресов с html страниц, чтобы точно знать, на какие мыльники ты никогда в жизни не соберешься послать нежелательную корреспонденцию. Пожалуй, наша программа будет уметь не просто грабить мыльники, но и собирать по нашему приказу и линки (чтобы их впоследствии не посещать). В качестве входных параметров софтина будет получать путь к папке, в которой хранятся html и htm файлы. Итак, включаемся в процесс.
Как обычно, создаем в Delphi новый проект и сразу же подключаем недавно скачанный нами модуль. После этого нарисуем простенькую форму, внешний вид которой ты можешь наблюдать на соответствующей иллюстрации.
Принцип действия примера будет следующим. По нажатию кнопки перед пользователем должен появляться диалог выбора директории. После выбора каталога, управление передается самописной процедуре FindFiles()
. Ее код приведен в соответствующей врезке.
Код процедуры FindFiles()
var
_se:TSearchRec;
begin
//если в директории для поиска отсутствует слеш, то нужно его добавить
if dir[length(dir)]<>'' then
dir:=dir+'';
//начинаем поиск
if FindFirst(dir+'*.htm?', faAnyFile, _se)=0 then
repeat
findMailsUrls(dir+_se.Name, mode);
until FindNext(_se)<>0;
//если нашли поддиректорию, то начинаем поиск в ней.
if FindFirst(dir+'*.*', faDirectory, _se)=0 then
begin
repeat
if ((_se.Attr and faDirectory)=faDirectory) and (_se.Name[1]<>'.') then
FindFiles(dir+_se.Name+'', mode);
until FindNext(_se)<>0;
FindClose(_se);
end;
В этой процедуре реализован алгоритм рекурсивного поиска файлов по маске. Для поиска используется функция FindFirst()
, в качестве параметров которой нужно передать:
TSearchRec
, в которую попадут результаты поиска. Для прохода по всем вложенным папкам используется рекурсия (вызов процедурой самой себя). Если вместо директории нашелся нужный файл, то значит, можно смело передавать работу процедуре findMailsUtils()
, которая в зависимости от последнего параметра, будет искать либо мыльники, либо url’ы. Код процедуры findMailUtils приведен во врезке, из названия которой ты никогда не догадаешься о ее содержимом :).Код процедуры findMailUtils
var
_tempFile:TStringList;
_regexp:TRegExpr;
i, b:integer;
begin
//Инициализируем объекта для работы с регулярными выражениями
_regexp:=TRegExpr.Create;
//Устанавливаем шаблон поиска в зависимости от условия.
case mode of
//Будем искать мыльник
0: _regexp.Expression:='[wd-.]+@([wd-]+(.[w-]+)+)';
//Будем искать url адрес
1: _regexp.Expression:='(http|ftp)://([wd-]+(.[wd-]+)+)(([wd-=?\./]+)+)*';
End;
_tempFile:=TStringList.Create;
_tempFile.LoadFromFile(file_name);
ProgressBar1.Max:=_tempFile.Count;
b:=StrToInt(CountMailLAbel.Caption);
for i:=0 to _tempFile.Count-1 do
begin
progressBar1.Position:=i;
if (_regexp.Exec(_tempFile.Strings[i])) then
repeat
ResultMemo1.Lines.Add(_regexp.Match[0]);
Inc(b);
CountMailLabel.Caption:=IntToStr(b);
until not _regexp.ExecNext;
end;
//Освобождаем память
_regexp.Free;
_tempFile.Free;
Взглянем на вторую врезку. Перед использованием объекта для работы с регулярными выражениями, его нужно инициализировать, после которой можно приступать и к составлению регулярного выражения. Поскольку мы собираемся закодить более-менее универсальную тулзу, придется проверить передаваемый в процедуру параметр mode. Значение 0 будет свидетельствовать о том, что нам требуется распотрошить файлы на предмет мыльников, а 1 будет означать, что нужны только url-адреса. Для отлова e-mail адресов я устанавливаю вот такой шаблон [wd-.]+@([wd-]+(.[w-]+)+). На первый взгляд он абсолютно непонятен. Давай разбираться:
Вот и все, одной строчкой мы описали шаблон для поиска мыльника, не правда ли классно? Думаю, что да, но не стоит забывать, ведь если mode = 1, то значит, нужно искать url’ы. Шаблон для определения ссылки выглядит более громоздко: (http|ftp)://([wd-]+(.[wd-]+)+)(([wd-=?\./]+)+)*. Опять же, попробуем разобраться с его внутренностями.
Еще один полезный примерчик
Представим себе ситуацию. Ты нашел себе базу номеров своего оператора. Только вот незадача – она вся хранится в обычном текстовом файле. Было бы здорово перегнать все эти данные в какую-нибудь БД и потом сортировать, производя поиск удобными средствами. Для перегонки (блин, крутое слово, напоминает мне о деревенском самогоне) можно написать и отладить свой алгоритм, но лучше и проще воспользоваться регулярными выражениями. Итак, новая задачка. У нас имеется текстовый файл с записями вида: «Василий Петрович 12.02.1975 +7-912-455-24-14». Наша цель - разделить информацию в строке и записать в колонки: Имя, Фамилия, Дата рождения, номер телефона. Для решения поставленной задачи я создал в своем проекте еще одну закладку и придал ей следующий вид (см. картинку).
По нажатию кнопки, предназначенной для открытия файла, накатай код из правильной врезки (а правильная врезка называется «перегонный куб») и возвращайся к тексту статьи за объяснениями.
Перегонный куб
var
_regexp:TRegExpr;
_tempFile:TStringList;
I:Integer;
begin
if not (OpenDialog1.Execute) then
Exit;
ListView1.Items.Clear;
Edit2.Text:=OpenDialog1.FileName;
_regexp:=TRegExpr.Create;
_regexp.Expression:='([^s]+)s([^s]+)s([d.]+)s([d+-]+)';
_tempFile:=TStringList.Create;
_tempFile.LoadFromFile(OpenDialog1.FileName);
for i:=0 to _tempFile.Count-1 do
begin
_regexp.Exec(_tempFile.Strings[i]);
if (_regexp.Exec) then
with ListView1.Items.Add do
begin
Caption:=_regexp.Match[1];
SubItems.Add(_regexp.Match[2]);
SubItems.Add(_regexp.Match[3]);
SubItems.Add(_regexp.Match[4]);
end;
end;
_regexp.Free;
_tempFile.Free;
Как и в прошлом примере, перед тем, как использовать объект TRegExpr его нужно инициализировать, а уже потом присвоить текст регулярного выражения. Для решения данной задачки можно составить вот такой ([^s]+)s([^s]+)s([d.]+)s([d+-]+) шаблон. Как обычно, остановимся на тексте шаблона подробней.
Итог
Использовать регулярные выражения удобно и не так сложно, как может показаться на первый взгляд. Сегодняшние простые, но в тоже время полезные примеры - лишнее тому подтверждение. К сожалению, в рамках одной статьи мы не можем рассказать тебе все о регулярных выражениях. Тема настолько большая, что для ее изучения потребуется прочитать немало умных книг. Мы в тебя верим, и знаем, что при особом желании ты во всем разберешься. Ну а пока можешь задавать свои вопросы мне на мыло.
Статья опубликована журнале "Хакер" (http://xakep.ru). Ноябрь 2007 г.
Ссылка на опубликованную статью сайта издания: http://goo.gl/X3VZyc
Ссылка на журнал: http://goo.gl/L4Hp90