Шаблон Regex збігається, виключаючи, коли… / За винятком між


108

- редагувати - в поточних відповідях є корисні ідеї, але я хочу щось більш повне, що я можу на 100% зрозуміти і використовувати повторно; тому я встановив щедроту. Також ідеї, які працюють скрізь, для мене краще, ніж не стандартний синтаксис\K

Це питання стосується того, як я можу відповідати шаблону, за винятком деяких ситуацій s1 s2 s3. Я даю конкретний приклад, щоб показати своє значення, але вважаю за краще загальну відповідь, яку я можу на 100% зрозуміти, щоб я міг повторно використовувати її в інших ситуаціях.

Приклад

Я хочу відповідати п'яти цифрам, використовуючи, \b\d{5}\bале не в трьох ситуаціях s1 s2 s3:

s1: не на рядку, який закінчується періодом, як це речення.

s2: ніде всередині паролів.

s3: не всередині блоку, який починається з if(і закінчується//endif

Я знаю, як вирішити будь-який з s1 s2 s3 за допомогою lookahead та lookbehind, особливо в C # lookbehind або \KPHP.

Наприклад

s1 (?m)(?!\d+.*?\.$)\d+

s3 з видом на C # (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

s3 з PHP \ K (?:(?:if\(.*?//endif)\D*)*\K\d+

Але суміш умов разом змушує мою голову вибухнути. Ще гірша новина полягає в тому, що мені може знадобитися додати інші умови s4 s5 в інший час.

Хороша новина полягає в тому, що мені байдуже, чи обробляю я файли за допомогою найбільш поширених мов, таких як PHP, C #, Python або пральна машина мого сусіда. :) Я майже новачок у Python & Java, але зацікавлений дізнатися, чи є у нього рішення.

Тому я прийшов сюди подивитися, чи хтось придумає гнучкий рецепт.

Підказки в порядку: вам не потрібно вводити мені повний код. :)

Дякую.


1
\Kнемає спеціального синтаксису php. Будь ласка, докладіть і поясніть, що ви хочете сказати. Якщо ви прагнете сказати нам, що вам не потрібно "складне" рішення, ви повинні сказати, що для вас складно і чому.
хакре

@hakre Ви маєте на увазі, тому що рубін зараз використовує його, і він почався в perl?
Ганс Шиндлер

1
Ні, тому що це PCRE, це не PHP (ні Ruby). Perl відрізняється, проте PCRE прагне бути сумісним Perl Regex.
хакре

Ваші вимоги s2 та s3 здаються суперечливими. s2 означає, що круглі дужки завжди збігаються і можуть бути вкладені, але s3 вимагає, щоб: "if("відкритий батьківський параметр був закритим, а не з a ")", а скоріше з a "//endif":? І якщо для s3 ви дійсно мали на увазі, що пункт if повинен бути закритий "//endif)"символом :, тоді вимога s3 є підмножиною s2.
вершник

@hakre Так, я знаю PCRE, але для того, щоб пояснити, питання стосується мови програмування ... це говорить especially in C# lookbehind or \K in PHP... Але C # дивись не тільки на C # це. NET, так що ви можете також поскаржитися, я кажу C # not .NET :) А у відповідь кажу Ruby не Onigurama, це теж погано ... Чи існує інша мова, яка використовує PCRE? Не кажу про Notepad ++ або серверні інструменти, це питання про використання функції в мові, сподіваюся, поясніть і вибачте, якщо це виглядає неправильно
Hans Schindler

Відповіді:


205

Ганс, я вийму приманку і м'якоть своєї попередньої відповіді. Ви сказали, що хочете "чогось більш повного", тому я сподіваюся, що ви не заперечуєте проти довгої відповіді - просто намагаючись догодити. Почнемо з деякого тла.

По-перше, це відмінне питання. Часто виникають питання щодо відповідності певним шаблонам, за винятком певних контекстів (наприклад, у кодовому блоці чи всередині дужок). Ці питання часто породжують досить незручні рішення. Тож ваше запитання щодо кількох контекстів є особливим завданням.

Сюрприз

Дивно, але є принаймні одне ефективне рішення, яке є загальним, простим у виконанні та задоволенням у обслуговуванні. Він працює з усіма ароматами регулярного вибору, які дозволяють вам перевірити групи захоплення у вашому коді. І трапляється відповісти на ряд поширених запитань, які спочатку можуть звучати інакше, ніж ваше: "відповідати всім, крім пончиків", "замінити всіх, крім ...", "відповідати всім словам, крім тих, що в чорному списку моєї мами", "ігнорувати теги "," температура відповідності, крім курсиву "...

На жаль, методика недостатньо відома: я вважаю, що у двадцяти запитаннях, які можуть використати її, є лише одна відповідь, яка згадує її - це означає, можливо, один із п’ятдесяти чи шістдесяти відповідей. Дивіться мій обмін з Кобі в коментарях. Ця методика описана в цій статті в глибині, яка називає її (оптимістично) "найкращим трюком з регексу". Не вдаючись до деталей, я спробую зрозуміти, як працює техніка. Для отримання більш детальної інформації та зразків коду на різних мовах я рекомендую вам проконсультуватися з цим ресурсом.

Більш відома варіація

Існує варіант із використанням синтаксису, характерного для 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)(?!)

демонстраційна версія для цієї версії

Програми

Ось кілька загальних проблем, які цю техніку часто можна легко вирішити. Ви помітите, що вибір слова може призвести до того, що деякі з цих проблем звучать по-різному, насправді вони практично однакові.

  1. Як я можу зіставити foo, крім будь-якого місця в тезі, як <a stuff...>...</a>?
  2. Як я можу зіставити foo, окрім <i>тега чи фрагмента javascript (більше умов)?
  3. Як я можу зіставити всі слова, які відсутні в цьому чорному списку?
  4. Як я можу ігнорувати що-небудь всередині блоку SUB ... END SUB?
  5. Як я можу зіставити все, крім ... 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, що запропонували це чудове доповнення.

Заміни

Це приводить нас до заміни, на якій я коротко торкнуся.

  1. Замінюючи нічим, див. Підказку "Видалення" вище.
  2. При заміні, якщо ви використовуєте Perl або PCRE, використовуйте (*SKIP)(*F)згадану вище варіацію, щоб відповідати саме тому, що вам потрібно, і виконайте пряму заміну.
  3. В інших ароматах, під час виклику функції заміни, перевірте відповідність за допомогою зворотного дзвінка або лямбда та замініть, якщо встановлено групу 1. Якщо вам потрібна допомога з цим, стаття, на яку вже йдеться, дасть вам код на різних мовах.

Веселіться!

Ні, чекай, є ще більше!

Ах, ну, я врятую це для своїх спогадів у двадцяти томах, які вийдуть наступної весни.


2
@Kobi Відповідь з двох частин. Так, захопився писемністю вчора ввечері і написав внизу, що я спатиму на ньому та охайно пізніше. :) Так, трюк простий, але я не поділяю ваше уявлення про те, що він "основний", оскільки він, здається, не є частиною загальних інструментів, якими користуються люди для вирішення проблем із виключенням. Коли я поглядав на проблеми "окрім" або "якщо тільки" чи "не всередині", то лише одна відповідь (без голосів) запропонувала це, ніхто з інших не зробив. До речі, я не бачив ваших відповідей, які є приголомшливими. :)
zx81

