Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Textdatei zum Lesen & Schreiben öffnen (https://www.delphipraxis.net/88342-textdatei-zum-lesen-schreiben-oeffnen.html)

glkgereon 14. Mär 2007 10:23


Textdatei zum Lesen & Schreiben öffnen
 
Hi,

Ich möchte eine TextDatei öffnen.
Mein Ideal wäre folgendes:
- Öffnen zum lesen und schreiben gleichzeitig
- Zeilenweise auslesen/reinschreiben (wichtig!)
- An bestimmte Zeile springen (Seek o.ä. für Zeilen)
- Einfügen in datei (wäre das nonplusultra^^)

Was ich nicht brauche ist byteweiser Zugriff...

Was nehme ich da am besten?
öffnen und schreiben gleichzeitig fällt textfile raus
zeilenweise fällt FileStream raus


was könnt man da noch nehmen?

himitsu 14. Mär 2007 10:40

Re: Textdatei zum Lesen & Schreiben öffnen
 
TStringList, damit wäre auch Einfügen/Entfernen gelöst.

Abgesehn von #13#10, #13, #10 und den anderen "Zeilenvorschüben" wird alles Byte für Byte (unverändert) verwaltet.
Und ein Byteweiser Zugriff wäre auch realisierbar ... entweder Stückchenweise/Zeilenweise, oder mit der Text-Eigenschaft über alles.


am Anfang alles per LoadFromFile da reinladen,
dann wild drin rumlesen/-ändern
und zum Schluß mit SaveToFile alles zurück in die Datei

Andidreas 14. Mär 2007 12:03

Re: Textdatei zum Lesen & Schreiben öffnen
 
oder mein vorschlag denn ich immer wieder erwähne...

ein 2. temporäres textfile erstellen in dem die änderungen gespeichert werden, original löschen, temp in original umbenennen....

himitsu 14. Mär 2007 12:18

Re: Textdatei zum Lesen & Schreiben öffnen
 
Zitat:

Zitat von Andidreas
ein 2. temporäres textfile erstellen in dem die änderungen gespeichert werden, original löschen, temp in original umbenennen....

geht aber nur, wenn man die Datei der Reihe nach abarbeitet und man nicht später rückwirkend an einer vorherigen Stelle was machen will.

glkgereon 14. Mär 2007 14:10

Re: Textdatei zum Lesen & Schreiben öffnen
 
Ähm ja, das hatte ich vergessen :wall:

Das ganze soll eine art Buffering werden.
d.h. ich will einen Editor haben welcher nicht die gesamte Datei im Speicher hat.
Daher kommt es ebenfalls nicht in Frage die gesamte Datei (TStringList) zu laden.

himitsu 14. Mär 2007 14:34

Re: Textdatei zum Lesen & Schreiben öffnen
 
Da wird es dann etwas schwieriger, wenn du die Anzahl/Länge der Strings in einem Teilbereich ändern willst.

Also entweder doch mit 2 Dateien und dann per ReadLn in der einen einlesen und per WriteLn in die andere Schreiben ... und dann halt Zeilen zwischenschieben, bzw. unerwünschte Zeilen einfach nicht mehr speichern.


- ansonsten mußt du ja die Datei selber nach einem Zeilenwechsel nahe der gewünschten Blockgröße durchsuchen
- dann den Bereich einlesen
- ändern...
- je nach Längenänderung des Blocks die nachfolgenden Daten in der Datei verschieben
- den Block speichern
- nach diesem Block dann den nächsten Zeilenwechsel suchen
- dann den Bereich einlesen
.....

glkgereon 14. Mär 2007 15:09

Re: Textdatei zum Lesen & Schreiben öffnen
 
Also es sollte so funktionieren:

Code:
Buffer an Stelle P laden:
Start = ScrollToLineStart(P - BufSize div 2)
Stop = ScrollToLineEnd(P + BufSize div 2)
Buffer = Copy(Start,Stop)

Buffer zurückschreiben:
Start = ScrollToLineStart(P - BufSize div 2)
Stop = ScrollToLineEnd(P + BufSize div 2)
Schieben(Stop,Size(Buffer)-BufSize) //Bytes ab Stelle Stop um x Bytes verschieben
Write(Buffer,Start)
Nur: Schieben???

[Edit]
Eine andere Änderung war folgendes:
den Text teilweise in Ram laden, aber NICHT ändern.
sozusagen bearbeitung im readonly-mode und die änderung werden entsprechend gespeichert...(wie wäre noch die frage)
beim speichern würden dann einfach die änderungen nach dem auftreten in der datei sortiert und die datei einmal von vorne nach hinten durchgeackert...

oder eine weitere möglichkeit (da ganze soll ein Editor für SEHR große dateien werden) die Datei read-only zu öffnen und bearbeiten nur als überschreiben zuzulassen (=länge konstant) und löschen oder einfügen nur als sonderfunktion ausserhalb des eigentlichen editors...

himitsu 14. Mär 2007 15:28

Re: Textdatei zum Lesen & Schreiben öffnen
 
Mir fängt eh grad der Kopf vor Treibern an zu rauchen ...
mal sehn ob ich schnell 'ne kleine Klasse zusammenbekomm. ^^

*Abwechslung brauch*

glkgereon 14. Mär 2007 16:15

Re: Textdatei zum Lesen & Schreiben öffnen
 
Ich hatte grad schon wieder ne neue Idee :lol:

Eine verkette Liste mit Strings...

Delphi-Quellcode:
TLine = class
  Next, Prev: TLine;
  Text: String;
  Changed: Boolean; //2 Byte?
end;
jedes repräsentiert eine Zeile...
Vorteil:
man könnte einmal die Datei "analysieren" und sich die Liste aufbauen (erzeugt natürlich Overhead :-( 10 Byte pro Zeile ist leider nicht wenig... )

Nun wird zum Laden einfach mittels eines gemoddeten TFileStreams zur x'ten Zeile gesprungen (x = Pos-(Bufsize div 2)) und mittels FS.ReadLn geladen. Alles was ausserhalb des gebufferten Bereichs liegt und nicht geändert wurde wird geleert (Text:='';)

Somit befindet sich immer nur der Overhead, die gebufferten Zeilen und die geänderten Zeilen im Speicher. Ich denke deutlich besser geht es nicht^^
Beispielrechung (Das Beispiel ist eine (der kleineren) Datei die ich vorliegen hab, also durchaus realistisch ;-) ):
2 Mio Zeilen
ca. 50 Zeichen/Zeile
1000 Zeilen editiert
Buffer: 100000 Zeilen

Dateigröße: 2Mio * 50 = 100MB
Overhead: 10*2Mio = 20MB
Buffer: 100000*50 = 5MB
Geändert: 1000*50 = 50KB (Die höchstwahrscheinlich schon im Buffer sind)

Das ganze ist eigentlich genial:
Einfügen von Zeilen ist dank der verketteten Liste ein Kinderspiel, einfügen von Buchstaben kann in handlichen Stücken (in der Zeile) erfolgen. Löschen ist ebenfalls kein Problem.

Einziges Problem ist das Speichern, aber auch das sollte gar nicht so schwer sein...Mein Ansatz: Speichern in eine neue Datei, alte Datei löschen, neue umbenennen, fertig. ;-)
Also Zeile für Zeile durchgehen, falls was im Buffer steht das nehmen, ansonsten aus der alten Datei lesen.
Event. sogar das 2-Dateien-System an den User weitergeben und mit einer "Quelldatei" und einer "Arbeitsdatei" basteln lassen.

