Почему в 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 шт):

Автор решения: Zhenyria

Мою проблему решает следующий regex:

^((?!(^|])[^\[]*#).)*$

Любопытно, что я пытался использовать похожий regex ранее, но он не давал нужного результата:

^(?!(^|])[^\[]*#).*$

Что здесь происходит?

Решение я нашёл вот здесь. Также там имеется объяснение и я его перевёл как смог (принимаю правки).

Данное решение работает, потому что эти конструкции с прогнозами, которые называются lookaround (обозреватель, если очень грубо) не поглощают символы, а утверждают/подтверждают что-либо. Именно поэтому здесь: ((?!(^|])[^\[]*#).) перед тем, как поглотить один любой символ (точка в конце, на которую и идёт прогноз), идёт анализ: а не будет ли там дальше то-слово-которое-нельзя-называть?

Это всего лишь прогнозирование, поэтому достаточно всего лишь одного символа, чтобы спрогнозировать целую последовательность символов, так как этот один символ не поглощается, а служит триггером для начала анализа.

То есть наш regex говорит: в тексте может быть любое количество символов (смотрим на эту часть: .)*), и на каждом символе будет запускаться прогнозирование.

По-видимому в моей предыдущей версии: ^(?!(^|])[^\[]*#).*$ прогноз шёл один раз в самом начале на всю строку. Именно поэтому такой текст проходил проверку:

Text text [#] # text text [text#text] text text [text#].

Так как вот это часть: Text text [#] # вовсе не соответствует этому выражению: (^|])[^\[]*#, если мы рассматриваем строку с самого начала.

Разумеется, производительность данного решения оставляет желать лучшего - это следует учитывать.

P.S.: я принимаю любые правки и приглашаю профессионалов, которые меня подправят!

→ Ссылка