Reading HTML content from Clipboard in Delphi - delphi

I have a webpage which has various tables on it. These tables are Javascript components, not just pure HTML tables. I need to process the text of this webpage (somewhat similar to screen scraping) with a Delphi program (Delphi 10.3).
I do a Ctrl-A/Ctrl-C to select all the webpage and copy everything to the clipboard. If I paste this into a TMemo component in my program, I am only getting text outside the table. If I paste into MS Word, I can see all the content, including the text inside the table.
I can paste this properly into TAdvRichEditor (3rd party), but it takes forever, and I often run out of memory. This leads me to believe that I need to directly read the clipboard with an HTML clipboard format.
I set up a clipboard HTML format. When I inspect the clipboard contents, I get what looks like all Kanji characters.
How do I read the contents of the clipboard when the contents are HTML?
In a perfect world, I would like ONLY the text, not the HTML itself, but I can strip that out later. Here is what I am doing now...
On initialization.. (where CF_HTML is a global variable)
CF_HTML := RegisterClipboardFormat('HTML Format');
then my routine is...
function TMain.ClipboardAsHTML: String;
var
Data: THandle;
Ptr: PChar;
begin
Result := '';
with Clipboard do
begin
Open;
try
Data := GetAsHandle(CF_HTML);
if Data <> 0 then
begin
Ptr := PChar(GlobalLock(Data));
if Ptr <> nil then
try
Result := Ptr;
finally
GlobalUnlock(Data);
end;
end;
finally
Close;
end;
end;
end;
** ADDITIONAL INFO - When I copy from the webpage... I can then inspect the contents of the Clipboard buffer using a free tool called InsideClipBoard. It shows that the clipboard contains 1 entry, with 5 formats: CT_TEXT, CF_OEMTEXT, CF_UNICODETEXT, CF_LOCALE, and 'HTML Format' (with Format ID of 49409). Only 'HTML Format' contains what I am looking for.... and that is what I am trying to access with the code that I have shown.

The HTML format is documented here. It is placed on the clipboard as UTF-8 encoded text, and you can extract it like this.
{$APPTYPE CONSOLE}
uses
System.SysUtils,
Winapi.Windows,
Vcl.Clipbrd;
procedure Main;
var
CF_HTML: Word;
Data: THandle;
Ptr: Pointer;
Error: DWORD;
Size: NativeUInt;
utf8: UTF8String;
Html: string;
begin
CF_HTML := RegisterClipboardFormat('HTML Format');
Clipboard.Open;
try
Data := Clipboard.GetAsHandle(CF_HTML);
if Data=0 then begin
Writeln('HTML data not found on clipboard');
Exit;
end;
Ptr := GlobalLock(Data);
if not Assigned(Ptr) then begin
Error := GetLastError;
Writeln('GlobalLock failed: ' + SysErrorMessage(Error));
Exit;
end;
try
Size := GlobalSize(Data);
if Size=0 then begin
Error := GetLastError;
Writeln('GlobalSize failed: ' + SysErrorMessage(Error));
Exit;
end;
SetString(utf8, PAnsiChar(Ptr), Size - 1);
Html := string(utf8);
Writeln(Html);
finally
GlobalUnlock(Data);
end;
finally
Clipboard.Close;
end;
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.

Related

Delphi VCL TMediaPlayer: file path/name length limit

Using Delphi 10.4 Community Edition, VCL, Windows 10 64bit, although the compiled .exe application is 32bit.
The VCL's TMediaPlayer seems to have a file path/name length limit of 128 characters. Is this really an internal limitation? Is there any way to access longer file paths/names?
I was coding a small soundPad player by using the TMediaPlayer component.
The installer I am using installs the .exe program in the user's home directory, and at the same time a few sample audio files in the program's root directory.
In this case, the path to the audio file may be quite long. For example:
C:\Users\user\AppData\Local\Programs\MySoundPlayer\ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav
When trying to play such a file, TMediaPlayer will give an error message:
Exception class name = 'EMCIDeviceError'
Exception message = 'Invalid filename. Make sure the filename has 8 characters, a period, and an extension.'
I tried different lengths in the file name, and it looks like 127 is the maximum length.
So, the VCL TMediaPlayer component does not recognize file paths / names longer than 127 characters?
I tried the same code with a Delphi FMX app, and FMX's TMediaPlayer worked ok. It seems that the maximum file path and name length of the FMX TMediaPlayer is 259, which is quite sufficient.
The length 259 seem to be the limit of the File Explorer overall...
It is said that the VCL's TMediaPlayer component is starting to become obsolete, and is only involved in backward compatibility reasons. But what can replace it in the future?
So, I guess I have to move on to FMX and learn its secrets. Is VCL a receding component system?
procedure TForm1.PlayButtonClick(Sender: TObject);
var
pathstring, playerfilename, playstring : string;
begin
try
pathstring := ExtractFilePath(Application.ExeName);
playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
playstring := pathstring + playerfilename;
MediaPlayer1.FileName := playstring;
MediaPlayer1.Open;
MediaPlayer1.Play;
except
on E : Exception do
begin
ShowMessage('Exception class name = ' + E.ClassName);
ShowMessage('Exception message = ' + E.Message);
end;
end;
end;
Per this answer to mciSendString() won't play an audio file if path is too long:
Here, mmioOpen is called with MMIO_PARSE flag to convert file path to fully qualified file path. According to MSDN, this has a limitation:
The buffer must be large enough to hold at least 128 characters.
That is, buffer is always assumed to be 128 bytes long. For long filenames, the buffer turns out to be insufficient and mmioOpen returns error, causing mciSendCommand to think that sound file is missing and return MCIERR_FILENAME_REQUIRED.
The Invalid filename error message you are seeing is the system text for the MCIERR_FILENAME_REQUIRED error code.
The VCL's TMediaPlayer is based on MCI and internally uses mciSendCommand(), which is just the binary version of mciSendString(). They both suffer from the same problem.
The preferred fix is to either use shorter paths, or use a more modern audio API.
However, since mmioInstallIOProc() can be used to let TMediaPlayer play media files from memory instead of files, I think a similar solution could be used to play files with long file paths, since you could take over the responsibility of opening/reading/seeking a file, bypassing the path limitation of the troublesome mmioOpen(). Just replace the TResourceStream in that code with a TFileStream, and update the MMIOM_READ and MMIOM_SEEK handlers accordingly to read/seek that TFileStream.
For example (untested, might need some tweaking):
uses
Winapi.MMSystem;
var
ccRES: FOURCC;
playstring: string;
function MAKEFOURCC(ch0, ch1, ch2, ch3: BYTE): FOURCC;
begin
Result := DWORD(ch0) or (DWORD(ch1) shl 8) or (DWORD(ch2) shl 16) or (DWORD(ch3) shl 24);
end;
function MyLongFileIOProc(lpMMIOInfo: PMMIOInfo; uMessage: UINT; lParam1, lParam2: LPARAM): LRESULT; stdcall;
var
FStrm: TFileStream;
NumRead: Integer;
function GetFileStream: TFileStream;
begin
Move(lpMMIOInfo.adwInfo, Result, SizeOf(TFileStream));
end;
procedure SetFileStream(Stream: TFileStream);
begin
Move(Stream, lpMMIOInfo.adwInfo, SizeOf(TFileStream));
end;
begin
if uMessage = MMIOM_OPEN then
begin
try
FStrm := TFileStream.Create(playstring, fmOpenRead or fmShareDenyWrite);
except
SetFileStream(nil);
Exit(MMIOM_CANNOTOPEN);
end;
SetFileStream(FStrm);
lpMMIOInfo.lDiskOffset := 0;
end else
begin
FStrm := GetFileStream;
case uMessage of
MMIOM_CLOSE: begin
SetFileStream(nil);
FStrm.Free;
end;
MMIOM_READ: begin
NumRead := FStrm.Read(Pointer(lParam1)^, lParam2);
Inc(lpMMIOInfo.lDiskOffset, NumRead);
Exit(NumRead);
end;
MMIOM_SEEK: begin
FStrm.Seek(Int64(lParam1), TSeekOrigin(lParam2));
lpMMIOInfo.lDiskOffset := FStrm.Position;
Exit(lpMMIOInfo.lDiskOffset);
end;
end;
Exit(MMSYSERR_NOERROR);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ccRES := MAKEFOURCC(Ord('L'), Ord('F'), Ord('N'), Ord(' '));
mmioInstallIOProc(ccRES, TFNMMIOProc(MyLongFileIOProc), MMIO_INSTALLPROC);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
mmioInstallIOProc(ccRES, nil, MMIO_REMOVEPROC);
end;
procedure TForm1.PlayButtonClick(Sender: TObject);
var
pathstring, playerfilename : string;
begin
try
pathstring := ExtractFilePath(Application.ExeName);
playerfilename := 'ThisIsMySoundWithAVeryLongFileNameThereIsSomeCopyrightInfoAndSomeOther.wav';
playstring := pathstring + playerfilename;
MediaPlayer1.DeviceType := dtWaveAudio;
MediaPlayer1.FileName := 'playstring.LFN+';
MediaPlayer1.Open;
MediaPlayer1.Play;
except
on E : Exception do
begin
ShowMessage('Exception class name = ' + E.ClassName);
ShowMessage('Exception message = ' + E.Message);
end;
end;
end;

