Lazarus

Programming => General => Topic started by: pixelink on April 18, 2019, 02:36:00 am

Title: [SOLVED] TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 02:36:00 am
I am trying to build a simple FileStream that works some what.

1) I have a file SAVE that works fine (I think). Output (*.txt) looks like this...

Quote
Edit1Edit2a 3rd text string

I can read my file, but my Stream stays all on one line, just the way it saves.

I thought I have specified str length hoping to read each one back separately...
But, it doesn't work... I can't seem to figure out how to separate it  into 3 distinct pieces.

I should be getting this...
Edit1
Edit2
a 3rd text string

Here is my code, any help would be greatly appreciated!!!

WRITE ===================
Code: Pascal  [Select][+][-]
  1.  
  2. procedure TForm1.Button1Click(Sender: TObject);
  3. var
  4.   strm: TFileStream;
  5.   n1: longint;
  6.   n2: longint;
  7.   n3: longint;
  8.   txt1: string;
  9.   txt2: string;
  10.   txt3: string;
  11.   fname: String = 'test.txt';
  12. begin
  13.   strm := TFileStream.Create(fname, fmCreate);
  14.   txt1:= Edit1.Text;
  15.   txt2:= Edit2.Text;
  16.   txt3:= Memo1.Text;
  17.  
  18.   n1 := Length(txt1);
  19.   n2 := Length(txt2);
  20.   n3 := Length(txt3);
  21.  
  22.   try
  23.     strm.Position := 0;
  24.     strm.Write(txt1[1], n1);
  25.     strm.Write(txt2[1], n2);
  26.     strm.Write(txt3[1], n3);
  27.   finally
  28.     strm.Free;
  29.   end;
  30.  
  31. End;
  32.  
  33.  


READ ===================================

Code: Pascal  [Select][+][-]
  1.  
  2. procedure TForm1.Button2Click(Sender: TObject);
  3. var
  4.   strm: TFileStream;
  5.   n1: longint;
  6.   n2: longint;
  7.   n3: longint;
  8.   txt1: string;
  9.   txt2: string;
  10.   txt3: string;
  11.   fname: String = 'test.txt';
  12. begin
  13.   txt1 := '';
  14.   txt2 := '';
  15.   txt3 := '';
  16.   strm := TFileStream.Create(fname, fmOpenRead or fmShareDenyWrite);
  17.   try
  18.     n1 := strm.Size;
  19.     n2 := strm.Size;
  20.     n3 := strm.Size;
  21.  
  22.     SetLength(txt1, n1);
  23.     strm.Read(txt1[1], n1);
  24.     Edit1.Text:=txt1;
  25.  
  26.     SetLength(txt2, n2);
  27.     strm.Read(txt2[1], n2);
  28.     Edit2.Text:=txt2;
  29.  
  30.     SetLength(txt3, n3);
  31.     strm.Read(txt3[1], n3);
  32.     Memo1.Text:=txt3;
  33.  
  34.   finally
  35.     strm.Free;
  36.   end;
  37. end;
  38.  
  39.  
Title: Re: TFileStream Issue - Works Partially
Post by: jamie on April 18, 2019, 03:24:33 am
You know there are ready made system calls that do this for you?

ReadLn, WriteLn, etc...

But in any case...

 You write out the EDITS one by one but you have no markers (line endings) embedded in the file so when you
go and read it back, the SIZE reported is the sum of all.
 
 In this case here, you have set three sizes from one source, the first read you do is going to read the complete file in
and the others will read nothing because you have moved to the end of the file on the first one.

If you are trying to make some custom format then you need a different approach.

 Try this for an idea...

 each time you write a string out you first write out a 2 byte value that indicates the length of the following string and use
that to read in that amount of data.

 After the first read in, the file pointer will be sitting on the next 2 byte value that reports the length of the next string so
you repeat the cycle.

 But honestly if all you want to do is read and write text lines you can use the built in PASCAL IN/OUT functions
Filevar :Text;


AssignFile(FileVar, 'FileName');
Rewrite(fileVar);

WriteLn(FileVar, EDIT1);
Writeln(FileVar,  EDIT2); and so on..

