Lemmy Danger: Validierung mit Regex endet bei einem Mismatch in Endlosschleife

Hallo zusammen!

Ich als Regex-Novize habe irgendwie Schwierigkeiten, einen passenden Ausdruck für folgende Aufgabenstellung zu finden:

Ich möchte einen Text validieren, der keine Sonderzeichen außer Punkt, Komma, Bindestrich, Ausrufe- und Fragezeichen enthalten darf. Leerzeichen und Zeilenumbrüche dürfen vorkommen. Das bereitet mir noch keine Schwierigkeiten. Es dürfen aber im Text zwei mit Sonderzeichen versehene Strings enthalten sein, nämlich $[VERSION] und $[NAME]. Diese können irgendwo im Text vorkommen, auch mehrfach. Ich habe dafür folgenden Ausdruck definiert:

([\w \r\n.?!,-]*($[(NAME|VERSION)])*[\w \r\n.?!,-]*)*

Die Idee von mir war, dass vor oder nach den fixen Strings, die 0 oder mehrfach vorkommen dürfen, 0 oder mehrere valide Zeichen stehen dürfen... und das 0 oder mehrmals, damit die Strings auch nicht nur einmal vorkommen dürfen.

Im Prinzip funktioniert das Ganze, solange der Text, den ich prüfe, valide ist. Nur leider endet meine (Java-)Applikation in einer Endlosschleife, wenn ich im Text ein Sonderzeichen wie z.B. = oder $ einbaue bzw. statt $[NAME] $[TEST] schreibe. Ich habe aber keinen blaßen Schimmer, warum...

Wenn ich die äußerste Gruppierung weglasse, also

[\w \r\n.?!,-]*($[(NAME|VERSION)])*[\w \r\n.?!,-]*

verwende, schmiert meine Anwendung nicht ab, wenn die Prüfung fehlschlägt. Allerdings darf dann nur $[VERSION] oder $[NAME] im Text vorkommen, und das auch nur ein einziges Mal.

Vielleicht hat hier der ein oder andere Regex-Profi einen Tipp für mich und kann mir bei der Fehlersuche behilflich sein? Falls jemand eine andere Idee hat, wie man einen solchen Text validieren kann, bin ich natürlich auch dankbar.

Vielen Dank vorab!
Oliver