How to correctly download csv without 'save as' dialog' using TWebbrowser and Delphi?

I subscribe to a secure https web page containing a button that downloads some data as csv. I am trying to automate the download without the 'save as' dialog appearing but always seem to get an empty file downloaded. I suspect it has something to do with file type I'm using with IdHttp as most of my code works correctly.
Please can anyone help with my use of IdHttp or see where else I am going wrong?
The download button on the site calls some javascript to perform the download as follows
<a class="dlCSV" href="javascript:void(0);" onclick="dl_module.DownloadCsv();return false;">Download in CSV format…</a>
In Delphi I use a TWeb browser to log on securely and navigate to the page.
Clicking the download button in the TwebBrowser by hand shows the 'save as' dialog and then correctly downloads the csv data, defaulting to the filename 'data.csv'.
Automating clicking the button using execScript (below) also works, again showing the 'save as' dialog and correctly downloading the data with the same default filename.
procedure TForm1.BtnClickDownloadbuttonClick(Sender: TObject);
var TheDocument : IHTMLDocument2; // current HTML document
HTMLWindow: IHTMLWindow2; // parent window of current HTML document
begin
TheDocument := WebBrowser1.Document as IHTMLDocument2; // Get reference to current document
if not Assigned(TheDocument) then
Exit;
HTMLWindow := TheDocument.parentWindow; // Get parent window of current document
if Assigned(HTMLWindow) then
try
HTMLWindow.execScript('dl_module.DownloadCsv()', 'JavaScript'); // execute JS function to do download
except
on E : Exception do
begin
showmessage ('Exception class name = '+E.ClassName+ slinebreak
+ 'Exception message = '+E.Message);
end //on E
end;
end;
Then I added TLama's code from here How do I keep an embedded browser from prompting where to save a downloaded file? to use IDownloadManager to intercept the download and prevent the 'save as' dialog. This is where it seems to go wrong as I then get an empty file downloaded, and not with the name data.csv.
My code for function TWebBrowser.Download, TWebBrowser.InvokeEvent, function TWebBrowser.QueryService and TForm1.FormCreate are identical to that provided by TLama in the link above.
My procedure TForm1.Button1Click is the same except that I changed the download function being called to the one on my page by changing the line
HTMLWindow.execScript('SRT_stocFund.Export()', 'JavaScript');
to
HTMLWindow.execScript('dl_module.DownloadCsv()', 'JavaScript');
and my procedure TForm1.BeforeFileDownload is identical except that because I'm on a secure site I added the variable
var
LHandler: TIdSSLIOHandlerSocketOpenSSL; //<< on a https site
and after creating the Filestream I added the lines
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
IdHTTP.IOHandler := LHandler;
The issue seems to be in procedure TForm1.BeforeFileDownload where I note that the value of FileSource is
https://www.the_web_site_name/Ashx/GenericCSV.ashx.
There is a short delay while IdHTTP.Get(FileSource, FileStream); executes and then a file is created on my hard disc but called 'GenericCSV.ashx' (not data.csv) and the file is zero bytes long and completely empty.
Any ideas why its not downloading the file called data.csv (Do I somehow have to execute GenericCSV.ashx as well? if so how?)
For info here is my version of procedure TForm1.BeforeFileDownload
procedure TForm1.BeforeFileDownload(Sender: TObject; const FileSource: WideString; var Allowed: Boolean);
var
IdHTTP: TIdHTTP;
FileTarget: string;
FileStream: TMemoryStream;
LHandler: TIdSSLIOHandlerSocketOpenSSL; // added as its a https site
begin
FileSourceEdit.Text := FileSource;
Allowed := ShowDialogCheckBox.Checked;
if not Allowed then
try
IdHTTP := TIdHTTP.Create(nil);
try
FileStream := TMemoryStream.Create;
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); //<<< added as its a https site
IdHTTP.IOHandler := LHandler; //<<< added as its a https site
try
IdHTTP.HandleRedirects := True;
IdHTTP.Get(FileSource, FileStream);
FileTarget := IdHTTP.URL.Document;
if FileTarget = '' then
FileTarget := 'File';
FileTarget := ExtractFilePath(ParamStr(0)) + FileTarget;
FileStream.SaveToFile(FileTarget);
finally
FileStream.Free;
end;
finally
IdHTTP.Free;
end;
ShowMessage('Downloading finished! File has been saved as:' + sLineBreak +
FileTarget);
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
After you login, you can use this code to retrieve cookies from TWebBrowser
procedure GetHttpOnlyCookie(const AUrl: string; var ACookies: string);
const
INTERNET_COOKIE_HTTPONLY = 8192;
var
i: Integer;
hModule: THandle;
InternetGetCookieEx: function(lpszUrl, lpszCookieName, lpszCookieData
: PAnsiChar; var lpdwSize: DWORD; dwFlags: DWORD; lpReserved: pointer)
: BOOL; stdCall;
CookieSize: DWORD;
CookieData: PAnsiChar;
begin
LoadLibrary('wininet.dll');
hModule := GetModuleHandle('wininet.dll');
if (hModule <> 0) then
begin
#InternetGetCookieEx := GetProcAddress(hModule, 'InternetGetCookieExA');
if (#InternetGetCookieEx <> nil) then
begin
CookieSize := 1024;
Cookiedata := AllocMem(CookieSize);
try
if InternetGetCookieEx(PAnsiChar(AUrl), nil, Cookiedata, CookieSize, INTERNET_COOKIE_HTTPONLY, nil) then
begin
ACookies:=CookieData;
end;
finally
FreeMem(Cookiedata);
end;
end;
end;
end;
Then you just parse your cookies and add them (you have to create CookieManager in IdHTTP first)
IdHTTP1.CookieManager.AddServerCookie();
Then you start your download and it should work if you passed all parameters correctly (unfortunately, it is not possible to find out what your site requires).
Thank you smooty86 but I think its time I gave up trying to doing it this way and simply parse the page I can see.
I don't mind trying to understand code and adapting it to my needs but its so much harder trying to follow hints and suggestions when I'm working in the dark and especially don't know what parameters are needed everywhere. (I'm not daft, I've been programming for nearly 30 years and have spent over 4 years developing this particular data processing application but rarely touch web stuff)
However, the progress so far is...
Running your GetHttpOnlyCookie code after a successful login using automated filling in of the fields and clicking the login button returned an empty string so I used this code instead that at least seemed to return something that looked a little similar to your cookie string, ie seveveral strings separated by semicolons, most being name=value. (IdCookieManager1 is connected to IdHttp)
CookieList := Tstringlist.Create ;
try
CookieList.Delimiter := ';' ;
document := WebBrowser1.Document as IHTMLDocument2;
CookieList.DelimitedText := document.cookie;
for i := 0 to CookieList.Count-1 do
IdCookieManager1.AddCookie(CookieList[i],LOGIN_URL)
finally
CookieList.Free;
end;
Then in my original procedure BeforeFileDownload I try to log IdHttp into the site as well using code I adapted from here Log in to website from Delphi and the the cookies held in the cookie manager.
Displaying the string returned showed lots of HTML that appeared to represent the oringinal log in page and not the page you see after log in
procedure TFrmInportGrades.BeforeFileDownload(Sender: TObject; const FileSource: WideString; var Allowed: Boolean);
var
FileTarget: string;
FileStream: TMemoryStream;
request : Tstringlist;
s : string;
begin
FileSourceEdit.Text := FileSource;
Allowed := ShowDialogCheckBox.Checked;
if not Allowed then
begin
try
FileStream := TMemoryStream.Create;
IdHTTP.CookieManager := IdCookieManager1;
s := LogInIdHttp; //<<<< log in the IdHttp
showmessage(s); //<<<< debug
IdHTTP.Get(FileSource, FileStream);
FileTarget := IdHTTP.URL.Document;
if FileTarget = '' then
FileTarget := 'File';
FileTarget := ExtractFilePath(ParamStr(0)) + FileTarget;
FileStream.SaveToFile(FileTarget);
finally
FileStream.Free;
end;
ShowMessage('Downloading finished! File has been saved as:' + sLineBreak +
FileTarget);
end;
end;
The login code I used is below but I don't really know what I am doing here or what needs to be put into the Request.Add() parameters. I used 'Inspect element' from firefox to get the name of the user and password boxes and put the correct users name and password after the '=' sign in lines {3} and {4}. In lines {2},{6} and {7} I put the url of the log in site. I've no idea what lines {1}, {2}, {5} do or even if they are correct or necessary
function TFrmInportGrades.LogInIdHttp: string;
var
Request: TStringList;
Response: TMemoryStream;
LHandler: TIdSSLIOHandlerSocketOpenSSL; // added as its a https site
begin
Result := '';
try
Response := TMemoryStream.Create;
try
Request := TStringList.Create;
try
{1} Request.Add('op=login');
{2} Request.Add('redirect=https://www.thewebsite.com/Login.aspx' );
{3} Request.Add('ctl00$ctl00$Body$Body$loginManager$ctl00$loginEmailInput=usernme');
{4} Request.Add('ctl00$ctl00$Body$Body$loginManager$ctl01$passwordInput=password'});
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(nil); //<<< added as its a https site
IdHTTP.IOHandler := LHandler; //<<< added as its a https site
IdHTTP.AllowCookies := True;
IdHTTP.HandleRedirects := True;
{5} IdHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
{6} IdHTTP.Post('https://www.thewebsite.com/Login.aspx', Request, Response);
{7} Result := IdHTTP.Get('https://www.thewebsite.com/Login.aspx');
finally
Request.Free;
end;
finally
Response.Free;
end;
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
The net result of all this is that I don't get a file created at all now, not even a zero byte one. This all seems very overcomplicated simply to avoid or automate the 'Save As' dialog and is requiring lots of code that I won't be able to maintan afterwards. Unless somebody has a simpler solution I'll just parse what I can see (BTW I tried TEmbeddedWebBrowser but there is so little documentation for it I couldn't see how to make it download correctly. Might try again later.) Thank you for trying to help!

