delphi write to file oem encoding - delphi

How is it possible to write to Delphi files, oem encoding?
How do you set the encoding? as string encoding is set by
setCodePage(RawBytes;Word;boolean);

You need the Windows API function CharToOemBuff().
EDIT
Inspired by #Free Consulting, the above API is what you would use in an older non-Unicode Delphi.
As #Free Consulting correctly points out, the new versions of Delphi offer extensive code page translation services. As a more modern variant of the old-style Pascal I/O, you could use a TStringList saved with a specified encoding.
Encoding := TEncoding.GetEncoding(GetOEMCP);
Try
StringList.SaveToFile('test.txt', Encoding);
Finally
Encoding.Free;
End;

I have a wrote a function that does that. It is no pretty, but it works.
function SetFileContent(aFileName: string; aFileContent: string; out aErrorMsg: string; aEncoding: TEncoding = nil; aRecreateFile: Boolean = True): Boolean;
var
vStream: TFileStream;
vCurEncoding: TEncoding;
vPreamble, vContent: TBytes;
vOffSet: Integer;
procedure _SetFileContent(aNewFile: Boolean);
begin
if aNewFile then begin
vStream := TFileStream.Create(aFileName, fmCreate);
try
vPreamble := aEncoding.GetPreamble;
If Length(vPreamble) > 0 then begin
vStream.WriteBuffer(Pointer(vPreamble)^, Length(vPreamble));
end;
vStream.WriteBuffer(Pointer(vContent)^, Length(vContent));
finally
vStream.Free;
End;
end
else begin
vStream := TFileStream.Create(aFileName, fmOpenWrite);
try
vStream.Position := vStream.Size;
vStream.WriteBuffer(Pointer(vContent)^, Length(vContent));
finally
vStream.Free;
end;
end;
end;
begin
Result := True;
try
vContent := BytesOf(aFileContent);
vCurEncoding := nil;
if aEncoding = nil then begin
aEncoding := TEncoding.Default;
end;
vOffSet := TEncoding.GetBufferEncoding(vContent, vCurEncoding);
if (vCurEncoding <> aEncoding) and aRecreateFile then begin
vContent := TEncoding.Convert(vCurEncoding, aEncoding, vContent, vOffSet, Length(vContent) - vOffSet);
end;
if FileExists(aFileName) then begin
if aRecreateFile then begin
_SetFileContent(True);
end
else begin
_SetFileContent(False);
end;
end
else begin
_SetFileContent(True);
end;
except
on E: Exception do begin
Result := False;
aErrorMsg := 'There was an error while trying to write the string ' + aFileContent + ' in the file ' + aFileName + '. Error: ' + E.Message;
end;
end;
end;

TStringList.SaveToFile() has a TEncoding parameter, you can use TEncoding.GetEncoding() to get an encoding object for any installed codepage, so you can specify the return value of GetOEMCP() for that. Or use TFileStream or FileWrite() to write to a file manually, and then use TEncoding.GetBytes() to encode String values during your writing.

With ease. It depends on file I/O method to be used. Example of one:
procedure TForm11.FormCreate(Sender: TObject);
var
S: string;
begin
DefaultSystemCodePage := GetOEMCP();
S := 'Если используешь мой код, то ты должен мне почку';
AssignFile(Output, 'license.txt');
Rewrite(Output);
Write(Output, S); // converts to single byte here
CloseFile(Output);
Application.Terminate;
end;
NO bum rap about Pascal I/O, please :-P

Related

TIniFile.WriteBinaryStream creates exception

In Delphi 10.4, I try to save a valid TPicture base64-encoded to an INI file:
procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
var
LInput: TMemoryStream;
LOutput: TMemoryStream;
MyIni: TIniFile;
ThisFile: string;
begin
if FileSaveDialog1.Execute then
ThisFile := FileSaveDialog1.FileName
else EXIT;
LInput := TMemoryStream.Create;
LOutput := TMemoryStream.Create;
try
APicture.SaveToStream(LInput);
LInput.Position := 0;
TNetEncoding.Base64.Encode(LInput, LOutput);
LOutput.Position := 0;
MyIni := TIniFile.Create(ThisFile);
try
MyIni.WriteBinaryStream('Custom', 'IMG', LOutput); // Exception# 234
finally
MyIni.Free;
end;
finally
LInput.Free;
LOutput.Free;
end;
end;
WriteBinaryStream creates an exception:
ERROR_MORE_DATA 234 (0xEA) More data is available.
Why? What does this mean? How can this problem be solved?
EDIT: Taking into consideration what #Uwe Raabe and #Andreas Rejbrand said, this code (which does not use base64-encoding) now works:
procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
var
LInput: TMemoryStream;
MyIni: System.IniFiles.TMemIniFile;
ThisFile: string;
begin
if FileSaveDialog1.Execute then
ThisFile := FileSaveDialog1.FileName
else EXIT;
LInput := TMemoryStream.Create;
try
APicture.SaveToStream(LInput);
LInput.Position := 0;
MyIni := TMemIniFile.Create(ThisFile);
try
MyIni.WriteBinaryStream('Custom', 'IMG', LInput);
MyIni.UpdateFile;
finally
MyIni.Free;
end;
finally
LInput.Free;
end;
end;
I believe this is a limitation in the operating system's functions for handling INI files; the string is too long for it.
If you instead use the Delphi INI file implementation, TMemIniFile, it works just fine. Just don't forget to call MyIni.UpdateFile at the end.
Yes, this is indeed a limitation in the Windows API, as demonstrated by the following minimal example:
var
wini: TIniFile;
dini: TMemIniFile;
begin
wini := TIniFile.Create('C:\Users\Andreas Rejbrand\Desktop\winini.ini');
try
wini.WriteString('General', 'Text', StringOfChar('W', 10*1024*1024));
finally
wini.Free;
end;
dini := TMemIniFile.Create('C:\Users\Andreas Rejbrand\Desktop\pasini.ini');
try
dini.WriteString('General', 'Text', StringOfChar('D', 10*1024*1024));
dini.UpdateFile;
finally
dini.Free;
end;
(Recall that INI files were initially used to store small amounts of configuration data in the 16-bit Windows era.)
Also, Uwe Raabe is right: you should save the Base64 string as text.

passing a TStream using Data Snap in Delphi XE6

I am in need of passing a couple things using Data Snap in Delphi XE6 (TStream & TClientdataSet). Lets start with a TStream - maybe what I learn here, I can figure out the TClientDataSet.
Here is my attempt, but it throws an error:
Remote error: Access violaion at address 0040801C in module DSServer.exe
Client demo (DSClient.exe)
//RunReportObj is the real object I will be passing to the server method
Procedure TForm8.Button1Click(Sender: TObject);
var
RunReportObj: TRunReportObject;
S: TStream;
FS: TFileStream;
begin
RunReportObj:= TRunReportObject.Create;
RunReportObj.ID:= '10101';
RunReportObj.ReportName:= 'Test';
RunReportObj.ExportType:= 'PDF';
S:= TStream.Create;
try
S:= ClientModule1.ServerMethods1Client.GetReport(RunReportObj);
S.Seek(0,soFromBeginning);
FS:= TFileStream.Create(RunReportObj.ReportName + '.' + RunReportObj.ExportType, fmOpenWrite);;
try
FS.CopyFrom(S, S.Size);
finally
FS.Free;
end;
finally
S.Free;
end;
end;
ClientClassesUnit1.pas
function TServerMethods1Client.GetReport(RunReportObj: TRunReportObject): TStream;
begin
if FGetReportCommand = nil then
begin
FGetReportCommand := FDBXConnection.CreateCommand;
FGetReportCommand.CommandType := TDBXCommandTypes.DSServerMethod;
FGetReportCommand.Text := 'TServerMethods1.GetReport';
FGetReportCommand.Prepare;
end;
if not Assigned(RunReportObj) then
FGetReportCommand.Parameters[0].Value.SetNull
else
begin
FMarshal := TDBXClientCommand(FGetReportCommand.Parameters[0].ConnectionHandler).GetJSONMarshaler;
try
FGetReportCommand.Parameters[0].Value.SetJSONValue(FMarshal.Marshal(RunReportObj), True);
if FInstanceOwner then
RunReportObj.Free
finally
FreeAndNil(FMarshal)
end
end;
FGetReportCommand.ExecuteUpdate;
Result := FGetReportCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;
Server demo (DSServer.exe)
//not really doing anything with the RunReportObj yet,
// just trying to test whether or not I can pass a TStream back first
function TServerMethods1.GetReport(RunReportObj: TRunReportObject): TStream;
var
Stream: TMemoryStream;
Writer: TBinaryWriter;
Bytes: TBytes;
begin
result := TMemoryStream.Create;
try
Writer := TBinaryWriter.Create(result);
try
Writer.Write(TEncoding.UTF8.GetBytes('Hello World' + sLineBreak));
finally
Writer.Free;
end;
finally
Stream.Free;
end;
end;
I'm sure I did something foolish :)
You have to take care who is responsible for freeing objects sent with DataSnap. TServerMethods1.GetReport() should not free the Result, as it has to be sent to the client first. On the other side, the client should not free the TStream it gets from TServerMethods1Client.GetReport(), as long as FInstanceOwner is true (which it is by default).
The first condition is fulfilled more by accident, although as David pointed out, you are freeing the uninitialized local variable Stream.
Without being able to actually test this in the moment, the correct code for the client should look like:
Procedure TForm8.Button1Click(Sender: TObject);
var
RunReportObj: TRunReportObject;
S: TStream;
FS: TFileStream;
begin
RunReportObj:= TRunReportObject.Create;
RunReportObj.ID:= '10101';
RunReportObj.ReportName:= 'Test';
RunReportObj.ExportType:= 'PDF';
S:= ClientModule1.ServerMethods1Client.GetReport(RunReportObj);
S.Seek(0,soFromBeginning);
FS:= TFileStream.Create(RunReportObj.ReportName + '.' + RunReportObj.ExportType, fmOpenWrite);;
try
FS.CopyFrom(S, S.Size);
finally
FS.Free;
end;
end;
And for the server side:
function TServerMethods1.GetReport(RunReportObj: TRunReportObject): TStream;
var
Writer: TBinaryWriter;
Bytes: TBytes;
begin
result := TMemoryStream.Create;
Writer := TBinaryWriter.Create(result);
try
Writer.Write(TEncoding.UTF8.GetBytes('Hello World' + sLineBreak));
finally
Writer.Free;
end;
end;

Delphi 2007 & Updated Indy 10

I am sending alright files (doc, pdf, xls) with english filenames but when I am sending files with greek filenames I am getting on server side ????????? characters for filename & the error message Socket Error 10053, software caused connection abort. Is there a solution for this kind of problem.
Code:
procedure TForm1.LoadFileButtonClick(Sender: TObject);
begin
OpenDialog1.Filter := 'All Files (*.*)';
OpenDialog1.FilterIndex := 1;
if OpenDialog1.Execute then
begin
Edit1.Text := ExtractFileName(OpenDialog1.FileName);
Edit3.Text := OpenDialog1.FileName;
Fstream := TFileStream.Create(OpenDialog1.FileName, fmopenread);
Edit2.Text := inttostr(Fstream.Size);
Fstream.Position := 0;
FreeandNil(FStream);
//Fstream.Free;
end;
end;
procedure TForm1.SendFileButtonClick(Sender: TObject);
var
IncommingText: string;
begin
if (opendialog1.filename<>'') and (CheckBox1.Checked = True) then begin
IdTCPClient1.iohandler.writeln(edit1.text + '#' + edit2.text + ';' + edit3.text + ',');
Sleep(2000);
try
IdTCPClient1.IOHandler.largestream:=true;
Fstream := TFileStream.Create(OpenDialog1.FileName, fmopenread);
IdTCPClient1.IOHandler.Write(Fstream, 0 ,true);
finally
Fstream.Position := 0;
FreeandNil(FStream);
//Fstream.Free;
memo1.Lines.Add('File Sent');
IncommingText := IdTCPClient1.iohandler.readln;
if IncommingText = 'DONE!' then begin
Memo1.Lines.Add('File ' +Edit1.Text +' ' +Edit2.Text +' was received successfully by the Server');
//APPLICATION.ProcessMessages;
end else begin Memo1.Lines.Add('File ' +Edit1.Text +' was not received by the Server'); end;
end; //try - finally
end else begin
showmessage('Please choose a file Or Try to connect to the Server');
end;
end;
Indy's default text encoding is ASCII (because the majority of Internet protocols are still largely ASCII based, unless they define extra extensions to support Unicode). That is why you are getting ? for non-ASCII characters. To send non-ASCII characters, you need to tell Indy which text encoding to use that is compatible with the characters you are exchanging. UTF-8 is usually the best choice for that. There are three ways you can do that:
set the global GIdDefaultTextEncoding variable in the IdGlobal unit. It is set to encASCII by default, you can set it to encUTF8 instead:
procedure TForm1.FormCreate(Sender: TObject);
begin
GIdDefaultTextEncoding := encUTF8;
end;
set the TIdIOHandler.DefStringEncoding property to TIdTextEncoding.UTF8 (or IndyTextEncoding_UTF8 if you are using Indy 10.6+):
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
begin
IdTCPClient1.IOHandler.DefStringEncoding := TIdTextEncoding.UTF8;
// or:
// IdTCPClient1.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;
end;
pass TIdTextEncoding.UTF8 (or IndyTextEncoding_UTF8) directly to the AByteEncoding parameter of WriteLn():
IdTCPClient1.IOHandler.WriteLn(..., TIdTextEncoding.UTF8);
// or:
// IdTCPClient1.IOHandler.WriteLn(..., IndyTextEncoding_UTF8);
Keep in mind that you are using an Ansi version of Delphi, where string maps to AnsiString, and thus Indy has to perform an additional Ansi-to-Unicode conversion of AnsiString data before it can then apply the specified text encoding to produce the bytes it transmits. Typically, Indy uses the OS's default Ansi encoding to handle that initial conversion (so if your AnsiString data is Greek encoded, and your OS is set to Greek, you will be fine), however you can use the TIdIOHandler.DefAnsiEncoding property, or the ASrcEncoding parameter of WriteLn(), if you need to specify that your AnsiString data is using a different encoding.
As for your socket error, without seeing a call stack leading up to the error, or at least which line of your code is raising it, that is difficult to troubleshoot. My guess is that it is related to you calling ReadLn() inside of the finally block regardless of whether WriteLn() or Write() actually succeeded. That code needs to be moved out of the finally block, it does not belong there.
Try something more like this instead:
procedure TForm1.LoadFileButtonClick(Sender: TObject);
begin
OpenDialog1.Filter := 'All Files (*.*)';
OpenDialog1.FilterIndex := 1;
if OpenDialog1.Execute then
begin
Edit1.Text := ExtractFileName(OpenDialog1.FileName);
Edit3.Text := OpenDialog1.FileName;
// Indy has its own FileSizeByName() function...
Edit2.Text := IntToStr(FileSizeByName(OpenDialog1.FileName));
end;
end;
procedure TForm1.SendFileButtonClick(Sender: TObject);
var
IncommingText: string;
Strm: TFileStream;
begin
if not CheckBox1.Checked then
begin
ShowMessage('Please connect to the Server');
Exit;
end;
if OpenDialog1.FileName = '' then
begin
ShowMessage('Please choose a file');
Exit;
end;
Strm := TFileStream.Create(OpenDialog1.FileName, fmOpenRead);
try
IdTCPClient1.IOHandler.WriteLn(Edit1.Text + '#' + Edit2.Text + ';' + Edit3.Text + ',', TIdTextEncoding.UTF8);
IdTCPClient1.IOHandler.LargeStream := True;
IdTCPClient1.IOHandler.Write(Strm, 0 , True);
finally
Strm.Free;
end;
Memo1.Lines.Add('File Sent');
IncommingText := IdTCPClient1.IOHandler.ReadLn;
if IncommingText = 'DONE!' then begin
Memo1.Lines.Add('File ' + Edit1.Text + ' ' + Edit2.Text + ' was received successfully by the Server');
//APPLICATION.ProcessMessages;
end else
begin
Memo1.Lines.Add('File ' + Edit1.Text + ' was not received by the Server');
end;
end;
Lastly, just an FYI, you are setting the AWriteByteCount parameter of Write() to True, so it is going to transmit the stream size (as an Int64 because of LargeStream=True) before then sending the TStream data, so putting the file size in the WriteLn() data is redundant.

