inno setup - Delete arbitrary substring from a string - inno-setup

I need to delete a substring which is arbitrary each time. For example:
..\HDTP\System\*.u should become ..\System\*.u and/or ..\New Vision\Textures\*.utx should become ..\Textures\*.utx. More specifically: Ignore the first three characters, delete whatever comes after, until the next \ character (including that character), leave the rest of the string intact. Could you, please, help me with this? I know, I have the worst explaining skills in the whole world, if something isn't clear, I'll try to explain again.

This is a little bit of copy and split work for Inno Setup but I have here a function for you with some extra comments.
Read it carefully as it isn't tested properly and if you have to edit it you will
have to know what it is doing ;)
function FormatPathString(str : String) : String;
var
firstThreeChars : String;
charsAfterFirstThree : String;
tempString : String;
finalString : String;
dividerPosition : Integer;
begin
firstThreeChars := Copy(str, 0, 3); //First copy the first thee character which we want to keep
charsAfterFirstThree := Copy(str,4,Length(str)); //copy the rest of the string into a new variable
dividerPosition := Pos('\', charsAfterFirstThree); //find the position of the following '\'
tempString := Copy(charsAfterFirstThree,dividerPosition+1,Length(charsAfterFirstThree)-dividerPosition); //Take everything after the position of '\' (dividerPosition+1) and copy it into a temporary string
finalString := firstThreeChars+tempString; //put your first three characters and your temporary string together
Result := finalString; //return your final string
end;
And this is how you would call it
FormatPathString('..\New Vision\Textures\*.utx');
You will have to rename the function and the var's so that it will match your program but I think this will help you.

Related

Find a variable string in another string using Delphi

Given a field/string like 'G,H,1,AA,T,AAA,1,E,A,H,....'. The characters can be in any combination/order.
How do I search that string and return True when searching for just 'A' or 'AA'?
i.e. If doing a search for say 'A', it should only find the 'A' between the E & H.
Regards & TIA,
Ian
You can simply split your string into an array by your delimiter and search in that array, e.g.
function FindItem(const List, Item: string): Boolean;
var
SArr: TArray<string>;
S: string;
begin
Result := False;
//Separators could also be a parameter
SArr := List.Split([',']);
for S in SArr do
begin
//use S.Trim if needed
//use AnsiSameText(S, Item) for case insensitive check
if Item = S then
Exit(True);
end;
end;
If you need to search for multiple items in your data, you might want to sort the array and use a binary search.
TArray.Sort<string>(SArr);
Result := TArray.BinarySearch(SArr, Item, Tmp);
Another approach would be using regular expressions with word boundaries to search for whole words only
Result := TRegex.IsMatch(List, '\bA\b');
Split this string into a list, for example with TStringList.CommaText (alternatively, into an array with StrUtils.SplitString()).
Then, just walk through the list and check every string (or use TStrings.IndexOf() - note: it uses CaseSensitive property, as Remy mentioned in comments).
If you are going to make many queries for the same list - sort it and use an effective binary search (TStringList.Find()).

Passing a commandline parameter containing quotes to installer

I'm trying to pass a custom commandline parameter to an installer created with Inno Setup. The parameter value actually consists of several parameters that will be used to launch the installed program when installation is complete, so the value contains spaces as well as quotes to group together parameters.
For example, when -arg "C:\path with spaces" -moreargs should be used as Parameters in a [Run] section entry, I would like to launch the installer like this:
setup.exe /abc="-arg "C:\path with spaces" -moreargs"
Outputting the parameters that the installer receives in a [Code] section via ParamStr() shows them (of course) split up: /abc=-arg C:\path, with, spaces -moreargs
How do I escape the quotes to retain them?
I tried doubling the inner quotes:
setup.exe /abc="-arg ""C:\path with spaces"" -moreargs"
This correctly keeps the parameter together (/abc=-arg C:\path with spaces -moreargs), however it seems that ParamStr() removes all quotes.
Is there a way to retain quotes within a parameter retrieved with ParamStr() or a param constant {param:abc|DefaultValue}?
Alternatives seem to be to either do my own parameter parsing from GetCmdTail (which contains the original parameter string) or use another character instead of the inner quotes that are retained in ParamStr() and then replace them with quotes afterwards. But I would prefer not doing that if there is a way to use the built-in functions.
It seems both {param} and ParamStr() strip out double quotes, but as you pointed out (thanks!) the GetCmdTail function returns the original.
So here's a function to get the original parameters with quotes:
function ParamStrWithQuotes(ParamName: String) : string;
var
fullCmd : String;
currentParamName : string;
i : Integer;
startPos : Integer;
endPos : Integer;
begin
fullCmd := GetCmdTail
// default to end of string, in case the option is the last item
endPos := Length(fullCmd);
for i := 0 to ParamCount-1 do
begin
// extract parameter name (eg, "/Option=")
currentParamName := Copy(ParamStr(i), 0, pos('=',ParamStr(i)));
// once found, we want the following item
if (startPos > 0) then
begin
endPos := pos(currentParamName,fullCmd)-2; // -1 to move back to actual end position, -1 for space
break; // exit loop
end;
if (CompareText(currentParamName, '/'+ParamName+'=') = 0) then // case-insensitive compare
begin
// found target item, so save its string position
StartPos := pos(currentParamName,fullCmd)+2+Length(ParamName);
end;
end;
if ((fullCmd[StartPos] = fullCmd[EndPos])
and ((fullCmd[StartPos] = '"') or (fullCmd[StartPos] = ''''))) then
begin
// exclude surrounding quotes
Result := Copy(fullCmd, StartPos+1, EndPos-StartPos-1);
end
else
begin
// return as-is
Result := Copy(fullCmd, StartPos, EndPos-StartPos+1);
end;
end;
You can access this with {code:ParamStrWithQuotes|abc}
When invoking the setup.exe, you do have to escape the quotes, so one of the following works for that:
setup.exe /abc="-arg ""C:\path with spaces"" -moreargs"
or
setup.exe /abc='-arg "C:\path with spaces" -moreargs'

Why does ShowMessage give unexpected output when my string contains #0 characters?

Today I stumbled upon something mysterious. This line of code:
showmessage(menuMain.player[2] + ' ready!');
Generates this message (For example menuMain.player[2] = Player):
Player
But if I put code this way (For example menuMain.player[2] = Player):
showmessage('Test: ' + menuMain.player[2]);
It will generate this message:
Test: Player
According to the debugger, the exact value of the string (buffer := menuMain.player[2] + ' ready!') is this:
'Player'#0#0#0#0#0#0#0#0' ready!'
I do honestly believe this is a compiler glitch, because I have the exact same line in another block of code, and it works flawlessly.
Now the tough part for me, is that me being dumb, or this is indeed a glitch?
the exact value of buffer string (buffer = menuMain.player[2] + ' ready!'): 'Player'#0#0#0#0#0#0#0#0' ready!'
The problem is the embedded null characters (the #0). Those characters act as string terminators, which means the string stops being processed at that point for most WinAPI functions, including those that output or paint text. When the first #0 is found, the string is considered to have ended.
You can test this easily enough yourself, with code something like this:
var
TestStr: string;
begin
TestStr := 'This is a test';
ShowMessage(TestStr); // Outputs This is a test
TestStr[5] := #0;
ShowMessage(TestStr); // Outputs This
end;
The remaining question, of course, is how did you end up with those embedded nulls in the first place? Since you've not posted the code that populates menuMain.player, it's impossible to say, but that's that's the area you need to inspect, because string array elements in Delphi do not contain nulls on their own. You can also check that yourself:
var
TestArr: array[1..2] of string;
begin
TestArr[1] := 'Player one';
TestArr[2] := 'Player two';
ShowMessage(TestArr[1] + ' defeated ' + TestArr[2]);
end;
So the answer to your question
Now the tough part for me, is that me being dumb, or this is indeed a glitch?
It's a glitch, but the glitch is somewhere in your code. It's not a glitch in Delphi or its strings.

How to get last string in TStringList

I've been searching for days on how to do this, and nothing is exactly what I need to do (or I just don't understand how to implement the solution).
What I need to do is parse a string, which is a street address, into a TStringList, and then set those strings from the list to variables I can then pass to the rest of the program. I already have the list working ok:
var
AddressList : TStringList;
i : integer;
begin
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr; //RawAddressStr is parsed from another file and the string result is something like '1234 Dark Souls Lane Wyoming, MI 48419'
for i := 0 to AddressList.Count-1 do
AddressList.Strings[i]; //Not sure what to do here
The issue is that the address isn't always the same length. The address will sometimes be like '1234 Dark Souls Two Lane Wyoming...' or '1234 Dark Lane Wyoming...'
So I need to be able to use this TStringList to set the Zip, State and City into variables for later use. I would use TStringList.Find, but the ZIP code isn't always the same. Is there a way to get the last string and then go backwards from there? (Going backwards because once I get the City, State ZIP I can remove that from the RawAddressStr and then set the rest to the address string.)
Thanks.
Edit, here's the code I needed (thanks to below comments):
AddressList := TStringList.Create;
AddressList.Delimiter := ' ';
AddressList.DelimitedText := RawAddressStr;
for i := 0 to AddressList.Count-1 do
LastIndex := AddressList.Count - 1;
ZipStr := AddressList[LastIndex];
StateStr := AddressList[LastIndex - 1];
CityStr := AddressList[LastIndex - 2];
Now I can use these with StringReplace to take out the City, State Zip from the full address string, and set that as the Address string to use.
I am a little bit unsure of exactly what you're looking for since you ask
of how to get the last string of your StringList,
then at the end of the question you ask
Is there a way to get the last string and then go backwards from
there?
If you want to get the last string of a StringList you can use
var AddressList : TStringList;
MyString: string;
begin
MyString := AddressList.Last; //this...
MyString := AddressList.Strings[AddressList.Count-1]; //...and this is essentially the same thing
end;
if you would like to for-loop in reverse or backwards you should write:
for i := AddressList.Count-1 downto 0 do
AddressList.Strings[i]; //Not sure what to do here
Notice that it says "DOWNTO" and not "TO".
Now, if you would like to stop at a specific string, lets say the ZIP code,
you need to make your software understand what it is reading.
Which one of the delimited strings is the City?
Which one is the address?
To the software, a string is a string, it doesn't know, or even care what it is reading.
So I would like to suggest that you have a database of citys which it can compare the strings
of AddressList t,o and see if there is a match.
You could also implement some logic in to your algorithm.
If you know that the last string of your delimited AdressList string always is the City-name,
then you know you have the city name right there which you can use.
If you know that everything between the ZIP code and the City Name is the Street Address,
then just copy everything between the ZIP and the City-name and use that as a Street-name information.

ada split() method

I am trying to write an Ada equivalent to the split() method in Java or C++. I am to intake a string and an integer and output two seperate string values. For example:
split of "hello" and 2 would return:
"The first part is he
and the second part is llo"
The code I have is as follows:
-- split.adb splits an input string about a specified position.
--
-- Input: Astring, a string,
-- Pos, an integer.
-- Precondition: pos is in Astring'Range.
-- Output: The substrings Astring(Astring'First..Pos) and
-- Astring(Pos+1..Astring'Last).
--------------------------------------------------------------
with Ada.Text_IO, Ada.Integer_Text_IO, Ada.Strings.Fixed;
use Ada.Text_IO, Ada.Integer_Text_IO, Ada.Strings.Fixed;
procedure Split is
EMPTY_STRING : String := " ";
Astring, Part1, Part2 : String := EMPTY_STRING;
Pos, Chars_Read : Natural;
------------------------------------------------
-- Split() splits a string in two.
-- Receive: The_String, the string to be split,
-- Position, the split index.
-- PRE: 0 < Position <= The_String.length().
-- (Ada arrays are 1-relative by default)
-- Passback: First_Part - the first substring,
-- Last_Part - the second substring.
------------------------------------------------
function Split(TheString : in String ; Pos : in Integer; Part1 : out String ; Part2 : out String) return String is
begin
Move(TheString(TheString'First .. Pos), Part1);
Move(TheString(Pos .. TheString'Last), Part2);
return Part1, Part2;
end Split;
begin -- Prompt for input
Put("To split a string, enter the string: ");
Get_Line(Astring, Chars_Read);
Put("Enter the split position: ");
Get(Pos);
Split(Astring, Pos, Part1, Part2);
Put("The first part is ");
Put_Line(Part1);
Put(" and the second part is ");
Put_Line(Part2);
end Split;
The main part I am having trouble with is returning the two separate string values and in general the whole split() function. Any pointers or help is appreciated. Thank you
Instead of a function, consider making Split a procedure having two out parameters, as you've shown. Then decide if Pos is the last index of Part1 or the first index of Part2; I've chosen the latter.
procedure Split(
TheString : in String; Pos : in Integer;
Part1 : out String; Part2 : out String) is
begin
Move(TheString(TheString'First .. Pos - 1), Part1);
Move(TheString(Pos .. TheString'Last), Part2);
end Split;
Note that String indexes are Positive:
type String is array(Positive range <>) of Character;
subtype Positive is Integer range 1 .. Integer'Last;
Doing this is so trivial, I'm not sure why you'd bother making a routine for it. Just about any routine you could come up with is going to be much harder to use anyway.
Front_Half : constant String := Original(Original'first..Index);
Back_Half : constant String := Original(Index+1..Original'last);
Done.
Note that static Ada strings are very different than strings in other languages like C or Java. Due to their static nature, they are best built either inline like I've done above, or as return values from functions. Since functions cannot return more than one value, a single unified "split" routine is just plain not a good fit for static Ada string handling. Instead, you should either do what I did above, call the corresponding routines from Ada.Strings.Fixed (Head and Tail), or switch to using Ada.Strings.Unbounded.Unbounded_String instead of String.
The latter is probably the easiest option, if you want to keep your Java mindset about string handling. If you want to really learn Ada though, I'd highly suggest you learn to deal with static fixed Strings the Ada way.
From looking over your code you really need to read up in general on the String type, because you're dragging in a lot of expectations in from other languages on how to work with them--which aren't going to work with them. Ada's String type is not one of its more flexible features, in that they are always fixed length. While there are ways of working around the limitations in a situation such as you're describing, it would be much easier to simply use Unbounded_Strings.
The input String to your function could remain of type String, which will adjust to the length of the string that you provide to it. The two output Unbounded_Strings then are simply set to the sliced string components after invoking To_Unbounded_String() on each of them.
Given the constraints of your main program, with all strings bounded by the size of EMPTY_STRING. the procedure with out parameters is the correct approach, with the out parameter storage allocated by the caller (on the stack as it happens)
That is not always the case, so it is worth knowing another way. The problem is how to deal with data whose size is unknown until runtime.
Some languages can only offer runtime allocation on the heap (via "new" or "malloc") and can only access the data via pointers, leaving a variety of messy problems including accesses off the end of the data (buffer overruns) or releasing the storage correctly (memory leaks, accessing freed pointers etc)
Ada will allow this method too, but it is usually unnecessary and strongly discouraged. Unbounded_String is a wrapper over this method, while Bounded_String avoids heap allocation where you can accept an upper bound on the string length.
But also, Ada allows variable sized data structures to be created on the stack; the technique just involves creating a new stack frame and declaring new variables where you need to, with "declare". The new variables can be initialised with function calls.
Each function can only return one object, but that object's size can be determined at runtime. So either "Split" can be implemented as 2 functions, returning Part1 or Part2, or it can return a record containing both strings. It would be a record with two size discriminants, so I have chosen the simpler option here. The function results are usually built in place (avoids copying).
The flow in your example would require two nested Declare blocks; if "Pos" could be identified first, they could be collapsed into one...
procedure Split is
function StringBefore( Input : String; Pos : Natural) return String is
begin
return Input(1 .. Pos-1);
end StringBefore;
function StringFrom ...
begin
Put("To split a string, enter the string: ");
declare
AString : String := Get_Line;
Pos : Natural;
begin
Put("Enter the split position: ");
Get(Pos);
declare
Part1 : String := StringBefore(AString, Pos);
Part2 : String := StringFrom(AString, Pos);
begin
Put("The first part is ");
Put_Line(Part1);
Put(" and the second part is ");
Put_Line(Part2);
end; -- Part1 and Part2 are now out of scope
end; -- AString is now out of scope
end Split;
This can obviously be wrapped in a loop, with different size strings each time, with no memory management issues.
Look at the Head and Tail functions in Ada.Strings.Fixed.
function Head (Source : in String; Count : in Natural; Pad : in Character := Space) return String;
function Tail (Source : in String; Count : in Natural; Pad : in Character := Space)
return String;
Here's an approach that just uses slices of the string.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Fixed; use Ada.Strings.Fixed;
procedure Main is
str : String := "one,two,three,four,five,six,seven,eight";
pattern : String := ",";
idx, b_idx : Integer;
begin
b_idx := 1;
for i in 1..Ada.Strings.Fixed.Count ( Source => str, Pattern => pattern ) loop
idx := Ada.Strings.Fixed.Index( Source => str(b_idx..str'Last), Pattern => pattern);
Put_Line(str(b_idx..idx-1)); -- process string slice in any way
b_idx := idx + pattern'Length;
end loop;
-- process last string
Put_Line(str(b_idx..str'Last));
end Main;

Resources