writing and reading inifiles ordered - delphi

I want to know how I can write an Inifile so that I can read from it in order.
First of all writing it: Let say I have 2 strings I want to save (Name and Lastname).
This is a button. So it will be clicked everytime:
Ini.WriteString(person1, 'Name', Name.text);
Ini.WriteString(person1, 'Lastname', Lastname.text);
How can I make this person1, alter everytime a new person is added. so next time person2, person3, etc. How is it possible? Do I have to look at what the previous one said? or can it remember? Maybe if I understand this, reading from the file will go in same maner.
kind regards and thank you

I suppose you want to store n data items in an INI file. I do that all the time -- it's easy. First, saving is trivial, but of course, the exact procedure depends on where you get the data from. If you have two arrays of strings, for example (let's call them FirstNames and LastNames), then you just do
for i := 0 to high(FirstNames) do
begin
IniFile.WriteString('Names', 'FirstName' + IntToStr(i), FirstNames[i]);
IniFile.WriteString('Names', 'LastName' + IntToStr(i), LastNames[i]);
end;
To read the (unknown number of items), do something like
for i := 0 to MaxInt do
if ValueExists('Names', 'FirstName' + IntToStr(i)) then
// Do something with ReadString('Names', 'FirstName' + IntToStr(i))
// and ReadString('Names', 'LastName' + IntToStr(i))
else
break;
Notice that 'Names' is the arbitrary name of the section in the INI file under which you store the data.
Update
If you just want to save items when clicking a button, why not do
private
{ Private declarations }
n: integer;
and
procedure TForm1.FormCreate(Sender: TObject);
begin
n := 0;
end;
and then
procedure TForm1.Button1Click(Sender: TObject);
begin
with TIniFile.Create('myfile.ini') do
try
WriteString('Names', 'FirstName' + IntToStr(n), Edit1.Text);
WriteString('Names', 'LastName' + IntToStr(n), Edit2.Text);
inc(n);
finally
Free;
end;
end;
If you instead would prefer to have a section per item, do
procedure TForm1.Button1Click(Sender: TObject);
begin
with TIniFile.Create('myfile.ini') do
try
WriteString('Name' + IntToStr(n), 'FirstName', Edit1.Text);
WriteString('Name' + IntToStr(n), 'LastName', Edit2.Text);
inc(n);
finally
Free;
end;
end;
instead.

To get sequential numbering of sections each time you save a person you would have to read all the section names, determine the highest number, increment that and then use it to write the a new section with that name and the new person's values.
Something like:
var
IniFile: TIniFile;
SL: TStringList;
i: Integer;
Highest: Integer;
begin
IniFile := TIniFile.Create('MyIni.ini');
try
SL := TStringList.Create;
try
IniFile.ReadSections(SL);
Highest := 0;
for i := 0 to SL.Count - 1 do begin
Highest := Max(Highest, StrToIntDef(Copy(SL[i], Length('Person'), MAXINT), 0));
end;
IniFile.WriteString('Person' + IntToStr(Highest), 'Name', Name.Text);
IniFile.WriteString('Person' + IntToStr(Highest), 'LastName', LastName.Text);
finally
SL.Free;
end;
finally
IniFile.Free;
end;
end;

I suspect that using an ini file to do this is the wrong way to go about it. Ini files are not designed to store things in order. The simplest way of achievng this is to use a TStringList and save and load it to disk.

Related

How to extract the first instance of unique strings

I need to extract a list of unique items from 12 years' worth of consistent computer-generated one-per day text files. The filenames vary only by the included date, so it is easy to generate the required name in code. They consist of a list of all the aircraft movements at my local airport during the given day, in time order. Naturally, the same aircraft come and go many times, and the objective is to loop through the files, pick out the first instance of when each individual aircraft appears (the first visit or FV) copy it to a list and then ignore it from then on. The result should be a list of all the first visits in date order. Should be simple, but... My program is small so I am including the entire implementation code.
procedure TForm1.FormCreate(Sender: TObject);
begin
FileDate := StrToDate('01/01/2007');
FName := 'E:LGW Reports/SBSLGW2007-01-01.txt'; //1st file to be read
FDStr := copy(FName, 21, 10);
TempList := TStringList.Create; //temp holder for file contents
FVCheckList := TStringList.Create; //holds unique identifier (UID)
FVCheckList.Sorted := TRUE;
FVCheckList.Duplicates := dupIgnore;
FVList:= TStringList.Create; //the main output
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
Memo1.Lines.Append('Started');
Repeat
TempList.Clear;
TempList.LoadFromFile(FName);
for i := 1 to TempList.Count-1 do
begin
Line := TempList.Strings[i];
//create a //create a Unique identifier (UID) from elements in Line
Serial := Trim(Copy(Line, 22, 9));
MsnPos1 := Pos('[', Line) + 1;
MsnPos2 := Pos(']', Line);
Msn := copy(Line, MsnPos1, (MsnPos2 - MsnPos1));
UID := Serial + '/' + Msn;
//
if (FVCheckList.IndexOf(UID) < 0) then
begin
FVCheckList.Append(UID);
//Add date of file to Line, otherwise it gives no clue when FV was
FVList.Append(FormatDateTime('YYYY-MM-DD', FileDate) + ' ' + Line);
FileDate := IncDay(FileDate, 1);
FName := 'E:LGW Reports/SBSLGW' + FormatDateTime('YYYY-MM-DD', FileDate) + '.txt';
end;
end;
Until FileExists(FName) = FALSE;
FVCheckList.SaveToFile('E:LGW Reports/First Visit Checklist.txt');
FVList.SaveToFile('E:LGW Reports/First Visits.txt');
Memo1.Lines.Append('Finished');
Memo1.Lines.SaveToFile('E:LGW Reports/Files parsed.txt');
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
TempList.Free;
FVCheckList.Free;
FVList.Free;
end;
There are no compiler errors, it runs to completion in seconds and produces the two text files specified, correctly formatted. The big problem is that the lines actually listed in FVList are not always the very first visit of the aircraft, they can be the first, the most recent or somewhere in between. I cannot see any obvious clue as to why the wrong instance is appearing: if my code is right, then something is wrong with the functioning of TStringList FVCheckList. The fault is far more likely to be something I have overlooked, or my understanding of how .dupIgnore works, or maybe my looping isn't working as it should.
I should be very grateful for any practical help. Many thanks in advance.
Repeat
...
Until FileExists(FName) = FALSE;
Should be
While FileExists(FName) = TRUE do
Begin
End;
If the first 2007-01-01 file does not exist, your code will crash on the first LoadFromFile() since you don't check for the file's existence before loading it, unlike with the subsequent files.
Otherwise, I would suggest sticking with repeat but assign FName at the top of each loop iteration instead of initializing it outside the loop and then reassigning at the bottom of each iteration. No need to duplicate efforts.
If you check IndexOf() manually, you don't need to use Sorted or dupIgnore at all. This is what you should be doing in this situation. When dupIgnore ignores a new string, Append() doesn't tell you that the string was ignored. To do that, you would have to check whether the Count was actually increased or not.
Inside the outer loop, the reassignment of FileDate and FName should be outside of the inner for loop,not inside the for loop at all.
Try this instead:
procedure TForm1.FormCreate(Sender: TObject);
begin
FileDate := EncodeDate(2007,1,1);
FDStr := FormatDateTime('YYYY-MM-DD', FileDate);
TempList := TStringList.Create; //temp holder for file contents
FVCheckList := TStringList.Create; //holds unique identifier (UID)
FVList := TStringList.Create; //the main output
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
Memo1.Lines.Append('Started');
Repeat
FName := 'E:LGW Reports/SBSLGW' + FormatDateTime('YYYY-MM-DD', FileDate) + '.txt';
if not FileExists(FName) then Break;
Memo1.Lines.Append(FName)
TempList.LoadFromFile(FName);
for i := 1 to TempList.Count-1 do
begin
Line := TempList.Strings[i];
//create a Unique identifier (UID) from elements in Line
Serial := Trim(Copy(Line, 22, 9));
MsnPos1 := Pos('[', Line) + 1;
MsnPos2 := PosEx(']', Line, MsnPos1);
Msn := copy(Line, MsnPos1, (MsnPos2 - MsnPos1));
UID := Serial + '/' + Msn;
if FVCheckList.IndexOf(UID) = -1 then
begin
FVCheckList.Append(UID);
//Add date of file to Line, otherwise it gives no clue when FV was
FVList.Append(FormatDateTime('YYYY-MM-DD', FileDate) + ' ' + Line);
end;
end;
FileDate := IncDay(FileDate, 1);
end;
FVCheckList.SaveToFile('E:LGW Reports/First Visit Checklist.txt');
FVList.SaveToFile('E:LGW Reports/First Visits.txt');
Memo1.Lines.Append('Finished');
Memo1.Lines.SaveToFile('E:LGW Reports/Files parsed.txt');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
TempList.Free;
FVCheckList.Free;
FVList.Free;
end;

