[geloest] CSV-File aus TList schreiben

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

[geloest] CSV-File aus TList schreiben

Beitrag von photor »

Hallo Forum,

ich habe vor, im Programm berechnete Daten/Zustandspunkte in eine CSV-Datei zu speichern, um sie eventuell extern weiterverarbeiten zu können. Die einzelnen Punkte werden an eine TList angehängt und im Code vorgehalten; sie sehen wie folgt aus:

Code: Alles auswählen

 
  TAccelPoint = record
    { the physical state }
    t : double;     { time }
    a : double;     { acceleration }
    v : double;     { velocity }
    s : double;     { distance }
    { aditional info about the state of the bike }
    revs : double;
    factor : double;
    gear : integer;
    clutch_slipping : Boolean;
    wheel_slipping : Boolean;
    wheelie : Boolean;
  end;

Gibt es einen eleganten Weg, diese Daten in eine CSV-Datei zu speichern? Ich kann mir im Prinzip für jede Zeile einen String basteln und rausschreiben.

Aber vielleicht gibt es schon etwas vorgefertigtes? Meine Suche dazu im Wiki hat folgendes ergeben: [1], [2] oder [3]: Diese legen aber sehr viel Wert auf CSV als Datenbank. Dieses Feature brauche ich aber nicht, da die Daten sowieso immer zusammenhängend betrachtet werden (also keine separaten Datensätze sind). Und sind es Standards (SDF ist mir bislang noch nirgendwo begegnet)?

Für jeden Tipp bin ich dankbar,

Photor

[1] http://wiki.freepascal.org/CSV
[2] http://wiki.freepascal.org/SDF
[3] http://wiki.freepascal.org/CsvDocument
Zuletzt geändert von photor am Fr 9. Sep 2016, 17:46, insgesamt 1-mal geändert.

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

Re: CSV-File aus TList schreiben

Beitrag von wp_xyz »

Eigentlich braucht man dafür nichts fertiges. Aber wenn Zellen leer sind, oder die Spaltentrenner oder Zeilenumbrüche enthalten, ist es doch gut auf das "offizielle" Tool des FPC zurückzugreifen, das CSVDocument. Seitdem dieses in fpc integriert ist, sind die Schreib/Leseroutinen in Unit csvreadwrite abgespalten und das reicht für diese Aufgabe.

  • Also zunächst "uses csvreadwrite".
  • Dann eine Instanz von TCSVBuilder erzeugen (csv := TCSVBuilder.Create).
  • Spaltentrennzeichen festlegen: csv.Delimiter := #9
  • Ausgabestream festlegen: csv.SetOutput(stream), wenn nicht geht die Ausgabe in einem Memorystream (csv.DefaultOutput), den man am Ende in eine Datei schreiben kann.
  • Liste Element für Element durchlaufen
  • Jedes RecordElement in einen String umwandeln und per csv.AddCell(string) in den Stream schreiben. Bei Gleitkommazahlen schreibe ich immer mit Dezimalpunkt (und lese später auch genauso wieder ein), d.h. FormatSettings entsprechend präparieren (dieser Schritt kann aber entfallen).
  • Nach dem letzten Element csv.AddRow für neue Zeile
  • Zum nächsten Listenelement
Hier ein lauffähiges Beispiel (nur ein Formular mit einem Button), allerdings nur der Normalfall mit "normalen" Zellen, keine Zeilenumbrüche in der Zelle und sowas (dazu müsste man entsprechende Properties im csvBuilder bzw. dessen Vorfahren TCSVHandler setzen):

Code: Alles auswählen

unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
 
  public
 
  end;
 
var
  Form1: TForm1;
 
implementation
 
uses
  csvreadwrite;       // <--- wichtig
 
{$R *.lfm}
 
type
  TData = record
    x: Double;
    s: String;
  end;
  PData = ^TData;
 
var
  list: TList;
 
function NewDataRecord(x: Double; s: String): PData;
begin
  New(Result);
  Result^.x := x;
  Result^.s := s;
end;
 
procedure DisposeDataRecord(P: PData);
begin
  P^.s := '';
  Dispose(P);
end;
 
procedure CreateDataList(out list: TList);
var
  i: Integer;
begin
  list := TList.Create;
  for i:=1 to 100 do
    List.Add(NewDatarecord(Random, 'abcdefg' + IntToStr(i)));
end;
 
procedure DestroyDataList(list: TList);
var
  i: Integer;
begin
  for i:=0 to list.Count-1 do
    DisposeDataRecord(PData(list[i]));
  list.Free;
end;
 
procedure WriteToCSV(AFilename: String; list: TList);   // <--- das ist die CSV-Routine
var
  csv: TCSVBuilder;
  P: PData;
  i: Integer;
  fs: TFormatSettings;
begin
  fs := DefaultFormatSettings;
  fs.DecimalSeparator := '.';   // Dezimalpunkt statt -komma
  csv := TCSVBuilder.Create;
  try
    csv.Delimiter := #9// Tabulator als Spaltentrenner
    // Spaltennamen
    csv.AppendCell('x');
    csv.AppendCell('s');
    csv.AppendRow;
    // Wertetabelle
    for i:=0 to list.Count-1 do begin
      P := PData(list[i]);
      csv.AppendCell(FloatToStr(P^.x, fs));
      csv.AppendCell(P^.s);
      if i < list.Count - 1 then
        csv.AppendRow;
    end;
    csv.DefaultOutput.SaveToFile(AFilename);
  finally
    csv.Free;
  end;
end;
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
var
  list: TList;
begin
  CreateDataList(list);
  try
    WriteToCSV('d:\test.txt', list);
  finally
    DestroyDataList(list);
  end;
end;
 
end.

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

Re: CSV-File aus TList schreiben

Beitrag von photor »

Perfekt wp_xyz. Danke für den Tipp; damit komme ich bestimmt weiter - und es wird eleganter als mein Stückwerk.

Ciao,

Photor

Benutzeravatar
photor
Beiträge: 443
Registriert: Mo 24. Jan 2011, 21:38
OS, Lazarus, FPC: Arch Linux: L 2.2.6 FPC 3.2.2 (Gtk2)
CPU-Target: 64Bit

[geloest] CSV-File aus TList schreiben

Beitrag von photor »

Kurze Rückmeldung: macht was es soll bzw. ich mir vorgestellt habe - Danke! Als "gelöst" markiert.

Ciao,

Photor

Antworten