Dateien parsen - mit welchen Werkzeugen ?

Für Fragen von Einsteigern und Programmieranfängern...
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: Eine Ausgabe auf der GUI wird nicht gemacht sondern gleich in die DB auch ohne in DB schreiben dauert es.
Falls du in Sqlite3 schreibst sollten die Records in einer gemeinsamen Transaktion geschrieben werden, sonst wird bei jedem INSERT gesynct und das ist langsam.

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,

Danke für den Tip - das hatte ich leider nicht gemacht.

Code: Alles auswählen

Query.insert;
.
.
.
Query.post;
Query.applyupdates;
Wie macht man es den anders ?

Gruß Frank

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

Nochmals visuelle Controls: Hängen am Query visuelle DBControls? Ein DBGrid z.B.? Hier bringt Query.DisableControls vor und Query.EnableControls nach der gesamten Aktion Wunder.

Welche Datenbank?

Ein Tipp zum Prüfen, ob GetADIFValue der Sündenbock ist: Kommentiere einmal den Zugriff auf die Datenbank aus, so dass GetADIF Value die gefundenen Daten ins Leere schreibt. Das muss sehr schnell gehen. Wenn du das bestätigen kannst, sollte klar sein, dass RegEx auch nicht schneller sein kann.

Wenn nicht weiterhilft, müsstest du den gesamten Code posten.

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: Wie macht man es den anders ?
In MSEgui würde ich TSqlite3Connection.options slo_transactions aktivieren und die Daten mit einem TSQLStatement schreiben um den
TSQLQuery overhead zu vermeiden.
In Lazarus kannst du etwas entsprechendes mit TSQLQuery.ExecSQL() erreichen, eine SQL-Statement Komponente gibt es AFAIK mittlerweile auch. Die SQL-property muss ein SQL-INSERT-Statement enthalten, welches die Werte in die Datenbank schreibt. Etwa so:

Code: Alles auswählen

 
 while datenvorhanden do begin
  werte nächsten Record aus;
  speichere die Felddaten in die entsprechenden "<sqlquery>.Params" Items;
  <sqlquery>.execsql();
 end;
 <transaction>.commit();
 

Der implizite-Transaktions-Modus muss ausgeschaltet sein, damit nicht bei jedem execsql() commited wird. Wie das in FPC geht, weiss ich nicht.
Kennst du dich mit SQL-INSERT statements aus?
Wie lange dauert der Schreibvorgang? Bei wie vielen records?

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 »

... ich verwende die Zeos komponenten für meine sqlite3 DB
In der Query habe ich CachedUpdates aktiviert damit nicht sofort in die DB geschrieben wird und mit ApplayUpdates wird in die DB geschrieben.

Werde mal rumsuchen wie man das mit den Zeos lösen kann.

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 »

Vermutlich sollte TZConnection.AutoCommit auf "false" gesetzt werden.

Benutzeravatar
gladio
Beiträge: 223
Registriert: Sa 21. Jun 2014, 06:15
OS, Lazarus, FPC: Win10-64 - aktuelle Lazarus/FPC Standard-Edition
CPU-Target: 64Bit
Wohnort: Rügen

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von gladio »

Versuche es mal ohne ApplyUpdates. Das einfache Query.Post sollte reichen.

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

Nimm dir die Zeit und extrahiere die fraglichen Programmteile (parsen und in DB schreiben) in eine kleine Demo, so dass der Fehler reproduziert werden kann, und poste das Projekt zusammen mit der ADIF-Datei. Wahrscheinlich findest du dabei den Fehler schon von allein, oder du bekommst hier innerhalb kürzester Zeit konkrete Hinweise, was falsch ist. Sonst bleibt hier alles beim unverbindlichen Herumraten.

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,

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.

Gruß
Frank
Zuletzt geändert von DL3AD am Mi 12. Jul 2017, 10:17, insgesamt 1-mal geändert.

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von braunbär »