Nun, was natürlich stört ist die Dateigröße...
event. wäre es ja möglich die liste nur so groß zu machen wie der Buffer ist und somit den overhead auf ein minimum beschränken...
allerdings ist dann das mit dem ändern nicht mehr so schön einfach :-/
ideen?

himitsu 14. Mär 2007 17:08

Re: Textdatei zum Lesen & Schreiben öffnen
 
OK, das wäre och noch 'ne Möglichkeit ... hast'e da inzwischen schon was zusammen?

hier mal meine Klasse.
- ist bisher aber noch nicht getestet worden
(nur im Kopf entstanden ... ich hoff mal da sind keine großen Denkfehler drin)
- aber der Compiler meckert schonmal nicht :stupid:

Overhead wäre hierbei "nur" der aktuell geladene Block,
allerdings würde natürlich beim Speichern eines Blockes eventuell der gesamte nachfolgende Teil verschoben werden.

Delphi-Quellcode:
.
  Type TPartialTextfile = Class
    Private
      _FileName:        String;
      _FileHandle:      THandle;
      _FileSize, _Start: Int64;
      _OrgLen:          Integer;
      _Part:            TStringList;
      Function _FindNextLineBreak(Const i: Int64): Integer;
    Public
      Constructor Create;
      Destructor Destroy; Override;
      Property FileName: String     Read _FileName;
      Property FileSize: Int64       Read _FileSize;
      Property Start:   Int64       Read _Start;
      Property OrgLen:  Integer    Read _OrgLen;
      Property Part:    TStringList Read _Part;
      Function Open     (Const FileName: String): Boolean;
      Function LoadPart (Const Pos: Int64; MinLen: Integer): Boolean; Overload;
      Function LoadPart (      {next part} MinLen: Integer): Boolean; Overload;
      Function LoadPartLi(Const Pos: Int64; Lines: Integer): Boolean; Overload;
      Function LoadPartLi(      {next part} Lines: Integer): Boolean; Overload;
      Function SavePart: Boolean;
      Procedure Close;
    End;

  Function TPartialTextfile._FindNextLineBreak(Const i: Int64): Integer;
    Var B:  Array[0..65535] of Char;
      i2:   LARGE_INTEGER;
      W, W2: Cardinal;

    Begin
      Result := 0;
      Repeat
        i2.QuadPart := i + Result;
        i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
        If i2.QuadPart <> i + Result Then Exit;
        ReadFile(_FileHandle, B, SizeOf(B), W, nil);
        If (W <> SizeOf(B)) and (W <> _FileSize - i - Result) Then Exit;
        W2 := 0;
        While W2 < W do Begin
          Inc(W2);
          Case B[W2 - 1] of
            #0, #10: Break;
            #13: Begin
              If W2 > W Then Begin
                i2.QuadPart := i + Result + W2;
                i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
                If i2.QuadPart = i + Result + W2 Then Begin
                  ReadFile(_FileHandle, B, 1, W, nil);
                  If (W = 1) and (B[0] = #10) Then Inc(W2);
                End;
              End Else If B[W2] = #10 Then Inc(W2);
              Break;
            End;
          End;
        End;
        Inc(Result, W2);
      Until W < SizeOf(B);
    End;

  Constructor TPartialTextfile.Create;
    Begin
      _FileHandle := INVALID_HANDLE_VALUE;
      _Part      := TStringList.Create;
    End;

  Destructor TPartialTextfile.Destroy;
    Begin
      _Part.Free;
    End;

  Function TPartialTextfile.Open(Const FileName: String): Boolean;
    Var i64: LARGE_INTEGER;

    Begin
      If _FileHandle <> INVALID_HANDLE_VALUE Then Close;
      _FileHandle := CreateFile(PChar(FileName), GENERIC_READ   or GENERIC_WRITE,
        FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, 0);
      If _FileHandle <> INVALID_HANDLE_VALUE Then Begin
        i64.LowPart := GetFileSize(_FileHandle, @i64.HighPart);
        _FileName  := FileName;
        _FileSize  := i64.QuadPart;
        Result     := True;
      End Else Result := False;
    End;

  Function TPartialTextfile.LoadPart(Const Pos: Int64; MinLen: Integer): Boolean;
    Var S: String;
      W:  Cardinal;
      i:  LARGE_INTEGER;

    Begin
      Result := False;
      If (_FileHandle = INVALID_HANDLE_VALUE) or (Pos > _FileSize) or (MinLen <= 0) Then Exit;
      Try
        SetLength(S, _FindNextLineBreak(Pos + MinLen));
      Except
        Exit;
      End;
      i.QuadPart := Pos;
      i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN);
      If i.QuadPart <> Pos Then Exit;
      ReadFile(_FileHandle, S[1], Length(S), W, nil);
      If Length(S) <> Integer(W) Then Exit;
      Try
        _Start    := Pos;
        _OrgLen   := Length(S);
        _Part.Text := S;
      Except
        _OrgLen := 0;
        _Part.Clear;
        Exit;
      End;
      Result := True;
    End;

  Function TPartialTextfile.LoadPart(MinLen: Integer): Boolean;
    Begin
      Result := LoadPartLi(_Start + _OrgLen, MinLen);
    End;

  Function TPartialTextfile.LoadPartLi(Const Pos: Int64; Lines: Integer): Boolean;
    Var S: String;
      W:  Cardinal;
      i:  LARGE_INTEGER;

    Begin
      Result := False;
      If (_FileHandle = INVALID_HANDLE_VALUE) or (Pos > _FileSize) or (Lines <= 0) Then Exit;
      W := 0;
      While Lines > 0 do Begin
        Inc(W, _FindNextLineBreak(Pos + W));
        If Integer(W) < 0 Then Exit;
        Dec(Lines);
      End;
      Try
        SetLength(S, W);
      Except
        Exit;
      End;
      i.QuadPart := Pos;
      i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN);
      If i.QuadPart <> Pos Then Exit;
      ReadFile(_FileHandle, S[1], Length(S), W, nil);
      If Length(S) <> Integer(W) Then Exit;
      Try
        _Start    := Pos;
        _OrgLen   := Length(S);
        _Part.Text := S;
      Except
        _OrgLen := 0;
        _Part.Clear;
        Exit;
      End;
      Result := True;
    End;

  Function TPartialTextfile.LoadPartLi(Lines: Integer): Boolean;
    Begin
      Result := LoadPartLi(_Start + _OrgLen, Lines);
    End;

  Function TPartialTextfile.SavePart: Boolean;
    Var S:  String;
      W, W2: Cardinal;
      B:    Array[0..65535] of Char;
      i, i2: LARGE_INTEGER;
      i3:   Integer;

    Begin
      Result := False;
      If _FileHandle = INVALID_HANDLE_VALUE Then Exit;
      Try
        S := _Part.Text;
      Except
        Exit;
      End;
      i3 := _OrgLen - Length(S);
      If i3 > 0 Then Begin
        i.QuadPart := _Start + _OrgLen;
        While i.QuadPart < _FileSize do Begin
          i2.QuadPart := i.QuadPart;
          i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
          If i2.QuadPart <> i.QuadPart Then Exit;
          ReadFile(_FileHandle, B, SizeOf(B), W, nil);
          If (W <> SizeOf(B)) and (W <> _FileSize - i.QuadPart) Then Exit;
          W2          := W;
          i2.QuadPart := i.QuadPart - i3;
          i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
          If i2.QuadPart <> i.QuadPart - i3 Then Exit;
          WriteFile(_FileHandle, B, W2, W, nil);
          If W <> W2 Then Exit;
          Inc(i.QuadPart, SizeOf(B));
        End;
      End Else If i3 < 0 Then Begin
        i3         := -i3;
        i.QuadPart := _Start + _OrgLen;
        Inc(i.QuadPart, (_FileSize - i.QuadPart) and -SizeOf(B));
        While i.QuadPart >= _Start + _OrgLen do Begin
          i2.QuadPart := i.QuadPart;
          i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
          If i2.QuadPart <> i.QuadPart Then Exit;
          ReadFile(_FileHandle, B, SizeOf(B), W, nil);
          If (W <> SizeOf(B)) and (W <> _FileSize - i.QuadPart) Then Exit;
          W2          := W;
          i2.QuadPart := i.QuadPart + i3;
          i2.LowPart := SetFilePointer(_FileHandle, i2.LowPart, @i2.HighPart, FILE_BEGIN);
          If i2.QuadPart <> i.QuadPart + i3 Then Exit;
          WriteFile(_FileHandle, B, W2, W, nil);
          If W <> W2 Then Exit;
          dec(i.QuadPart, SizeOf(B));
        End;
      End;
      Dec(_FileSize, i3);
      i.QuadPart := _FileSize;
      i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN);
      If i.QuadPart = _FileSize Then SetEndOfFile(_FileHandle);
      i.QuadPart := _Start;
      i.LowPart := SetFilePointer(_FileHandle, i.LowPart, @i.HighPart, FILE_BEGIN);
      If i.QuadPart <> _Start Then Exit;
      WriteFile(_FileHandle, S[1], Length(S), W, nil);
      _OrgLen := Length(S);
      Result := Length(S) <> Integer(W);
    End;

  Procedure TPartialTextfile.Close;
    Begin
      CloseHandle(_FileHandle);
      _FileName  := '';
      _FileHandle := INVALID_HANDLE_VALUE;
      _FileSize  := 0;
      _Start     := 0;
      _OrgLen    := 0;
      _Part.Clear;
    End;
Delphi-Quellcode:
// Edit:   TEdit;
// Button1: TButton;

  Procedure TForm1.Button1Click(Sender: TObject);
    Var PT: TPartialTextfile;

    Begin
      PT := TPartialTextfile.Create;
      PT.Open('Unit1.pas');
      Memo1.Lines.Clear;
      Repeat
        PT.LoadPartLi(10);
        Memo1.Lines.Add(Format('*** %d, %d/%d', [PT.Start, PT.OrgLen, Length(PT.Part.Text)]));
        Memo1.lines.AddStrings(PT.Part);
        Memo1.Lines.Add('');
      Until PT.Part.Text = '';
      PT.Free;
    End;
[edit]
Fehler im Code behoben
das Auslesen funktioniert anscheinend
und auch eine kleine Testprozedur ist nun mit dabei


[add]
wenn du die geänderten Strings erstmal hinter dem "originalem" Dateiende in der Datei speicherst, dann würde auch einer dieser beiden Varianten für deine Idee ausreichen ... außerdem würden die dann nicht im RAM liegen, was bei vielen Änderungen in großen Dateien besser wäre.
Delphi-Quellcode:
PLine = ^TLine;
TLine = packed record
  Next, Prev: PLine;
  TextPos: Int64;
  TextLen: Integer;
end;

TLineArray = Array of Record
  TextPos: Int64;
  TextLen: Integer;
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:07 Uhr.
Seite 1 von 2  1 2      

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