Версия для печати


Использование VBScript RegExp в Delphi
http://www.delphikingdom.com/asp/viewitem.asp?catalogID=1062

Александр Шабля
дата публикации 05-10-2004 16:10

Использование VBScript RegExp в Delphi

Регулярные выражения (RegExp - regular expression) поддерживаются и широко используются в Perl, php, Java, C# и др. языках и интерпретаторах. Этот инструмент используется разработчиками для комплексного поиска соответствий выражению (паттерну) в текстовых строках согласно алгоритму поиска. Подробнее о синтаксисе VBScript Regular Expression можно почитать в MSDN.

Фирма Borland не включила в свою библиотеку этот часто очень необходимый инструмент. Эту ситуацию попытался исправить Андрей Сорокин, написав замечательный компонент TRegExpr (статья на Королевстве "Текст с высоты птичьего полета или Регулярные выражения"). Однако возможность использовать регулярные выражения в Delphi существует и без этого компонента. Эта возможность предоставлена фирмой Microsoft в библиотеке VBScript (vbscript.dll версии 5.0 и выше), поставляемой в составе Windows, начиная с версии 98. Скачать обновления VBScript можно здесь или здесь.

Подключаем VBScrip RegExp

Для подключения RegExp необходимо импортировать библиотеку типов Microsoft VBScript Regular Expressions и создать модуль библиотеки типов (смотрите рисунок).

В библиотеку будут включены объекты (интерфейсы) RegExp, MatchCollection, Match, SubMatches и класс-наследник TOLEServer TRegExp.

Для использования в uses включим сгенерированный модуль VBScript_RegExp_55_TLB.pas.

Объект RegExp

Объект VBScript RegExp (IRegExp2) имеет 4 свойства и 3 метода.
Свойства объекта RegExp:
Свойство Назначение
Pattern Строка, содержащая регулярное выражение. Должна быть задана до использования методов RegExp
IgnoreCase Тип Boolean. Если True, то игнорирует регистр символов, по-умолчанию False. Соответствует модификатору "i".
Global Тип Boolean. Если True, то RegExp будет пытаться найти все возможные соответствия в строке. Соответствует модификатору "g".
Multiline Тип Boolean. Если True, то RegExp будет поддерживать многострочный режим, по-умолчанию False. Соответствует модификатору "m".

Другие модификаторы (x, s, r и др.) в выражениях VBScript RegExp не поддерживаются.

Методы объекта RegExp:
Метод Назначение
Test(search-string) Тестирует строку search-string на соответствие паттерну. В случае удачи возвращает True.
Replace(search-string, replace-string) Метод имеет два аргумента: строку, в которой будет осуществляться поиск и строку замены. Все найденные соответствия в строке search-string, заданные в Pattern, будут замещены значениями, указанными в строке замены replace-string.
Excecute(search-string) Работает как Test, только все найденные соответвия в строке search-string, заданные в Pattern, будут возвращены в коллекции MatchCollection (см. ниже), содержащей объекты Match.

Объект MatchCollection

Содержит коллекцию объектов соответствия Match. Имеет два свойства: Count возвращает количество найденных соответствий, Item содержит объект Match с индексом от 0 до Count - 1. В VBScript RegExp версии 5.5 Item является свойством default, т.е. к свойству Item обращаться не обязательно, т.е. при обращении к объекту типа Matches Item можно опускать.

Объект Match

Объект Match (IMatch2) содержит найденное в строке соответствие, заданное в паттерне поиска. Имеет 4 свойства:
Метод Назначение
FirstIndex Позиция найденного соответствия в строке
Length Длина найденной строки
Value Текст найденного соответствия
SubMatches Колекция субсоответствий. Субсоответствия задаются группировкой с помощью круглых скобок "(" и ")" (см. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/script56/html/jsgrpregexpsyntax.asp).

Объект SubMatches

Содержит коллекцию субсоответствий. Это как бы соответствия в соответствии. Имеет два свойства: Count - количество найденных субсоответствий и Item (default - можно опустить) возвращает текст найденного субсоответствия с индексом от 0 до Count - 1.

Например, задан RegExp паттерн '(\d*)\.?(\d*)', строка поиска '123.45', тогда коллекция SubMatches вернет нам Count = 2, Item[0] = '123' и Item[1] = '45'.