CloseFile(fileVar);

For reading you use RESET(fileVar).
and Readln etc...

Title: Re: TFileStream Issue - Works Partially
Post by: Thaddy on April 18, 2019, 07:00:28 am
Although jamie's answer is partially correct, it has several disadvantages over a stream, e.g. you loose the assignment compatibility with other streams.
More importantly, the stream methods store a length specifier, something that jamie's code does not do. (that's the partially)
In your case I would use the TFileStream.ReadAnsiString/WriteAnsiString methods.
It looks like you missed those methods? Note that a future version of TFilestream will also write UTF16 strings, but for most purposes this should work, you may also store the codepage.
Title: Re: TFileStream Issue - Works Partially
Post by: Handoko on April 18, 2019, 07:24:08 am
Tested Thaddy suggestion. It works. Problem solved.
Title: Re: TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 01:04:44 pm
Okay... some good info.

The code above is just a test demo I am working on.

My end game is to save a CSV file from a ListView (2 column) so that each line in the text file looks like this....

Quote
Title Text,Image path location

Thanks
Title: Re: TFileStream Issue - Works Partially
Post by: Handoko on April 18, 2019, 03:10:05 pm
I remember there were several discussions not long ago, which maybe useful to you:

https://forum.lazarus.freepascal.org/index.php/topic,43439.msg304055.html
https://forum.lazarus.freepascal.org/index.php/topic,44914.msg316154.html#msg316154
http://forum.codecall.net/topic/74040-basic-working-with-tlistview/
Title: Re: TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 04:42:44 pm
Okay... got the demo working... took some time, lots of documentation telling me what everything is "methods etc etc, BUT no Code examples.

After a few hours I finally got this to work.
It may be crude, please feel free to make it better.

On in this example, I have a field text that has two fields separated by a "|" delimiter.

Then I use TStrngList to separate the data using the delimiter "^".

Therefore I can store lots of different values using stream in a text file without using binary.
FYI... I do have a working demo of binary too. But I wanted to workout the TFileStream for just plain text.

1) Single value text box
2) Delimited value string "This is two values,^in a test string" (to read in 2 separate values)
3) Memo string (including commas)

Here is my file ouput.

test.txt

Code: Pascal  [Select][+][-]
  1. Edit1|Edit2|This is two values,^in a test string|a 3rd text string that is a MEMO text control.
  2. |
  3.  

Here is my code...

WRITE:
Code: Pascal  [Select][+][-]
  1.  
  2. procedure TForm1.Button1Click(Sender: TObject);
  3. var
  4.   strm: TFileStream;
  5.   n1: longint;
  6.   n2: longint;
  7.   n3: longint;
  8.   n4: longint;
  9.   txt1: string;
  10.   txt2: string;
  11.   txt3: string;
  12.   txt4: string;
  13.   fname: String = 'test.txt';
  14. begin
  15.   strm := TFileStream.Create(fname, fmCreate);
  16.  
  17.   txt1:= Edit1.Text+'|';
  18.   txt2:= Edit2.Text+'|';
  19.   txt3:= Edit3.Text+'|';
  20.   txt4:= Memo1.Text+'|';
  21.  
  22.   n1 := Length(txt1);
  23.   n2 := Length(txt2);
  24.   n3 := Length(txt3);
  25.   n4 := Length(txt4);
  26.  
  27.   try
  28.     strm.Position := 0;
  29.     strm.Write(txt1[1], n1);
  30.     strm.Write(txt2[1], n2);
  31.     strm.Write(txt3[1], n3);
  32.     strm.Write(txt4[1], n4);
  33.   finally
  34.     strm.Free;
  35.   end;
  36.  
  37.  

