Das Auslesen von Adress-/Anschriftbereichen in Briefen war schon immer eine recht schwierige Problematik. Die Freude war umso größer, als Kofax vor einigen KTM-Versionen (Kofax Transformation Modules ) ein Werkzeug (Adress-Lokator) für das automatisierte Auslesen von Adressen bereitstellte. Leider währte die Freude darüber nicht lange, denn dieser neue Lokator erkannte nur amerikanische Adressen. Er ist nur eine Blackbox und nicht konfigurierbar (abgesehen von Regionszonen). Da sich in den neueren KTM-Versionen daran nichts änderte und ein Kunde mal wieder Bedarf nach dem Auslesen des Anschriftbereichs äußerte, habe ich nun dafür eine einfache ‚Zu-Fuß‘-Lösung entwickelt. Damit sollte ein großer Teil der deutschen Anschreiben erkannt werden.
Die erste Idee war, einen Datenbanklokator mit einer Fuzzy DB zu nutzen. Grundlage dafür war eine CSV-Datei, die die Straßen mit Hausnummern, PLZ und Ort enthält. Solche Dateien kann man käuflich erwerben oder sich mit etwas Aufwand selbst zusammenstellen:
:
Schwedenloch;1;94250;Achslach
Dorfplatz;1;94250;Achslach
Bahnhofstraße;2;85111;Adelschlag
Am Bründl;3;85111;Adelschlag
Am Bründl;9;85111;Adelschlag
Am Bründl;7;85111;Adelschlag
:
Leider waren die Testergebnisse nicht so gut wie erhofft. Das lag einerseits am doch unvollständigen und nicht immer korrekten Datenmaterial, aber auch den Eigenheiten der Suche mit der Fuzzy DB. Als nächstes kam eine Fuzzy DB nur mit den PLZ und Orten zum Einsatz. Aber auch damit konnten keine zufriedenstellenden Ergebnisse erzielt werden.
Das Ziel beider Ansätze war, die Zeile mit dem Straßennamen bzw. die Zeile mit PLZ/Ort mit sehr hoher Konfidenz zu finden. Ausgehend von dieser Zeile kann man dann mit etwas KTM-Skripting die restlichen Adressbestandteile „zusammensuchen“.
Schlussendlich wurde dann der einfache, aber deterministische Ansatz mit einem einfachen Formatlokator gewählt, um die Zeile mit PLZ/Ort eindeutig zu identifizieren. Dabei wäre es naheliegend, im Formatlokator Wörterbücher mit PLZ bzw. Orten einzusetzen:
:
01067,Dresden,,
01069,Dresden,Innere Altstadt,
01097,Dresden,,
01099,Dresden,,
:
Aber auch dabei fielen wieder Fehler im Datenbestand auf.
Daher wurde nur ein Wörterbuch mit den Ortsnamen genutzt. Die PLZ wurde stattdessen durch einen regulären Ausdruck dargestellt. Das hatte den weiteren Vorteil, dass auch alle PLZ von Postfächern gefunden wurden. Der Formatlokator sucht also alle fünfstelligen Zahlen links vor einem deutschen Städtnamen in der Region, in der normalerweise die Empfängeradresse steht. Hier der eingesetzte, einfache Formatlokator:
Bem.: „Staedte“ ist ein KTM-Wörterbuch, das aus einer Liste aller deutschen Städtenamen besteht (siehe unten).
Der Test findet zwei mögliche Treffer:
42697 (Solingen) und
12345 (Dortstadt)
Hier noch die Definition des Wörterbuchs „Staedte“:
Mit den Ergebnissen des Formatlokators werden nun die restlichen Adressbestandteile per KTM-Skript bestimmt. Der Einfachheit halber wird nach einer Zeile mit PLZ/Ort, einer Zeile mit Straße/Hausnummer bzw. Postfach und nach maximal zwei Namenszeilen gesucht (Leerzeilen ausgenommen).
Grundsätzlich wurde dabei folgendes Vorgehen implementiert:
Von allen Alternativen des Formatlokators mit einer Konfidenz >.99 wird die „unterste“ genommen, um einen eventuellen Treffer in einer Absenderzeile zu vermeiden. Die gefundenen Alternativen legt die Zeilenummer der Zeile mit PLZ/Ort fest. Aus dieser Zeile können dann PLZ und Ort bestimmt werden (Details: siehe Skripting unten).
Die Straße/Postfach sollte in der darüberliegenden Zeile stehen. Dabei müssen einerseits Leerzeilen ignoriert werden, aber auch Zeilen, die nur „rechts“ in der Zeile Text beinhalten. Das kann über die Eigenschaft „left“ im Skript festgestellt werden. Sobald eine passende Zeile gefunden wurde, kann dann die Straße bzw. das Postfach bestimmt werden.
Ein ähnliches Vorgehen liefert dann noch bis zu zwei Namenszeilen.
Das Skripting im KTM-Projekt wurde im Document_AfterExtract-Event der Dokumentklasse untergebracht. Aber man kann es natürlich als Skriptlokator implementieren.
1' ' Class script: Doks 2Private Sub Document_AfterExtract(ByVal pXDoc As CASCADELib.CscXDocument) 3 4Dim anztreffer As Integer 5Dim i As Integer 6Dim PLZ As String 7Dim zeilennummer As Integer 8Dim zeile As String 9Dim Ort As String 10Dim j As Integer 11Dim tmpZeile As String 12Dim Name1 As String 13Dim Name2 As String 14Dim sPattern As String 15 16'*************************************************************** 17'PLZ und Ort finden 18'alle Alternativen des Lokators 'Orte' durchgegehen, die eine Konfifdenz >.99 haben und die letzte (unterste) davon nehmen 19'dadurch wird verhindert, dass eine eventuell vorhandene Absenderzeile oberhalb der Empfängeradresse genommen wird 20anztreffer=pXDoc.Locators.ItemByName("Orte").Alternatives.Count 'Anzahl Alternativen des Lokators 21If anztreffer>0 Then 22 For i= 0 To anztreffer-1 'Schleife über alle Alternativen 23 If pXDoc.Locators.ItemByName("Orte").Alternatives(i).Confidence>.99 Then 'nur 100% nehmen 24 PLZ=pXDoc.Locators.ItemByName("Orte").Alternatives(i).Text 'die PLZ ist der Wert der aktuellen Alternative 25 'Bestimmen der Zeilennummer der aktuellen Alternative 26 zeilennummer=pXDoc.Locators.ItemByName("Orte").Alternatives(i).Words.ItemByIndex(0).LineIndex 27 zeile=pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(zeilennummer).Text 'Die komplette Textzeile der Alternative 28 'Der Ort steht rechts von der PLZ 29 'Es könnte auch noch Text von der rechten Seite enthalten sein 30 'Nur Words mit left<1100 nehmen. 31 Ort="" 32 For j= 0 To pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(zeilennummer).Words.Count-1 'nur words mit left<1100 nehmen 33 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(zeilennummer).Words(j).Left<1100 Then 34 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(zeilennummer).Words(j).Text<>PLZ Then 35 Ort=Ort+pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(zeilennummer).Words(j).Text+" " 36 End If 37 End If 38 Next 39 Ort=Trim(Ort) 40 End If 41 Next 42 pXDoc.Fields.ItemByName("PLZ").Text=PLZ 'Ergebnis in die Felder stellen 43 pXDoc.Fields.ItemByName("Ort").Text=Ort 44 45 '*************************************************************** 46 'Oberhalb von PLZ/Ort sollte die Strasse stehen (oder Leerzeilen) 47 'Nur die ersten 40 Zeichen berücksichtigen, da weiter rechts noch anderer Text stehen kann. 48 'Die ganze Adresse soll nur aus maximal 5 Zeilen bestehen 49 If zeilennummer>0 Then 'PLZ/Ort wurden oben gefunden 50 tmpZeile="" 51 i=zeilennummer 'das ist die Zeile mit PLZ/Ort 52 Do While tmpZeile="" 'ausgehend von der PLZ/Ort-Zeile aufwärts nach der Strassenzeile suchen 53 i=i-1 54 'Nur die ersten 40 Zeichen berücksichtigen, da weiter rechts noch anderer Text stehen kann. 55 tmpZeile=Trim(Left(pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i).Text,40)) 56 'Es kann sein, dass der Text von der rechten Seite kommt, weil die Adresse hier eine Leerzeile hat. 57 'Alles was left>1100 ist von der rechten Seite und kann weg 58 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i).Left>1100 Then 59 tmpZeile="" 60 End If 61 'irgendwann muss Schluß sein. Bei Adressen mit sehr vielen ggf. hier anpassen 62 If zeilennummer-i=5 Then Exit Do 63 Loop 64 'in der Strassenzeile alle Worte mit Left>=1100 entfernen, da sie von der rechten Bildseite stammen 65 ' i ist Index der Strassenzeile 66 tmpZeile="" 67 For j= 0 To pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i).Words.Count-1 'nur words mit left<1100 nehmen 68 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i).Words(j).Left<1100 Then 69 tmpZeile=tmpZeile+pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i).Words(j).Text+" " 70 End If 71 Next 72 tmpZeile=Trim(tmpZeile) 73 pXDoc.Fields.ItemByName("Strasse").Text=tmpZeile 'Ergebnis in Feld stellen 74 75 76 '*************************************************************** 77 'jetzt noch maximal zwei Zeilen aufwärts für Name/Abtlg. o.ä. 78 Name2="" 79 For j= 0 To pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-1).Words.Count-1 80 'nur words mit left<1100 nehmen, damit nichts von der rechten Seite genommen wird 81 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-1).Words(j).Left<1100 Then 82 Name2=Name2+pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-1).Words(j).Text+" " 83 End If 84 Next 85 Name1="" 86 For j= 0 To pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-2).Words.Count-1 87 'nur words mit left<1100 nehmen, damit nichts von der rechten Seite genommen wird 88 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-2).Words(j).Left<1100 Then 89 Name1=Name1+pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-2).Words(j).Text+" " 90 End If 91 Next 92 If Name1="" Then 'es gab nochmal eine Leerzeile 93 For j= 0 To pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-3).Words.Count-1 94 'nur words mit left<1100 nehmen, damit nichts von der rechten Seite genommen wird 95 If pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-3).Words(j).Left<1100 Then 96 Name1=Name1+pXDoc.Pages.ItemByIndex(0).TextLines.ItemByIndex(i-3).Words(j).Text+" " 97 End If 98 Next 99 End If 100 'falls Name1 eine PLZ (5 Ziffern) enthält, ist es die Absenderzeile - ignorieren 101 sPattern="\d{5}" 102 'Funktion zur Suche mit regulären Ausdrücken (siehe Skript auf Projektebene) 103 If RegExprFound(Name1,sPattern)=True Then 104 Name1="" 105 End If 106 pXDoc.Fields.ItemByName("Namenszeile1").Text=Name1'Ergebnis in Feld stellen 107 pXDoc.Fields.ItemByName("Namenszeile2").Text=Name2 108 End If 109 110End If 111End Sub 112 113 114'*************************************************************************** 115' Mit dieser Funktion kann mit Regulären Ausdrücken gesucht werden 116' Als Referenz ist "Microsoft VBScript Regular Expressions 5.5" einzubinden 117' 118Public Function RegExprFound(sText As String, sPattern As String) As Boolean 119 Dim oRegEx As New RegExp 120 Dim oMatches As MatchCollection 121 122 oRegEx.Pattern = sPattern 123 Set oMatches = oRegEx.Execute(sText) 124 If oMatches.Count = 1 Then 125 RegExprFound=True 126 Else 127 RegExprFound=False 128 End If 129End Function
Hier die Ergebnisse mit unterschiedlich aufgebauten Briefköpfen (Adresse und rechte Seite):
Fazit
Der standardmäßig vorhandene Adress-Lokator von KTM liefert nur für amerikanische Adressen Ergebnisse. Versuche, deutsche Adressen mit Fuzzy-DB-Suche und verschiedenen Datenbeständen (PLZ, Orte, Strassen, Hausnummern) zu extrahieren, lieferten auch keine befriedigenden Resultate. Wir haben daher einen einfachen Formatlokator benutzt, um die Textzeile mit der PLZ und dem Ort zu bestimmen. Davon ausgehend konnten dann mit etwas KTM-Skripting die restlichen Adressbestandteile bestimmt werden.
Weitere Beiträge
von Jürgen Voss
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Jürgen Voss
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.