Dateien parsen - mit welchen Werkzeugen ?

Für Fragen von Einsteigern und Programmieranfängern...
DL3AD
Beiträge: 478
Registriert: Fr 13. Sep 2013, 12:07
OS, Lazarus, FPC: Debian Bullseye (L 2.2.0)
CPU-Target: 64Bit
Wohnort: Rügen

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von DL3AD »

Hallo mse,

dein Tipp die Stringlist in einen string zu pumpen war der entscheidende Faktor.
Nun dauert es statt einer Minute nur noch eine knappe sekunde - das ist ja krass :shock:

Hallo braunbär,

Danke für das Beispiel - nun ist mir auch klar wie man im Programm mit regex umgeht.
Ich werde das heute noch ausprobieren. Der Code wird ja sehr übersichtlich :)
Den regex Ausdruck zu bauen ist als ungeübter jedoch eine herausforderung.
Mit den globalen Var ist mir bekannt - bei einer Function bekomme ich jedoch nur einen Wer zurück - ok man kann auch eine Array zurückgeben.
OOP klassen bauen mag ich nicht so gern - Proprerty 's kann man die auch ohne klassen verwenden ?

Gruß
Frank

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

DL3AD hat geschrieben:im Dateianhang ein Auszug aus dem Projekt.
Es ist nur das Parsen und zuordnen zu den DB Feldern ohne DB schreiben.
Es dauer ewig bis er fertig hat.
Die Adif mit ca 2500 Datensätzen ist auch dabei.

Hier findest du eine Version deines Parsers, die mit PChar arbeitet, und die wird in knapp 0.2 Sekunden fertig.

Code: Alles auswählen

procedure GetAdifValue(var p: PChar);
var
  n   : integer;
  Vlng: string;
begin
  n     := 0;
  ATag  := '';
  AValue:= '';
  Vlng  := '';
 
  while p^ <> #0 do   // Wenn Zeichen nicht das abschließende Null-Byte...
  begin
    if p^ = '<' then
    begin // Hier stehen wir auf den '<'
      inc(p);//nächstes Zeichen und weiter zum Tagende, oder ein ':' kommt
      while (p^ <> #0) and  (p^ <> ':') and (p^ <> '>') do
      begin
        ATag:= ATag + p^;//gefundenes Zeichen Tag zuordnen
        ATag:= Upcase(ATag);
        inc(p);// Zum nächsten Zeichen.
      end;
      //Jetzt sind wir auf dem ':' oder haben das Tagende erreicht.
 
      if p^ = #0 then
        exit;//Fall 1) StringEnde --> raus
 
      if p^ = ':' then//Fall 2) Wir stehen auf dem ':'
      begin
        inc(p);
        while (p^ <> #0) and (p^ <> '>') do
        begin
          Vlng:= Vlng + p^;
          inc(p);
        end;//Jetzt sind wir auf dem '>' und haben das Tagende erreicht
        n:= StrToInt(Vlng);//Länge Adif Value in Interger wandeln
        inc(p);
      end else
        inc(p);
 
      while (p^ <> #0) and (n > 0) do
      begin
        AValue:= AValue + p^;//Adif Value Zeichen aufaddieren
        dec(n);
        inc(p);//Von '>' weiter aud das erste Zeichen vom Adif Value
      end;
      if n = 0 then
        exit;
    end;
    inc(p);
  end;
end;
 
//Daten einlesen
procedure TForm1.Button1Click(Sender: TObject);
var
  t: TDateTime;
  s: String;
  p: PChar;
begin
  t := now;
  s := AFile.Text;
  p := @s[1];
  while p^ <> #0 do //Ai < Length(s) do
  begin
    ClearVar;
    while (ATag <> 'EOR') and (p^ <> #0) do
    begin
      ADIFPars.GetAdifValue(p);
      Case ADIFPars.ATag of
          'CALL'         : ACALL         := ADIFPars.AValue;
          'QTH'          : AQTH          := ADIFPars.AValue;
          'NAME'         : ANAME         := ADIFPars.AValue;
          'TIME_ON'      : ATIME_ON      := ADIFPars.AValue;
          'QSO_DATE'     : AQSO_DATE     := ADIFPars.AValue;
          'RST_SENT'     : ARST_SENT     := ADIFPars.AValue;
          'RST_RCVD'     : ARST_RCVD     := ADIFPars.AValue;
          'BAND'         : ABAND         := ADIFPars.AValue;
          'FREQ'         : AFREQ         := ADIFPars.AValue;
          'MODE'         : AMODE         := ADIFPars.AValue;
          'CONT'         : ACONT         := ADIFPars.AValue;
          'ITUZ'         : AITUZ         := ADIFPars.AValue;
          'CQZ'          : ACQZ          := ADIFPars.AValue;
          'DXCC'         : ADXCC         := ADIFPars.AValue;
          'COUNTRY'      : ACOUNTRY      := ADIFPars.AValue;
          'GRIDSQUARE'   : AGRIDSQUARE   := ADIFPars.AValue;
          'IOTA'         : AIOTA         := ADIFPars.AValue;
          'STATE'        : ASTATE        := ADIFPars.AValue;
          'QSL_RCVD'     : AQSL_RCVD     := ADIFPars.AValue;
          'LOTW_QSL_RCVD': ALOTW_QSL_RCVD:= ADIFPars.AValue;
          'EQSL_QSL_RCVD': AEQSL_QSL_RCVD:= ADIFPars.AValue;
          'COMMENT'      : ACOMMENT      := ADIFPars.AValue;
          'PFX'          : APFX          := ADIFPars.AValue;
          'EOR'          : ChData;
      end;
      inc(p);
    end;
    ATag:= '';
  end;
  ShowMessage('Fertig! (' + FormatDateTime('s.zzz', now-t) + ' s)');
end;


P.S.
Noch zu einem möglichen Problem: In der ADIF-Doc, die ich weiter oben zitiert habe, steht, dass hinter der Längenangabe im Tag noch eine Datentyp-Angabe kommen kann, z.B. <qso_date:8:d>. Das bedeutet, dass die Längenangabe in diesem Code nicht richtig ausgelesen wird. Du findest sicher, wie das abzuändern ist.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2636
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von m.fuchs »

DL3AD hat geschrieben:Mit den globalen Var ist mir bekannt - bei einer Function bekomme ich jedoch nur einen Wer zurück - ok man kann auch eine Array zurückgeben.
OOP klassen bauen mag ich nicht so gern - Proprerty 's kann man die auch ohne klassen verwenden ?

Du kannst auch records verwenden um mehrere Werte in einer Struktur zurückzugeben.

Properties ohne Klassen gibt es auch, das ist aber etwas anderes.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von mse »

wp_xyz hat geschrieben:P.S.
Noch zu einem möglichen Problem: In der ADIF-Doc, die ich weiter oben zitiert habe, steht, dass hinter der Längenangabe im Tag noch eine Datentyp-Angabe kommen kann, z.B. <qso_date:8:d>. Das bedeutet, dass die Längenangabe in diesem Code nicht richtig ausgelesen wird. Du findest sicher, wie das abzuändern ist.

Und die Felder können binäre Daten enthalten.

Code: Alles auswählen

 
      while (p^ <> #0) and (n > 0) do //#0 kann in binären daten vorkommen.
      begin
        AValue:= AValue + p^;//Adif Value Zeichen aufaddieren
 

ist nicht zuverlässig. Es wäre auch schneller AValue auf einmal zu laden und nicht Zeichen für Zeichen.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von braunbär »

DL3AD hat geschrieben:Den regex Ausdruck zu bauen ist als ungeübter jedoch eine herausforderung.

Das stimmt, am Anfang bin ich bei jedem Regex eine ganze Weile gesessen. Aber es zahlt sich wirklich aus, da in etwas Übung zu investieren, die Programme werden um ein Vielfaches einfacher, und, wenn man die einzelnen Teile des Regex entsprechend kommentiert, auch viel leichter verständlich als die unzähligen Schleifen eines spezialisierten Parsers.

DL3AD hat geschrieben:bei einer Function bekomme ich jedoch nur einen Wer zurück - ok man kann auch eine Array zurückgeben.

Vor allem brauchst du hier keine Function - mach eine procedure mit einem var-Parameter und zwei out-parametern. Struktur brauchst du da gar keine.

Code: Alles auswählen

 
procedure ScanAdif(var s: String; out aTag, aValue: string);
 
Zuletzt geändert von braunbär am Mo 10. Jul 2017, 13:58, insgesamt 1-mal geändert.

DL3AD
Beiträge: 478
Registriert: Fr 13. Sep 2013, 12:07
OS, Lazarus, FPC: Debian Bullseye (L 2.2.0)
CPU-Target: 64Bit
Wohnort: Rügen

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von DL3AD »

Hallo wp_xyz,

Danke für die Analyse meines Problemes.
Hatte vorhin die Stringlist ein eine string geladen - dann ging es auch rasent schnell - habe mal die Zeitmessung eingebaut und auf meinem alten Bürorechner CoreI3 sind es 0,218s 8)

Hallo braunbär,

Code: Alles auswählen

procedure ScanAdif(var s: String; out aTag, aValue: string);

Das mit out kannte ich noch garnicht - seit wann gibt es dass ?
Das werde ich heute auch mal testen und den Code entsprechen umbauen - dann sich funktionen ja qusai obsolett.

Gruß
Frank

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

Hab jetzt auch ALLE StringList.Text durch einen String ersetzt, und komme auf dieselbe Zeit wie bei der PChar-Variante (vorher hatte ich einige übersehen) -> auf PChar umzustellen lohnt sich nicht, v.a. wenn dir PChar suspekt ist.

braunbär
Beiträge: 369
Registriert: Do 8. Jun 2017, 18:21
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: 64Bit
Wohnort: Wien

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von braunbär »

DL3AD hat geschrieben:dann sich funktionen ja quasi obsolet.

Obsolet sind sie nicht, weil du nur Funktionen direkt in einem Ausdruck verwenden kannst. Eine function, die nur einen Wert berechnet, ist in der Verwendung schon um einiges praktischer als eine procedure.

DL3AD hat geschrieben:Das mit out kannte ich noch garnicht - seit wann gibt es dass ?

Schon recht lange. Aber in Turbo Pascal gab es das noch nicht, wenn ich mich richtig erinnere. :wink:
Im Grunde genommen ist out nichts anderes als var, bloss dass der Compiler (und vor allem auch der, der den Code liest) weiss, dass die Werte, die beim Start der Prozedur in der Variable stehen, nicht verwendet werden. Der Compiler kann eine Warnung ausgeben, wenn du in der Prozedur den Wert der Variablen verwendest, ohne der Variablen in der Prozedur vorher einen Wert zuzuweisen.
Und wenn du eine Prozedur mit so einer Signatur benützt, weisst du, dass du diese Variablen vor dem Prozeduraufruf nicht initialisieren brauchst.

DL3AD
Beiträge: 478
Registriert: Fr 13. Sep 2013, 12:07
OS, Lazarus, FPC: Debian Bullseye (L 2.2.0)
CPU-Target: 64Bit
Wohnort: Rügen

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von DL3AD »

... und ich komme noch aus der Generation Turbo Pascal. 8)
Ich habe es gerade probiert es tatsächlich sehr praktisch mit out

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

mse hat geschrieben:Und die Felder können binäre Daten enthalten.

Stimmt! Dann wäre es von vornhinein sinnvoller, die Datei binär in ein Array of byte einzulesen und auf alle Stringfunktionen zu verzichten. Nicht dass FPC/Lazarus da plötzlich unbemerkt irgendwelche Codeseiten-Anpassungen und UTF8-Konvertierungen vornehmen...

Mathias
Beiträge: 6162
Registriert: Do 2. Jan 2014, 17:21
OS, Lazarus, FPC: Linux (die neusten Trunk)
CPU-Target: 64Bit
Wohnort: Schweiz

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von Mathias »

TStringlist.text ist eine sehr teure Operation da dort der String immer aus allen Zeilen zusammengesetzt wird.

Das sieht man seht gut, wen man die Source von TStringList.Text anguckt.

Code: Alles auswählen

Function TStrings.GetTextStr: string;
Var P : Pchar;
    I,L,NLS : Longint;
    S,NL : String;
begin
  CheckSpecialChars;
  // Determine needed place
  if FLineBreak<>sLineBreak then
    NL:=FLineBreak
  else
    Case FLBS of
      tlbsLF   : NL:=#10;
      tlbsCRLF : NL:=#13#10;
      tlbsCR   : NL:=#13;
    end;
  L:=0;
  NLS:=Length(NL);
  For I:=0 to count-1 do
    L:=L+Length(Strings[I])+NLS;
  if SkipLastLineBreak then
    Dec(L,NLS);
  Setlength(Result,L);
  P:=Pointer(Result);
  For i:=0 To count-1 do
    begin
    S:=Strings[I];
    L:=Length(S);
    if L<>0 then
      System.Move(Pointer(S)^,P^,L);
    P:=P+L;
    if (I<Count-1) or Not SkipLastLineBreak then
      For L:=1 to NLS do
        begin
        P^:=NL[L];
        inc(P);
        end;
    end;
end;
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

DL3AD
Beiträge: 478
Registriert: Fr 13. Sep 2013, 12:07
OS, Lazarus, FPC: Debian Bullseye (L 2.2.0)
CPU-Target: 64Bit
Wohnort: Rügen

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von DL3AD »

Hallo,

nun läuft es recht fluffig knappe 0,4s um die Daten zu parsen und in die ZQuery zu schreiben dass ist absolut OK
ABER nach ApplyUpdates also dass die Query die Daten in die Datenbank schaufelt dauert immerhin noch 8s für 2500 Datensätze mit 28 Feldern je Datensatz.
Nun kann ich es nicht einschätzen ob dass OK ist oder das zu lange dauert für eine sqlite3 DB.

Gruß
Frank

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von af0815 »

DL3AD hat geschrieben:ABER nach ApplyUpdates also dass die Query die Daten in die Datenbank schaufelt dauert immerhin noch 8s für 2500 Datensätze mit 28 Feldern je Datensatz.
Nun kann ich es nicht einschätzen ob dass OK ist oder das zu lange dauert für eine sqlite3 DB.


Hängt davon ab wie die DB/Verbindung konfiguriert ist und wie sicher deine Daten sein sollen. Ich habe dazu schon mal Versuche gemacht, damals mit JSON in die DB schreiben, da geht schon was wenn man es darauf anlegt. Ist aber eine Abwägungssache zwischen Geschwindigkeit und Sicherheit.

Siehe JSON Array in Datenbanktabelle hier im Forum.

Andreas
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

mse
Beiträge: 2013
Registriert: Do 16. Okt 2008, 10:22
OS, Lazarus, FPC: Linux,Windows,FreeBSD,(MSEide+MSEgui 4.6,git master FPC 3.0.4,fixes_3_0)
CPU-Target: x86,x64,ARM

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von mse »

DL3AD hat geschrieben:nun läuft es recht fluffig knappe 0,4s um die Daten zu parsen und in die ZQuery zu schreiben dass ist absolut OK
ABER nach ApplyUpdates also dass die Query die Daten in die Datenbank schaufelt dauert immerhin noch 8s für 2500 Datensätze mit 28 Feldern je Datensatz.

Siehe meine Bemerkung zu den Transaktionen. CachedUpdates bringen auch nur unnötigen overhead.
Zum Vergleich, mit tsqlstatement von MSEgui komme ich für 2500 records auf 0.2 Sekunden.
adlog.png

Das Projekt ist hier:
https://gitlab.com/mseide-msegui/mseuni ... ertrecords
Ist es OK deine DB mit leerer Log Tabelle dort zu platzieren?

Antworten