String Arrays in Innosetup - inno-setup

I am trying to get a particular integer after a sub string in InnoSetup in a line. There are Trim, TrimLeft, TrimRight functions but no substring extraction functions.
Example :
line string: 2345
line another string: 3456
I want to extract "2345" and "3456".
I am loading the file content in a array but can't get it de-referenced by array[count][char_count].

I would load the input file to a TStrings collection and iterate it line by line. For each line I would find a ":" char position and from this position I would copy the string which I would finally trim. In steps:
1. line string: 2345
2. 2345
3. 2345
Now remains to convert such string to an integer and add it to the final collection. Since you've shown an empty line in your file sample, and since you didn't said if this format will always be fixed, let's convert this string in the safe way. Inno Setup provides for this safe conversion just one function, the StrToIntDef function. This function however requires a default value, which is returned when the conversion fails and so you will have to pass to its call a value, which you'll never expect in your file. In the following example I chose -1, but you can replace it with any other value that you never expect in your input file:
[Code]
type
TIntegerArray = array of Integer;
procedure ExtractIntegers(Strings: TStrings; out Integers: TIntegerArray);
var
S: string;
I: Integer;
Value: Integer;
begin
for I := 0 to Strings.Count - 1 do
begin
// trim the string copied from a substring after the ":" char
S := Trim(Copy(Strings[I], Pos(':', Strings[I]) + 1, MaxInt));
// try to convert the value from the previous step to integer;
// if such conversion fails, because the string is not a valid
// integer, it returns -1 which is treated as unexpected value
// in the input file
Value := StrToIntDef(S, -1);
// so, if a converted value is different from unexpected value,
// add the value to the output array
if Value <> -1 then
begin
SetArrayLength(Integers, GetArrayLength(Integers) + 1);
Integers[GetArrayLength(Integers) - 1] := Value;
end;
end;
end;
procedure InitializeWizard;
var
I: Integer;
Strings: TStringList;
Integers: TIntegerArray;
begin
Strings := TStringList.Create;
try
Strings.LoadFromFile('C:\File.txt');
ExtractIntegers(Strings, Integers);
for I := 0 to GetArrayLength(Integers) - 1 do
MsgBox(IntToStr(Integers[I]), mbInformation, MB_OK);
finally
Strings.Free;
end;
end;

Related

Delphi Pos always returning 0

I really don't know why Pos keep returning 0 instead of the char ";" position in string
I have to get a response of a php page which outputs a Content-Type: text/plain
So one example output is
2;fulano;fulano;0
3;ciclano;ciclano;0
4;beltrano;beltrano;0
5;foo;foo;0
8;jose;jose;0
9;maria;maria;0
and the code is
var
linha,uid,login,senha,email,tipo : WideString;
resposta : TStringList;
I : Integer;
begin
try
resposta := TStringList.Create;
resposta.Text := frmMain.IdHTTP1.Get(frmMain.cdsConfig.FieldByName('WebService').AsString+'listdest.php');
for I := 0 to resposta.Count-1 do
begin
linha := resposta.Strings[i];
if i = 0 then
Delete(linha,1,1); // the first line have one wierd $FEFF
if length(linha) > 5 then
begin
uid := Copy(linha,1,Pos(linha,';')-1);
Delete(linha,1,Pos(linha,';'));
login:=Copy(linha,1,Pos(linha,';')-1);
Delete(linha,1,Pos(linha,';'));
senha:=Copy(linha,1,Pos(linha,';')-1);
Delete(linha,1,Pos(linha,';'));
email:=Copy(linha,1,Pos(linha,';')-1);
Delete(linha,1,Pos(linha,';'));
tipo:=Copy(linha,1,Pos(linha,';')-1);
Delete(linha,1,Pos(linha,';'));
end;
end;
//dlgWait.Close;
except on E :Exception do
begin
MessageBox(Self.Handle,PWideChar(E.Message),'Erro',MB_OK+MB_ICONERROR+MB_APPLMODAL);
dlgWait.Close;
FreeAndNil(resposta);
end;
end;
Your call to Pos is backwards. The parameters are:
function Pos(const SubStr, Str: _ShortStr; Offset: Integer): Integer;
But your code assumes they are:
function Pos(const Str, SubStr: _ShortStr; Offset: Integer): Integer;
So actually what it's trying to do is look for the value of linha within ';', which of course unless linha = ';', it will return 0.
Another way to put it, as Rudy said, instead of looking for a needle in a haystack, your code is looking for a haystack in a needle.
Swap around the first and second parameters to these calls.
On a side note, just a tip for performance. Rather than calling Pos twice for each, keep a cached copy of the value...
P := Pos(';', linha);
uid := Copy(linha,1,P-1);
Delete(linha,1,P);

