Übereinstimmendes Regex-Muster, außer wenn… / Außer dazwischen

108

--Edit-- Die aktuellen Antworten haben einige nützliche Ideen, aber ich möchte etwas vollständigeres, das ich zu 100% verstehen und wiederverwenden kann. Deshalb habe ich ein Kopfgeld gesetzt. Auch Ideen, die überall funktionieren, sind für mich besser als keine Standard-Syntax wie\K

In dieser Frage geht es darum, wie ich ein Muster mit Ausnahme einiger Situationen s1 s2 s3 abgleichen kann. Ich gebe ein spezifisches Beispiel, um meine Bedeutung zu zeigen, bevorzuge aber eine allgemeine Antwort, die ich zu 100% verstehen kann, damit ich sie in anderen Situationen wiederverwenden kann.

Beispiel

Ich möchte fünf Ziffern mit \b\d{5}\bs1 s2 s3 abgleichen , aber nicht in drei Situationen:

s1: Nicht in einer Zeile, die mit einem Punkt wie diesem Satz endet.

s2: Nicht irgendwo in Parens.

s3: Nicht in einem Block, der mit beginnt if(und mit endet//endif

Ich weiß, wie man eines von s1 s2 s3 mit einem Lookahead und Lookbehind löst, besonders in C # Lookbehind oder \Kin PHP.

Zum Beispiel

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

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

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

Aber die Mischung der Bedingungen zusammen lässt meinen Kopf explodieren. Noch schlimmer ist, dass ich möglicherweise zu einem anderen Zeitpunkt andere Bedingungen s4 s5 hinzufügen muss.

Die gute Nachricht ist, dass es mir egal ist, ob ich die Dateien mit den gängigsten Sprachen wie PHP, C #, Python oder der Waschmaschine meines Nachbarn verarbeite. :) Ich bin so ziemlich ein Anfänger in Python & Java, aber interessiert zu erfahren, ob es eine Lösung gibt.

Also bin ich hergekommen, um zu sehen, ob jemand an ein flexibles Rezept denkt.

Hinweise sind in Ordnung: Sie müssen mir keinen vollständigen Code geben. :) :)

Danke dir.

Hans Schindler
quelle
1
\Kist keine spezielle PHP-Syntax. Bitte erläutern und klären Sie, was Sie sagen möchten. Wenn Sie uns mitteilen möchten, dass Sie keine "komplizierte" Lösung benötigen, müssen Sie sagen, was für Sie kompliziert ist und warum.
hakre
@hakre Du meinst, weil Ruby es jetzt benutzt und es in Perl angefangen hat?
Hans Schindler
1
Nein, denn es ist PCRE, das weder PHP (noch Ruby) ist. Perl ist anders, PCRE strebt jedoch an, Perl Regex-kompatibel zu sein.
hakre
Ihre Anforderungen an s2 und s3 scheinen widersprüchlich zu sein. s2 impliziert, dass Klammern immer übereinstimmen und verschachtelt sein können, aber s3 erfordert, dass das: "if("open paren geschlossen wird, nicht mit a ")", sondern mit a : "//endif"? Und wenn Sie für s3 wirklich gemeint haben, dass die if-Klausel mit: geschlossen werden soll "//endif)", dann ist die s3-Anforderung eine Teilmenge von s2.
Ridgerunner
@hakre Ja, ich kenne PCRE, aber um es zu erklären, die Frage bezieht sich auf die Programmiersprache ... es heißt especially in C# lookbehind or \K in PHP... Aber C # sieht nicht nur C # aus, es ist .NET, also kannst du dich auch beschweren, ich sage C # nicht .NET :) Und als Antwort sage ich Ruby nicht Onigurama, das ist auch schlecht ... Gibt es eine andere Sprache, die PCRE verwendet? Wenn ich nicht über Notepad ++ oder Server-Tools spreche, ist dies eine Frage zur Verwendung der Funktion in der Sprache. Ich hoffe, das erklärt und entschuldigt, wenn es falsch aussieht
Hans Schindler

Antworten:

205

