Saving a string with null characters to a file - delphi

I have a string that contains null characters.
I've tried to save it to a file with this code:
myStringList.Text := myString;
myStringList.SaveToFile('c:\myfile');
Unfortunately myStringList.Text is empty if the source string has a null character at the beginning.
I thought only C string were terminated by a null character, and Delphi was always fine with it.
How to save the content of the string to a file?

I think you mean "save a string that has #0 characters in it".
If that's the case, don't try and put it in a TStringList. In fact, don't try to save it as a string at all; just like in C, a NULL character (#0 in Delphi) causes the string to be truncated at times. Use a TFileStream and write it directly as byte content:
var
FS: TFileStream;
begin
FS := TFileStream.Create('C:\MyFile', fmCreate);
try
FS.Write(myString[1], Length(myString) * SizeOf(Char));
finally
FS.Free;
end;
end;
To read it back:
var
FS: TFileStream;
begin
FS := TFileStream.Create('C:\MyFile', fmOpenRead);
try
SetLength(MyString, FS.Size);
FS.Read(MyString[1], FS.Size);
finally
FS.Free;
end;
end;

When you set the Text property of a TStrings object, the new value is parsed as a null-terminated string. Therefore when the code reaches your null character, the parsing stops.
I'm not sure why the Delphi RTL code was designed that way, and its not documented, but that's just how setting the Text property works.
You can avoid this by using the Add method rather than the Text property.
myStringList.Clear;
myStringList.Add(myString);
myStringList.SaveToFile(FileName);

About writing strings to a file in general.. I still see people creating streams or stringlists just to write some stuff to a file, and then destroy the stream or stringlist.
Delphi7 didn't have IOUtuls.pas yet, but you're missing out on that.
There's a handy TFile record with class methods that lets you write text to a file with a single line, without requiring temporary variables:
TFile.WriteAllText('out.txt','hi');
Upgrading makes your life as a Delphi developer a lot easier. This is just a tiny example.

Related

How can I trim / replace the first character in a FileStream in Delphi?

I have the following code for a series of file drawdowns using IDHttp.Get, the contents of the files
procedure Tform1.GetData;
{***************************}
var
fs2 : tfilestream;
s : char;
begin
Sleep(1000);
idhttp1.HandleRedirects := TRUE;
fsjson2 := tfilestream.Create((GstrPath+GstrRep+'-'+GstrHome+'.json'),fmcreate);;
idhttp1.IOHandler := idssl;
IdSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
IdSSL.SSLOptions.Mode := sslmUnassigned;
try
idhttp1.Get(GstrURL,fs2);
except
on E: Exception do
begin
rememo1.Lines.Add('Seems to be an issue, trying again...');
Sleep(500);
idhttp1.Get(GstrURL,fs2);
end;
end;
I would like there to be a method of either trimming the first character although I don't think this is possible, or replacing the first character (which comes with the info by default) with a blank character. I think it's a little out of my skillset at the moment to do, and so would appreciate any help someone can give.
Thanks
Ant
There are several possible solutions:
You are receiving data into a stream. If you want to trim the first character, you could, at the place you handle the stream, skip reading the first character and throw it away.
If you have no control on where the receiving stream is handled, then you may simply create a new stream and loop reading all characters from the receiving stream and write them into the destination stream. Then throw away the received stream and keep the one you created.
If the data received (currently the stream) is not too big, you could receive it into a string instead of a stream, then you can trim/delete/insert anything with simple string manipulation, and finally write the modified string back to a stream for later use.

Encoding in Indy 10 and Delphi

I am using Indy 10 with Delphi. Following is my code which uses EncodeString method of Indy to encode a string.
var
EncodedString : String;
StringToBeEncoded : String;
EncoderMIME: TIdEncoderMIME;
....
....
EncodedString := EncoderMIME.EncodeString(StringToBeEncoded);
I am not getting the correct value in encoded sting.
What is the purpose of IndyTextEncoding_OSDefault?
Here's the source code for IndyTextEncoding_OSDefault.
function IndyTextEncoding_OSDefault: IIdTextEncoding;
begin
if GIdOSDefaultEncoding = nil then begin
LEncoding := TIdMBCSEncoding.Create;
if InterlockedCompareExchangeIntf(IInterface(GIdOSDefaultEncoding), LEncoding, nil) <> nil then begin
LEncoding := nil;
end;
end;
Result := GIdOSDefaultEncoding;
end;
Note that I stripped out the .net conditional code for simplicity. Most of this code is to arrange singleton thread-safety. The actual value returned is synthesised by a call to TIdMBCSEncoding.Create. Let's look at that.
constructor TIdMBCSEncoding.Create;
begin
Create(CP_ACP, 0, 0);
end;
Again I've remove conditional code that does not apply to your Windows setting. Now, CP_ACP is the Active Code Page, the current system Windows ANSI code page. So, on Windows at least, IndyTextEncoding_OSDefault is an encoding for the current system Windows ANSI code page.
Why did using IndyTextEncoding_OSDefault give the same behaviour as my Delphi 7 code?
That's because the Delphi 7 / Indy 9 code for TEncoderMIME.EncodeString does not perform any code page transformation and MIME encodes the input string as though it were a byte array. Since the Delphi 7 string is encoded in the active ANSI code page, this has the same effect as passing IndyTextEncoding_OSDefault to TEncoderMIME.EncodeString in your Unicode version of the code.
What is the difference between IndyTextEncoding_Default and IndyTextEncoding_OSDefault?
Here is the source code for IndyTextEncoding_OSDefault:
function IndyTextEncoding_Default: IIdTextEncoding;
var
LType: IdTextEncodingType;
begin
LType := GIdDefaultTextEncoding;
if LType = encIndyDefault then begin
LType := encASCII;
end;
Result := IndyTextEncoding(LType);
end;
This returns an encoding that is determined by the value of GIdDefaultTextEncoding. By default, GIdDefaultTextEncoding is encASCII. And so, by default, IndyTextEncoding_Default yields an ASCII encoding.
Beyond all this you should be asking yourself which encoding you want to be using. Relying on default values leaves you at the mercy of those defaults. What if those defaults don't do what you want to do. Especially as the defaults are not Unicode encodings and so support only a limited range of characters. And what's more are dependent on system settings.
If you wish to encode international text, you would normally choose to use the UTF-8 encoding.
One other point to make is that you are calling EncodeString as though it were an instance method, but it is actually a class method. You can remove EncoderMIME and call TEncoderMIME.EncodeString. Or keep EncoderMIME and call EncoderMIME.Encode.

How can a message client read an attachment downloaded by indy?

