Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Neuen Beitrag zur Code-Library hinzufügen (https://www.delphipraxis.net/33-neuen-beitrag-zur-code-library-hinzufuegen/)
-   -   Delphi csv Datei Import ClassHelper für TClientDataSet (https://www.delphipraxis.net/164041-csv-datei-import-classhelper-fuer-tclientdataset.html)

MaBuSE 26. Okt 2011 20:26


csv Datei Import ClassHelper für TClientDataSet
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,
oft kommt es vor, das man eine csv Datei ("comma seperated value" - Datei) laden möchte um die Daten zu verarbeiten.

Eine Beispiel csv Datei könnte so aussehen:
Code:
Test;"Test Test";"Test "" Test";"Test ; Test";"Test "";"" Test"
Das würde folgenden Feldern entsprechen:
Code:
Spalte 1: Test
Spalte 2: Test Test
Spalte 3: Test " Test
Spalte 4: Test ; Test
Spalte 5: Test ";" Test
Im Netz habe ich nur relativ komplizierte "Lösungen" gefunden, die das mit den Delimiter und QuoteChar richtig machen.

Aus diesem Grund habe ich mir einen kleinen ClassHelper für das TClientDataSet geschrieben.

In meiner "einfachen" Lösung mache ich mir den Umstand zunutze, das die csv Implementierung eigentlich schon im TStrings Objekt vorhanden ist. Deshalb muss ich nicht mir Pos(';', ... arbeiten.

Ich habe das TClientDataSet verwendet, da es ein vollwertiges TDataSet ist und so alle Möglichkeiten der Bearbeitung und Anzeige zu Verfügung stehen.

Folgendes kleines Beispiel soll die Benutzung demonstrieren:
  • neue VCL Anwendung erzeugen
  • TClientDataSet auf das Formular legen
  • TDataSource auf das Formular legen und mit TClientDataSet verbinden
  • TDBGrid auf das Formular legen und mit TDataSource verbinden
  • TButton auf das Formular legen
  • TButton doppelklicken
  • Die uses Anweisung oben in der Unit um meine Unit erweitern (ClientDatasetCsvClassHelper)
  • in das OnClick Ereignis folgenden Code schreiben:
    Delphi-Quellcode:
      ClientDataSet1.LoadFromFile('Dateiname.txt');
    (Dateiname.txt sollte durch einen gültigen Dateinamen einer *.csv Datei ersetzt werden)
  • Kopilieren und Button drücken

Diese Lösung ist so felxibel, dass auch andere Delimiter und QuoteChar verwendet werden können.
Eine evtl vorhandene Titelzeile mit Feldnamen kann auch berücksichtigt werden.
Es gibt natürlich noch viel Raum für Verbesserungen. Aber es soll ja nur ein kleines Beispiel sein.
  • nach Import Feldlängen bestimmen und Feler entsprechend kürzen
  • FeldTypen erkennen und Felder entsprechend setzen (String, Integer, TDateTime, ...)
  • Encoding (z.B. UTF8 / Unicode) als Parameter durchreichen
  • ...

Ich hoffe das es einige nützlich finden
Euer MaBuSE

Das ist der komplette Quelltext:
Delphi-Quellcode:
////////////////////////////////////////////////////////////////////////////////
// ClientDatasetCsvClassHelper
//
// 2 einfache Methoden um csv Dateien (comma seperated value Dateien) in ein
// TClientDataSet zu laden bzw. zu speichern.
//
// Der ClassHelper wurde Quick and Dirty geschrieben, und ist nicht als
// Lehrbuchbeispiel der Programmierung geeignet. Trotzdem hoffe ich, das es
// für den ein oder anderen lehrreich ist.
//
// Benutzung:
//
// - diese Unit in der uses Anweisung aufnehmen
// - ClientDataSet hat nun die 2 Methoden zur Verfügung
//
// unit TestIt;
// interface
// uses
//   ...
//   Dialogs, StdCtrls, Grids, DBGrids, DB, DBClient, ComCtrls,
//   ClientDatasetCsvClassHelper;
// type
//   TForm1 = class(TForm)
//     Button1: TButton;
//     DataSource1: TDataSource;
//     DBGrid1: TDBGrid;
//     ClientDataSet1: TClientDataSet;
//     procedure Button1Click(Sender: TObject);
//   end;
// ...
// implementation
// ...
// procedure TForm1.Button1Click(Sender: TObject);
//   ClientDataSet1.LoadFromFile('C:\temp\Test.txt', 15);
// end;
// ...
//
// Download des neusten Quelltextes auf www.delphipraxis.net
// URL: http://www.delphipraxis.net/1132710-post1.html (url bitte anpassen)
//
// verwendete 3rd party Komponenten:
//   - keine
//
// sonstige verwendete Dateien, die nicht Bestandteil von Delphi sind:
//   - keine
//
////////////////////////////////////////////////////////////////////////////////
// Der Classhelper ist Freeware und darf beliebig benutzt und erweitert werden.
// Es wäre nett, wenn dann auch der geänderte Quelltext in obiges URL-Adresse
// gesendet wird. Dann haben alle was davon.
// Es wäre auch nett wenn mein (unser) Name in den Dateien enthalten bleibt.
// Der ClassHelper wird von Ihnen auf eigenes Risiko eingesetzt. Ich übernehme
// keine Haftung für Schäden die durch den Classhelper oder die Benutzung des
// Classhelper entstanden sind bzw. entstehen.
////////////////////////////////////////////////////////////////////////////////
// (C) 2011, MaBuSE, member of DelphiPraxis.net
////////////////////////////////////////////////////////////////////////////////
// ReleaseNotes:
// v1.0 - 26.10.2011 - MaBuSE: Erste Version mit Kommentaren versehen
////////////////////////////////////////////////////////////////////////////////
unit ClientDatasetCsvClassHelper;

interface

uses
  SysUtils, Classes, DB, DBClient;

type
  TClientDataSetCsvClassHelper = class helper for TClientDataSet
    procedure LoadFromFile(Filename: string; const StringLength: Integer = 100;
      const FirstLineTitle: Boolean = False;
      const Delimiter: Char = ';'; const QuoteChar: Char = '"');
    procedure SaveToFile(Filename: string; const FirstLineTitle: Boolean = False;
      const Delimiter: Char = ';'; const QuoteChar: Char = '"');
  end;


implementation

{ TClientDataSetCsvClassHelper }

// LoadFromFile importiert eine csv Datei in ein TClientDataSet.
// Dabei wird die Tabellenstruktur entsprechend der csv Datei neu erzeugt
//
// Parameter:
//   Filename: string                      Dateiname der csv Datei
//   const StringLength: Integer = 100      Länge der Felder (alle Felder sind gleich lang)
//   const FirstLineTitle: Boolean = False In 1. Zeile stehen Spaltennamen
//   const Delimiter: Char = ';'           Delimiter trennt die einzelnen Felder
//   const QuoteChar: Char = '"'           QuoteChar schließt Strings ein
//
procedure TClientDataSetCsvClassHelper.LoadFromFile;
var
  slFile: TStringList;
  slRow: TStringList;
  i: Integer;
  j: Integer;
begin
  slFile := TStringList.Create;
  slRow := TStringList.Create;
  try
    slRow.Delimiter := Delimiter;
    slRow.QuoteChar := QuoteChar;
    slRow.StrictDelimiter := True;
    slFile.LoadFromFile(Filename);

    // ClientDataset Initialisieren
    if slFile.Count > 0 then
    begin
      Active := False;
      slRow.DelimitedText := slFile[0];
      FieldDefs.Clear;
      for i := 0 to slRow.Count - 1 do
      begin
        if FirstLineTitle then
        begin
          FieldDefs.Add(slRow[i], ftString, StringLength);
        end
        else
        begin
          FieldDefs.Add(Format('Field%d',[i]), ftString, StringLength);
        end;
      end;
      CreateDataSet;
      Active := True;
    end;

    // TClientDataset füllen
    DisableControls;
    for i := 0 to slFile.Count - 1 do
    begin
      slRow.DelimitedText := slFile[i];
      Append;
      for j := 0 to slRow.Count - 1 do
      begin
        Fields[j].AsString := slRow[j];
      end;
      Post;
    end;
    EnableControls;
  finally
    slFile.Free;
    slRow.Free;
  end;
end;

// SaveToFile exportiert ein TClientDataSet in eine csv Datei.
//
// Parameter:
//   Filename: string                      Dateiname der csv Datei
//   const FirstLineTitle: Boolean = False In 1. Zeile stehen Spaltennamen
//   const Delimiter: Char = ';'           Delimiter trennt die einzelnen Felder
//   const QuoteChar: Char = '"'           QuoteChar schließt Strings ein
//
procedure TClientDataSetCsvClassHelper.SaveToFile(Filename: string;
  const FirstLineTitle: Boolean; const Delimiter, QuoteChar: Char);
var
  slFile: TStringList;
  slRow: TStringList;
  i: Integer;
  c: Integer;
begin
  slFile := TStringList.Create;
  slRow := TStringList.Create;
  try
    slRow.Delimiter := Delimiter;
    slRow.QuoteChar := QuoteChar;
    slRow.StrictDelimiter := True;

    DisableControls;
    c := FieldDefs.Count;
    if FirstLineTitle then
    begin
      for i := 0 to c - 1 do
      begin
        slRow.Add(FieldDefs[i].Name);
      end;
      slFile.Add(slRow.DelimitedText);
    end;

    First;
    while not Eof do
    begin
      slRow.Clear;
      for i := 0 to c - 1 do
      begin
        slRow.Add(Fields[i].AsString);
      end;
      slFile.Add(slRow.DelimitedText);
      Next;
    end;
    slFile.SaveToFile(Filename);
    EnableControls;
  finally
    slFile.Free;
    slRow.Free;
  end;
end;

end.

Furtbichler 27. Okt 2011 07:46

AW: csv Datei Import ClassHelper für TClientDataSet
 
Geht das auch mit einer CSV-Datei der Form
Code:
Test;"Zeile 1 von Test
Zeile 2 von Test";Test
Daraus muss ein Record mit drei Feldern werden.

Uwe Raabe 27. Okt 2011 08:45

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von Furtbichler (Beitrag 1132756)
Geht das auch mit einer CSV-Datei der Form
Code:
Test;"Zeile 1 von Test
Zeile 2 von Test";Test
Daraus muss ein Record mit drei Feldern werden.

Das hat allerdings weniger mit dem ClassHelper zu tun, als mit der Implementation von SetDelimitedText in TStringList. MaBuSE geht einfach davon aus, daß diese Implementation korrekt ist.

Aber was heißt schon korrekt. Ein verbindliches Format für CSV gibt es nicht (nur RFC 4180) und ich habe selbst schon mehrfach eigene Implementierungen schreiben müssen, weil die Kollegen, die die erzeugenden Programme geschrieben haben, einfach ihre Hausaufgaben nicht gemacht hatten.

MaBuSE 27. Okt 2011 09:10

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von Furtbichler (Beitrag 1132756)
Geht das auch mit einer CSV-Datei der Form
Code:
Test;"Zeile 1 von Test
Zeile 2 von Test";Test
Daraus muss ein Record mit drei Feldern werden.

Nein, das geht nicht.
In csv Dateien bedeutet das Zeilenende in der Regel dass ein neuer Datensatz beginnt. Sprich: Es sind keine Zeilenunbrüche in Feldern erlaubt.

In meiner Implementierung kopiere ich jede einzelne Zeile in ein TStrings und hole dann die einzelnen Felder aus diesem heraus.
Mit meiner Methode geht das also auf keinen Fall.

Ich habe mir das ursprünglich geschrieben um bestimmte Log-Dateien auszuwerten.
Für die CodeLib habe ich dann noch SaveToFile ergänzt.

Meine Intention, das in die CodeLib zu stellen, war zu zeigen, dass es einen relativ einfachen Weg gibt csv Dateien zu importieren.

Letztendlich reicht schon folgender Quellcode aus um eine csv-Datei zu lesen und auf alle einzelnen Felder zuzugreifen:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  slFile, slRow: TStringList;
  i, j: Integer;
begin
  slFile := TStringList.Create;
  slRow := TStringList.Create;
  slRow.Delimiter := ';';
  slRow.QuoteChar := '"';
  try
    slFile.LoadFromFile('dateiname.csv');
    for i := 0 to slFile.Count - 1 do
    begin
      slRow.DelimitedText := slFile[i]; // wandelt eine csv-Zeile um -> jedes Feld bekommt eine eigene Zeile in slRow
      for j := 0 to slRow.Count - 1 do
      begin
        Memo1.Lines.Add(Format('Row:%3d Col:%2d: Value:%s', [i, j, slRow[j]]));
      end;
    end;
  finally
    slFile.Free;
    slRow.Free;
  end;
end;
Das ist ein gutes Beispiel für KISS (keep it stupid simple).

Ich hoffe diese Info hilft Dir.

MaBuSE 27. Okt 2011 09:35

AW: csv Datei Import ClassHelper für TClientDataSet
 
Auf der Basis des Beispiels in dem vorherigen Beitrag habe ich noch einen Class Helper geschrieben.

Damit kann man ein TStringGrid mit einer csv Datei befüllen.

Beispiel:
Delphi-Quellcode:
...
uses StringGridCsvClassHelper;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  StringGrid1.LoadFromFile('Dateiname.csv');
end;
...
Das ist ein einfaches Beispiel für Class Helper und csv Import.
Vieleicht hilft's wem.

mfg
MaBuSE

ps: Hier ist die Unit des ClassHelpers:

Delphi-Quellcode:
unit StringGridCsvClassHelper;
// use at your own risk
interface

uses
  SysUtils, Classes, Grids;

type
  TStringGridCsvClassHelper = class helper for TStringGrid
    procedure LoadFromFile(Filename: string;
      const Delimiter: Char = ';'; const QuoteChar: Char = '"');
  end;

implementation

{ TStringGridCsvClassHelper }

procedure TStringGridCsvClassHelper.LoadFromFile(Filename: string;
  const Delimiter: Char = ';'; const QuoteChar: Char = '"');
var
  slFile: TStringList;
  slRow: TStringList;
  i: Integer;
  j: Integer;
begin
  slFile := TStringList.Create;
  slRow := TStringList.Create;
  slRow.Delimiter := Delimiter;
  slRow.QuoteChar := QuoteChar;
  try
    slFile.LoadFromFile(Filename);

    // StringGrid Init
    if slFile.Count > 0 then
    begin
      slRow.DelimitedText := slFile[0];
      FixedCols := 0;
      FixedRows := 0;
      ColCount := slRow.Count;
      RowCount := slFile.Count;
    end;

    // Fill StringGrid
    for i := 0 to slFile.Count - 1 do
    begin
      slRow.DelimitedText := slFile[i];
      for j := 0 to slRow.Count - 1 do
      begin
        Cells[j,i] := slRow[j];
      end;
    end;
  finally
    slFile.Free;
    slRow.Free;
  end;
end;

end.

H.Bothur 7. Jun 2017 14:58

AW: csv Datei Import ClassHelper für TClientDataSet
 
Moin,

sorry wenn ich hier so was altes raussuche aber das hilft mir gerade sehr weiter. Allerdings ...

1) ich müsste den Trenner von Semikolon auf TAB ändern .. das wäre dann imho hier die "#9" statt "';'"
Delphi-Quellcode:
type
  TClientDataSetCsvClassHelper = class helper for TClientDataSet
    procedure LoadFromFile(Filename: string; const StringLength: Integer = 100;
      const FirstLineTitle: Boolean = False;
      const Delimiter: Char = #9; const QuoteChar: Char = '"');
    procedure SaveToFile(Filename: string; const FirstLineTitle: Boolean = False;
      const Delimiter: Char = #9; const QuoteChar: Char = '"');
  end;
2) Was mache ich wenn ich nicht auf QuoteChars reagieren möchte ? Die Datei die ich habe hat keinen Texttrenner, dafür aber mitten im Text Anführungsstriche.

Hans

nahpets 7. Jun 2017 15:04

AW: csv Datei Import ClassHelper für TClientDataSet
 
Statt dem " ein Zeichen nehmen, das nicht vorkommt?

z. B. #7

Jumpy 7. Jun 2017 15:16

AW: csv Datei Import ClassHelper für TClientDataSet
 
Oder alles mit QuoteChar zu tun hat aus dem Classhelper entfernen?

Probele können aber glaub ich auftreten, wenn du Leerzeichen in deinen Daten hast.

p80286 7. Jun 2017 15:18

AW: csv Datei Import ClassHelper für TClientDataSet
 
Den QuoteChar kannst Du ja selbst definieren, meist schließen sich ja ' und " gegenseitig aus.
Bei den SteuerZeichen STX,ETX,EOT...(x00..x1F) hab ich mich schon des Öfteren verhaspelt.
U.U wäre xFF eine gute Wahl.

Gruß
K-H

P.S.
Zitat:

Zitat von Jumpy (Beitrag 1373732)
Probele können aber glaub ich auftreten, wenn du Leerzeichen in deinen Daten hast.

Sollte eigentlich nicht der Fall sein. Der Stolperstein ist eher der Zeilenumbruch.

H.Bothur 7. Jun 2017 15:19

AW: csv Datei Import ClassHelper für TClientDataSet
 
Nicht ganz sauber - aber ich ersetzte jetzt das " durch ein ' :-)

Hans

MaBuSE 7. Jun 2017 15:54

AW: csv Datei Import ClassHelper für TClientDataSet
 
Hallo,
es freut mich, das der Code jemanden helfen konnte.
Ich hatte schon ganz vergessen, das ich das damals gemacht hatte. :stupid:
Viele Grüße
MaBuSE

H.Bothur 7. Jun 2017 20:50

AW: csv Datei Import ClassHelper für TClientDataSet
 
Ja ... der ist sehr nützlich, auch wenn ich ihn immer noch nicht ganz verstehe :-D Trotzdem .... vielen Dank !!

Hans

MaBuSE 8. Jun 2017 09:51

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von H.Bothur (Beitrag 1373777)
..., auch wenn ich ihn immer noch nicht ganz verstehe :-D ....

Die Funktionalität ist quasi Bestandteil des TStrings Objekts ;-)

Das kannst Du ganz einfach testen. Lege zwei TMemo und einen TButton auf dein TForm.

Auf den TButton hinterlegst Du auf das OnClick Ereignis folgenden Text:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo2.Lines.Add(Memo1.Lines.DelimitedText);
end;
Damit wird jeweils eine Zeile mit dem kommaseparierten Werten an Memo2 hinzugefügt.

Welche Auswirkungen die Parameter/Eigenschaften haben kann man leicht mit folgendem Quelltext testen:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
begin
  Memo1.Lines.Delimiter := Edit1.Text[1]; // Default: ,
  Memo1.Lines.QuoteChar := Edit2.Text[1]; // Default: "
  Memo1.Lines.StrictDelimiter := CheckBox1.Checked; // Default: False

  Memo2.Lines.Add(Memo1.Lines.DelimitedText);
end;
Einfach noch 2 TEdit und eine TCheckBox auf das Form legen. Und mit den Werten spielen (Testen)

Dann ist relativ schnell klar wie der Mechanismus funktioniert.

Das funktioniert natürlich auch in der anderen Richtung:
Delphi-Quellcode:
Memo1.Lines.DelimitedText := 'Eins,Zwei,Drei,"Vier","Fünf"';

Memo1 kann also eine Zeile der CSV Datenmenge darstellen (Ein Wert(Spalte) pro Zeile)

Mit einem paar Klicks auf Button 1 wird schnell klar wie man eine ganze Datei verarbeiten kann.
Man braucht nur ein 2. TStrings um die Liste abzulegen. (In unserem Beispiel Memo2.) Diese wird dann mit einem
Delphi-Quellcode:
for
zeilenweise verarbeitet.

Das ist alles.

Die ganze Funktionalität der Umwandlung von/in das CSV Format ist schon im TStrings implementiert :thumb:

Deswegen ist das so wenig Programieraufwand.

Siehe auch Doku: http://docwiki.embarcadero.com/Libra....DelimitedText

Der
Delphi-Quellcode:
class helper for TClientDataSet
ist nur eine Einfache Methode die Prozedur an das TClientDataSet zu hängen, so dass es aussieht, als ob TClientDataSet das könnte. (ohne Vererbung)
Siehe auch Doku: http://docwiki.embarcadero.com/RADSt...ecords_(Delphi)

Ich hoffe Du verstehst es nun besser.
De

H.Bothur 8. Jun 2017 16:23

AW: csv Datei Import ClassHelper für TClientDataSet
 
DANKE für die Erklärung - ich teste das morgen mal kurz aus :-)
Im Moment habe ich noch ein problemn wenn im Text ein Semikolon vorkommt - dann gibt es eine Exception. Also bei

"das ist der;eigentliche Text Feld 1";"Feld 2";"Feld 3"

da bringt das erste Semikolon reproduzierbar ne Exception.

Gruß
Hans

Jumpy 9. Jun 2017 08:25

AW: csv Datei Import ClassHelper für TClientDataSet
 
Auch mit QuoteChar="? Genau dafür ist das doch gedacht.

himitsu 9. Jun 2017 09:29

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von H.Bothur (Beitrag 1373736)
Nicht ganz sauber - aber ich ersetzte jetzt das " durch ein ' :-)

Wie wäre es mit #0 ?

Vorallem da dieses Zeichen wohl niemals in einer CSV vorkommen sollte. (und C-Programme / PChar kommen auch nicht mit #0 klar, drum kommt es so selten in Texten vor, da es ja ENDE bedeutet)
0 heißt eh so viel wie Nichts.


Und "ersetzen" bedeutet jetzt doch hoffentlich auch "ich übergebe es beim Aufruf" und nicht "ich ändere den Code des Helpers" ?

MaBuSE 9. Jun 2017 09:35

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von H.Bothur (Beitrag 1373895)
Im Moment habe ich noch ein problemn wenn im Text ein Semikolon vorkommt - dann gibt es eine Exception. Also bei

"das ist der;eigentliche Text Feld 1";"Feld 2";"Feld 3"

da bringt das erste Semikolon reproduzierbar ne Exception.

Um das Problem analysieren zu können benötige ich mehr Informationen:
  • Welche Exception?
  • An welcher Stelle tritt die auf?
  • Stack?
Ich hab das mal kurz angetestet:
Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
var
  sl : TStringList;
begin
  sl := TStringList.Create;
  sl.QuoteChar := '"';
  sl.Delimiter := ';';
  sl.DelimitedText := '"das ist der;eigentliche Text Feld 1";"Feld 2";"Feld 3"';
  Caption := sl[0];
end;
Das bringt keine Exception. Es wird
Delphi-Quellcode:
das ist der;eigentliche Text Feld 1
in das Caption des Formulars gesetzt. Und das ist richtig.
Es liegt also nicht an dem CSV Support der TStrings Klasse.

H.Bothur 9. Jun 2017 11:22

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von himitsu (Beitrag 1373968)
Zitat:

Zitat von H.Bothur (Beitrag 1373736)
Nicht ganz sauber - aber ich ersetzte jetzt das " durch ein ' :-)

Und "ersetzen" bedeutet jetzt doch hoffentlich auch "ich übergebe es beim Aufruf" und nicht "ich ändere den Code des Helpers" ?

Nein - viel besser - ich habs in der Kundendatenbank ersetzt, also in der CSV-Datei :-)
Was aber natürlich nicht heißt das irgend jemand wieder ein " in den nächsten Kundensatz einträgt.

Hans

H.Bothur 9. Jun 2017 11:36

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von MaBuSE (Beitrag 1373969)
Zitat:

Zitat von H.Bothur (Beitrag 1373895)
Im Moment habe ich noch ein problemn wenn im Text ein Semikolon vorkommt - dann gibt es eine Exception. Also bei

"das ist der;eigentliche Text Feld 1";"Feld 2";"Feld 3"

da bringt das erste Semikolon reproduzierbar ne Exception.

Um das Problem analysieren zu können benötige ich mehr Informationen:
  • Welche Exception?
  • An welcher Stelle tritt die auf?
  • Stack?

Das passiert an der Stelle Fields[j].AsString := slRow[j];

Delphi-Quellcode:
    DisableControls;
    for i := 0 to slFile.Count - 1 do
    begin
      slFile[i] := StringReplace(slFile[i], '"', '''', [rfReplaceAll, rfIgnoreCase]);
      slRow.DelimitedText := slFile[i];
      Append;
      for j := 0 to slRow.Count -1 do
      begin
        Fields[j].AsString := slRow[j];
      end;
      Post;
    end;
    EnableControls;
  finally
    slFile.Free;
    slRow.Free;
  end;
---------------------------
Benachrichtigung über Debugger-Exception
---------------------------
Im Projekt Mahnung.exe ist eine Exception der Klasse $C0000005 mit der Meldung 'access violation at 0x00684abe: read of address 0x00000000' aufgetreten.
---------------------------
Anhalten Fortsetzen Hilfe
---------------------------

slRow[j] hat zu dem Zeitpunkt folgenden Inhalt:

"12345";"1";"Müller-Meyer";"Müller-Meyer-Schulze GmbH";"";"Elbchaussee 1";"D";"22000";"Hamburg";"";"Frau";"Michaela";"Mül ler";"+49 (40) 123456-0";"+49 (40) 123456 111";"m.mueller@mueller-meyer.de";"1";"36";"rechnung@mueller-meyer.de; einkauf@mueller-meyer.de"

Gruß

Hans

H.Bothur 9. Jun 2017 12:01

AW: csv Datei Import ClassHelper für TClientDataSet
 
Komischerweise ... mit diesem Source:

Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1Fuellen;
  Memo2.Lines.Delimiter := ';';
  Memo2.Lines.QuoteChar := '"';
  Memo2.Lines.StrictDelimiter := false;
  Memo2.Lines.DelimitedText := Memo1.Lines[i];
end;
und dieser CSV klappt es:

"Test 5 - Hochkomma, ein Semikolon im Text ";"das ist der;eigentliche Text Feld 2";"Feld 3";"Feld 4"

Hans *verwirrt*

nahpets 9. Jun 2017 12:13

AW: csv Datei Import ClassHelper für TClientDataSet
 
Was verwirrt daran?

; trennt die einzelnen Werte.

Die Werte werden mit " eingerahmt.

; sind damit, wenn sie sich im mit " eingerahmten Text befinden, keine Trenner mehr.

Die "verwirrenden" Variante entspricht dem definierten und sowohl erwünschten, wie auch dem erwarteten Verhalten.

Wenn man " in 'ner CSV hat und ; als Trenner, dann sollte man nicht die " aus der CSV entfernen und hoffen, dass im Text keine ; vorkommen. Sollte dem dann doch so sein, kann das nur scheitern.

MaBuSE 9. Jun 2017 12:32

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von H.Bothur (Beitrag 1373986)
Das passiert an der Stelle Fields[j].AsString := slRow[j];

Delphi-Quellcode:
    DisableControls;
    for i := 0 to slFile.Count - 1 do
    begin
      slFile[i] := StringReplace(slFile[i], '"', '''', [rfReplaceAll, rfIgnoreCase]);
      slRow.DelimitedText := slFile[i];
      Append;
      for j := 0 to slRow.Count -1 do
      begin
        Fields[j].AsString := slRow[j];
      end;
      Post;
    end;
    EnableControls;
  finally
    slFile.Free;
    slRow.Free;
  end;
---------------------------
Benachrichtigung über Debugger-Exception
---------------------------
Im Projekt Mahnung.exe ist eine Exception der Klasse $C0000005 mit der Meldung 'access violation at 0x00684abe: read of address 0x00000000' aufgetreten.
---------------------------
Anhalten Fortsetzen Hilfe
---------------------------

slRow[j] hat zu dem Zeitpunkt folgenden Inhalt:

"12345";"1";"Müller-Meyer";"Müller-Meyer-Schulze GmbH";"";"Elbchaussee 1";"D";"22000";"Hamburg";"";"Frau";"Michaela";"Mül ler";"+49 (40) 123456-0";"+49 (40) 123456 111";"m.mueller@mueller-meyer.de";"1";"36";"rechnung@mueller-meyer.de; einkauf@mueller-meyer.de"

Die Fehlermeldung deutet auf einen Zugriff auf ein nicht erzeugtes Objekt.

Könnte es sein, das in der 1. Zeile weniger Felder definiert sind als in dieser Zeile.
Der Helper legt er "nur" die Anzahl der Felder an, die in der 1. Zeile stehen.

Das sollte ja auch nur als Beispiel dienen. ;-)

Welche Wert hat
Delphi-Quellcode:
slRow.Count
bei i=0 ?
Und welchen Wert hat es in der Zeile, die den Fehler wirft?

MaBuSE 9. Jun 2017 12:35

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von nahpets (Beitrag 1373988)
Was verwirrt daran?

Wenn Du den ganzen Beitrag gelesen hättest wüsstest Du was ihn verwirrt. :thumb: :stupid:

nahpets 9. Jun 2017 12:50

AW: csv Datei Import ClassHelper für TClientDataSet
 
Was ihn verwirrt ist doch ganz einfach:

Wenn ich die QuotedChar entferne und dann durch das ; im Text ein Feld mehr bekomme, als in der ersten Zeile, dann muss das krachen.

Wenn ich 'ne CSV mit QuotedChar habe, dann sollte ich das auch entsprechend verwenden.
Ein zusätzliches ; zwischen den QuotedChars führt dann nicht zu 'ner abweichenden Feldzahl in der entsprechenden Zeile und damit auch nicht zu der Exception.

Zitat:

Zitat von MaBuSE
Wenn Du den ganzen Beitrag gelesen hättest wüsstest Du was ihn verwirrt.

Habe den ganzen Beitrag gelesen und deshalb beschrieben, wo die Verwirrung herkommt und dass sie, bei korrekter Verwendung von QuotedChar und Delimiter in CSV-Dateien, nicht aufkommen würde.

Oder anders:

Wenn man aus der CSV die " entfernt, dann führt das ; in diesem Text rechnung@mueller-meyer.de; einkauf@mueller-meyer.de zu der Exception, es ist dann der Trenner, der zuviel ist und den Zugriff auf ein nicht erzeugtes Objekt verursacht.

MaBuSE 9. Jun 2017 12:52

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von nahpets (Beitrag 1373992)
Was ihn verwirrt ist doch ganz einfach:

Wenn ich die QuotedChar entferne und dann durch das ; im Text ein Feld mehr bekomme, als in der ersten Zeile, dann muss das krachen.

Was ihn verwirrt, ist das es kracht, obwohl die QuotedChar richtig gesetzt ist !!!

nahpets 9. Jun 2017 13:01

AW: csv Datei Import ClassHelper für TClientDataSet
 
Wieso,

in Post #20 wird es richtig gemacht und funktioniert.

Und darunter steht, dass es ihn verwirrt.

Wenn ich das richtigt interpretiere, verwirrt ihn nicht die fehlerhafte Variante, sondern die korrekte. ;-)

