Regex Pattern da abbinare, escluso quando ... / Tranne tra


108

--Edit-- Le risposte attuali hanno alcune idee utili ma voglio qualcosa di più completo che posso capire e riutilizzare al 100%; ecco perché ho impostato una taglia. Anche le idee che funzionano ovunque sono migliori per me della sintassi non standard come\K

Questa domanda riguarda come posso abbinare un pattern eccetto alcune situazioni s1 s2 s3. Faccio un esempio specifico per mostrare il mio significato, ma preferisco una risposta generale che posso capire al 100% in modo da poterlo riutilizzare in altre situazioni.

Esempio

Voglio abbinare cinque cifre usando \b\d{5}\bma non in tre situazioni s1 s2 s3:

s1: Non su una riga che termina con un punto come questa frase.

s2: Non da nessuna parte all'interno delle parentesi.

s3: Non all'interno di un blocco che inizia con if(e finisce con//endif

So come risolvere uno qualsiasi di s1 s2 s3 con un lookahead e lookbehind, specialmente in C # lookbehind o \Kin PHP.

Per esempio

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

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

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

Ma il mix di condizioni insieme mi fa esplodere la testa. Ancora più cattive notizie è che potrei aver bisogno di aggiungere altre condizioni s4 s5 in un altro momento.

La buona notizia è che non mi interessa se elaboro i file utilizzando i linguaggi più comuni come PHP, C #, Python o la lavatrice del mio vicino. :) Sono praticamente un principiante in Python e Java ma sono interessato a sapere se ha una soluzione.

Quindi sono venuto qui per vedere se qualcuno pensa a una ricetta flessibile.

I suggerimenti vanno bene: non è necessario che tu mi fornisca il codice completo. :)

Grazie.


1
\Knon è una sintassi php speciale. Si prega di elaborare e chiarire ciò che si vuole dire. Se vuoi dirci che non hai bisogno di una soluzione "complicata" devi dire cosa è complicato per te e perché.
hakre

@hakre Vuoi dire perché ruby ​​ora lo usa ed è iniziato in perl?
Hans Schindler

1
No, perché è PCRE che non è PHP (né Ruby). Perl è diverso, tuttavia PCRE mira ad essere compatibile con Perl Regex.
hakre

I tuoi requisiti s2 e s3 sembrano essere contraddittori. s2 implica che le parentesi sono sempre abbinate e possono essere annidate, ma s3 richiede che la parentesi: "if("open sia chiusa, non con a ")", ma piuttosto con a "//endif":? E se per s3 intendevi davvero che la clausola if dovrebbe essere chiusa con "//endif)":, allora il requisito s3 è un sottoinsieme di s2.
ridgerunner

@hakre Sì, lo so PCRE ma per spiegare, la domanda riguarda il linguaggio di programmazione ... dice especially in C# lookbehind or \K in PHP... Ma C # guarda dietro non solo C # è .NET quindi puoi lamentarti anche tu dico C # non .NET :) E in risposta dico Ruby non è Onigurama anche questo è brutto ... C'è un altro linguaggio che usa PCRE? Non parliamo di Notepad ++ o degli strumenti del server, questa è una domanda sull'uso della funzionalità nel linguaggio, spero che ti spieghi e mi dispiace se sembra sbagliato
Hans Schindler

Risposte:


205

Hans, abbatterò l'esca e rimpolperò la mia risposta precedente. Hai detto che volevi "qualcosa di più completo", quindi spero che non ti dispiaccia la risposta lunga, cercando solo di accontentare. Cominciamo con un po 'di background.

Prima di tutto, questa è un'ottima domanda. Ci sono spesso domande sulla corrispondenza di determinati modelli tranne che in determinati contesti (ad esempio, all'interno di un blocco di codice o tra parentesi). Queste domande spesso danno luogo a soluzioni piuttosto scomode. Quindi la tua domanda su più contesti è una sfida speciale.

