Уже почти год назад я затрагивал тему о работе с регулярными выражениями 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 — регулярные выражения
А как по части багов/производительности/удобства в сравнении со старыми сторонними решениями?
2Keeper, проверил регулярки, которые раньше использовал с VBScript — повезло, всё работает :) Удобство…это понятие сугубо личное, но по мне так предложенная новинка в XE вполне удобна и понятна, можно работать. Производительность пока не проверял.
Поздновато они это
Во всех проектах использовал библиотеку Андрея Сорокина, из-за совместимости и нежелания тратить время на освоение новой библиотеки буду продолжать дальше пользоваться ею.
А вот если бы родная либа уже была когда я только начинал работать с регулярками…
Да… поздно они регулярки прикрутили к среде.
Лучше поздно, чем никогда :) Прикрепили и слава Богу — лишними не будут точно
Давно пора было регулярку приерктить.
А я как работал на легальной Turbo Delphi (Portable) вместе с компонентом TRegExpr, так и буду работать.
Кстати интересно, почему они не работают с кириллицей…
Хм…странно, активно юзаю их сейчас как раз на русскоязычных сайтах для парсинга. Вроде как с кирилицей проблем вообще не встречал
А у меня
RegEx:=TRegEx.Create(‘\w’);
if RegEx.IsMatch(‘Маша мыла раму
‘)then
в упор не работают.
Проверил. С русскими буковками работает, т.к.
RegEx:=TRegEx.Create('Маша');
RegEx.IsMatch('Маша мыла раму ')
Возвращает True, а вот с метасимволами беда, некотрые не пашут, в т.д. и \w — почему-то считается, что w — обычный символ, а вот \s работает :)
Вот с метасимволам как раз и интересно, как заставить это все работать )
по-моему действие опций roMultiLine и roSingleLine перепутано, почему-то у меня многострочный парсинг работает только при опции roSingleLine
Сдаётся мне, что
If RegEx.IsMatch(S) Then….
и потом Param:=RegEx.Matches(S)
Это двойная работа, что очень замедляет процесс при больших объёмах данных… Не уж то после проверки нельзя было сразу сохранять результат?
или может делать
Try
Param:=RegEx.Matches(S)
сразу лучше?
а как сделать так чтобы находил слово и перед ним вставлял нужный текст не изменяя само слово?
Достаточно давно использую библиотеку Андрея Сорокина, и до сих пор ее функционала вполне хватало, но сейчас захотелось большего. Встроенные регулярные выражения (Delphi XE5) оказались гораздо медленнее, и у меня так и не получилось заставить их воспринимать символы русского алфавита, как буквы. Нашел еще пару бесплатных сторонних решений, но они также основаны на PCRE, и заставить их воспринимать русский текст в метасимволах не удалось. Что касается производительности, ниже приведены замеры скорости и код (дабы не быть голословным). В некоторых случаях вполне возможно поступиться производительностью, если бы не русский язык. Может кто-нибудь найдет решение… Текст: ’10’ Регулярка: ‘\d’ [1 итерация] [1000 итераций]… Подробнее »
Андрей, спасибо за анализ скорости… наткнулся на те же самые грабли когда кириллица и ‘\n’ в одном шаблоне не работает. Нашёл временное решение, заменил \n на [^.][^.] Я так понял, ошибка возникает когда русские символы и одновременно слеш ‘\’ присутствует. Вобщем это баг 100% (Embarcadero или кто там не знаю). Пока удалось заменить ‘\’ на аналог, но нет гарантий, что в будущем это спасёт. А поскольку преимуществ у стандартной библиотеки Delphi перед RegExpr Андрея Сорокина нет, тогда буду потихонечку переходить на неё. Моя конфигурация: — Delphi XE 3 — Win 7, 32 bit Если у кого получится решить эту проблему… Подробнее »
Сообщаю о том, что возможное (приемлемое с некоторыми оговорками) решение найдено. Говорю сразу, я — не профессиональный программист, поэтому решение, которое я нашел, кому-то из профессионалов может показаться идиотским. Тем не менее, опишу, так как оно работает: вдруг кому-то будет полезно. Я запилил две функции. Одна — кодирует русские буквы в последовательность из трех символов (в моем случае: латинская буква и две цифры). Другая — декодирует эти последовательности обратно в русские буквы. Обе функции в качестве аргумента получают строку и возвращают строку. При этом, кодирующая функция любые символы, кроме русских букв ([‘а’..’Я’]) в исходной строке (в полученном аргументе) переносит в… Подробнее »
Андрей, если не трудно — в двух словах напишите, каких возможностей вам стало не хватать в библиотеке Андрея Сорокина. Спасибо.
Метасимвол \b (граница слова), пассивные группы и просмотр вперед/назад.
\b не заработал с русскими символами, на StackOverflow нашел ему замену, но с помощью как раз просмотров. Пассивные группы просто облегчают разбор результата при большом количестве скобок.
А вообще можно сравнить библиотеки Сорокина и, например, by Dave Child.
P.S. Ха, библиотека Сорокина поддерживает метасимвол «граница слова» — оказывается, у меня была старая версия демки + распечатка описания синтаксиса библиотеки без описания этого метасимвола. Что ж Вы раньше то не спросили… :)
Уважаемые программисты на Delphi. Я вас очень прошу, когда вы пишете статью, пожалуйста, и про модули, которые вы используете в проекте, не забывайте написать!!!!!
Что непонятно? RegularExpressionsAPI.pas -обёртка для библиотеки PCRE. PCRE — это библиотека содержащая ряд функций для работы с регулярными выражениями синтакис которых максимально предлижен к синтаксису регулярных выражений в Perl 5. RegularExpressionsCore.pas — как становится поняным из названия — модуль представляет собой некое ядро вокруг которого и крутится вся работа с регулярками. Так, например, в модуле RegularExpressionsCore содержася такие «говорящие» классы как TPerlRegEx, TPerlRegExOptions и т.д. Соответственно этот модуль опирается в своей работе на функции из RegularExpressionsAPI.pas. RegularExpressionsConsts.pas — содержит ресурсные строки, используемые при выводе сообщений об ошибках при работе с регулярными выражениями. RegularExpressions.pas — модуль в котором содержаться всё необходимое… Подробнее »
Ошибка там где пример без создания экземпляра TRegEx. В примере написано просто RegEx.
Не прошло и десяти лет :)
Главное — вовремя заметить)))