MaBuSE 9. Jun 2017 13:08

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von nahpets (Beitrag 1373994)
Wieso,
in Post #20 wird es richtig gemacht und funktioniert.
Und darunter steht, dass es ihn verwirrt.
Wenn ich das richtigt interpretiere, verwirrt ihn nicht die fehlerhafte Variante, sondern die korrekte. ;-)

Ich vermute es verwirrt ihn, das der selbe Datensatz im Helper einen Fehler wirft und im Beispiel funktioniert.
Aber es ist völlig egal. Wir können nur vermuten. Er wird es uns schreiben.
Er möchte wahrscheinlich "nur" die Allgemeine Schmutzverletzung weg haben :)

In Post #19 wird es auch richtig gemacht und funktioniert nicht.
Es wird 2 mal richtig gemacht. Einmal geht es und einmal nicht.

himitsu 9. Jun 2017 13:12

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von nahpets (Beitrag 1373988)
Die "verwirrenden" Variante entspricht dem definierten und sowohl erwünschten, wie auch dem erwarteten Verhalten.

Dank dem
Delphi-Quellcode:
StrictDelimiter:=False
(Antwort #20) gibt es noch mehr Verwirrungen, da dort nicht nur der definierte Delimiter als Trennzeichen verwendet wird. :stupid:

H.Bothur 9. Jun 2017 13:30

AW: csv Datei Import ClassHelper für TClientDataSet
 
Hi,

Zitat:

Zitat von MaBuSE (Beitrag 1373990)
Könnte es sein, das in der 1. Zeile weniger Felder definiert sind als in dieser Zeile. Der Helper legt er "nur" die Anzahl der Felder an, die in der 1. Zeile stehen.

Nein - jedenfalls nicht wenn ich die Datei mit Excel öffne - dann passt das.

Zitat:

Zitat von MaBuSE (Beitrag 1373990)
Welche Wert hat
Delphi-Quellcode:
slRow.Count
bei i=0 ?
Und welchen Wert hat es in der Zeile, die den Fehler wirft?

bei Beginn: slRow.Count = 19
bei Fehler: slRow.Count = 20

Also interpretiert Delphi dieset "rechnung@mueller-meyer.de; einkauf@mueller-meyer.de" als zwei Felder OBWOHL diese von Anführungszeichen eingeschlossen sind. Wenn ich die zweite Email-Adresse weiter nach vorne setze ... also so:

"12345";"1";"Müller-Meyer";"Müller-Meyer-Schulze GmbH";"";"Elbchaussee 1";"D";"22000";"Hamburg";"";"Frau";"Michaela";"Mül ler";"+49 (40) 123456-0";"+49 (40) 123456 111";"m.mueller@mueller-meyer.de; einkauf@mueller-meyer.de";"1";"36";"...eller-meyer.de"

dann knallt es genauso - und wieder bei Feld 20 :-) In sofern hattest Du schon recht das Feld Nr. 20 nicht existiert. Aber warum ??

