Delphi-PRAXiS
Seite 1 von 2  1 2      

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)

himitsu 19. Mai 2010 19:41


TTextStream - Textdateien einlesen
 
Liste der Anhänge anzeigen (Anzahl: 8)
So, den nun hab ich erstmal den Schreib-/Lesekern meiner neuen StringListe seppariert und er läuft endlich.
Manchmal muß man eben mit mehrfachem Code leben ... hartkodierte Konstanten sind eben schneller, als Variablen und eine dynamische Verarbeitung.

Diese Klasse ließt eine beliebig große Textdatei sequentiell ein, wobei sogar unterschiedliche Kodierungen (TEncoding) unterstützt werden und ein eventuelles BOM ausgewertet wird.
Speichern ist natürlich auch möglich.

Nja, die Speicherverwaltung des Lesepuffers gefällt mir noch nicht so ganz
( http://www.delphipraxis.net/internal...t.php?t=177739 ),
aber für diesen Fall dürfte es denoch ausreichend sein.

Ja nach Datei und Computer ist es etwa gleichschnell oder schneller als eine TStringList zu Einlesen braucht (wobei die TStringList irgendwann an ihre Speichergrenzen stößt, da sie alles im Arbeitsspeicher verarbeitet, welches beim Einlesen einer einfachen Ansi-Datei in Delphi2009/2010 mehr als den 4-fachen Speicherbearf, der ursprünglichen Dateigröße verlangt)



Als Zusatzmodul ist mir eingefallen, daß man die (ur)alten Pascal-Datei-Funktionen ersetzen könnte,
aber leider ist es nicht möglich einen adequaten Ersatz für Read, ReadLn, Write und WriteLn zu finden :cry:,
Aber vielleicht hat ja jemand eine brillante Erleuchtung.


PS: Der Name "TStringStreamEx" der Klasse gefällt mir eigentlich auch nicht, aber irgendwer hatte schon die Idee eine andere Klasse TStringStream zu nennen. :?

[edit] Name angenommen :stupid:

[edit 20.05.2010]
  • alles etwas überarbeitet
  • eine Version für Delphis vor 2009 erstellt
    Als Bonus hat sie eine einfache Variante des TEncoding bekommen, welches man natürlich auch für andere Dinge nutzen könnte.
  • und Zusatzmodul TTextStreamEx fertiggestellt

[edit 21.05.2010 v1.2c]
Wo es nun zu laufen scheint, hab ich mir mal die Unterschiede angesehn
und beide Versionen miteinander kombiniert.

Außerdem hatte ich glatt was vergessen zu übernehmen. :shock:
Beim Einlesen einer Datei werden die Zeilenumbrüche analysiert und das Property LineBreak enthält dann den häufigsten Zeilenumbruch (falls es mal ein bissl gemischt ist) ... die FileStringList wird somit später den Zeilenumbruch einer Datei quasi erhalten und ihn nicht ständig auf Windowsstandard (CRLF) abändern. :-D

[edit 17.08.2010 v1.3]
- BOM-Erkennung bei Angabe einer Kodierung integriert
- BOM kann nun beim Schreiben weggelassen werden

[edit 18.10.2010 v1.4a2]
- neue Testversion (sie Beitrag #31)

Namenloser 19. Mai 2010 19:53

Re: TStringStreamEx - Textdateien einlesen
 
Nur kurz zur Namensgebung:
Zitat:

Zitat von himitsu
PS: Der Name "TStringStreamEx" der Klasse gefällt mir eigentlich auch nicht, aber irgendwer hatte schon die Idee eine andere Klasse TStringStream zu nennen. :?

Wie wäre es mit TTextStream?

[edit]BBCode gefixt[/edit]

gsh 20. Mai 2010 10:57

Re: TStringStreamEx - Textdateien einlesen
 
Hallo himitsu,

nette Klasse hast du da erstellt. Wollte sie mal ausprobieren aber mir fehlen leider folgende Typen:
TEncoding, TBytes, TMBCSEncoding

Habe die Classes, SysUtils bereits eigebunden ... aber keine Unit für die oberen Typen gefunden. (Sind die vielleicht von dir?)

Danke
gsh

DeddyH 20. Mai 2010 11:01

Re: TStringStreamEx - Textdateien einlesen
 
Das ist vermutlich wieder alles Unicode-Gedöns (zumindest TEncoding), also erst ab D2009 zu gebrauchen.

himitsu 20. Mai 2010 11:04

Re: TStringStreamEx - Textdateien einlesen
 
@DeddyH
Noar, also ab Delphi 2009 ... aber man könnte sich notfalls auch einen Dummy für TEncoding erstellen und dann dürfte es auch in älteren Delphis laufen.

TEncoding hatte ich eben genommen, da es "aktuell" auch in der VCL verwendet wird und somit leichter benutzbar ist
, außerdem befindet sich somit kein Code für eine En-/Decodierung im eigenen Projekt und es kann alles extern verarbeitet werden.

TBytes ist einfach nur ein Array of Byte.

[add]
Ich mach jetzt nur noch schnell was Anderes fertig und dann versuch ich es mal in TDE zum Laufen zu bekommen.

p80286 20. Mai 2010 12:56

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von himitsu

Als Zusatzmodul ist mir eingefallen, daß man die (ur)alten Pascal-Datei-Funktionen ersetzen könnte,
aber leider ist es nicht möglich einen adequaten Ersatz für Read, ReadLn, Write und WriteLn zu finden :cry:,

Write und Writeln sind doch recht einfach durch ein Blockwrite(Datei,satz[1],sizeof(satz)) bzw. seiner TStream-Entsprechung zu ersetzen. Fummelig ist nur der Unterschied zwischen length() und sizeof() in den Griff zu kriegen.

für ein Read (bei "Text - Dateien") sehe ich eigentlich keine Verwendung (wer braucht schon ein Read(char)).
Beim Readln kommt man um das scannen nach dem EOL nicht herum, dafür kann man dann auch frei definieren wodurch EOL gekennzeichnet ist (CRLF, LFCR, CR, LF, #0.....)
Und man baut noch gleich einen Interpreter für die alten ANSI-Codes mit ein (!?)
Und das alles natürlich frei definierbar......
Ach ja bei der Gelegenheit kann man gleich noch BigEndian und LittleEndian berücksichtigen......

;-))



Gruß
K-H

himitsu 20. Mai 2010 14:00

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von p80286
Write und Writeln sind doch recht einfach ...

Nja, es geht mehr um sowas schön Kombiniertes wie ReadLn(i, S).
WriteLn(123, i, 'ads', s) kann man ja notfalls durch WriteLn([...]) ersetzen.

Zitat:

Zitat von p80286
Und man baut noch gleich einen Interpreter für die alten ANSI-Codes mit ein (!?)
Und das alles natürlich frei definierbar......
Ach ja bei der Gelegenheit kann man gleich noch BigEndian und LittleEndian berücksichtigen.....

Das macht doch alles schon das nette TEncoding
und der "Parser" sollte sowas auch beachten. :angel:

So, ein Art kombiniertes Write/WriteLn gibt es, aber natülich nicht das Read/ReadLn.
Read ließt nur einen Wert, der aktuellen Zeile und EoLn gibt an, ob es noch weitere Werte in der aktuellen Zeile gibt.
(praktisch fast genauso, wie bei den alten Pascalfunktionen)

Diese Textzeile
Code:
132 abc true
könnte man also folgendermaßen auslesen
Delphi-Quellcode:
F.Read(int);
F.Read(str);
F.Read(bool);
oder 'ne ganze Datei gleich mal so ... Wert für Wert:
Delphi-Quellcode:
F := TTextStreamEx.Create('Datei.txt', saRead);
Try
  While not EoF do Begin
    While not EoLn do
      ShowMessage(ReadString);
    ReadLn;
  End;
Finally
  F.Free;
End;
Ja, und ich hoffe mal die Vor2009-Variante läuft gut.
Als Bonus hat sie eine einfache Variante des TEncoding bekommen, welches man natürlich auch für andere Dinge nutzen könnte.

gsh 20. Mai 2010 15:02

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von himitsu
Ja, und ich hoffe mal die Vor2009-Variante läuft gut.
Als Bonus hat sie eine einfache Variante des TEncoding bekommen, welches man natürlich auch für andere Dinge nutzen könnte.

Danke für das "Update".
Gleich mal als erstes: Zeile 259 steht einfach das Wort CompilerVersion ... was da sicher nicht hingehört und ws ein "Strg+V Fehler" ist :zwinker:
Dann wollte ich noch sagen das ich unter D2006 folgende Hinweise und Warnungen erhalte:
Zitat:

[Pascal Warnung] TextStreamNUC.pas(479): W1050 WideChar in Set-Ausdrücken auf ByteChar verkürzt
[Pascal Hinweis] TextStreamNUC.pas(525): H2077 Auf 'i' zugewiesener Wert wird niemals benutzt
[Pascal Warnung] TextStreamNUC.pas(806): W1050 WideChar in Set-Ausdrücken auf ByteChar verkürzt
[Pascal Hinweis] TextStreamNUC.pas(894): H2077 Auf 'Value' zugewiesener Wert wird niemals benutzt
[Pascal Warnung] TextStreamNUC.pas(928): W1044 Bedenkliche Typumwandlung von WideString in PAnsiChar
[Pascal Hinweis] TextStreamNUC.pas(952): H2077 Auf 'Value' zugewiesener Wert wird niemals benutzt
[Pascal Warnung] TextStreamNUC.pas(986): W1044 Bedenkliche Typumwandlung von WideString in PAnsiChar
[Pascal Warnung] TextStreamNUC.pas(1040): W1044 Bedenkliche Typumwandlung von WideString in PAnsiChar
[Pascal Warnung] TextStreamNUC.pas(1146): W1035 Rückgabewert der Funktion 'TTextStreamEx.ReadBool' könnte undefiniert sein
[Pascal Warnung] TextStreamNUC.pas(1200): W1035 Rückgabewert der Funktion 'TTextStreamEx.ReadLnBool' könnte undefiniert sein
Werde dann gleich mal die Funktionalität testen.

himitsu 20. Mai 2010 15:24

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von gsh
Gleich mal als erstes: Zeile 259 steht einfach das Wort CompilerVersion ... was da sicher nicht hingehört und ws ein "Strg+V Fehler" ist :zwinker:

Ups :lol:

Zitat:

W1050 WideChar in Set-Ausdrücken auf ByteChar verkürzt
Dieses kannst'e ignorieren.

Ansonsten hoff' ich mal, daß die anderen Meldungen nun weg sind.


Zitat:

Bedenkliche Typumwandlung von WideString in PAnsiChar
Dieses war 'ne übereifrige Ersetzungaktion ... der Code hätte eigentlich unverändert bleiben können
(also String und PChar unverändert ... im restlichen Code mußte es aber nach WideString geändert werden)

Zitat:

Rückgabewert der Funktion ... könnte undefiniert sein
blöde vergessene VAR, im Parameter :oops:

p80286 20. Mai 2010 16:14

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von himitsu
Diese Textzeile
Code:
132 abc true
könnte man also folgendermaßen auslesen
Delphi-Quellcode:
F.Read(int);
F.Read(str);
F.Read(bool);

Ihr kommt auf Ideen, solche Perversitäten, hab ich früher immer mit Records und festen Satzlängen erschlagen.
(die Großrechner-Schädigung kommt eben immer wieder durch)

und bevor es jemand anmerkt, die Konsole hab ich immer mit
Delphi-Quellcode:
repeat until keypressed; read(inkey)
bedient. Ein schnödes
Delphi-Quellcode:
readln(konsolendaten)
ist mir sehr selten über den Weg gelaufen.

Gruß
K-H

himitsu 20. Mai 2010 18:29

Re: TStringStreamEx - Textdateien einlesen
 
Zitat:

Zitat von p80286
Ihr kommt auf Ideen, solche Perversitäten,

*rrrrrrrr*

Manchmal sind die alten Pascal-Funktionen eigentlich recht praktisch.
Nur schade ist, daß man damit nur ANSI-Text-Dateien erstellen und auslesen kann
und das interne Caching ist nicht unbedingt .... nja, ich wie soll man es denn nennen? ... optimal?

Hiermit könnte man sogar eine normale TStringList effektiver füllen. Zumindestens was größere Dateien angeht, dann so ab einer 300 MB-Textdatei bekommt man schonmal ein OutOfMemory, da ja alles am Stück eingelesen und verarbeite wird, also wärend des LoadFrom... und SaveTo....

himitsu 21. Mai 2010 08:56

Re: TTextStream - Textdateien einlesen
 
[info] nicht wundern ... es gibt jetzt nur noch eine Version für Unicode (D2009/2010) und die anderen Delphis.

Nja, und ein paar vergessene Codezeilen wurden noch schnell integriert ... die Erkennung der Zeilenumbrüche (siehe [edit] in #1).

chaosben 21. Mai 2010 11:32

Re: TTextStream - Textdateien einlesen
 
Fein gemacht! :thumb:

Ein Verbesserungsvorschlag für Zeile 707:
Delphi-Quellcode:
Else Mode := fmOpenReadWrite or fmShareDenyWrite or fmCreate;
Ohne dem dem fmCreate machts bei neuen Dateien einfach nur buuuuuuum. ;)

himitsu 21. Mai 2010 12:09

Re: TTextStream - Textdateien einlesen
 
Ups, also für Zeile 706+707 dann Dieses:
Delphi-Quellcode:
If     Access = saRead  Then Mode := fmOpenRead     or fmShareDenyWrite
Else If Access = saAppend Then Mode := fmOpenReadWrite or fmShareDenyWrite
Else              Mode := fmCreate or fmOpenReadWrite or fmShareDenyWrite;
Im Testcode lese ich über einen Stream ein und schreibe auch darüber. Und dort wird über CreateFile + einen speziellen THandleStream gearbeitet ... da ist dieser CodeTeil wohl etwas zukurzgeraten, beim Test. :oops:


Hier knallt's nicht, aber es ist schon blöd, daß man dem TFileStream nicht weitere Attribute mitgeben kann :cry: (vorallem FILE_FLAG_RANDOM_ACCESS und FILE_FLAG_SEQUENTIAL_SCAN geb ich ja gerne mal an, um Windows ein bissl unter die Arme zu greifen)
Delphi-Quellcode:
Case Mode of
  fmReadWrite: Begin
                  A := GENERIC_READ or GENERIC_WRITE;
                  C := OPEN_EXISTING;
                End;
  fmCreate:    Begin
                  A := GENERIC_READ or GENERIC_WRITE;
                  C := CREATE_NEW;
                  Mode := fmReadWrite;
                End;
  fmCreateOrRW: Begin
                  A := GENERIC_READ or GENERIC_WRITE;
                  C := OPEN_ALWAYS;
                  Mode := fmReadWrite;
                End;
  Else         Begin
                  A := GENERIC_READ;
                  C := OPEN_EXISTING;
                End;
End;
H := CreateFile(PChar(Filename), A, FILE_SHARE_READ, nil, C,
  FILE_FLAG_RANDOM_ACCESS or FILE_ATTRIBUTE_NOT_CONTENT_INDEXED, 0);
If H = INVALID_HANDLE_VALUE Then
  Raise EFCreateError.CreateResFmt(@SFCreateErrorEx,
    [ExpandFileName(Filename), SysErrorMessage(GetLastError)]);
AssignToStream(THandleStream2.Create(H), True, Mode = fmReadOnly, Encoding);
Mal sehn, vielleich verbau ich noch 'ne Abwandlung davon in diesem Code. :gruebel:

chaosben 21. Mai 2010 13:02

Re: TTextStream - Textdateien einlesen
 
Dann habe ich noch ein Problem ... beim Flush im Destructor eines schreibenden (logisch :) ) TTextStreams kommt eine Zugriffverletzung an der Stelle
Delphi-Quellcode:
FByteBuffer := FEncoding.GetBytes(FStringBuffer);
in der Funktion "WideCharToMultiByte".

