Entwickler-Ecke
Grafische Benutzeroberflächen (VCL & FireMonkey) - Suche im String nach Euro-Beträgen
zongo-joe - Sa 02.02.19 17:52
Titel: Suche im String nach Euro-Beträgen
Tag zusammen, ich muss meine Kontoauszüge parsen, dazu habe ich einen kleinen Parser geschrieben, der aus Strings Euro-Beträge extrahieren kann; soweit ich das probiert habe, kommt er mit sehr vielen versteckten Tücken der Kontoauszüge zurecht.
Ist zwar etwas krude gecodet, aber funzt.
Hoffe er gefällt Euch. Für Verbesserungsvorschläge bin ich natürlich offen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135:
| function eurosuchen(x:string;trenner:char;erster,meldung,negativ,zweinachkomma,runden:boolean):string; const waehrung='€'; var i,pos1,pos2 : integer; rbetrag:string; gefund:boolean; hlp:char;
function betrag(x:string; posi:integer; trenner:char):string; const meldung=true; var i,pos2,pos3:integer; res:string; begin res:='';
dec(posi); while (posi > 0) and (x[posi] in [' ','0'..'9','-',trenner]) do begin res:= x[posi] + res; dec(posi); end;
while (length(res) > 0) and (res[length(res)] in [' ',trenner,'-']) do begin res := copy(res,1,length(res)-1); end;
while (length(res) > 0) and (res[1] in [' ',trenner]) do begin res := copy(res,2,length(res)-1); end;
pos1:=0; for i:=1 to length(res) do if res[i]=' ' then pos1:=i; if pos1>0 then res := copy(res,pos1+1,length(res)-pos1);
repeat i:=length(res); pos2:=0; for i:=length(res) downto 1 do if res[i]=trenner then begin inc(pos2); pos3:=i; end; if pos2>1 then res:=copy(res,pos3+1,length(res)-pos3); until pos2<2;
repeat i:=length(res); pos2:=0; for i:=length(res) downto 1 do if res[i]='-' then begin inc(pos2); pos3:=i; end; if pos2>1 then res:=copy(res,pos3+1,length(res)-pos3); until pos2<2;
if pos('-',res) > 1 then res:=copy(res,pos('-',res),length(res)-pos('-',res)+1);
result:=res;
end;
begin rbetrag:=''; gefund:=false;
if length(x) > 0 then begin
if erster then begin i:=1; while (not gefund) and (i<=length(x)) do begin if x[i]=waehrung then begin rbetrag:=betrag(x,i,trenner); if rbetrag<>'' then gefund:=true; end; inc(i); end;
end else begin i:=length(x); while (not gefund) and (i>0) do begin if x[i]=waehrung then begin rbetrag:=betrag(x,i,trenner); if rbetrag<>'' then gefund:=true; end; dec(i); end;
end; if gefund and (not negativ) and (rbetrag[1]='-') then rbetrag:=copy(rbetrag,2,length(rbetrag)-1);
if zweinachkomma and (pos(trenner,rbetrag)>0) and (pos(trenner,rbetrag) < length(rbetrag)-2) then begin if runden then begin hlp:=rbetrag[pos(trenner,rbetrag)+3]; if hlp in ['5','6','7','8','9'] then rbetrag:=copy(rbetrag,1,pos(trenner,rbetrag)+1) + intTostr(strToInt(copy(rbetrag,pos(trenner,rbetrag)+2,1))+1) else rbetrag := copy(rbetrag,1,pos(trenner,rbetrag)+2);
end else begin rbetrag := copy(rbetrag,1,pos(trenner,rbetrag)+2);
end; end;
end; if (not gefund) and meldung then showmessage('Keinen gültigen Betrag in '+x+' gefunden');
result:=rbetrag;
end; |
Aufruf: zB:
showmessage(eurosuchen('Hans Dampf, Geld für Bestellnummer 4567.44, 314,14€',',',true,true,true,true,true));
Delete - Sa 02.02.19 18:02
- Nachträglich durch die Entwickler-Ecke gelöscht -
zongo-joe - Sa 02.02.19 18:10
Warum ? die nützt hier nicht viel, weil sie imho nur die erste position im String zurückgibt..
Wenn Du damit nach '€' suchen willst und dann mal eben schnell die Zahlen davor abgeifen möchtest:
wie siehts dann hier aus:
Bestellnummer 12345 34,67€
Bestellnummer 12345,34,67€
Bestellnummer 12345 34,67 €
Bestellnummer 12345 -34,67€
das Problem ist etwas komplexer, darum der Code.
Delete - Sa 02.02.19 18:42
- Nachträglich durch die Entwickler-Ecke gelöscht -
jaenicke - So 03.02.19 07:11
Was ist denn das für eine Bank, die keinen digitalen Export anbietet? :shock:
Bei der Postbank bekommt man z.B. auch XML, bei der 1822direkt CSV, ...
Jedenfalls würde ich an der Stelle am besten mit Regular Expressions arbeiten, denn da ist doch etwas Flexibilität erforderlich. Für die genannten Fälle würde es damit gehen:
Quelltext
1:
| ([a-zA-ZäöüÄÖÜ0-9\.\,\ ]{1,})[\ ]{1,}([0-9\.]*)[\,\ ]{1,}([\-0-9\ ]*\,[0-9\€\ ]*) |
Man könnte das auch kürzer schreiben, aber so ist es denke ich verständlichsten:
Immer die Elemente in runden Klammern werden als Ergebnisgruppe erfasst.
- In der ersten Gruppe also Buchstaben, Zahlen, Punkt, Komma und Leerzeichen, davon mindestens eins, maximal unbegrenzt
- Dazwischen mindestens ein Leerzeichen
- In der zweiten Gruppe nur Zahlen oder Punkte mit unbegrenzter Anzahl
- Dazwischen mindestens ein Komma oder Leerzeichen
- In der dritten Gruppe Minuszeichen, Zahlen und Leerzeichen, dann ein Komma, dann noch einmal Zahlen, Leerzeichen oder Eurozeichen mit unbegrenzter Anzahl
Hier im
Online-Tester [
https://regex101.com/]:
Und dazu der Delphi-Quelltext als kurzes Beispiel (kompilierbar in der aktuellen kostenlosen Community Edition):
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| uses System.RegularExpressions, Vcl.Dialogs;
procedure Test(const AText: string); var RegEx: TRegEx; Match: TMatch; begin RegEx := TRegEx.Create('([a-zA-ZäöüÄÖÜ0-9\.\,\ ]{1,})[\ ]{1,}([0-9\.]*)[\,\ ]{1,}([\-0-9\ ]*\,[0-9\€\ ]*)'); Match := RegEx.Match(AText); if Match.Success then ShowMessage(AText + ' ergibt:' + sLineBreak + Match.Groups[1].Value + sLineBreak + Match.Groups[2].Value + sLineBreak + Match.Groups[3].Value) else ShowMessage(AText + ': keine Übereinstimmung!'); end;
Test('Hans Dampf, Geld für Bestellnummer 4567.44, 314,14€'); Test('Bestellnummer 12345 34,67€'); Test('Bestellnummer 12345,34,67€'); Test('Bestellnummer 12345 34,67 €'); Test('Bestellnummer 12345 -34,67€'); |
Das gibt aus:
(Und so weiter)
Sinspin - So 03.02.19 13:39
jaenicke hat folgendes geschrieben : |
Was ist denn das für eine Bank, die keinen digitalen Export anbietet? :shock: |
Interessanter finde ich die Frage warum Banken heutzutage alle ihr eigenes Süppchen kochen und jede Bank was anderes liefert.
Zumindest MT940 würde ich von jeder Bank erwarten und CSV was zumindest die gleichen Felder enthält.
Ich habe auch schon xls Exporte gesehen oder Tabellen mit nur drei Feldern.
Faszinierend ist für mich die Frage, wenn da keine fortlaufende Buchungsnummer an den Datensätzen ist, haben die intern eine? Und was machen die bei Rückfragen oder Fehlbuchungen?
Ich denke jeder Entwickler hat irgendwo eine Sammlung von Routinen um Text zu zerpflücken.
Aber ich würde mir garantiert keine Routine mit zig Parametern schreiben bei der gerade das Währungszeichen (übrigens können das bis zu drei Zeichen sein) nicht übergeben werden kann.
Und Sowas wie "Runden" geht bei Währung schonmal garnicht. Per definition hat Währung 4 Nachkommastellen und es gibt dafür einen Datentyp : Currency.
jaenicke - So 03.02.19 20:36
Ach ja, eins noch:
Es ist hilfreich, wenn eine Bestellnummer ein bestimmtes Format hat. Zum Beispiel mit einem Präfix wie AB oder auch 121 am Anfang und eine bestimmte Länge. Dann lässt sich die Bestellnummer viel einfacher per RegEx herausfiltern und wird auch gefunden egal was der Kunde sonst noch schreibt.
jasocul - Mo 04.02.19 09:28
Banken sind so ein besonderes Thema.
CSV ist im privaten Bereich durchaus ein Angebot in den Bank-Portalen. Dass dabei völlig wilde Zusammensetzungen existieren, bleibt nicht aus, da es keinem Banken-Standard unterrworfen ist.
Standard-Formate im Bankenbereich:
MT940 gibt es auch und kann derzeit durchaus noch als Standard betrachtet werden. Allerdings wird es von einigen Banken für den privaten Endkunden nur gegen bares zur Verfügung gestellt, da das Format eigentlich für die Kommunikation zwischen Geldinstituten vorgesehen war/ist.
CAMT (53 für Kontoauszüge) ist quasi ein "Nachfolger" von MT940. Es gibt durchaus Banken, die die MT-Formate bereits verweigern (wollen), sofern sie CAMT schon liefern können. Dass liegt daran, dass CAMT sein einigen Jahren ein verpflichtendes Format für Banken ist.
UNIFI soll MT-Format ablösen. Dabei handelt es sich um XML-Formate. Meines Wissens ist die Definition aber noch nicht abgeschlossen. Das mag vielleicht daran liegen, dass die Definition durch ein ISO-Konsortium definiert wird, bei dem die Banken angeblich nicht vernünftig eingebunden wurden/werden.
Fazit:
Es gibt definierte Formate und man muss keine Texte parsen, wenn man wirklich nur den Buchungssatz benötigt.
Problematisch sind z.B. eher Buchungen, die als Sammelbuchung gelten. Bei denen werden im Buchungstext dann die Einzelbuchungen aufgeführt. Diese unterligen keinem definierten Format, wodurch die automatisierten Zuordnungen erheblich erschwert werden.
Woher ich das alles weiß?
Ich bin Entwickler bei einer Bank und habe auch mit diesen Schnittstellen zu kämpfen. Besonders dann, wenn eine Bank mal wieder meint, klar definierte Feldinhalte zu ignorieren.
Auch wir müssen die Texte analysieren, um automatische Zuordnungen zu machen. Es bleibt dabei immer ein gewisser Prozentsatz an manuellen Prüfungen übrig.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2024 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!