2
Вибачте, але "Найкращий трюк" Рекса просто не працює ( надійно ). Скажіть, ви хочете співставити Tarzan, але не коли-небудь всередині подвійних лапок. /no|no|(yes)/Регекс : фокус був би на кшталт: /"[^"]*"|Tarzan/(ігнорування втечених символів). Це буде працювати в багатьох випадках, але не може повністю , коли призначено для таких поважних тексту JavaScript: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Хитрість Рекса працює лише тоді, коли всі можливі структури зібрані - іншими словами - вам потрібно повністю розібрати текст, щоб гарантувати 100% точність.
ridgerunner

1
Вибачте, якщо я звучав суворо - це, звичайно, не було моїм наміром. Моя думка (як і в моєму другому коментарі до початкового питання вище) полягає в тому, що правильне рішення сильно залежить від цільового тексту, який шукається. У моєму прикладі вихідний код JavaScript є цільовим текстом, який містить одну подвійну цитату, вкладену в один рядок, що цитується. Він міг би так само легко отримати буквальний RegExp, такий як: var bug1 = /"[^"]*"|(Tarzan)/gi;і мав той же ефект (і цей другий приклад, безумовно, не є кращим випадком). Є ще багато прикладів, які я можу навести, де ця методика не працює надійно.
вершник