Hans, ich nehme den Köder und konkretisiere meine frühere Antwort. Sie sagten, Sie wollen "etwas vollständigeres", also hoffe ich, dass Ihnen die lange Antwort nichts ausmacht - versuchen Sie nur zu gefallen. Beginnen wir mit Hintergrundinformationen.

Zunächst einmal ist dies eine ausgezeichnete Frage. Es gibt häufig Fragen zum Abgleichen bestimmter Muster, außer in bestimmten Kontexten (z. B. innerhalb eines Codeblocks oder in Klammern). Diese Fragen führen oft zu ziemlich umständlichen Lösungen. Ihre Frage zu mehreren Kontexten ist daher eine besondere Herausforderung.

Überraschung

Überraschenderweise gibt es mindestens eine effiziente Lösung, die allgemein, einfach zu implementieren und angenehm zu warten ist. Es funktioniert mit allen Regex-Varianten , mit denen Sie Erfassungsgruppen in Ihrem Code überprüfen können. Und es beantwortet eine Reihe häufig gestellter Fragen, die auf den ersten Blick anders klingen als Ihre: "Alles außer Donuts abgleichen", "Alle außer ... abgleichen", "Alle Wörter außer denen auf der schwarzen Liste meiner Mutter abgleichen", "Ignorieren" Tags "," Temperatur anpassen, sofern nicht kursiv geschrieben "...

Leider ist die Technik nicht gut bekannt: Ich schätze, dass in zwanzig SO-Fragen, die sie verwenden könnten, nur eine eine Antwort hat, die sie erwähnt - was vielleicht eine von fünfzig oder sechzig Antworten bedeutet. Siehe meinen Austausch mit Kobi in den Kommentaren. Die Technik wird ausführlich beschrieben diesem Artikel beschrieben, der sie (optimistisch) als den "besten Regex-Trick aller Zeiten" bezeichnet. Ohne auf so viele Details einzugehen, werde ich versuchen, Ihnen einen genauen Überblick über die Funktionsweise der Technik zu geben. Für weitere Details und Codebeispiele in verschiedenen Sprachen empfehle ich Ihnen, diese Ressource zu konsultieren.

Eine bekanntere Variante

Es gibt eine Variation unter Verwendung der für Perl und PHP spezifischen Syntax, die dasselbe erreicht. Sie werden es auf SO in den Händen von Regex-Meistern wie CasimiretHippolyte und HamZa sehen . Ich werde Ihnen weiter unten mehr darüber erzählen, aber mein Fokus liegt hier auf der allgemeinen Lösung, die mit allen Regex-Varianten funktioniert (solange Sie die Erfassungsgruppen in Ihrem Code überprüfen können).

Danke für den ganzen Hintergrund, zx81 ... Aber wie lautet das Rezept?

Hauptfakt

Die Methode gibt die Übereinstimmung in der Erfassung der Gruppe 1 zurück. Es kümmert sich überhaupt nicht um das Gesamtspiel.

Eigentlich, der Trick darin, die verschiedenen Kontexte, die wir nicht wollen, abzugleichen (diese Kontexte mit dem |ODER / Wechsel zu verketten ) , um sie "zu neutralisieren". All unerwünschten Kontexte, der letzte Teil des Wechsels nach passender übereinstimmt , was wir tun wollen und fängt es an Gruppe 1.

Das allgemeine Rezept ist

Not_this_context|Not_this_either|StayAway|(WhatYouWant)

Dies wird übereinstimmen Not_this_context, aber in gewissem Sinne wird diese Übereinstimmung in einen Papierkorb verschoben, da wir uns nicht die Gesamtübereinstimmungen ansehen: Wir betrachten nur die Erfassungen der Gruppe 1.

In Ihrem Fall können wir mit Ihren Ziffern und Ihren drei zu ignorierenden Kontexten Folgendes tun:

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

Beachten Sie, dass die einzelnen Ausdrücke für s1, s2 und s3 als Tag klar bleiben können, da wir tatsächlich s1, s2 und s3 abgleichen, anstatt zu versuchen, sie mit Lookarounds zu vermeiden. (Sie sind die Unterausdrücke auf jeder Seite von a |)

