Ганс, я клюну и дополню свой предыдущий ответ. Вы сказали, что хотите «чего-то более полного», поэтому я надеюсь, что вы не будете возражать против длинного ответа - просто пытаетесь угодить. Начнем с некоторой предыстории.
Во-первых, это отличный вопрос. Часто возникают вопросы о сопоставлении определенных шаблонов, за исключением определенных контекстов (например, внутри блока кода или внутри скобок). Эти вопросы часто приводят к довольно неудобным решениям. Так что ваш вопрос о множественных контекстах - особая проблема.
Сюрприз
Удивительно, но существует по крайней мере одно эффективное решение, которое является общим, простым в реализации и приятным в обслуживании. Он работает со всеми разновидностями регулярных выражений, которые позволяют вам проверять группы захвата в вашем коде. И он отвечает на ряд общих вопросов, которые на первый взгляд могут показаться отличными от вашего: «сопоставить все, кроме пончиков», «заменить все, кроме ...», «сопоставить все слова, кроме тех, что в черном списке моей мамы», «игнорировать теги "," соответствует температуре, если не выделено курсивом "...
К сожалению, этот метод малоизвестен: по моим оценкам, из двадцати SO-вопросов, которые могли бы его использовать, только один имеет один ответ, в котором он упоминается, что означает, может быть, один из пятидесяти или шестидесяти ответов. Смотрите мой обмен с Коби в комментариях. Этот метод подробно описан в этой статье, в которой он (оптимистично) назван «лучшим трюком с регулярными выражениями». Не вдаваясь в подробности, я постараюсь дать вам четкое представление о том, как работает эта техника. Для получения более подробной информации и примеров кода на разных языках я рекомендую вам обратиться к этому ресурсу.
Наиболее известная вариация
Существует вариант использования синтаксиса, специфичного для Perl и PHP, который выполняет то же самое. Вы увидите его на SO в руках регулярных выражений мастеров , таких как CasimiretHippolyte и Хамза . Я расскажу вам больше об этом ниже, но я сосредоточусь здесь на общем решении, которое работает со всеми разновидностями регулярных выражений (при условии, что вы можете проверять группы захвата в своем коде).
Всем спасибо за предысторию, zx81 ... А рецепт какой?
Ключевой факт
Метод возвращает совпадение в захвате группы 1. Его совершенно не волнует общий матч.
Фактически, уловка состоит в том, чтобы сопоставить различные контексты, которые нам не нужны (объединяя эти контексты в цепочку с помощью |
ИЛИ / чередования), чтобы «нейтрализовать их». После сопоставления всех нежелательных контекстов последняя часть чередования соответствует тому, что мы действительно хотим, и фиксирует это в Группе 1.
Общий рецепт
Not_this_context|Not_this_either|StayAway|(WhatYouWant)
Это будет совпадение Not_this_context
, но в некотором смысле совпадение попадет в мусорное ведро, потому что мы не будем смотреть на общие совпадения: мы будем смотреть только на захваты группы 1.
В вашем случае, игнорируя ваши цифры и три контекста, мы можем:
s1|s2|s3|(\b\d+\b)
Обратите внимание: поскольку мы на самом деле сопоставляем s1, s2 и s3 вместо того, чтобы пытаться избежать их с помощью поиска, отдельные выражения для s1, s2 и s3 могут оставаться ясными как день. (Это подвыражения по обе стороны от a |
)
Все выражение можно записать так:
(?m)^.*\.$|\([^\)]*\)|if\(.*?//endif|(\b\d+\b)
Посмотрите эту демонстрацию (но сосредоточьтесь на группах захвата в нижней правой панели.)
Если вы мысленно попытаетесь разделить это регулярное выражение по каждому |
разделителю, на самом деле это будет всего лишь серия из четырех очень простых выражений.
Это особенно хорошо читается для вкусов, которые поддерживают свободный интервал.
(?mx)
### s1: Match line that ends with a period ###
^.*\.$
| ### OR s2: Match anything between parentheses ###
\([^\)]*\)
| ### OR s3: Match any if(...//endif block ###
if\(.*?//endif
| ### OR capture digits to Group 1 ###
(\b\d+\b)
Это исключительно легко читать и поддерживать.
Расширение регулярного выражения
Если вы хотите игнорировать больше ситуаций s4 и s5, вы добавляете их в большем количестве слева:
s4|s5|s1|s2|s3|(\b\d+\b)
Как это работает?
Нежелательные контексты добавляются в список альтернатив слева: они будут совпадать, но эти общие совпадения никогда не проверяются, поэтому их сопоставление - это способ поместить их в «мусорное ведро».
Однако желаемый контент сохраняется в Группу 1. Затем необходимо программно проверить, установлена ли Группа 1 и не пуста. Это тривиальная задача программирования (и позже мы поговорим о том, как это делается), особенно с учетом того, что она оставляет вам простое регулярное выражение, которое вы можете сразу понять и при необходимости изменить или расширить.
Я не всегда являюсь поклонником визуализаций, но эта хорошо показывает, насколько прост метод. Каждая «строка» соответствует потенциальному совпадению, но только нижняя строка попадает в группу 1.
Демо Debuggex
Вариант Perl / PCRE
В отличие от общего решения, приведенного выше, существует вариант для Perl и PCRE, который часто встречается в SO, по крайней мере, в руках богов регулярных выражений, таких как @CasimiretHippolyte и @HamZa. Это:
(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant
В твоем случае:
(?m)(?:^.*\.$|\([^()]*\)|if\(.*?//endif)(*SKIP)(*F)|\b\d+\b
Этот вариант немного проще в использовании, потому что контент, сопоставленный в контекстах s1, s2 и s3, просто пропускается, поэтому вам не нужно проверять захваты Группы 1 (обратите внимание, что скобки исчезли). Матчи содержат толькоwhatYouWant
Обратите внимание , что (*F)
, (*FAIL)
и (?!)
все то же самое. Если вы хотите быть более непонятным, вы можете использовать(*SKIP)(?!)
демо для этой версии
Приложения
Вот некоторые общие проблемы, которые часто можно легко решить с помощью этого метода. Вы заметите, что при выборе слова некоторые из этих проблем могут казаться разными, хотя на самом деле они практически идентичны.
- Как я могу сопоставить foo кроме любого тега вроде
<a stuff...>...</a>
?
- Как я могу сопоставить foo, кроме
<i>
тега или фрагмента javascript (дополнительные условия)?
- Как я могу сопоставить все слова, которых нет в этом черном списке?
- Как я могу игнорировать что-либо внутри блока SUB ... END SUB?
- Как мне сопоставить все, кроме ... s1 s2 s3?
Как запрограммировать захват группы 1
Вы не сделали этого в отношении кода, но для завершения ... Код для проверки Группы 1, очевидно, будет зависеть от выбранного вами языка. Во всяком случае, он не должен добавлять больше пары строк в код, который вы будете использовать для проверки совпадений.
Если вы сомневаетесь, я рекомендую вам взглянуть на раздел примеров кода в упомянутой ранее статье, в котором представлен код для нескольких языков.
Альтернативы
В зависимости от сложности вопроса и используемого механизма регулярных выражений существует несколько альтернатив. Вот два, которые могут применяться в большинстве ситуаций, включая несколько условий. На мой взгляд, ни один из них не так привлекателен, как s1|s2|s3|(whatYouWant)
рецепт, хотя бы потому, что ясность всегда побеждает.
1. Замените, затем сопоставьте.
Хорошее решение, которое кажется хакерским, но хорошо работает во многих средах, - это работа в два этапа. Первое регулярное выражение нейтрализует контекст, который вы хотите игнорировать, заменяя потенциально конфликтующие строки. Если вы хотите только сопоставить, вы можете заменить его пустой строкой, а затем выполнить сопоставление на втором этапе. Если вы хотите заменить, вы можете сначала заменить строки, которые нужно игнорировать, чем-то особенным, например, окружить ваши цифры цепочкой фиксированной ширины @@@
. После этой замены вы можете заменить то, что действительно хотели, тогда вам придется вернуть свои отличительные @@@
строки.
2. Обращения.
Ваш исходный пост показал, что вы понимаете, как исключить одно условие с помощью поиска. Вы сказали, что C # отлично подходит для этого, и вы правы, но это не единственный вариант. Варианты регулярных выражений .NET, найденные, например, в C #, VB.NET и Visual C ++, а также все еще экспериментальный regex
модуль для замены re
в Python - единственные два известных мне движка, которые поддерживают просмотр назад с бесконечной шириной. С помощью этих инструментов одно условие в одном просмотре назад может позаботиться о просмотре не только назад, но и в соответствии с совпадением и за его пределами, избегая необходимости согласовывать с просмотром вперед. Еще условия? Больше поисков.
Переработав регулярное выражение, которое у вас было для s3 в C #, весь шаблон будет выглядеть так.
(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
Но теперь вы знаете, что я не рекомендую это, верно?
Удаления
@HamZa и @Jerry предложили мне упомянуть дополнительный трюк для случаев, когда вы стремитесь просто удалить WhatYouWant
. Вы помните, что рецепт совпадения WhatYouWant
( включение его в группу 1) был s1|s2|s3|(WhatYouWant)
правильным? Чтобы удалить все экземпляры WhatYouWant
, вы измените регулярное выражение на
(s1|s2|s3)|WhatYouWant
Для строки замены вы используете $1
. Здесь происходит то, что для каждого s1|s2|s3
сопоставленного $1
экземпляра замена заменяет этот экземпляр самим собой (на который ссылается $1
). С другой стороны, при WhatYouWant
сопоставлении она заменяется пустой группой и ничем другим - и поэтому удаляется. Посмотрите эту демонстрацию , спасибо @HamZa и @Jerry за предложение этого замечательного дополнения.
Замены
Это подводит нас к заменам, о которых я кратко коснусь.
- При замене ничем см. Трюк с удалением выше.
- При замене, если вы используете Perl или PCRE, используйте
(*SKIP)(*F)
вариант, упомянутый выше, чтобы точно соответствовать тому, что вы хотите, и выполните прямую замену.
- В других вариантах в вызове функции замены проверьте соответствие с помощью обратного вызова или лямбда-выражения и замените, если установлена Группа 1. Если вам нужна помощь с этим, в уже упомянутой статье вы найдете код на разных языках.
Радоваться, веселиться!
Нет, подождите, это еще не все!
Ах, нет, я приберегу это для своих мемуаров в двадцати томах, которые выйдут следующей весной.
\K
нет специального синтаксиса php. Пожалуйста, уточните и поясните, что вы хотите сказать. Если вы хотите сказать нам, что вам не нужно «сложное» решение, вы должны сказать, что для вас сложно и почему.