Delphi - how to implement sorted table of integers and strings, sorted by integer?

I'd like to create a table with each row consisting of an integer and a string. The table should be sorted by the integer. The end goal is to extract the "n" strings with the smallest associated integer.
TStringlist isn't quite right because it is only a single string. They have Name-value pairs but they sort in the wrong order - I want value-name, sorted by value.
TDictionary is not sorted and cannot be sorted (other than by hash, which they are)
I suppose I could format the integer into a string long enough to hold the largest integer with leading zeros, and concatenate that onto the start of the rest of the string data in a TStringList, but that seems ugly. Is there a more elegant approach to implement this table, sorted on the integer part?
A string list will still work, just cast your integer to the object field.
function SortStringListByObject(List: TStringList; Index1, Index2: Integer): Integer;
begin
if Integer(List.Objects[Index1]) = Integer(List.Objects[Index2])
then result := 0
else
if Integer(List.Objects[Index1]) < Integer(List.Objects[Index2])
then result := -1
else result := 1;
end;
procedure TForm3.Button1Click(Sender: TObject);
var
sl: TStringList;
x: Integer;
begin
StartTime := Now;
sl := TStringList.Create;
try
// add some objects (and strings)
sl.AddObject('One',TObject(3));
sl.AddObject('Two',TObject(2));
sl.AddObject('Three',TObject(1));
// sory by my function
sl.CustomSort(SortStringListByObject);
// show results
for x := 0 to sl.count-1 do begin
Memo1.lines.Add(sl[x]);
end;
finally
sl.Free;
end;
end;

Inno Setup type mismatch when assigning function return value to a string

I have the following function in my Inno setup:
function GetSerialNumber(ADelimiter: Char): string;
var
I: Integer;
begin
Result := '';
for I := 0 to GetArrayLength(SerialEdits) - 1 do
Result := Result + SerialEdits[I].Text + ADelimiter;
if GetArrayLength(SerialEdits) > 1 then
begin
Delete(Result, Length(Result), 1);
end
end;
Within another function below, I have a variable named Serial: string, but when I do
Serial := GetSerialNumber('');
I get a type mismatch error, does anyone know what I'm doing wrong? Thanks!
The problem is not the return value, but the argument.
The '' is not a valid char literal. A char literal must be exactly one character long. The '' is a string.
If you want to allow an empty delimiter, change the argument type to the string.

How do I store and load a list of key-value pairs in a string?

I have a list of strings and the values they are to be replaced with. I'm trying to combine them in a list like 'O'='0',' .'='.', ... so it's easy for me to edit it and add more pairs of replacements to make.
Right now the best way I could think of it is:
var
ListaLimpeza : TStringList;
begin
ListaLimpeza := TStringList.Create;
ListaLimpeza.Delimiter := '|';
ListaLimpeza.QuoteChar := '"';
ListaLimpeza.DelimitedText := 'O=0 | " .=."';
ShowMessage('1o Valor = '+ListaLimpeza.Names[1]+' e 2o Valor = '+ListaLimpeza.ValueFromIndex[1]);
This works, but it's not good for visuals, since I can't code the before string (for ex ' .') like that (which is very visual for the SPACE character), only like (" .) so that the = works to assign a name and value in the TStringList.
The Names and Values by default have to be separated by =, in the style of Windows INI files. There's no way AFAICT to change that separator. As #SirRufo indicates in the comment (and which I had never noticed), you can change that using the TStringList.NameValueSeparator property.
This will give you an idea of what Delphi thinks is in your TStringList, which is not what you think it is:
procedure TForm1.FormCreate(Sender: TObject);
var
SL: TStringList;
Temp: string;
i: Integer;
begin
SL := TStringList.Create;
SL.Delimiter := '|';
SL.QuoteChar := '"';
SL.StrictDelimiter := True;
SL.DelimitedText := 'O=0 | ! .!=!.!';
Temp := 'Count: ' + IntToStr(SL.Count) + #13;
for i := 0 to SL.Count - 1 do
Temp := Temp + Format('Name: %s Value: %s'#13,
[SL.Names[i], SL.ValueFromIndex[i]]);
ShowMessage(Temp);
end;
This produces this output:
TStringList Names/Values probably isn't going to do what you need. It's not clear what your actual goal is, but it appears that a simple text file with a simple list of text|replacement and plain parsing of that file would work, and you can easily use TStringList to read/write from that file, but I don't see any way to do the parsing easily except to do it yourself. You could use an array to store the pairs when you parse them:
type
TReplacePair = record
TextValue: string;
ReplaceValue: string;
end;
TReplacePairs = array of TReplacePair;
function GetReplacementPairs: TReplacePairs;
var
ConfigInfo: TStringList;
i, Split: Integer;
begin
ConfigInfo := TStringList.Create;
try
ConfigInfo.LoadFromFile('ReplacementPairs.txt');
SetLength(Result, ConfigInfo.Count);
for i := 0 to ConfigInfo.Count - 1 do
begin
Split := Pos('|`, ConfigInfo[i];
Result[i].TextValue := Copy(ConfigInfo[i], 1, Split - 1);
Result[i].ReplaceValue := Copy(ConfigInfo[i], Split + 1, MaxInt);
end;
finally
ConfigInfo.Free;
end;
end;
You can then populate whatever controls you need to edit/add/delete the replacement pairs, and just reverse the read operation to write them back out to save.

Short Strings in a Variant Record?

I'd like to be able to access sections of a short string as part of a record
Something like
TMyRecord = record
case Boolean of
True:
(
EntireString: String[20];
);
False
(
StringStart: String[8];
StringMiddle: String[4];
StringEnd: String[8];
);
end;
Is this possible or would I have to declare each char individually
TMyRecord = record
private
Chars: Array[1..20] of Char;
Function GetStringStart:String;
Procedure SetStringStart(Value: String);
public
Property StringStart: String read GetStringStart write SetStringStart; // Can I have properties on a record?
end;
Function GetStringStart: String;
begin
Result := Chars[1] + Char[2]....;
end;
Procedure SetStringStart(Value: String);
begin
for i := 1 to 8 do
begin
Chars[i] := Value[i];
end;
end;
Is this possible / worth the effort?
A Delphi short string contains more than just the string contents. The initial byte in the data structure contains the length of the string. This is why short strings are limited to 255 characters.
So, you can't use short strings in your variant array the way you propose.
What you could do is adapt your second approach based on getter and setter methods to be a bit more readable.
For example:
function TMyRecord.GetStringStart: string;
begin
SetString(Result, #Chars[1], 8);
end;
You might consider using a string rather than a char array, but it's a little hard to be 100% sure of that advice without knowing exactly what your underlying problem is.
As a final thought, why not turn the problem around? Store 3 strings: StartString, MiddleString and EndString. Then have a property backed with a getter and setter called EntireString. When you read EntireString it pieces it together from the 3 individual parts, and when you write to it it pulls the individual parts out. I suspect it would be easier that way around.
Your first sample doesn't consider the length byte. The memory layout looks like this:
case True:
L12345678901234567890
^....................
case False:
L12345678L1234L12345678
^........^....^........
(L = length byte).
Depending on your requirements (e.g.: Are the partial strings always 8, 4 and 8 Chars?) I'd try storing the partial strings and make EntireString the property, using System.Copy, StrUtils.LeftStr etc.
ShortString has an implied length, so your first example will map the length parts of the substrings on top of the main string.
Your second sample is the way to start, with these notes:
properties on records are possible
you should think of the length of each sub-string (or is it always a fixed array of 20 characters?)
Edit
It totally depend on the reason you want this, and mixing character arrays and strings will get you into trouble because strings can be shorter than the array length.
Small example:
program VariantRecordsWithCharactersAndStrings;
{$APPTYPE CONSOLE}
uses
SysUtils,
Math;
const
Size20 = 20;
Size8 = 8;
Size4 = 4;
type
TChar20 = array[0..Size20-1] of Char;
TChar8 = array[0..Size8-1] of Char;
TChar4 = array[0..Size4-1] of Char;
TMyRecord = record
class var FillCharValue: Byte;
function GetEntireString: string;
function GetStringStart: string;
function GetStringMiddle: string;
function GetStringEnd: string;
procedure SetEntireString(const Value: string);
procedure SetStringStart(const Value: string);
procedure SetStringMiddle(const Value: string);
procedure SetStringEnd(const Value: string);
property EntireString: string read GetEntireString write SetEntireString;
property StringStart: string read GetStringStart write SetStringStart;
property StringMiddle: string read GetStringMiddle write SetStringMiddle;
property StringEnd: string read GetStringEnd write SetStringEnd;
procedure SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
case Boolean of
True:
(
CharFull: TChar20;
);
False:
(
CharStart: TChar8;
CharMiddle: TChar4;
CharEnd: TChar8;
);
end;
function TMyRecord.GetEntireString: string;
begin
Result := CharFull;
end;
function TMyRecord.GetStringStart: string;
begin
Result := CharStart;
end;
function TMyRecord.GetStringMiddle: string;
begin
Result := CharMiddle;
end;
function TMyRecord.GetStringEnd: string;
begin
Result := CharEnd;
end;
procedure TMyRecord.SetEntireString(const Value: string);
begin
SetCharArray(CharFull, SizeOf(CharFull), Value);
end;
procedure TMyRecord.SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
begin
FillChar(CharArrayPointer^, CharArraySize, FillCharValue);
Move(Value[1], CharArrayPointer^, Min(CharArraySize, SizeOf(Char)*Length(Value)));
end;
procedure TMyRecord.SetStringStart(const Value: string);
begin
SetCharArray(CharStart, SizeOf(CharStart), Value);
end;
procedure TMyRecord.SetStringMiddle(const Value: string);
begin
SetCharArray(CharMiddle, SizeOf(CharMiddle), Value);
end;
procedure TMyRecord.SetStringEnd(const Value: string);
begin
SetCharArray(CharEnd, SizeOf(CharEnd), Value);
end;
var
MyRecord: TMyRecord;
procedure Dump();
begin
Writeln(MyRecord.EntireString);
Writeln(MyRecord.StringStart);
Writeln(MyRecord.StringMiddle);
Writeln(MyRecord.StringEnd);
end;
procedure TestWithFillCharValue(const FillCharValue: Byte);
begin
Writeln('Testing with FillCharValue ', FillCharValue);
TMyRecord.FillCharValue := FillCharValue;
MyRecord.EntireString := '123456789001234567890';
Dump();
MyRecord.StringStart := 'AAA';
MyRecord.StringMiddle := 'BBB';
MyRecord.StringEnd := 'CCC';
Dump();
end;
begin
try
TestWithFillCharValue(0); // this will truncated all the sub arrays when you pass strings that are too short
TestWithFillCharValue(20); // when using Unicode, this fails even more horribly
Write('Press <Enter>');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
This class does more or less what you want:
it has overlapping data structures
when you assign the arrays: no problem
when you assign the strings: be aware when strings get to short
As other stated, it won't work, because the variant-sized record will add some lengths for StringStart/StringMiddle/StringEnd in the middle of the EntireString type.
You are confusing the *char type of C with the pascal shortstring type. There is an hidden character at position [0] which is the shortstring length.
You could use regular string type, then split in on purpose:
procedure StringSplit(const EntireString: string; out StringStart, StringMiddle, StringEnd: string);
begin
if length(EntireString)<>20 then
exit;
StringStart := copy(EntireString,1,8);
StringMiddle := copy(EntireString,9,4);
StringEnd := copy(EntireString,13,8);
end;
Note that the out parameter type will set all output String* variables into '' before calling the function.
This version will expect entering entire string of 20 chars long.
You could use shortstrings, but with custom types of the exact length, if you want to avoid hidden copies from/to string[255] (which occur when you use a shortstring type and work with string[n] with n<255):
type
String20 = string[20];
String4 = string[4];
String8 = string[8];
procedure StringSplit(const EntireString: String20; out StringStart: String8;
out StringMiddle: String4; out StringEnd: String8);
begin
if length(EntireString)<>20 then
exit;
StringStart := copy(EntireString,1,8);
StringMiddle := copy(EntireString,9,4);
StringEnd := copy(EntireString,13,8);
end;

Resources