Модераторы: Poseidon, Snowy, bems, MetalFan

Поиск:

Добавить материал
 

Регулярные выражения в Delphi
kemiisto
Репутация: 16
Всего: 160

Профиль
Быстрая цитата Цитата
Теги:
Вторая редакция статьи. Исправлены ошибки, синтаксис регулярных выражений описан в более распространенных терминах. Огромное спасибо source777 за помощь в подготовке статьи! smile 

Последнюю редакцию статьи всегда можно получить по ссылке.

Введение
Регулярные выражения (regular expressions) — современная технология поиска текстовых фрагментов в электронных документах, соответствующих определенным образцам. Правила (rules) — это новое название регулярных выражений. Именно так они именуются в последней версиии языка Perl, признанного лидера по работе со строками.
Образец (pattern), задающий правило поиска, по-русски также иногда называют «шаблоном»«маской»
Регулярные выражения произвели прорыв в электронной обработке текста в конце XX века. Сейчас регулярные выражения используются многими текстовыми редакторами и утилитами для поиска и изменения текста на основе выбранных правил. Многие языки программирования уже поддерживают регулярные выражения для работы со строками. На моё удивление, в Delphi не оказалось встроенного модуля/компонента для работы с регулярными выражениями. Это существенное упущение разработчиков Delphi. Однако, как известно, свято место пусто не бывает! Итак, знакомьтесь, TRegExpr — класс для работы с регулярными выражениями в Delphi. Берём с официального сайта архив с модулем и примерами.

Составные части регулярных выражений
  • Символы.
  • Cимвольные классы (перечни символов + метасимволы - границы слов).
  • Фиксирующие директивы (метасимволы - разделители строк).
  • Квантификаторы (метасимволы - повторения).
  • Альтернативы (метасимволы - варианты).
  • Группировки (метасимволы - подвыражения).
  • Модификаторы.
Символы
Любой символ совпадает с самим собой (если только он не относится к метасимволам). Любая последовательность символов совпадает с такой же во входной строке. Чтобы представить метасимволы ., - [ ] и другие в регулярных выражениях без интерпретации, то есть, в качестве простых (не специальных) символов необходимо предварить их обратной косой чертой: \. Этот приём называется защитой метасимволов. Например, чтобы представить сам символ «точка» (просто точка, и ничего более), надо написать \. (обратная косая черта, а за ней - точка). Сам метасимвол \ тоже может быть защищен, то есть представлен как \\ (две обратных косых черты), и тогда интерпретатор регулярных выражений воспримет его как простой символ обратной косой черты \. Также можно использовать escape-последовательности, например:
Код

\t // табуляция
\n // новая строка 


Символьные классы
Символьный класс — просто конечный набор символов. Он ограничивается квадратными скобками и содержит перечисление символов, которые можно вместо него подставить. Заменяется он всего на один символ, входящий в это перечисление. Примеры:
Код

[абвгде]    // простое перечисление символов
[а-яА-Я]    // все русские буквы
[0-9a-z]    // цифры и строчная латиница
[^0-9]      // все символы, кроме цифр
  • Можно использовать - (дефис) для указания диапазонов символов. 
  • Если первый символ класса (идущий сразу после открывающейся квардратной скобки) — ^, то это означает символ, который отсутствует в данном перечислении. Фактически префикс ^ инвертирует список; вместо того, чтобы перечислять символы принадлежащие классу, мы перечисляем символы, не входящие в него.
  • Некоторые популярные символьные классы имеют короткую запись.
Стандартные символьные классы

user posted image


Фиксирующие директивы
Фиксирующие директивы — это символы, которые привязывают правило к некоторому признаку. Например, к концу или началу строки. Наиболее часто используемые:
Код

^    // начало строки  
$    // конец строки


Квантификаторы
Показывают, сколько раз может повторяться предыдущий символ (символьный класс, альтернатива и т.д.) Ограничиваются парой фигурных скобок. Примеры:
Код

\w{3}           // три латинских буквы или цифры
\d{1, 3}        // одна, две или три цифры
[а-яА-Я]{3, }   // русское слово длинной три символа и больше

Квантификаторы
  • С одним параметром называются точными и указывают точное количество повторений. 
  • С двумя агрументами называются конечными и указывают конечный диапазон, в котором варьируется количество повторений. 
  • Без второго параметра (но с запятой) называются бесконечными и ограничивают количество повторений лишь снизу. 
  • Некоторые популярные имеют короткую запись.
Короткие записи популярных квантификаторов