Hans

H.Bothur 9. Jun 2017 13:34

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von himitsu (Beitrag 1374001)
Zitat:

Zitat von nahpets (Beitrag 1373988)
Die "verwirrenden" Variante entspricht dem definierten und sowohl erwünschten, wie auch dem erwarteten Verhalten.

Dank dem
Delphi-Quellcode:
StrictDelimiter:=False
(Antwort #20) gibt es noch mehr Verwirrungen, da dort nicht nur der definierte Delimiter als Trennzeichen verwendet wird. :stupid:

Das ist egal .. sowohl bei
Delphi-Quellcode:
StrictDelimiter:=False
als auch bei ":=True" knallt es :-(

Hans

MaBuSE 9. Jun 2017 13:36

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von himitsu (Beitrag 1374001)
Zitat:

Zitat von nahpets (Beitrag 1373988)
Die "verwirrenden" Variante entspricht dem definierten und sowohl erwünschten, wie auch dem erwarteten Verhalten.

Dank dem
Delphi-Quellcode:
StrictDelimiter:=False
(Antwort #20) gibt es noch mehr Verwirrungen, da dort nicht nur der definierte Delimiter als Trennzeichen verwendet wird. :stupid:

Was aber in diesem Beispiel keinen Unterschied macht. :-D

H.Bothur 9. Jun 2017 13:49

AW: csv Datei Import ClassHelper für TClientDataSet
 
Wenn ich jemandem mal eine Testdatei geben soll .... müsste ich dann nur schnell umarbeiten weil im Moment sind das echte Kundendaten - dann mache ich das gerne :-)

Hans

nahpets 9. Jun 2017 13:52

AW: csv Datei Import ClassHelper für TClientDataSet
 
Ich verstehe den Sinn dieser Zeile nicht:
Delphi-Quellcode:
slFile[i] := StringReplace(slFile[i], '"', '''', [rfReplaceAll, rfIgnoreCase]);
QuotedChar in der CSV ist das ".

Warum das " durch das ' ersetzen?

Hast Du denn dann auch QuotedChar auf ' gesetzt?

Wenn nein, dann ist das (zusammen mit StrictDelimiter:=False) wie russisch Roulette.

Irgend wann geht der Schuss los, aber man weiß nicht, wen es wann trifft.

Blackpit 16. Mai 2019 20:09

AW: csv Datei Import ClassHelper für TClientDataSet
 
Hi Folks,
hier eine Implemtierung für LF in Quoted-Fields:

Code:

// v1.1 - 26.02.2019 - Blackpit: Anpassung LF in Quotes
/// /////////////////////////////////////////////////////////////////////////////
procedure TClientDataSetCsvClassHelper.LoadFromFile;
var
  myString :Variant;
  slFile :TStringList;
  slRow :TStringList;
  x :Integer;
  i :Integer;
  j :Integer;
  myPos :Integer;
  myFields :Integer;
  cntFields :Integer;
begin
  slFile := TStringList.Create;
  slRow := TStringList.Create;
  try
    myPos := 0;
    slRow.Delimiter := Delimiter;
    slRow.QuoteChar := QuoteChar;
    slRow.StrictDelimiter := True;
    slFile.StrictDelimiter := True;
    slFile.LoadFromFile( Filename );

      // ClientDataset Initialisieren
    if slFile.Count > 0 then
    begin
      Active := False;
      for x := 0 to slFile.Count - 1 do
      begin
        myString := '';
        if (x = 0) then
        begin
          myPos := x;
            // Build Header for DS
          slRow.DelimitedText := slFile[0];
          if FirstLineTitle then myFields := slRow.Count;
          FieldDefs.Clear;
          for i := 0 to myFields - 1 do
          begin
            if FirstLineTitle and ( slRow[i] <> '' ) then
            begin
              FieldDefs.Add( slRow[i], ftWideString, StringLength );
            end
            else
            begin
              FieldDefs.Add( Format( 'Field%d',[i]), ftWideString, StringLength );
            end;
          end;
          CreateDataSet;
          Active := True;
          myString := slFile[myPos];
        end;
        begin
        if (x=0) and FirstLineTitle then inc( myPos );
          if myPos = slFile.Count then
            break;
            myString := slFile[myPos];
            cntFields := length( myString )-
              length( stringreplace( myString, Delimiter,'',[rfreplaceall, rfIgnoreCase]));
          while cntFields < (myFields-1) do
            begin
              inc( myPos );
              myString := myString+slFile[myPos];
              cntFields := length( myString )-
                length( stringreplace( myString, Delimiter,'',[rfreplaceall, rfIgnoreCase]));
            end;
        end;
          // fill TClientDataset
        DisableControls;
        if (myPos > 0) then
        begin
          slRow.DelimitedText := myString;
          Append;
          for j := 0 to slRow.Count - 1 do
          begin
            Fields[j].AsString := slRow[j];
          end;
          Post;
        end;
        EnableControls;
        inc( myPos );
      end;
    end;
  finally
    slFile.Free;
    slRow.Free;
  end;
end;
Über Feedback würde ich mich freuen.
HTH somebody ;)

Rollo62 17. Mai 2019 10:14

AW: csv Datei Import ClassHelper für TClientDataSet
 
Sieht auf den ersten Blick OK aus.

Ich sehe das Du immer einen Header in der CSV-Datei erwartest.
Da würde ich eine Option machen, so das auch "Headerlose" CSV erzeugt werden können,
z.B. mit Dummynamen "Field1", "Field2"

MaBuSE 17. Mai 2019 17:03

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von Blackpit (Beitrag 1432368)
Hi Folks,
hier eine Implemtierung für LF in Quoted-Fields:
...
Über Feedback würde ich mich freuen.
HTH somebody ;)

Hallo,
ich glaube Deine Lösung würde fehlschlagen, wenn in der 1. Zeile schon ein CRLF in einem Feld enthalten ist.
Wenn ich das beim Lesen richtig verstanden habe entfernst Du einfach die CRLF
Delphi-Quellcode:
 myString := myString + slFile[myPos];
. Sie sind dann auch beim Speichern nicht mehr enthalten.

Ich würde noch einen anderen Ansatz vorstellen.
Man könnte die CRLF in den Quoted Fields auch per Regular Expression identifizieren und dann durch ein String ersetzen. Beim Zuweisen der Felder würde man das dann wieder rückgängig machen. Damit sind die CRLF auch in den Datenbank Feldern enthalten.

Damit würde das Beispiel eines ClassHelpers und der einfachen Konvertierung von CSV Dateien via TStringList um ein Beispiel zur Verwendung von System.RegularExpressions und die Verwendung eines Records zum "Erzeugen einer Methode" erweitert ;)

Ich hab mal schnell so was zum Maskieren der CRLF geschrieben. In diesem Beispiel wird das " verwendet. Das kann man natürlich auch noch variabel gestalten.
Der String
Delphi-Quellcode:
'"[^"]*\n[^"]*"'
müsste dann entsprechende angepasst werden.
Bei Param
Delphi-Quellcode:
const QuoteChar: Char = '"'
wäre das dann so was wie
Delphi-Quellcode:
QuoteChar+'[^'+QuoteChar+']*\n[^'+QuoteChar+']*'+QuoteChar
oder
Delphi-Quellcode:
Format('%s[^%0:s]*\n[^%0:s]*%0:s', [QuoteChar])
.

Anmerkung zu RegEx. Der verwendete Ausdruck
Delphi-Quellcode:
'"[^"]*\n[^"]*"'
wurde von mir verwendet, da
Delphi-Quellcode:
'".*\n.*"'
nicht funktioniert, da das * greedy ist. (Siehe https://www.regular-expressions.info/repeat.html )
Anmerkung zum Replace: Ich habe das an das Beispiel aus der Dokumentation angelehnt (Siehe http://docwiki.embarcadero.com/CodeE...place_(Delphi) )

Mit MaskCRLF werden alle CRLF in "" durch ^P ersetzt.
Mit UnMaskCRLF wird das wieder zu CRLF.

In LoadFromFile müssten nur 2 Zeilen ergänzt werden.
SaveToFile müsste natürlich auch entsprechend geändert werden.
Delphi-Quellcode:
...
uses Unit2; // Die Unit einbinden.
...
    // TClientDataset füllen
    DisableControls;
    slFile.Text := MaskCRLF(slFile.Text); // <----- Diese Zeile ist neu ;)
    for i := 0 to slFile.Count - 1 do
    begin
      slRow.DelimitedText := slFile[i];
      Append;
      for j := 0 to slRow.Count - 1 do
      begin
//        Fields[j].AsString := slRow[j];
        Fields[j].AsString := DeMaskCRLF(slRow[j]); // <----- Diese Zeile wurde geändert ;)
      end;
      Post;
    end;
    EnableControls;
...
Hier ist die verwendete Unit2.pas:
(Ich hab sie noch nicht gespeichert, deshalb hab ich mir auch noch keinen Gedanken über den Unit-Namen gemacht ;)

Delphi-Quellcode:
unit Unit2;

interface

function MaskCRLF(const Source: string; Mask: string = '^P'): string;
function DeMaskCRLF(const Source: string; Mask: string = '^P'): string;

implementation

uses
  System.SysUtils, System.RegularExpressions;

type
  TmyReplaceRec = record
    Mask: string;
    function MyReplace(const Match: TMatch): string;
  end;

function TmyReplaceRec.MyReplace(const Match: TMatch): string;
begin
  Result := System.SysUtils.StringReplace(Match.Value, #13#10, Mask, [rfReplaceAll]);
end;

function MaskCRLF(const Source: string; Mask: string): string;
var mr: TmyReplaceRec;
begin
  mr.Mask := Mask;
  Result := TRegEx.Replace(Source, '"[^"]*\n[^"]*"', mr.MyReplace, [roSingleLine]);
end;

function DeMaskCRLF(const Source: string; Mask: string): string;
begin
  Result := System.SysUtils.StringReplace(Source, Mask, #13#10, [rfReplaceAll]);
end;

end.

Schokohase 17. Mai 2019 18:15

AW: csv Datei Import ClassHelper für TClientDataSet
 
Ihr macht da viel zu viel Brimborium drum.

Hier mal drei Zeilen CSV
Code:
"a","b","c"
"A
a","B
b","C
c"
a,b,c
Die mittlere hat Felder mit Zeilenumbrüchen.

Jetzt die Theorie:
Die Datenzeile ist komplett, wenn die Anzahl der Quote-Zeichen gerade ist.
Also liest man Zeile für Zeile ein, bis man eine gerade Anzahl an Quote-Zeichen hat und gibt diese dann der
Delphi-Quellcode:
TStringList
Instanz zum auseinandernehmen.

Die erste Zeile hat gleich 6 Quote-Zeichen - ist also komplett.

Die nächste Zeile hat 1 Quote-Zeichen - ist also nicht komplett.
Mit der nächsten Zeile hat man 3 Quote-Zeichen - ist also nicht komplett.
Mit der nächsten Zeile hat man 5 Quote-Zeichen - ist also nicht komplett.
Mit der nächsten Zeile hat man 6 Quote-Zeichen - ist also komplett.

Die letzte Zeile hat 0 Quote-Zeichen - ist also komplett.

KISS

Blackpit 19. Mai 2019 07:54

AW: csv Datei Import ClassHelper für TClientDataSet
 
@Rollo62
könnte sein das du zu schnell drauf geschaut hast ;)

@MaBuSE
Um CrLf ging es mir gar nicht. Ich hatte Probleme mit Dateien die auch Linefeed als Satztrenner haben.
Bei Dateien mit CrLf als Satztrenner hätte vmtl. TStringList kein Problem.
Deinen Vorschlag mit RegEx werde ich mal testen, wobei es mir gar nicht um den erhalt ging.

@Schokohase
In der Art habe ich es gemacht, jedoch mit den Delimitern.

Schokohase 19. Mai 2019 10:04

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von Blackpit (Beitrag 1432527)
@Schokohase
In der Art habe ich es gemacht, jedoch mit den Delimitern.

Nein, hast du nicht.

Du hast damit aber einen bösen Bug in deinem Code.

Diese Zeile mit 3 Feldern
Code:
a,"b,b",c
und dein Code
Delphi-Quellcode:
cntFields := length( myString )-
              length( stringreplace( myString, Delimiter,'',[rfreplaceall, rfIgnoreCase]));
liefert 3 zurück, obwohl du hier eine 2 benötigen würdest, denn es befinden sich in der Zeile 2 Feldtrenner. Das andere Komma gehört den Daten.
Delphi-Quellcode:
StringReplace
kann das aber nicht unterscheiden.

Du zählst also die Feldtrenner, was aber wie gezeigt fehleranfällig ist.

Mein Vorschlag ist es die Quote-Zeichen zu zählen, was immer passt.

Rollo62 19. Mai 2019 11:40

AW: csv Datei Import ClassHelper für TClientDataSet
 
Zitat:

Zitat von Blackpit (Beitrag 1432527)
@Rollo62
könnte sein das du zu schnell drauf geschaut hast

Mir gings um den Header ...
Und Du wolltest ein paar Feedbacks bekommen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:36 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz