![]() |
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:
Das würde folgenden Feldern entsprechen:
Test;"Test Test";"Test "" Test";"Test ; Test";"Test "";"" Test"
Code:
Im Netz habe ich nur relativ komplizierte "Lösungen" gefunden, die das mit den Delimiter und QuoteChar richtig machen.
Spalte 1: Test
Spalte 2: Test Test Spalte 3: Test " Test Spalte 4: Test ; Test Spalte 5: Test ";" Test 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:
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.
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Geht das auch mit einer CSV-Datei der Form
Code:
Daraus muss ein Record mit drei Feldern werden.
Test;"Zeile 1 von Test
Zeile 2 von Test";Test |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Aber was heißt schon korrekt. Ein verbindliches Format für CSV gibt es nicht (nur ![]() |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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:
Das ist ein gutes Beispiel für KISS (keep it stupid simple).
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; Ich hoffe diese Info hilft Dir. |
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:
Das ist ein einfaches Beispiel für Class Helper und csv Import.
...
uses StringGridCsvClassHelper; ... procedure TForm1.Button1Click(Sender: TObject); begin StringGrid1.LoadFromFile('Dateiname.csv'); end; ... 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. |
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:
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.
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; Hans |
AW: csv Datei Import ClassHelper für TClientDataSet
Statt dem " ein Zeichen nehmen, das nicht vorkommt?
z. B. #7 |
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. |
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:
|
AW: csv Datei Import ClassHelper für TClientDataSet
Nicht ganz sauber - aber ich ersetzte jetzt das " durch ein ' :-)
Hans |
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 |
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 |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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:
Damit wird jeweils eine Zeile mit dem kommaseparierten Werten an Memo2 hinzugefügt.
procedure TForm1.Button1Click(Sender: TObject);
begin Memo2.Lines.Add(Memo1.Lines.DelimitedText); end; Welche Auswirkungen die Parameter/Eigenschaften haben kann man leicht mit folgendem Quelltext testen:
Delphi-Quellcode:
Einfach noch 2 TEdit und eine TCheckBox auf das Form legen. Und mit den Werten spielen (Testen)
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; 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:
zeilenweise verarbeitet.
for
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: ![]() Der
Delphi-Quellcode:
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)
class helper for TClientDataSet
Siehe auch Doku: ![]() Ich hoffe Du verstehst es nun besser. De |
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 |
AW: csv Datei Import ClassHelper für TClientDataSet
Auch mit QuoteChar="? Genau dafür ist das doch gedacht.
|
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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" ? |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Delphi-Quellcode:
Das bringt keine Exception. Es wird
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;
Delphi-Quellcode:
in das Caption des Formulars gesetzt. Und das ist richtig.
das ist der;eigentliche Text Feld 1
Es liegt also nicht an dem CSV Support der TStrings Klasse. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Was aber natürlich nicht heißt das irgend jemand wieder ein " in den nächsten Kundensatz einträgt. Hans |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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 |
AW: csv Datei Import ClassHelper für TClientDataSet
Komischerweise ... mit diesem Source:
Delphi-Quellcode:
und dieser CSV klappt es:
procedure TForm1.FormCreate(Sender: TObject);
begin Memo1Fuellen; Memo2.Lines.Delimiter := ';'; Memo2.Lines.QuoteChar := '"'; Memo2.Lines.StrictDelimiter := false; Memo2.Lines.DelimitedText := Memo1.Lines[i]; end; "Test 5 - Hochkomma, ein Semikolon im Text ";"das ist der;eigentliche Text Feld 2";"Feld 3";"Feld 4" Hans *verwirrt* |
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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:
bei i=0 ?
slRow.Count
Und welchen Wert hat es in der Zeile, die den Fehler wirft? |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
|
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:
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
|
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. ;-) |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Delphi-Quellcode:
(Antwort #20) gibt es noch mehr Verwirrungen, da dort nicht nur der definierte Delimiter als Trennzeichen verwendet wird. :stupid:
StrictDelimiter:=False
|
AW: csv Datei Import ClassHelper für TClientDataSet
Hi,
Zitat:
Zitat:
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 |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Delphi-Quellcode:
als auch bei ":=True" knallt es :-(
StrictDelimiter:=False
Hans |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
|
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 |
AW: csv Datei Import ClassHelper für TClientDataSet
Ich verstehe den Sinn dieser Zeile nicht:
Delphi-Quellcode:
QuotedChar in der CSV ist das ".
slFile[i] := StringReplace(slFile[i], '"', '''', [rfReplaceAll, rfIgnoreCase]);
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Hi Folks,
hier eine Implemtierung für LF in Quoted-Fields:
Code:
Über Feedback würde ich mich freuen.// 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; HTH somebody ;) |
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" |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
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:
. Sie sind dann auch beim Speichern nicht mehr enthalten.
myString := myString + slFile[myPos];
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:
müsste dann entsprechende angepasst werden.
'"[^"]*\n[^"]*"'
Bei Param
Delphi-Quellcode:
wäre das dann so was wie
const QuoteChar: Char = '"'
Delphi-Quellcode:
oder
QuoteChar+'[^'+QuoteChar+']*\n[^'+QuoteChar+']*'+QuoteChar
Delphi-Quellcode:
.
Format('%s[^%0:s]*\n[^%0:s]*%0:s', [QuoteChar])
Anmerkung zu RegEx. Der verwendete Ausdruck
Delphi-Quellcode:
wurde von mir verwendet, da
'"[^"]*\n[^"]*"'
Delphi-Quellcode:
nicht funktioniert, da das * greedy ist. (Siehe
'".*\n.*"'
![]() Anmerkung zum Replace: Ich habe das an das Beispiel aus der Dokumentation angelehnt (Siehe ![]() 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:
Hier ist die verwendete Unit2.pas:
...
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; ... (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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Ihr macht da viel zu viel Brimborium drum.
Hier mal drei Zeilen CSV
Code:
Die mittlere hat Felder mit Zeilenumbrüchen.
"a","b","c"
"A a","B b","C c" a,b,c 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:
Instanz zum auseinandernehmen.
TStringList
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 |
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Du hast damit aber einen bösen Bug in deinem Code. Diese Zeile mit 3 Feldern
Code:
und dein Code
a,"b,b",c
Delphi-Quellcode:
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.
cntFields := length( myString )-
length( stringreplace( myString, Delimiter,'',[rfreplaceall, rfIgnoreCase]));
Delphi-Quellcode:
kann das aber nicht unterscheiden.
StringReplace
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. |
AW: csv Datei Import ClassHelper für TClientDataSet
Zitat:
Und Du wolltest ein paar Feedbacks bekommen. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:49 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz