views:

356

answers:

4

I have a record that looks similar to:

type
  TNote = record
    Title : string;
    Note  : string;
    Index : integer;
  end;

Simple. The reason I chose to set the variables as string (as opposed to an array of chars) is that I have no idea how long those strings are going to be. They can be 1 char long, 200 or 2000. Of course when I try to save the record to a type file (file of...) the compiler complains that I have to give a size to string. Is there a way to overcome this? or a way to save those records to an untyped file and still maintain a sort of searchable way?

Please do not point me to possible solutions, if you know the solution please post code. Thank you

+3  A: 

You can't do it with a typed file. Try something like this, with a TFileStream:

type
   TStreamEx = class helper for TStream
   public
      procedure writeString(const data: string);
      function readString: string;
      procedure writeInt(data: integer);
      function readInt: integer;
  end;

function TStreamEx.readString: string;
var
   len: integer;
   iString: UTF8String;
begin
   self.readBuffer(len, 4);
   if len > 0 then
   begin
      setLength(iString, len);
      self.ReadBuffer(iString[1], len);
      result := string(iString);
   end;
end;

procedure TStreamEx.writeString(const data: string);
var
   len: cardinal;
   oString: UTF8String;
begin
   oString := UTF8String(data);
   len := length(oString);
   self.WriteBuffer(len, 4);
   if len > 0 then
      self.WriteBuffer(oString[1], len);
end;

function TStreamEx.readInt: integer;
begin
   self.readBuffer(result, 4);
end;

procedure TStreamEx.writeInt(data: integer);
begin
   self.WriteBuffer(data, 4);
end;

type
  TNote = record
    Title : string;
    Note  : string;
    Index : integer;
    procedure Save(stream: TStream);
  end;

procedure TNote.Save(stream: TStream);
var
   temp: TMemoryStream;
begin
   temp := TMemoryStream.Create;
   try
      temp.writeString(Title);
      temp.writeString(Note);
      temp.writeInt(Index);
      temp.seek(0, soFromBeginning);
      stream.writeInt(temp.size);
      stream.copyFrom(temp, temp.size);
   finally
      temp.Free;
   end;
end;

I'll leave the Load procedure to you. Same basic idea, but it shouldn't need a temp stream. With the record size in front of each entry, you can read it and know how far to skip if you're looking for a certain record # instead of reading the whole thing.

EDIT: This was written specifically for versions of Delphi that use Unicode strings. On older versions, you could simplify it quite a bit.

Mason Wheeler
thanks. Let see if I can work this one. I need to change a lot of my code.
wonderer
thank you for being so straight. The code pointed me to the right solution.
wonderer
+2  A: 

Why not write this out as XML? See my session "Practical XML with Delphi" on how to get started with this.

Another possibility would be to make your records into classes descending form TComponent and store/retreive your data in DFM files.

This Stackoverflow entry shows you how to do that.

--jeroen

PS: Sorry my XML answer was a bit dense; I'm actually on the road for two conferences (BASTA! and DelphiLive! Germany).

Basically what you need to do is very simple: create a sample XML file, then start the Delphi XML Data Binding Wizard (available in Delphi since version 6).

This wizard will generate a unit for you that has the interfaces and classes mapping XML to Delphi objects, and a few helper functions for reading them from file, creating a new object, etc. My session (see the first link above) actually contains most of the details for this process.

The above link is a video demonstrating the usage of the Delphi XML Data Binding Wizard.

Jeroen Pluimers
thanks. so now I need to write a full XML parsing function. For such a simple task (saving a string to a file) it's way too much work. unless you have (as I requested) sample code that can do it.
wonderer
+1 because Delphi provides an XML interface (msxmldom, XMLDoc) and IMHO it's easier than implementing a custom format.
Nick D
if it's so easy please post the code. I don't understand why people don't read the question properly.
wonderer
XML is all well for some type of data, but in this case I think it's overkill.
dummzeuch
@wonderer, a) I understanded your question perfectly, b) I post code only if I want to, not because you say so and c) personaly I implement such facilities with a database, like SQLite, because variable length records require more effort to make them efficient.
Nick D
thank you. that was very informative.
wonderer
@Jeroen Pluimers Thanks for the clarification. I can't see anything on that video, the resolution is so low I can't even make out the text.This might be a good solution but I just need text and a sample code.
wonderer
A: 

You could work with two different files, one that just stores the strings in some convenient way, the other stores the records with a reference to the strings. That way you will still have a file of records for easy access even though you don't know the size of the actual content.

(Sorry no code.)

dummzeuch
A: 
  TNote = record
    Title : string;
    Note  : string;
    Index : integer;
  end;

could be translated as

  TNote = record
    Title : string[255];
    Note  : string[255];
    Index : integer;
  end;

and use Stream.writebuffer(ANodeVariable, sizeof(TNode), but you said that strings get go over 255 chars in this case IF a string goes over 65535 chars then change WORD to INTEGER

type
TNodeHeader=Record
  TitleLen,
  NoteLen: Word;
end;

(* this is for writing a TNode *)
procedure saveNodetoStream(theNode: TNode; AStream: TStream);
var
  header: TNodeHeader;
  pStr: PChar;
begin
  ...
  (* writing to AStream which should be initialized before this *)
  Header.TitleLen := Length(theNode.Title);
  header.NodeLen := Length(theNode.Note);
  AStream.WriteBuffer(Header, sizeof(TNodeHeader);
  (* save strings *)
  PStr := PChar(theNode.Title);
  AStream.writeBuffer(PStr^, Header.TitleLen);
  PStr := PChar(theNode.Note);
  AStream.writebuffer(PStr^, Header.NoteLen);
  (* save index *)
  AStream.writebuffer(theNode.Index, sizeof(Integer));
end;
(* this is for reading a TNode *)
function readNode(AStream: TStream): TNode;
var
  header: THeader
  PStr: PChar;
begin
  AStream.ReadBuffer(Header, sizeof(TNodeHeader);
  SetLength(Result.Title, Header.TitleLen);
  PStr := PChar(Result.Title);
  AStream.ReadBuffer(PStr^, Header.TitleLen);
  SetLength(Result.Note, Header.NoteLen);
  PStr := PChar(Result.Note);
  AStream.ReadBuffer(PStr^, Header.NoteLen);
  AStream.ReadBuffer(REsult.Index, sizeof(Integer)(* 4 bytes *);
end;
delphigeist