Обучение - Регулярные выражения

Это небольшое руководство по разгадке тайн загадочного StringRegExp().

StringRegExp( "test", "pattern" [, flag ] )

"test" = Строка, в которой выполняется поиск совпадений.
"pattern" = Строка (шаблон), состоящая из определенных ключевых символов (метасимволов), которые позволяют ТОЧНО узнать, что необходимо получить. Но это не аналог ключевых слов And, Or, Not, а это либо совпадение, либо не совпадение.
flag [необязательный] = Указывает функции, хотите ли вы узнать найдено совпадение по шаблону или нет, или вы хотите получить первое совпадение с шаблоном, или вы хотите получить все совпадения с шаблоном в тексте "test".

Основы

Как вы уже наверное догадались, только "pattern" - строка шаблона, является самой трудной частью функции StringRegExp() (далее сокращённо: SRE). Я думаю лучше представлять шаблон, как последовательный поиск строки посимвольно. Существуют различные способы поиска определенного символа: Если вы хотите найти совпадение со строкой "test", это будет очень просто. Функции SRE сообщается, что необходимо найти первый символ строки - "t". Если она его находит, то предполагает, что имеется совпадение последующей части шаблона, и пытается доказать, что найденное является или не является совпадением. То есть, если следующий символ "e", то это ещё может оказаться совпадением. Предположим следующая буква "x", то SRE немедленно узнает, что это не является совпадением, потому что третий символ для поиска "s".
 

Пример 1

MsgBox(0, "SRE, пример 1", StringRegExp("text", 'test'))

В этом примере, окно сообщения выдаст "0", это означает, что шаблон "test" не был найден в тестовой строке "text". Я знаю, что это слишком просто, но теперь вы знаете почему он не был найден.

Следующий способ задать шаблон - использование диапазона/набора символов ("[ ... ]"). Это можно сравнить к логической функцией "ИЛИ". Давайте используем предыдущий пример. Предположим нам необходимо найти строку "test" или "text". Итак, я начинаю искать шаблон, рассуждая подобно функции SRE: первый символ совпадения - "t", далее буква "e", это одинаково для обоих строк, которые мы ищем. Теперь необходимо найти "s" или "x", поэтому можно использовать набор в качестве подмены: "[sx]" означающий соответствию "s" или "x". Далее последняя буква "t".

Пример 2

MsgBox(0, "SRE, пример 2", StringRegExp("text", 'te[sx]t'))
MsgBox(0, "SRE, пример 2", StringRegExp("test", 'te[sx]t'))

Оба окна сообщения должны выдать "1", потому что шаблон соответствует обоим строкам "test" и "text".

Вы также можете указать количество повторов символа используя "{число повторов}" или вы можете указать диапазон, используя "{min, max}". Первый пример ниже содержит излишние указания повторов, но показывает что имеется в виду:

Пример 3

MsgBox(0, "SRE, пример 3", StringRegExp("text", 't{1}e{1}[sx]{1}t{1}'))
MsgBox(0, "SRE, пример 3", StringRegExp("aaaabbbbcccc", 'b{4}'))



Более чем основы

Теперь вы наверное думаете "Разве это не аналог известной функции StringInStr()?". Допустим с использованием "flag" равным 0, в большинстве случаев вы правы. Но SRE является гораздо более мощным инструментом. По мере использования SRE все больше и больше, вы поймёте, что меньше и меньше знаете о типе шаблона, который ищете. Есть способы наименее определяющие конкретность каждого символа, который вы задаёте в шаблоне. Возьмем, к примеру, строки из лог-файла: "Были 18 листов оставшихся в пачке бумаги". Вы хотите узнать количество оставшихся листов. Но вы не можете использовать, StringInStr(), потому что вы не ищете "18", но ищете неизвестное число "????", которое состоит из любых цифр.

Вот как бы я составил шаблон поиска. Смотрите, что нужно делать, не зная то, что вы хотите найти:
1) Вы знаете, что искомое ВСЕГДА содержит только цифры.
2) Вы знаете, что искомое ИНОГДА состоит из 2-х символов.
2а) Вы знаете, что полная пачка содержит 500 листов бумаги.
2б) Вы знаете, что пустая пачка содержит 0 листов.
3) Вы знаете, что число ВСЕГДА состоит от 1 до 3 символов.
4) Вы знаете, что в строке нет других цифр, кроме количества листов.

На данный момент, я хотел бы установить значение FLAG равным "1" и группировать символы "()". Значение flag = "1" означает, что SRE не только указывает на совпадение шаблону, но и возвращает массив, где каждый элемент массива состоит из захваченных "групп" символов. Так что не уходя от темы слишком далеко попробуйте этот пример:

Пример 4

$asResult = StringRegExp("Это тестовый пример", '(тест)', 1)
If @error == 0 Then
    MsgBox(0, "SRE, пример 4", $asResult[0])
EndIf
$asResult = StringRegExp("Это тестовый пример", '(те)(ст)', 1)
If @error == 0 Then
    MsgBox(0, "SRE, пример 4", $asResult[0] & "," & $asResult[1])
EndIf


Итак, первый шаблон должен соответствовать какому-нибудь месту текстовой строки. Если это так, то SRE сообщит о "захвате" любой группы ("()") и поместит их в возвращаемый массив. Вы можете использовать множественный захват, о чем свидетельствует вторая часть кода в примере 4.

Ладно, вернемся к лог-файлу. Теперь, когда мы знаем, как "захватывать" текст, давайте составим наш шаблон: Поскольку мы уже определились, что нужно искать число, известно 3 способа указать "соответствие любой цифре": "[:digit:]", "[0-9]", и "\d". Первый наверно самый лёгкий для понимания. Есть несколько классов (digit, alnum, space, и т.д. Смотрите в этой справке полный список) вы можете использовать определённый набор символов, одним из которых является набор цифр. Обычный диапазон можно задать с помощью "[0-9]", что означает все цифры от 0 до 9. Метасимвол "\d" означает тоже, что первые два. Между этими 3-мя способами нет различия и со всеми SRE существуют несколько способов создать любой шаблон.

Итак, первоначально нам из известно, что необходимо захватить цифры, поэтому указываем это открытием скобки "(". Далее, нам известно, что количество символов от 1 до 3 и все состоят из цифр, поэтому наш шаблон теперь выглядит так: "([0-9]{1,3}". И, наконец, закрываем его закрывающей скобкой, обозначающий конец группы: "([0-9]{1,3})". Давайте попробуем:

Пример 5

$asResult = StringRegExp("Были 18 листов оставшихся в пачке бумаги.", '([0-9]{1,3})', 1)
If @error == 0 Then
    MsgBox(0, "SRE, пример 5", $asResult[0])
EndIf


В примере вы увидите, диалоговое окно корректно отображающее число "18".

Далее необходимо предотвратить захват группы. Для этого открываем группу таким способом: "(?:" вместо обычного "(". Допустим, что ваш log-файл сообщает "Вы использовали 36 из 279 страниц." Теперь, если вы запустите пример 5, то вы найдёте "36" вместо "279". Теперь то, что я люблю делать - определить разницу между числами. Одно число - то, которое выскакивает в диалоговом окне, второе число всегда содержит последующий пробел и слово "страниц". Можно было бы изменить предыдущий шаблон "([0-9]{1,3} страниц)", но что если наш скрипт должен просто отображать начальное количество страниц, без присоединение текста " страниц" в конец числа? Здесь вы можете использовать группы без захвата, чтобы решить эту задачу.

Пример 6

$asResult = StringRegExp("Вы использовали 36 из 279 страниц.", '([0-9]{1,3})(?: страниц)', 1)
If @error == 0 Then
    MsgBox(0, "SRE, пример 6", $asResult[0])
EndIf


Это можно продолжать бесконечно, но в основном хотелось выложить основу работы регулярных выражений, и то как "думает" SRE. Имейте ввиду:
- Не забывайте воспринимать части шаблона как за один символ
- Функция StringRegExp() ищет первый символ указанный в шаблоне, далее уже ваша задача - предоставить достаточно подтверждений, чтобы "доказать" является это или не является точным совпадением. Пример 6 наглядно показывает это.
- Помните, набор [ ... ] означает ИЛИ ([xyz] соответствует одному из символов "x" ИЛИ "y", ИЛИ "z")
Если у вас есть какие-либо вопросы, обратитесь сначала к файлу справки! Он подробно объясняет синтаксис, который поставляется вместе с SRE. Обращайте внимание на группу "повторителей символов". Это может сделать ваш шаблон более читабельным заменяя определенные символы для диапазонов. Например: "*" эквивалентно {0,} или диапазону от 0 до любого количества символов.

Удачи. Регулярные выражения могут значительно уменьшить длину вашего кода, и сделать его легко понятным для последующего изменения. Исправления и отзывы приветствуются!

Полезные ресурсы

Статья на Wikipedia - Регулярные выражения - Спасибо blindwig.

Учебник регулярных выражений за 30 минут - от Jim Hollenhorst.


GUI для тестирования различных шаблонов StringRegExp() - Спасибо steve8tch. Credit: w0uter

 



Благодарность neogia за этот урок.