Der gesamte Ausdruck kann folgendermaßen geschrieben werden:

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

Siehe diese Demo (konzentrieren Sie sich jedoch auf die Erfassungsgruppen im unteren rechten Bereich.)

Wenn Sie mental versuchen, diesen regulären Ausdruck bei jedem zu teilen | Ausdruck Trennzeichen , handelt es sich tatsächlich nur um eine Reihe von vier sehr einfachen Ausdrücken.

Für Aromen, die Freiräume unterstützen, liest sich dies besonders gut.

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

Dies ist außerordentlich einfach zu lesen und zu warten.

Den regulären Ausdruck erweitern

Wenn Sie mehr Situationen s4 und s5 ignorieren möchten, fügen Sie sie links abwechselnd hinzu:

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

Wie funktioniert das?

Die Kontexte, die Sie nicht möchten, werden zu einer Liste von Änderungen auf der linken Seite hinzugefügt: Sie stimmen überein, aber diese Gesamtübereinstimmungen werden nie untersucht. Wenn Sie sie also abgleichen, können Sie sie in einen "Mülleimer" werfen.

Der gewünschte Inhalt wird jedoch in Gruppe 1 erfasst. Anschließend müssen Sie programmgesteuert überprüfen, ob Gruppe 1 festgelegt und nicht leer ist. Dies ist eine triviale Programmieraufgabe (und wir werden später darüber sprechen, wie es gemacht wird), insbesondere wenn man bedenkt, dass Sie einen einfachen regulären Ausdruck erhalten, den Sie auf einen Blick verstehen und nach Bedarf überarbeiten oder erweitern können.

Ich bin nicht immer ein Fan von Visualisierungen, aber diese zeigt sehr gut, wie einfach die Methode ist. Jede "Zeile" entspricht einer möglichen Übereinstimmung, aber nur die unterste Zeile wird in Gruppe 1 erfasst.

Visualisierung regulärer Ausdrücke

Debuggex-Demo

Perl / PCRE-Variation

Im Gegensatz zur obigen allgemeinen Lösung gibt es eine Variation für Perl und PCRE, die häufig bei SO zu sehen ist, zumindest in den Händen von Regex-Göttern wie @CasimiretHippolyte und @HamZa. Es ist:

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

In deinem Fall:

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

Diese Variante ist etwas einfacher zu verwenden, da der in den Kontexten s1, s2 und s3 übereinstimmende Inhalt einfach übersprungen wird, sodass Sie die Erfassungen der Gruppe 1 nicht überprüfen müssen (beachten Sie, dass die Klammern weg sind). Die Übereinstimmungen enthalten nurwhatYouWant

Beachten Sie, dass (*F), (*FAIL)und (?!)sind alle das Gleiche. Wenn Sie dunkler sein wollten, könnten Sie verwenden(*SKIP)(?!)

Demo für diese Version

Anwendungen

Hier sind einige häufige Probleme, die diese Technik oft leicht lösen kann. Sie werden feststellen, dass die Wortwahl dazu führen kann, dass einige dieser Probleme anders klingen, obwohl sie praktisch identisch sind.

  1. Wie kann ich foo außer irgendwo in einem Tag wie übereinstimmen <a stuff...>...</a> ?
  2. Wie kann ich foo außer in einem <i>Tag oder einem Javascript-Snippet abgleichen (weitere Bedingungen)?
  3. Wie kann ich alle Wörter abgleichen, die nicht auf dieser schwarzen Liste stehen?
  4. Wie kann ich etwas in einem SUB ... END SUB-Block ignorieren?
  5. Wie kann ich alles außer ... s1 s2 s3 abgleichen?

So programmieren Sie die Captures der Gruppe 1

Sie haben den Code nicht gewählt, aber zur Vervollständigung ... Der Code zur Überprüfung der Gruppe 1 hängt natürlich von der Sprache Ihrer Wahl ab. In jedem Fall sollte der Code, mit dem Sie Übereinstimmungen überprüfen, nicht mehr als ein paar Zeilen enthalten.