READ:
Code: Pascal  [Select][+][-]
  1.  
  2. procedure TForm1.Button2Click(Sender: TObject);
  3. var
  4.   strm: TFileStream;
  5.   txt:String;
  6.   sl1: TStringlist;
  7.   sl2: TStringlist;
  8.   spltxt: String;
  9.   lng: LongInt;
  10.   fname: String = 'test.txt';
  11. begin
  12.   txt:='';
  13.   try
  14.      strm:= TFileStream.Create(fname, fmOpenRead);
  15.      SetLength(txt, lng);
  16.      strm.Read(txt[1], lng);
  17.   finally
  18.      strm.Free;
  19.   end;
  20.  
  21. sl1:= TStringList.Create;
  22.  
  23. try
  24.   sl1.StrictDelimiter:= True ;
  25.   sl1.Delimiter:='|';
  26.   sl1.DelimitedText:=txt;
  27.   Edit1.Text:=sl1[0];
  28.   Edit2.Text:=sl1[1];
  29.   spltxt:=sl1[2];
  30.   Edit3.Text:=spltxt;
  31.   Memo1.Text:=sl1[3];
  32. finally
  33.        sl1.Free;
  34. end;
  35. sl2 := TStringList.Create;
  36. try
  37.    sl2.StrictDelimiter := True ;
  38.    sl2.Delimiter:= '^';
  39.    sl1.DelimitedText := spltxt;
  40.    Edit4.Text:=sl2[0];
  41.    Edit5.Text:=sl2[1];
  42. finally
  43.   sl2.free
  44. end;
  45.  
  46. end;  
  47.  
  48.  

Thanks for your inputs!!

Title: Re: TFileStream Issue - Works Partially
Post by: lucamar on April 18, 2019, 04:46:55 pm
I was thinking CSV for the end game, but decided that using ',' isn't a good idea, because if you want to store a sentence that has a "," in it, then you will get an un-wanted break in the sentence, thus breaking the CSV fields.

In CSV you surround those strings with quotes:
Code: Text  [Select][+][-]
  1. "A string, with a comma",12345,normal string

You do know that the LazUtils package has some CSV-related goodies, don't you?
Title: Re: TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 04:51:09 pm
You know there are ready made system calls that do this for you?

ReadLn, WriteLn, etc...

But in any case...

 You write out the EDITS one by one but you have no markers (line endings) embedded in the file so when you
go and read it back, the SIZE reported is the sum of all.
 
 In this case here, you have set three sizes from one source, the first read you do is going to read the complete file in
and the others will read nothing because you have moved to the end of the file on the first one.

If you are trying to make some custom format then you need a different approach.

 Try this for an idea...

 each time you write a string out you first write out a 2 byte value that indicates the length of the following string and use
that to read in that amount of data.

 After the first read in, the file pointer will be sitting on the next 2 byte value that reports the length of the next string so
you repeat the cycle.

 But honestly if all you want to do is read and write text lines you can use the built in PASCAL IN/OUT functions
Filevar :Text;


AssignFile(FileVar, 'FileName');
Rewrite(fileVar);

WriteLn(FileVar, EDIT1);
Writeln(FileVar,  EDIT2); and so on..

CloseFile(fileVar);

For reading you use RESET(fileVar).
and Readln etc...

Yes, I know. I have often used the  Assign methods a lot.

But, from what I read, it is old school and Stream Reader is more modern, so I just wanted to get some type of Stream reader working.
Title: Re: TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 04:55:26 pm
Also, if you read my code close enough, you will see that I use StringList twice.

1) To separate initial values using '|' as a delimiter
2) 2nd one to separate the string value that has just the "^" delimiter.

So I have two delimiter routines that are being flushed out.

This way I can store single values and Multiple values in one string.
Title: Re: TFileStream Issue - Works Partially
Post by: pixelink on April 18, 2019, 05:09:19 pm
I was thinking CSV for the end game, but decided that using ',' isn't a good idea, because if you want to store a sentence that has a "," in it, then you will get an un-wanted break in the sentence, thus breaking the CSV fields.

In CSV you surround those strings with quotes:
Code: Text  [Select][+][-]
  1. "A string, with a comma",12345,normal string

You do know that the LazUtils package has some CSV-related goodies, don't you?

@LUCAMAR
I was thinking about the what you said. Because you can also use commas and use quotes for a CSV, I updated my post and removed the verbiage about the CSV breaking. That wasn't entirely what I was wanting to say, and i don't want to give the impression that using CSV with quotes is bad.

Thanks for your input!!
TinyPortal © 2005-2018