Lazarus

Programming => General => Topic started by: OC DelGuy on May 07, 2025, 07:37:24 am

Title: Gooble-dee-gok in my data file...
Post by: OC DelGuy on May 07, 2025, 07:37:24 am
I'm not sure what's going on here.  I've tried to find what's wrong, but I can't see it.  Best I can figure is that writing the whole record as a TNote or writing the records into a File of TNotes is wrong somehow.

Code: Pascal  [Select][+][-]
  1. unit SimpNotes;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Interfaces, FileUtil, ExtCtrls;
  5. type
  6.   { TForm1 }
  7.   TShortString = String[255]; // Fixed-length string type of 255 characters.
  8.   TContentString = String[255]; // Fixed-length string type of 255 characters.
  9.  
  10.   TNotes = packed record
  11.     Title: TShortString;
  12.     Content: TContentString;
  13.   end;
  14.  
  15.   TForm1 = class(TForm)
  16.     BtnAdd: TButton;
  17.     BtnDelete: TButton;
  18.     BtnSave: TButton;
  19.     BtnMod: TButton;
  20.     BtnNew: TButton;
  21.     Memo1: TMemo;
  22.     Title: TEdit;
  23.     Label1: TLabel;
  24.     Label2: TLabel;
  25.     TitlesList: TListBox;
  26.     Content: TMemo;
  27.     procedure BtnAddClick(Sender: TObject);
  28.     procedure BtnDeleteClick(Sender: TObject);
  29.     procedure BtnModClick(Sender: TObject);
  30.     procedure BtnSaveClick(Sender: TObject);
  31.     procedure BtnNewClick(Sender: TObject);
  32.     procedure FormCreate(Sender: TObject);
  33.     procedure TitlesListClick(Sender: TObject);
  34.   private
  35.     Note: array of TNotes;
  36.     NoteCount: Integer;
  37.     procedure LoadNotes;
  38.     procedure SaveNotes;
  39.   public
  40.   end;
  41.  
  42.   var
  43.     Form1: TForm1;
  44.  
  45. implementation
  46. {$R *.lfm}
  47. { TForm1 }
  48.  
  49.   Procedure TForm1.BtnAddClick(Sender: TObject);
  50.     Var
  51.       NewNote: TNotes;
  52.     Begin
  53.       If Trim(Title.Text) = '' Then
  54.         Begin
  55.           ShowMessage('Please enter a title for the note.');
  56.           Exit;
  57.         End;
  58.       If Trim(Content.Text) = '' Then
  59.         Begin
  60.           ShowMessage('Please enter content for the note.');
  61.           Exit;
  62.         End;
  63.       NewNote.Title := Title.Text;
  64.       NewNote.Content := Content.Text;
  65.       Inc(NoteCount);  // Increment the Note Counter.
  66.       SetLength(Note, NoteCount);  // Add a blank space for new Note
  67.       Note[NoteCount - 1] := NewNote;  // Add the new note.
  68.       TitlesList.Items.Add(NewNote.Title);  // Add the title to the ListBox.
  69.       Label2.Caption := 'Notes: ' + IntToStr(NoteCount);  // Update the note counter display;
  70.       Title.Clear;
  71.       Content.Clear;
  72.     End;
  73.  
  74.   Procedure TForm1.BtnModClick(Sender: TObject);
  75.     Var
  76.       I: Integer;
  77.     Begin
  78.       If TitlesList.ItemIndex >= 0 Then
  79.         Begin
  80.           I := TitlesList.ItemIndex;
  81.           Note[I].Title := Title.Text;
  82.           Note[I].Content := Content.Text;
  83.           TitlesList.Items[I] := Title.Text; // Update the listbox text
  84.         End;
  85.     End;
  86.  
  87.   Procedure TForm1.BtnDeleteClick(Sender: TObject);
  88.     Var
  89.       I: Integer;
  90.     Begin
  91.       If TitlesList.ItemIndex >= 0 Then
  92.         Begin
  93.           I := TitlesList.ItemIndex;  // This is the highlighted Title in the ListBox
  94.           TitlesList.Items.Delete(I);  // This removes the Title to be deleted.
  95.           Move(Note[I + 1], Note[I], (NoteCount - I - 1) * SizeOf(TNotes));  // Move the below records up one on the list.
  96.           Dec(NoteCount);  // Decrement the Note Counter.
  97.           SetLength(Note, NoteCount);  // Set the note array records to NoteCount records.
  98.           Label2.Caption := 'Notes: ' + IntToStr(NoteCount);  // Update the note counter display;
  99.           Title.Clear;
  100.           Content.Clear;
  101.         End;
  102.     End;
  103.  
  104.   Procedure TForm1.BtnSaveClick(Sender: TObject);
  105.     Begin
  106.       SaveNotes;
  107.     End;
  108.  
  109.   Procedure TForm1.BtnNewClick(Sender: TObject);
  110.     Begin
  111.       Title.Clear;
  112.       Content.Clear;
  113.     End;  // BtnNewClick
  114.  
  115.   Procedure TForm1.FormCreate(Sender: TObject);
  116.     Begin
  117.       NoteCount := 0;  // Init the Note Count
  118.       LoadNotes;
  119.       Label2.Caption := 'Notes: ' + IntToStr(NoteCount);  // Update the note counter display;
  120.     End;  // FormCreate
  121.  
  122.   Procedure TForm1.TitlesListClick(Sender: TObject);
  123.     Begin
  124.       If TitlesList.ItemIndex >= 0 Then
  125.         Begin
  126.           Title.Text := Note[TitlesList.ItemIndex].Title;
  127.           Content.Text := Note[TitlesList.ItemIndex].Content;
  128.         End;
  129.     End;  // Proc TitlesListClick
  130.  
  131.   Procedure TForm1.LoadNotes;
  132.     Var
  133.       F: File of TNotes;
  134.       I, ErrorCode: Integer;
  135.     Begin
  136.       If FileExists('Notes.dat') Then
  137.         Begin
  138.           AssignFile(F, 'Notes.dat');
  139.           {$I-} // Disable I/O checking
  140.           Reset(F);
  141.           ErrorCode := IOResult;
  142.           Memo1.Lines.Add('Notes Opened: ' + IntToStr(ErrorCode));
  143.           {$I+} // Enable I/O checking
  144.           If ErrorCode <> 0 Then
  145.             Begin
  146.               ShowMessage('Error opening notes file: ' + SysErrorMessage(ErrorCode));
  147.               NoteCount := 0;  SetLength(Note, 0);  Exit;
  148.             End;
  149.            Try
  150.             NoteCount := {FileSize(F)}512 div SizeOf(TNotes);
  151.             Memo1.Lines.Add('FileSize: ' + IntToStr(FileSize(F)));
  152.             Memo1.Lines.Add('Size Of - TNotes: ' + IntToStr(SizeOf(TNotes)));
  153.             Memo1.Lines.Add('Note Count: ' + IntToStr(NoteCount));
  154.             SetLength(Note, NoteCount);
  155.             For I := 0 to NoteCount - 1 Do
  156.               Begin
  157.                 {$I-} // Disable I/O checking
  158.                 Read(F, Note[I]);
  159.                 ErrorCode := IOResult;
  160.                 Memo1.Lines.Add('Reading Notes: ' + IntToStr(ErrorCode));
  161.                 {$I+} // Enable I/O checking
  162.                 If ErrorCode <> 0 Then
  163.                   Begin
  164.                     ShowMessage('Error reading notes file at index: ' + IntToStr(I) + ' - ' + SysErrorMessage(ErrorCode));
  165.                     NoteCount := 0;
  166.                     SetLength(Note, 0);
  167.                     TitlesList.Clear;
  168.                     CloseFile(F);
  169.                     Exit;
  170.                   End; // ErrorCode
  171.                 TitlesList.Items.Add(Note[I].Title);
  172.               End; // For I
  173.           Finally
  174.             {$I-} // Disable I/O checking
  175.             CloseFile(F);
  176.             ErrorCode := IOResult;
  177.             {$I+} // Enable I/O checking
  178.             If ErrorCode <> 0 Then
  179.               Begin
  180.                 ShowMessage('Error closing notes file: ' + SysErrorMessage(ErrorCode));
  181.               End;
  182.           End; // Finally
  183.         End // FileExists
  184.       Else
  185.         Begin
  186.           NoteCount := 0;
  187.           SetLength(Note, 0);
  188.         End; // Else
  189.     End; // Proc LoadNotes
  190.  
  191.   Procedure TForm1.SaveNotes;
  192.     Var
  193.       F: File of TNotes;
  194.       I: Integer;
  195.     Begin
  196.       AssignFile(F, 'Notes.dat');
  197.       Rewrite(F);
  198.       For I := 0 to NoteCount - 1 Do
  199.         Begin
  200.           Write(F, Note[I]);
  201.         End;
  202.       CloseFile(F);
  203.     End;  // Proc SaveNotes
  204.  
  205. end.