Im Zweifelsfall empfehle ich Ihnen, den Abschnitt mit den Codebeispielen des zuvor erwähnten Artikels zu lesen, in dem Code für einige Sprachen dargestellt wird.

Alternativen

Abhängig von der Komplexität der Frage und der verwendeten Regex-Engine gibt es mehrere Alternativen. Hier sind die beiden, die für die meisten Situationen gelten können, einschließlich mehrerer Bedingungen. Meiner Meinung nach ist keines von beiden annähernd so attraktiv wie das s1|s2|s3|(whatYouWant)Rezept, schon allein deshalb, weil immer die Klarheit gewinnt.

1. Ersetzen Sie dann Match.

Eine gute Lösung, die hackig klingt, aber in vielen Umgebungen gut funktioniert, besteht darin, in zwei Schritten zu arbeiten. Ein erster regulärer Ausdruck neutralisiert den Kontext, den Sie ignorieren möchten, indem er möglicherweise widersprüchliche Zeichenfolgen ersetzt. Wenn Sie nur übereinstimmen möchten, können Sie diese durch eine leere Zeichenfolge ersetzen und die Übereinstimmung im zweiten Schritt ausführen. Wenn Sie ersetzen möchten, können Sie zuerst die zu ignorierenden Zeichenfolgen durch etwas Besonderes ersetzen, z. B. um Ihre Ziffern herum mit einer Kette mit fester Breite von @@@. Nach diesem Austausch können Sie das ersetzen, was Sie wirklich wollten. Dann müssen Sie Ihre markanten @@@Zeichenfolgen zurücksetzen.

2. Lookarounds.

Ihr ursprünglicher Beitrag hat gezeigt, dass Sie verstehen, wie Sie eine einzelne Bedingung mithilfe von Lookarounds ausschließen können. Sie sagten, dass C # dafür großartig ist, und Sie haben Recht, aber es ist nicht die einzige Option. Die .NET-Regex-Varianten, die beispielsweise in C #, VB.NET und Visual C ++ zu finden sind, sowie das noch regexzu ersetzende experimentelle Modulre in Python ersetzt werden soll, sind die einzigen zwei mir bekannten Engines, die Lookbehind mit unendlicher Breite unterstützen. Mit diesen Tools kann eine Bedingung in einem Lookbehind dafür sorgen, dass nicht nur hinter, sondern auch auf das Match und darüber hinaus geschaut wird, sodass keine Koordination mit einem Lookahead erforderlich ist. Weitere Bedingungen? Weitere Lookarounds.

Wenn Sie den regulären Ausdruck, den Sie für s3 in C # hatten, recyceln, würde das gesamte Muster so aussehen.

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

Aber jetzt weißt du, dass ich das nicht empfehle, oder?

Löschungen

@HamZa und @Jerry haben vorgeschlagen, einen zusätzlichen Trick für Fälle zu erwähnen, in denen Sie nur löschen möchten WhatYouWant. Sie erinnern sich, dass das passende Rezept WhatYouWant(Erfassung in Gruppe 1) s1|s2|s3|(WhatYouWant)richtig war? Um alle Instanzen von zu löschen WhatYouWant, ändern Sie den regulären Ausdruck in

(s1|s2|s3)|WhatYouWant

Für die Ersatzzeichenfolge verwenden Sie $1. Was hier passiert, ist, dass für jede Instanz s1|s2|s3, die übereinstimmt, der Ersatz $1diese Instanz durch sich selbst ersetzt (referenziert von $1). Wenn WhatYouWantes dagegen übereinstimmt, wird es durch eine leere Gruppe und nichts anderes ersetzt - und daher gelöscht. Sehen Sie sich diese Demo an , danke @HamZa und @Jerry, dass Sie diese wunderbare Ergänzung vorgeschlagen haben.

Ersatz

