Add ini file as resource file and read from it - delphi

I would like to add an ini file to my delphi project as a resource file.
I know where you go to add a file as a resource file:
Project > Resources and Images > Add
But once thats done what else do I need to do to be able to read from the file? I haven't used resource files before.
Is there any documentation on the process?
Thanks,

The built-in INI file classes in the RTL, providing in the System.IniFiles unit, require the INI file to be a disk file. So you could extract the resource to disk and read it from there.
If you don't like the idea of that then you could write your own INI file parser that operated on a stream rather than a file. You could use the code of TMemIniFile to guide you. Copy that code and replace LoadValues with code to read from a stream rather than a file. Or if you look hard enough then you may well find a third party INI parser that operates on streams.
If you are prepared to consider other formats then you might use JSON rather than INI. The built-in JSON parser does not require the input data to reside on disk. They can operate on in-memory strings, which sounds rather more convenient.
This above text is in fact nonsense. Thank you to Remy for pointing that out. You can use TMemIniFile and its SetStrings method to parse INI content that does not reside on disk. It goes like this:
Put your INI content into a resource as a string.
Load that resource into a string variable.
Create a TStringList, and assign the string variable to the Text property of the string list.
Create a TMemIniFile.
Call SetStrings on the TMemIniFile passing the string list.
Or:
Put your INI content into a resource as a string.
Create a TResourceStream object to read that resource.
Create a TStringList object.
Call LoadFromStream on the string list passing the resource stream.
Create a TMemIniFile.
Call SetStrings on the TMemIniFile passing the string list.
Having said all of this, it seems odd that you would choose to do this at all. Wouldn't it be simpler just to hard code the configuration information in a unit, as a series of constants?

Using the "Resources and Images" dialog, add the .ini file to the project as a RCDATA resource type. Then, you can load it at runtime like this:
uses
..., Classes, IniFiles;
var
Ini: TMemIniFile;
List: TStringList;
Strm: TResourceStream;
begin
Ini := TMemIniFile.Create;
try
List := TStringList.Create;
try
Strm := TResourceStream.Create(HInstance, 'ResourceIDHere', RT_RCDATA);
try
List.LoadFromStream(Strm);
finally
Strm.Free;
end;
Ini.SetStrings(List);
finally
List.Free;
end;
// use Ini as needed...
finally
Ini.Free;
end;
end;

To add the files in the executable, be sure to add a .rcfile (not the .ini files directly), for example inis.rc (so in the source file dpr you'll have {$R 'inis.res' 'inis.rc'}) and write in this file a list like this:
this 100 "this.ini"
that 100 "that.ini"
If you've stored the ini files in a (relative) directory, be sure to double the backslashes since this in C-syntax. (The 100here is the resource type, there's no number assigned to ini-files specifically so we'll use an unassigned number. The best next thing is 23 which is assigned to RT_HTML, see below)
If you're not using groups (and lines with just [GroupName]), I'd suggest you use plain TStringList objects and their Values property. To load them with the data, use something like this:
var
sl:TStringList;
r:TResourceStream;
begin
sl:=TStringList.Create;
try
r:=TResourceStream.Create(HInstance,'this',MAKEINTRESOURCE(100));
try
sl.LoadFromStream(r);
finally
r.Free;
end;
//sl.Values['Setting']
finally
sl.Free;
end;
end;

imho, an ini file is just a text file to distribute with an application so you can control behavior of the application in one particular environment. For example, you could store a language in "language.ini", read it from your source code, and present the GUI based on that language.
To accomplish this, your ini file contains:
[general]
language=Russian
then read it from Delphi:
...
uses Inifiles;
...
var CurrentLanguage:string;
...
Ini := TIniFile.Create('C:\somedir\languages.ini');
CurrentLanguage := Ini.ReadString('General', 'language', 'English');//if key isn't found, language is English
Ini.free();
So basically it contains TEXT info... as said above, if you add it as a resource, you might as well hardcode it. Resources should be used primarily for binary data (an image, audio file, video, etc).

Related

The best solution to process the huge text file data in Delphi 7

I have text file like this:
"01","AAA","AAAAA"
"02","BBB","BBBBB","BBBBBBBB"
"03","CCC"
"04","DDD","DDDDD"
I want to load this text file data into temp table in sybase db. So, I need to build a program to read line by line this text file until eof. If the text file size is small, the process to read line by line is fast. But if text file size is too big (can be more than 500M), the process read line by line is too slow. I think the read line by line method not suitable for huge text file. So, need to find other solution to load text file data into db instead of read text file line by line method. Any suggestion?
Example code:
var
myFile : TextFile;
text : string;
begin
// Try to open the Test.txt file for writing to
AssignFile(myFile, 'Test.txt');
// Display the file contents
while not Eof(myFile) do
begin
ReadLn(myFile, text);
TempTable.append;
TempTable.FieldByName('Field1').asstring=Copy(text,2,2);
TempTable.FieldByName('Field2').asstring=Copy(text,7,3);
TempTable.FieldByName('Field3').asstring=Copy(text,13,5);
TempTable.FieldByName('Field4').asstring=Copy(text,21,8);
TempTable.post;
end;
// Close the file for the last time
CloseFile(myFile);
end;
Some general tips:
Ensure your TempTable is in memory, or use a fast database engine - take a look at SQlite3 or other means (like FireBird embedded, NexusDB or ElevateDB) as possible database alternatives;
If you do not use a TTable, but a true database, ensure you nest the insert within a Transaction;
For a true database, check out if you can not use ArrayDML feature, which is much faster for inserting a lot of data as you want in a remote database (like Sybase) - such Array DML is handled for instance with FireDAC AFAIK;
The FieldByName('...') method is known to be very slow: use locals TField variables instead;
When using a TextFile, assign a bigger temporary buffer;
If you are using newer Unicode versions of Delphi (2009+), using TextFile is not the best option.
So your code may be:
var
myFile : TextFile;
myFileBuffer: array[word] of byte;
text : string;
Field1, Field2, Field3, Field4: TField;
begin
// Set Field* local variables for speed within the main loop
Field1 := TempTable.FieldByName('Field1');
Field2 := TempTable.FieldByName('Field2');
Field3 := TempTable.FieldByName('Field3');
Field4 := TempTable.FieldByName('Field4');
// Try to open the Test.txt file for writing to
AssignFile(myFile, 'Test.txt');
SetTextBuf(myFile, myFileBuffer); // use 64 KB read buffer
// Display the file contents
while not Eof(myFile) do
begin
ReadLn(myFile, text);
TempTable.append;
Field1.asInteger := StrToInt(Copy(text,2,2));
Field2.asString := Copy(text,7,3);
Field3.asString := Copy(text,13,5);
Field4.asString := Copy(text,21,8);
TempTable.post;
end;
// Close the file for the last time
CloseFile(myFile);
end;
You can achieve very high speed with embedded engines, with almost no size limit, but your storage. See for instance how fast we can add content to a SQLite3 database in our ORM: about 130,000 / 150,000 rows per second in a database file, including all ORM marshalling. I also found out that SQLite3 generates much smaller database files than alternatives. If you want fast retrieval of any field, do not forget to define INDEXes in your database, if possible after the insertion of row data (for better speed). For SQLite3, there is already an ID/RowID integer primary key available, which maps your first data field, I suppose. This ID/RowID integer primary key is already indexed by SQLite3. By the way, our ORM now supports FireDAC / AnyDAC and its advanced Array DML feature.
Text files normally have a very small buffer. Look into using the SetTextBuf function to increase your performance.
var
myFile : TextFile;
text : string;
myFileBuffer: Array[1..32768] of byte;
begin
// Try to open the Test.txt file for writing to
AssignFile(myFile, 'Test.txt');
SetTextBuf(MyFile, myFileBuffer);
Reset(MyFile);
// Display the file contents
while not Eof(myFile) do
begin
ReadLn(myFile, text);
end;
// Close the file for the last time
CloseFile(myFile);
end;
In addition to what has already been said, I would also avoid using any TTable component. You would be better off using a TQuery type component (depending on the access layer you're using). Something like this :-
qryImport.SQL := 'Insert Into MyTable Values (:Field1, :Field2, :Field3, :Field4);';
Procedure ImportRecord(Const pField1, pField2, pField3, pField4 : String);
Begin
qryImport.Close;
qryImport.Params[0].AsString := pField1;
qryImport.Params[1].AsString := pField2;`
qryImport.Params[2].AsString := pField3;
qryImport.Params[3].AsString := pField4;
qryImport.ExecSQL;
End;
Hope this helps.
Another approach would be to use memory-mapped files (you can google or go torry.net to find implementations). it would not work well for files >1gb (in win32,, in win64 you can map virtually any file). It would turn all your file into PAnsiChar that you would be able to scan like a one large buffer, searching for #10 and #13 (alone or in pairs) and thus manually splitting strings.
If you use (or don't mind starting to use) the JEDI Jvcl, they have a TJvCSVDataSet which allows you to simply use your CSV file like any other dataset in Delphi, including being able to define persistent fields and use "standard" Delphi database functionality:
JvCSVDataSet1.FileName := 'MyFile.csv';
JvCSVDataSet1.Open;
while not JvCSVDataSet1.Eof do
begin
TempTable.Append; // Posts last appended row automatically;
// no need to call Post here.
// Assumes TempTable has same # of fields in the
// same order
for i := 0 to JvCSVDataSet1.FieldCount - 1 do
TempTable.Fields[i].AsString := JvCSVDataSet1.Fields[i].AsString;
JvCSVDataSet1.Next;
end;
// Post the last row appended when the loop above exited
if TempTable.State in dsEditModes then
TempTable.Post;
In Delphi 7 you can use Turbo Power SysTools TStAnsiTextStream() to read and write in a line oriented way, but using the thread safe TStream implementation and not the unsafe old pascal file interface. In later Delphi versions you will find something alike in the standard RTL (although they are a little different in their implementation), but Delphi 7 didn't offer much for text file manipulation.

Parsing Memo for Files and Folders

I'm totally new to Delphi I guess learning by doing something should be ok (My hope!)
My Idea was because I often have to re-create the same tasks:
creating always the same directorys which contains sometimes files and sometimes leaved empty...
So my conclusion was to automate it in some way.
Assume a Memo containing the following:
config.xml|enc
/skin
/data/defines.dat:blub
/temp
The Basepath where all the stuff above should be created inside:
C:\users\BGates\test
":blub" is just placeholders e.g. :blub can contain any text which comes from another memo in my application which means that later defines.dat is filled with the text blub contains...
As you can see sometimes I use | and sometimes : for the placeholder...
So from the information above I would like to parse the contents of the memo to create a directory structure like this:
C:\users\BGates\test\
config.xml
skin
data
defines.dat (while defines.dat will contain the stuff which comes from blub)
temp
My problem is the parsing of the memo esspecially how to decide its a folder or its a folder in another folder, then its a file in the root or a file inside of a folder and so on...
Well it might be there is an easier way (I was reading about csv files and such but then? My tool would be hard to understand for someone using it which doesn't know how a csv file needs to look like) while my example above feels maybe familier to them...
Could someone show me please an example how to parse it in a correct (best practice) way So I could learn from it?
There are routines in the SysUtils unit that make file path parsing a lot easier. Have a look at ExtractFileName and ExtractFilePath, for starters. Also, if you're using a recent version of Delphi, (D2010 or any of the XE line,) the IOUtils unit contains a set of helper methods under the TPath record that simplify working with paths.
For example, if I wanted to deal with the line /data/defines.dat:blub, I'd do something like this:
function NormalizePath(const name: string): string;
begin
result := StringReplace(name, '/', '\', [rfReplaceAll]);
end;
procedure ProcessLine(line: string);
var
path, filename, data: string;
colonPos: integer;
begin
colonPos := pos(':', line);
if colonPos > 0 then
begin
data := copy(line, colonPos + 1);
delete(line, colonPos, MAXINT);
end;
line := TPath.Combine(BASE_PATH, normalizePath(line));
if ExtractFileExt(line) = '' then
path := line
else begin
path := ExtractFilePath(line);
filename := line;
end;
ForceDirectories(path); //ensure that the folder exists
if filename <> '' then
TFile.WriteAllText(filename, data);
end;
Note: I just wrote this off the top of my head. It may contain bugs. Don't trust it without testing it first. Also, this uses functionality from IOUtils, and things will be a little bit trickier if you don't have it in your version of Delphi. But this should give you the general idea of how to deal with the problem you're trying to solve.

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.

Problem adding lots of strings to a TStringList

I have a problem adding strings to a TStringList. I've searched other posts but couldn't find an answer to this.
What I'm trying to do is to add a big amount of strings to a TStringList (more than 14000) but somewhere in the process I get an EAccessViolation. Here's the code I'm using:
procedure TForm1.FormCreate(Sender: TObject);
begin
List := TStringList.Create;
List.Duplicates := dupAccept;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
List.Clear;
List.Add('125-AMPLE');
List.Add('TCUMSON');
List.Add('ATLV 4300');
List.Add('150T-15');
List.Add('TDL-08ZE');
List.Add('RT20L');
List.Add('SIN LINEA');
List.Add('TIARA');
List.Add('FL200ZK1');
List.Add('FL250ZK1');
List.Add('SIN LINEA');
List.Add('CENTAURO-70 S.P.');
List.Add('CORSADO');
{ This list continues to about 14000 strings...}
List.Add('VOSJOD 2');
List.Add('Z 125');
List.Add('ZUMY');
List.Add('NEW AGE 125');
List.Add('SIN LINEA');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(List);
end;
¿What's wrong with this code? The list contains duplicate strings so I set the Duplicates property to dupAccept. I was able to load the list using LoadFromFile, but I don't want to have a text file outside my application.
I hope you can help me!!! Please tell me if you need any further information.
Thank you very much. I really appreciate your help.
The suggestions for using an external file are on the mark here. However, your post indicates your desire for not having an external file. I would then suggest you link the file to the executable as a resource. You can easily do this following these steps:
Place all the strings into a text file called stringdata.txt (or whatever name you choose). Then create a .rc file of whatever name you choose and put the following into it (STRING_DATA can be any identifier you choose):
STRING_DATA RCDATA "stringdata.txt"
Create a .res file from the .rc:
BRCC32 <name of rc>.rc
Now reference this file from the source code. Place the following someplace in the unit:
{$R <name of res>.res}
Instead of loading from a file stream, load from a resource stream:
StringData := TResourceStream.Create(HInstance, 'STRING_DATA', RT_RCDATA);
try
List.LoadFromStream(StringData);
finally
StringData.Free;
end;
If you do command-line automated builds, I would suggest you keep the .rc file under source control and build the .res during the build process. This way you can also keep the stringdata.txt file under source control and any edits are automatically caught on the next build without having to explicitly build the .res file each time the .txt file changes.
What Delphi version are you using? Some older versions had a bug in the memory manager that can cause an access violation when trying to reallocate an array to a size that's too large.
Try adding FastMM4 to your project to replace the old memory manager and see if that helps.
Also, you're probably better off keeping the list in an external file. Yes, it's another file, but it also means that you can change the list without having to recompile the entire program. This also makes creating (and distributing!) updates easier.
Mason is probably right for the cause of the AV; this is quite a large array to grow.
On a side note, when doing such a long processing on a StringList, it's recommended to surround it by BeginUpdate/EndUpdate to avoid firing any update event.
Even if you don't have any now, they might be added later and you'll get problems.
Set list.capacity to the number of items you plan to add, immediately after you create the list. Alternatively, place the list in an RC file (named other than with the name of your project) and add it to your project. This gets compiled into your application, but does not involve executable code to create the list.
I would also worry about compiler integrity with a 14,000 line procedure. People have found other cases where going beyond anything reasonable breaks the compiler in various ways.
You may also want to try THashedStringList, could see a speed boost (although not in this function), although I'm not sure if the add method is a whole lot different.
try using the following instead of your code to add the strings to the StringList
var
Str: string;
begin
Str := '125-AMPLE' + #13#10;
Str := Str + 'TCUMSON' + #13#10;
Str := Str + 'ATLV 4300' + #13#10;
Str := Str + '150T-15' + #13#10;
................
List.Text := Str;
end;

Open an ANSI file and Save a a Unicode file using Delphi

For some reason, lately the *.UDL files on many of my client systems are no longer compatible as they were once saved as ANSI files, which is no longer compatible with the expected UNICODE file format. The end result is an error dialog which states "the file is not a valid compound file".
What is the easiest way to programatically open these files and save as a unicode file? I know I can do this by opening each one in notepad and then saving as the same file but with the "unicode" selected in the encoding section of the save as dialog, but I need to do this in the program to cut down on support calls.
This problem is very easy to duplicate, just create a *.txt file in a directory, rename it to *.UDL, then edit it using the microsoft editor. Then open it in notepad and save as the file as an ANSI encoded file. Try to open the udl from the udl editor and it will tell you its corrupt. then save it (using notepad) as a Unicode encoded file and it will open again properly.
Ok, using delphi 2009, I was able to come up with the following code which appears to work, but is it the proper way of doing this conversion?
var
sl : TStrings;
FileName : string;
begin
FileName := fServerDir+'configuration\hdconfig4.udl';
sl := TStringList.Create;
try
sl.LoadFromFile(FileName, TEncoding.Default);
sl.SaveToFile(FileName, TEncoding.Unicode);
finally
sl.Free;
end;
end;
This is very simple to do with my TGpTextFile unit. I'll put together a short sample and post it here.
It should also be very simple with the new Delphi 2009 - are you maybe using it?
EDIT: This his how you can do it using my stuff in pre-2009 Delphis.
var
strAnsi : TGpTextFile;
strUnicode: TGpTextFile;
begin
strAnsi := TGpTextFile.Create('c:\0\test.udl');
try
strAnsi.Reset; // you can also specify non-default 8-bit codepage here
strUnicode := TGpTextFile.Create('c:\0\test-out.udl');
try
strUnicode.Rewrite([cfUnicode]);
while not strAnsi.Eof do
strUnicode.Writeln(strAnsi.Readln);
finally FreeAndNil(strUnicode); end;
finally FreeAndNil(strAnsi); end;
end;
License: The code fragment above belongs to public domain. Use it anyway you like.

Resources