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

Dateien parsen - mit welchen Werkzeugen ?

Beitrag von DL3AD »

Hallo,

ich möchte mir einen Converter bauen um Dateien in einem speziellen Format (ADIF) einzulesen und in meiner Datenbank speichern
Die Daten sind etwa so abgelegt

Code: Alles auswählen

<call:6>VP8STI <qso_date:8>20160122 <time_on:4>2359 <band:3>40M <mode:2>CW <freq:5>7.019
<station_callsign:6>DL13xyz
<dxcc:3>240 <iota:6>AN-009
<rst_sent:3>599 <rst_rcvd:3>599 <lotw_qsl_sent:1>Y <lotw_qsl_rcvd:1>Y <eqsl_qsl_sent:1>N <eqsl_qsl_rcvd:1>N <qsl_sent:1>N <qsl_rcvd:1>Y <eor>
<call:6>VP8STI <qso_date:8>20160123 <time_on:4>1450 <band:3>17M <mode:2>CW <freq:6>18.079
<station_callsign:6>DL1uvw
<dxcc:3>240 <cqz:2>13 <ituz:2>73 <iota:6>AN-009 <tx_pwr:3>100
<rst_sent:3>599 <rst_rcvd:3>599 <lotw_qsl_sent:1>Y <lotw_qsl_rcvd:1>Y <eqsl_qsl_sent:1>N <eqsl_qsl_rcvd:1>N <qsl_sent:1>N <qsl_rcvd:1>Y <eor>
<call:6>Z63MED <qso_date:8>20160124 <time_on:4>1517 <band:3>30M <mode:2>CW <freq:6>10.128
<station_callsign:6>DL9iii <name:5>DAVID
<dxcc:3>296 <cqz:2>15 <ituz:2>28 <tx_pwr:3>100
<rst_sent:3>599 <rst_rcvd:3>599 <lotw_qsl_sent:1>Y <lotw_qsl_rcvd:1>N <eqsl_qsl_sent:1>N <eqsl_qsl_rcvd:1>N <qsl_sent:1>N <qsl_rcvd:1>N <eor>
 


Die Tag's sind in beliebiger Anzahl und Reihenfolge (jede Datei kann anders sein) angeordnet.
Jeder Datensatz ist mit <eor> abgeschlossen.

Welche Werkzeuge bietet Lazarus um sowas zu bearbeiten ?

Gruß
Frank

Mathias
Beiträge: 6193
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 »

Welche Werkzeuge bietet Lazarus um sowas zu bearbeiten

Das wichtigste sicher mal TStringList.
Pos um Zeichenfolgen zu finden.
Mit Poas kannst du zB. <band: suchen.
So wie ich sehe, hast du nach jedem Parameter ein Leerzeichen, somit kannst du mit String.Spitt schon mal die Parameter trennen.

Mit folgendem Code, hast du schon mal auf die einzelnen Parameter zerlegt.

Code: Alles auswählen

  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
.....
procedure TForm1.Button1Click(Sender: TObject);
var
  sl: TStringList;
  sa: TStringArray;
  i: integer;
begin
  sl := TStringList.Create;
  sl.LoadFromFile('test.txt');
 
  sa := sl.Text.Split([' ', LineEnding]);
 
  Memo1.Clear;
  for i := 0 to Length(sa) - 1 do begin
    Memo1.Lines.Add(sa[i]);
  end;
 
  sl.Free;
end;   
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

Schau dir das beiliegende Projekt an. Es verwendet einen schnell hingeschriebenen, vielleicht noch fehlerhaften ADIF-Parser, der sich am fasthtmlParser (im FPC-Ordner packages\chm\src) orientiert. Der Parser setzt eine PChar-Laufvariable auf das 1.Zeichen des gesamten Textes und durchläuft den Text dann Zeichen für Zeichen. Wenn ein '<' kommt, beginnt ein "Tag"; dieses wird durchsucht, bis ein ':' kommt oder ein '>'. Im ersteren Fall wird bis zum '>' weitergelesen, der String wird in eine Zahl umgewandelt und ist die Anzahl der folgenden Textzeichen(*), die im folgenden eingelesen werden, usw. Falls das letzte Tag-Zeichen ein '>' war, kommt ein Text. Immer wenn ein Tag oder ein Text eingelesen ist, wird ein entsprechendes Event OnTagFound bzw OnTextFound generiert. Was nun in den Events geschicht, ist Sache des Programms. Mehr macht der Parser nicht, dadurch ist er sehr allgemein verwendbar.

