Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   GUI-Design mit VCL / FireMonkey / Common Controls (https://www.delphipraxis.net/18-gui-design-mit-vcl-firemonkey-common-controls/)
-   -   Delphi CSV-Datei in Stringgrid bringen (https://www.delphipraxis.net/135066-csv-datei-stringgrid-bringen.html)

peschai 4. Jun 2009 06:38


CSV-Datei in Stringgrid bringen
 
Hallo Delphianer,
CSV-Datei in Stringgrid bringen mit Feldern welche auch einen Zeilenwechsel beinhalten

Es gibt viele Lösungsansätze im Internet oder hier um das zu tun, aber überall fehlt mir ein Wichtigkeit, welche ich entweder in den Lösungen übersehen habe oder ...?
Es geht um Feldinhalte mit Zeilenumbrüche. Also nicht der Zeileumbruch am Ende einer CSVZeile, sondern um mögliche Zeileumbrüche innerhalb eines Feldes.
Das ist im RFC für die CSV aussdrücklich berückssichtigt, also möglich. Excel kann das.
Fast alle berücksichtigen nur die Sonderverarbeitung von Delimiter, Quote aber vernachlässigen mögliche Zeilumbrüche in Feldern selbst.

Wie mache ich das aber elegent in Delphi ?
Wie bringe ich folgende CSV-Datei in ein Stringgrid (4Spalten,7Zeilen, 2Zellen mit Zeileumbruch)?
Hinweis: Daß ich im Stringgrid den Zeilewechsel nicht sehe ist nicht relevant.

Inhalt der Test.csv:

SpalteA;SpalteB;"SpalteC";"SpalteD"
1A;1B;1C;1D
"2A";2B;"2C";"2D"
;;;
4A;"4BMitUmbruch
4BVonNeuerZeile";"4CMitSeparatorUndQuote;""";4D
5A;5B;5C;"5DMitUmbruchInLetzerSpalte
5DVonNächsterZeile"
6A;6B;6C;6D

alzaimar 4. Jun 2009 06:52

Re: CSV-Datei in Stringgrid bringen
 
Hallo,

Das hier könnte funktionieren. Ich habe es nicht getestet, aber laut Code könnte es klappen.

Chemiker 4. Jun 2009 08:59

Re: CSV-Datei in Stringgrid bringen
 
Hallo peschai,

könntest Du die besagte CSV-Datei mal als Anhang dranhängen?

Bis bald Chemiker

p80286 4. Jun 2009 09:50

Re: CSV-Datei in Stringgrid bringen
 
[OT]
hieß so etwas nicht mal ASCII delimited?
und das mit dem "Comma" ist ja auch so eine Sache
[/OT]
Gruß
K-H

mkinzler 4. Jun 2009 09:56

Re: CSV-Datei in Stringgrid bringen
 
[OT]
Zitat:

Zitat von p80286
[OT]
hieß so etwas nicht mal ASCII delimited?
und das mit dem "Comma" ist ja auch so eine Sache
[/OT]
Gruß
K-H

CSV = Comma Separated Values; da im englischen sprachraum ein Komma statt dem Semikolon verwendet wird
[/OT]

peschai 26. Okt 2009 22:31

Re: CSV-Datei in Stringgrid bringen
 
Hallo Delphianer,

zuersteinmal Danke für die Antworten und dann eine Entschuldigung für das späte reagieren nun...
... aber dafür beantworte ich meine Frage nun selbst und villeicht hilft es ja jemand anders.

Frage war:
Wie kann ich eine CSV-Datei mit Zeilenumbrüchen innerhalb von Zellen richtig einladen ?

Lösung wie folgt:
Der Trick ist, daß bei einer CSVZeile mit einem Zeilenumbruch innerhalb einer Zelle die Anzahl der Quotes ungerade ist.
Nachdem ich diese Einfachheit erkannt hatte, war die Lösung einfach:
CSV Datei in Stringgrid einladen und dann Zeilenweise durchgehen. Addiere die nächste Zeile solange die Anzahl der Quotes ungerade ist.
Ciao...

Lannes 26. Okt 2009 23:23

Re: CSV-Datei in Stringgrid bringen
 
Hallo,

Zitat:

Zitat von peschai
Das ist im RFC für die CSV aussdrücklich berückssichtigt, also möglich. Excel kann das.

das halte ich aber für ein Gerücht :roll: Excel kann sie ausgeben aber nicht richtig einlesen(bei neueren Excel-Versionen > 2000 kA).

Nur die Quotes zu überprüfen reicht nicht:
Code:
Hallo;1;"dad
sdfsfaf";"aa"""
Hallo;1;"dad
sdfsfaf""";aa
Wieviel Zeilen sind das?

alzaimar 27. Okt 2009 07:25

Re: CSV-Datei in Stringgrid bringen
 
Zitat:

Zitat von peschai
Der Trick ist, ...

sich Post #2 zu Herzen zu nehmen, kurz drüberschauen und einfach auskodieren;
Delphi-Quellcode:
Uses csCSV, Math; // Math nur wegen 'Max'
var
  csvReader: TCSVReader;
  data: TStringStream;
  sl: TStringList;
  i, r: Integer;

begin
  StringGrid1.RowCount := 1;
  StringGrid1.ColCount := 1;
  sl := TStringlist.Create; // Für das Beispiel
  try
// Füllen mit Test-CSV
    sl.Add('SpalteA;SpalteB;"SpalteC";"SpalteD"');
    sl.Add('1A;1B;1C;1D');
    sl.Add('"2A";2B;"2C";"2D"');
    sl.Add(';;;');
    sl.Add('4A;"4BMitUmbruch');
    sl.Add('4BVonNeuerZeile";"4CMitSeparatorUndQuote;""";4D');
    sl.Add('5A;5B;5C;"5DMitUmbruchInLetzerSpalte');
    sl.Add('5DVonNächsterZeile"');
    sl.Add('6A;6B;6C;6D');
    data := TStringStream.Create(sl.Text); // der TCSVReader erwartet einen InputStream
    csvReader := TCSVReader.Create(data);
    try
      csvReader.Delimiter := ';';
      csvReader.Quote := '"';
      csvReader.EOLChar := #13; // In unserer 'Datei' werden die Zeilen durch #13#10 getrennt
      csvReader.EOLLength := 2; // #13 trennt also, aber die EOL-Länge ist 2
      csvReader.First;
      while not csvReader.Eof do begin
// Die CSV-Zeilen können unterschiedlich viele Spalten beinhalten
        StringGrid1.ColCount := Max(stringGrid1.ColCount, csvReader.ColumnCount + 1);
// Eine Hilfsvariable (nicht Refactoring-konform, aber etwas lesbarer)
        r := StringGrid1.RowCount - 1;
// Spaltenbezeichnung '1','2' usw. gilt aber nicht für die Überschrift
        if r > 0 then
          StringGrid1.Cells[0, r] := IntToStr(r);
// Einlesen der Spalten
        for I := 0 to csvReader.ColumnCount - 1 do
          StringGrid1.Cells[i + 1, r] := csvReader.Columns[i];
// Nächste CSV-Zeile einlesen
        csvReader.Next;
// Nur, wenn wir noch nocht am Ende sind, eine Zeile hinzuzählen
        if not csvReader.Eof then
          StringGrid1.RowCount := StringGrid1.RowCount + 1;
      end;
// Zum Schluss wieder eine Zeile abziehen (komisches Verhalten des StringGrid)
      StringGrid1.RowCount := StringGrid1.RowCount - 1;
    finally
      csvReader.Free;
      data.Free;
    end;
  finally
    sl.free;
  end;
end;

p80286 27. Okt 2009 13:09

Re: CSV-Datei in Stringgrid bringen
 
@Lannes
Zitat:

Zitat von Lannes
Hallo,

Zitat:

Zitat von peschai
Das ist im RFC für die CSV aussdrücklich berückssichtigt, also möglich. Excel kann das.

das halte ich aber für ein Gerücht :roll: Excel kann sie ausgeben aber nicht richtig einlesen(bei neueren Excel-Versionen > 2000 kA).

Nur die Quotes zu überprüfen reicht nicht:
Code:
Hallo;1;"dad
sdfsfaf";"aa"""
Hallo;1;"dad
sdfsfaf""";aa
Wieviel Zeilen sind das?

zwei

Allgemeingültig ist dieses "Anzahl der Quotes ungerade" wohl nicht, aber vielleicht reicht es peschai ja so.
Richtig wäre es auf jeden Fall mit "IstInQuote" zu arbeiten, damit dann auch gleich das/die Trennzeichen richtig verarbeitet Wird/werden.

Gruß
K-H

Ach ja mein Excel (2003) kann CSV, wenn ich ihm sage was was ist. Die Automatik ist meist nicht dazu in der Lage.

peschai 6. Nov 2009 05:32

Re: CSV-Datei in Stringgrid bringen
 
Hallo Leute,
doch es ist wirklich so einfach und allgemeingültig und keineswegs eine Einschränkung
Die Überprüfung einer Zeile auf ungerade Anzahl der Quotes ist die Beantwortung der Frage "IstInQuote".
Trennzeichen innerhalb Quotes sind durch die Quotes neutralisiert und spielen damit keine Rolle
Quotes selber innerhalb Text werden verdoppelt, damit sind sie gerade und spielen ebenfalls keine Rolle.

Nehmen wir das Beispiel und stellen wir uns vor, daß ein in eine StringList geladen wurde.

Hallo;1;"dad
sdfsfaf";"aa"""
Hallo;1;"dad
sdfsfaf""";aa


1.) Nimm erste QuellZeile
-> Hallo;1;"dad

2.) Zähle Quotes
-> 1 ungerade

3.) da ungerade MUSS Zeilenumbruch in letzter "QuellZeilenzelle" vorliegen, also nächste Zeile dazunehmen
-> Hallo;1;"dad#13#10sdfsfaf";"aa"""

4.) Zähle Quotes
-> 6 gerade

5.) da gerade ist also erste Ziel-CSVZeile komplett eingelesen. Nächste QuellZeile nehmen
-> Hallo;1;"dad

6.) Zähle Quotes
-> 1 ungerade

7.) da ungerade MUSS Zeilenumbruch in letzter "QuellZeilenzelle" vorliegen, also nächste Zeile dazunehmen
-> Hallo;1;"dad#13#10sdfsfaf""";aa

8.) Ergebnis also zwei echte Zeilen
Hallo;1;"dad#13#10sdfsfaf";"aa"""
Hallo;1;"dad#13#10sdfsfaf""";aa

Die 3.Zelle der 1.Zeile hat also den Inhalt
Zitat:

dad#13#10sdfsfaf
Die 3.Zelle der 2.Zeile hat also den Inhalt
Zitat:

dad#13#10sdfsfaf"
Das lässt sich sehr effizient in einer normalen Stringlist ohne Fremdkomponente realisieren

Das Beispiel mit dem TCSVReader verwendet im Prinzip eine ähnlichen Ansatz indem er schaut ob pro Zeile die Spaltenanzahl stimmt. Wenn nicht, dann nimm nächste Zeile dazu ...
-> aber es könnte sein, daß der Ansatz über die TStringlist effizienter ist ? ... :roll:

peschai 6. Nov 2009 06:05

Re: CSV-Datei in Stringgrid bringen
 
Hier die Implementierung auf Basis von TStringList


Delphi-Quellcode:
         // Neutralisierung Zeilenumbrüche innerhalb CSVZelle
         try
            FStrings.BeginUpdate;
            FStrings.Capacity := j1.Count;
            i7 := j1.Count-1;
            i6 := -1;
            While (i6<i7) do
              begin
                // Loop
                Inc(i6);
                s4 := j1[i6];
                If Odd(fctLocalCharCount(s4,c3)) then
                  begin
                    // Zeilenumbruch in Zelle erkannt, nächste Zeile(n) dazunehmen
                    if (i6<i7)
                      then
                        begin
                          Inc(i6);
                          s5 := j1[i6];
                          s4 := s4 + #13#10 + s5;
                          // eventuell weitere Zeilen
                          While Not(Odd(fctLocalCharCount(s5,c3))) and (i6<i7) do
                            begin
                              Inc(i6);
                              s5 := j1[i6];
                              s4 := s4 + #13#10 + s5;
                            end;
                        end
                      else
                        begin
                          raise Exception.Create('CSV Parser Fehler in Zeile '+IntToStr(i6));
                        end;
                  end;
                // Datenzeile in Pool speichern
                FStrings.Add(s4);
              end;
          finally
            FStrings.EndUpdate;
          end;

taveuni 6. Nov 2009 06:43

Re: CSV-Datei in Stringgrid bringen
 
Hallo,

Bei Deiner Delphi Version kann alles mit Bordmitteln gelöst werden.
Das Zauberwort heisst TStringList.StrictDelimiter.

Gruss Werner

peschai 6. Nov 2009 07:26

Re: CSV-Datei in Stringgrid bringen
 
Hallo taveuni

Nein, es kann nicht mir Bordmitteln gelöst werden.
Ja StrictDelimiter hat mit dem gesamten beiCSV zu tun, hilft mir aber nicht bei den zeilumbrüchen in den Zellen. StrictDelimiter wirkt Zeileweise und nicht Kompletttextweise.

TStringlist.LoadFromFile, dann ist das Kind bereits in den Brunnen gefallen, egal mit oder ohne StrictDelimiter. Denn pro Quelltext-Zeile wird ein Stringlist Zeile erzeugt. StrictDelimiter wirkt nicht bei LoadFromFile oder bei LoadFromStream ....

alzaimar 6. Nov 2009 18:19

