Андрей Сорокин дата публикации 20-03-2000 00:00 Текст с высоты птичьего полета или Регулярные выражения
"Look for a white shirt and a white apron," said the head which had
been put together, speaking in a rather faint voice. "I'm the cook."
L. Frank Baum, The Emerald City of Oz
При решении прикладных задач, полезно рассматривать их с
высоты "птичьего полета". Многие знают что это
может существенно ускорить разработку, но не многие этим
пользуются.
Разница в посимвольной обработке строк и обработке с помощью
регулярных выражений в том, что в первом случае Вы думаете
прежде всего как достичь цели, а во втором - а какая цель
Вам собственно нужна ? %-) Кроме того, посимвольные алгоритмы
трудно модифицировать, не говоря уж о том, что любая модификация
сопровождается перекомпиляцией приложения.
В этой небольшой статье собрано несколько иллюстраций
использования регулярных выражений в Delphi.
- Прим.
-
- Если для Вас приведенные примеры выражений выглядят как древнеегипетские письмена, то ознакомьтесь
с описанием их синтаксиса в любой книге о Perl или на
http://regexpstudio.com/RU/TRegExpr/Help/RegExp_Syntax.html.
Они гораздо проще чем кажутся !
- Для компиляции этих примеров достаточно добавить
regexpr.pas в список файлов проекта и вписать
'uses regexpr;' в юниты, где Вы используете регулярные выражения.
Предположим, Вам необходимо выманить ;) у пользователя адрес
его электронной почты (моральную сторону и маркетинговую
обоснованность подобной затеи мы здесь рассматривать не будем).
Идея в том, что если отвергать синтаксически некорректные
адреса, то большинству пользователей надоест играть в эту
орлянку и они либо откажутся от Вашей программы / уйдут с
web-страницы, либо введут синтаксически корректный адрес.
А как рядовому юзеру проще всего ввести такой адрес ?..
Правильно ! Проще всего ввести свой реальный e-mail !
Естественно, что вариант с
p := Pos ('@', email);
if (p > 1) and (p < length (email))
then ...
проблемы не решает. Желательно как минимум просмотреть
строку на предмет отсутствия некорретных символов а также
наличия домена второго (или выше) уровня. Конечно, любой
программист напишет такой анализатор... строк этак на *дцать
и с перспективой перекомпилировать программу если что-то не
впишется в эту проверку.
А теперь забудьте о посимвольной обработке и посмотрите на
этот же анализатор, упрятанный в одну строку :
if ExecRegExpr ('[\w\d\-\.]+@[\w\d\-]+(\.[\w\d\-]+)+', email)
then ... gotcha! ...
Регулярные выражения позволяют гибко реализовать достаточно
изощренные проверки. Вот, скажем абсолютно корректная проверка
на ... римские цифры любой величины (шаблон позаимствован
из книги "Mastering Perl"):
const
Mask1 = '^(?i)M*(D?C{0,3}|C[DM])(L?X{0,3}|X[LC])(V?I{0,3}|I[VX])$';
...
if not ExecRegExpr (Mask1, DBEdit1.Text) then begin
... show error message ...
DBEdit1.SetFocus;
end;
К оглавлению
В последнее время появилось неимоверное число программок,
вылущивающих информацию из web-страниц. Так вот, на мой взгляд
это гораздо разумнее делать с помощью регулярных выражений.
Не изобретайте велосипед, используйте метро ! 8-)
Например вот таким нехитрым способом можно получить курс
доллара и дату этого курса программно, не рассматривая
рекламные баннеры (да простят меня CityCat и ФинМаркет ;) ).
Бросьте на форму TBitBtn, TLabel и TNMHTTP (TNMHTTP здесь
использован исключительно для упрощения примера. Использовать
эту гадость в реальной жизни не советую :-E~ ) и вставьте такой
код обработки нажатия BitBtn1:
procedure TForm1.BitBtn1Click(Sender: TObject);
const
Template = '(?i)Официальный курс ЦБ по доллару'
+ '.*Дата\s*Курс\s*Курс пок.\s*Курс прод. [^<\d]*'
+ '(\d?\d)/(\d?\d)/(\d\d)\s*[\d.]+\s*([\d.]+)';
begin
NMHTTP1.Get ('http://win.www.citycat.ru/finance/finmarket/_CBR/');
with TRegExpr.Create do try
Expression := Template;
if Exec (NMHTTP1.Body) then begin
Label1.Caption := Format ('Курс на %s.%s.%s: %s',
[Match [2], Match [1], Match [3], Match [4]]);
end;
finally Free;
end;
end;
В этом примере используется очень мощный механизм backtrack,
отличающий NFA (non-deterministic finite state machine)
реализацию регулярных выражений от DFA (deterministic
finite state machine). В случае с NFA (на базе которого
построен и TRegExpr) мы получаем возможность работать с
подвыражениями, что и использовано в примере выше для
выделения из шаблона элементов даты и собственно курса.
Кстати, здесь уже проявляются и ограничения регулярных
выражений (см. Панацея ?).
Решая подобную задачу, я бы предварительно обработал текст:
убрал бы незначимые тэги (ИМХО для надержного анализа
достаточно оставить только табличные тэги), из оставшихся
тэгов убрал бы все модификаторы (size, align и т.п.),
убрал бы все переводы строк, а табуляции заменил на
пробелы и убрал после этого повторяющиеся пробелы.
После этого можно уже написать гораздо более надежное
регулярное выражение.
А вот так можно достаточно надежно вынуть из неформализованного
текста все Санкт-Петербургские номера телефонов (представленные
как '(812)123-4567' или '+7 (812) 12-345-67' и т.д., причем
извлечены будут внутригородские части номеров):
procedure ExtractPhones (const AText : string; APhones : TStrings);
begin
with TRegExpr.Create do try
Expression := '(\+\d *)?(\((\d+)\) *)?(\d+(-\d*)*)';
if Exec (AText) then
REPEAT
if Match [3] = '812'
then APhones.Add (Match [4])
UNTIL not ExecNext;
finally Free;
end;
end;
К оглавлению
Необходимо некий текст отобразить в html-странице, но предварительно желательно выделить
гиперссылками все встречающиеся в нем URL.
Вот пример реализации (он не всегда сработает, но ведь 100% распознавание даже
теоретически невозможно, да и в такого рода задачах не страшно если что-то не будет
найдено. Страшно впустую тратить время на вспомогательные по сути вещи):
type
TDecorateURLsFlags = (
// Включаемые в видимую часть гипер-ссылки поля
durlProto, // Протокол ('ftp://' или 'http://')
durlAddr, // IP-адрес или символическое имя домена
durlPort, // номер порта (например ':8080')
durlPath, // путь (unix-формат)
durlBMark, // объект внутри страницы (напрмер '#bookmark')
durlParam // параметры запроса (например '?ID=13&User=Pupkin')
);
TDecorateURLsFlagSet = set of TDecorateURLsFlags;
function DecorateURLs (const AText : string; AFlags : TDecorateURLsFlagSet = [durlAddr,
durlPath]) : string;
const
URLTemplate =
'(?i)' // регистро-независимый режим
+ '('
+ '(FTP|HTTP)://' // Протокол
+ '|www\.)' // Позволяет отловить ссылки указанные без 'http://'
+ '([\w\d\-]+(\.[\w\d\-]+)+)' // IP-адрес или символическое имя домена
+ '(:\d\d?\d?\d?\d?)?' // номер порта
+ '(((/[%+\w\d\-\\\.]*)+)*)' // путь (unix-формат)
+ '(\?[^\s=&]+=[^\s=&]+(&[^\s=&]+=[^\s=&]+)*)?' // параметры запроса
+ '(#[\w\d\-%+]+)?'; // объект внутри страницы
var
PrevPos : integer;
s, Proto, Addr, HRef : string;
begin
Result := '';
PrevPos := 1;
with TRegExpr.Create do try
Expression := URLTemplate;
if Exec (AText) then
REPEAT
s := '';
if CompareText (Match [1], 'www.') = 0 then begin
Proto := 'http://';
Addr := Match [1] + Match [3];
HRef := Proto + Match [0];
end
else begin
Proto := Match [1];
Addr := Match [3];
HRef := Match [0];
end;
if durlProto in AFlags
then s := s + Proto; // Match [1] + '://';
if durlAddr in AFlags
then s := s + Addr; // Match [2];
if durlPort in AFlags
then s := s + Match [5];
if durlPath in AFlags
then s := s + Match [6];
if durlParam in AFlags
then s := s + Match [9];
if durlBMark in AFlags
then s := s + Match [11];
Result := Result + System.Copy (AText, PrevPos,
MatchPos [0] - PrevPos) + '<a href="' + HRef + '">' + s + '</a>';
PrevPos := MatchPos [0] + MatchLen [0];
UNTIL not ExecNext;
Result := Result + System.Copy (AText, PrevPos, MaxInt); // Tail
finally Free;
end;
end; { of function DecorateURLs -------------------------------}
Обратите внимание, что в приведенном выше примере Вы имеете возможность легко выделять из
URL протокол, домен, путь и параметры запроса (см. параметр AFlags).
К оглавлению
Возможно, в этом месте уже не лишним будет умерить
пыл энтузиастов, в особенности тех, кому случалось
использовать Перл.
Дело в том, что Перл - интерпретирующий язык. Основное
следствие из этого - чем меньше операторов выполняется,
тем быстрее (как правило) работает программа. В большистве
случаев регулярное выражение отработает быстрее чем самый
элементарный посимвольный анализ строки.
Поэтому, не кажется диким реализация функции Trim
как выражения '^\s*(\S*)\s*$'.
Думаю, не надо объяснять насколько это глупо в истинно
компилируемом Паскале. Так что, если анализируемая строка
имеет простую структуру - напишите элементарный и очень
быстрый цикл по ее разбору и не связывайтесь с регулярными
выражениями.
Кроме того, не рекомендую использовать регулярные выражения
там, где нужен полноценный парсер. Если, например, Вам
нужно разобрать на теги HTML - поищите для этого более
подходящий инструмент !
Если же искомая или проверяемая строка имеет сложную
структуру, если эта структура может меняться, тогда
это наш клиент ;) Если же описание должно меняться
без перекомпиляции программы, то серьезной альтернативы
регулярным выражениям практически нет.
К оглавлению
Успехов !
Да, чуть не забыл, библиотека которая устраняет досадную
забывчивость разработчиков Delphi и позволяет использовать
в Delphi регулярные выражения без необходимости таскать
за собой какие-либо DLL, лежит здесь:
http://regexpstudio.com/RU/.
Андрей Сорокин
Специально для Королевства «Delphi»
[Регулярные выражения]
Обсуждение материала [ 29-09-2009 15:55 ] 20 сообщений |