Sorpresa

Sorprendentemente, esiste almeno una soluzione efficiente che è generale, facile da implementare e piacevole da mantenere. Esso funziona con tutti i sapori regex che consentono di ispezionare i gruppi di cattura nel codice. E capita di rispondere a una serie di domande comuni che a prima vista possono sembrare diverse dalle tue: "abbina tutto tranne Donuts", "sostituisci tutto tranne ...", "trova tutte le parole tranne quelle sulla lista nera di mia madre", "ignora tag "," corrisponde alla temperatura a meno che non sia in corsivo "...

Purtroppo, la tecnica non è ben nota: stimo che in venti domande SO che potrebbero usarla, solo una ha una risposta che la menziona, il che significa forse una su cinquanta o sessanta risposte. Vedi il mio scambio con Kobi nei commenti. La tecnica è descritta in modo approfondito in questo articolo che la definisce (ottimisticamente) il "miglior trucco regex di sempre". Senza entrare nei dettagli, cercherò di darti una solida comprensione di come funziona la tecnica. Per maggiori dettagli ed esempi di codice in varie lingue ti incoraggio a consultare quella risorsa.

Una variante meglio conosciuta

Esiste una variazione che utilizza la sintassi specifica di Perl e PHP che realizza lo stesso. Lo vedrai su SO nelle mani di maestri di regex come CasimiretHippolyte e HamZa . Ti dirò di più su questo di seguito, ma il mio focus qui è sulla soluzione generale che funziona con tutti i gusti di regex (purché tu possa ispezionare i gruppi di acquisizione nel tuo codice).

Grazie per tutto il background, zx81 ... Ma qual è la ricetta?

Fatto fondamentale

Il metodo restituisce la corrispondenza nell'acquisizione del gruppo 1. Non si cura affatto della partita generale.

