Урок 2. Шаблоны
Урок 3. Специальные приемы
Урок 4. Практика
Урок 5. Другие RegExp в AutoIt
Замечания, дополнения и исправления приветствуются (здесь)!
Урок 3. Специальные приемы
Все рассмотренные ниже приемы используются довольно редко и, возможно, вам они никогда не пригодятся, но если вы хотите изучить RexExp поглубже - то это для вас :sorcerer:
Лень, жадность и ревность квантификаторов (lazy, greedy and possessive quantifier)
Не путать с грехами
Вернемся к квантификаторам (символам повтора) "*" и "+", по-умолчанию они являются жадными, т.е. они будут "съедать" текст максимально возможной длины, но такое поведение иногда может мешать.
Пример показывает извлечение текста, находящегося между тегами
и
В результате мы получили не совсем то, что хотели
Дело в том, что шаблон "съел" весь текст между первым тегом и последним .
Для получения нужного результата нужно чтобы квантификатор "+" стал ленивым, дописываем после него знак "?" и тогда новый шаблон будет "съедать" наименьший фрагмент текста:
Можно сказать так: жадный квантификатор просматривает текст справа налево (с конца строки), а ленивый слева направо (с начала строки).
Кроме ленивого квантификаторы можно сделать ревнивыми (иногда такую квантификацию называют сверхжадностью или завистью). Такая квантификация не только захватывает текст максимальной длины, но и не отдает этот текст другим элементам шаблона.
Поясняющий пример:
Шаблон "(.+)!" сработает примерно так: "(.+)" захватит весь текст, но поскольку после этого стоит "!", то он будет вынужден вернуть восклицательный знак, и на выходе мы будем иметь строку без ! в конце.
Пример с ревнивым квантификатором:
"(.++)" "съест" весь текст и не отдаст последний символ для "!", поэтому выражение не даст совпадений шаблону.
Для чего же нужен такой "бестолковый" квантификатор? В первую очередь для ускорения работы регулярных выражений, ведь при проверке шаблонов много времени тратится именно на возвраты, непосредственно в алгоритмах поиска текста ревнивые квантификаторы используются крайне редко.
Прирост скорости при использовании ревнивых квантификаторов мизерный, так что использовать их или нет решайте сами
, но медленнее ваши выражения не станут точно. Использовать их надо так, чтобы они захватывали только нужный текст.
Пример:
В примере шаблон "(\d++)", не может захватить "лишний" текст, поэтому работает верно.
И несколько ссылок на темы, где в решениях применялись ревнивые квантификаторы:
http://autoit-script.ru/index.php?topic=1567.0
http://autoit-script.ru/index.php?topic=1483.0
http://autoit-script.ru/index.php?topic=741.0
Пример показывает извлечение текста, находящегося между тегами
Код
<td>
и
Код
</td>
Код
#include <Array.au3>
$sText = 'собака<td>слон</td><td>жираф</td><td>кот</td>рыба'
$sPattern = '<td>(.+)</td>'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = 'собака<td>слон</td><td>жираф</td><td>кот</td>рыба'
$sPattern = '<td>(.+)</td>'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
В результате мы получили не совсем то, что хотели
Дело в том, что шаблон
Код
"<td>(.+)</td>"
Код
<td>
Код
</td>
Для получения нужного результата нужно чтобы квантификатор "+" стал ленивым, дописываем после него знак "?" и тогда новый шаблон
Код
"<td>(.+?)</td>"
Код
#include <Array.au3>
$sText = 'собака<td>слон</td><td>жираф</td><td>кот</td>рыба'
$sPattern = '<td>(.+?)</td>'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = 'собака<td>слон</td><td>жираф</td><td>кот</td>рыба'
$sPattern = '<td>(.+?)</td>'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Можно сказать так: жадный квантификатор просматривает текст справа налево (с конца строки), а ленивый слева направо (с начала строки).
Кроме ленивого квантификаторы можно сделать ревнивыми (иногда такую квантификацию называют сверхжадностью или завистью). Такая квантификация не только захватывает текст максимальной длины, но и не отдает этот текст другим элементам шаблона.
Поясняющий пример:
Код
#include <Array.au3>
$sText = 'жираф!'
$sPattern = '(.+)!'
If StringRegExp($sText, $sPattern) Then
ConsoleWrite(StringRegExpReplace($sText, $sPattern, '$1') & @CRLF)
Else
ConsoleWrite('none' & @CRLF)
EndIf
$sText = 'жираф!'
$sPattern = '(.+)!'
If StringRegExp($sText, $sPattern) Then
ConsoleWrite(StringRegExpReplace($sText, $sPattern, '$1') & @CRLF)
Else
ConsoleWrite('none' & @CRLF)
EndIf
Шаблон "(.+)!" сработает примерно так: "(.+)" захватит весь текст, но поскольку после этого стоит "!", то он будет вынужден вернуть восклицательный знак, и на выходе мы будем иметь строку без ! в конце.
Пример с ревнивым квантификатором:
Код
#include <Array.au3>
$sText = 'жираф!'
$sPattern = '(.++)!'
If StringRegExp($sText, $sPattern) Then
ConsoleWrite(StringRegExpReplace($sText, $sPattern, '$1') & @CRLF)
Else
ConsoleWrite('none' & @CRLF)
EndIf
$sText = 'жираф!'
$sPattern = '(.++)!'
If StringRegExp($sText, $sPattern) Then
ConsoleWrite(StringRegExpReplace($sText, $sPattern, '$1') & @CRLF)
Else
ConsoleWrite('none' & @CRLF)
EndIf
"(.++)" "съест" весь текст и не отдаст последний символ для "!", поэтому выражение не даст совпадений шаблону.
Для чего же нужен такой "бестолковый" квантификатор? В первую очередь для ускорения работы регулярных выражений, ведь при проверке шаблонов много времени тратится именно на возвраты, непосредственно в алгоритмах поиска текста ревнивые квантификаторы используются крайне редко.
Прирост скорости при использовании ревнивых квантификаторов мизерный, так что использовать их или нет решайте сами
, но медленнее ваши выражения не станут точно. Использовать их надо так, чтобы они захватывали только нужный текст. Пример:
Код
$sText = 'abc123abc123'
$sPattern = '(\d+)'
$hTimer = TimerInit()
For $i = 1 To 100000
$aResult = StringRegExp($sText, $sPattern, 3)
Next
ConsoleWrite(TimerDiff($hTimer) & @CRLF)
$sPattern = '(\d++)'
$hTimer = TimerInit()
For $i = 1 To 100000
$aResult = StringRegExp($sText, $sPattern, 3)
Next
ConsoleWrite(TimerDiff($hTimer) & @CRLF)
$sPattern = '(\d+)'
$hTimer = TimerInit()
For $i = 1 To 100000
$aResult = StringRegExp($sText, $sPattern, 3)
Next
ConsoleWrite(TimerDiff($hTimer) & @CRLF)
$sPattern = '(\d++)'
$hTimer = TimerInit()
For $i = 1 To 100000
$aResult = StringRegExp($sText, $sPattern, 3)
Next
ConsoleWrite(TimerDiff($hTimer) & @CRLF)
В примере шаблон "(\d++)", не может захватить "лишний" текст, поэтому работает верно.
И несколько ссылок на темы, где в решениях применялись ревнивые квантификаторы:
http://autoit-script.ru/index.php?topic=1567.0
http://autoit-script.ru/index.php?topic=1483.0
http://autoit-script.ru/index.php?topic=741.0
Условия просмотра вперед и назад (lookahead и lookbehind assertions)
Данные условия позволяют находить в тексте определенные позиции.
"(?=pattern)" - позитивный просмотр вперед, т.е. это позиция в тексте перед "pattern".
Для ясности пример - нужно из текста получить значения ячеек для имен, начинащихся на "A":
Расшифровка шаблона:
"(?!pattern)" - негативный просмотр вперед, т.е. это позиция в тексте не перед "pattern".
Пример выводит из текста значения ячеек, которые начинаются не на "A":
"(?<=pattern)" - позитивный просмотр назад, т.е. это позиция в тексте после "pattern".
Пример показывает извлечение имен, которые идут после "USA":
Расшифровка шаблона:
"(?<=USA)" - позиция после USA
"\s" - пробел одна штука
"([^<]+)" - группа с захватом - любое количество любых символов кроме "<", т.е. весь текст после пробела и до <.
"(?" - негативный просмотр назад, т.е. это позиция в тексте не после "pattern".
Пример показывает извлечение имен, которые идут не после "USA":
Для лучшего понимания условий можно представить, что они указывают где поставить условный курсор в тексте - до или после определенной последовательности символов, при этом условия не захватывают символы.
Для ясности пример - нужно из текста
Код
<td>Mark 25</td><td>Twen 15</td><td>Anna 30</td><td>Bob 40</td><td>Antony 60</td>
Код
#include <Array.au3>
$sText = '<td>Mark 25</td><td>Twen 15</td><td>Anna 30</td><td>Bob 40</td><td>Antony 60</td>'
$sPattern = '<td>(?=A)(\S+\s+\d+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = '<td>Mark 25</td><td>Twen 15</td><td>Anna 30</td><td>Bob 40</td><td>Antony 60</td>'
$sPattern = '<td>(?=A)(\S+\s+\d+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Расшифровка шаблона:
Код
"<td>" - просто строка из литеральных символов
"(?=A)" - указывает на позицию в тексте перед буквой "A"
"(.+?\d+)" - группа с захватом: ".+?" - любое количество любых символов (но не менее одного), ленивый квантификатор "?" не даст съесть весь текст, а только до цифр, "\d+" - любое количество цифровых символов (но не менее одного).
"(?=A)" - указывает на позицию в тексте перед буквой "A"
"(.+?\d+)" - группа с захватом: ".+?" - любое количество любых символов (но не менее одного), ленивый квантификатор "?" не даст съесть весь текст, а только до цифр, "\d+" - любое количество цифровых символов (но не менее одного).
"(?!pattern)" - негативный просмотр вперед, т.е. это позиция в тексте не перед "pattern".
Пример выводит из текста значения ячеек, которые начинаются не на "A":
Код
#include <Array.au3>
$sText = '<td>Mark 25</td><td>Twen 15</td><td>Anna 30</td><td>Bob 40</td><td>Antony 60</td>'
$sPattern = '<td>(?!A)(\S+\s+\d+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = '<td>Mark 25</td><td>Twen 15</td><td>Anna 30</td><td>Bob 40</td><td>Antony 60</td>'
$sPattern = '<td>(?!A)(\S+\s+\d+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
"(?<=pattern)" - позитивный просмотр назад, т.е. это позиция в тексте после "pattern".
Пример показывает извлечение имен, которые идут после "USA":
Код
#include <Array.au3>
$sText = '<td>USA Alex</td><td>Ireland Mark</td><td>USA Anna</td><td>India Louis</td>'
$sPattern = '(?<=USA)\s([^<]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = '<td>USA Alex</td><td>Ireland Mark</td><td>USA Anna</td><td>India Louis</td>'
$sPattern = '(?<=USA)\s([^<]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Расшифровка шаблона:
"(?<=USA)" - позиция после USA
"\s" - пробел одна штука
"([^<]+)" - группа с захватом - любое количество любых символов кроме "<", т.е. весь текст после пробела и до <.
"(?" - негативный просмотр назад, т.е. это позиция в тексте не после "pattern".
Пример показывает извлечение имен, которые идут не после "USA":
Код
#include <Array.au3>
$sText = '<td>USA Alex</td><td>Ireland Mark</td><td>USA Anna</td><td>India Louis</td>'
$sPattern = '(?<!USA)\s([^<]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = '<td>USA Alex</td><td>Ireland Mark</td><td>USA Anna</td><td>India Louis</td>'
$sPattern = '(?<!USA)\s([^<]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Для лучшего понимания условий можно представить, что они указывают где поставить условный курсор в тексте - до или после определенной последовательности символов, при этом условия не захватывают символы.
Условные подмаски (conditional subpatterns)
Использование таких подмасок дает возможность направить RegEx в нужном направлении, при выполнении (или невыполнении) условия.
В качестве условий выступают рассмотренные выше Lookahead и Lookbehind условия.
Возможный формат условных подмасок:
"(?(condition)yes-pattern)"
"(?(condition)yes-pattern|no-pattern)"
"condition" - само условие
"yes-pattern" - подмаска, выполняемая при выполнении условия
"no-pattern" - подмаска, используемая при невыполнении условия
Пример (шаблон типа "(?(condition)yes-pattern|no-pattern)") из многострочного текста для стран, начинающихся на букву "Р" выводит всю строку, а для других только название страны:
Расшифровка шаблона:
"'Страна:\s" - слово "Страна" с пробелом на конце
"(?=Р)" - условие позитивного просмотра вперед - позиция перед буквой "Р", т.е. сначала идет "Страна, пробел", а потом буква "Р", перед которой и встанет курсор (если конечно найдет такую позицию)
".+" - захватит всю строку до символа начала новой строки
"[^;]+" - захватит всё, пока не встретит символ ";"
Второй пример (шаблон типа "(?(condition)yes-pattern)") из текста выводит слова, которые начинаются на с буквы "т":
Расшифровка шаблона:
"(?:\s|^)" - группа без захвата - начало строки или пробельный символ
"(?=т)" - позиция перед буквой "т" (сразу за пробелом или началом строки)
"\S+" - любое количество непробельных символов, но не менее одного (этим и производится захват слова от буквы "т" до конца слова)
Возможный формат условных подмасок:
"(?(condition)yes-pattern)"
"(?(condition)yes-pattern|no-pattern)"
"condition" - само условие
"yes-pattern" - подмаска, выполняемая при выполнении условия
"no-pattern" - подмаска, используемая при невыполнении условия
Пример (шаблон типа "(?(condition)yes-pattern|no-pattern)") из многострочного текста для стран, начинающихся на букву "Р" выводит всю строку, а для других только название страны:
Код
#include <Array.au3>
$sText = 'Страна: Китай; Столица: Пекин; Население: 1347млн.' & @CRLF
$sText &= 'Страна: Россия; Столица: Москва; Население: 144млн.' & @CRLF
$sText &= 'Страна: США; Столица: Вашингтон; Население: 313млн.' & @CRLF
$sText &= 'Страна: Румыния; Столица: Бухарест; Население: 21млн.'
$sPattern = 'Страна:\s(?(?=Р).+|[^;]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = 'Страна: Китай; Столица: Пекин; Население: 1347млн.' & @CRLF
$sText &= 'Страна: Россия; Столица: Москва; Население: 144млн.' & @CRLF
$sText &= 'Страна: США; Столица: Вашингтон; Население: 313млн.' & @CRLF
$sText &= 'Страна: Румыния; Столица: Бухарест; Население: 21млн.'
$sPattern = 'Страна:\s(?(?=Р).+|[^;]+)'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Расшифровка шаблона:
"'Страна:\s" - слово "Страна" с пробелом на конце
"(?=Р)" - условие позитивного просмотра вперед - позиция перед буквой "Р", т.е. сначала идет "Страна, пробел", а потом буква "Р", перед которой и встанет курсор (если конечно найдет такую позицию)
".+" - захватит всю строку до символа начала новой строки
"[^;]+" - захватит всё, пока не встретит символ ";"
Второй пример (шаблон типа "(?(condition)yes-pattern)") из текста выводит слова, которые начинаются на с буквы "т":
Код
#include <Array.au3>
$sText = 'текст окно тело горн овес порт отвертка кино тоник'
$sPattern = '(?:\s|^)(?(?=т)\S+)'
$aResult = StringRegExp($sText, $sPattern, 3)
For $i = 0 To UBound($aResult) - 1
$aResult[$i] = '[' & $aResult[$i] & ']'
Next
_ArrayDisplay($aResult)
$sText = 'текст окно тело горн овес порт отвертка кино тоник'
$sPattern = '(?:\s|^)(?(?=т)\S+)'
$aResult = StringRegExp($sText, $sPattern, 3)
For $i = 0 To UBound($aResult) - 1
$aResult[$i] = '[' & $aResult[$i] & ']'
Next
_ArrayDisplay($aResult)
Расшифровка шаблона:
"(?:\s|^)" - группа без захвата - начало строки или пробельный символ
"(?=т)" - позиция перед буквой "т" (сразу за пробелом или началом строки)
"\S+" - любое количество непробельных символов, но не менее одного (этим и производится захват слова от буквы "т" до конца слова)
Цитата важное замечание
Есть один неприятный момент - при использовании условных масок (даже при использовании групп без захвата) в вывод попадает все что лежит за условной подмаской и записано в шаблоне, во всяком случае у меня не получилось иначе.
Т.е. в вывод второго примера попадают пробелы. Если в первом примере часть шаблон изменить на такой: "(?:рана:\s)(?(?=Р).+|[^;]+)" или "рана:\s(?(?=Р).+|[^;]+)", то результат будет одинаков (кусок слова "рана" из "Страна" попадет в вывод).
Если кто-то найдет решение - прошу написать об этом, но ИМХО тут дело не в условных масках, а в самом механизме RegExp.
Т.е. в вывод второго примера попадают пробелы. Если в первом примере часть шаблон изменить на такой: "(?:рана:\s)(?(?=Р).+|[^;]+)" или "рана:\s(?(?=Р).+|[^;]+)", то результат будет одинаков (кусок слова "рана" из "Страна" попадет в вывод).
Если кто-то найдет решение - прошу написать об этом, но ИМХО тут дело не в условных масках, а в самом механизме RegExp.
Атомарная группировка (atomic grouping)
Это группировка без захвата и без возврата найденных значений.
"(?>pattern)" - такая группировка обладает свойствами группы без захвата - "(?:pattern)" и ревнивого квантификатора одновременно, т.е. всё что будет соответствовать такому шаблону останется при нем и не попадет в вывод RegExp.
Пример выводит из текста имена, чей возраст лежит в пределах от 20 до 39 лет:
Расшифровка шаблона:
"\S+\s" - любое количество непробельных символов, но не менее одного и один пробел
"(?>2|3)" - атомарная группировка - выбор между цифрами 2 и 3
"\d" - один цифровой символ
Поскольку атомарная группировка не захватывает символы, то в вывод не попадает то что находится внутри скобок, а то что соответствует шаблону в целом.
Второй пример демонстрирует ревность атомарной группировки:
Атомарная группировка является сверхжадной и данный пример не выдаст результата, т.к. часть шаблона "(?>\d+)" "съест" все цифры до конца строки и не отдаст последний символ для "4" (чтобы убедиться в этом исправьте ">" на ":" - тогда данная группа перестанет быть сверхжадной и RegExp даст совпадение)
Кроме вышеуказанных свойств атомарная группировка как и сверхжадная квантификация не делает откатов, а значит экономит время на проверку выражения.
Пример выводит из текста имена, чей возраст лежит в пределах от 20 до 39 лет:
Код
#include <Array.au3>
$sText = 'Anna 25 Joe 30 Carl 20 Piter 13 Lora 21 Nicole 33 Ted 22 Bob 42'
$sPattern = '\S+\s(?>2|3)\d'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = 'Anna 25 Joe 30 Carl 20 Piter 13 Lora 21 Nicole 33 Ted 22 Bob 42'
$sPattern = '\S+\s(?>2|3)\d'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Расшифровка шаблона:
"\S+\s" - любое количество непробельных символов, но не менее одного и один пробел
"(?>2|3)" - атомарная группировка - выбор между цифрами 2 и 3
"\d" - один цифровой символ
Поскольку атомарная группировка не захватывает символы, то в вывод не попадает то что находится внутри скобок, а то что соответствует шаблону в целом.
Второй пример демонстрирует ревность атомарной группировки:
Код
#include <Array.au3>
$sText = '1234'
$sPattern = '(?>\d+)4'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
$sText = '1234'
$sPattern = '(?>\d+)4'
$aResult = StringRegExp($sText, $sPattern, 3)
_ArrayDisplay($aResult)
Атомарная группировка является сверхжадной и данный пример не выдаст результата, т.к. часть шаблона "(?>\d+)" "съест" все цифры до конца строки и не отдаст последний символ для "4" (чтобы убедиться в этом исправьте ">" на ":" - тогда данная группа перестанет быть сверхжадной и RegExp даст совпадение)
Кроме вышеуказанных свойств атомарная группировка как и сверхжадная квантификация не делает откатов, а значит экономит время на проверку выражения.



