Регулярні вирази в Delphi
- Трішки історії
- основи основ
- Регулярні вирази в Delphi
- Збираємо свій антиспам-лист
- Код процедури FindFiles ()
- Код процедури findMailUtils
- Ще один корисний прімерчік
- Перегінний куб
- підсумок
Регулярні вирази - один з головних «інструментів» завзятих линуксоидов і 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Спробуй вирішити це завдання самостійно, на основі отриманих знань. Моє рішення буде виглядати так: «[az] {3} [3-6] {4}». Найбільш зручним способом вирішення даного завдання є установка обмежень на кількість знаходять символів. Отже, нам потрібно знайти 3 латинських букви. Можна, звичайно, три рази записати клас [az], а можна просто в фігурних дужках вказати їх кількість. Це я і зробив. Наступним умовою є цифри від 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 (), в якості параметрів якої потрібно передати:
Код процедури 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
Як бути, якщо мені потрібно відобразити всі імена файлів, у яких розширення html і htm?Htm?
У цьому записі присутній два метасимвола - «*» і «?
Працює?
А чому?
Htm?
Wd - =?
Ось і все, одним рядком ми описали шаблон для пошуку Мильники, чи не так класно?
Wd - =?
Wd - =?