Laden...

Sich überschneidende Regex-Matches finden?

Erstellt von bredator vor 9 Jahren Letzter Beitrag vor 9 Jahren 1.664 Views
B
bredator Themenstarter:in
357 Beiträge seit 2010
vor 9 Jahren
Sich überschneidende Regex-Matches finden?

Hi zusammen,

ich habe aus einer OCR eine Ausgabe erhalten, die wie folgt aussehen kann:

[cre][lItl/x1i][1Itl/x][9oO030][0oO0][1Itl/x2][1Itl/x][53S][HMNRBER][0oO0][HMNRBE2][HMNRBEK][0oO0][tItl/xi(][HMNRBEhbL]

Dieser Teil des Programms ist fix, ich kann da keinen Einfluss auf die Ausgabe nehmen. Nun habe ich aus diesem Kram (zwischen den Eckigen Klammern sind alle Möglichkeiten, die ein Zeichen laut OCR haben kann) mittels Regex ein Datum extrahiert. Der Regex sieht bisher so aus:

[[\w()/]{0,7}[0-3]{1}[\w()/]{0,7}][[\w()/]{0,7}[0-9]{1}[\w()/]{0,7}][[\w()/]{0,7}[0-1]{1}[\w()/]{0,7}][[\w()/]{0,7}[0-9]{1}[\w()/]{0,7}][[\w()/]{0,7}[1-2]{1}[\w()/]{0,7}][[\w()/]{0,7}[0-9]{1}[\w()/]{0,7}]

Funktioniert bei einem Großteil der Daten auch ganz gut. Allerdings in diesem Beispiel hier nicht, da dieser Regex auch ein "Zeichen" vorher matcht. Herauskommen würde dann 119011, was natürlich für ein Datum eher ungünstig ist. Einen weiteren Match gibt es allerdings nicht, da die gematchten Teile bei der weiteren Untersuchung nicht mehr berücksichtigt werden. Nun ja, klar könnte ich jetzt an dieser Stelle eine Datumsprüfung machen und anhang des Index des Matches dann einen neuen Eingabe-String bauen, auf den dann wiederrum geprüft wird usw. Aber ich frage mich halt, ob das nicht auch einfacher geht. Kann man dem Regex nicht irgendwie sagen, dass er auch bereits gematchte Teile für weitere Matches berücksichtigen soll?

Gruß

2.921 Beiträge seit 2005
vor 9 Jahren

Erklär doch mal etwas mehr über die Bedingungen.
Dann kann man zusammen vielleicht den Ausdruck auch reduzieren.
Meistens gehen die viel einfacher als man denkt.

und hast Du das Regex-Lab hier aus dem Forum dazu schon mal benutzt, um Deine Ausdrücke zu testen?

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

B
bredator Themenstarter:in
357 Beiträge seit 2010
vor 9 Jahren

Also, der Teil, den ich aus der OCR erhalte ist Teil eines Formulars. In diesem Formular ist ein Datum gedruckt im Format TTMMYY. Dazu ist aber auch noch alles mögliche andere aufgedruckt oder reingekritzelt. Daher soll das Datum irgendwie rausgehoolt werden aus diesem Zeichengewirr. Es gibt natürlich auch den Fall, dass etwas über das Datum gedruckt wurde, aber das sind jene Fälle, die ich ignorieren kann und muss. Allerdings habe ich auch in Ausnahmefällen schon gesehen, dass das Datum auch mit TT.MM.YY aufgedruckt sein kann. Diesen Spezialfall abzudecken wäre aber erst in der 2. Stufe interessant, da er wohl recht selten auftritt...

Daher dachte ich mir, dass ich diesen Regex ja von mir aus schon möglichst einschränken kann. Und zwar so:

  1. Stelle: 0-3
  2. Stelle: 0-9
  3. Stelle: 0-1
  4. Stelle: 0-9
  5. Stelle: 1-2
  6. Stelle: 0-9

Damit kann ich Datumsangaben bis 2029 abdecken, was die Laufzeit der Software wohl überschreiten würde. Evtl. kann ich aus 1-2 noch 1-3 machen an der 5. Stelle, dann wäre 2039 als Enddatum angesagt.

Wie gesagt, der Teil, den ich aus der OCR erhalte ist fix und daran kann ich am Code auch leider nichts ändern. Ebenfalls der Bildausschnitt, da der von dem Programm um die OCR herum festgelegt wird (sonst hätte ich versucht, diesen exakter um das Datumsfeld zu platzieren - aber da die Bedruckung nicht immer 100%ig platziert ist, wäre auch das erst mal problematisch).

In meinem Beispiel würde mit meinem Pattern allerdings die Zeichenkombination [lItl/x1i][1Itl/x][9oO030][0oO0][1Itl/x2][1Itl/x] gefunden. Nach Extraktion der jeweils ersten Zahl aus jeder Gruppe würde hier allerdings 119011 als Datum herauskommen. Die führende 1 kommt von erwähntem Gekritzel und unsauberem Bedrucken, die OCR kann das in bestimmten Fällen nicht unterscheiden.

Das Parsen schlägt logischerweise fehlt und ich habe kein Datum als Ergebnis. Es wird jetzt allerdings von der Regex-Klasse alles ignoriert, was bei einem vorherigen Match schonmal betroffen war. Die gesamte Gruppe vom ersten (falschen) Datum fliegt raus und aus dem Rest ergibt sich kein weiterer Match. Besser fände ich, wenn ALLE möglichen Matches aus dem gesamten Eingabestring gefunden werden würden - also [lItl/x1i][1Itl/x][9oO030][0oO0][1Itl/x2][1Itl/x] und [1Itl/x][9oO030][0oO0][1Itl/x2][1Itl/x][53S]. Bei letzterem würde das Parsen des Datums funktionieren und die Plausiprüfung (darf nicht in der Zukunft liegen) wäre erfolgreich. Ich hätte mein Datum.

Falls sich da von Seiten der Regex-Klasse nichts anbietet, werde ich halt den umständlichen Weg gehen und meinen Eingabestring so lange um je eine Zeichengruppe verkürzen, bis ich ein passendes Ergebnis habe (bis hin zu "Es gibt kein Ergebnis"). Möglich wäre zwar auch (in diesem Beispiel) den Regex von rechts nach links laufen zu lassen. Da finde ich zwar dieses Datum, allerdings kann mir dasselbe Problem natürlich auch hier in die Quere kommen.

Wie gesagt, war eher als Frage gemeint, ob ich da einfach was übersehen habe, was mir die Arbeit erheblich erleichtern könnte. Man lernt ja immer gerne mal was dazu. Aber im Zweifelsfall gehe ich halt den etwas holprigeren Weg und komme damit ebenfalls an mein Ziel 😉

Edit: Ja, Regex Studio ist bei mir immer in Gebrauch, da einfach bequemer. Vor ein paar Jahren noch habe ich mich gefragt, wozu man so ein Tool brauchen könnte, aber inzwischen will ich nicht mehr ohne 😉

49.485 Beiträge seit 2005
vor 9 Jahren

Hallo bredator,

wenn du die Einschränkungen auf die gültigen Bereiche für die Ziffern direkt in den Pattern aufnimmst, bekommst du den falschen Treffer aus deinem Beispiel erst gar nicht, sondern gleich nur den richtigen. Das nur so am Rande.

Ansonsten frisst Regex den kompletten Match und sucht nach folgenden Matches erst dahinter weiter. Das ist so und das ist - in den allermeisten Fällen - auch gut so.

Es gibt natürlich noch Lookarounds. Diese zählen nicht zum Match und fressen daher auch nichts weg. Trotzdem kann man das, was ein Lookaround gematcht hat, auslesen, wenn man (zusätzlich) eine Gruppe benutzt, z.B. a(?=(\d)). In der Gruppe, die man natürlich bei Bedarf auch benennen kann, steht dann die gefundene Ziffer.

Wenn du den Pattern so schreibst, dass er immer nur auf das erste (OCR-)Zeichen passt, vorausgesetzt, dahinter folgt per Lookahead das Gewünschte, dann bekommst du alle Treffer von allen Stellen, bei denen es sich möglicherweise um ein Datum handelt.

herbivore

B
bredator Themenstarter:in
357 Beiträge seit 2010
vor 9 Jahren

Ah ok, ich würde mich also von einem ersten Zeichen ausgehend weiter durchtasten und das erste Zeichen verwerfen (+Matching von vorne), wenn es nicht passen sollte? Das sollte eigentlich machbar sein. Danke für den Anstoß 😉