Dies bringt uns zu Ersatz, auf die ich kurz eingehen werde.

  1. Wenn Sie durch nichts ersetzen, lesen Sie den obigen Trick "Löschen".
  2. Wenn Sie Perl oder PCRE verwenden, verwenden Sie beim Ersetzen die (*SKIP)(*F)oben genannte Variante, um genau das zu erreichen, was Sie möchten, und führen Sie einen direkten Austausch durch.
  3. Überprüfen Sie in anderen Varianten innerhalb des Ersetzungsfunktionsaufrufs die Übereinstimmung mit einem Rückruf oder Lambda und ersetzen Sie sie, wenn Gruppe 1 festgelegt ist. Wenn Sie dabei Hilfe benötigen, erhalten Sie in dem Artikel, auf den bereits verwiesen wird, Code in verschiedenen Sprachen.

Habe Spaß!

Nein, warte, da ist noch mehr!

Ah, nein, ich werde das für meine Memoiren in zwanzig Bänden speichern, die im nächsten Frühjahr veröffentlicht werden.

zx81
quelle
2
@Kobi Zweiteilige Antwort. Ja, ich habe mich letzte Nacht vom Schreiben mitreißen lassen und unten geschrieben, dass ich darauf schlafen und später aufräumen würde. :) Ja, der Trick ist einfach, aber ich teile nicht Ihre Auffassung, dass er "grundlegend" ist, da er nicht Teil der gängigen Tools zu sein scheint, mit denen Menschen Ausschlussprobleme lösen. Als ich bei SO nach "außer" oder "es sei denn" oder "nicht drinnen" gegoogelt habe, hat nur eine Antwort (ohne Stimmen) dies vorgeschlagen, keine der anderen. Ich hatte übrigens Ihre Antworten nicht gesehen, die großartig sind. :)
zx81
2
Sorry, aber Rex '"Bester Trick" funktioniert einfach nicht ( zuverlässig ). Angenommen, Sie möchten übereinstimmen Tarzan, aber nicht, wenn Sie sich in doppelten Anführungszeichen befinden. Der: /no|no|(yes)/Trick-Regex wäre so etwas wie: /"[^"]*"|Tarzan/(Ignorieren von entkommenen Zeichen). Dies funktioniert in vielen Fällen, schlägt jedoch vollständig fehl, wenn es auf den folgenden gültigen JavaScript-Text angewendet wird : var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";. Der Trick von Rex funktioniert nur, wenn ALLE möglichen Strukturen übereinstimmen - mit anderen Worten - Sie müssen den Text vollständig analysieren, um eine 100% ige Genauigkeit zu gewährleisten.
Ridgerunner
1
Tut mir leid, wenn ich hart klang - das war sicherlich nicht meine Absicht. Mein Punkt (wie in meinem zweiten Kommentar zur ursprünglichen Frage oben) ist, dass eine korrekte Lösung stark vom gesuchten Zieltext abhängt. Mein Beispiel hat JavaScript-Quellcode als Zieltext, der ein doppeltes Anführungszeichen in einer einfachen Zeichenfolge in Anführungszeichen enthält. Es hätte genauso gut ein wörtliches RegExp sein können wie: var bug1 = /"[^"]*"|(Tarzan)/gi;und hatte den gleichen Effekt (und dieses zweite Beispiel ist sicherlich kein Randfall). Es gibt noch viele weitere Beispiele, bei denen diese Technik nicht zuverlässig funktioniert.
Ridgerunner
1
@ridgerunner Ich höre immer gerne von dir, es klingt für mich einfach ungerechtfertigt hart. Wenn wir wissen, dass unsere Zeichenfolgen "falsche Warnungen" enthalten können, passen wir alle unsere Muster an. Um beispielsweise eine Zeichenfolge abzugleichen, die möglicherweise maskierte Anführungszeichen enthält, die einen Zeichenfolgenabgleich auslösen könnten, verwenden Sie möglicherweise " (?<!\\)"(?:\\"|[^"\r\n])*+" Sie ziehen die großen Waffen nicht, es sei denn, Sie haben einen Grund." Das Prinzip der Lösung gilt weiterhin. Wenn wir nicht in der Lage sind, ein Muster für die linke Seite auszudrücken, ist das eine andere Geschichte, wir brauchen eine andere Lösung. Aber die Lösung macht das, was sie bewirbt.
zx81
1
Diese Antwort wurde von Benutzer @funkwurm zu den häufig gestellten Fragen zu Stapelüberlauf- regulären Ausdrücken hinzugefügt.
Aliteralmind
11