Um es vorauszuschicken: Mit Regex wird das Parsing ziemich sicher nicht schneller. Ich denke aber, dass der Zeitverbrauch des Parsens bei deiner Aufgabe vernachlässigbar ist, egal, welches System du verwendest, Das Laufzeitproblem ist irgendwo anders begründet.
Der Vorteil von Regex liegt in der höheren Flexibilität, in der Einfacheit des Programms und in der Direktheit des Ansatzes. Statt einen Algorithmus zu programmieren, der die vorgegebene Syntax auflöst, schreibe ich ein Regex, dessen Komponenten nur beschreiben, wie der gesuchte String ausschaut. Um Details der Implementierung der Matchings brauche ich mich dann nicht zu kümmern, und wenn sich irgend wann an den Anforderugen etwas ändert, brauche ich nicht in Code wühlen, um zu finden, was ich jetzt in meinem Algorithmus anders machen muss, sondern ich brauche nur den regulären Ausdruck dort ändern, wo sich die Vorgabe geändert hat.

Code: Alles auswählen

 
uses
  RegExpr;
 
...
 
procedure GetAdifValue(var Txt: string);
const 
regex =
   '(?i)'       // Case-insensitive
  +'^<'         // Der String muss mit dem Zeichen < anfangen
  +'(?:'        // Was hier innerhalb der Klammern folgt, ist als Submatch uninteressant. Die Klammern sind nur dazu da, um die beiden Alternativen zu klammern
  +'(eor)>\s*'  // 1. Alternative: Der Text eor>, wobei "eor" als erster Submatch gespeichert wird. Eventuelle Leerzeichen u.dgl. nach dem > werden ignoriert 
  +'|'          // Hier endet die erste Alternative und beginnt die zweite Alternative
  +'([^:]+)'    // 1 oder mehr beliebige Zeichen bis zu einem ":" , wird im zweiten Submatch gespeichert
  +':'          // Dann ein Doppelpunkt
  +'([^>]+)'    // 1 oder mehr beliebige Zeichen bis zu einem ">" , wird im dritten Submatch gespeichert. Anzahl der Zeichen des Datenfelds   
  +'>'          // Das Zeichen >
  +'([^<]+)'    // 1 oder mehr beliebige Zeichen bis zu einem < oder dem Stringende, wird im vierten Submatch gespeichert, 
                // eventuell statt dessen '(.{\3})\s*' probieren, wenn in den Daten das <-Zeichen vorkommen kann
  +')';         //  Schließt die Klammer von "(?:" - hier endet die zweite Alternative
 
var
re: TRegExpr;
begin
re := TRegExpr.Create(regex);
try
  if re.Exec(txt) then
  begin
   ATag:=upcase(trim(re.Match2));
   AValue:=upcase(trim(re.Match4));
   delete(txt,1,length(re.Match));
  end
  else showmessage('Syntax passt nicht: '+Txt);
  finally re.free;
end.
 
Die Prozedur schneidet aus dem String den gefundenen Teil heraus, sodass der nächste Prozeduraufruf richtig weitermacht.
Wenn der Tag eor gematcht wird, wird ein leerer Tag als ATag gesetzt (eor liegt in re.Match[1] und nicht in re.Match[2]).
Es wird alles in Upper Case konvertiert, wenn das bei den Daten unerwünscht ist, lass das upcase dort weg.
An sich gehören aTag und aValue als Prozedurparameter übergeben, globale Variable dafür zu nehmen ist prinzipiell keine gute Idee.

Eventuell wäre statt des Showmessage eine Exception angebracht, weil ja nicht sinnvoll weitergematcht werden kann.
Etwas effizienter wird es, wenn man das Regex nur einmal außerhalb dieser Prozedur erzeugt und erst ganz am Ende wieder freigibt.
Zuletzt geändert von braunbär am Mo 10. Jul 2017, 11:56, insgesamt 2-mal geändert.

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 »

Probiere mal

Code: Alles auswählen

 
var
 s1: string;
begin
 s1:= AFile.Text;
  while Ai < Length(s1) do
  begin
    ClearVar;
    while (ATag <> 'EOR') and (Ai <= Length(s1)) do
      begin
        ADIFPars.GetAdifValue(s1);
        Case ADIFPars.ATag of
          'CALL'         : ACALL         := ADIFPars.AValue;
 
TStringlist.text ist eine sehr teure Operation da dort der String immer aus allen Zeilen zusammengesetzt wird. Wenn die Geschwindigkeit nicht reicht, gibt es noch viele weitere Optimierungsmöglichkeiten.

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: 5134
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: 2807
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.

Antworten