In effetti, il trucco sta nel far corrispondere i vari contesti che non vogliamo (concatenando questi contesti utilizzando l' |alternanza OR /) in modo da "neutralizzarli". Dopo aver abbinato tutti i contesti indesiderate, la parte finale del alternanza corrisponde a quello che facciamo vogliamo e lo cattura al gruppo 1.

La ricetta generale è

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Questa corrisponderà Not_this_context, ma in un certo senso quella partita finisce in un cestino della spazzatura, perché non guarderemo le partite complessive: guardiamo solo le acquisizioni del Gruppo 1.

Nel tuo caso, ignorando le tue cifre e i tuoi tre contesti, possiamo fare:

s1|s2|s3|(\b\d+\b)

Tieni presente che poiché in realtà abbiniamo s1, s2 e s3 invece di cercare di evitarli con i lookaround, le singole espressioni per s1, s2 e s3 possono rimanere chiare come il giorno. (Sono le sottoespressioni su ogni lato di a |)

L'intera espressione può essere scritta in questo modo:

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

Guarda questa demo (ma concentrati sui gruppi di acquisizione nel riquadro in basso a destra.)

Se provi mentalmente a dividere questa regex in ogni |delimitatore, in realtà è solo una serie di quattro espressioni molto semplici.

Per i gusti che supportano la spaziatura libera, questo si legge particolarmente bene.

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

Questo è eccezionalmente facile da leggere e mantenere.

Estensione della regex

Quando vuoi ignorare più situazioni s4 e s5, le aggiungi in più alternanze a sinistra:

s4|s5|s1|s2|s3|(\b\d+\b)

Come funziona?

I contesti che non vuoi vengono aggiunti a un elenco di alternanze sulla sinistra: corrisponderanno, ma queste corrispondenze complessive non vengono mai esaminate, quindi abbinarle è un modo per metterle in un "cestino della spazzatura".

Il contenuto desiderato, tuttavia, viene acquisito nel gruppo 1. È quindi necessario verificare a livello di programmazione che il gruppo 1 sia impostato e non vuoto. Questo è un compito di programmazione banale (e parleremo più avanti di come è fatto), soprattutto considerando che ti lascia con una semplice regex che puoi capire a colpo d'occhio e rivedere o estendere secondo necessità.

Non sono sempre un fan delle visualizzazioni, ma questa fa un buon lavoro nel mostrare quanto sia semplice il metodo. Ogni "linea" corrisponde a una potenziale corrispondenza, ma solo la linea di fondo viene catturata nel Gruppo 1.

Visualizzazione di espressioni regolari

Debuggex Demo

Variazione Perl / PCRE

In contrasto con la soluzione generale sopra, esiste una variazione per Perl e PCRE che è spesso vista su SO, almeno nelle mani di regex Gods come @CasimiretHippolyte e @HamZa. È:

(?:s1|s2|s3)(*SKIP)(*F)|whatYouWant

Nel tuo caso:

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

Questa variazione è un po 'più facile da usare perché il contenuto corrispondente nei contesti s1, s2 e s3 viene semplicemente ignorato, quindi non è necessario ispezionare le acquisizioni del gruppo 1 (notare che le parentesi sono sparite). Le partite contengono solowhatYouWant

Notalo (*F), (*FAIL)e (?!)sono tutti la stessa cosa. Se volevi essere più oscuro, potresti usare(*SKIP)(?!)

demo per questa versione

applicazioni

Ecco alcuni problemi comuni che questa tecnica può spesso risolvere facilmente. Noterai che la parola scelta può far suonare alcuni di questi problemi in modo diverso mentre in realtà sono praticamente identici.

  1. Come posso abbinare foo tranne che ovunque in un tag come <a stuff...>...</a>?
  2. Come posso abbinare foo tranne che in un <i>tag o in uno snippet javascript (più condizioni)?
  3. Come posso abbinare tutte le parole che non sono in questa lista nera?
  4. Come posso ignorare qualsiasi cosa all'interno di un blocco SUB ... END SUB?
  5. Come posso abbinare tutto tranne ... s1 s2 s3?

Come programmare le acquisizioni del gruppo 1

Non hai fatto come per il codice, ma, per il completamento ... Il codice per ispezionare il Gruppo 1 dipenderà ovviamente dalla tua lingua scelta. In ogni caso, non dovrebbe aggiungere più di un paio di righe al codice che useresti per controllare le corrispondenze.

In caso di dubbio, ti consiglio di consultare la sezione degli esempi di codice dell'articolo citato in precedenza, che presenta il codice per un bel po 'di lingue.

alternative

A seconda della complessità della domanda e del motore di regex utilizzato, ci sono diverse alternative. Ecco i due che possono essere applicati alla maggior parte delle situazioni, comprese più condizioni. A mio avviso, nessuno dei due è attraente come la s1|s2|s3|(whatYouWant)ricetta, se non altro perché la chiarezza vince sempre.

1. Sostituisci quindi Abbina.

Una buona soluzione che suona hacky ma funziona bene in molti ambienti è lavorare in due passaggi. Una prima regex neutralizza il contesto che vuoi ignorare sostituendo stringhe potenzialmente in conflitto. Se desideri solo la corrispondenza, puoi sostituirla con una stringa vuota, quindi eseguire la corrispondenza nel secondo passaggio. Se vuoi sostituire, puoi prima sostituire le stringhe da ignorare con qualcosa di distintivo, ad esempio circondando le tue cifre con una catena a larghezza fissa di @@@. Dopo questa sostituzione, sei libero di sostituire ciò che volevi veramente, quindi dovrai ripristinare le tue @@@corde distintive .

2. Lookaround.

Il tuo post originale mostrava che comprendi come escludere una singola condizione utilizzando i lookaround. Hai detto che C # è ottimo per questo e hai ragione, ma non è l'unica opzione. Le versioni regex .NET trovate in C #, VB.NET e Visual C ++, ad esempio, così come il regexmodulo ancora sperimentale da sostituire rein Python, sono gli unici due motori che conosco che supportano il lookbehind a larghezza infinita. Con questi strumenti, una condizione in uno sguardo dietro può occuparsi di guardare non solo dietro ma anche sulla partita e oltre la partita, evitando la necessità di coordinarsi con uno sguardo in avanti. Altre condizioni? Altri lookaround.

Riciclando la regex che avevi per s3 in C #, l'intero modello sarebbe simile a questo.

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

Ma ormai sai che non lo sto raccomandando, giusto?

eliminazioni

@HamZa e @Jerry hanno suggerito di menzionare un trucco aggiuntivo per i casi in cui si cerca di eliminare WhatYouWant. Ricordi che la ricetta da abbinare WhatYouWant(catturandola nel Gruppo 1) era s1|s2|s3|(WhatYouWant), giusto? Per eliminare tutte le istanze di WhatYouWant, cambia la regex in

(s1|s2|s3)|WhatYouWant

Per la stringa sostitutiva, usi $1. Quello che succede qui è che per ogni istanza di s1|s2|s3quello è abbinato, la sostituzione $1sostituisce quell'istanza con se stessa (a cui fa riferimento $1). D'altra parte, quando WhatYouWantviene abbinato, viene sostituito da un gruppo vuoto e nient'altro - e quindi eliminato. Guarda questa demo , grazie @HamZa e @Jerry per aver suggerito questa fantastica aggiunta.

sostituzioni

Questo ci porta alle sostituzioni, sulle quali toccherò brevemente.

  1. Quando si sostituisce con niente, vedere il trucco "Eliminazioni" sopra.
  2. Quando si sostituisce, se si utilizza Perl o PCRE, utilizzare la (*SKIP)(*F)variante menzionata sopra per corrispondere esattamente a ciò che si desidera ed eseguire una sostituzione diretta.
  3. In altri gusti, all'interno della chiamata alla funzione di sostituzione, ispeziona la corrispondenza utilizzando un callback o lambda e sostituisci se è impostato il gruppo 1. Se hai bisogno di aiuto con questo, l'articolo già citato ti fornirà il codice in varie lingue.

Divertiti!

No, aspetta, c'è di più!

Ah, nah, lo terrò per le mie memorie in venti volumi, in uscita la prossima primavera.


2
@ Kobi Risposta in due parti. Sì, ieri sera mi sono lasciato trasportare dalla scrittura e ho scritto in fondo che ci avrei dormito sopra e che avrei riordinato più tardi. :) Sì, il trucco è semplice ma non condivido la tua percezione che sia "basilare" perché non sembra far parte degli strumenti comuni che le persone usano per risolvere i problemi di esclusione. Quando ho cercato su Google i problemi "tranne" o "a meno che" o "non dentro" su SO, solo una risposta (senza voti) lo ha suggerito, nessuno degli altri lo ha fatto. A proposito, non avevo visto le tue risposte, che sono fantastiche. :)
zx81

