¿Cómo evito que TStrings.SaveToFile cree una línea vacía final?

8

Tengo un archivo .\input.txtcomo este:

aaa
bbb
ccc

Si lo leo usando TStrings.LoadFromFiley lo escribo de nuevo (incluso sin aplicar ningún cambio) TStrings.SaveToFile, crea una línea vacía al final del archivo de salida.

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

    //...

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

Se puede observar el mismo comportamiento utilizando la TStrings.Textpropiedad que devolverá una cadena que contiene una línea vacía al final.

Fabrizio
fuente
solo me pregunto, ¿por qué querrías escribirlo de nuevo incluso cuando no hay ningún cambio aplicado en el archivo? ¿Por qué no simplemente leerlo?
Bilal Ahmed
3
@BilalAhmed: claro, es una prueba simplificada, aparece la misma línea vacía cuando se aplican cambios a la lista de cadenas
Fabrizio
Por "crea una línea vacía" ¿Supongo que quiere decir que su archivo original no termina con el \ncarácter y la función agrega \nel archivo? ¿O la función agrega literalmente un \nderecho después de uno existente \nal final del archivo? POSIX requiere que los archivos de texto tengan todas sus líneas terminadas por un \n, solo para su información. Un montón de software fue escrito para seguir algunas normas y por eso una gran cantidad de editores añadirá la terminación falta \nal guardar archivos por defecto (por ejemplo vim, entornos de desarrollo, etc., todo de forma predeterminada hacer que sus archivos compatible con POSIX.)
Giacomo Alzetta

Respuestas:

12

Para Delphi 10.1 y posteriores hay una propiedad que TrailingLineBreakcontrola este comportamiento.

Cuando la propiedad TrailingLineBreak es True (valor predeterminado), la propiedad Text contendrá un salto de línea después de la última línea. Cuando es falso, el valor del texto no contendrá el salto de línea después de la última línea. Esto también puede controlarse mediante la opción soTrailingLineBreak.

Uwe Raabe
fuente
Gran información, estoy trabajando en Delphi2007 y DelphiXE7, pero seguramente me complacerá usar la TrailingLineBreakpropiedad tan pronto como actualice el IDE. +1 y aceptado
Fabrizio
1

Para Delphi 10.1 (Berlín) o posterior, la mejor solución se describe en la respuesta de Uwe.

Para versiones anteriores de Delphi, encontré una solución creando una clase secundaria TStringListy anulando la TStrings.GetTextStrfunción virtual, pero me alegrará saber si hay una mejor solución o si alguien más encontró algo incorrecto en mi solución

Interfaz:

  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;

Implementación:

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;

Ejemplo:

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
fuente
77
Vale la pena señalar que su código ocasionalmente lo hace SetLength(Result, -2).
Andreas Rejbrand
1
En tu GetTextStr, si Length(Result)es 0, entonces lo haces SetLength(Result, -2), lo cual es malo. Podría ser el caso de que el efecto sea el mismo SetLength(Result, 0), pero no tengo ninguna garantía al respecto. La documentación oficial, al menos, no contiene dicha garantía. (Entonces, en teoría, podrían suceder cosas malas)
Andreas Rejbrand
2
¡Pero ahora todavía tienes otro error! Si Length(Result) = 1, entonces lo haces SetLength(Result, -1), ¡lo cual es igualmente malo! Además, podría ser el caso que Resultno termine con un salto de línea, en cuyo caso eliminará los dos últimos caracteres de la última línea. Eso también es un error. (Y eso podría suceder, por ejemplo, si lo usa TrailingLineBreak, sospecho. Incluso si no, puede haber otras instancias). Realmente debería probar si la cadena realmente termina con un salto de línea, como if not IncludeLastLineBreakInText and Result.EndsWith(LineBreak) then.
Andreas Rejbrand
1
@AndreasRejbrand: Di por sentado que en presencia de cualquier carácter, los TStrings agregarían al menos un LineBreak, pero este comportamiento podría cambiar en el futuro. Respuesta actualizada nuevamente, gracias
Fabrizio
1
Lo siento, pero la nueva condición sigue siendo incorrecta ... :( Pos(LineBreak, Result) = Length(Result) - Length(LineBreak) + 1. PosDa el índice de la primera coincidencia. Si su cadena contiene 6 saltos de línea, le dará la posición de la primera, pero claramente espera la última one ...
Andreas Rejbrand