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

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

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

Пример

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

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

s2: Не где-либо внутри parens.

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

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

Например

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

s3 с С# lookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

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

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

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

Итак, я пришел сюда, чтобы узнать, думает ли кто-нибудь о гибком рецепте.

Подсказки в порядке: вам не нужно указывать полный код.:)

Спасибо.

Ответ 1

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

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

сюрприз

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

К сожалению, методика не очень хорошо известна: по моим оценкам, в двадцати 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 могут оставаться ясными как день. (Они являются подвыражениями на каждой стороне |)

Все выражение можно записать так:

(?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.

Regular expression visualization

Debuggex Demo

Вариант 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. Lookarounds.

Ваш оригинальный пост показал, что вы понимаете, как исключить одно условие, используя обходные пути. Вы сказали, что С# отлично подходит для этого, и вы правы, но это не единственный вариант. Например, разновидности регулярных выражений .NET, встречающиеся в С#, VB.NET и Visual C++, а также все еще экспериментальный модуль regex для замены re в Python - это единственные два движка, которые я знаю, которые поддерживают просмотр бесконечной ширины. С помощью этих инструментов одно условие в одном взгляде может заботиться о том, чтобы смотреть не только за спиной, но и за матчем и за его пределами, избегая необходимости координировать свои действия с предвидением. Больше условий? Больше взглядов.

Повторно использовав регулярное выражение для s3 в С#, весь шаблон будет выглядеть следующим образом.

(?!.*\.)(?<!\([^()]*(?=\d+[^)]*\)))(?<!if\(\D*(?=\d+.*?//endif))\b\d+\b

Но теперь вы знаете, что я не рекомендую это, верно?

Пропуски

@HamZa и @Jerry предложили упомянуть дополнительный прием для случаев, когда вы WhatYouWant просто удалить 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. Если вам нужна помощь в этом, уже упоминавшаяся статья предоставит вам код на разных языках.

Повеселись!

Нет, подожди, там больше!

Ах, нет, я сохраню это для моих мемуаров в двадцати томах, которые выйдут следующей spring.

Ответ 2

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

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)., а затем попытайтесь сопоставить несколько тестовых строк. Распечатанные результаты должны быть такими, как показано в комментариях рядом с каждой тестовой строкой.

Ответ 3

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

(....) + 55555 + (.....) - не внутри parens, но есть ( и ) влево и вправо

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

((.....) + 55555 + (.....)) - внутри парнов, даже если есть закрытие ) и ( слева и справа.

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

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

Ответ 4

Ханс, если вы не против, я использовал вашу соседнюю стиральную машину под названием 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:

[email protected]:~$ 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

И script validator.pl:

[email protected]:~$ 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";
    }
}

Исполнение:

[email protected]:~$ cat input.txt | perl validator.pl 
it should match 12345
it should match 12345
it should match 12345

Ответ 5

Не уверен, что это поможет вам или нет, но я предлагаю решение, учитывая следующие предположения -

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

Однако я также рассмотрел следующее:

  • Указанный файл содержит минимальные ошибки. Если это произойдет, то моему коду могут потребоваться некоторые изменения, чтобы справиться с этим.
  • Я использовал Stack для отслеживания блоков if(.

Хорошо, вот решение -

Я использовал С# и с ним MEF (Microsoft Extensibility Framework) для реализации настраиваемых парсеров. Идея состоит в том, чтобы использовать синтаксический анализатор для синтаксического анализа и список настраиваемых классов валидатора для проверки строки и возврата true или false на основе проверки. Затем вы можете добавить или удалить любой валидатор в любое время или добавить новые, если хотите. До сих пор я уже реализовал для S1, S2 и S3 вы упомянули, проверьте классы в точке 3. Вам нужно добавить классы для s4, s5, если вам нужно в будущем.

  • Сначала создайте интерфейсы -

    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);
        }
    }
    
  • Затем появляется программа чтения и проверки файлов -

    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();
            }
        }
    }
    
  • Затем идет реализация отдельных шашек, имена классов объясняются сами собой, поэтому я не думаю, что им нужно больше описаний.

    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);
            }
        }
    }
    
  • Программа -

    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, оно будет автоматически вызвано и, таким образом, будет проверяться.

Ответ 6

То же, что и @zx81 (*SKIP)(*F), но с использованием отрицательного утверждения.

(?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