Примечание:
при использовании библиотеки типов VBScript RegExp версии 1.0 свойство Item не является default и его нужно указывать явно. Также отсутсвуют свойство Multiline у объекта RegExp (IRegExp) и свойство SubMatches у объекта Match (IMatch).

Примеры применения регулярных выражений (класса TRegExp)

Разбор строки URL на составляющие пример взят с сайта MSDN

procedure TForm1.Button1Click(Sender: TObject);
var
  R: TRegExp;
  mc: MatchCollection;
  m: Match;
  sm: SubMatches;
  i, j: Integer;
begin
  R := TRegExp.Create(Self);
  try
    // задаем паттерн выражения...
    R.Pattern := '(\w+):\/\/([^/:]+)(:\d*)?([^#]*)(.*)';
    R.IgnoreCase := True; // игнорировать регистр символов
    R.Global := False; // поиск по всему тексту отключен
    Memo1.Lines.Clear;
    // входная строка с URL
    // методом Execute получаем коллекцию найденных соответствий
    mc := R.Execute(' http://msdn.microsoft.com:80/sctipting/default.htm#123')
          as MatchCollection;
    if mc.Count > 0 then begin
      for i := 0 to mc.Count - 1 do begin
        m := mc[i] as Match;
        Memo1.Lines.Add(Format(' Match[%d] = "%s"', [i, m.Value]));
        // разбираем субсоответствия (сгруппированные в скобках)
        sm := m.SubMatches as SubMatches;
        for j := 0 to sm.Count - 1 do
          Memo1.Lines.Add(Format('  SubMatch[%d] = "%s"', [j,
                           VarToStr(sm.Item[j])]));
      end;
    end;
  finally
    m := nil;
    sm := nil;
    mc := nil;
    R.Free;
  end;
end;

Теперь рассмотрим подробнее. В строке R.Pattern := '(\w+):\/\/([^/:]+)(:\d*)?([^#]*)(.*)' записываем паттерн регулярного выражения. Далее в mc := R.Execute(' http://msdn.microsoft.com:80/sctipting/default.htm#123') as MatchCollection; получаем коллекцию найденных соответсвий (MatchCollection). Получаем объект Match в переменную m := mc[i] as Match; и субсоответствия в переменную sm := m.SubMatches as SubMatches;

Результатом работы данного примера в Memo1 будет записано следующее:

Match[0] = "http://msdn.microsoft.com:80/sctipting/default.htm#123"
  SubMatch[0] = "http"
  SubMatch[1] = "msdn.microsoft.com"
  SubMatch[2] = ":80"
  SubMatch[3] = "/sctipting/default.htm"
  SubMatch[4] = "#123"

Где первая строка - это найденное соответствие Match, а остальные - субсоответствия (SubMatches).

Проверка того, что строка является числом

пример взят с сайта MSDN

procedure TForm1.Button2Click(Sender: TObject);
var
  mc: MatchCollection;
  S: String;
begin
  with TRegExp.Create(Self) do
  try
    Pattern := Format('[-+]?([0-9]*\%s)?[0-9]+([eE][-+]?[0-9]+)?',
              [DecimalSeparator]);
    IgnoreCase := True;
    Global := True;
    S := '';
    mc := Execute('5.23') as MatchCollection;
    if mc.Count = 1
      then ShowMessageFmt('Число "%s" задано верно', [(mc [0] as Match).Value])
      else ShowMessage('Это не число!');
  finally
    mc := nil;
    Free;
  end;
end;

Проверка валидности даты формата dd.mm.yyyy

пример взят с сайта www.regular-expressions.info

