Шаблон регулярного выражения для соответствия, исключая, когда… / кроме между


108

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

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

пример

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

s1: Не в строке, которая заканчивается точкой, как в этом предложении.

s2: нигде внутри парен.

s3: Не внутри блока, который начинается if(и заканчивается на//endif

Я знаю, как решить любой из s1 s2 s3 с просмотром вперед и назад, особенно в C # lookbehind или \Kв PHP.

Например

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

@hakre Вы имеете в виду, потому что Ruby теперь его использует, и это началось с Perl?
Ганс Шиндлер

1
Нет, потому что это PCRE, а не PHP (и не Ruby). Perl отличается, однако PCRE стремится быть совместимым с Perl Regex.
hakre

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

@hakre Да, я знаю PCRE, но, чтобы объяснить, вопрос о языке программирования ... он говорит especially in C# lookbehind or \K in PHP... Но C # lookbehind не только C #, это .NET, так что вы тоже можете жаловаться Я говорю C #, а не .NET :) И в ответ я говорю Ruby, а не Onigurama, это тоже плохо ... Есть ли другой язык, использующий PCRE? Не говоря уже о Notepad ++ или серверных инструментах, это вопрос об использовании функции на языке, я надеюсь, что объясню и извиняюсь, если это выглядит неправильно,
Ханс Шиндлер

Ответы:


205

Ганс, я клюну и дополню свой предыдущий ответ. Вы сказали, что хотите «чего-то более полного», поэтому я надеюсь, что вы не будете возражать против длинного ответа - просто пытаетесь угодить. Начнем с некоторой предыстории.

Во-первых, это отличный вопрос. Часто возникают вопросы о сопоставлении определенных шаблонов, за исключением определенных контекстов (например, внутри блока кода или внутри скобок). Эти вопросы часто приводят к довольно неудобным решениям. Так что ваш вопрос о множественных контекстах - особая проблема.

Сюрприз

Удивительно, но существует по крайней мере одно эффективное решение, которое является общим, простым в реализации и приятным в обслуживании. Он работает со всеми разновидностями регулярных выражений, которые позволяют вам проверять группы захвата в вашем коде. И он отвечает на ряд общих вопросов, которые на первый взгляд могут показаться отличными от вашего: «сопоставить все, кроме пончиков», «заменить все, кроме ...», «сопоставить все слова, кроме тех, что в черном списке моей мамы», «игнорировать теги "," соответствует температуре, если не выделено курсивом "...

К сожалению, этот метод малоизвестен: по моим оценкам, из двадцати 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)(?!)

демо для этой версии

Приложения

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

  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 # отлично подходит для этого, и вы правы, но это не единственный вариант. Варианты регулярных выражений .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 Ответ из двух частей. Да, вчера вечером увлекся писанием и написал внизу, что на нем буду спать, а потом приберу. :) Да, уловка проста, но я не разделяю вашего мнения о том, что она «простая», потому что она, кажется, не является частью обычных инструментов, которые люди используют для решения проблем исключения. Когда я искал в Google проблемы «кроме», «если» или «не внутри» на SO, только один ответ (без голосов) предлагал это, а остальные не предлагали. Я, кстати, не видел ваших ответов, они потрясающие. :)
zx81

2
Извините, но "Лучший трюк" Рекса просто не работает ( надежно ). Допустим, вы хотите найти совпадение Tarzan, но не в двойных кавычках. /no|no|(yes)/Регулярное выражение : trick будет выглядеть примерно так: /"[^"]*"|Tarzan/(игнорируя экранированные символы). Это будет работать во многих случаях, но не может полностью , когда применяется к следующему уважительной тексту JavaScript: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Уловка Рекса работает только тогда, когда ВСЕ возможные структуры совпадают - другими словами, вам нужно полностью проанализировать текст, чтобы гарантировать 100% точность.
ridgerunner 07

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

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

1
Этот ответ был добавлен в часто задаваемые вопросы о регулярных выражениях при переполнении стека пользователем @funkwurm.
aliteralmind 07

11

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

РЕДАКТИРОВАТЬ: позвольте мне немного расширить, потому что вопрос стал более интересным :-)

Общая идея, которую вы здесь пытаетесь уловить, - это сопоставление с определенным шаблоном регулярного выражения, но не тогда, когда в тестовой строке присутствуют определенные другие (может быть любое число) шаблоны. К счастью, вы можете воспользоваться преимуществами своего языка программирования: используйте простые регулярные выражения и просто используйте составные условные выражения. Лучшей практикой было бы зафиксировать эту идею в повторно используемом компоненте, поэтому давайте создадим класс и метод, которые его реализуют:

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 + (.....))- внутри паренсы, хотя есть закрывающие )и (слева, и справа.

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

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


2
Никто ничего не сказал о вложенных скобках, и ваш случай № 1 прекрасно обрабатывается ответом zx81.
Дэн Бечард

Спасибо за хорошие мысли :) но вложенные круглые скобки меня не беспокоят в этом вопросе, это скорее идея плохих ситуаций s1 s2 s3
Ганс Шиндлер

Конечно, это не невозможно! Именно поэтому вам нужно отслеживать уровень парсеров, в которых вы в настоящее время разбираетесь.
MrWonderful

Что ж, если вы разбираете какой-то CFG, как кажется, OP, вам лучше будет сгенерировать 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) для реализации настраиваемых парсеров. Идея состоит в том, чтобы использовать один синтаксический анализатор для синтаксического анализа и список настраиваемых классов валидатора для проверки строки и возврата истинного или ложного значения на основе проверки. Затем вы можете добавить или удалить любой валидатор в любое время или добавить новые, если хотите. До сих пор я уже реализовал для 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)но с использованием утверждения отрицательного просмотра вперед .

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

ДЕМО

В 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.