unit Main;
{
In 10.24: wieder Formatwechsel. Neuer Versuch, Header korrekt zu lesen
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='Anstehende Umsätze') then begin // ANGEKUENDIGT folgt
result:=ANGEKUENDIGT;
exit;
end;
if (s='Heute') or (s='Gestern') or (s='Diese Woche') or (s='Letzte Woche') or
(s='Dieser Monat') or (s='Letzter Monat') then begin // UMSAETZE folgt
result:=UMSAETZE;
exit;
end;
if (s='Die wichtigsten Funktionen') 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) );
{
ShowMessage(DefaultFormatSettings.DecimalSeparator);
DefaultFormatSettings.DecimalSeparator:='.';
ShowMessage(DefaultFormatSettings.DecimalSeparator);
}
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
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.