Регулярные выражения

Регулярные выражения предназначены для выполнения сложного поиска или замены в строке. В языке VBA нет встроенной поддержки регулярных выражений, однако можно получить доступ через объект VBScript.RegExp. Создать объект позволяет следующий код:

Dim re
Set re = CreateObject("VBScript.RegExp")
Примечание

Синтаксис регулярных выражений

После создания объекта нужно задать значения следующим свойствам:

  • Pattern — устанавливает или возвращает шаблон;
  • IgnoreCase — если указано значение True, то поиск не будет зависить от регистра символов, а если False (значение по умолчанию) — то будет:
Dim re
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "^[а-яё]+$"        ' Задаем шаблон
re.IgnoreCase = False           ' Зависит от регистра
Debug.Print re.Test("АБВГДЕЁ")  ' False (не совпадает)
re.IgnoreCase = True            ' Не зависит от регистра
Debug.Print re.Test("АБВГДЕЁ")  ' True  (совпадает)
  • Global — если указано значение True, то будут искаться все совпадения с шаблоном (глобальный поиск), а если False (значение по умолчанию) — то только первое совпадение:
Dim re
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "[0-9]+"
re.Global = False
Debug.Print re.Replace("12 5 45", "+")  ' + 5 45
re.Global = True                        ' Глобальный поиск
Debug.Print re.Replace("12 5 45", "+")  ' + + +
  • MultiLine — если указано значение True, то используется многострочный режим, а если False (значение по умолчанию) — то однострочный. При многострочном режиме производится поиск в строке, состоящей из нескольких подстрок, разделенных символом новой строки ("\n"). Символ ^ соответствует привязке к началу каждой подстроки, а символ $ соответствует позиции перед символом перевода строки.

Шаблон может содержать комбинации следующих символов, имеющих специальное значение:

  • \n — перевод строки;
  • \r — возврат каретки;
  • \t — знак табуляции;
  • \v — вертикальная табуляция;
  • \f — перевод формата;
  • \N — восьмеричное значение N. Например, \74 соответствует символу <;
  • \xN — шестнадцатеричное значение N. Например, \x6a соответствует символу j;
  • \uxxxx — символ Unicode. Например, \u043a соответствует русской букве к;
  • \cN — специальное значение N. \cJ — перевод строки, \cM — возврат каретки, \cI — знак табуляции, \cK — вертикальная табуляция, \cL — перевод формата.