stuck with streaming file to string

okay, so I (VERY) recently started playing with lazaruz/free pascal, and I'm a little stuck with reading files with TMemoryStream and it's streaming kin.
I'm trying to write a simple base64 encoder, that can encode strings of text, or files (like images and WAVs) to then be used in html and javascript.
The following code compiles great but I get EReadError Illegal stream image when trying to load a file. I'll include the working string only procedure for reference:
procedure TForm1.TextStringChange(Sender: TObject);
begin
Memo1.Lines.Text := EncodeStringBase64(TextString.Text);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.Text := '';
Form1.BorderIcons := [biSystemMenu,biMinimize];
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
filename := OpenDialog1.Filename;
stream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
stream.LoadFromFile(filename);
stream.Seek(0, soFromBeginning);
ObjectBinaryToText(stream, StrStream);
StrStream.Seek(0, soFromBeginning);
Memo1.Lines.Text := EncodeStringBase64(StrStream.DataString);
finally
StrStream.Free;
end;
finally
stream.Free;
end;
end;
end;
Can anyone help me out?
You get the "illegal stream image" exception because the file you're loading probably isn't a binary DFM file. That's what ObjectBinaryToText is meant to process. It's not for arbitrary data. So get rid of that command.
You can skip the TMemoryStream, too. TStringStream already has a LoadFromFile method, so you can call it directly instead of involving another buffer.
StrStream.LoadFromFile(filename);
But a string isn't really the right data structure to store your file in prior to base64-encoding it. The input to base64 encoding is binary data; the output is text. Using a text data structure as an intermediate format means you may introduce errors into your data because of difficulties in encoding certain data as valid characters. The right interface for your encoding function is this:
function Base64Encode(Data: TStream): string;
You don't need to load the entire file into memory prior to encoding it. Just open the file with a TFileStream and pass it to your encoding function. Read a few bytes from it at a time with the stream's Read method, encode them as base64, and append them to the result string. (If you find that you need them, you can use an intermediate TStringBuilder for collecting the result, and you can add different buffering around the file reads. Don't worry about those right away, though; get your program working correctly first.)
Use it something like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
filename: string;
stream: TStream;
begin
if OpenDialog1.Execute then begin
filename := OpenDialog1.Filename;
stream := TFileStream.Create(filename, fmOpenRead);
try
Memo1.Lines.Text := Base64Encode(stream);
finally
stream.Free;
end;
end;
end;
I never heard before about ObjectBinaryToText(), but looks like funky one. Also, what is EncodeStringBase64() function?
At first place, you shouldn't convert binary stream to text to encode it, instead you should directly B64 encode binary data. B64 algorithm is intended to work on array of bytes.
Since Delphi 6, there is EncdDecd.pas unit, which implements B64 encoding methods. I'm not sure if Lazarus/FPC have this, but if they do, your code to B64 encode file should look like this (add EncdDecd to uses list):
procedure TForm1.Button1Click(Sender: TObject);
var
instream : TFileStream;
outstream: TStringStream;
begin
if OpenDialog1.Execute then
begin
instream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead or fmShareDenyNone);
try
outstream := TStringStream.Create;
try
EncodeStream(instream, outstream);
Memo1.Lines.Text := outstream.DataString;
finally
outstream.Free;
end;
finally
instream.Free;
end;
end;
end;

How do I read a UTF8 encoded INI file?

I have an INI file in UTF-8 format.
I am using Delphi 2010 to read the INI file and populate a TStringGrid with the values in the INI file.
var
ctr : Integer;
AppIni : TIniFile;
begin
AppIni := TIniFile.Create(ExtractFilePath(Application.ExeName) + 'test.ini');
for ctr := 1 to StringGrid1.RowCount do begin
StringGrid1.Cells[0,ctr] := AppIni.ReadString('Column1','Row'+IntToStr(ctr),'');
StringGrid1.Cells[1,ctr] := AppIni.ReadString('Column2','Row'+IntToStr(ctr),'');
end;
AppIni.Free;
The problem is that the unicode characters are appearing in the TStringGrid displaying 2 characters, rather than the 1 unicode character.
How do I resolve this?
The TIniFile class is a wrapper of the Windows API for INI files. This does support Unicode INI files, but only if those files are encoded as UTF-16. Michael Kaplan has more details here: Unicode INI function; Unicode INI file?
So, you are out of luck with TIniFile. Instead you could use TMemIniFile which allows you to specify an encoding in its constructor. The TMemIniFile class is a native Delphi implementation of INI file support. There are various pros and cons between the two classes. In your situation, only TMemIniFile can serve your needs, so it's looking like its pros are going to outweigh its cons.
Uses IniFiles;
const
SZ_APP_NAME = 'demo_test';
Procedure TForm1.GetSettings;
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
begin
try
_SettingsPath := GetHomePath + PathDelim + SZ_APP_NAME + PathDelim;
if ForceDirectories(_SettingsPath) then
begin
_MemIniU := TMemIniFile.Create(ChangeFileExt(_SettingsPath,
'Settings.ini'), TEncoding.UTF8);
try
if _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowLeft', -1) = -1 then
Form1.Position := poScreenCenter
else
begin
Form1.Left := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowLeft', 10);
Form1.Top := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowTop', 10);
Form1.Width := _MemIniU.ReadInteger(SZ_APP_NAME, 'WindowWidth', 594);
Form1.Height := _MemIniU.ReadInteger(SZ_APP_NAME,
'WindowHeight', 342);
end;
Edit1.Text := _MemIniU.ReadString(SZ_APP_NAME, 'UnicodeText', 'ąčę');
finally
_MemIniU.Free;
end;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError,
[TMsgDlgBtn.mbOK], 0);
end;
end;
Procedure TForm1.SaveSettings;
var
_MemIniU: TMemIniFile;
_SettingsPath: string;
begin
try
_SettingsPath := GetHomePath + PathDelim + SZ_APP_NAME + PathDelim;
_MemIniU := TMemIniFile.Create(ChangeFileExt(_SettingsPath, 'Settings.ini'),
TEncoding.UTF8);
try
if Form1.WindowState <> TWindowState.wsMaximized then
begin
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowLeft', Form1.Left);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowTop', Form1.Top);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowWidth', Form1.Width);
_MemIniU.WriteInteger(SZ_APP_NAME, 'WindowHeight', Form1.Height);
_MemIniU.WriteString(SZ_APP_NAME, 'UnicodeText', Edit1.Text);
end;
_MemIniU.UpdateFile;
finally
_MemIniU.Free;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError,
[TMsgDlgBtn.mbOK], 0);
end;
end;
In an application were I was using TIniFile i had the need to start storing Unicode chars.
To do this i simply changed the variable type from TIniFile to TMemIniFile and in the constructor, after the filename i added the second parameter TEncoding.UTF8.
Then before freeing the object i called UpdateFile. If Ini File is opened for reading, call to UpdateFile is not needed.
// ANSI version
var myIniFile: TIniFile;
begin
myIniFIle := TIniFile.Create('c:\Temp\MyFile.ini');
myIniFile.WriteString(par1,par2,par3);
// [...]
myIniFile.Free;
end
// Unicode version
//1) "Mem" added here
var myIniFile: TMemIniFile;
begin
// 2) Enconding added
myIniFIle := TIniFile.Create('c:\Temp\MyFile.ini', TEncoding.UTF8);
myIniFile.WriteString(par1,par2,par3);
// [...]
// 3) call to UpdateFile to save to disc the changes
myIniFile.UpdateFile;
myIniFile.Free;
end
The good news is that UpdateFile causes the ini file to be saved with the proper encoding, this means that if a ini file encoded in ANSI already exists it is overwriten so it becomes UTF-8, so the transaction between ANSI and UTF-8 is smooth and not painful at all.