user posted image

Жадность
Речь пойдёт о жадности среди квантификаторов. квантификаторам в регулярных выражениях соответствует максимально длинная строка из возможных (они являются «жадными», greedy). Это может оказаться значительной проблемой. Например, часто ожидают, что выражение <.*> найдёт в тексте теги HTML. Однако этому выражению соответствует целиком строка

<p><b>Википедия</b> — свободная энциклопедия, в которой <i>каждый</i> может изменить или дополнить любую статью</p>

Эту проблему можно решить двумя способами. Первый состоит в том, что в регулярном выражении учитываются символы, не соответствующие желаемому образцу ([^>]* для вышеописанного случая). Второй заключается в определении квантификатора как "нежадного" ("ленивого", lazy), добавив после него знак вопроса.
Например, выражению <.*?> соответствует не вся показанная выше строка, а отдельные теги (выделены цветом):
 
<p><b>Википедия</b> — свободная энциклопедия, в которой <i>каждый</i> может изменить или дополнить любую статью</p>

"Жадные" варианты квантификаторов пытаются захватить как можно большую часть входного текста, в то время как "не жадные" - как можно меньшую. Например, 'b+' как и 'b*' примененные к входной строке 'abbbbc' найдут 'bbbb', в то время как 'b+?' найдет только 'b', а 'b*?' - вообще - пустую строку; 'b{2,3}?' найдет 'bb', в то время как 'b{2,3}' найдет 'bbb'.

Альтернативы
Нужны, когда необходимо объединить несколько правил в одно. При этом совпадение засчитывается, когда есть совпадение хотя бы с одним правилом. Желательно альтернативы заключать внутрь группировки (круглые скобки). Правила, входящие в вариант, разделяются | (вертикальной чертой). Примеры:
Код

(жы|шы)            // или "жы", или "шы"
([a-zA-Z]+|[а-яА-Я]+)    // или слово на латинице, или русское

В данном примере продемонстрирована альтернатива в группировке. В принципе альтернатива может существовать и вне группировки, но так возникает больше ошибок.

Группировки
Используются, когда необходимо обрабатывать результат частями. Например, при обработке ссылок в HTML-документе удобно отдельно обрабатывать текст ссылки и URL. Группировки заключаются в круглые скобки.

Модификаторы
Модификаторы предназначены для изменения поведения правила. Назначение и примеры -  смотри в справке.

Использование в Delphi
Рассмотрим несколько примеров использования регулярных выражений в Delphi.

Пример использования
Использовать регулярные выражения в Delphi просто.
  • Распаковываем архив в любой каталог.
  • Добавляем RegExpr.pas (размещен в подкаталоге Source) в список файлов нашего проекта (главное меню Delphi Project -> Add to project...)
  • Используем класс TRegExpr в нашем проекте. Не забудьте добавить 'uses RegExpr' в соотв. модули проекта.
Итак, небольшой пример. Попробуем найти в строке все целые числа. Регулярное выражение для этого случая будет выглядеть так: -?\d+, т.е. содержать соответствующий символьный класс \d (т.е. цифры) и квантификатор +, означающий любое количество. Думаю, то, что целое число есть произвольное количество цифр понятно. ;-) Также перед числом возможно будет стоять знак «минус». Остальное должно быть понятно из коментариев.
Код

// Очень простой пример - извлечение чисел из введённой строки.
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  // добавляем нужный модуль
  RegExpr in 'RegExpr.pas';

var
  // нам необходим экземпляр класса TRegExpr
  RegExp: TRegExpr;
  s: string;
  
begin
  // выводим запрос и считываем строку
  Write('Enter a string containing numbers: ');
  Readln(s);

  // создаём объект
  RegExp := TRegExpr.Create;

  // гарантирует освобождение занятой объектом памяти
  try
    // регулярное выражение находится в свойстве Expression
    RegExp.Expression := '-?\d+';
    // ищем первое совпадение с помощью функции
    // Exec(const AInputString : string) : boolean, которая вернет true,
    // если в строке AInputString будет найдено совпадение c
    // регулярным выражением, хранящимся в свойтве Expression
    if RegExp.Exec(s) then
    // если находим
    begin
      Writeln('Entered string contains numbers: ');
      repeat
      // выводим найденное выражение, которое хранится в Match[0]
        Writeln(RegExp.Match[0]);
      // и продолжаем поиск
      until not RegExp.ExecNext;
    end
    else
    // иначе - сообщаем, что ничего не нашли
      Writeln('Entered string contains no numbers!');
  finally
    RegExp.Free;
  end;
  Readln;
end.

Результат работы программы:
user posted image

Пример использования вариантов и подвыражений
Код

// Чуть более сложный пример - извлечение целой и дробной части числа.
// Регулярное выражение содержит варианты и подвыражения.
program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  // добавляем нужный модуль
  RegExpr in 'RegExpr.pas';

var
  // нам необходим экземпляр класса TRegExpr
  RegExp: TRegExpr;
  s: string;
  
begin
  // выводим запрос и считываем строку
  Write('Enter a string containing numbers: ');
  Readln(s);

  // создаём объект
  RegExp := TRegExpr.Create;

  // гарантирует освобождение занятой объектом памяти
  try
    // регулярное выражение находится в свойстве Expression
    RegExp.Expression := '(\d+)([.,])(\d+)';
    if RegExp.Exec(s) then
    // если находим
    begin
      Writeln('Whole number: ', RegExp.Match[0]);
      Writeln('Integer part: ', RegExp.Match[1]);
      Writeln('Divider : ', RegExp.Match[2]);
      Writeln('Fractional part: ', RegExp.Match[3]);
    end
    else
    // иначе - сообщаем, что ничего не нашли
      Writeln('Entered string doesn''t contain any number!');
  finally
    RegExp.Free;
  end;
  Readln;
end.

Регулярное выражение для этого случая будет выглядеть несколько сложнее: (\d+)([.,])(\d+), т.е. содержать соотв. подвыражения (целая и дробная части) и вариант (в качестве разделителя может выступать как точка, так и запятая), который также представляет собой подвыражение. 
Выражение, соотв. всему регулярному выражению по-прежнему находится в Match[0], а вот Match[i] содержит i-ую группировку. 
Также обратите своё внимание на достаточно интересную функцию 
Код

function Substitute (const ATemplate : string) : string;

которая возвращает ATemplate, в котором все '$&' и '$0' заменены на найденное регулярное выражение, а '$n' на n-ое подвыражение. Например, оператор 
Код

Writeln(RegExp.Substitute('$1$2$3'));

добавленный в предыдущий пример, вывел бы исходное число.

Пример анализа текста
Давайте напишем простенький анализатор текста. На это раз сделаем GUI-приложение. На форме расположим один экземпляр TButton, один экземпляр TMemo и пять  экземпляров TLabel. 
user posted image

При нажатии на кнопку, реализуем показ диалога выбора текстового файла и загрузку его в Memo1. 
Код

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
end;

Теперь давайте реализуем сбор статистики по мере изменения текста в Memo1:
Код

procedure TForm1.Memo1Change(Sender: TObject);
var
  Count, i: Integer;
  RegExp: TRegExpr;
begin
  RegExp := TRegExpr.Create;

  // кол-во строк
  Label1.Caption := 'Строк: ' + IntToStr(Memo1.Lines.Count);

  // кол-во символов
  Label2.Caption := 'Символов: ' + IntToStr(Length(Memo1.Text));

  // кол-во символов, исключая пробельные
  Count := 0;
  RegExp.Expression := '\s';
  Count := Count + Length(RegExp.Replace(Memo1.Text, '', False));
  Label3.Caption := 'Непробельных символов: ' + IntToStr(Count);

  // кол-во слов
  Count := 0;
  RegExp.Expression := '\s*[^\s.-]+-?[^\s.-]*';
  if RegExp.Exec(Memo1.Text) then
  repeat
    Count := Count + 1;
  until not RegExp.ExecNext;
  Label4.Caption := 'Слов: ' + IntToStr(Count);

  // кол-во предложений
  Count := 0;
  RegExp.Expression := '[.!?]+(\s|$)';
  for i := 0 to Memo1.Lines.Count - 1 do
  if RegExp.Exec(Memo1.Text) then
  repeat
    Count := Count + 1;
  until not RegExp.ExecNext;
  Label5.Caption := 'Предложений: ' + IntToStr(Count);  
end;

Во-первых, остановимся на подсчёте количества символов, исключая пробельные. Здесь был использован метод
Код

function Replace (AInputStr: RegExprString; const AReplaceStr: RegExprString; AUseSubstitution: boolean = False): RegExprString; 

заменяющий в AInputStr все вхождения регулярного выражения на  AreplaceStr. Вычисляя сумму длин полученных таким образом строк, получим искомую величину.

Теперь взглянем на подсчёт количества предложений в нашем примере. Ответом на вопрос «Что надо найти?», будет, скорее всего, «Количество точек, знаков восклицания и знаков вопроса, стоящих в конце слова или строки.» Кроме того, следует учитывать возможность наличия неединичных знаков препинания (…, !?). Составляем регулярное выражение '[.!?]+(\s|$)', и собственно всё! Задача решена!

При подсчёте количества слов можно, например, использовать регулярное выражение вида '\s*[^\s.-]+-?[^\s.-]*. Слово может следовать за пробелом, а может и нет (если стоит в начале текста). Поэтому вначале нашеговыражения стоит \s*. Собственно слово (написанное без орфографических ошибок) — последовательность непробельных символов. То есть мы могли бы записать регулярное вражение так: '\s*[\S]'. Но под такой шаблон попадут и некоторые другие символы и их последовательности, которые врядли можно назвать словами — многоточие, тире. Чтобы исправить ситуацию мы пишем '\s*[^\s.-]'. Ну и, наконец, необходимо учесть наличие слов, которые пишутся через дефис и получим '\s*[^\s.-]+-?[^\s.-]*. 

Важно понимать, что основное преимущество при использовании регулярных выражений заключается в экономии времени! Вам необходимо лишь задаться вопросом «Что надо найти?» и составить регулярное выражение, которое является ответом на этот вопрос, а не разрабатывать «с нуля» алгоритм поиска.

Успехов!  smile 

Архив с примерами
Статья в PDF

P.S. Для тестирования регулярных выражений можнов воспользоваться инструментом RegExp Studio, взять который можно тут.

Это сообщение отредактировал(а) kemiisto - 7.2.2009, 20:37


Комментарии посетителей:


  Дата 25.5.2008, 10:58 (ссылка) |   (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата
MetalFan ****   Репутация: 62  Всего: 128 
Цитата(kemiisto @  24.5.2008,  23:11 Найти цитируемый пост)
Взглянем, например, на подсчёт количества предложений в нашем примере. Ответом на вопрос «Что надо найти?», будет, скорее всего, «Количество точек, знаков восклицания и знаков вопроса.» По крайней мере, это именно то, что мне пришло на ум. Составляем регулярное выражение '\.|\?|!', и собственно всё! Задача решена!

а если многоточие? )
ничего так статья, для начинающих самое то!
кстати, к самим RegExpr идет очень подробный и познавательный хелп на русском. многие ньансы там освещены.

Это сообщение отредактировал(а) MetalFan - 25.5.2008, 11:01
PM MAIL   Вверх

Дата 25.5.2008, 11:03 (ссылка) |    (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата
kemiisto ****   Репутация: 16  Всего: 160 
Цитата(MetalFan @  25.5.2008,  11:58 Найти цитируемый пост)
а если многоточие? )

MetalFan, согласен! Просто первое, что пришло на ум было '\.|\?|!'

Цитата(MetalFan @  25.5.2008,  11:58 Найти цитируемый пост)
ничего так статья, для начинающих самое то!

Спасибо! Рад стараться! smile 
PM MAIL WWW   Вверх

Дата 25.5.2008, 13:21 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
THandle Group Icon   Репутация: 65  Всего: 372 
kemiisto, хорошая статья, спасибо, начну изучать. Все понятно и хорошо написано smile 
PM WWW   Вверх

Дата 25.5.2008, 15:39 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
lukas **   Репутация: 3  Всего: 15 
Да регулярные выражение это крутая вещь... где я их только не использую... а на счет скорости работы функций из этого модуля... Все из них при обращении создают новый объект TRegExpr и исбавляются от него в конце, что и замедляет выполнение функции при большом кол-во использований... 
PM MAIL WWW   Вверх

Дата 25.5.2008, 17:12 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
Poseidon ****   Репутация: 53  Всего: 133 
PM MAIL ICQ   Вверх

Дата 25.5.2008, 21:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
lukas **   Репутация: 3  Всего: 15 
На этой странице проблемы с кодировкой, пробовал в 3х браузерах... скорее вот из за этого...

Код

<HTML xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">



PM MAIL WWW   Вверх

Дата 25.5.2008, 23:24 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
Poseidon ****   Репутация: 53  Всего: 133 
IE7 при установке в ручную "Кириллица (Windows)" нормально отображает
PM MAIL ICQ   Вверх

Дата 29.5.2008, 10:37 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
source777 ***   Репутация: 4  Всего: 56 
Цитата(kemiisto @  25.5.2008,  11:03 Найти цитируемый пост)
 согласен! Просто первое, что пришло на ум было '\.|\?|!'