Внутри шаблона символы ., ^, $, *, +, ?, {, [, ], \, |, ( и ) имеют специальное значение. Если эти символы должны трактоваться как есть, то их следует экранировать с помощью слеша. Некоторые специальные символы теряют свое специальное значение, если их разместить внутри квадратных скобок. В этом случае экранировать их не нужно. Например, метасимвол "точка" соответствует любому символу, кроме символа перевода строки. Если необходимо найти именно точку, то перед точкой необходимо указать символ \ или разместить точку внутри квадратных скобок ([.]). Продемонстрируем это на примере проверки правильности введенной даты (листинг 7.1).

Листинг 7.1. Проверка правильности ввода даты

Dim re, d As String
Set re = CreateObject("VBScript.RegExp")

d = "29,12.2012"  ' Вместо точки указана запятая

re.Pattern = "^[0-3][0-9].[01][0-9].[12][09][0-9][0-9]$"
' Символ "\" не указан перед точкой
If re.Test(d) = True Then
   Debug.Print "Дата введена правильно"
Else
   Debug.Print "Дата введена неправильно"
End If
' Так как точка означает любой символ,
' выведет: Дата введена правильно

re.Pattern = "^[0-3][0-9]\.[01][0-9]\.[12][09][0-9][0-9]$"
' Символ "\" указан перед точкой
If re.Test(d) = True Then
   Debug.Print "Дата введена правильно"
Else
   Debug.Print "Дата введена неправильно"
End If
' Так как перед точкой указан символ "\",
' выведет: Дата введена неправильно

re.Pattern = "^[0-3][0-9][.][01][0-9][.][12][09][0-9][0-9]$"
' Точка внутри квадратных скобок
If re.Test(d) = True Then
   Debug.Print "Дата введена правильно"
Else
   Debug.Print "Дата введена неправильно"
End If
' Выведет: Дата введена неправильно
Примечание

Метасимвол "точка" соответствует любому символу, кроме символа перевода строки. Чтобы обозначить любой символ, включая символ перевода строки, следует воспользоваться комбинаций [\s\S]. Пример:

Dim re
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "^.$"
Debug.Print re.Test(vbLf) ' False
re.Pattern = "^[\s\S]$"
Debug.Print re.Test(vbLf) ' True

В этом примере мы осуществляли привязку к началу и концу строки с помощью следующих метасимволов:

  • ^ — привязка к началу строки или подстроки (зависит от значения свойства MultiLine);
  • $ — привязка к концу строки или подстроки (зависит от значения свойства MultiLine).

Если свойство MultiLine равно значению True, то поиск производится в строке, состоящей из нескольких подстрок, разделенных символом новой строки (\n). В этом случае символ ^ соответствует привязке к началу каждой подстроки, а символ $ соответствует позиции перед символом перевода строки (листинг 7.2).

Листинг 7.2. Пример использования многострочного режима

Dim re, Matches, Item, s As String
s = "123" & vbLf & "456"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "^.+$"      ' Точка не соответствует \n
re.Global = True         ' Глобальный поиск
re.MultiLine = False     ' Однострочный режим
Debug.Print re.Test(s)   ' False (Ничего не найдено)
re.MultiLine = True      ' Многострочный режим
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' 123
' 456

Привязку к началу и концу строки следует использовать, если строка должна полностью соответствовать регулярному выражению. Например, привязку нужно использовать для проверки, содержит ли строка число (листинг 7.3).

Листинг 7.3. Проверка наличия целого числа в строке

Dim re
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "^[0-9]+$"
re.MultiLine = False             ' Однострочный режим
Debug.Print re.Test("245")       ' True
Debug.Print re.Test("Строка245") ' False

Если убрать привязку к началу и концу строки, то любая строка, содержащая хотя бы одну цифру, будет соответствовать шаблону (листинг 7.4).

Листинг 7.4. Отсутствие привязки к началу или концу строки

re.Pattern = "[0-9]+"
Debug.Print re.Test("Строка245") ' True

Можно указать привязку только к началу или только к концу строки (листинг 7.5).

Листинг 7.5. Привязка к началу и концу строки

Dim re
Set re = CreateObject("VBScript.RegExp")

re.Pattern = "[0-9]+$"
If re.Test("Строка245") = True Then
   Debug.Print "Есть число в конце строки"
Else
   Debug.Print "Нет числа в конце строки"
End If
' Выведет: Есть число в конце строки

re.Pattern = "^[0-9]+"
If re.Test("Строка245") = True Then
   Debug.Print "Есть число в начале строки"
Else
   Debug.Print "Нет числа в начале строки"
End If
' Выведет: Нет числа в начале строки

В квадратных скобках [] можно указать символы, которые могут встречаться на этом месте в строке. Можно перечислять символы подряд или указать диапазон через тире:

  • [09] — соответствует числу 0 или 9;
  • [0-9] — соответствует любому числу от 0 до 9;
  • [абв] — соответствует буквам "а", "б" и "в";
  • [а-г] — соответствует буквам "а", "б", "в" и "г";
  • [а-яё] — соответствует любой букве от "а" до "я";
  • [АБВ] — соответствует буквам "А", "Б" и "В";
  • [А-ЯЁ] — соответствует любой букве от "А" до "Я";
  • [а-яА-ЯёЁ] — соответствует любой русской букве в любом регистре;
  • [0-9а-яА-ЯёЁa-zA-Z] — любая цифра и любая буква независимо от регистра и языка.
Внимание!

Значение можно инвертировать, если после первой скобки указать символ ^. Таким образом можно указать символы, которых не должно быть на этом месте в строке:

  • [^09] — не цифра 0 или 9;
  • [^0-9] — не цифра от 0 до 9;
  • [^а-яА-ЯёЁa-zA-Z] — не буква.

Как вы уже знаете, точка теряет свое специальное значение, если ее заключить в квадратные скобки. Кроме того, внутри квадратных скобок могут встретиться символы, которые имеют специальное значение (например, ^ и -). Символ ^ теряет свое специальное значение, если он не расположен сразу после открывающей квадратной скобки. Чтобы отменить специальное значение символа -, его необходимо указать после перечисления всех символов, перед закрывающей квадратной скобкой, или сразу после открывающей квадратной скобки. Все специальные символы можно сделать обычными, если перед ними указать символ \.

Вместо перечисления символов можно использовать стандартные классы:

  • \d — соответствует любой цифре; эквивалентно [0-9];
  • \D — не цифра; эквивалентно [^0-9];
  • \w — соответствует любой латинской букве, цифре или символу подчеркивания; эквивалентно [a-zA-Z0-9_];
  • \W — не буква, не цифра и не символ подчеркивания; эквивалентно [^a-zA-Z0-9_];
  • \s — любой пробельный символ; эквивалентно [ \t\n\r\f\v];
  • \S — не пробельный символ; эквивалентно [^ \t\n\r\f\v].

Количество вхождений символа в строку задается с помощью квантификаторов:

  • {n} — n вхождений символа в строку. Например, шаблон "^[0-9]{2}$" соответствует двум вхождениям любой цифры;
  • {n,} — n или более вхождений символа в строку. Например, шаблон "^[0-9]{2,}$" соответствует двум и более вхождениям любой цифры;
  • {n,m} — не менее n и не более m вхождений символа в строку. Числа указываются через запятую без пробела. Например, шаблон "^[0-9]{2,4}$" соответствует от двух до четырех вхождениям любой цифры;
  • * — ноль или большее число вхождений символа в строку. Эквивалентно комбинации {0,};
  • + — одно или большее число вхождений символа в строку. Эквивалентно комбинации {1,};
  • ? — ни одного или одно вхождение символа в строку. Эквивалентно комбинации {0,1}.

Все квантификаторы являются "жадными". При поиске соответствия ищется самая длинная подстрока, соответствующая шаблону, и не учитываются более короткие соответствия. Рассмотрим это на примере. Получим содержимое всех тегов <b>, вместе с тегами:

Dim re, Matches, Item, s As String
s = "<b>Text1</b>Text2<b>Text3</b>"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "<b>.*</b>"
re.Global = True         ' Глобальный поиск
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' <b>Text1</b>Text2<b>Text3</b>

Вместо желаемого результата мы получили полностью строку. Чтобы ограничить "жадность", необходимо после квантификатора указать символ ? (листинг 7.6).

Листинг 7.6. Ограничение жадности квантификаторов

Dim re, Matches, Item, s As String
s = "<b>Text1</b>Text2<b>Text3</b>"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "<b>.*?</b>"
re.Global = True         ' Глобальный поиск
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' <b>Text1</b>
' <b>Text3</b>

Этот код вывел то, что мы искали. Если необходимо получить содержимое без тегов, то нужный фрагмент внутри шаблона следует разместить внутри круглых скобок (листинг 7.7). В этом случае фрагмент будет доступен через коллекцию Submatches.

Листинг 7.7. Получение значения определенного фрагмента

Dim re, Matches, Submatches, Item, SubItem, s As String
s = "<b>Text1</b>Text2<b>Text3</b>"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "<b>(.*?)</b>"
re.Global = True         ' Глобальный поиск
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
   Set Submatches = Item.Submatches
   For Each SubItem In Submatches
      Debug.Print "      " & SubItem
   Next
Next
' <b>Text1</b>
'       Text1
' <b>Text3</b>
'       Text3

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

Листинг 7.8. Ограничение захвата фрагмента

Dim re, Matches, Submatches, Item, SubItem, s As String
s = "test text"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "([a-z]+((st)|(xt)))"
re.Global = True         ' Глобальный поиск
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
   Set Submatches = Item.Submatches
   For Each SubItem In Submatches
      Debug.Print "      " & SubItem
   Next
Next
' test
'       test
'       st
'       st
'
' text
'       text
'       xt
'
'       xt
re.Pattern = "([a-z]+(?:(?:st)|(?:xt)))"
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
   Set Submatches = Item.Submatches
   For Each SubItem In Submatches
      Debug.Print "      " & SubItem
   Next
Next
' test
'       test
' text
'       text

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

Обратите внимание на регулярное выражение в предыдущем примере:

"([a-z]+((st)|(xt)))"

Здесь мы использовали метасимвол |, который позволяет сделать выбор между альтернативными значениями. Выражение n|m соответствует одному из символов: n или m. Пример:

красн((ая)|(ое)) — красная или красное, но не красный.

К найденному фрагменту в круглых скобках внутри шаблона можно обратиться с помощью механизма обратных ссылок. Для этого порядковый номер круглых скобок в шаблоне указывается после слеша, например, \1. Нумерация скобок внутри шаблона начинается с 1. Для примера получим текст между одинаковыми парными тегами (листинг 7.9).

Листинг 7.9. Обратные ссылки

Dim re, Matches, Submatches, Item, SubItem, s As String
s = "<b>Text1</b>Text2<I>Text3</I><b>Text4</b>"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "<([a-zA-Z]+)>(.*?)</\1>"
re.Global = True
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
   Set Submatches = Item.Submatches
   For Each SubItem In Submatches
      Debug.Print "      " & SubItem
   Next
Next
' <b>Text1</b>
'       b
'       Text1
' <I>Text3</I>
'       I
'       Text3
' <b>Text4</b>
'       b
'       Text4

Внутри круглых скобок могут быть расположены следующие конструкции:

  • (?=...) — положительный просмотр вперед. Выведем все слова, после которых расположена запятая:
Dim re, Matches, Item, s As String
s = "text1, text2, text3 text4"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "\w+(?=[,])"
re.Global = True
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' text1
' text2
  • (?!...) — отрицательный просмотр вперед. Выведем все слова, после которых нет запятой:
Dim re, Matches, Item, s As String
s = "text1, text2, text3 text4"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "[a-z]+[0-9](?![,])"
re.Global = True
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' text3
' text4

Рассмотрим небольшой пример. Предположим, необходимо получить все слова, расположенные после тире, причем перед тире и после слов должны следовать пробельные символы:

Dim re, Matches, Item, s As String
s = "-word1 -word2 -word3 -word4 -word5"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "\s\-([a-z0-9]+)\s"
re.Global = True
Set Matches = re.Execute(s)
For Each Item In Matches
   Debug.Print Item.Value
Next
' -word2
' -word4

Как видно из примера, мы получили только два слова вместо пяти. Первое и последнее слово не попали в результат, т. к. расположены в начале и конце строки. Чтобы эти слова попали в результат, необходимо добавить альтернативный выбор (^|\s) для начала строки и (\s|$) для конца строки. Чтобы найденные выражения внутри круглых скобок не попали в результат, следует добавить символы ?: после открывающей скобки:

re.Pattern = "(?:^|\s)\-([a-z0-9]+)(?:\s|$)"
' -word1
' -word3
' -word5

Первое и последнее слово успешно попали в результат. Почему же слова "word2" и "word4" не попали? Ведь перед тире есть пробел и после слова есть пробел. Чтобы понять причину, рассмотрим поиск по шагам. Первое слово успешно попадает в результат, т. к. перед тире расположено начало строки и после слова есть пробел. После поиска указатель перемещается, и строка для дальнейшего поиска примет следующий вид:

"-word1 <Указатель>-word2 -word3 -word4 -word5"

Обратите внимание на то, что перед фрагментом "-word2" больше нет пробела и тире не расположено вначале строки. Поэтому следующим совпадением будет слово "word3", и указатель снова будет перемещен:

"-word1 -word2 -word3 <Указатель>-word4 -word5"

Опять перед фрагментом "-word4" нет пробела и тире не расположено вначале строки. Поэтому следующим совпадением будет слово "word5" и поиск будет завершен. Таким образом, слова "word2" и "word4" не попадают в результат, т. к. пробел до фрагмента уже был использован в предыдущем поиске. Чтобы этого избежать следует воспользоваться положительным просмотром вперед (?=...):

Dim re, Matches, Submatches, Item, SubItem, s As String
s = "-word1 -word2 -word3 -word4 -word5"
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "(?:^|\s)\-([a-z0-9]+)(?=\s|$)"
re.Global = True
Set Matches = re.Execute(s)
For Each Item In Matches
   Set Submatches = Item.Submatches
   For Each SubItem In Submatches
      Debug.Print SubItem
   Next
Next
' word1
' word2
' word3
' word4
' word5

В этом примере мы заменили фрагмент (?:\s|$) на (?=\s|$). Поэтому все слова успешно попали в результат.

Visual Basic for Applications (VBA)
Самоучитель по VBA

Помощь сайту

Yandex-деньги: 410011140483022

ПАО Сбербанк:
Счет: 40817810855006152256
Реквизиты банка:
Наименование: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК
Корреспондентский счет: 30101810500000000653
БИК: 044030653
КПП: 784243001
ОКПО: 09171401
ОКОНХ: 96130
Скриншот реквизитов

Поиск по сайту в Яндексе