Convert a URL to a valid win32 filename in Delphi

As part of a Delphi application I'm wanting to convert a URL to a filename. What I'm doing is caching QR-Codes. The QR-Code represents a URL, and I want to store the QR-Code image with a filename that represents that URL. For example http://myurl.com/bla might become http___myrul.com_bla.png - here I've just replaced any non alpha-numeric characters with underscores (and added the .png extension).
I was wondering if there was a simple/standard way of doing this (preferably in Delphi) or should I code up the algorithm mentioned above.
try encoding the URL (the returned string will be always a valid filename) and then add the extention. also the process is reversable so you can obtain the original url from the filename.
{$APPTYPE CONSOLE}
uses
SysUtils;
function URLEncode(const AUrl: string): string;
var
Index: Integer;
begin
Result := '';
for Index := 1 to Length(AUrl) do
begin
case AUrl[Index] of
'A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.': Result := Result + AUrl[Index];
' ' : Result := Result + '%20';
else
Result := Result + '%' + IntToHex(Ord(AUrl[Index]), 2);
end;
end;
end;
function UrlToFileName(const AUrl,Ext : string) : TFileName;
begin
Result := Format('%s.%s',[URLEncode(AUrl),Ext]);
end;
begin
try
Writeln(UrlToFileName('http://stackoverflow.com/questions/5784218/convert-a-url-to-a-valid-win32-filename-in-delphi','png'));
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
Readln;
end.
this will return
http%3A%2F%2Fstackoverflow.com%2Fquestions%2F5784218%2Fconvert-a-url-to-a-valid-
win32-filename-in-delphi.png
for additional info read the Naming Files, Paths, and Namespaces article.
You can also use PathGetCharType function from the Windows API.
uses ShLwApi;
...
var s: string;
i: integer;
begin
s := 'http://stackoverflow.com/questions/5784218/convert-a-url-to-a-valid-win32-filename-in-delphi';
for i := 1 to Length(s) do
if PathGetCharType(s[i]) in [GCT_INVALID, GCT_SEPARATOR] then
s[i] := '_';
Writeln(s);
end;
This will return
http___stackoverflow.com_questions_5784218_convert-a-url-to-a-valid-win32-filename-in-delphi
Anyway, I was wondering how many functions exists for path handling. Maybe PathCreateFromUrl do what you want (except extension addition of course), I haven't tested it yet.

Resources