Лучше уж заменить эту конструкцию на '[.!?](\s|$)', так будет намного точнее...

Добавлено через 3 минуты и 42 секунды
Вообще, я мельком только статью просмотрел, но даже при этом бросилось в глаза непонимание автором оператора | , очень во многих местах он используется абсолютно не к месту...
PM MAIL   Вверх

Дата 30.5.2008, 07:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Foux   Репутация: нет  Всего: нет 
Здесь рассмотрены примеры поиска конкретных символов,
но как получить символы находящиеся между определенных символов,
например содержимое HTML тэга?
PM MAIL   Вверх

Дата 30.5.2008, 13:27 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
source777 ***   Репутация: 4  Всего: 56 
Цитата(Foux @  30.5.2008,  07:35 Найти цитируемый пост)
как получить символы находящиеся между определенных символов,
например содержимое HTML тэга? 

'<a\s+href=(.*?)\s.*?>(.*?)</a>'
В итоге ссылка будет в RegExp.Match[1], а текст ссылки в RegExp.Match[2]. 
PM MAIL   Вверх

Дата 25.9.2008, 02:05 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата
kemiisto ****   Репутация: 16  Всего: 160 
source777, спасибо за feedback! Но, буду защищаться!

Цитата(source777 @  29.5.2008,  11:37 Найти цитируемый пост)
Вообще, я мельком только статью просмотрел, но даже при этом бросилось в глаза непонимание автором оператора | , очень во многих местах он используется абсолютно не к месту... 

Хм... За прошедшее время пролистал несколько книг по регулярным выражениям. Вот что, к примеру, пишет Джеффри Фридл
Цитата

Не путайте конструкцию выбора с символьным классом. Класс [abc] и конструкция выбора (a|b|c) фактически означают одно и то же, но этот пример не распростроняется на общий случай. Символьный класс совпадает ровно с одним символом, каким бы длинным или короткым не был список допустимых символов. С другой стороны, конструкция выбора может содержать альтернативы произвольной длинны, совершенно не связанные друг с другом длиной текста.

Я и не путаю. Мне пришло в голову записать '\.|\?|!', Вам - '[.!?]'. Это фактически одно и тоже. Где же тут, простите, "непонимание"? Да и, вообще, "к месту/ не к месту" определяет, по-моему, автор. 

Цитата(source777 @  29.5.2008,  11:37 Найти цитируемый пост)
Лучше уж заменить эту конструкцию на '[.!?](\s|$)', так будет намного точнее...

Да, лучше. Но я же чётко написал:
Цитата(kemiisto @  25.5.2008,  00:11 Найти цитируемый пост)
По крайней мере, это именно то, что мне пришло на ум.


Добавлено через 2 минуты и 55 секунд
Народ, а кто тег " Регульрные" добавил? Исправить можно?
PM MAIL WWW   Вверх

Дата 25.9.2008, 10:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
source777 ***   Репутация: 4  Всего: 56 
Цитата(kemiisto @  25.9.2008,  02:05 Найти цитируемый пост)
Я и не путаю. Мне пришло в голову записать '\.|\?|!', Вам - '[.!?]'. Это фактически одно и тоже. Где же тут, простите, "непонимание"? Да и, вообще, "к месту/ не к месту" определяет, по-моему, автор. 
Тихо, тихо. Во-первых, это только логически одно и то же, да и то далеко невсегда. Например, '\w+|[а-яА-Я]+' совсем не то же самое, что '[\wа-яА-Я]+', вот тебе и непонимание, а говорил, что не путаешь...
А во-вторых, это дополнение твоей заметки, а не перечёркивание её целиком, имхо, с твоей стороны вместо того, чтобы гордиться тем, что тебе пришло на ум по крайней мере, стоило бы внести изменения на более правильные варианты.

PM MAIL   Вверх

Дата 25.9.2008, 12:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Matematik ***   Репутация: 17  Всего: 50 
Еще есть TPerlRegEx http://www.regular-expressions.info/delphi.html
В тяжелых случаях работает быстрее чем TRegExpr 

PM MAIL WWW ICQ   Вверх

Дата 25.9.2008, 17:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата
Akella ****   Репутация: 36  Всего: 329 
Matematik
Цитата

Use System.Text.RegularExpressions with Delphi for .NET

PM MAIL   Вверх

Страницы: (3) Все [1] 2 3 
 
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0929 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.