Das Beispiel-Programm schickt die vor dir angegebene Datei durch den Parser und sortiert die einzelnen elemente in ein StringGrid ein. Dafür sind entsprechende Eventhandler für die genannten Events vorhanden: Immer wenn Tag-Wert "eor" ist, beginn eine neue Zeile im Grid. Ansonsten wird der Tag-Wert als Spaltenüberschrift verwendet. Es wird gesucht, ob es diese Spalte schon gibt und der Spaltenindex zurückgegeben; oder es wird eine neue Spalte generiert. Der SpaltenIndex wird in FCurrCol gespeichert. Wenn nun das Event für den zugehörigen Text kommt, wird der übergebene Text in die entsprechende Zelle geschrieben.

(*) Das habe ich aus: http://www.adif.org/adif.html
Dateianhänge
ADIFparser.zip
(2.99 KiB) 88-mal heruntergeladen

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 - wollte mich eigentlich selber quälen :mrgreen:
Werde mir das morgen mal anschauen.

Vielen Dank !

Gruß Frank

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 »

Für eine derartige Aufgabenstellung drängen sich meines Erachtens reguläre Ausdrücke auf. Damit lassen sich derartige Texte wirklich auf einfachste Art parsen.
Es ist wahrscheinlich nicht die schnellste Methode, aber wenn die zu bearbeitenden Files nicht Megabytes groß sind, wird man das in der Praxis nicht merken.

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 braunbär,

hast du mal einen Link wo man das Prinzip nachlesen kann wie man das mit regulären Ausdrücken macht ?

Warf
Beiträge: 1909
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von Warf »

Du könntest dir auch einen deterministischen endlichen Automaten(DEA) bauen, welcher tags erkennt, und einen der beliebige strings erkennt, und die dann nach dem longest Match Prinzip die Automaten auf dem String laufen lassen. In den Tag DEA kannst du gleichzeitig zur Laufzeit die Taginformationen Parsen. Dabei erhälst du alle information mit nur einem Durchlauf durch den String. Dieser Vorgang heist lexical analysis

Ein Beispiel habe ich vor etwa einem Jahr mal gebaut, einen Leser welcher google Proto Dateien Parsen kann. Das Projekt hab ich nicht weiter verfolgt, aber der Lexer ist vollständig implementiert: Lexer, und die DEA's.

Grundsätzlich sollten RegEx Komponenten genauso arbeiten, allerdings bin ich mir nicht so sicher wie es mit RegEx Komponenten für lazarus aussieht, zumal ob es überhaupt vernünftige gibt, und wie diese Implementiert sind. (Oftmals findet man zwar welche, die sind dann aber teilweise echt schlecht implementiert, ohne Automaten oder ähnliches)

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 »

Wenn du nach regular expressions googelst, findest du Unmengen Seiten.

Wie man Regex in Free Pascal einsetzt, ist in meinem Link beschrieben. Dort steht auch ganz am Anfang ein Link zur Wiki, die eine gute Übersicht bietet.


Hier oder hier findest du kompakt dargestellt, wie man ein Regex aufbaut.

Und das habe ich eben gefunden, das ist die beste Erklärungsseite, die mir bisher untergekommen ist.
Zuletzt geändert von braunbär am Mi 5. Jul 2017, 23:44, insgesamt 1-mal geändert.

Mathias
Beiträge: 6193
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 »

So hast du deine Datei zerlegt, der Rest sollte eigentlich kein Problem mehr darstellen.

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  s: string;
  sl: TStringList;
  sa, sa2: TStringArray;
  i, j: integer;
