1
votes

Good evening all.

I have a StringList and each record contains a multi-line string.

MyStringList [0]:

<li>
  Test1
</li>

MyStringList [1]:

<a href="#">
  <b>      
    Test2
  </b>
</a>

etc.

How can I save and load those strings to a file (text, ini, binary, record, savetofile, etc.) ? The main issue is than I can save it to a text file, but when reading back, each line is considered a new record while there are 3 lines for the first record, 5 lines for the second and so on.

What do you suggest for a process to save and load those strings ?

1
If you save it as a text file then there's no chance of recovering the information.David Heffernan
Your MyStringList[0] is a single line string. <li> does not cause a line break; <li>Test1</li> in a single line is totally valid HTML. If you write it with line breaks to a file, it will be read back as separate lines. If you've stored <li>line break` Test1line break</li>`, then it's not a single line string.Ken White
@Ken I take it that the strings in the string list contain linebreaksDavid Heffernan
Indeed, it does contain linebreaks: '<li> #10#13 Test1 #10#13 </li>'David K.
Are you prepared to save a separate file which says where to put the linebreaks?David Heffernan

1 Answers

5
votes

If you save it to a .txt file, you lose the ability to re-load it correctly, since you don't know if a give line break was originally embedded in a string, or separates two strings.

If you save it to another textual format, like .ini, you can escape/unescape the line breaks as needed, eg:

function Encode(const S: String): String;
begin
  Result := StringReplace(S, '<', '<<', [rfReplaceAll]);
  Result := StringReplace(Result, #13#10, '<CRLF>', [rfReplaceAll]);
  Result := StringReplace(Result, #13, '<CR>', [rfReplaceAll]);
  Result := StringReplace(Result, #10, '<LF>', [rfReplaceAll]);
end;

Ini := TIniFile.Create(...);
try
  Ini.WriteInteger('section', 'count', MyStringList.Count);
  for I := 0 to MyStringList.Count-1 do
    Ini.WriteString('section', IntToStr(I), Encode(MyStringList[I]);
finally
  Ini.Free;
end;

function Decode(const S: String): String;
begin
  Result := StringReplace(S, '<LF>', #10, [rfReplaceAll]);
  Result := StringReplace(Result, '<CR>', #13, [rfReplaceAll]);
  Result := StringReplace(Result, '<CRLF>', #13#10, [rfReplaceAll]);
  Result := StringReplace(Result, '<<', '<', [rfReplaceAll]);

end;

Ini := TIniFile.Create(...);
try
  Count := Ini.ReadInteger('section', 'count', 0);
  for I := 0 to Count-1 do
    MyStringList.Add(Decode(Ini.ReadString('section', IntToStr(I), ''));
finally
  Ini.Free;
end;

If you save it to a binary format, you can preserve the line breaks as-is, eg:

procedure WriteIntegerToStream(Stream: TStream; Value: Integer);
begin
  Stream.WriteBuffer(Value, SizeOf(Integer));
end;

procedure WriteStringToStream(Stream: TStream; const Value: String);
var
  U: UTF8String;
  Count: Integer;
begin
  U := UTF8String(Value); // or UTF8Encode(Value) prior to D2009
  Count := Length(U);
  WriteIntegerToStream(Stream, Count);
  if Count > 0 then
    Stream.WriteBuffer(PAnsiChar(U)^, Count * SizeOf(AnsiChar));
end;

Strm := TFileStream.Create(..., fmCreate);
try
  WriteIntegerToStream(Stream, MyStringList.Count);
  for I := 0 to MyStringList.Count-1 do
    WriteStringToStream(Stream, MyStringList[I]);
finally
  Stream.Free;
end;

function ReadIntegerFromStream(Stream: TStream): Integer;
begin
  Stream.ReadBuffer(Result, SizeOf(Integer));
end;

function ReadStringFromStream(Stream: TStream): String;
var
  Count: Integer;
  U: UTF8String;
begin
  Count := ReadIntegerFromStream(Stream);
  if Count > 0 then
  begin
    SetLength(U, Count);
    Stream.ReadBuffer(PAnsiChar(U)^, Count * SizeOf(AnsiChar));
    Result := String(U); // or UTF8Decode(U) prior to D2009
  end else
    Result := '';
end;

Stream := TFileStream.Create(..., fmOpenRead or fmShareDenyWrite);
try
  Count := ReadIntegerFromStream(Stream);
  for I := 0 to Count-1 do
    MyStringList.Add(ReadStringFromStream(Stream));
finally
  Stream.Free;
end;