I need to change some values in a configuration file. The file is UTF-8 without BOM. I need to save it the same way. How do I do it with Inno Setup Unicode edition? Note: This doesn't work, and this doesn't show how to read the file correctly.
const
CP_UTF8 = 65001;
{ ... }
var
FileName: string;
S: string;
begin
FileName := 'test.txt';
if not LoadStringFromFileInCP(FileName, S, CP_UTF8) then
begin
Log('Error loading the file');
end
else
if StringChangeEx(S, 'žluťoučký kůň', 'ďábelské ódy', True) <= 0 then
begin
Log('No value was replaced');
end
else
if not SaveStringToFileInCP(FileName, S, CP_UTF8) then
begin
Log('Error writing the file');
end
else
begin
Log('Replacement successful');
end;
end;
LoadStringFromFileInCP and SaveStringToFileInCP come from:
Inno Setup - Convert array of string to Unicode and back to ANSI
The code needs Unicode version of Inno Setup (the only version as of Inno Setup 6).
For Unicode string literals, your .iss file must be in UTF-8 encoding with BOM.
Related
I have a custom font that is installed with my Inno Setup, and I would like to overwrite the existing font only if the font in my setup was upgraded.
In order to do this, I have tried to get the version from my font file but GetVersionNumbersString in a function or GetFileVersionString in the Inno Setup preprocessor. As far as I have understood, those functions only apply to exe or dll but I might be wrong.
Any lead to help me achieve this would be very much appreciated.
Thanks,
Olivier
You can use the following code to extract TTF file (possibly any file) version:
function GetShellItemVersion(Path: string): string;
var
Shell, Folder, Item, Version: Variant;
FolderPath: string;
begin
Shell := CreateOleObject('Shell.Application');
FolderPath := ExtractFilePath(Path);
Folder := Shell.NameSpace(FolderPath);
if VarIsClear(Folder) then
begin
Log(Format('Error reading folder "%s"', [FolderPath]));
end
else
begin
Item := Folder.ParseName(ExtractFileName(Path));
if VarIsClear(Item) then
begin
Log(Format('Error accessing "%s"', [Path]));
end
else
begin
Version := Folder.GetDetailsOf(Item, 166);
if VarIsClear(Version) then
begin
Log(Format('Error reading version of "%s"', [Path]));
end
else
begin
Result := Version;
Log(Format('Version of "%s" is "%s"', [Path, Result]));
end;
end;
end;
end;
Based on Get details of uninstalled Windows fonts via PowerShell.
I made a program to put a number in a file. But the file don't show that number(i'mean my code is working but when open the file i created he doesn't show the number.
Here is the code:
Program firstfich;
Uses Wincrt;
Type fich = file Of Integer;
Var f: fich;
x: Integer; {start }
Begin
Assign(f,'C:\a progremming works bac\first bac programe\kill.dat');
Rewrite(f);
x := 47;
Write(f,x);
Close(f);
End.
If you want to see "47" when you open the file in a text editor, you should not create a file containing the byte 47 (which is the ASCII code for the solidus character "/").
Instead, you should create a file containing the bytes 52 and 55, which are the ASCII codes for the characters "4" and "7", respectively.
You should read about how text files are represented in computer memory. See, e.g., the Wikipedia article on character encodings.
Now, to create a file containing the bytes 52 and 55, you just write the string "47" to the file (all code is in Delphi, a modern Pascal implementation -- if you are using some other Pascal implementation, you might need to modify the code slightly):
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
var
f: text;
begin
try
try
AssignFile(f, 'D:\number.txt');
Rewrite(f);
Write(f, '47');
CloseFile(f);
except
on E: Exception do
Writeln(E.Message);
end;
finally
Writeln('Done.');
Readln;
end;
end.
This will create a file that contains two bytes: 52 and 55. A text editor will display the characters "4" and "7" (assuming ASCII).
On the contrary, if you would create a file that contains only the byte 47, a text editor would display the solidus character ("/", assuming ASCII).
If you want to see the actual bytes in a file, you shouldn't open it in a text editor, but in a hex editor. I encourage you to download a hex editor and play around with these concepts to learn more about them.
Update, in response to comment: If you insist on writing bytes manually, using old Pascal I/O, the following works in Delphi:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
var
b: Byte;
f: file;
begin
try
try
AssignFile(f, 'C:\Users\Andreas Rejbrand\Desktop\number.txt');
Rewrite(f, 1);
b := 52; // Or, better: Ord('4')
BlockWrite(f, b, 1);
b := 55; // Or, better: Ord('7')
BlockWrite(f, b, 1);
CloseFile(f);
except
on E: Exception do
Writeln(E.Message);
end;
finally
Writeln('Done.');
Readln;
end;
end.
Still, this is a horrible way of creating a text file containing the number "47"; it should only be considered for educational purposes. To see alternatives to old Pascal I/O, check out https://stackoverflow.com/a/58298368/282848.
program numbertofile;
var
f: TextFile;
x: Integer;
begin
assign(f, 'kill.dat');
rewrite(f);
x := 47;
write(f, x);
close(f);
end.
Just make your file a text file and your integer will be saved in the file the way you expect it to. Just set your file to a text variable like this:
Var f: text;
x: Integer; {start }
Begin
Assign(f,'C:\a progremming works bac\first bac programe\kill.dat');
Rewrite(f);
x := 47;
Write(f,x);
Close(f);
End.
note the write will not put a carriage return or linefeed after the line. if you want numbers on different lines use writeln instead of write.
Writeln(f,x);
I have delphi application, i need to rewrite it for OS X.
This app writes/reads data to/from HID-device.
I have issues when i'm trying to write string from mac.
Here is the line that i'm writing(from debugger on windows): 'Новый комплекс 1'
and this works good. Meanwhile if copy this from debugger to somewhere it becomes 'Íîâûé êîìïëåêñ 1'. Device shows it as it was written, in cyrillic. And that's OK.
When i'm trying to repeat this steps on OS X, device shows unreadeble symbols. But if i do hardcode 'Íîâûé êîìïëåêñ 1' from windows example it's OK again.
Give some hints.
How it on windows
Some code:
s:= 'Новый комлекс 1'
s:= AnsiToUtf8(ReplaceNull(s));
Here is ReplaceNULL:
function ReplaceNull(const Input: string): string;
var
Index: Integer;
Res: String;
begin
Res:= '';
for Index := 1 to Length(Input) do
begin
if Input[Index] = #0 then
Res:= Res + #$12
else
Res:= Res + Input[Index];
end;
ReplaceNull:= Res;
end;
this string i put to Tstringlist and then save to file:
ProgsList.SaveToFile(Mwork.pathLibs+'stream.ini', TEncoding.UTF8);
Other program read this list and then writes to device:
Progs:= TStringList.Create();
Progs.LoadFromFile(****);
s:= UTF8ToAnsi(stringreplace(Progs.Strings[i], #$12, #0, [rfReplaceAll, rfIgnoreCase]));
And then write it to device.
So the line wich writes seems like this:
"'þ5'#0'ÿ'#$11'Новый комплекс 1'#0'T45/180;55;70;85;90;95;100;T45/180'#0'ÿ'"
On the mac i succesfully get the same string. But device can't show this in cyrillic.
A Delphi string is encoded in UTF-16 on all platforms. There is no need to convert it, unless you are interacting with non-Unicode data outside of your app.
That being said, if you have a byte array that is encoded in a particular charset, you can convert it to another charset using Delphi's TEncoding.Convert() method. You can use the TEncoding.GetEncoding() method to get a TEncoding object for a particular charset (if different than the standard supported charsets - ANSI, ASCII, UTF-7, UTF-8, and UTF-16 - which have their own property getters in TEncoding).
var
SrcEnc, DstEnc: TEncoding;
SrcBytes, ConvertedBytes: TBytes;
begin
SrcBytes := ...; // Cyrillic encoded bytes
SrcEnc := TEncoding.GetEncoding('Cyrillic'); // or whatever the real name is...
try
DstEnc := TEncoding.GetEncoding('Windows-1251');
try
ConvertedBytes := TEncoding.Convert(SrcEnc, DstEnc, SrcBytes);
finally
DstEnc.Free;
end;
finally
SrcEnc.Free;
end;
// use ConvertedBytes as needed...
end;
Update: To encode a Unicode string in a particular charset, simply call the TEncoding.GetBytes() method, eg:
s := 'Новый комлекс 1';
Enc := TEncoding.GetEncoding('Windows-1251');
try
bytes := Enc.GetBytes(s);
finally
Enc.Free;
end;
s := 'Новый комлекс 1';
bytes := TEncoding.UTF8.GetBytes(s);
You can use the TEncoding.GetString() to decode bytes in a particular charset back to a String, eg:
bytes := ...; // Windows-1251 encoded bytes
Enc := TEncoding.GetEncoding('Windows-1251');
try
s := Enc.GetString(bytes);
finally
Enc.Free;
end;
bytes := ...; // UTF-8 encoded bytes
s := TEncoding.UTF8.GetString(bytes);
The answer was next. Delphi Berlin 10.1 uses KOI8-R, and my device - cp1251.
As i'd wanted to write russian symbols(Cyrillic) i've created table of matches for symbols from KOI8-R and cp1251.
So, i take string in KOI8-R make it in cp1251.
Simple code:
Dict:=TDictionary<String,String>.Create;
Dict.Add(#$439,#$E9);//'й'
Dict.Add(#$44E,#$FE);//'ю'
Dict.Add(#$430,#$E0);//'а'
....
function tkoitocp.getCP1251Code(str:string):string;
var i:integer; res,key,val:string; pair:Tpair<String,String>;
begin
res:='';
for i:=1 to length(str) do
begin
if dict.ContainsKey(str[i]) then
begin
pair:= dict.ExtractPair(str[i]);
res:=res+pair.Value;
dict.Add(pair.Key,pair.Value);
end
else
res:=res+str[i];
end;
Result:=res;
end;
So I wonder if it is possible to allow user to enter only ASCII as installation path? (warn him and make input path again) (problem is application we install is old and can not work with Cyrillic paths so we need to restrict user on installation stage)
To restrict the user input for the application directory for the Basic Latin character set you may use the following code. The code only checks if any char of the selected directory name doesn't exceed the Basic Latin character set range. If that happens, an error message is shown and the user is forced to stay on the directory selection page. The remaining folder name validation (based on the file system naming conventions) is left on Inno Setup internals, as it already was:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
function IsCharValid(Value: Char): Boolean;
begin
Result := Ord(Value) <= $007F;
end;
function IsDirNameValid(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to Length(Value) do
if not IsCharValid(Value[I]) then
Exit;
Result := True;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if (CurPageID = wpSelectDir) and
not IsDirNameValid(WizardForm.DirEdit.Text) then
begin
Result := False;
MsgBox('There is an invalid char in the selected directory name. ' +
'Directory path may contain only chars that are valid for the ' +
'file system naming conventions and only in the range of the ' +
'Basic Latin character set.', mbError, MB_OK);
end;
end;
I'm starting to use Inno Setup, and I have some problems with my INI file encoding.
I want to save user input in the INI file, and this input can contain accents.
I use Inno Setup Unicode, my setupScript.iss is UTF-8 encoded, and here is my code (a part) :
[INI]
Filename: "{app}\www\conf\config.ini"; Section: "Settings"; Key: "ca.plafondAnnuel"; String: "{code:GetUser|Plafond}"
Filename: "{app}\www\conf\config.ini"; Section: "Settings"; Key: "app.siren"; String: "{code:GetUser|Siren}"
Filename: "{app}\www\conf\config.ini"; Section: "Settings"; Key: "app.adresse"; String: "{code:GetUser|Adresse}"
[Code]
var
UserPage: TInputQueryWizardPage;
ExamplePage : TInputOptionWizardPage;
ImmatriculationPage : TInputOptionWizardPage;
FakeElemIndex: Integer;
FakeElem: TCustomEdit;
AdresseTextarea: TNewMemo;
procedure InitializeWizard;
begin
UserPage := CreateInputQueryPage(wpWelcome,
'Configuration de l''application', '',
'Configurez ici votre application. Une fois installée, vous pourrez modifier ces valeurs.');
UserPage.Add('Siren :', False);
UserPage.Add('Plafond annuel (utilisé par les auto-entreprises, mettre 0 si vous ne souhaitez pas plafonner votre chiffre d''affaire.):', False);
FakeElemIndex := UserPage.Add('Votre adresse complète (telle qu''elle s''affichera sur les devis et factures, avec nom complet):', False);
FakeElem := UserPage.Edits[FakeElemIndex];
AdresseTextarea := TNewMemo.Create(WizardForm);
AdresseTextarea.Parent := FakeElem.Parent;
AdresseTextarea.SetBounds(FakeElem.Left, FakeElem.Top, FakeElem.Width, ScaleY(50));
// Hide the original single-line edit
FakeElem.Visible := False;
end;
function GetUser(Param: String): String;
begin
if Param = 'Adresse' then
Result := AdresseTextarea.Text
else if Param = 'Siren' then
Result := UserPage.Values[0]
else if Param = 'Plafond' then
Result := UserPage.Values[1];
end;
The value returned by getUser|Adresse in the [INI] part is not UTF-8 encoded: I open the INI file with Notepad++ and I see the file is UTF-8 encoded. But the value adresse is ANSI encoded (If I change the encoding of the file to ANSI, this value is readable)
Someone can help me understand how can I save this user input in UTF-8 ?
Thanks a lot !
The INI functions of Inno Setup ([INI] section and SetIni* functions) use internally the Windows API function WritePrivateProfileString.
This function does not support UTF-8 at all. All it supports is the ANSI encoding and UTF-16.
See How to read/write Chinese/Japanese characters from/to INI files?
So it's even questionable whether the target application will be able to read UTF-8-encoded INI file, if it relies on the Windows API function to read it.
Anyway, if you need the UTF-8, you would have to format the entries to INI format yourself and use SaveStringsToUTF8File function to write it.
The last option is to hack it by using the system call WritePrivateProfileString to write seemingly ANSI-encoded string, which will be in fact UTF-8-encoded.
For that you need to convert the string to UTF-8 in your code. You can use WideCharToMultiByte for that.
function WideCharToMultiByte(CodePage: UINT; dwFlags: DWORD;
lpWideCharStr: string; cchWideChar: Integer; lpMultiByteStr: AnsiString;
cchMultiByte: Integer; lpDefaultCharFake: Integer;
lpUsedDefaultCharFake: Integer): Integer;
external 'WideCharToMultiByte#kernel32.dll stdcall';
const
CP_UTF8 = 65001;
function GetStringAsUtf8(S: string): AnsiString;
var
Len: Integer;
begin
Len := WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, 0, 0, 0);
SetLength(Result, Len);
WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, Len, 0, 0);
end;
function WritePrivateProfileString(
lpAppName, lpKeyName, lpString, lpFileName: AnsiString): Integer;
external 'WritePrivateProfileStringA#kernel32.dll stdcall';
procedure CurStepChanged(CurStep: TSetupStep);
var
IniFileName: string;
begin
if CurStep = ssInstall then
begin
Log('Writting INI file');
if not ForceDirectories(ExpandConstant('{app}\www\conf')) then
begin
MsgBox('Error creating directory for INI file', mbError, MB_OK);
end
else
begin
IniFileName := ExpandConstant('{app}\www\conf\config.ini');
if (WritePrivateProfileString(
'Settings', 'ca.plafondAnnuel', GetStringAsUtf8(GetUser('Plafond')),
IniFileName) = 0) or
(WritePrivateProfileString(
'Settings', 'app.siren', GetStringAsUtf8(GetUser('Siren')),
IniFileName) = 0) or
(WritePrivateProfileString(
'Settings', 'app.adresse', GetStringAsUtf8(GetUser('Adresse')),
IniFileName) = 0) then
begin
MsgBox('Error writting the INI file', mbError, MB_OK);
end;
end;
end;
end;