Ich gebe zu, dafür kannst du wahrscheinlich nicht direkt was, aber falls dir eine Lösung einfällt, würde ich mich freuen. :)

Das hier funktioniert nicht:
Delphi-Quellcode:
procedure TForm30.FormCreate(Sender: TObject);
var
  tt : TTextStream;
begin
  tt := TTextStream.Create('f:\test.txt', saWrite);
  tt.WriteLine(DateTimeToStr(Now));
  tt.Free;

end;
Das gleiche in einer ConsolenApp geht [edit]auch nicht[/edit]. :wall:

himitsu 21. Mai 2010 13:20

Re: TTextStream - Textdateien einlesen
 
Oh, da hat sich noch ein Fehler bei der Pufferumstellung eingeschlichen
(früher Byte-Array mit 0-Index und jetzt String mit der bösen 1, aber die anderen Codestellen sehen diesbezüglich OK aus :angel2: )

Zeile 811
Code:
[b]Procedure[/b] TTextStream.WritePart(Const S: SString);
  [b]Begin[/b]
    ...
      Move(S[1], FStringBuffer[FBufferSize[color=#ff0000][b] + 1[/b][/color]], Length(S) * SizeOf(Char));
und Zeile 836
Code:
[b]Procedure[/b] TTextStream.Flush;
  [b]Begin[/b]
    [b]If[/b] FAccess <> saRead [b]Then Begin[/b]
      FByteBuffer := FEncoding.GetBytes([color=#ff0000][b]Copy([/b][/color]FStringBuffer[color=#ff0000][b], 1, FBufferSize)[/b][/color]);
Aber schon witzig, daß es dennoch manchmal problemlos läuft. :shock:

Zitat:

Zitat von chaosben
[edit]auch nicht[/edit]. :wall:

manchmal schon :lol:


Zum Glück muß ich Fehler jetzt nur noch in einer Version fixen :-D

chaosben 21. Mai 2010 21:01

Re: TTextStream - Textdateien einlesen
 
Vielen Danke fürs fixen. :dp:

freejay 17. Aug 2010 10:53

AW: TTextStream - Textdateien einlesen
 
Hi Himitsu,

ich benutze Deine Unit TextStream und habe dabei ein Problem und eine weitere Frage:

1. Wenn man eine Datei einliest, die UTF-8 codiert ist (mit! BOM), dann erhält man in der ersten Zeile als erstes "Zeichen" immer $FEFF (65279). Dies passiert nur, wenn man als Encoding beim Öffnen (=Create) TEncoding.UTF8 angibt. Lässt man das Encoding weg (nil), funktioniert es korrekt. Was kann man da machen?

2. Es scheint mir mit Deiner Unit nicht möglich zu sein eine UTF-8 codierte Datei ohne! BOM zu erzeugen, oder habe ich da etwas übersehen?

Danke in Voraus

Freejay

himitsu 17. Aug 2010 13:07

AW: TTextStream - Textdateien einlesen
 
Joar, da hatte ich wohl ein bissl zuviel gelöscht.

1. sollte jetzt funktionieren.
(die Prüfung wurde ausversehn mit entfernt)

2. joar, das war hier eigentlich nicht vorgesehn
(in meiner größeren Version gibt es dafür eine interne Option ... hab dafür aber nun einen neuen Access-Wert eingeführt > saWriteWithoutBOM)


#$FEFF ist das "Zeichen" für Unicode ... wenn man dieses Zeichen entsprechend kodiert, dann ergibt dieses das BOM der zugehörigen Kodierung. :angel:

ChrisE 17. Aug 2010 13:47

AW: TTextStream - Textdateien einlesen
 
Hallo,

erstmal TOP :thumb:

Hab beim Überfliegen gerade gesehen, dass sich u.U. ein Copy&Paste-Fehler eingeschlichen hat. In Zeile 287-289 von
Delphi-Quellcode:
Class Function TEncoding.GetASCII: TEncoding;
wird
Delphi-Quellcode:
FBigEndianUnicodeEncoding
verwendet anstatt wahrscheinlich
Delphi-Quellcode:
FASCIIEncoding
.

Ist aber nur eine Vermutung.

Gruß, Chris

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...

himitsu 18. Okt 2010 13:51

AW: TTextStream - Textdateien einlesen
 
Zitat:

Zitat von BoolString (Beitrag 1056281)
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;

FIndex ist nur ein interner Index, welcher eigentlich extern nix zu suchen hat.
Ich lese die Datei ja blockweise ein, die Zeilen im aktuellen Block werden über FLines indiziert.
FIndex gibt nun an, wieviel von diesem Block schon eingelesen wurde, bzw. an welcher Stelle (Index) man sich dort grade befindet und ob bei einem ReadLine nun noch eine Zeile aus diesem Puffer genommen werden kann, oder ob ein neues Stück geladen+geparst werden muß.

Das mit dem Quote-Zeichen sollte kein Problem sein.

Die Anzahl der Trennzeichen sollte egal sein.
Über das enthaltene TRIM werden mehrere nachfolgene Trennzeichen zusammengefasst.
Ich könnte eventuell aber eine Art StrictDelemiter einführen, wo dann immer nach jedem Leer-/Trennzeichen geteilt und nichts zusammengefaßt wird.

Dieses .ActualRow hat mich noch auf die Idee gebracht, daß man auch noch die Spalte mitzählen könnte, wenn die Zeilen "Value"-weise ausgelesen werden.

Na mal sehn, was sich noch so machen läßt.

[add]
aktuelle Testversion siehe Beitrag #1

Moony 4. Mär 2011 14:00

AW: TTextStream - Textdateien einlesen
 
Hallo,

ich habe mir mal deine Klasse runtergeladen und unter Windows XP funktioniert sie. Jetzt habe ich versucht meine Applikation auf einem Windows7 32Bit System zu starten und er kommt an der Stelle zu scheitern, wenn ich versuche den Textstream zu erstellen. Ich rufe folgendes beim Initialisieren auf:

Delphi-Quellcode:
var myFile : TTextStream;
...
myFile := TTextStream.Create(ChangeFileExt(Application.Exename, '.lng'), saRead);
try
  ...
finally
  myFile.Free;
end:
Hoffe auf baldige Hilfe!!!

Danke & Gruß, Moony

himitsu 4. Mär 2011 14:22

AW: TTextStream - Textdateien einlesen
 
Gut, dann erstmal ganz allgemein gefragt:

Wo liegt eine EXE und sicher daß dein Programm dort über ausreichende Zugriffsrechte verfügt?

Moony 4. Mär 2011 14:24

AW: TTextStream - Textdateien einlesen
 
Die Exe liegt in einem Ordner auf dem Desktop. Für diesen User ist bekanntlich in dem Userordner Schreibzugriff drauf.

himitsu 4. Mär 2011 14:39

AW: TTextStream - Textdateien einlesen
 
Gut, bei Win7 war's halt naheliegend, daß es daran liegen könnte, wenn es unter XP noch geht. :stupid:

Hmmm, wie heißt denn die Fehlermeldung und kannst du zufällig die Zeile nennen, in welcher es knallt?

Eigentlich wäre es sehr ungewöhnlich, wenn es genau in dieser Zeile knallt.
Delphi-Quellcode:
myFile := TTextStream.Create(ChangeFileExt(Application.Exename, '.lng'), saRead);
.

Ich glaub die Debuginfos hatte ich zu diesen Units nicht abgestellt, weswegen ich eher glauben würde der Debugger sollte weiter reingehn. :gruebel:
Notfalls mal einen Haltepunkt auf diese Zeile, dann Schritt für Schritt mit F7 weitergehn und die Zeile merken, welche aktiv war, bevor es knallte.

Moony 4. Mär 2011 14:46

AW: TTextStream - Textdateien einlesen
 
Nee, es kracht genau dort. Leider kann ich nicht debuggen, weil ich auf WinXp entwickel und die Applikation auf einem Win7 laufen soll.

Ich habe versucht mir Messages und ein Tracefile zu schreiben. Er ist bis zu der Create-Zeile mit den Messages gekommen und danach kam schon die Meldung dass die Funktion mit einer Exception beendet wurde.

Moony 7. Mär 2011 07:37

AW: TTextStream - Textdateien einlesen
 
Guten Morgen,

hmm, keine weitere Hilfe oder Idee zur Hand???

Gruß, Moony

himitsu 7. Mär 2011 07:50

AW: TTextStream - Textdateien einlesen
 
Tschuldschung.

Kann es zur Zeit schlecht testen/debuggen,
abgesehn davon, daß ich die letzten Tage flach lag und fast keinen klaren Gedanken fassen konnte. :vernupft:

Moony 7. Mär 2011 09:37

AW: TTextStream - Textdateien einlesen
 
Kein Problem....Gesundheit geht vor.

Debuggen kann ich leider auch nicht, sonst hätte ich das schon gemacht.
Die Frage wäre dann nur wie kommen wir dem Problem auf die Schliche?

Gruß, Moony

Sir Rufo 7. Mär 2011 10:36

AW: TTextStream - Textdateien einlesen
 
Zitat:

Zitat von Moony (Beitrag 1086395)
Kein Problem....Gesundheit geht vor.

Debuggen kann ich leider auch nicht, sonst hätte ich das schon gemacht.
Die Frage wäre dann nur wie kommen wir dem Problem auf die Schliche?

Gruß, Moony

Debugger defekt oder nicht mit dem Debugger vertraut?


Alle Zeitangaben in WEZ +1. Es ist jetzt 18:37 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