Führen Sie drei verschiedene Übereinstimmungen durch und behandeln Sie die Kombination der drei Situationen mithilfe der programminternen bedingten Logik. Sie müssen nicht alles in einem riesigen regulären Ausdruck erledigen.

EDIT: lass mich etwas erweitern, weil die Frage gerade interessanter geworden ist :-)

Die allgemeine Idee, die Sie hier erfassen möchten, besteht darin, mit einem bestimmten Regex-Muster übereinzustimmen, jedoch nicht, wenn bestimmte andere (möglicherweise beliebig viele) Muster in der Testzeichenfolge vorhanden sind. Glücklicherweise können Sie Ihre Programmiersprache nutzen: Halten Sie die regulären Ausdrücke einfach und verwenden Sie einfach eine zusammengesetzte Bedingung. Eine bewährte Methode wäre, diese Idee in einer wiederverwendbaren Komponente zu erfassen. Erstellen wir also eine Klasse und eine Methode, die sie implementiert:

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

Also haben wir oben die Suchzeichenfolge (die fünf Ziffern) und mehrere Ausnahmezeichenfolgen (Ihre s1 , s2 und s3 ) eingerichtet und dann versucht, sie mit mehreren Testzeichenfolgen abzugleichen. Die gedruckten Ergebnisse sollten den Kommentaren in den Kommentaren neben jeder Testzeichenfolge entsprechen.

Yawar
quelle
2
Du meinst vielleicht wie Match drei Regex in einer Reihe? Regex 1 eliminiere Situation 1 (lösche vielleicht nur eine schlechte Ziffer), r2 entferne s2, r3 entferne s3 und stimme mit den verbleibenden Ziffern überein? Das ist eine interessante Idee.
Hans Schindler
Ha, klar, deshalb habe ich dich gestimmt. :) Versteh mich nicht falsch, ich denke immer noch, dass meine Antwort in diesem speziellen Fall effizienter und wartbarer ist. Hast du die freie Version gesehen, die ich gestern hinzugefügt habe? Das ist ein Durchgang und außergewöhnlich einfach zu lesen und zu warten. Aber ich mag deine Arbeit und deine erweiterte Antwort. Entschuldigung, ich kann nicht wieder upvoten, sonst würde ich. :)
zx81
2

Ihre Forderung, dass es nicht innerhalb von Parens ist, ist unmöglich, in allen Fällen zu befriedigen. Wenn Sie nämlich irgendwie (links und )rechts eine finden können, bedeutet dies nicht immer, dass Sie sich in Parens befinden. Z.B.

(....) + 55555 + (.....)- noch nicht in parens gibt es (und )links und rechts

Jetzt könnten Sie sich für schlau halten und (nur dann nach links suchen , wenn Sie )vorher nicht begegnen, und umgekehrt nach rechts. Dies funktioniert in diesem Fall nicht:

((.....) + 55555 + (.....))- innerhalb der Eltern, obwohl es schließen )und( links und rechts.

Es ist unmöglich herauszufinden, ob Sie sich mit Regex in Parens befinden, da Regex nicht zählen kann, wie viele Parens geöffnet und wie viele geschlossen wurden.

Betrachten Sie diese einfachere Aufgabe: Stellen Sie mithilfe von Regex fest, ob alle (möglicherweise verschachtelten) Parens in einer Zeichenfolge geschlossen sind, dh für alle, die (Sie suchen müssen ). Sie werden feststellen, dass es unmöglich ist, es zu lösen, und wenn Sie das nicht mit Regex lösen können, können Sie nicht herausfinden, ob sich ein Wort in allen Fällen innerhalb der Parens befindet, da Sie an einer bestimmten Position in der Zeichenfolge nicht herausfinden können, ob alle vorhergehenden (haben eine entsprechende ).

RokL
quelle
2
Niemand hat etwas über verschachtelte Klammern gesagt, und Ihr Fall Nr. 1 wird durch die Antwort von zx81 einwandfrei behandelt.
Dan Bechard
Vielen Dank für nette Gedanken :) aber verschachtelte Klammern beunruhigen mich nicht für diese Frage, es geht mehr um die Idee von schlechten Situationen s1 s2 s3
Hans Schindler
Natürlich ist es nicht unmöglich! Genau aus diesem Grund müssen Sie die Anzahl der Parens verfolgen, in denen Sie gerade analysieren.
MrWonderful
Wenn Sie eine Art CFG analysieren, wie es OP zu tun scheint, ist es besser, einen LALR oder einen ähnlichen Parser zu generieren, der damit keine Probleme hat.
RokL
2