Why is my code causing a I/O 104 error?

This program raises an I/O 104 error on EoF when first entering the while loop.
The purpose of the program is to look up if a username is already taken. The existing usernames are stored in a text file.
procedure TForm1.btnRegisterClick(Sender: TObject);
begin
sCUser := edtUserName.Text;
AssignFile(tNames, 'Names.txt');
begin
try
Reset(tNames);
except
ShowMessage('File not found');
Exit;
end;
end;
rewrite(tNames);
while not EoF(tNames) do // I get a I/O 104 Error here `
begin
Readln(tNames, sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine, 1, iPosComme - 1);
Delete(sLine, 1, iPosComme - 1);
if sCUser = sUser then begin
ShowMessage('Username taken');
end
else
begin
rewrite(tNames);
Writeln(tNames, sCUser + ',' + '0');
CloseFile(tNames);
end;
end;
end;
Remove the call to Rewrite()before Eof(). Even if you were not getting an IO error, your code would still fail because Rewrite() closes the file you opened with Reset() and then it creates a new bank file, so Eof() would always be True.
Update: error 104 is file not open for input, which means Reset() is not opening the file but is not raising an exception (which sounds like an RTL bug if Eof() is raising an exception, indicating that {I+} is active).
In any case, using AssignFile() and related routines is the old way to do file I/O. You should use newer techniques, like FileOpen() with FileRead(), TFileStream with TStreamReader, TStringList, etc...
Update: your loop logic is wrong. You are comparing only the first line. If it does not match the user, you are wiping out the file, writing the user to a new file, closing the file, and then continuing the loop. EoF() will then fail at that point. You need to rewrite your loop to the following:
procedure TForm1.btnRegisterClick(Sender: TObject
var
SCUser, sUser: String;
tNames: TextFile;
iPosComme: Integer;
Found: Boolean;
begin
sCUser := edtUserName.Text;
AssignFile(tNames,'Names.txt');
try
Reset(tNames);
except
ShowMessage('File not found');
Exit;
end;
try
Found := False;
while not EoF(tNames) do
begin
Readln(tNames,sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine ,1,iPosComme -1);
if sCUser = sUser then
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
if not Found then
Writeln(tNames,sCUser + ',0');
finally
CloseFile(tNames);
end;
end;
For the sake of completeness, this Version works for me, but it is hard to guess what the code is intended to do. Especially the while loop seems a bit displaced, since the file will contain exactly one line after the rewrite-case has ben hit once.
program wtf;
{$APPTYPE CONSOLE}
{$I+}
uses
SysUtils;
procedure Sample( sCUser : string);
var sUser, sLine : string;
iPosComme : Integer;
tnames : textfile;
begin
AssignFile(tNames,'Names.txt');
try
Reset(tNames);
except
Writeln('File not found');
Exit;
end;
while not EoF(tNames) do
begin
Readln(tNames,sLine);
iPosComme := Pos(',', sLine);
sUser := Copy(sLine ,1,iPosComme -1);
Delete( sLine,1, iPosComme -1);
if sCuser = sUser then begin
Writeln('Username taken') ;
end
else begin
Rewrite(tNames);
Writeln(tNames,sCUser + ',' + '0');
CloseFile(tNames);
Break; // file has been overwritten and closed
end;
end;
end;
begin
try
Sample('foobar');
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
end.
I wrote a version of this method that uses the newer TStreamReader and TStreamWriter classes.
This won't work with Delphi 7 of course, it's just to show how this could be done in newer versions of Delphi.
The code was heavily inspired by Remys answer.
procedure TForm1.btnRegisterClick(Sender: TObject);
var
Stream: TStream;
Reader: TStreamReader;
Writer: TStreamWriter;
Columns: TStringList;
UserName: string;
Found: Boolean;
FileName: string;
Encoding: TEncoding;
begin
FileName := ExpandFileName('Names.txt'); // An absolute path would be even better
UserName := edtUsername.Text;
Found := False;
Encoding := TEncoding.Default; // or another encoding, e.g. TEncoding.Unicode for Unicode
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Reader := TStreamReader.Create(Stream, Encoding);
try
Columns := TStringList.Create;
try
Columns.Delimiter := ',';
Columns.StrictDelimiter := True; // or False, depending on the file format
while not Reader.EndOfStream do
begin
Columns.DelimitedText := Reader.ReadLine;
if Columns.Count > 0 then
begin
if AnsiSameStr(Columns[0], UserName) then // or AnsiSameText if UserName is not case-sensitive
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
end;
finally
Columns.Free;
end;
finally
Reader.Free;
end;
finally
Stream.Free;
end;
if not Found then
begin
Writer := TStreamWriter.Create(FileName, True, Encoding);
try
// Warning: This will cause problems when the file does not end with a new line
Writer.WriteLine(UserName + ',0');
finally
Writer.Free;
end;
end;
end;
If performance and memory usage are not a concern:
procedure TForm1.btnRegisterClick(Sender: TObject);
var
Rows: TStringList;
Columns: TStringList;
UserName: string;
Found: Boolean;
FileName: string;
Encoding: TEncoding;
Row: string;
begin
FileName := ExpandFileName('Names.txt'); // An absolute path would be even better
UserName := edtUsername.Text;
Found := False;
Encoding := TEncoding.Default; // or another encoding, e.g. TEncoding.Unicode for Unicode
Rows := TStringList.Create;
try
Rows.LoadFromFile(FileName, Encoding);
Columns := TStringList.Create;
try
Columns.Delimiter := ',';
Columns.StrictDelimiter := True; // or False, depending on the file format
for Row in Rows do
begin
Columns.DelimitedText := Row;
if Columns.Count > 0 then
begin
if AnsiSameStr(Columns[0], UserName) then // or AnsiSameText if UserName is not case-sensitive
begin
ShowMessage('Username taken') ;
Found := True;
Break;
end;
end;
end;
finally
Columns.Free;
end;
if not Found then
begin
Rows.Add(UserName + ',0');
Rows.SaveToFile(FileName, Encoding);
end;
finally
Rows.Free;
end;
end;
This solution can be adapted to Delphi 7 by removing the Encoding variable.
If it's part of a bigger database it should be stored in a real database management system rather than a text file.

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