Почему в regex неправильно работает lookahead negative?
У меня имеется regex:
(^|])[^\[]*#
Он предназначен для того, чтобы находить символы #, которые не заключены в квадратные скобки (скобки идут всегда парами, это проверяется отдельно). Если этот regex выдавал соответствие, то я бросал ошибку. Я использовал его для проверки примерно вот таких текстов:
Text text [#] text text [text#text] text text [text#].
Представленный выше текст валиден, в нём не будет найдено совпадений.
Мне пришлось поменять условие на противоположное. То есть мне надо сделать так, чтобы текст был валиден, если бы он соответствовал regex. Тогда я использовал lookahead negative таким образом:
(?!(^|])[^\[]*#)^.*$
Такой текст не выдаёт соответствие (как и задумывалось):
Text # text [#] text text [text#text] text text [text#].
а вот такой почему-то проходит:
Text text [#] # text text [text#text] text text [text#].
Не понимаю, где может быть ошибка? Может быть я не знаю каких-то тонкостей в работе с lookahead negative в регулярных выражениях?
Ответы (1 шт):
Мою проблему решает следующий regex:
^((?!(^|])[^\[]*#).)*$
Любопытно, что я пытался использовать похожий regex ранее, но он не давал нужного результата:
^(?!(^|])[^\[]*#).*$
Что здесь происходит?
Решение я нашёл вот здесь. Также там имеется объяснение и я его перевёл как смог (принимаю правки).
Данное решение работает, потому что эти конструкции с прогнозами, которые называются lookaround (обозреватель, если очень грубо) не поглощают символы, а утверждают/подтверждают что-либо. Именно поэтому здесь: ((?!(^|])[^\[]*#).) перед тем, как поглотить один любой символ (точка в конце, на которую и идёт прогноз), идёт анализ: а не будет ли там дальше то-слово-которое-нельзя-называть?
Это всего лишь прогнозирование, поэтому достаточно всего лишь одного символа, чтобы спрогнозировать целую последовательность символов, так как этот один символ не поглощается, а служит триггером для начала анализа.
То есть наш regex говорит: в тексте может быть любое количество символов (смотрим на эту часть: .)*), и на каждом символе будет запускаться прогнозирование.
По-видимому в моей предыдущей версии: ^(?!(^|])[^\[]*#).*$ прогноз шёл один раз в самом начале на всю строку. Именно поэтому такой текст проходил проверку:
Text text [#] # text text [text#text] text text [text#].
Так как вот это часть: Text text [#] # вовсе не соответствует этому выражению: (^|])[^\[]*#, если мы рассматриваем строку с самого начала.
Разумеется, производительность данного решения оставляет желать лучшего - это следует учитывать.
P.S.: я принимаю любые правки и приглашаю профессионалов, которые меня подправят!