Como evito que TStrings.SaveToFile crie uma linha final vazia?

8

Eu tenho um arquivo .\input.txtcomo este:

aaa
bbb
ccc

Se eu o ler TStrings.LoadFromFilee escrevê-lo novamente (mesmo sem aplicar nenhuma alteração) TStrings.SaveToFile, ele criará uma linha vazia no final do arquivo de saída.

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

    //...

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

O mesmo comportamento pode ser observado usando a TStrings.Textpropriedade que retornará uma string contendo uma linha vazia no final.

Fabrizio
fonte
apenas imaginando, por que diabos você gostaria de escrevê-lo de volta, mesmo quando não há alterações aplicadas no arquivo? por que não simplesmente ler?
Bilal Ahmed
3
@BilalAhmed: certeza, é um teste simplificado, a mesma linha vazia aparece quando aplicar as alterações à lista de corda
Fabrizio
Por "cria uma linha vazia", ​​eu acho que você quer dizer que seu arquivo original não termina com o \ncaractere e a função adiciona o \nao arquivo? Ou a função literalmente adiciona um \nlogo após um existente \nno final do arquivo? O POSIX exige que os arquivos de texto tenham todas as suas linhas terminadas com a \n, apenas fyi. Lotes de software foi escrito para seguir algumas normas de modo que é por isso que muitos editores irá adicionar a terminação ausente \nquando você salvar arquivos por padrão (por exemplo vim, IDEs etc todos por padrão tornar seus arquivos compatível com POSIX.)
Giacomo Alzetta

Respostas:

12

Para o Delphi 10.1 e mais recente, existe uma propriedade que TrailingLineBreakcontrola esse comportamento.

Quando a propriedade TrailingLineBreak for True (valor padrão), a propriedade Text conterá quebra de linha após a última linha. Quando for Falso, o valor do Texto não conterá quebra de linha após a última linha. Isso também pode ser controlado pela opção soTrailingLineBreak.

Uwe Raabe
fonte
Ótima informação, estou trabalhando no Delphi2007 e DelphiXE7, mas certamente ficarei feliz em usar a TrailingLineBreakpropriedade assim que atualizar o IDE. +1 e aceito
Fabrizio
1

Para Delphi 10.1 (Berlin) ou mais recente, a melhor solução é descrita na resposta de Uwe.

Para versões mais antigas do Delphi, encontrei uma solução criando uma classe filho TStringListe substituindo a TStrings.GetTextStrfunção virtual, mas ficarei feliz em saber se existe uma solução melhor ou se alguém encontrou algo errado na minha solução.

Interface:

  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;

Implementação:

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;

Exemplo:

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
fonte
7
Vale ressaltar que seu código ocasionalmente faz isso SetLength(Result, -2).
Andreas Rejbrand
1
No seu GetTextStr, se Length(Result)é 0, então você faz SetLength(Result, -2), o que é ruim. Pode ser que o efeito seja o mesmo SetLength(Result, 0), mas não conheço nenhuma garantia sobre isso. A documentação oficial, pelo menos, não contém essa garantia. (Portanto, em teoria, coisas ruins podem acontecer.)
Andreas Rejbrand 17/10/1919
2
Mas agora você ainda tem outro bug! Se Length(Result) = 1, então você faz SetLength(Result, -1), o que é igualmente ruim! Além disso, pode ser que Resultnão termine com uma quebra de linha; nesse caso, você removerá os dois últimos caracteres da última linha. Isso também é um bug. (E isso pode acontecer, por exemplo, se você usar TrailingLineBreak, eu suspeito. Mesmo se não, pode haver outras instâncias.) Você realmente deve testar se a sequência realmente termina com uma quebra de linha, como if not IncludeLastLineBreakInText and Result.EndsWith(LineBreak) then.
Andreas Rejbrand 17/10/19
1
@AndreasRejbrand: Eu assumi que, na presença de qualquer caractere, os TStrings adicionariam pelo menos um LineBreak, mas esse comportamento pode mudar no futuro. Resposta atualizada novamente, obrigado
Fabrizio
1
Sinto muito, mas a nova condição ainda está errada ... :( Pos(LineBreak, Result) = Length(Result) - Length(LineBreak) + 1. PosFornece o índice da primeira correspondência. Se a string contém 6 quebras de linha, ela dará a posição da primeira, mas você claramente espera a última um ...
Andreas Rejbrand 17/10/19