How can I split a string in Inno Setup?
Is there any special function in Inno Setup to split the string?
I want to get the following from the string '11.2.0.16':
tokens: array of string = ('11', '0', '2', '16');
Thanks in advance!
For anyone who prefers the function format, I have modified #cezarlamann's answer:
function StrSplit(Text: String; Separator: String): TArrayOfString;
var
i, p: Integer;
Dest: TArrayOfString;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
Result := Dest
end;
I've been looking for the same thing today...
This one works just fine on Inno Setup Scripts. Paste this excerpt inside your script before the procedure/function which will call this "split" procedure.
You can also modify this onto a function, if you desire...
procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String);
var
i, p: Integer;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
end;
procedure Whatever();
var
str: String;
strArray: TArrayOfString;
i: Integer;
begin
Explode(strArray,str,'.');
for i:=0 to GetArrayLength(strArray)-1 do begin
{ do something }
end;
end;
Taken from here
Here's what I use:
procedure SplitString(S, Delim: string; var Dest: TArrayOfString);
var
Temp: string;
I, P: Integer;
begin
Temp := S;
I := StringChangeEx(Temp, Delim, '', true);
SetArrayLength(Dest, I + 1);
for I := 0 to GetArrayLength(Dest) - 1 do
begin
P := Pos(Delim, S);
if P > 0 then
begin
Dest[I] := Copy(S, 1, P - 1);
Delete(S, 1, P + Length(Delim) - 1);
end
else
Dest[I] := S;
end;
end;
This version avoids repeated array resizing by counting the delimiters using StringChangeEx and setting the array size only once. Since we then know the array size, we can just use a for loop. I also opted for Delete rather than Copy, which (IMO) makes the code easier to read. (This version also fixes the bug where the split does not occur correctly if the delimiter is longer than 1 character.)
If there is a possibility that the delimiter could also be right at the end of the string, then this is what I used (modified from #timyha's answer)
function StrSplit(Text: String; Separator: String): TArrayOfString;
var
i, p: Integer;
Dest: TArrayOfString;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
//add an empty string if delim was at the end
if Text = '' then begin
Dest[i]:='';
i := i + 1;
end;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
Result := Dest
end;
Related
Hello I want to split a simple string into a group of two characters then reverse them.
For example MyStringToBeReversed to two charachters each My St ri ng To Be Re ve rs ed
then reverse each splited two charachters to :
St := ts
ri := ir
etc ...
Here's the code I'm using but it only displays one charachter on memo lines
procedure TForm7.Button1Click(Sender: TObject);
var
MyString: string;
i: integer;
k: integer;
j: integer;
begin
MyString:= Edit1.Text;
for i:= 1 to length(MyString) do
Memo1.Lines.Add(MyString[i]);
// Move(Str[1], MyString, Length(Str));
end;
end.
This should do what you need
function ReverseEachTwoChars(AInput : string) : string;
var
i : integer;
begin
if(Odd(Length(AInput))) then
begin
SetLength(AInput, Length(AInput) + 1);
AInput[Length(AInput)] := 'A';
end;
SetLength(Result, Length(AInput));
i := 1;
while(i < Length(Result)) do
begin
Result[i] := AInput[i + 1];
Result[i + 1] := AInput[i];
Inc(i, 2);
end;
end;
Test:
ShowMessage(ReverseEachTwoChars('010203040506'));
Output:
102030405060
I'm attempting to split a csv string and then loop though the array and alter those string, before building it back into a comma separated string again.
function StrSplit(Text: String; Separator: String): Array of String;
var
i, p: Integer;
Dest: Array of String;
begin
i := 0;
repeat
SetArrayLength(Dest, i+1);
p := Pos(Separator,Text);
if p > 0 then begin
Dest[i] := Copy(Text, 1, p-1);
Text := Copy(Text, p + Length(Separator), Length(Text));
i := i + 1;
end else begin
Dest[i] := Text;
Text := '';
end;
until Length(Text)=0;
Result := Dest
end;
function FormatHttpServer(Param: String): String;
var
build: string;
s: string;
ARRAY1: Array of String;
begin
ARRAY1 := StrSplit(param, ',');
build:='';
for s in ARRAY1 do
begin
build := build + DoSomething(C);
end;
end;
I call the FormatHttpServer from elsewhere. I can't get the script to compile though because on the following line I get a "type mismatch error" and I don't understand why. It should be looping through an array of strings using the string s.
for s in ARRAY1 do
Inno Setup Pascal Script does not support for ... in syntax.
You have to use indexes:
var
I: Integer;
A: array of string;
S: string;
begin
A := { ... };
for I := 0 to GetArrayLength(A) - 1 do
begin
S := A[I];
{ Do something with S }
end;
end;
I need to remove repeated spaces from a string.
The following code, grabbed from internet, works decently except that it duplicate the first char of the string.
Also maybe there is something faster that this.
function DeleteRepeatedSpaces(OldText: string): string;
var
i: integer;
s: string;
begin
if length(OldText) > 0 then
s := OldText[1]
else
s := '';
for i := 1 to length(OldText) do
begin
if OldText[i] = ' ' then
begin
if not (OldText[i - 1] = ' ') then
s := s + ' ';
end
else
begin
s := s + OldText[i];
end;
end;
DelDoubleSpaces := s;
end;
Function based on the simplest state machine (DFA). Minimum of memory reallocations.
State is number of continuous spaces.
J is count of deleted spaces.
function DeleteRepeatedSpaces(const s: string): string;
var
i, j, State: Integer;
begin
SetLength(Result, Length(s));
j := 0;
State := 0;
for i := 1 to Length(s) do begin
if s[i] = ' ' then
Inc(State)
else
State := 0;
if State < 2 then
Result[i - j] := s[i]
else
Inc(j);
end;
if j > 0 then
SetLength(Result, Length(s) - j);
end;
Iterate all members of the string, move the characters to the Result, but skip repeated spaces.
function DeleteRepeatedSpaces(const OldText: string): string;
var
i,j,hi: Integer;
begin
SetLength(Result,Length(OldText));
i := Low(OldText);
j := i;
hi := High(OldText);
while (i <= hi) do begin
Result[j] := OldText[i];
Inc(j);
if (OldText[i] = ' ') then begin
repeat //Skip additional spaces
Inc(i);
until (i > hi) or (OldText[i] <> ' ');
end
else
Inc(i);
end;
SetLength(Result,j-Low(Result)); // Set correct length
end;
The above code is rather fast (faster than any other contribution, so far).
Below is an even more optimized routine:
function DeleteRepeatedSpaces(const OldText: string): string;
var
pO,pR: PChar;
begin
SetLength(Result,Length(OldText));
pR := Pointer(Result);
pO := Pointer(OldText);
while (pO^ <> '') do begin
pR^ := pO^;
Inc(pR);
if (pO^ <> ' ') then begin
Inc(pO);
Continue;
end;
repeat // Skip additional spaces
Inc(pO);
until (pO^ = '') or (pO^ <> ' ');
end;
SetLength(Result,pR-Pointer(Result));
end;
The following isn't wildly efficient, but possibly more so that processiing the string character by character because it doesn't require a new string allocation for each character in the output:
function RemoveDupSpaces(const Input : String) : String;
var
P : Integer;
begin
Result := Input;
repeat
P := Pos(' ', Result); // that's two spaces
if P > 0 then
Delete(Result, P + 1, 1);
until P = 0;
end;
You can use something like this:
function DeleteRepeatedSpaces(const s: string):string;
var
i:integer;
begin
Result := '';
for i := 1 to Length(S) do begin
if not ((s[i]=' ') and (s[i-1]=' ')) then begin
Result := Result + s[i];
end;
end;
end;
Delete two o more spaces contiguous in a string.
This string (without spaces):
The string have groups of spaces inside
return this:
The string have groups of spaces inside
This string (with spaces groups inside):
The string have groups of spaces inside
Return this:
The string have groups of spaces inside
What's the most efficient way to replace every third character of the same type in a string?
I have a string like this:
str := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9'
I want to replace every third #9 by #13#10, so that i get:
str1 := 'c1'#9'c2'#9'c3'#13#10'c4'#9'c5'#9'c6'#13#10'
I would do this in this way:
i:=0;
newStr:='';
lastPos := Pos(str,#9);
while lastPos > 0 do begin
if i mod 3 = 2 then begin
newStr := newStr + Copy(str,1,lastPos-1) + #13#10;
end else begin
newStr := newStr + Copy(str,1,lastPos);
end;
str := Copy(str,lastPos+1,MaxInt);
i := i + 1;
lastPos := Pos(str,#9);
end;
newStr := Copy(str,1,MaxInt);
But thats a lot of copying. Is there a string manipulation function to do this?
I think the problem as stated doesn't match the code you provided. Is every third character a #9? If so, do you want to change every third appearance of #9 for #13#10?
If so, I would do it this way:
function test(str: string): string;
var
i, c, l: integer;
begin
l := Length(str);
SetLength(Result, l + l div 9);
c := 1;
for i := 1 to l do
begin
if (i mod 9 = 0) and (i > 0) then
begin
Result[c] := #13;
Inc(c);
Result[c] := #10;
end
else
Result[c] := str[i];
Inc(c);
end;
end;
I actually have no idea if this function performs well. But given that the constraints aren't clear, I guess so.
If the position of the #9 character is unknown then this solution won't work at all.
Edit: as David points out, this is not nearly equivalent to the original code posted. This seems to work, but it requires two passes on the original string. The thing is, to know if its more efficient or not we need to know more about the input and context.
function OccurrencesOfChar(const S: string; const C: char): integer;
var
i: integer;
begin
result := 0;
for i := 1 to Length(S) do
if S[i] = C then
inc(result);
end;
function Test(str: string): string;
var
len, n, C, i: integer;
begin
C := 1;
len := Length(str);
n := OccurrencesOfChar(str, #9);
SetLength(result, len + n div 3);
n := 1;
for i := 1 to len do
begin
if str[i] = #9 then
begin
if n mod 3 = 0 then
begin
result[C] := #13;
inc(C);
result[C] := #10;
end
else
result[C] := #9;
Inc(n);
end
else
result[C] := str[i];
inc(C);
end;
end;
I expect this question will be closed, but just for fun, that would be my proposal.
Function Replace(const Instr:String;Re:Char;const ReWith:String):String;
var
i,o,cnt,l:Integer;
begin
cnt:=0;
o:=0;
SetLength(Result,Length(Instr)*Length(ReWith));// just for security
for I := 1 to Length(Instr) do
begin
if Instr[i]=Re then inc(cnt);
if cnt=3 then
begin
for l := 1 to Length(ReWith) do
begin
inc(o);
Result[o] := ReWith[l];
end;
cnt := 0;
end
else
begin
inc(o);
Result[o] := Instr[i];
end;
end;
SetLength(Result,o);
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
Edit2.Text := Replace(Edit1.Text,'A','xxx')
end;
I would probably do something like this (coded in the browser). It only needs one string resize and should have less movement of data around. I exit when I have made the last replacement or if it didn't need any:
procedure ReplaceXChar(var aStringToReplace: string; const aIteration:
Integer; const aChar: Char; const aReplacement: string);
var
replaceCount: Integer;
cntr: Integer;
outputCntr: Integer;
lengthToReplace: Integer;
begin
// Find the number to replace
replaceCount := 0;
for cntr := 1 to Length(aStringToReplace) do
begin
if aStringToReplace[cntr] = aChar then
Inc(replaceCount);
end;
if replaceCount >= aIteration then
begin
// Now replace them
lengthToReplace := Length(aReplacement);
cntr := Length(aStringToReplace);
SetLength(aStringToReplace, cntr +
(replaceCount div aIteration) * (lengthToReplace - 1));
outputCntr := Length(aStringToReplace);
repeat
if aStringToReplace[cntr] = aChar then
begin
if (replaceCount mod aIteration) = 0 then
begin
Dec(outputCntr, lengthToReplace);
Move(aReplacement[1], aStringToReplace[outputCntr+1],
lengthToReplace * SizeOf(Char));
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(replaceCount);
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(cntr);
until replaceCount = 0;
end;
end;
Usage would be like this:
var
myString: String;
begin
myString := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9;
ReplaceXChar(myString, 3, #9, #13#10);
ShowMessage(myString);
end;
Is there a way to exclude blank strings from the dynamic array resulting of the SplitString function (Delphi XE, StrUtils), without having to iterate through the array?
If not, can anyone suggest the most efficient way to do it? Right now I'm doing it like this:
function SplitStringNoEmpty(myString : string; myDelimiters : string):TStringDynArray;
var
words_array_pre : TStringDynArray;
words_array_pos : TStringDynArray;
array_length : Integer;
actual_length : Integer;
current_word : string;
procedure AddElement(const Str: string);
begin
words_array_pos[actual_length]:= Str;
inc(actual_length);
end;
begin
words_array_pre:= SplitString(myString, whitespaceNewLineCharacterSet + punctuationCharacterSet);
array_length:= Length(words_array_pre);
if (array_length >0) then
begin
actual_length:= 0;
SetLength(words_array_pos, array_length);
for current_word in words_array_pre do
begin
if (current_word <> '') then
AddElement(current_word);
end;
SetLength(words_array_pos, actual_length);
result:= words_array_pos;
end
else
result:= words_array_pre;
end;
You can write your own implementation of the SplitString function ignoring the empty strings.
Check this sample
function SplitString2(const S, Delimiters: string): TStringDynArray;
var
LIndex, SIndex, FIndex, LMax, LPos: Integer;
foo : string;
begin
Result := nil;
if S <> '' then
begin
LPos := 0;
LMax := 0;
SIndex := 1;
for LIndex := 1 to Length(S) do
if IsDelimiter(Delimiters, S, LIndex) then Inc(LMax);
SetLength(Result, LMax + 1);
repeat
FIndex := FindDelimiter(Delimiters, S, SIndex);
if FIndex <> 0 then
begin
foo:= Copy(S, SIndex, FIndex - SIndex);
if foo<>'' then
begin
Result[LPos] := foo;
Inc(LPos);
end;
SIndex := FIndex + 1;
end;
until (LPos = LMax) or (FIndex=0);
if LPos<LMax then
SetLength(Result, LPos + 1);
foo:=Copy(S, SIndex, Length(S) - SIndex + 1);
if foo<>'' then
Result[LMax] := foo
else
SetLength(Result, LPos);
end;
end;
It's impossible to remove certain elements of an array without iterating over the array — how else would you know which elements to remove? The improvements to make to your code are to remove the need to allocate an extra array. You can cull the original array in-place:
function SplitStringNoEmpty(const s, delimiters: string): TStringDynArray;
var
Src, Dest: Integer;
begin
Result := SplitString(s, delimiters);
if Length(Result) <> 0 then begin
// Push all non-empty values to front of array
Dest := 0;
for Src := 0 to High(Result) do
if Result[Src] <> '' then begin
if Src <> Dest then
Result[Dest] := Result[Src];
Inc(Dest);
end;
// Remove excess from end of array
SetLength(Result, Dest);
end;
end;