Autor Beitrag
zongo-joe
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 133

win xp prof
D3, D4, D7
BeitragVerfasst: Sa 02.02.19 16:52 
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:

ausblenden volle Höhe 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;
  // sucht einen Eurobetrag in einem string; dieser wird anhand Const "Waehrung" (€,$..) identifiziert
  // result= betrag als string mit komma, wenn nichts gefunden ist dieser leer
  // x=string in dem gesucht wird
  // erster = true = erster betrag wird gesucht (=false = letzter)
  // meldung = true = meldung wenn nix gefunden wird
  // trenner = Euro/Cent trenner, meist , kann aber auch . sein
  // negativ=true= es werden auch negtaive bertäge ausgegeben, sonst Abosolutwerte
  // zweinachkomma = true = bei mehr als zwei nachkommastellen den rest abschneiden
  // runden = true = wenn mehr als zwei nachkommastellen, dann runden; false = nur abschneiden

  const waehrung='€';  // sonst zB $

  var i,pos1,pos2 : integer;
      rbetrag:string;
      gefund:boolean;
      hlp:char;

      function betrag(x:string; posi:integer; trenner:char):string// sucht den Betrag zu einem € zeichen
        // wenn nix gefunden = result = '';
        const meldung=true;
        var i,pos2,pos3:integer;
            res:string;
        begin
          res:='';

          // nach links suchen, alle möglichen zeichen einschliessen
          dec(posi);
          while (posi > 0and (x[posi] in [' ','0'..'9','-',trenner]) do begin
            res:= x[posi] + res;
            dec(posi);
          end;


          // jetzt die zeichen abschneiden, die nicht dazugehören

          // am ende alle nicht-zahlen entfernen
          while (length(res) > 0and (res[length(res)] in [' ',trenner,'-']) do begin
            res := copy(res,1,length(res)-1);
          end;


          // jetzt am Anfang alle trenner und leerzeichen abschneiden
          while (length(res) > 0and (res[1in [' ',trenner]) do begin
            res := copy(res,2,length(res)-1);
          end;



          // jetzt alles links von dem rechtesten vorhandenen Leerzeichen abschneiden
          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);


          // jetzt prüfen ob mehr als ein trenner oder - vorhanden, wenn ja dann alles links von dem linkesten davon abschneiden
          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;


          // wenn jetzt das - nicht am Anfang steht, alles links davon abschneiden
          if pos('-',res) > 1 then res:=copy(res,pos('-',res),length(res)-pos('-',res)+1);


          result:=res;

        end// von function Betrag


  begin
    rbetrag:='';
    gefund:=false;


    if length(x) > 0 then begin

      // ersten Betrag suchen
      if erster then begin
        i:=1;
        while (not gefund) and (i<=length(x)) do begin
          if x[i]=waehrung then begin   // waehrung = € oder $...
            rbetrag:=betrag(x,i,trenner);
            if rbetrag<>'' then gefund:=true;
          end;
          inc(i);
        end;

      end else begin // Sonst letzten Betrag suchen
        i:=length(x);
        while (not gefund) and (i>0do begin
          if x[i]=waehrung then begin   // waehrung = € oder $...
            rbetrag:=betrag(x,i,trenner);
            if rbetrag<>'' then gefund:=true;
          end;
          dec(i);
        end;

      end// von letzten Betrag suchen

      // wenn nur Absolutbeträge, dann ggfs - am anfang löshen
      if gefund and (not negativ) and (rbetrag[1]='-'then rbetrag:=copy(rbetrag,2,length(rbetrag)-1);

      // wenn auf zwei Nachkommastellen begrenzt, dann ggfs abschneiden und ggfs. runden
      if zweinachkomma and (pos(trenner,rbetrag)>0and (pos(trenner,rbetrag) < length(rbetrag)-2then begin
        if runden then begin // ab 3. Nachkommastelle abschneiden und betrag runden
          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 // nicht runden, nur abschneiden
          rbetrag := copy(rbetrag,1,pos(trenner,rbetrag)+2);

        end// von nicht runden
      end;

    end// von if length(x) > 0

    // wenn Meldung, dann ggfs Meldung ausgeben, wenn kein Betrag gefunden wurde
    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));
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Sa 02.02.19 17:02 
- Nachträglich durch die Entwickler-Ecke gelöscht -
zongo-joe Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 133

win xp prof
D3, D4, D7
BeitragVerfasst: Sa 02.02.19 17:10 
user profile iconFrühlingsrolle hat folgendes geschrieben Zum zitierten Posting springen:
Guten Abend

bevor du noch weitere Themen veröffentlichst:
Schau' dir mal die Funktion Pos() an.



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.
Frühlingsrolle
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Sa 02.02.19 17:42 
- Nachträglich durch die Entwickler-Ecke gelöscht -
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18732
Erhaltene Danke: 1631

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 03.02.19 06: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:
ausblenden 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:
RegExExample

Und dazu der Delphi-Quelltext als kurzes Beispiel (kompilierbar in der aktuellen kostenlosen Community Edition):
ausblenden 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;

// Aufruf:
  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:
RegExResult
(Und so weiter)
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: Narses
Sinspin
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1192
Erhaltene Danke: 100

Win7
DXE2 Prof, Lazarus
BeitragVerfasst: So 03.02.19 12:39 
user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
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.

_________________
Solange keine Zeile Code geschrieben ist, läuft ein Programm immer fehlerfrei.
Ich teste nicht, weil ich Angst habe Fehler zu finden.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 18732
Erhaltene Danke: 1631

W10 x64 (Chrome, IE11)
Delphi 10.2 Ent, Oxygene, C# (VS 2015), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: So 03.02.19 19: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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6330
Erhaltene Danke: 134

Windows 7 + Windows 10
Tokyo Prof + CE
BeitragVerfasst: Mo 04.02.19 08: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.