Hans, wenn es dir nichts ausmacht, habe ich die Waschmaschine deines Nachbarn namens Perl benutzt :)

Bearbeitet: Unterhalb eines Pseudocodes:

  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

Angesichts der Datei 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

Und das Skript 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";
    }
}

Ausführung:

tiago @ dell: ~ $ cat input.txt | perl validator.pl
es sollte mit 12345 übereinstimmen
es sollte mit 12345 übereinstimmen
es sollte mit 12345 übereinstimmen
Tiago Lopo
quelle
2

Ich bin mir nicht sicher, ob dies Ihnen helfen würde oder nicht, aber ich biete eine Lösung unter Berücksichtigung der folgenden Annahmen:

  1. Sie benötigen eine elegante Lösung, um alle Bedingungen zu überprüfen
  2. Die Bedingungen können sich in Zukunft und jederzeit ändern.
  3. Eine Bedingung sollte nicht von anderen abhängen.

Allerdings habe ich auch folgendes berücksichtigt -

  1. Die angegebene Datei enthält nur minimale Fehler. Wenn dies der Fall ist, muss mein Code möglicherweise geändert werden, um damit fertig zu werden.
  2. Ich habe Stack verwendet, um die if(Blöcke im Auge zu behalten .

Ok hier ist die Lösung -

Ich habe C # und damit MEF (Microsoft Extensibility Framework) verwendet, um die konfigurierbaren Parser zu implementieren. Die Idee ist, einen einzelnen Parser zum Parsen und eine Liste konfigurierbarer Validierungsklassen zu verwenden, um die Zeile zu validieren und basierend auf der Validierung true oder false zurückzugeben. Dann können Sie jederzeit einen beliebigen Validator hinzufügen oder entfernen oder neue hinzufügen, wenn Sie möchten. Bisher habe ich bereits für S1, S2 und S3 implementiert, die Sie erwähnt haben. Überprüfen Sie die Klassen unter Punkt 3. Sie müssen Klassen für s4, s5 hinzufügen, wenn Sie dies in Zukunft benötigen.

  1. Erstellen Sie zunächst die Schnittstellen -

    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. Dann kommt der Dateireader und Checker -

    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. Dann kommt die Implementierung einzelner Prüfer, die Klassennamen sind selbsterklärend, daher denke ich nicht, dass sie weitere Beschreibungen benötigen.

    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. Das Programm -

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

Zum Testen habe ich die Beispieldatei von @ Tiago genommen, Test.txtdie die folgenden Zeilen enthielt:

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

Gibt die Ausgabe -

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

Ich weiß nicht, ob dir das helfen würde oder nicht, ich hatte eine lustige Zeit damit zu spielen .... :)

Das Beste daran ist, dass zum Hinzufügen einer neuen Bedingung lediglich eine Implementierung bereitgestellt werden muss IPatternMatcher, diese automatisch aufgerufen und somit validiert wird.

hirnloser Codierer
quelle
2

Wie @ zx81, (*SKIP)(*F)jedoch mit einer negativen Lookahead-Behauptung.

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

DEMO

In Python würde mir das leicht gefallen,

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.

Ausgabe:

000
111
222
333
Avinash Raj
quelle