unit Main;


{

In 03.24: neuerdings ist der "Vorspann" von Chrome länger und anders als z.B. beim Firefox.

In 09.23: erneut Anpassung nötig nach Strukturänderung, PLUS: Vereinfachung meines Algorithmus !!!!!

NEUE Version ab 10.5.23 wg. Website-Umstellung (andere Reihenfolge der Daten)

Leider ist die Website keine definierte abgestimmte Schnittstelle. Deshalb kann jederzeit eine
Strukturänderung passieren. Dann muss der arme Programmierer anpassen. So am 10.5.23.




toDos:

1. z.Zt. keine
2.
3.

}


{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Buttons, ComCtrls,
  ExtCtrls, StdCtrls;

type

  modes = (mGuthaben, mAngekuendigt, mUmsaetze, mEnde);
  headerTyp = (NIX, ANGEKUENDIGT, UMSAETZE, ENDE);

  { TMainForm }

  TMainForm = class(TForm)
    BitBtnClose: TBitBtn;
    BitBtnSave: TBitBtn;
    BitBtnGetClip: TBitBtn;
    Image1: TImage;
    Label1: TLabel;
    Label2: TLabel;
    LabelLink: TLabel;
    ListView: TListView;
    Panel1: TPanel;
    StatusBar: TStatusBar;
    procedure BitBtnCloseClick(Sender: TObject);
    procedure BitBtnGetClipClick(Sender: TObject);
    procedure BitBtnSaveClick(Sender: TObject);
    procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
    procedure FormShow(Sender: TObject);
    procedure LabelLinkClick(Sender: TObject);
  private
    TL:TStringList;
    function getDownloadFolder:string;
    function isDatum(s:string):string;
    function isEuro(s:string):string;
    function isHeader(s:string):headerTyp;
    procedure addRow(s1,s2,s3,s4,s5,s6:string);
  public

  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

{ TMainForm }

uses Windows, Clipbrd, Shellapi, Shlobj;

const WINDOWNAME:string='PostBank-Exporter';


function TMainForm.getDownloadFolder:string;
var s:integer;
    pPath: pWideChar;
    path: array[0..Max_Path] of WideChar;
    g:TGuid ='{374DE290-123F-4565-9164-39C4925E467B}';
begin
  pPath:=@path;
  s:=shlobj.SHGetKnownFolderPath(g,0,0,pPath);
  if s=0 then
     result:=pPath
  else
     result:='';
end;


function TMainForm.isDatum(s:string):string;
var d:TDate;                 // "13.12.2022"
begin
  try
    d:=StrToDate(s);
  except
    s:=''
  end;
  result:=s;
end;

function TMainForm.isEuro(s:string):string;
var e:extended;              // "-138,83 EUR"
    st:string;               // "40.029,86 EUR"
begin
  st:=RightStr(s,3);
  if st<>'EUR' then begin
     result:='';
     exit;
  end;

  s:=LeftStr(s,length(s)-4); // = copy(s,1,length(s)-4);
  s:=StringReplace(s, '.', '',[rfReplaceAll]); // Tausender-Trenner entfernen
  try
    e:=StrToFloat(s);
  except
    s:='';
  end;
  result:=s;
end;

function TMainForm.isHeader(s:string):headerTyp;
begin
  result:=NIX;

  if (s='clock') then begin              // ANGEKUENDIGT folgt
     result:=ANGEKUENDIGT;
     exit;
  end;

  if (s='transfer') then begin           // UMSAETZE folgt
     result:=UMSAETZE;
     exit;
  end;

  if (length(s)=1) or
     (length(s)=2) then begin            // ? und ?? auch: UMSAETZE folgt
     result:=UMSAETZE;                   // nach Umstellung 09.23 nicht mehr zuverlässig !!!!!
     exit;
  end;

  if (s='favorite-outline') then begin   // ENDE: nichts Weiteres folgt
     result:=ENDE;
     exit;
  end;
end;

procedure TMainForm.addRow(s1,s2,s3,s4,s5,s6:string);
var idx:integer;
begin
  ListView.Items.Add.Caption:='';        // 0. Spalte nicht sichtbar
  idx:=ListView.Items.Count-1;
  ListView.Items.Item[idx].SubItems.Add(s1);
  ListView.Items.Item[idx].SubItems.Add(s2);
  ListView.Items.Item[idx].SubItems.Add(s3);
  ListView.Items.Item[idx].SubItems.Add(s4);
  ListView.Items.Item[idx].SubItems.Add(s5);
  ListView.Items.Item[idx].SubItems.Add(s6);
end;


procedure TMainForm.FormShow(Sender: TObject);
var wowner:THandle;
begin
  wowner:=GetWindow(handle,GW_OWNER);
  SetWindowText(wowner,pchar(WINDOWNAME));
  MainForm.Caption:=WINDOWNAME;
  TL:=TStringList.Create;
end;

procedure TMainForm.BitBtnGetClipClick(Sender: TObject);
var lauf:integer;
    zeile,s:string;
    mode:modes;
    ht:headerTyp;
    guthaben:Extended;
    betragAngekuendigt:Extended;
    artAngekuendigt:string;
    datumAngekuendigt:string;
    buchungsZeile:integer;
    buchungsEmpfaenger, buchungsHinweis, buchungsArt, buchungsDatum:string;
    buchungsBetrag,saldo:Extended;
begin
  listview.Clear;
  TL.Clear;
  if clipboard.HasFormat(CF_TEXT) then begin
    TL.AddText(clipboard.asText);
 // ShowMessage( inttostr(TL.Count) );

    mode:=mGuthaben;              // damit fängt es an
    for lauf:=1 to tl.Count do begin
      zeile:=tl.Strings[lauf-1];

      if zeile='' then
         continue;                // Leerzeile: ignorieren...

      ht:=isHeader(zeile);        // Header gefunden?
      case ht of
         ANGEKUENDIGT: mode:=mAngekuendigt;
         UMSAETZE:     mode:=mUmsaetze;   // !!!!! dafür gibt es leider
                                          // keinen zuverlässigen Header mehr !!!!!
         ENDE:         mode:=mEnde;
      end;

      case mode of
         mGuthaben:     begin
                           s:=isEuro(zeile);
                           if s='' then
                              continue;             // Zeile kein Guthaben

                           guthaben:=StrToFloat(s); // erstes Muster "xxx,yy EUR" gefunden = Guthaben
                           saldo:=guthaben;
                           mode:=mAngekuendigt;     // erzwingen, sollte der Header fehlen
                           continue;
                        end;

         mAngekuendigt: begin
                           s:=isDatum(zeile);
                           if s<>'' then begin
                              datumAngekuendigt:=s; // das zugehörige Datum
                              continue;
                           end;

                           s:=isEuro(zeile);
                           if s<>'' then begin      // angekündigte Position gefunden
                              betragAngekuendigt:=StrToFloat(s);
                              artAngekuendigt:=tl.Strings[lauf-3];  // "Art" steht 2 Zeilen oberhalb

                              if tl.Strings[lauf-5] <> 'clock' then begin  // !!! ist KEINE Ankündigung !!!
                                                                           // sondern bereits Umsatz !!!
                                 buchungsDatum:=datumAngekuendigt;
                                 buchungsBetrag:=betragAngekuendigt;
                                 buchungsArt:=artAngekuendigt;
                                 buchungsHinweis:=tl.Strings[lauf-4];      // 3 Zeilen oberhalb
                                 buchungsEmpfaenger:=tl.Strings[lauf-5];   // 4 Zeilen oberhalb
                                 addRow( buchungsDatum, buchungsArt, buchungsEmpfaenger, buchungsHinweis, format('%.2F',[buchungsBetrag]), format('%.2F',[saldo]) );
                                 saldo:=saldo-buchungsBetrag;

                                 mode:=mUmsaetze;   // Umsätze folgen:
                                 continue;          // raus HIER!
                              end;

                              addRow( datumAngekuendigt, artAngekuendigt, '', 'angekündigt', format('%.2F',[betragAngekuendigt]), format('%.2F',[saldo]) );
                              saldo:=saldo-betragAngekuendigt;
                           end;
                        end;

         mUmsaetze:     begin
                           s:=isDatum(zeile);
                           if s<>'' then begin
                              buchungsDatum:=s;     // Datum gefunden
                              continue;
                           end;

                           s:=isEuro(zeile);
                           if s<>'' then begin      // Betrag gefunden
                              buchungsBetrag:=strtofloat(s);
                              buchungsArt:=tl.Strings[lauf-3];
                              buchungsHinweis:=tl.Strings[lauf-4];
                              buchungsEmpfaenger:=tl.Strings[lauf-5];

                              addRow( buchungsDatum, buchungsArt, buchungsEmpfaenger, buchungsHinweis, format('%.2F',[buchungsBetrag]), format('%.2F',[saldo]) );
                              saldo:=saldo-buchungsBetrag;
                              continue;
                           end;
                        end;

         mEnde:         begin                       // "Header" ist Postambel
                          break;                    // raus aus der for-Schleife
                        end;
      end;  // case
    end;    // for
  end;      // if

  StatusBar.Panels.Items[0].Text:='Zeilen gelesen: '+inttostr(TL.Count);
  StatusBar.Panels.Items[1].Text:='Umsätze extrahiert: '+inttostr(ListView.Items.Count);
  StatusBar.Panels.Items[2].Text:='';

  if ListView.Items.Count >0 then
     ActiveControl:=BitBtnSave
  else
    ActiveControl:=BitBtnClose;
end;

procedure TMainForm.BitBtnSaveClick(Sender: TObject);
var expPath:string;
    strL:TStrings;
    lauf:integer;
    tf:textfile;
begin
  ActiveControl:=BitBtnClose;

  if ListView.Items.Count=0 then begin
     StatusBar.Panels.Items[2].Text:='nichts gespeichert!';
     exit;
  end;

  expPath:=getDownloadFolder;

  if paramcount>0 then         // bei Parameter /t wird das Clipboard gespeichert
    if paramstr(1)='/t' then
      TL.SaveToFile(expPath+'\pb-export.txt');

  assignfile(tf,expPath+'\pb-export.csv');
  rewrite(tf);
  for lauf:=1 to ListView.Items.Count do begin
     strL:=ListView.Items.Item[lauf-1].SubItems;
     strL.Delimiter:=';';      // Trenner
     strL.StrictDelimiter:=TRUE;
     strL.QuoteChar := #0;
     writeln(tf,strL.DelimitedText);
  end;
  closefile(tf);

  StatusBar.Panels.Items[2].Text:='Zeilen gespeichert: '+inttostr(ListView.Items.Count);
end;

procedure TMainForm.LabelLinkClick(Sender: TObject);
begin
   Shellexecute(Application.Handle,'open',PChar(TLabel(Sender).Hint),nil,nil,0);
end;

procedure TMainForm.BitBtnCloseClick(Sender: TObject);
begin
   Close;
end;

procedure TMainForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
begin
  TL.Free;
  //CloseAction:=caFree;
end;

end.