I have a message client, written in delphi using Indy libraries, that receives email messages. I am having difficulties decoding an MMS text message email.
These messages come as multipart/mixed emails with one message part (an attachment) that of text/plain (that is base64 encoded) with a filename like text0.txt.
My TIdMessageClient calls ProcessMessage (using the stream-based version) to populate a TidMessage that I'm going to display on the screen. But as I go through the message parts and try to unravel them, that attached file is a thorn in my side. Currently, I have it printing out the name of the attachment into a string which works fine (see code snippet below, FBody is a string type), but can't get the text file's contents.
Here's the bit that does work:
FBody := 'Attachment: ['+TidAttachment(Msg.MessageParts.Items[0]).FileName+']';
(Edited:) Originally when I wrote this question I wasn't sure if the attachment was stored in a TidAttachmentFile or TidAttachmentMemory object. But with the right debugger commands, I've determined it's a TidAttachmentFile. I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message). I would really prefer to do this all "in memory" without temp files if possible.
What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage), and (b) read the attached text file into a string?
Based on the indy documentation, the start I have at how this code would look is roughly like this:
TidAttachmentMemory(Msg.MessageParts.Items[0]).PrepareTempStream();
FBody := FBody + TidAttachmentMemory(Msg.MessageParts.Items[0]).DataString;
TidAttachmentMemory(Msg.MessageParts.Items[0]).FinishTempStream;
Please feel free to point me in the right direction if this is not the right way to go or use TidAttachment(s).
I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message).
When using TIdAttachmentFile, the file is always on disk. The TIdAttachmentFile.StoredPathName property specifies the path to the actual file. The TIdAttachmentFile.SaveToFile() method merely copies the file to the specified location.
I would really prefer to do this all "in memory" without temp files if possible.
It is possible.
What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage)
In the TIdMessage.OnCreateAttachment event, return a TIdAttachmentMemory object, eg:
procedure TMyForm.IdMessage1CreateAttachment(const AMsg: TIdMessage; const AHeaders: TStrings; var AAttachment: TIdAttachment);
begin
AAttachment := TIdAttachmentMemory.Create(AMsg.MessageParts);
end;
If no handler is assigned to the TIdMessage.OnCreateAttachment event, or if it does not assign anything to AAttachment, then a TIdAttachmentFile is created by default.
You could optionally implement your own custom TIdAttachment-derived class instead, say one that uses TStringStream internally if you know the attachment contains textual data (which the AHeaders parameter will tell you).
and (b) read the attached text file into a string?
Based on the indy documentation, the start I have at how this code would look is roughly like this:
You are close. You need to use the TIdAttachment.OpenLoadStream() method instead of TIdAttachment.PrepareTempStream(), and you need to read the data from the TStream that TIdAttachment.OpenLoadStream() returns. In your example, you could use Indy's ReadStringFromStream() function for that, eg:
// if using Indy 10.6 or later...
var
Attachment: TIdAttachment;
Strm: TStream;
begin
...
Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
Strm := Attachment.OpenLoadStream;
try
FBody := FBody + ReadStringFromStream(Strm, -1, CharsetToEncoding(Attachment.Charset));
finally
Attachment.CloseLoadStream;
end;
...
end;
Or:
// if using Indy 10.5.x or earlier...
var
Attachment: TIdAttachment;
Strm: TStream;
Enc: TIdTextEncoding;
begin
...
Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
Strm := Attachment.OpenLoadStream;
try
Enc := CharsetToEncoding(Attachment.Charset);
try
FBody := FBody + ReadStringFromStream(Strm, -1, Enc);
finally
Enc.Free;
end;
finally
Attachment.CloseLoadStream;
end;
...
end;

How operate on TFileStream

Hello recently I replace TextFile with TFileStream. I never use it so I have small problem with it.
How can I add someting to my file after I assign it to variable?
How can I read someting form that file?
I need defined line form that file so I was doing something like that:
var linia_klienta:array[0..30] of string;
AssignFile(tempPlik,'klienci.txt');
Reset(tempPlik);
i:=0;
While Not Eof(tempPlik) do
begin
Readln(tempPlik,linia_klient[i]);
inc(i);
end;
CloseFile(tempPlik);
Then when line two is needed I simply
edit1.text = linia_klienta[1];
If you need to read a text file and access each line, try instead using a TStringList class with this class you can load a file, read the data (accesing each line using a index) and save the data back.
something like this
FText : TStringList;
i : integer;
begin
FText := TStringList.Create;
try
FText.LoadFromFile('C:\Foo\Foo.txt');
//read the lines
for i:=0 to FText.Count-1 do
ProcessLine(FText[i]); //do something
//Add additional lines
FText.Add('Adding a new line to the end');
FText.Add('Adding a new line to the end');
//Save the data back
FText.SaveToFile('C:\Foo\Foo.txt');
finally
FText.Free;
end;
end;
end;
I newer versions of Delphi you can use TStreamReader / TStreamWriter here is an example of using TStreamReader ... this is only for manipulating text files
var
SR : TStreamReader;
line : String;
begin
SR := TStreamReader.Create('D:\test.txt');
while not (SR.EndOfStream) do
begin
line := SR.ReadLine;
ShowMessage(line);
end;
SR.Free;
end;
TStream and its immediate descendants are mostly low-level access class. They mostly deal with generic buffers. There are some more specialized classes that descend from or use a stream to perform higher level tasks.
Since Delphi 1 TReader and TWriter could be used to read and write Delphi types directly (inlcuding strings), but they were not designed to handle "line-oriented" files (unluckily they were designed too much with component properties streaming in mind, not as a general purpose framework).
Turbo Power SysTools has a nice TStAnsiTextStream class that implements line-oriented access to text files in a way similar to that of TextFile. Since Delphi 2009 new classes (see opc0de answer) implement the same kind of access without the need of third party libraries (moreover they support different encodings thanks to Delphi 2009 extend codepage support, including Unicode).
Depending with what you want to do, its the stream class you need.
Do you want to work with text (characters with break-lines and end-of-line characters) data ?
OR, do you want to work with binary data ?
I see you are using an array of char, instead, of a string.
Do you really want to use character data as if it was binary ?
Sometimes, some applications require that case.