1
@ridgerunner Мені завжди подобається чути від вас, це просто звучить для мене невиправдано суворо. Коли ми знаємо, що наші рядки можуть містити "помилкові сповіщення", ми все коригуємо наші шаблони. Наприклад, щоб відповідати рядку, який може містити пропущені лапки, які можуть відкинути відповідник рядків, ви можете використати (?<!\\)"(?:\\"|[^"\r\n])*+" Ви не тягнете великі гармати, якщо у вас немає причини. Принцип рішення все ще діє. Якщо ми не в змозі викласти шаблон, який слід поставити з лівого боку, це вже інша історія, нам потрібно інше рішення. Але рішення робить те, що рекламується.
zx81

1
Ця відповідь була додана користувачем @funkwurm до FAQ щодо регулярних виразів переповнення стека .
aliteralmind

11

Зробіть три різні відповідники та обробіть комбінацію трьох ситуацій, використовуючи внутрішньопрограмна умовна логіка. Вам не потрібно обробляти все в одному гігантському регулярному виразі.

EDIT: Дозвольте мені трохи розширити, тому що питання стало цікавішим :-)

Загальна ідея, яку ви намагаєтеся зафіксувати тут, полягає у тому, щоб відповідати певній схемі регулярних виразів, але не тоді, коли в тестовому рядку є певні інші (можуть бути будь-які числа) шаблони. На щастя, ви можете скористатися своєю мовою програмування: зберігайте регулярні вирази та просто використовуйте складні умови. Найкращою практикою було б захоплення цієї ідеї у багаторазовий компонент, тож давайте створимо клас та метод, що реалізують його:

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public class MatcherWithExceptions {
  private string m_searchStr;
  private Regex m_searchRegex;
  private IEnumerable<Regex> m_exceptionRegexes;

  public string SearchString {
    get { return m_searchStr; }
    set {
      m_searchStr = value;
      m_searchRegex = new Regex(value);
    }
  }

  public string[] ExceptionStrings {
    set { m_exceptionRegexes = from es in value select new Regex(es); }
  }

  public bool IsMatch(string testStr) {
    return (
      m_searchRegex.IsMatch(testStr)
      && !m_exceptionRegexes.Any(er => er.IsMatch(testStr))
    );
  }
}

public class App {
  public static void Main() {
    var mwe = new MatcherWithExceptions();

    // Set up the matcher object.
    mwe.SearchString = @"\b\d{5}\b";
    mwe.ExceptionStrings = new string[] {
      @"\.$"
    , @"\(.*" + mwe.SearchString + @".*\)"
    , @"if\(.*" + mwe.SearchString + @".*//endif"
    };

    var testStrs = new string[] {
      "1." // False
    , "11111." // False
    , "(11111)" // False
    , "if(11111//endif" // False
    , "if(11111" // True
    , "11111" // True
    };

    // Perform the tests.
    foreach (var ts in testStrs) {
      System.Console.WriteLine(mwe.IsMatch(ts));
    }
  }
}

Отже вище ми встановлюємо рядок пошуку (п'ять цифр), кілька рядків виключень (ваші s1 , s2 та s3 ), а потім намагаємось відповідати декільком тестовим рядкам. Друковані результати повинні бути такими, як показано в коментарях поруч із кожним тестовим рядком.


2
Ти маєш на увазі, можливо, подобається відповідати трьом регулярним вираженням поспіль? Regex 1 усуне ситуацію 1 (можливо, просто видаліть неправильну цифру), r2 видаліть s2, r3 видаліть s3 та збіг цифр, що залишилися? Це цікава ідея.
Ганс Шиндлер

Га, звичайно, тому я підтримав тебе. :) Не зрозумійте мене неправильно, я все ще вважаю, що в цьому конкретному випадку моя відповідь є більш ефективною і доцільною. Ви бачили версію з вільним інтервалом, яку я додала вчора? Це однопрохідний і надзвичайно простий для читання та обслуговування. Але мені подобається ваша робота та ваша розгорнута відповідь. Вибачте, що не можу повторно проголосувати, інакше я б. :)
zx81

2

Ваша вимога, щоб це не було всередині парен, неможливо задовольнити для всіх випадків. А саме, якщо ви можете якось знайти (ліворуч і )праворуч, це не завжди означає, що ви знаходитесь всередині парен. Напр.

(....) + 55555 + (.....)- не всередині парен все ще є, (а )ліворуч і праворуч

Тепер ви можете вважати себе розумним і шукати (зліва лише в тому випадку, якщо ви не стикаєтесь )раніше і навпаки праворуч. У цьому випадку це не допоможе:

((.....) + 55555 + (.....))- всередині парен, навіть якщо вони закриваються )і (вліво, і вправо.

Неможливо дізнатися, чи знаходитесь ви всередині паронів, використовуючи регулярний вираз, оскільки регулярний вираз не може підрахувати, скільки паронів було відкрито, а скільки закрито.

Розглянемо це простіше завдання: використовуючи регулярний вираз, з’ясуємо, чи закриті всі (можливо вкладені) паролі в рядку, тобто для кожного, (що потрібно знайти ). Ви дізнаєтесь, що це неможливо вирішити, і якщо ви не можете вирішити це за допомогою regex, то ви не можете зрозуміти, чи є слово всередині паролів для всіх випадків, оскільки ви не можете розібратися в якійсь позиції в рядку, якщо всі попередні (мають відповідні ).


2
Ніхто нічого не сказав про вкладені дужки, і ваш випадок №1 добре відповідає за відповідь zx81.
Ден Бешард

Дякую за приємні думки :) але вкладені дужки не хвилюють мене за це питання, це більше про ідею поганих ситуацій s1 s2 s3
Hans Schindler

Звичайно, це неможливо! Саме тому вам потрібно буде відстежувати рівень паролів, в яких ви зараз розбираєтеся.
MrWonderful

Добре, якщо ви розбираєте якийсь CFG, як, здається, працює ОП, вам краще подавати LALR або подібний аналізатор, який не має з цим проблем.
RokL

2

Ганс, якщо ви не заперечуєте, я використав пральну машину вашого сусіда під назвою perl :)

Відредаговано: Під псевдокодом:

  loop through input
  if line contains 'if(' set skip=true
        if skip= true do nothing
        else
           if line match '\b\d{5}\b' set s0=true
           if line does not match s1 condition  set s1=true
           if line does not match s2 condition  set s2=true
           if s0,s1,s2 are true print line 
  if line contains '//endif' set skip=false

Враховуючи файл input.txt:

tiago@dell:~$ cat input.txt 
this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

І скрипт validator.pl:

tiago@dell:~$ cat validator.pl 
#! /usr/bin/perl
use warnings;
use strict;
use Data::Dumper;

sub validate_s0 {
    my $line = $_[0];
    if ( $line =~ \d{5/ ){
        return "true";
    }
    return "false";
}

sub validate_s1 {
    my $line = $_[0];
    if ( $line =~ /\.$/ ){
        return "false";
    }
    return "true";
}

sub validate_s2 {
    my $line = $_[0];
    if ( $line =~ /.*?\(.*\d{5.*?\).*/ ){
        return "false";
    }
    return "true";
}

my $skip = "false";
while (<>){
    my $line = $_; 

    if( $line =~ /if\(/ ){
       $skip = "true";  
    }

    if ( $skip eq "false" ) {
        my $s0_status = validate_s0 "$line"; 
        my $s1_status = validate_s1 "$line";
        my $s2_status = validate_s2 "$line";

        if ( $s0_status eq "true"){
            if ( $s1_status eq "true"){
                if ( $s2_status eq "true"){
                    print "$line";
                }
            }
        }
    } 

    if ( $line =~ /\/\/endif/) {
        $skip="false";
    }
}

Виконання:

tiago @ dell: ~ $ cat input.txt | perl validator.pl
вона повинна відповідати 12345
вона повинна відповідати 12345
вона повинна відповідати 12345

2

Не впевнений, чи допоможе це вам чи ні, але я пропоную рішення, враховуючи такі припущення -

  1. Вам потрібно елегантне рішення, щоб перевірити всі умови
  2. Умови можуть змінюватися в майбутньому та в будь-який час.
  3. Одна умова не повинна залежати від інших.

Однак я розглядав також наступне -

  1. Даний файл має в ньому мінімальні помилки. Якщо це так, то мій код може потребувати деяких модифікацій, щоб впоратися з цим.
  2. Я використовував Stack для відстеження if(блоків.

Добре ось рішення -

Я використовував C # і разом з ним MEF (Microsoft Extensibility Framework) для реалізації настроюваних парсерів. Ідея полягає у використанні одного аналізатора для розбору та списку настроюваних класів валідатора для перевірки рядка та повернення true чи false на основі перевірки. Тоді ви можете будь-коли додати або видалити будь-який валідатор або додати нові, якщо хочете. Поки що я вже реалізував для згаданих вами S1, S2 і S3, перевірте класи в пункті 3. Ви повинні додати класи для s4, s5, якщо вам це потрібно в майбутньому.

  1. Спочатку створіть інтерфейси -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.Contracts
    {
        public interface IParser
        {
            String[] GetMatchedLines(String filename);
        }
    
        public interface IPatternMatcher
        {
            Boolean IsMatched(String line, Stack<string> stack);
        }
    }
  2. Потім приходить зчитувач файлів і перевірка -

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using FileParserDemo.Contracts;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Collections;
    
    namespace FileParserDemo.Parsers
    {
        public class Parser : IParser
        {
            [ImportMany]
            IEnumerable<Lazy<IPatternMatcher>> parsers;
            private CompositionContainer _container;
    
            public void ComposeParts()
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new AssemblyCatalog(typeof(IParser).Assembly));
                _container = new CompositionContainer(catalog);
                try
                {
                    this._container.ComposeParts(this);
                }
                catch
                {
    
                }
            }
    
            public String[] GetMatchedLines(String filename)
            {
                var matched = new List<String>();
                var stack = new Stack<string>();
                using (StreamReader sr = File.OpenText(filename))
                {
                    String line = "";
                    while (!sr.EndOfStream)
                    {
                        line = sr.ReadLine();
                        var m = true;
                        foreach(var matcher in this.parsers){
                            m = m && matcher.Value.IsMatched(line, stack);
                        }
                        if (m)
                        {
                            matched.Add(line);
                        }
                     }
                }
                return matched.ToArray();
            }
        }
    }
  3. Потім йде реалізація індивідуальних перевірок, назви класів пояснюються самі собою, тому я не думаю, що їм потрібно більше описів.

    using FileParserDemo.Contracts;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Threading.Tasks;
    
    namespace FileParserDemo.PatternMatchers
    {
        [Export(typeof(IPatternMatcher))]
        public class MatchAllNumbers : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\d+");
                return regex.IsMatch(line);
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveIfBlock : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("if\\(");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        //push the if
                        stack.Push(m.ToString());
                    }
                    //ignore current line, and will validate on next line with stack
                    return true;
                }
                regex = new Regex("//endif");
                if (regex.IsMatch(line))
                {
                    foreach (var m in regex.Matches(line))
                    {
                        stack.Pop();
                    }
                }
                return stack.Count == 0; //if stack has an item then ignoring this block
            }
        }
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithEndPeriod : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("(?m)(?!\\d+.*?\\.$)\\d+");
                return regex.IsMatch(line);
            }
        }
    
    
        [Export(typeof(IPatternMatcher))]
        public class RemoveWithInParenthesis : IPatternMatcher
        {
            public Boolean IsMatched(String line, Stack<string> stack)
            {
                var regex = new Regex("\\(.*\\d+.*\\)");
                return !regex.IsMatch(line);
            }
        }
    }
  4. Програма -

    using FileParserDemo.Contracts;
    using FileParserDemo.Parsers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace FileParserDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var parser = new Parser();
                parser.ComposeParts();
                var matches = parser.GetMatchedLines(Path.GetFullPath("test.txt"));
                foreach (var s in matches)
                {
                    Console.WriteLine(s);
                }
                Console.ReadLine();
            }
        }
    }

Для тестування я взяв файл зразка @ Tiago, у Test.txtякому були наступні рядки -

this is a text
it should match 12345
if(
it should not match 12345
//endif 
it should match 12345
it should not match 12345.
it should not match ( blabla 12345  blablabla )
it should not match ( 12345 )
it should match 12345

Дає вихід -

it should match 12345
it should match 12345
it should match 12345

Не знаю, чи допоможе вам це чи ні, я весело провів час, граючи з ним .... :)

Найкраще, що для додавання нової умови все, що вам потрібно зробити, це забезпечити реалізацію IPatternMatcher, вона автоматично викличеться і, таким чином, буде підтверджена.


2

Те саме, що і @ zx81, (*SKIP)(*F)але з використанням негативного твердження lookahead .

(?m)(?:if\(.*?\/\/endif|\([^()]*\))(*SKIP)(*F)|\b\d+\b(?!.*\.$)

DEMO

У python я б легко так зробив,

import re
string = """cat 123 sat.
I like 000 not (456) though 111 is fine
222 if(  //endif if(cat==789 stuff  //endif   333"""
for line in string.split('\n'):                                  # Split the input according to the `\n` character and then iterate over the parts.
    if not line.endswith('.'):                                   # Don't consider the part which ends with a dot.
        for i in re.split(r'\([^()]*\)|if\(.*?//endif', line):   # Again split the part by brackets or if condition which endswith `//endif` and then iterate over the inner parts.
            for j in re.findall(r'\b\d+\b', i):                  # Then find all the numbers which are present inside the inner parts and then loop through the fetched numbers.
                print(j)                                         # Prints the number one ny one.

Вихід:

000
111
222
333
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.