Delphi-PRAXiS
Seite 3 von 5     123 45      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Software-Projekte der Mitglieder (https://www.delphipraxis.net/26-software-projekte-der-mitglieder/)
-   -   TTextStream - Textdateien einlesen (https://www.delphipraxis.net/151481-ttextstream-textdateien-einlesen.html)

freejay 17. Aug 2010 13:54

AW: TTextStream - Textdateien einlesen
 
Hallo Himitsu,

vielen Dank für die schnelle Antwort und Änderung! Respekt!

Allerdings hat sich glaube ich da noch ein kleiner Fehler eingeschlichen. Mir wurden jetzt bei UTF8-ohne-Bom-Dateien auch die drei ersten Zeichen abgeschnitten.
Ich habe dann die Zeile 638 von
Delphi-Quellcode:
If (FBufferSize < i)...
auf
Delphi-Quellcode:
If (FBufferSize >= i)...
geändert und jetzt scheint alles korrekt zu funktionieren.

Vielen Dank nochmal!

Gruß

Freejay

himitsu 17. Aug 2010 14:01

AW: TTextStream - Textdateien einlesen
 
@Freejay:
Delphi-Quellcode:
If (FBufferSize < i) or not CompareMem(@FByteBuffer[0], @B[0], i) Then i := 0;
wäre richtiger.
(hab beim Umdrehen des äußeren IFs das AND vergessen mitzudrehen :wall: )

@ChrisE: ich seh nix :roll:
danke




Hatte dieses
Delphi-Quellcode:
If not Assigned(FEncoding) Then Begin
  i := TEncoding.GetBufferEncoding(FByteBuffer, FEncoding);
  If i > 0 Then Begin
    Move(FByteBuffer[i], FByteBuffer[0], FBufferSize - i);
    Dec(FBufferSize, i);
  End;
End Else Begin
  B := Encoding.GetPreamble;
  i := Length(B);
  If (FBufferSize >= i) and CompareMem(@FByteBuffer[0], @B[0], i) Then Begin
    Move(FByteBuffer[i], FByteBuffer[0], FBufferSize - i);
    Dec(FBufferSize, i);
  End;
End;
auf jenes
Delphi-Quellcode:
If Assigned(FEncoding) Then Begin
  B := Encoding.GetPreamble;
  i := Length(B);
  If (FBufferSize < i) or not CompareMem(@FByteBuffer[0], @B[0], i) Then i := 0;
End Else i := TEncoding.GetBufferEncoding(FByteBuffer, FEncoding);
If i > 0 Then Begin
  Move(FByteBuffer[i], FByteBuffer[0], FBufferSize - i);
  Dec(FBufferSize, i);
End;
gekürzt.

ChrisE 17. Aug 2010 14:12

AW: TTextStream - Textdateien einlesen
 
Zitat:

Zitat von himitsu (Beitrag 1042767)
@ChrisE: ich seh nix :roll:
danke

OH :shock: ich auch nicht. Muss mich vergugt haben
kein Problem

freejay 17. Aug 2010 14:19

AW: TTextStream - Textdateien einlesen
 
@himutsu

Alles klar! Danke!

freejay 17. Aug 2010 16:59

AW: TTextStream - Textdateien einlesen
 
Hallo himitsu,

TTextStream (ohne "Ex") funktioniert jetzt bei mir tadellos!

Aber ich verstehe TTextStreamEx (mit "Ex") überhaupt nicht...

Folgender simpler Alltags-Code führt z.B. dazu, dass sich das Programm aufhängt:

Delphi-Quellcode:
TextStream := TTextStreamEx.Reset(FileName,FEncoding);
TextStream.ReadLn(sHeader);
Meine Vermutung war: Die Funktion EoLn ist genau falsch herum definiert:

Statt
Delphi-Quellcode:
Result := Trim(FLine) <> '';
müsste es meiner Meinung nach
Delphi-Quellcode:
Result := Trim(FLine) = '';
heißen.

Dann bekommt man zwar soetwas ähnliches wie die erste Zeile geliefert, aber eben nicht genau. Meine Zeile enthält z.B. TABs: Die sind verschwunden und stattdessen sind an ihrer Stelle Leerzeichen.

Die Zeile wird also irgendwie zerstückelt und anschließend zusammengesetzt, da bei ReadLn die Funktion ReadFull aufgerufen wird, die sozusagen einzelne Werte aus einer Zeile einliest und anschließend mit Leerzeichen als Trenner wieder zusammensetzt.

So gehen mehrfache Leerzeichen, TABs und möglicherweise noch andere Dinge aus der ursprünglichen Zeile verloren.

Muss ich eine andere Methode benutzen oder habe ich TTextStreamEx blos nicht verstanden?

Gruß

Freejay

himitsu 17. Aug 2010 17:16

AW: TTextStream - Textdateien einlesen
 
Hab grade kein Delphi hier, drum werd ich mir das später nochmals genauer ansehn.

Aber mit dem EoLn hast du Recht.
Wenn nichts mehr am Zeilenende oder nur noch Leer-/ Steuerzeichen am Ende enthalten sind, dann muß es True werden, also
Delphi-Quellcode:
= ''

BoolString 14. Okt 2010 15:18

AW: TTextStream - Textdateien einlesen
 
Hallo Himitsu!

Ich habe mal folgendes probiert:
Delphi-Quellcode:

aFile := 'C:\Users\D. Jan Schulz\Desktop\Iris-Tab - Kopie UTF8.txt';

F := TTextStreamEx.Create(aFile, saRead);
Try
  Memo1.Clear;

  While not f.EoF do Begin
    aString := f.ReadLine;
    Memo1.Lines.Add(aString);
  end;
Finally
  F.Free;
end;
Dabei ist mir aufgefallen, daß die zweite Zeile einer Datei nicht ausgelesen wird. Meiner Meinung nach liegt dies wohl an der Art, wie die Funktion TTextStreamEx.ReadLine aufgerufen wird. Hier wird beim ersten Aufruf durch den EoLn Vergleich zwei mal das ReadLine aufgerufen. Wenn man es wie nachfolgend macht klappt es:


Delphi-Quellcode:
 
  Function TTextStreamEx.ReadLine: SString;
    Begin
      If EoLn Then Begin
        Result := inherited ReadLine;
//        FLine := inherited ReadLine; //Zeile 1429
        FLine := Result;
      End Else Begin
        Result := FLine;
        FLine := inherited ReadLine;
      End;
    End;
Ich bin mir jetzt aber nicht sicher, wie dieser Eingriff sich mit anderen Funktionen verhält. Ich fange gerade erst an mich mit der Unit zu beschäftigen. Vielleicht kannst du mit etwas mehr Hintergrundwissen da etwas zu sagen. Das Gleiche scheint auch zu gelten für TTextStreamEx.ReadLn.

Vielleicht wäre es auch gut die einzelnen FFormat Einstellungen als Property nach außen zu veröffentlichen, da man oftmals Dateien mit unterschiedlichen Ländereinstellungen vorfindet.

FIndex könnte man auch nach außen legen, um gezielt einzelne Zeilen anzuspringen. Wenn ich das bislang richtig sehe, dann hast du dies ja sehr gut beim Create in der FLines Struktur abgelegt. Es müsste natürlich dann eine Kontrolle stattfinden, ob der gewählte Wert innerhalb der Ranges von FLines liegt. Aber damit könnte man dann auch 're-parsing' machen wenn man die FFormats geändert hat...

Aber wie gesagt, ich fang da gerade erst mit an und hab sicherlich noch nicht alles in deiner Unit verstanden...

Jan

himitsu 16. Okt 2010 11:31

AW: TTextStream - Textdateien einlesen
 
Liste der Anhänge anzeigen (Anzahl: 1)
[edit] Sekunde, bin och blöd ... werd's gleich nochmal probieren, mit der richtigen Klasse :oops: und mich dann wieder melden.
[add] Die Funktion ReadLine sollte aber korrekt sein und ich vermute mal einen Fehler im .Create, denn sonst würde nicht nur die 2. Zeile fehlen, sondern womöglich jede Zweite.
[add2] Also, ein
Delphi-Quellcode:
Inherited ReadLine
im .Create könnte nicht schaden und das Problem mit der fehlenden Zeile ... es liegt daran, daß es ein "kleines" Problemchen mit "leeren" Zeilen gibt.
Da hatte ich damals in meiner Testdatei wol keine drin.
Muß mir dafür nur noch was überlegen, denn ich hatte den LeerString quasi als "Markierung" für das Zeilenende genutzt, so daß er nun Leerzeilen überspringt, aber ich hab schon eine Idee (muß nur noch ausprobieren ob's geht).

Hmmm, ich hab mir nochmal schnell ein kleines Testprogramm erstellt und da scheint es diesbezüglich keine Probleme zu geben. :gruebel:

Delphi-Quellcode:
Program Project1;

{$APPTYPE CONSOLE}

Uses SysUtils, TextStream;

Var S: TTextStream;
  i: Integer;

Begin
  Try
    S := TTextStream.Create('TextStream.pas', saRead);
    i := 0;
    While not S.EoF and (i < 15) do Begin
      WriteLn(Copy(S.Line, 1, 79));
      Inc(i);
    End;
    S.Free;
    ReadLn;
  Except
    On E: Exception do Begin
      WriteLn(E.ClassName, ': ', E.Message);
      ReadLn;
    End;
  End;
End.
(das Copy, falls die Zeile länger ist, als die Konsole breit)

Aber vielleicht liegt es ja an einer, wie die Juristen gern sagen, Verkettung unglücklicher Umstände?
Kannst du mir mal dein (Test)Projekt und die zu lesende Datei zukommen lassen?

OK, das mit dem FFormat war garnicht geplant es nach außen weiterzugeben, da ich intern so sicherstellen wollte, daß hiermit "gespeicherte" Daten überall korrekt gelesen werden können, egal welche Ländereinstellung im System vorliegen.
Auf die Idee daß man auch andersweitige Dateien damit leden können wöllte, bin ich garnicht gekommen. :oops:

Läßt sich aber leicht nachrüsten :D
Delphi-Quellcode:
Constructor Append (Filename: SString; Encoding: TEncoding = nil; OwnsEncoding: Boolean = False);
Destructor Close;

Property NumberFormat: TFormatSettings Read FFormat;
Procedure SetNumberFormat(Const Format: TFormatSettings);

Procedure Read  (Var  Value: SString);                                 Overload;
und dann unten noch das rein
Delphi-Quellcode:
Procedure TTextStreamEx.SetNumberFormat(Const Format: TFormatSettings);
  Begin
    FFormat := Format;
  End;

himitsu 16. Okt 2010 13:40

AW: TTextStream - Textdateien einlesen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hatte aber noch nicht die Gelegenheit alles zu Testen, darum quasi nur erstmal als PreAlpha, oder so :stupid: , da es so einige Änderungen gab.

[edit]
Anhang entfernt (Aktuelleres siehe Post #1)

BoolString 18. Okt 2010 09:47

AW: TTextStream - Textdateien einlesen
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo Himitsu,

habe am Wochenende mal deine Neuerungen getestet. Die Probleme scheinen sich jetzt gelöst zu haben.

Ganz klar ist mir allerdings noch nicht was intern mit deinem fIndex passiert. Ich habe diese Variable mal als Property ActualRow in tTextStream nach außen gelegt:
Delphi-Quellcode:
Property ActualRow: Integer Read fIndex Write fIndex;
Das funzt auch. Der Vorteil ist (aus meiner Sicht), daß man den Zeiger auf eine beliebige Stelle innerhalb der Datei setzen kann. Es ermöglicht, z.B. bei der Verwendung in einem Import-Wizard, daß man die Datei neu parsen kann, wenn irgendwelche Einstellungen verändert werden sollen. Neben der aktuellen Zeile hat man dann auch gleichzeitig noch die Angabe wie viele Zeilen (mit Wiederholungen) überhaupt schon verarbeitet wurden über f.GetProcessedLines.

Soweit ich das richtig verstanden habe ist fIndex ein Null-basierter Index, der in fLines verwendet wird. Nutze ich das mit dem folgenden Code:

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);
Var f: tTextStreamEx;
     aFile : String;
     astring : UnicodeString;
     aRunner : Integer;
     aString2: String;
     aString3: UnicodeString;
     aBytes : tBytes;
     aSize  : Integer;
     aLineNo : String;
begin

aFile := 'C:\Users\D. Jan Schulz\Desktop\Iris-Tab - Kopie UTF8.txt';

F := TTextStreamEx.Create(aFile, saRead);
Try
  Memo1.Clear;
  aBytes := f.Encoding.GetPreamble;
  Memo1.Lines.Add('Byte sequence:' + PWidechar(aBytes));
  aString2:= f.Encoding.ToString;
  Memo1.Lines.Add('BOM code: ' + aString2);
  Memo1.Lines.Add(IntToStr (ord (f.LineBreak))) ;
  aSize := f.CRLFCounter;
  Memo1.Lines.Add('CRLF count: ' + IntToStr(aSize));
  aSize := f.LFCounter;
  Memo1.Lines.Add('LF  count: ' + IntToStr(aSize));
  aSize := f.CRCounter;
  Memo1.Lines.Add('CR  count: ' + IntToStr(aSize));


  f.ActualRow := 0; // Unterschied, wenn diese Zeile auskommentiert ist

  While not f.EoF do Begin
      aLineNo := IntToStr(f.ActualRow) + '/'+IntToStr(f.GetProcessedLines) + ': ';
      aString := f.ReadLine;

      Memo1.Lines.Add(aLineNo + aString+ '-> '+ IntToStr(f.ActualRow) + '/'+IntToStr(f.GetProcessedLines));
  End;

Finally
  F.Free;
End;
end;
Bekomme ich die erste Zeile zwei mal, wobei GetProcessedLines schon vorher auf 1 steht und sich von fLines unterscheidet.
Kommentiere ich im obigen Beispiel die markierte Zeile aus, habe ich eine korrekte Auslesung und beide Werte sind identisch (ebenso als wenn ich f.ActualRow auf 1 setze). Ist der doch nicht Null-basiert?


Zusätzlich ist mir aufgefallen, daß du offensichtlich feste Delimiter verwendest. Oftmals wird aber auch das Leerzeichen genutzt oder irgendwelche mystischen Symbole. Dadurch ist deine Arbeit mit den schönen ReadValues nur bedingt universell.

Das gleiche gilt für Quote-Zeichen. Hier wird neben " auch oftmals ' verwendet oder irgendwas anders (hab schon mal ein @ gesehen). Aus dem Grund wäre es sicherlich gut, wenn man dies Zeichen frei definieren kann.

Was in der Praxis auch schwierig ist, ist die Tatsache, daß ein ReadValue sofort eine Exception wirft, sobald ein falsches Format vorliegt. Hier wäre sicherlich noch eine weitere Funktion sinnig, die einen Wert zurückgibt, der nach den Ländereinstellungen des Rechners umgeschrieben wurde (Dateien mit Komma als Dezimaltrenner, aber mit Punkt auf dem Zielrechner) und einfach den ausgelesenen String, wenn die Wandlung nicht möglich war (z.B. ein Datum, Zeit, Kommentar oder ähnliches).
Oftmals benötigt man die Daten weiterhin als String, aber hätte gerne die Formatierung auf das aktuelle System umgeschrieben. Besonders in gemischten Dateien.

Jan

PS: Ich hab mal vorsichtshalber meine Testdatei angehängt. Es ist der klassiche Iris Datensatz mit einer UTF8-Präambel.

PPS: Sicherlich sind einige Anmerkungen nur relativ selten vorkommende Fälle. Wenn man aber viel mit solchen Daten zu tun hat, dann stolpert man sehr häufig über solche Sonderfälle. Stell dir mal vor ein Amerikaner und ein Russe (mit jeweils eigenen Ländereinstellungen) erzeugen Daten und migrieren diese auf einem Rechner, der eine deutsche Ländereinstellung verwendet. Lustige Kombinationen von Dezimal- & Tausendertrennzeichen sind die Folge, ebenso wie Datumswerte...
Teilweise werden auch Mehrere Trennzeichen (z.B.) Space hintereinander geschrieben, die man dann als ein zeichen interpretieren muss. Und dabei meine ich KEINE festen Spaltenbreiten. z.B. immer 4 Space Zeichen, und dann die Werte/Informationen in unterschiedlicher Länge...


Alle Zeitangaben in WEZ +1. Es ist jetzt 17:14 Uhr.
Seite 3 von 5     123 45      

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