2
Mi dispiace, ma il "miglior trucco" di Rex semplicemente non funziona (in modo affidabile ). Supponi di voler abbinare Tarzan, ma non quando ovunque tra virgolette. L' /no|no|(yes)/espressione regolare : trick sarebbe qualcosa del tipo: /"[^"]*"|Tarzan/(ignorando i caratteri di escape). Questo funziona per molti casi, ma non riesce completamente quando applicato il seguente testo di JavaScript valida: var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Il trucco di Rex funziona solo quando TUTTE le strutture possibili sono abbinate, in altre parole, è necessario analizzare completamente il testo per garantire una precisione del 100%.
ridgerunner

1
Scusa se ho suonato duro, non era certo il mio intento. Il mio punto (come nel mio secondo commento alla domanda originale sopra) è che una soluzione corretta dipende in larga misura dal testo di destinazione cercato. Il mio esempio ha il codice sorgente JavaScript come testo di destinazione che ha una virgoletta doppia racchiusa all'interno di una singola stringa tra virgolette. Avrebbe potuto essere altrettanto facilmente una RegExp letterale come: var bug1 = /"[^"]*"|(Tarzan)/gi;e ha avuto lo stesso effetto (e questo secondo esempio non è certamente un caso limite). Ci sono molti altri esempi che potrei citare in cui questa tecnica non funziona in modo affidabile.
ridgerunner

1
@ridgerunner Mi fa sempre piacere sentirti, mi sembra solo ingiustificatamente duro. Quando sappiamo che le nostre stringhe possono contenere "falsi allarmi", adattiamo tutti i nostri schemi. Ad esempio, per trovare una stringa che può contenere virgolette con caratteri di escape che potrebbero generare un abbinamento di stringhe, potresti usare (?<!\\)"(?:\\"|[^"\r\n])*+" Non tirare i pezzi grossi a meno che tu non abbia una ragione. Il principio della soluzione è ancora valido. Se non siamo in grado di esprimere uno schema da mettere sul lato sinistro, questa è una storia diversa, abbiamo bisogno di una soluzione diversa. Ma la soluzione fa quello che pubblicizza.
zx81

1
Questa risposta è stata aggiunta alle domande frequenti sulle espressioni regolari di Stack Overflow dall'utente @funkwurm.
aliteralmind

11

Esegui tre corrispondenze diverse e gestisci la combinazione delle tre situazioni utilizzando la logica condizionale nel programma. Non è necessario gestire tutto in una gigantesca regex.

EDIT: lasciami espandere un po 'perché la domanda è diventata più interessante :-)

L'idea generale che stai cercando di catturare qui è quella di confrontare un certo pattern regex, ma non quando ci sono certi altri (potrebbero essere qualsiasi numero) pattern presenti nella stringa di test. Fortunatamente, puoi sfruttare il tuo linguaggio di programmazione: mantieni le espressioni regolari semplici e usa solo un condizionale composto. Una best practice sarebbe catturare questa idea in un componente riutilizzabile, quindi creiamo una classe e un metodo che la implementino:

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));
    }
  }
}

Quindi, sopra, abbiamo impostato la stringa di ricerca (le cinque cifre), più stringhe di eccezione (le tue s1 , s2 e s3 ) e quindi proviamo a confrontare diverse stringhe di test. I risultati stampati dovrebbero essere come mostrato nei commenti accanto a ciascuna stringa di test.


2
Vuoi dire forse come abbinare tre regex di fila? Regex 1 elimina la situazione 1 (forse elimina solo la cifra errata), r2 rimuove s2, r3 rimuove s3 e corrisponde alle cifre rimaste? È un'idea interessante.
Hans Schindler

Ah, certo, è per questo che ti ho votato. :) Non fraintendetemi, penso ancora che in questo caso particolare la mia risposta sia più efficiente e manutenibile. Hai visto la versione a spaziatura libera che ho aggiunto ieri? È un passaggio unico ed è eccezionalmente facile da leggere e mantenere. Ma mi piace il tuo lavoro e la tua risposta espansa. Mi dispiace di non poter votare di nuovo, altrimenti lo farei. :)
zx81

2

La tua esigenza che non sia all'interno dei genitori è impossibile da soddisfare per tutti i casi. Vale a dire, se in qualche modo riesci a trovare un (a sinistra ea )destra, non significa sempre che sei all'interno dei genitori. Per esempio.

(....) + 55555 + (.....)- non dentro le parentesi ancora ci sono (e) sinistra ea destra

Ora potresti pensare di essere intelligente e cercare (a sinistra solo se non incontri )prima e viceversa a destra. Questo non funzionerà per questo caso:

((.....) + 55555 + (.....))- all'interno delle parentesi anche se ci sono chiusura )e( sinistra ea destra.

È impossibile scoprire se sei all'interno di genitori usando regex, poiché regex non può contare quante parentesi sono state aperte e quante chiuse.

Considera questo compito più semplice: usando regex, scopri se tutte le parentesi (possibilmente annidate) in una stringa sono chiuse, cioè per ogni cosa che (devi trovare ). Scoprirai che è impossibile risolverlo e se non riesci a risolverlo con regex, non puoi capire se una parola è tra parentesi per tutti i casi, dal momento che non puoi capire in una qualche posizione nella stringa se tutti i precedenti (hanno un corrispondente ).


2
Nessuno ha detto nulla sulle parentesi nidificate e il tuo caso n. 1 è gestito bene dalla risposta di zx81.
Dan Bechard

Grazie per i bei pensieri :) ma la parentesi annidata non mi preoccupa per questa domanda è più sull'idea di brutte situazioni s1 s2 s3
Hans Schindler

Ovviamente non è impossibile! Questo è esattamente il motivo per cui dovresti tenere traccia del livello di parentesi in cui stai attualmente analizzando.
MrWonderful

Bene, se stai analizzando un qualche tipo di CFG come sembra che OP stia facendo, sei più servito generando un LALR o un parser simile che non ha problemi con questo.
RokL

2

Hans se non ti dispiace ho usato la lavatrice del tuo vicino chiamata perl :)

Modificato: sotto uno pseudo codice:

  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

Dato il file 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

E lo script 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";
    }
}

Esecuzione:

tiago @ dell: ~ $ cat input.txt | perl validator.pl
dovrebbe corrispondere a 12345
dovrebbe corrispondere a 12345
dovrebbe corrispondere a 12345

2

Non sono sicuro che questo possa aiutarti o meno, ma sto fornendo una soluzione considerando i seguenti presupposti:

  1. Serve una soluzione elegante per verificare tutte le condizioni
  2. Le condizioni possono cambiare in futuro e in qualsiasi momento.
  3. Una condizione non dovrebbe dipendere dalle altre.

Tuttavia ho considerato anche quanto segue:

  1. Il file fornito contiene errori minimi. In tal caso, il mio codice potrebbe aver bisogno di alcune modifiche per far fronte a questo.
  2. Ho usato Stack per tenere traccia dei if(blocchi.

Ok ecco la soluzione -

Ho usato C # e con esso MEF (Microsoft Extensibility Framework) per implementare i parser configurabili. L'idea è di utilizzare un singolo parser per analizzare e un elenco di classi di validatori configurabili per convalidare la riga e restituire vero o falso in base alla convalida. Quindi puoi aggiungere o rimuovere qualsiasi validatore in qualsiasi momento o aggiungerne di nuovi se lo desideri. Finora ho già implementato per S1, S2 e S3 che hai citato, controlla le classi al punto 3. Devi aggiungere classi per s4, s5 se ne avrai bisogno in futuro.

  1. Innanzitutto, crea le interfacce -

    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. Poi arriva il lettore di file e il controllo -

    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. Poi arriva l'implementazione dei singoli checker, i nomi delle classi sono autoesplicativi, quindi non credo abbiano bisogno di ulteriori descrizioni.

    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. Il programma -

    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();
            }
        }
    }

Per i test ho preso il file di esempio di @ Tiago poiché Test.txtaveva le seguenti righe:

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

Fornisce l'output -

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

Non so se questo ti aiuterebbe o no, mi sono divertito a giocarci ... :)

La parte migliore è che, per aggiungere una nuova condizione, tutto ciò che devi fare è fornire un'implementazione di IPatternMatcher, verrà automaticamente chiamata e quindi convalidata.


2

Uguale a @ zx81 (*SKIP)(*F)ma con l'utilizzo di un'asserzione lookahead negativa.

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

DEMO

In Python, lo farei facilmente,

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.

Produzione:

000
111
222
333
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.