begin
  sl := TStringList.Create;
  sl.LoadFromFile('test.txt');
 
  sa := sl.Text.Split([' ', LineEnding]);
 
  Memo1.Clear;
  for i := 0 to Length(sa) - 1 do begin
    s := copy(sa[i], 2);
    sa2 := s.Split([':', '>']);
    if Length(sa2) > 0 then begin
       if sa2[0]='eor' then sa2[0]:='Satz_Ende';
      s := '';
      for j := 0 to Length(sa2) - 1 do begin // Nur für Testausgabe.
        s := s + sa2[j] + '       ';
      end;
      Memo1.Lines.Add(s);
    end;
  end;
 
  sl.Free;
end; \0
Mit Lazarus sehe ich grün
Mit Java und C/C++ sehe ich rot

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

Und wenn ein Datenfeld ein Leerzeichen oder einen Doppelpunkt enthält? Warum steht im Tag nach dem Doppelpunkt die Länge des Datenfeldes, wenn man dieses ignorieren kann?

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 »

@Mathias
Die Verwendung von regex hat gegenüber diesem Ansatz den Vorteil, dass es wesentlich flexibler ist. Du verlässt dich z.B. hier darauf, dass Leerzeichen nur an einer Stelle, nämlich vor einem neuen Tag als Trennzeichen, vorkommen. Im Beispiel ist das der Fall. Wenn das (auch irgendwann später) nicht mehr so funktioniert, musst du im Prinzip die komplette Programmlogik kübeln und neu durchdenken, weil ein Split auf Basis von Leerzeichen funktioniert dann nicht mehr. Setzt du in der Programmlogik zum Splitten reguläre Ausdrücke ein, dann brauchst du bei den meisten Änderungen nur den regulären Ausdruck anpassen, und der Programmcode bleibt im wesentlichen unverändert.
Die Regex-Syntax ist zwar gewöhnungsbedürftig, aber dafür sehr mächtig.

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,

habe mir dass mal angeschaut :shock:
was ist p^ und #0 ?
was ist der unterschied zwischen nil und ''



Gruß frank

pluto
Lazarusforum e. V.
Beiträge: 7178
Registriert: So 19. Nov 2006, 12:06
OS, Lazarus, FPC: Linux Mint 19.3
CPU-Target: AMD
Wohnort: Oldenburg(Oldenburg)

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von pluto »

Ich löse sowas in der Regel so: Ich pass den Code natürlich an, ob ich jetzt CSS oder HTML oder sonst was parsen möchte:

Code: Alles auswählen

 
function LoadConfigFile(const aFileName: String; var aStringList:TStrings; DataItem:TPLDataItem): Boolean;
var
  FileStream:TFileStream;
  ch:char;
  inBlock, NoAddChar, NoAddLine, FirstCharLine:Boolean;
  LevelIndex:Integer;
  Token, Name, Value:String;
 
  LastDataItem, OldDataItem:TPLDataItem;