How to save string into text files in Delphi?

What is the easiest way to create and save string into .txt files?
Use TStringList.
uses
Classes, Dialogs; // Classes for TStrings, Dialogs for ShowMessage
var
Lines: TStrings;
Line: string;
FileName: string;
begin
FileName := 'test.txt';
Lines := TStringList.Create;
try
Lines.Add('First line');
Lines.Add('Second line');
Lines.SaveToFile(FileName);
Lines.LoadFromFile(FileName);
for Line in Lines do
ShowMessage(Line);
finally
Lines.Free;
end;
end;
Also SaveToFile and LoadFromFile can take an additional Encoding in Delphi 2009 and newer to set the text encoding (Ansi, UTF-8, UTF-16, UTF-16 big endian).
Actually, I prefer this:
var
Txt: TextFile;
SomeFloatNumber: Double;
SomeStringVariable: string;
Buffer: Array[1..4096] of byte;
begin
SomeStringVariable := 'Text';
AssignFile(Txt, 'Some.txt');
Rewrite(Txt);
SetTextBuf(Txt, Buffer, SizeOf(Buffer));
try
WriteLn(Txt, 'Hello, World.');
WriteLn(Txt, SomeStringVariable);
SomeFloatNumber := 3.1415;
WriteLn(Txt, SomeFloatNumber:0:2); // Will save 3.14
finally CloseFile(Txt);
end;
end;
I consider this the easiest way, since you don't need the classes or any other unit for this code. And it works for all Delphi versions including -if I'm not mistaken- all .NET versions of Delphi...
I've added a call to SetTextBuf() to this example, which is a good trick to speed up textfiles in Delphi considerably. Normally, textfiles have a buffer of only 128 bytes. I tend to increase this buffer to a multiple of 4096 bytes. In several cases, I'va also implemented my own TextFile types, allowing me to use these "console" functions to write text to memo fields or even to another, external application! At this location is some example code (ZIP) I wrote in 2000 and just modified to make sure it compiles with Delphi 2007. Not sure about newer Delphi versions, though. Then again, this code is 10 years old already.These console functions have been a standard of the Pascal language since it's beginning so I don't expect them to disappear anytime soon. The TtextRec type might be modified in the future, though, so I can't predict if this code will work in the future... Some explanations:
WA_TextCustomEdit.AssignCustomEdit allows text to be written to CustomEdit-based objects like TMemo.
WA_TextDevice.TWATextDevice is a class that can be dropped on a form, which contains events where you can do something with the data written.
WA_TextLog.AssignLog is used by me to add timestamps to every line of text.
WA_TextNull.AssignNull is basically a dummy text device. It just discards anything you write to it.
WA_TextStream.AssignStream writes text to any TStream object, including memory streams, file streams, TCP/IP streams and whatever else you have.
Code in link is hereby licensed as CC-BY
Oh, the server with the ZIP file isn't very powerful, so it tends to be down a few times every day. Sorry about that.
The IOUtils unit which was introduced in Delphi 2010 provides some very convenient functions for writing/reading text files:
//add the text 'Some text' to the file 'C:\users\documents\test.txt':
TFile.AppendAllText('C:\users\documents\text.txt', 'Some text', TEncoding.ASCII);
Or if you are using an older version of Delphi (which does not have the for line in lines method of iterating a string list):
var i : integer;
begin
...
try
Lines.Add('First line');
Lines.Add('Second line');
Lines.SaveToFile(FileName);
Lines.LoadFromFile(FileName);
for i := 0 to Lines.Count -1 do
ShowMessage(Lines[i]);
finally
Lines.Free;
end;
If you're using a Delphi version >= 2009, give a look to the TStreamWriter class.
It will also take care of text file encodings and newline characters.
procedure String2File;
var s:ansiString;
begin
s:='My String';
with TFileStream.create('c:\myfile.txt',fmCreate) do
try
writeBuffer(s[1],length(s));
finally
free;
end;
end;
Care needed when using unicode strings....

Resources