уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Уже почти год назад я затрагивал тему о работе с регулярными выражениями VBScript в Delphi 2010. Сегодня наконец-то докачал iso-образ диска с RAD Studio XE, установил Delphi XE для более пристального ознакомления с новинками и обнаружил несколько новых модулей, цель которых — обеспечение работы с регулярными выражениями в Delphi. Я решил вспомнить прошлое и написать небольшую программку, демонстрирующую работы с регулярными выражениями с использованием «родных» модулей в Delphi XE.


Вначале немного о том, что за новые модули появились в версии XE и вкратце, что в них содержится.

RegularExpressionsAPI.pas -обёртка для библиотеки PCRE. PCRE — это библиотека содержащая ряд функций для работы с регулярными выражениями синтаксис которых максимально приближен к синтаксису регулярных выражений в Perl 5.

RegularExpressionsCore.pas — как становится понятным из названия — модуль представляет собой некое ядро вокруг которого и крутится вся работа с регулярными выражениями в Delphi. Так, например, в модуле RegularExpressionsCore содержатся такие «говорящие» классы как TPerlRegEx, TPerlRegExOptions и т.д. Соответственно этот модуль опирается в своей работе на функции из RegularExpressionsAPI.pas.

RegularExpressionsConsts.pas — содержит ресурсные строки, используемые при выводе сообщений об ошибках при работе с регулярными выражениями.

RegularExpressions.pas — модуль в котором содержаться всё необходимое для работы с регулярными выражениями в Delphi XE. Естественно модуль опирается на все три предыдущих.

Теперь приступим к более пристальному рассмотрению новинок.

Работа с TRegEx в Delphi XE

В TRegEx содержаться все необходимые методы и свойства для работы с регулярными выражениями. Практически все методы имеют «двойников» в виде классовых функций. Поэтому можно работать с регулярками или так:

...
var RegEx: TRegEx;
begin
 RegEx:=TRegEx.Create('Any regular expression');
 if RegEx.IsMatch('Any text')then
   ShowMessage('Есть совпадения')
...

А можно и без создания нового экземпляра TRegEx:

...
begin
 if RegEx.IsMatch('Any text','Any regular expression')then
   ShowMessage('Есть совпадения')
...

Как Вам будет удобнее в той или иной ситуации. Рассмотрим методы TRegEx.
Конструкторы:

constructor Create(const Pattern: string); overload;
constructor Create(const Pattern: string; Options: TRegExOptions); overload;

где Pattern — регулярное выражение, а Options — множество, содержащее опции, используемые при поиске совпадений.

function IsMatch(const Input: string): Boolean; overload;
function IsMatch(const Input: string; StartPos: Integer): Boolean; overload;
class function IsMatch(const Input, Pattern: string): Boolean;overload; static;
class function IsMatch(const Input, Pattern: string; Options: TRegExOptions): Boolean; overload; static;

IsMatch возвращает True, если в тексте Input найдено хотя бы одно совпадение. При этом парсинг можно начать не с начала строки, а с позиции StartPos.

function Match(const Input: string): TMatch; overload;
function Match(const Input: string; StartPos: Integer): TMatch; overload;
function Match(const Input: string; StartPos, Length: Integer): TMatch; overload;
class function Match(const Input, Pattern: string): TMatch; overload; static;
class function Match(const Input, Pattern: string; Options: TRegExOptions): TMatch; overload; static;

Match возвращает запись TMatch, которая содержит результаты одного совпадения с регулярным выражением.

function Matches(const Input: string): TMatchCollection; overload;
function Matches(const Input: string; StartPos: Integer): TMatchCollection; overload;
class function Matches(const Input, Pattern: string): TMatchCollection; overload; static;
class function Matches(const Input, Pattern: string; Options: TRegExOptions): TMatchCollection; overload; static;

Matches возвращает все совпадения с регулярным выражением. При этом TMatchCollection представляет собой запись, которая содержит список всех найденных совпадений TMatch.

function Replace(const Input, Replacement: string): string; overload;
function Replace(const Input: string; Evaluator: TMatchEvaluator): string; overload;
function Replace(const Input, Replacement: string; Count: Integer): string; overload;
function Replace(const Input: string; Evaluator: TMatchEvaluator; Count: Integer): string; overload;
class function Replace(const Input, Pattern, Replacement: string): string; overload; static;
class function Replace(const Input, Pattern: string; Evaluator: TMatchEvaluator): string; overload; static;
class function Replace(const Input, Pattern, Replacement: string; Options: TRegExOptions): string; overload; static;
class function Replace(const Input, Pattern: string; Evaluator: TMatchEvaluator; Options: TRegExOptions): string; overload; static;

Заменяет все вхождения строки Replacement в строке Input. Напоминает всем известный метод StrinReplace, но предоставляет более широкие возможности по замене. При этом замена текста в исходной строке может осуществляться с использованием регулярного выражения для этого необходимо определить метод:

TMatchEvaluator = function(const Match: TMatch): string of object;

возвращающий в результате строку, исходя из содержащихся в TMatch данных. Также, в параметре Count можно указать максимальное количество замен. Рассморим возможности этой функции немного по-подробнее. Напишем простую программку, которая будет искать в строке текста все латинские символы A и менять их на B. Ничего особенного — просто демонстрация возможностей метода. Внешний вид приложения следующий:

В Edit будем записывать регулярное выражение, в Memo — текст для парсинга, а в нижний ListBox — все найденные совпадения и результаты работы метода Replace. С помощью CheckBox’ов будем устанавливать необходимые опции (об опциях чиайте ниже).
Вначале определим метод TMatchEvaluator следующим образом:

function TForm8.Evaluator(const Match: TMatch): string;
begin
Result:='';
  if pos('A',Match.Value)>0 then
    Result:='B'
end;

То есть при использовании этого метода в Replase замена будет происходить следующим образом: если найдена заглавная буква, то она заменяется на заглавную B, иначе — на пустую строку.

Теперь пишем обработчик OnClick «Проверить»

procedure TForm8.Button1Click(Sender: TObject);
var RegEx: TRegEx;
    Options: TRegExOptions;
    M: TMatchCollection;
    i,j:integer;
begin
 //определяем необходимые опции
 Options:=[];
 if CheckBox1.Checked then
   Include(Options, roIgnoreCase);
 if CheckBox2.Checked then
   Include(Options, roMultiLine);
 if CheckBox3.Checked then
   Include(Options, roExplicitCapture);
 if CheckBox4.Checked then
   Include(Options, roCompiled);
 if CheckBox5.Checked then
   Include(Options, roSingleLine);
 if CheckBox6.Checked then
   Include(Options, roIgnorePatternSpace);
 
 ListBox1.Items.Clear;
 //создаем объект
 RegEx:=TRegEx.Create(Edit1.Text,Options);
 //Если найдено хотя бы одно совпадение продолжаем работу
 if RegEx.IsMatch(Memo1.Text)then
   begin
     M:=RegEx.Matches(Memo1.Text,Edit1.Text,Options);//получаем коллекцию совпадений
     for  i:=0 to M.Count-1 do
       begin
         ListBox1.Items.Add(M.Item[i].Value);//выводим совпадение
         for j:= 0 to M.Item[i].Groups.Count-1 do //если в регулярном выражении есть группы
           ListBox1.Items.Add('   Группа '+IntToStr(j)+' '+M.Item[i].Groups[j].Value)
       end;
      //Выводим результаты работы Replace
      ListBox1.Items.Add(RegEx.Replace(Memo1.Text,Evaluator));
      ListBox1.Items.Add(RegEx.Replace(Memo1.Text,'B'));
   end;
end;

Теперь проверим работу. Запустим приложение и запишем в Memo, например, такую строку:

Evaluator - это метод, который "заставляет" заменять в исходной строке все символы "A" на "B" с использованием особых правил замены

А регулярное выражение зададим, например так:

.?A.?

Вначале проверяем без дополнительных опций. Результат поиска совпадение:

1 совпадение
Replace с Evaluator: Evaluator — это метод, который «заставляет» заменять в исходной строке все символы B на «B» с использованием особых правил замены
Replace без Evaluator: Evaluator — это метод, который «заставляет» заменять в исходной строке все символы B на «B» с использованием особых правил замены

То есть оба раза метод сработал идентично. Теперь включим опцию IgnoreCase (поиск без учёта регистра) и повторим замену. Результат:

3 совпадеия («A», uat, val)
Replace с Evaluator: Eor — это метод, который «заставляет» заменять в исходной строке все символы B на «B» с использованием особых правил замены
Replace без Evaluator: EBBor — это метод, который «заставляет» заменять в исходной строке все символы B на «B» с использованием особых правил замены

То есть, как видно из замен в слове «Evaluator», мы не просто провели замену подстрок, но и, в первом случае (с использованием метода Evaluator) определили свои собственные правила замены подстрок. Соответственно, включив в параметры Replace третье значение (Count) мы помимо всего прочего можем определить количество замен в строке.

И, наконец, последний метод TRegEx:

function Split(const Input: string): TArray; overload; inline;
function Split(const Input: string; Count: Integer): TArray; overload; inline;
function Split(const Input: string; Count, StartPos: Integer): TArray; overload;
class function Split(const Input, Pattern: string): TArray; overload; static;
class function Split(const Input, Pattern: string; Options: TRegExOptions): TArray; overload; static;

Split делит строку на подстроки с использованием регулярного выражения и помещает эти подстроки в массив. При этом совпадения с регулярным выражением выступают в качестве разделителя и выбрасываются из результатов деления.
Чтоб далеко не ходить — применим этот метод с использованием рассмотренного выше регулярного выражения и строки для поиска. Допишем наш обработчик onClick следующим образом:

procedure TForm8.Button1Click(Sender: TObject);
var
    ...
    Mass: TArray;
begin
      ...
      ListBox1.Items.Add(RegEx.Replace(Memo1.Text,'B'));
      //делим строку
      Mass:=RegEx.Split(Memo1.Text);
      for i := 0 to length(Mass)-1 do
         ListBox1.Items.Add(Mass[i])
   end;
end;

Результат деления строки с использованием опции IgnoreCase получился следующим:

3 совпадеия (“A”, uat, val)
Части строки:
E
» (пустая строка)
or — это метод, который “заставляет” заменять в исходной строке все символы
на “B” с использованием особых правил замены

Пустая строка является в приведенном примере результатом того, что два совпадения в строке идут один за другим — соответственно split возвращает символы между ними, т.е. строку нулевого размера.
Вот собственно те методы, используя которые можно использовать регулярные выражения в Delphi XE с использованием TRegEx. Осталось только упомянуть про опции, используемые при поиске и замене текста.

Опции TRegEx

Всего можно использовать шесть различных опций:
roIgnoreCase — поиск без учёта регистра символов
roMultiLine — включает многострочный режим парсинга. Имеет смысл использовать, когда в тексте встречаются символы перевода на новую строку.
roExplicitCapture — указывает на то, что в результаты работы будут включаться только именованные группы вида (…)
roCompiled — использование этой опции даёт более быстрое выполнение операций парсинга, но вызов методов TRegEx будет выполняться медленнее.
roSingleLine — однострочный режим. Парсинг будет осуществляться в строке до символа перевода /n
roIgnorePatternSpace — устраняет из паттерна неэкранированные пробелы и делает возможным использование в регулярных выражениях комментариев.

Вот, в принципе, краткий обзор возможностей Delphi XE по работе с регулярными выражениями. И в конце статьи пара ссылок на информацию по регулярным выражениям в Perl:
1. Регулярные выражения в Perl
2. WDH: Perl — регулярные выражения

3 2 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
23 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
Keeper
16/09/2010 04:52

А как по части багов/производительности/удобства в сравнении со старыми сторонними решениями?

Дмитрий
Дмитрий
19/09/2010 14:04

Поздновато они это
Во всех проектах использовал библиотеку Андрея Сорокина, из-за совместимости и нежелания тратить время на освоение новой библиотеки буду продолжать дальше пользоваться ею.
А вот если бы родная либа уже была когда я только начинал работать с регулярками…

Akella
21/09/2010 17:19

Да… поздно они регулярки прикрутили к среде.

GlooK
23/09/2010 14:39

Давно пора было регулярку приерктить.
А я как работал на легальной Turbo Delphi (Portable) вместе с компонентом TRegExpr, так и буду работать.

Keeper
02/04/2011 03:57

Кстати интересно, почему они не работают с кириллицей…

Keeper
02/04/2011 14:08

А у меня
 
RegEx:=TRegEx.Create(‘\w’);
if RegEx.IsMatch(‘Маша мыла раму
‘)then

в упор не работают.
 

Keeper
03/04/2011 02:31

Вот с метасимволам как раз и интересно, как заставить это все работать )

Treigol
Treigol
24/11/2012 00:40