begin
  LevelIndex:=-1;
  result:=false;
  NoAddChar:=false;
  NoAddLine:=false;
  FirstCharLine:=true;
  Token:='';
  Name:='';
  Value:='';
 
  DataItem.Items:=TobjectList.Create;
  LastDataItem:=DataItem;
  if FileExists(aFileName) then begin
    result:=true;
 
    FileStream:=TFileStream.Create(aFileName,fmOpenRead);
    while true do begin
      FileStream.ReadBuffer(ch,1);
      if ch = '#' then begin
        NoAddLine:=True;
        NoAddChar:=True;
      end;
 
      if not NoAddLine then begin
        if ch = '}' then begin
          inBlock:=False;
          Token:='';
          if LevelIndex-1 >=0 then begin
            LevelIndex:=LevelIndex-1;
 
            if LastDataItem.Parent <> nil then begin
              LastDataItem:=LastDataItem.Parent;
 
            end
            else begin
              LastDataItem:=DataItem;
            end;
          end
          else begin
            LastDataItem:=DataItem;
          end;
          NoAddChar:=true;
        end;
 
        if ch = '{' then begin
          inc(LevelIndex);
 
          if LastDataItem.FindByName(Trim(Token));
          LastDataItem:=LastDataItem.Add;
          LastDataItem.Items:=TobjectList.Create;
          LastDataItem.Name:=Trim(Token);
 
          inBlock:=True;
          Name:=Token;
          Token:='';
 
          NoAddChar:=True;
        end;
        // Test Test Test Test
        if (ch = ':') and (Name = '') then begin
          Name:=Token;
          Token:='';
          NoAddChar:=true;
        end;
      end;
 
      if ch in [#13,#10] then begin
        if not NoAddLine then begin
          if Token <> '' then value:=token;
 
          if Value <> '' then begin
            LastDataItem:=LastDataItem.Add;
            LastDataItem.Name:=Trim(Name);
            LastDataItem.Value:=Trim(Value);
            LastDataItem:=LastDataItem.Parent;
          end;
          Name:=''; Value:='';
 
        end;
 
        Token:='';
        NoAddLine:=False;
        NoAddChar:=True;
        FirstCharLine:=true;
      end;
 
      if (FirstCharLine) and (not NoAddChar) then begin
 
        if ch <> ' ' then begin
          FirstCharLine:=false;
        end
        else begin
          NoAddChar:=true;
        end;
      end;
 
      if not NoAddChar then begin
        Token:=Token+ch
      end
      else
        NoAddChar:=false;
 
      if FileStream.Position+1 >=FileStream.Size then break;
    end; // while
  end;
  FileStream.Free;
end; // LoadConfigFile
 


So sieht eine entsprechende Config Datei aus, inzwischen habe ich es stark erweitert.

Code: Alles auswählen

 
LCDModul2Button {
  Typ: REMOTE
  Titel: Stellt 8 Buttons zu verfügung
  ID: 271
  Controler: ArduinoNano
  Enabled: ON
  Pfad:Arbeitzimmer/Sofa
 
  ParamaterList {   
    Button1:%PageNext%
    Button2:%PagePrev%
    Button3: 
    Button4:
    Button5: 
    Button6:   
    Button7:%SendModul,/Modul/pmpdApp/PlayPause%
    Button8:%SendModul,/Modul/pacAPP/Serial/35,CMD,OUT,2%
  }
 
  Location {
    Sensor1:Arbeitzimmer
  }
}
 


TStringlist würde ich nicht nehmen, da sie immer erst die Datei komplett einlist und dann kann mit der Verarbeitung angefangen werden.
Mit TFileStream spart man sich das. Es ist auch nur geringfügig aufwendiger.
MFG
Michael Springwald

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,

gibt es eine String Suchfunktion mit der man nicht Casesensitiv suchen kann d.h. 'test' und 'TEST' liefert ein Ergebnis ?

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

Re: Dateien parsen - mit welchen Werkzeugen ?

Beitrag von wp_xyz »

DL3AD hat geschrieben:was ist p^ und #0 ?
was ist der unterschied zwischen nil und ''

p, deklariert als PChar, ist ein Zeiger auf die Zeichen eines Strings. p^ ist das Zeichen, auf das p zeigt. Im Unterschied dazu ist PChar(p) der String, der an dem Zeichen beginnt, auf das p zeigt.

Code: Alles auswählen

program Project1;
 
{$mode objfpc}{$H+}
 
var
  s: String;
  p: PChar;
begin
  s := 'abc';
  p := @s[1];
  WriteLn(p^);
  WriteLn(PChar(p));
 
  inc(p);
  WriteLn(p^);
  WriteLn(PChar(p));
 
  ReadLn;
end.

Da am Stringende immer ein Null-byte ist, erkennt man mit p^ = #0, ob das Stringende erreicht ist. Das Stringende kann man auch mit p = nil prüfen.

falls du damit Problem hast, kannst du versuchen, den Parser auf eine i-Schleife umzuschreiben, in der du i hochzählst und mit s[i] auf das aktuelle Zeichen zugreifen kannst. Achtung: Eine for-Schleife ist nicht geeignet, weil in dem Algorithmus i auch innerhalb der Schleife hochgezählt wird.

Diese Lösung hätte übrigens den Vorteil, dass im String auch Null-Bytes eingebettet sein können. Aber das wäre eher ein hypothetischer Vorteil, weil die GUI-Controls wahrscheinlich beim Nullbyte abschneiden, und weil es in deiner Datei nicht vorkommen wird.

Antworten