Why does the SaveNotes produce this:   :o (What's all the gooble-dee-gok around my Notes?)?
Code: Text  [Select][+][-]
  1. The Lord of the Rings                 ` €                            xébÍÿ           W      O¬òÿ  c       å*\.žÉ  þÿÿÿ    0             é?    y cÍÿ  þÿÿÿ    +I­òÿ  H                              p       ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒ³ôÿ  ·ôÿ          cFrodo and company set off to Mordor.  Isengard and Barad Dur.  Aragorn enters the white city.
  2.                             ðz      (    +       ¶    ÿÿÿÿ    ê?            (    T
  3.      ðz            ‘d     °KlÍÿ          T
  4.      ðz      
  5. The Hobbitòÿ                         ` €                            xébÍÿ          @ZW     O¬òÿ          [hŒ?‰         p–Š             é?    y cÍÿ         +I­òÿ  H                              p       ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒ³ôÿ  ·ôÿ          Bilbo and the 13 Dwarves go to Erebor.      °KlÍÿ  \¯ôÿ  p~    °KlÍÿ  N
  6.  
  7.      ðzM    ðz      N
  8.  
  9.                                      ðz      8à    +       F     ÿÿÿÿ    ê?            (    N
  10.  
  11.      ðzM    P!T    !h     °KlÍÿ          N
  12.  
  13.      ðz      
  14.  


And when reading the file, why does the File Size = 2?   >:D(see attachment)      Two is the number of records.
Shouldn't it be 1024?  %)
Title: Re: Gooble-dee-gok in my data file...
Post by: Fibonacci on May 07, 2025, 07:57:31 am
Why does the SaveNotes produce this

Part of the string not filled with text is random garbage, and also each ShortString (your String[255] is a ShortString) contains a byte with its lengh at front (non printable character usually).



And when reading the file, why does the File Size = 2?   >:D(see attachment)

https://www.freepascal.org/docs-html/rtl/system/filesize.html

"Filesize returns the total number of records in file F"



Code: Pascal  [Select][+][-]
  1. NoteCount := {FileSize(F)}512 div SizeOf(TNotes);

Isnt the app only reading one note even though the file contains more than one?



Suggestions

1. Stop using this archaic AssignFile/Rewrite, and "File of Record" thing; instead use MemoryStream/FileStream

2. Consider using JSON as a storage format
Title: Re: Gooble-dee-gok in my data file...
Post by: Thaddy on May 07, 2025, 09:44:01 am
I agree with you that using assignfile etc is a bit silly in an application that is written using classes and a gui based on lazarus, but it is not archaic for non-gui applications at all. In this case I would also recommend using memory/filestreams because the overhead is already in place.
But warn that assignfile should be simply assign....
Title: Re: Gooble-dee-gok in my data file...
Post by: OC DelGuy on May 07, 2025, 05:18:27 pm
And when reading the file, why does the File Size = 2?   >:D(see attachment)

https://www.freepascal.org/docs-html/rtl/system/filesize.html

"Filesize returns the total number of records in file F"
OK, this is really my mistake.  The reason this appears is because I saw it in an example on a website (See also second quote below.).

So, what is the way to get the actual file size and not the record size?

In my defense, with a name like FileSize, I didn't read the reference docs.  That is why I used it in line 150  to DIV it by the record size.  That is how I was getting the number of records.

Code: Pascal  [Select][+][-]
  1. NoteCount := {FileSize(F)}512 div SizeOf(TNotes);

Isnt the app only reading one note even though the file contains more than one?
Yes, but I was forcing the app to read one record.  I added the {}512 after my first test run.  The original line was:
Code: Pascal  [Select][+][-]
  1. NoteCount := FileSize(F) div SizeOf(TNotes);
I forgot to take that out for this posting.  When I was first testing my code, I was getting the output you see in the attachment.  I then added the {}512.


Suggestions

1. Stop using this archaic AssignFile/Rewrite, and "File of Record" thing; instead use MemoryStream/FileStream

2. Consider using JSON as a storage format
1. OK, I'll start learning about MemoryStream/FileStream.

2. Where do I start with "using JSON with Pascal"?  All the examples use Python and C++ and even some Javascript.

Title: Re: Gooble-dee-gok in my data file...
Post by: paweld on May 08, 2025, 08:52:14 am
2. Where do I start with "using JSON with Pascal"?
Your code after changing to write to JSON might look like this, for example:
Code: Pascal  [Select][+][-]
  1. unit SimpNotes;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6. uses
  7.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Interfaces, FileUtil, ExtCtrls,
  8.   fpjson, fpjsonrtti;
  9.  
  10. type
  11.  
  12.   { TNotes }
  13.  
  14.   TNotes = class(TCollectionItem)
  15.   private
  16.     FContent: String;
  17.     FTitle: String;
  18.   published
  19.     property Title: String read FTitle write FTitle;
  20.     property Content: String read FContent write FContent;
  21.   end;
  22.  
  23.   { TNotesList }
  24.  
  25.   TNotesList = class(TCollection)
  26.   private
  27.     function GetItems(Index: Integer): TNotes;
  28.     procedure SetItems(Index: Integer; AValue: TNotes);
  29.   public
  30.     constructor Create;
  31.     function Add: TNotes;
  32.     procedure Add(ATitle, AContent: String);
  33.     procedure SaveAsJson(AFileName: String);
  34.     procedure LoadFromJson(AFileName: String);
  35.     property Items[Index: Integer]: TNotes read GetItems write SetItems;
  36.   end;
  37.  
  38.   { TForm1 }
  39.  
  40.   TForm1 = class(TForm)
  41.     BtnAdd: TButton;
  42.     BtnDelete: TButton;
  43.     BtnSave: TButton;
  44.     BtnMod: TButton;
  45.     BtnNew: TButton;
  46.     Memo1: TMemo;
  47.     Title: TEdit;
  48.     Label1: TLabel;
  49.     Label2: TLabel;
  50.     TitlesList: TListBox;
  51.     Content: TMemo;
  52.     procedure BtnAddClick(Sender: TObject);
  53.     procedure BtnDeleteClick(Sender: TObject);
  54.     procedure BtnModClick(Sender: TObject);
  55.     procedure BtnSaveClick(Sender: TObject);
  56.     procedure BtnNewClick(Sender: TObject);
  57.     procedure FormCreate(Sender: TObject);
  58.     procedure FormDestroy(Sender: TObject);
  59.     procedure TitlesListClick(Sender: TObject);
  60.   private
  61.     noteslist: TNotesList;
  62.   public
  63.   end;
  64.  
  65. var
  66.   Form1: TForm1;
  67.  
  68. implementation
  69. {$R *.lfm}
  70.  
  71. { TNotesList }
  72.  
  73. function TNotesList.GetItems(Index: Integer): TNotes;
  74. begin
  75.   Result := TNotes(inherited Items[Index]);
  76. end;
  77.  
  78. procedure TNotesList.SetItems(Index: Integer; AValue: TNotes);
  79. begin
  80.   Items[Index].Assign(AValue);
  81. end;
  82.  
  83. constructor TNotesList.Create;
  84. begin
  85.   inherited Create(TNotes);
  86. end;
  87.  
  88. function TNotesList.Add: TNotes;
  89. begin
  90.   Result := inherited Add as TNotes;
  91. end;
  92.  
  93. procedure TNotesList.Add(ATitle, AContent: String);
  94. var
  95.   ni: TNotes;
  96. begin
  97.   ni := Add;
  98.   ni.Title := ATitle;
  99.   ni.Content := AContent;
  100. end;
  101.  
  102. procedure TNotesList.SaveAsJson(AFileName: String);
  103. var
  104.   sl: TStringList;
  105.   js: TJSONStreamer;
  106. begin
  107.   sl := TStringList.Create;
  108.   js := TJSONStreamer.Create(nil);
  109.   js.Options := js.Options + [jsoTStringsAsArray, jsoUseFormatString];
  110.   try
  111.     sl.Text := js.CollectionToJSON(Self);
  112.     sl.SaveToFile(AFileName);
  113.   finally
  114.     js.Free;
  115.     sl.Free;
  116.   end;
  117. end;
  118.  
  119. procedure TNotesList.LoadFromJson(AFileName: String);
  120. var
  121.   sl: TStringList;
  122.   jds: TJSONDeStreamer;
  123. begin
  124.   Clear;
  125.   if not FileExists(AFileName) then
  126.     exit;
  127.   sl := TStringList.Create;
  128.   jds := TJSONDeStreamer.Create(nil);
  129.   try
  130.     sl.LoadFromFile(AFileName);
  131.     jds.JSONToCollection(sl.Text, Self);
  132.   finally
  133.     jds.Free;
  134.     sl.Free;
  135.   end;
  136. end;
  137.  
  138. { TForm1 }
  139.  
  140. procedure TForm1.BtnAddClick(Sender: TObject);
  141. begin
  142.   if Trim(Title.Text) = '' then
  143.   begin
  144.     ShowMessage('Please enter a title for the note.');
  145.     Exit;
  146.   end;
  147.   if Trim(Content.Text) = '' then
  148.   begin
  149.     ShowMessage('Please enter content for the note.');
  150.     Exit;
  151.   end;
  152.   noteslist.Add(Title.Text, Content.Text);
  153.   TitlesList.Items.Add(Title.Text);  // Add the title to the ListBox.
  154.   Label2.Caption := 'Notes: ' + IntToStr(noteslist.Count);  // Update the note counter display;
  155.   Title.Clear;
  156.   Content.Clear;
  157. end;
  158.  
  159. procedure TForm1.BtnModClick(Sender: TObject);
  160. var
  161.   I: Integer;
  162. begin
  163.   if TitlesList.ItemIndex >= 0 then
  164.   begin
  165.     I := TitlesList.ItemIndex;
  166.     noteslist.Items[I].Title := Title.Text;
  167.     noteslist.Items[I].Content := Content.Text;
  168.     TitlesList.Items[I] := Title.Text; // Update the listbox text
  169.   end;
  170. end;
  171.  
  172. procedure TForm1.BtnDeleteClick(Sender: TObject);
  173. var
  174.   I: Integer;
  175. begin
  176.   if TitlesList.ItemIndex >= 0 then
  177.   begin
  178.     I := TitlesList.ItemIndex;  // This is the highlighted Title in the ListBox
  179.     noteslist.Delete(I);
  180.     TitlesList.Items.Delete(I);  // This removes the Title to be deleted.
  181.     Label2.Caption := 'Notes: ' + IntToStr(noteslist.Count);  // Update the note counter display;
  182.     Title.Clear;
  183.     Content.Clear;
  184.   end;
  185. end;
  186.  
  187. procedure TForm1.BtnSaveClick(Sender: TObject);
  188. begin
  189.   noteslist.SaveAsJson('notes.json');
  190. end;
  191.  
  192. procedure TForm1.BtnNewClick(Sender: TObject);
  193. begin
  194.   Title.Clear;
  195.   Content.Clear;
  196. end;  // BtnNewClick
  197.  
  198. procedure TForm1.FormCreate(Sender: TObject);
  199. begin
  200.   noteslist := TNotesList.Create;
  201.   noteslist.LoadFromJson('notes.json');
  202.   Label2.Caption := 'Notes: ' + IntToStr(noteslist.Count);  // Update the note counter display;
  203. end;  // FormCreate
  204.  
  205. procedure TForm1.FormDestroy(Sender: TObject);
  206. begin
  207.   noteslist.Free;
  208. end;
  209.  
  210. procedure TForm1.TitlesListClick(Sender: TObject);
  211. begin
  212.   if TitlesList.ItemIndex >= 0 then
  213.   begin
  214.     Title.Text := noteslist.Items[TitlesList.ItemIndex].Title;
  215.     Content.Text := noteslist.Items[TitlesList.ItemIndex].Content;
  216.   end;
  217. end;  // Proc TitlesListClick
  218.  
  219. end.

More info: https://wiki.lazarus.freepascal.org/JSON and https://wiki.lazarus.freepascal.org/Streaming_JSON
Title: Re: Gooble-dee-gok in my data file...
Post by: Thaddy on May 08, 2025, 05:32:41 pm
Note that in the example that is included in your question, Filesize, given file = file of TNotes, can be calulated as FileSize(handle) * SizeOf(TNotes);
NOT divide, since FileSize returns the number of records in the file.
Title: Re: Gooble-dee-gok in my data file...
Post by: Fibonacci on May 08, 2025, 05:42:32 pm
Your code after changing to write to JSON might look like this, for example:

This example is way too complex. He doesnt know what JSON is. Collections, RTTI, properties, streaming? Nah..



Use simple JSON objects and arrays.

Talk with GPT.

Here is a simple example. Play with it. Write "TJSON" then Ctrl+Space, see what is available. Right click on things -> Find declaration. Read the code.

Code: Pascal  [Select][+][-]
  1. uses fpjson, jsonparser;
  2.  
  3. var
  4.   notes: TJSONArray;
  5.   note: TJSONObject;
  6.   enum: TJSONEnum;
  7.   s: string;
  8.  
  9. begin
  10.   notes := TJSONArray.Create;
  11.  
  12.   note := TJSONObject.Create;
  13.   note.Add('title', 'note title');
  14.   note.Add('content', 'note content');
  15.   note.Add('letsget', TJSONObject.Create);
  16.   TJSONObject(note['letsget'])['deeper'] := TJSONString.Create('note 1');
  17.   notes.Add(note);
  18.  
  19.   note := TJSONObject.Create;
  20.   note['title'] := TJSONString.Create('another note');
  21.   note['content'] := TJSONString.Create('another note content');
  22.   note['letsget'] := TJSONObject.Create;
  23.   TJSONObject(note['letsget'])['deeper'] := TJSONString.Create('note 2');
  24.   notes.Add(note);
  25.  
  26.   s := notes.AsJSON;
  27.   writeln(s);
  28.   // ^ this is what you save to the file, as text
  29.  
  30.   writeln;
  31.  
  32.   // -------------------------------
  33.   // now read
  34.  
  35.   notes := TJSONArray(GetJSON(s));
  36.  
  37.   for enum in notes do begin
  38.     note := TJSONObject(enum.Value);
  39.     writeln('title = ', note.Get('title'), ' | content = ', note.Get('content'), ' | ', note.FindPath('letsget.deeper').AsString);
  40.   end;
  41.  
  42.   readln;
  43. end.
TinyPortal © 2005-2018