--
Es gibt drei Moeglichkeiten, eine Firma zu ruinieren: Mit Spielen, das ist am lustigsten. Mit Frauen, das ist am schoensten. Mit Computern, das ist am sichersten.
  1. Hallo Oliver

    Ich habe zwar von Java kaum Ahnung, arbeite aber unter Perl viel mit regulären Ausdrücken. Deinem Beispiel nach werden unter Perl und Java Regex sehr ähnlich benutzt. Daher äußere ich mich mal zum Problem.

    Die Endlosschleife könnte damit zusammenhängen, dass Java verzweifelt versucht, einen Ausdruck in deinem String zu finden, der zur Regex passt. Da es hierfür beim ersten Beispiel wegen der äußeren Klammer schier unerschöpflich viele Möglichkeiten gibt (die alle getestet werden müssen, bevor feststeht, dass das Gesuchte wirklich nicht vorkommt), dauert das Ganze eine halbe Ewigkeit.

    In Perl würde ich das Problem wie folgt lösen:
    /^([\w \r\n.?!,-]?($[VERSION]|$[NAME])*[\w \r\n.?!,-]?)*$/
    Ich lasse Perl also nach einem Ausdruck suchen, der mit einem der zulässigen Zeichen oder keinem Zeichen anfängt (falls $[VERSION] oder $[NAME] am Stringanfang stehen), dann kann entweder $[VERSION] oder $[NAME] folgen, muss aber nicht, und dann wieder eines der zulässigen Zeichen oder kein Zeichen. Dieser ganze Ausdruck kann zwischen Stringanfang (das ^) und Stringende ($) beliebig oft, also auch keinmal (leerer String passt bei dieser Regex auch), vorkommen. Der Trick an der Sache ist nun, dass zwischen Stringanfang und -ende keine Zeichen zulässig sind, die nicht auf das Suchmuster zwischen den beiden äußeren Klammern passen.

    Noch drei Anmerkungen, die mir beim Testen aufgefallen sind und möglicherweise auch für Java gelten:
    1.) \w passt auf alle Buchstaben und den Unterstrich! Falls du den Unterschrich nicht zulassen willst, musst du A-Za-z anstelle von \w verwenden
    2.) Umlaute und ß dürfen nicht vorkommen, da sie nicht als Buchstaben gelten. Falls sie zugelassen werden sollten, sind sie seperat anzugeben ([öÖäÄüÜß\w \r\n.?!,-])
    3.) $[ interpretiert Perl aus irgend einem Grund als Sonderzeichen. Da ich einen Teststring direkt einer Variable zugewiesen habe, musste ich $ maskieren. Bei Java oder Eingabe des Strings per Tastatur mag das anders aussehen. Auch unmaskierte $, welche von anderen Zeichen gefolgt werden, haben bei meinem Test Probleme gemacht. (Wieso? Keine Ahnung)

    Vielleicht hilft dir das weiter, auch wenn ich nur mit Perl getestet habe.

    Viele Grüße

    Uwe

    1. Hallo Uwe!

      Zunächst einmal vielen Dank für Deine ausführlichen Erläuterungen und für die Mühe, das Problem nachzuvollziehen. Ich habe das von Dir vorgeschlagene Pattern einmal ausprobiert, leider aber mit dem gleichen Ergebnis: Wenn der Text valide ist, funktioniert der Match reibungslos. Baue ich jedoch an irgendeiner Stelle ein Sonderzeichen ein (z.B. ein $ im Fließtext), hängt sich meine Anwendung wieder auf.

      Kannst Du das in Perl auch beobachten? Ich verwende z.B. den Text

      Die aktuellste $[NAME]-Version ist $[VERSION].\r\nDie aktuellste $[NAME]-Version ist $[VERSION].

      Das Pattern funktioniert hier wunderbar. Wenn ich nun aber

      Die aktuel$lste $[NAME]-Version ist $[VERSION].\r\nDie aktuellste $[NAME]-Version ist $[VERSION].

      prüfe, komme ich wieder in die Endlosschleife. Ich verstehe das einfach nicht. Dein Pattern müsste doch eigentlich nach "Die aktuel" ein ungültiges Zeichen feststellen und abbrechen...

      Als Workaround habe ich schon überlegt, die beiden fixen Strings vor der Prüfung durch Platzhalter à la ".V.E.R.S.I.O.N." zu ersetzen und danach wieder einzufügen, aber das wäre irgendwie unbefriedigend... schließlich sollte es m.E. doch auch per Regex gehen.

      Der Java-Code ist denkbar einfach; ich denke nicht, dass es daran liegen könnte, denn es handelt sich hierbei um eine statische Methode der Klasse "Pattern" aus der Java-Core-API:

      boolean match = Pattern.matches(this.getRegex(), this.getInput());

      Hast Du vielleicht noch eine Idee, was ich sonst versuchen könnte?

      Viele Grüße,
      Oliver

      PS: Danke auch für Deine drei Anmerkungen... Umlaute und ß hätte ich glatt vergessen.

      --
      Unter 1000 Luegen klingt eine Wahrheit wie ein falscher Ton.
      1. Hallo Oliver

        Ich habe es noch einmal mit deinen beiden Beispielen und noch ein paar anderen Fällen getestet. Die Ausführungszeit für das Perl-Skript ist immer annähernd null, unabhängig davon, ob der String passt oder nicht und ich erhalte auch stets das erwartete Ergebnis.
        Meine einzige Idee wäre noch, sich zuvor den Inhalt von this.getRegex() und this.getInput() ausgeben zu lassen und zu überprüfen, ob beide Strings das enthalten, was sie sollen. Außerdem kannst du noch mit anderen Sonderzeichen wie & oder § prüfen, ob dort das Problem auch auftritt.
        Ansonsten kann ich dir leider nicht weiterhelfen, da ich kaum Erfahrung mit Java habe.

        Das $-Problem, welches ich angesprochen habe, ist übrigens Perl-spezifisch. Jede Variable fängt mit $ und muss daher bei Strings, welche direkt im Quellcode angegeben werden, maskiert werden.

        Viele Grüße

        Uwe

        1. Hallo Uwe!

          Mmh, das ist natürlich interessant, dass es bei Dir mit Perl funktioniert, bei mir mit Java jedoch nicht. Damit weiß ich doch schon mal, dass der Fehler nicht im Pattern liegt.

          Am Aufruf in Java kann es meines Erachtens nicht liegen, denn an diesem Einzeiler gibt's glaube ich nicht viel zu verbessern. Ich habe das dennoch noch einmal auseinander gedröselt (mit Hilfe zweier Klassen, Pattern und Matcher) und mir die an diese Klassen übergebenen Strings ausgeben lassen, aber dort war kein Fehler zu finden. Der String, den mir mein Java-Pattern-Objekt aber ausgegeben hat, war identisch mit dem von Dir vorgeschlagenen.

          Es ist also entweder ein Bug in der Java-Core-API oder ein Problem zwischen Monitor und Stuhl... ich tippe eher auf Letzteres ;-)

          Ich denke mal, dass ich mir nun mit meinem Workaround behelfen werde. Wie schon erwähnt finde ich den zwar nicht sonderlich schick, aber er taugt für meine Zwecke. Dennoch werde ich das Thema im Hinterkopf behalten... vielleicht finde ich ja demnächst ein bißchen mehr Zeit und Muse, nach einer geeigneten Lösung zu suchen.

          Herzlichen Dank noch mal für die Mühe, die Du Dir gemacht hast, mein Problem nachzustellen, und die ausführlichen Erläuterungen!

          Viele Grüße,
          Oliver

          --
          Man braucht nicht immer denselben Standpunkt zu vertreten, denn niemand kann einen daran hindern, klueger zu werden.