по-моему действие опций roMultiLine и roSingleLine перепутано, почему-то у меня многострочный парсинг работает только при опции roSingleLine

Isaev
Isaev
20/03/2013 22:59

Сдаётся мне, что
If RegEx.IsMatch(S) Then….
и потом Param:=RegEx.Matches(S)
Это двойная работа, что очень замедляет процесс при больших объёмах данных… Не уж то после проверки нельзя было сразу сохранять результат?
или может делать
Try
Param:=RegEx.Matches(S)
сразу лучше?

werhad
werhad
13/05/2013 22:47

а как сделать так чтобы находил слово и перед ним вставлял нужный текст не изменяя само слово?

Андрей
Андрей
20/01/2014 12:44

Достаточно давно использую библиотеку Андрея Сорокина, и до сих пор ее функционала вполне хватало, но сейчас захотелось большего. Встроенные регулярные выражения (Delphi XE5) оказались гораздо медленнее, и у меня так и не получилось заставить их воспринимать символы русского алфавита, как буквы. Нашел еще пару бесплатных сторонних решений, но они также основаны на PCRE, и заставить их воспринимать русский текст в метасимволах не удалось. Что касается производительности, ниже приведены замеры скорости и код (дабы не быть голословным). В некоторых случаях вполне возможно поступиться производительностью, если бы не русский язык. Может кто-нибудь найдет решение… Текст: ’10’ Регулярка: ‘\d’ [1 итерация] [1000 итераций]… Подробнее »

Харитонов
Харитонов
11/07/2014 01:19
Ответить на  Андрей

Андрей, спасибо за анализ скорости… наткнулся на те же самые грабли когда кириллица и ‘\n’ в одном шаблоне не работает. Нашёл временное решение, заменил \n на [^.][^.] Я так понял, ошибка возникает когда русские символы и одновременно слеш ‘\’ присутствует. Вобщем это баг 100% (Embarcadero или кто там не знаю). Пока удалось заменить ‘\’ на аналог, но нет гарантий, что в будущем это спасёт. А поскольку преимуществ у стандартной библиотеки Delphi перед RegExpr Андрея Сорокина нет, тогда буду потихонечку переходить на неё. Моя конфигурация: — Delphi XE 3 — Win 7, 32 bit Если у кого получится решить эту проблему… Подробнее »

Максим
Максим
28/11/2014 03:24
Ответить на  Харитонов

Сообщаю о том, что возможное (приемлемое с некоторыми оговорками) решение найдено. Говорю сразу, я — не профессиональный программист, поэтому решение, которое я нашел, кому-то из профессионалов может показаться идиотским. Тем не менее, опишу, так как оно работает: вдруг кому-то будет полезно. Я запилил две функции. Одна — кодирует русские буквы в последовательность из трех символов (в моем случае: латинская буква и две цифры). Другая — декодирует эти последовательности обратно в русские буквы. Обе функции в качестве аргумента получают строку и возвращают строку. При этом, кодирующая функция любые символы, кроме русских букв ([‘а’..’Я’]) в исходной строке (в полученном аргументе) переносит в… Подробнее »

Харитонов
Харитонов
11/07/2014 01:26
Ответить на  Андрей

Андрей, если не трудно — в двух словах напишите, каких возможностей вам стало не хватать в библиотеке Андрея Сорокина. Спасибо.

Андрей
Андрей
11/07/2014 18:13
Ответить на  Харитонов

Метасимвол \b (граница слова), пассивные группы и просмотр вперед/назад.
\b не заработал с русскими символами, на StackOverflow нашел ему замену, но с помощью как раз просмотров. Пассивные группы просто облегчают разбор результата при большом количестве скобок.

А вообще можно сравнить библиотеки Сорокина и, например, by Dave Child.

P.S. Ха, библиотека Сорокина поддерживает метасимвол «граница слова» — оказывается, у меня была старая версия демки + распечатка описания синтаксиса библиотеки без описания этого метасимвола. Что ж Вы раньше то не спросили… :)

Bogdan Sirenko
04/02/2016 15:34

Уважаемые программисты на Delphi. Я вас очень прошу, когда вы пишете статью, пожалуйста, и про модули, которые вы используете в проекте, не забывайте написать!!!!!

Vadim Mihaylovsky
07/05/2019 15:26

Ошибка там где пример без создания экземпляра TRegEx. В примере написано просто RegEx.
Не прошло и десяти лет :)