TClientDataSet And Deleting Files In A Collection

I have an experimental app that I am developing that adds image filenames to a collection. I am attempting to find the most efficient way to delete all the files in a collection except
for files that exist in another collection. Files can exist in any collection.
I have a TClientDataSet with the following fields:
ClientDataSet1.FieldDefs.Add('Index', ftInteger);
ClientDataSet1.FieldDefs.Add('Collection', ftString, 50);
ClientDataSet1.FieldDefs.Add('Filename', ftString, 254);
I came up with this which seems to work but seems inefficient:
var
i: Integer;
j: Integer;
iCollectionToDelete: string;
iCollection: string;
iFilename: string;
iFilenameInOtherCollection: string;
iFilesInOtherCollectionsStringList: TStringList;
begin
iCollectionToDelete := ListBox1.Items[ListBox1.ItemIndex];
{ Set filtered to false to see all the records in the database }
ClientDataSet1.Filtered := False;
{ Create a list of files not in the collection to be deleted }
iFilesInOtherCollectionsStringList := TStringList.Create;
try
for i := 0 to ClientDataSet1.RecordCount - 1 do
begin
iCollection := ClientDataSet1.FieldByName('Collection').AsString;
iFilename := ClientDataSet1.FieldByName('Filename').AsString;
if iCollection <> iCollectionToDelete then
begin
iFilenameInOtherCollection := ClientDataSet1.FieldByName('Filename').AsString;
iFilesInOtherCollectionsStringList.Add(iFilename);
end;
ClientDataSet1.Next;
end;
{ Compare the iFilenameInOtherCollection with all the filenames in the
dataset and if the iFilename is not in the other collection then
erase the file }
ClientDataSet1.First;
for i := 0 to ClientDataSet1.RecordCount - 1 do
begin
iFilename := ClientDataSet1.FieldByName('Filename').AsString;
ClientDataSet1.Next;
for j := 0 to iFilesInOtherCollectionsStringList.Count - 1 do
begin
iFilenameInOtherCollection := iFilesInOtherCollectionsStringList[j];
if iFilesInOtherCollectionsStringList.IndexOf(iFilename) = -1 then
if FileExists(iFilename) then
WindowsErase(handle, iFilename, False, False, False, False);
end;
end;
finally
iFilesInOtherCollectionsStringList.Free;
end;
end;
My question is can this be made more efficient or is there a way to do the same thing
using just TClientDataset methods?
Just for amusement, I thought I'd try doing this without using Stringlists at all, and instead use a couple of features of ClientDataSets, namely Filtering and the ability to copy data from one CDS to another in a single statement. It's quite a bit shorter than using stringlists and probably easier to maintain/modify/refactor as a result.
I've not benchmarked it against the Stringlist version but would be surprised if it were any faster,
because it depends on TClientDataSet.Locate, which is not a particularly efficient operation even when working against an indexed field.
The code is below. Hopefully the comments will explain how it works.
procedure TForm1.SetUp;
begin
ClientDataSet1.FieldDefs.Add('Index', ftInteger);
ClientDataSet1.FieldDefs.Add('Collection', ftString, 50);
ClientDataSet1.FieldDefs.Add('Filename', ftString, 254);
ClientDataSet1.CreateDataSet;
// Create some test data
ClientDataSet1.InsertRecord([1, 'C1', 'F1']);
ClientDataSet1.InsertRecord([2, 'C2', 'F1']);
ClientDataSet1.InsertRecord([3, 'C3', 'F1']);
ClientDataSet1.InsertRecord([4, 'C1', 'F2']);
ClientDataSet1.InsertRecord([5, 'C3', 'F3']);
end;
procedure Tform1.ApplyCDSFilter(CDS : TClientDataSet; FilterExpr : String);
// utility routine to filter/unfilter a dataset
begin
CDS.Filtered := False;
CDS.Filter := FilterExpr;
if FilterExpr <> '' then
CDS.Filtered := True;
end;
procedure TForm1.RemoveFilesOnlyInCollection(CollectionName : String);
var
CDS : TClientDataSet;
FilterExpr : String;
AFileName : String;
begin
// In the following, I'm just going to add the names of the files which belong to the
// specified collection as well as to another one
// to a listbox so as to be able to check the results by inspection
Listbox1.Items.Clear;
// next create a temporary CDS
CDS := TClientDataSet.Create(Nil);
// Index it by Filename
CDS.IndexFieldNames := 'Filename';
// Copy the data from ClientDataSet1 into it
CDS.Data := ClientDataSet1.Data;
// Construct a filter expression to select the collection whose members are to be
// retained. NOTE : the QuotedStr is to handle quotes embedded in the collection name.
FilterExpr := '(Collection =' + QuotedStr(CollectionName) + ')';
// Apply the filter to ClientDataSet1, so that only records that contain the CollectionName
// are "visible", temporarily
ApplyCDSFilter(ClientDataSet1, FilterExpr);
// Next, negate the filter expression and apply it to the temporary CDS
FilterExpr := 'not ' + FilterExpr;
ApplyCDSFilter(CDS, FilterExpr);
// Now, we can loop through ClientDataSet1 and test whether the Filename is present
// in the temporary CDS. If it is, that means that the Filename belongs to another
// collection too.
try
ClientDataSet1.DisableControls;
ClientDataSet1.First;
while not ClientDataSet1.Eof do begin
AFileName := ClientDataSet1.FieldByName('Filename').AsString;
if not CDS.Locate('Filename', AFileName, [loCaseInsensitive]) then
Listbox1.Items.Add(AFileName);
ClientDataSet1.Next;
end;
finally
CDS.Free;
ClientDataSet1.EnableControls;
ApplyCDSFilter(ClientDataSet1, '');
end;
end;
Just add iFilesInOtherCollectionsStringList.Sorted := True after you fill it. IndexOf will then use fast binary search instead of extremely slow one-by-one looping. Probably that will be enough for your purposes.
Another option is to prepare list-to-delete first and then launch a worker thread which will execute removal in the background. This will likely help because usually file operations much more slow than memory comparisons. You may check if it is the deletion that slows down your process by commenting out the WindowsErase line.

Save Dbgrid Column Width Values to Ini and Reread them

I have Inherited form and a Ehlib dbgrid on it for selecting-listing records... The form is ready made for a lot of buttons and im using this form with different queries.
Like this...
If Dm.QrTmp.Active then Dm.QrTmp.Active:=False;
Dm.QrTmp.SQL.Clear;
Dm.QrTmp.SQL.Add(' SELECT ');
Dm.QrTmp.SQL.Add(' ch.cari_RECno AS KayitNo ');
Dm.QrTmp.SQL.Add(' FROM CARI_HESAPLAR ch ');
if FrmTmp=nil then FrmTmp:=TFrmTmp.Create(Self);
FrmTmp.StatusBar.Hide;
Dm.QrTmp.Open;
FrmTmp.DbGrid.DataSource:=Dm.DsQrTmp;
This query is cutted down but i have of course use a lot of fields. And Queries changes alot of time in the application.
The problem is column width. Manager wants to set column widths and restore them again. Actually my grid component supports save - restore column properties but as you can see my usage i m not using static columns. also i dont want to use xgrid.columns[0].width percent by percent.
Im using a ini in may app.
I want to add new section on it and named "Gridwidth"...
[Gridname]
Colwidths=x,y,z (where they are width values)
I'm now coding this line by line.
My write procedure is like this.
With dbgridx do
begin
For i:=0 to columns.count-1
begin
widthstr:=widthstr+Column[i].width+',';
end;
end;
Widthstr will be "15,23,45,67" etc...
But i want to know if this is good solution and if somebody know a better way and has some good code.
This should do it:
uses
IniFiles;
const
SETTINGS_FILE = 'Edijus\Settings.ini';
procedure TForm1.LoadDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i, j: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or (not Assigned(ADBGrid.DataSource)) or
(not Assigned(ADBGrid.DataSource.DataSet)) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.DataSource.DataSet.Fields.Count) do
for j := 0 to Pred(ADBGrid.Columns.Count) do
begin
if (ADBGrid.DataSource.DataSet.Fields[i].FieldName = ADBGrid.Columns[j]
.FieldName) then
ADBGrid.Columns[j].Width :=
_MemIniU.ReadInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[j].FieldName, 64);
end;
finally
FreeAndNil(_MemIniU);
end;
end;
procedure TForm1.SaveDBGridColumnsWidth(const ADBGrid: TDBGrid);
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
i: integer;
_ParentClass: TWinControl;
begin
_SettingsPath := GetHomePath + PathDelim + SETTINGS_FILE;
if (not Assigned(ADBGrid)) or
(not ForceDirectories(ExtractFilePath(_SettingsPath))) then
Exit;
_MemIniU := TMemIniFile.Create(_SettingsPath, TEncoding.UTF8);
try
_ParentClass := ADBGrid.Parent;
while not(_ParentClass is TForm) do
_ParentClass := _ParentClass.Parent;
for i := 0 to Pred(ADBGrid.Columns.Count) do
if (ADBGrid.Columns[i].FieldName <> '') then
_MemIniU.WriteInteger(_ParentClass.Name + '_' + ADBGrid.Name,
ADBGrid.Columns[i].FieldName, ADBGrid.Columns[i].Width);
_MemIniU.UpdateFile;
finally
FreeAndNil(_MemIniU);
end;
end;

Compare large files

I have some files (3-5) that i need to compare:
File 1.txt have 1 million strings.
File 2.txt have 10 million strings.
File 3.txt have 5 million strings.
All these files are compared with file keys.txt (10 thousand strings). If line from currently opened file is the same as one of lines from keys.txt, write this line into output.txt (I hope you understand what i mean).
Now i have:
function Thread.checkKeys(sLine: string): boolean;
var
SR: TStreamReader;
line: string;
begin
Result := false;
SR := TStreamReader.Create(sKeyFile); // sKeyFile - Path to file keys.txt
try
while (not(SR.EndOfStream)) and (not(Result))do
begin
line := SR.ReadLine;
if LowerCase(line) = LowerCase(sLine) then
begin
saveStr(sLine);
inc(iMatch);
Result := true;
end;
end;
finally
SR.Free;
end;
end;
procedure Thread.saveStr(sToSave: string);
var
fOut: TStreamWriter;
begin
fOut := TStreamWriter.Create('output.txt', true, TEncoding.UTF8);
try
fOut.WriteLine(sToSave);
finally
fOut.Free;
end;
end;
procedure Thread.updateFiles;
begin
fmMain.flDone.Caption := IntToStr(iFile);
fmMain.flMatch.Caption := IntToStr(iMatch);
end;
And loop with
fInput := TStreamReader.Create(tsFiles[iCurFile]);
while not(fInput.EndOfStream) do
begin
sInput := fInput.ReadLine;
checkKeys(sInput);
end;
fInput.Free;
iFile := iCurFile + 1;
Synchronize(updateFiles);
So, if i compare these 3 files with file key.txt it takes about 4 hours. How to decrease compare time?
An easy solution is to use an associative container to store your keys. This can provide efficient lookup.
In Delphi you can use TDictionary<TKey,TValue> from Generics.Collections. The implementation of this container hashes the keys and provides O(1) lookup.
Declare the container like this:
Keys: TDictionary<string, Boolean>;
// doesn't matter what type you use for the value, we pick Boolean since we
// have to pick something
Create and populate it like this:
Keys := TDictionary<string, Integer>.Create;
SR := TStreamReader.Create(sKeyFile);
try
while not SR.EndOfStream do
Keys.Add(LowerCase(SR.ReadLine), True);
// exception raised if duplicate key found
finally
SR.Free;
end;
Then your checking function becomes:
function Thread.checkKeys(const sLine: string): boolean;
begin
Result := Keys.ContainsKey(LowerCase(sLine));
if Result then
begin
saveStr(sLine);
inc(iMatch);
end;
end;
First of all you should load Keys.txt into for example TStringList. Don't read keys each time from file. The second in such high count loop you shouldn't use procedure/functions calls you should do all checks inline.
Something like this:
Keys:=TStringList.Create;
Keys.LoadFromFile('keys.txt');
fInput := TStreamReader.Create(tsFiles[iCurFile]);
fOut := TStreamWriter.Create('output.txt', true, TEncoding.UTF8);
while not(fInput.EndOfStream) do
begin
sInput := fInput.ReadLine;
if Keys.IndexOf(sInput)>=0 then
begin
fOut.WriteLine(sInput);
inc(iMatch);
end;
end;
fInput.Free;
fOut.Free;
iFile := iCurFile + 1;
Synchronize(updateFiles);
Keys.Free;

Delphi TQuery save to csv file

I want to export content of a TQuery to a CSV file without using a 3d part component(Delphi 7). From my knowledge this can not be accomplished with Delphi standard components.
My solution was to save the content in a StringList with a CSV format, and save it to a file.
Is there any comfortable solution?
PS:I don't want to use JvCsvDataSet or any component. Question is: can this be accomplished only with Delphi 7 or higher standard components?
Thank you in advance!
Of course it can.
You just have to do the work to properly output the CSV content (quoting properly, handling embedded quotes and commas, etc.). You can easily write the output using TFileStream, and get the data using the TQuery.Fields and TQuery.FieldCount properly.
I'll leave the fancy CSV quoting and special handling to you. This will take care of the easy part:
var
Stream: TFileStream;
i: Integer;
OutLine: string;
sTemp: string;
begin
Stream := TFileStream.Create('C:\Data\YourFile.csv', fmCreate);
try
while not Query1.Eof do
begin
// You'll need to add your special handling here where OutLine is built
OutLine := '';
for i := 0 to Query.FieldCount - 1 do
begin
sTemp := Query.Fields[i].AsString;
// Special handling to sTemp here
OutLine := OutLine + sTemp + ',';
end;
// Remove final unnecessary ','
SetLength(OutLine, Length(OutLine) - 1);
// Write line to file
Stream.Write(OutLine[1], Length(OutLine) * SizeOf(Char));
// Write line ending
Stream.Write(sLineBreak, Length(sLineBreak));
Query1.Next;
end;
finally
Stream.Free; // Saves the file
end;
end;
The original question asked for a solution using a StringList. So it would be something more like this. It will work with any TDataSet, not just a TQuery.
procedure WriteDataSetToCSV(DataSet: TDataSet, FileName: String);
var
List: TStringList;
S: String;
I: Integer;
begin
List := TStringList.Create;
try
DataSet.First;
while not DataSet.Eof do
begin
S := '';
for I := 0 to DataSet.FieldCount - 1 do
begin
if S > '' then
S := S + ',';
S := S + '"' + DataSet.Fields[I].AsString + '"';
end;
List.Add(S);
DataSet.Next;
end;
finally
List.SaveToFile(FileName);
List.Free;
end;
end;
You can add options to change the delimiter type or whatever.
This is like the Rob McDonell solution but with some enhancements: header, escape chars, enclosure only when required, and ";" separator.
You can easily disable this enhancements if not required.
procedure SaveToCSV(DataSet: TDataSet; FileName: String);
const
Delimiter: Char = ';'; // In order to be automatically recognized in Microsoft Excel use ";", not ","
Enclosure: Char = '"';
var
List: TStringList;
S: String;
I: Integer;
function EscapeString(s: string): string;
var
i: Integer;
begin
Result := StringReplace(s,Enclosure,Enclosure+Enclosure,[rfReplaceAll]);
if (Pos(Delimiter,s) > 0) OR (Pos(Enclosure,s) > 0) then // Comment this line for enclosure in every fields
Result := Enclosure+Result+Enclosure;
end;
procedure AddHeader;
var
I: Integer;
begin
S := '';
for I := 0 to DataSet.FieldCount - 1 do begin
if S > '' then
S := S + Delimiter;
S := S + EscapeString(DataSet.Fields[I].FieldName);
end;
List.Add(S);
end;
procedure AddRecord;
var
I: Integer;
begin
S := '';
for I := 0 to DataSet.FieldCount - 1 do begin
if S > '' then
S := S + Delimiter;
S := S + EscapeString(DataSet.Fields[I].AsString);
end;
List.Add(S);
end;
begin
List := TStringList.Create;
try
DataSet.DisableControls;
DataSet.First;
AddHeader; // Comment if header not required
while not DataSet.Eof do begin
AddRecord;
DataSet.Next;
end;
finally
List.SaveToFile(FileName);
DataSet.First;
DataSet.EnableControls;
List.Free;
end;
end;
Delphi does not provide any built-in access to .csv data.
However, following the VCL TXMLTransform paradigm, I wrote a TCsvTransform class helper that will translate a .csv structure to /from a TClientDataSet.
As for the initial question that was to export a TQuery to .csv, a simple TDataSetProvider will make the link between TQuery and TClientDataSet.
For more details about TCsvTransform, cf http://didier.cabale.free.fr/delphi.htm#uCsvTransform

Resources