procedure TForm1.Button3Click(Sender: TObject);

  function IsValidDate(const ADate: String): Boolean;
  var
    mc: MatchCollection;
    sm: SubMatches;
    d, m, y: Integer;
  begin
    Result := False;
    with TRegExp.Create(Self) do
    try
      Pattern := Format('(0?[1-9]|[12][0-9]|3[01])\%0:s(0?[1-9]|1[012])\%0:s((19|20)?\d\d)',
        [DateSeparator]);
      Global := True;
      mc := Execute(ADate) as MatchCollection;
      if mc.Count = 1 then begin
        sm := (mc[0] as Match).SubMatches as SubMatches;
        if sm.Count < 3 then Exit; // не задан год!
        d := StrToInt(sm.Item[0]);
        m := StrToInt(sm.Item[1]);
        y := StrToInt(sm.Item[2]);
        if sm.Item[3] = '' then y := y +
          Trunc(StrToInt(FormatDateTime('yyyy', Date)) / 100) * 100;
        if (d = 31) and ((m = 4) or (m = 6) or (m = 9) or (m = 11))
          then Exit // 31-й день у месяцев с 30-ю днями
          else
            if m = 2 then begin // разборки с февралем
              if d >= 30 then Exit; // в феврале 30 дней или 31 день
              if (d = 29) and not ((y mod 4 = 0) and ((y mod 100 <> 0) or
                                  (y mod 400 = 0)))
                then Exit; // 29 февраля не в високосном году
            end;
        Result := True; // правильная дата
      end;
    finally
      sm := nil;
      mс := nil;
      Free;
    end;
  end;

begin
  if IsValidDate('29.02.04') then ShowMessage('OK') else Beep;
end;

Удалить из списка повторяющиеся элементы

пример взят с сайта www.regular-expressions.info

procedure TForm1.Button4Click(Sender: TObject);
var
  R: TRegExp;
begin
  R := TRegExp.Create(Self);
  try
    R.Pattern := '([^,]*)(,\1)+(?=,|$)';
    R.IgnoreCase := True;
    R.Global := True;
    ShowMessage(R.Replace('раз,два,три,три,четыре,пять,пять,шесть,семь,,девять'#10 +
      '1,2,3,3,4,5,5,6,7,,9', '$1'));
  finally
    R.Free;
  end;
end;

Удалить повторяющиеся слова в тексте

пример взят с сайта MSDN

procedure TForm1.Button5Click(Sender: TObject);
var
  R: TRegExp;
begin
  R := TRegExp.Create(Self);
  try
    R.Pattern := '([a-zа-я]+) \1';
    R.IgnoreCase := True;
    R.Global := True;
    R.Multiline := True; // многострочный текст
    ShowMessage(R.Replace('Стоимость стоимость газолина все все растет растет?'#10 +
      'Is is the cost of of gasoline going up up?', '$1'));
  finally
    R.Free;
  end;
end;

Недостатки библиотеки VBScript RegExp:

  1. является платформозависимой;
  2. требуется наличие библиотеки VBScript (vbscript.dll) версии 5.0 и выше на клиентском компьютере (поставляется в составе Windows версии 98 и выше);
  3. объект RegExp разбивается на другие объекты (MatchCollection, SubMatches), которые нужно обрабатывать в отдельных циклах;
  4. не поддерживается Lookbehind утверждение (assertion);
  5. при ошибке в выражении (Pattern) возникает не информативное исключение с текстом ошибки "OLE Error 800A1399".

Сравнение скорости работы VBScript RegExp с компонентом Андрея Сорокина TRegExpr

Для сравнения была создана процедура, в которой использовались методы Exec(ute) и Replace. Скорость обработки определялась с помощью тиков (функция WinAPI GetTickCount). Для теста был выбран .pas файл, содержащий 1031 строку.

Первый тест был проведен для глобальной замены по всему тексту. Скорость работы VBScript RegExpr от фирмы Microsoft оказалась " в 5-6 раз выше.

Второй тест на скорость включал построчную обработку текста с созданием и уничтожением экземпляра класса TRegExp или TRegExpr. Скорость работы компонента TRegExpr (Андрея Сорокина) оказалась " в 10 раз выше.

Можно сделать такой вывод, что при использовании VBScript RegExp от фирмы Microsoft скорость сильно зависит от времени загрузки ActiveX объекта RegExp. Скорость же работы алгоритма поиска и замены от фирмы Microsoft реализован лучше.

Несмотря на результат этого сравнения, TRegExpr(Андрея Сорокина) более удобен чем VBScript RegExp от фирмы Microsoft в том смысле, что не требуется dll и для простых проверок ввода он идеален.

Скачать пример к статье MSRegExp.zip (9.5 K)
Ссылки по теме: