Wie verhindere ich, dass TStrings.SaveToFile eine letzte leere Zeile erstellt?

8

Ich habe eine Datei .\input.txtwie diese:

aaa
bbb
ccc

Wenn ich es mit lese TStrings.LoadFromFileund zurückschreibe (auch ohne Änderungen vorzunehmen) TStrings.SaveToFile, wird am Ende der Ausgabedatei eine leere Zeile erstellt.

var
  Lines : TStrings;
begin
  Lines := TStringList.Create;
  try
    Lines.LoadFromFile('.\input.txt');

    //...

    Lines.SaveToFile('.\output.txt');
  finally
    Lines.Free;
  end;
end;

Das gleiche Verhalten kann mit der TStrings.TextEigenschaft beobachtet werden, die eine Zeichenfolge zurückgibt, die am Ende eine leere Zeile enthält.

Fabrizio
quelle
Ich frage mich nur, warum um alles in der Welt möchten Sie es zurückschreiben, auch wenn in der Datei keine Änderung vorgenommen wurde? warum nicht einfach lesen?
Bilal Ahmed
3
@BilalAhmed: Sicher, es ist ein vereinfachter Test. Die gleiche leere Zeile wird angezeigt, wenn Änderungen an der Zeichenfolgenliste vorgenommen werden
Fabrizio,
Mit "erstellt eine leere Zeile" meinen Sie wohl, dass Ihre Originaldatei nicht mit dem \nZeichen endet und die Funktion das \nder Datei hinzufügt ? Oder fügt die Funktion buchstäblich ein \nRecht nach einem \nam Ende der Datei vorhandenen hinzu? Für POSIX müssen Textdateien alle Zeilen mit einem \n, nur zu Ihrer Information, abgeschlossen haben. Es wurde viel Software geschrieben, um einigen Standards zu entsprechen. Aus diesem Grund fügen viele Redakteure die fehlende Terminierung hinzu, \nwenn Sie Dateien standardmäßig speichern (z. B. vimIDEs usw. machen Ihre Dateien standardmäßig POSIX-konform.)
Giacomo Alzetta

Antworten:

12

Für Delphi 10.1 und höher gibt es eine Eigenschaft TrailingLineBreak die dieses Verhalten steuert.

Wenn die TrailingLineBreak-Eigenschaft True ist (Standardwert), enthält die Text-Eigenschaft einen Zeilenumbruch nach der letzten Zeile. Wenn es falsch ist, enthält der Textwert keinen Zeilenumbruch nach der letzten Zeile. Dies kann auch durch die Option soTrailingLineBreak gesteuert werden.

Uwe Raabe
quelle
Tolle Informationen, ich arbeite an Delphi2007 und DelphiXE7, aber ich werde die TrailingLineBreakEigenschaft sicherlich gerne nutzen, sobald ich die IDE aktualisiere. +1 und akzeptiert
Fabrizio
1

Für Delphi 10.1 (Berlin) oder neuer wird die beste Lösung in Uwes Antwort beschrieben.

Für ältere Delphi-Versionen habe ich eine Lösung gefunden, indem ich eine untergeordnete Klasse TStringListder TStrings.GetTextStrvirtuellen Funktion erstellt und diese überschrieben habe. Ich bin jedoch froh zu wissen, ob es eine bessere Lösung gibt oder ob jemand anderes in meiner Lösung einen Fehler gefunden hat

Schnittstelle:

  uses
    Classes;

  type
    TMyStringList = class(TStringList)
    private
      FIncludeLastLineBreakInText : Boolean;
    protected
      function GetTextStr: string; override;
    public
      constructor Create(AIncludeLastLineBreakInText : Boolean = False); overload;
      property IncludeLastLineBreakInText : Boolean read FIncludeLastLineBreakInText write FIncludeLastLineBreakInText;
    end;

Implementierung:

uses
  StrUtils;      

constructor TMyStringList.Create(AIncludeLastLineBreakInText : Boolean = False);
begin
  inherited Create;

  FIncludeLastLineBreakInText := AIncludeLastLineBreakInText;
end;

function TMyStringList.GetTextStr: string;
begin
  Result := inherited;

  if(not IncludeLastLineBreakInText) and EndsStr(LineBreak, Result)
  then SetLength(Result, Length(Result) - Length(LineBreak));
end;

Beispiel:

procedure TForm1.Button1Click(Sender: TObject);
var
  Lines : TStrings;
begin
  Lines := TMyStringList.Create();
  try
    Lines.LoadFromFile('.\input.txt');
    Lines.SaveToFile('.\output.txt');
  finally
    Lines.Free;
  end;
end;
Fabrizio
quelle
7
Es ist erwähnenswert, dass Ihr Code dies gelegentlich tut SetLength(Result, -2).
Andreas Rejbrand
1
In Ihrem GetTextStr, wenn Length(Result)ist 0, dann tun Sie SetLength(Result, -2), das ist schlecht. Es mag sein, dass der Effekt der gleiche ist wie SetLength(Result, 0), aber ich kenne keine Garantie dafür. Zumindest die offizielle Dokumentation enthält keine solche Garantie. (Theoretisch könnten also schlimme Dinge passieren.)
Andreas Rejbrand
2
Aber jetzt hast du noch einen Fehler! Wenn Length(Result) = 1ja, dann tust du das SetLength(Result, -1), was genauso schlimm ist! Außerdem kann es sein, dass Resultdies nicht mit einem Zeilenumbruch endet. In diesem Fall entfernen Sie die beiden letzten Zeichen aus der letzten Zeile. Das ist auch ein Fehler. (Und das kann zum Beispiel passieren, wenn Sie es verwenden TrailingLineBreak, vermute ich. Auch wenn nicht, kann es andere Instanzen geben.) Sie sollten wirklich testen, ob die Zeichenfolge wirklich mit einem Zeilenumbruch endet, wie z if not IncludeLastLineBreakInText and Result.EndsWith(LineBreak) then.
Andreas Rejbrand
1
@AndreasRejbrand: Ich nahm an, dass die TStrings in Gegenwart eines Zeichens mindestens einen LineBreak hinzufügen würden, aber dieses Verhalten könnte sich in Zukunft ändern. Antwort erneut aktualisiert, danke
Fabrizio
1
Es tut mir leid, aber die neue Bedingung ist immer noch falsch ... :( Pos(LineBreak, Result) = Length(Result) - Length(LineBreak) + 1. PosGibt den Index der ersten Übereinstimmung an. Wenn Ihre Zeichenfolge 6 Zeilenumbrüche enthält, wird die Position der ersten angegeben, aber Sie erwarten eindeutig die letzte eins ...
Andreas Rejbrand