Ганс, я вийму приманку і м'якоть своєї попередньої відповіді. Ви сказали, що хочете "чогось більш повного", тому я сподіваюся, що ви не заперечуєте проти довгої відповіді - просто намагаючись догодити. Почнемо з деякого тла.
По-перше, це відмінне питання. Часто виникають питання щодо відповідності певним шаблонам, за винятком певних контекстів (наприклад, у кодовому блоці чи всередині дужок). Ці питання часто породжують досить незручні рішення. Тож ваше запитання щодо кількох контекстів є особливим завданням.
Сюрприз
Дивно, але є принаймні одне ефективне рішення, яке є загальним, простим у виконанні та задоволенням у обслуговуванні. Він працює з усіма ароматами регулярного вибору, які дозволяють вам перевірити групи захоплення у вашому коді. І трапляється відповісти на ряд поширених запитань, які спочатку можуть звучати інакше, ніж ваше: "відповідати всім, крім пончиків", "замінити всіх, крім ...", "відповідати всім словам, крім тих, що в чорному списку моєї мами", "ігнорувати теги "," температура відповідності, крім курсиву "...
На жаль, методика недостатньо відома: я вважаю, що у двадцяти запитаннях, які можуть використати її, є лише одна відповідь, яка згадує її - це означає, можливо, один із п’ятдесяти чи шістдесяти відповідей. Дивіться мій обмін з Кобі в коментарях. Ця методика описана в цій статті в глибині, яка називає її (оптимістично) "найкращим трюком з регексу". Не вдаючись до деталей, я спробую зрозуміти, як працює техніка. Для отримання більш детальної інформації та зразків коду на різних мовах я рекомендую вам проконсультуватися з цим ресурсом.
Більш відома варіація
Існує варіант із використанням синтаксису, характерного для 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.

Демогрійна демонстрація
Варіант 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 # чудово підходить для цього, і ви маєте рацію, але це не єдиний варіант. Аромати .gege .gege .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. Будь ласка, докладіть і поясніть, що ви хочете сказати. Якщо ви прагнете сказати нам, що вам не потрібно "складне" рішення, ви повинні сказати, що для вас складно і чому.