Re: CSV-Datei in Stringgrid bringen
 
Zitat:

Zitat von peschai
Das Beispiel mit dem TCSVReader verwendet im Prinzip eine ähnlichen Ansatz indem er schaut ob pro Zeile die Spaltenanzahl stimmt. Wenn nicht, dann nimm nächste Zeile dazu ...

Äh. nö? :gruebel:
Der TCSVReader liest nur die CSV-Datei spezifikationskonform ein. Ganz einfach. Zeile für Zeile. Innerhalb von Quotes wird weder ein Spaltenbegrenzer noch ein Zeileende erkannt, sonst schon.

Das Gedöns drumherum habe ich nur gemacht, damit sich die StringGrid dynamisch an die maximale Spaltenzahl anpasst.

Zitat:

aber es könnte sein, daß der Ansatz über die TStringlist effizienter ist ? ... :roll:
Wen interessiert das?
Lesbarkeit, Robustheit und Erweiterbarkeit sind viel wichtiger. Und da muss ich sagen, finde ich die Lösung mit dem TCSVReader irgendwie ...

Zitat:

Zitat von peschai
Hier die Implementierung auf Basis von TStringList

Was ist j1? i7? i6? Wie ist fctLocalCharCount(i,j) definiert? Wo sind die Deklarationen? Das ist doch (entschuldige bitte) ein sehr unleserliches Codefragment. Wer soll den Code denn verstehen?

peschai 11. Nov 2009 06:52

Re: CSV-Datei in Stringgrid bringen
 
Hallo

Zu meinem implementierungs Beispiel in post #11 sollte ich noch etwas ergänzen, um die Lesbarkeit zu erhöhen (edit nicht mehr möglich):
Zitat:

"j1" ist eine TStringlist welche mit LoadFromFile die CSV Datei am Stück eingelesen hat und da natürlich die Zeilenumbrüche noch nicht passen...
Zitat:

"i6, i7" sind vom Typ Integer
Zitat:

"s4, s5" sind vom Typ Strings
Wenn das Codefragment durchlaufen ist, dann entspricht eine Zeile von FStrings einer Zeile im zu füllenden Stringgrid.
Mittels StrictDelimiter kann dann elegant die stringgrid Zeile für Zeile befüllt werden.

Ich wollte hier keinen kompletten Beitrag zur CodeLibrary machen, sondern nur die Thematik der Zeileumbrüche innerhalb einer Zelle mit dem Ziel das in eien stringgrid zu überführen grundsätzlich diskutieren ...

Robustheit, Lesbarkeit und auch Effizienz sind wichtig.
Die Klasse TCSVReader löst und kapselt im Prinzip die von mir angesprochene Problematik, wie ich jetzt verstanden habe, aber dazu muss eine Fremdkomponente benutzt werden (TCSVReader!). Diese hat als weitere Vorteile bestimmt noch weitere Fähigkeiten.

Wer keine Fremdkomponente nutzen möchte, sondern eine einfache Funktion sucht, welche eine CSV (incl Zeilenumbrüche) in z.b. eine Stringgrid einliest, der möge hier antworten und ich poste dann den kompletten source (aber nur wenn überhaupt Bedarf besteht!)

alzaimar 11. Nov 2009 07:09

Re: CSV-Datei in Stringgrid bringen
 
Zitat:

Zitat von peschai
Wer keine Fremdkomponente nutzen möchte, sondern eine einfache Funktion sucht, ...

Welcher Vorteil liegt in einer 'Fremdfunktion' ggü. einer 'Fremdkomponente'? :zwinker:

Lannes 11. Nov 2009 08:30

Re: CSV-Datei in Stringgrid bringen
 
Hallo,
Zitat:

Zitat von peschai
... Wer keine Fremdkomponente nutzen möchte, sondern eine einfache Funktion sucht, welche eine CSV (incl Zeilenumbrüche) in z.b. eine Stringgrid einliest, der möge hier antworten und ich poste dann den kompletten source (aber nur wenn überhaupt Bedarf besteht!)

Bedarf so direkt nicht, aber Interesse daran besteht. Wenn die Funktion gut ist, steigert das die Nachfrage und weckt dann den Bedarf. :wink:

query 11. Nov 2009 09:10

Re: CSV-Datei in Stringgrid bringen
 
Aktuell hab ich da keinen Bedarf dran, aber früher und zukünftig sicher mal. Für andere gilt da sicher das selbe. Also zeig doch deinen kompletten Code. Das ist doch das schöne an diesem Forum, daß man hier oft Lösungen finden kann, ohne vorher lange nachfragen zu müssen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 16:05 Uhr.

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