When you read ParamStr(), it's deliminated by spaces between each parameter. However, I've seen many command-line arguments which accept a space between the parameter name and its paired value, while also accepting an equals = sign and even no deliminator (just prefixed with the param name) or no value.
Here's some examples of possible param strings:
-name value
/name value
-name=value
/name=value
-namevalue
/namevalue
-name -nextname
/name /nextname
-name="value with spaces"
/name="value with spaces"
...etc.
What I would like to do is two things both related... Check if a parameter name exists, and read the value of the parameter. For example...
if ParamExists('ParamName') then
SomeString:= ParamValue('ParamName')
else
SomeString:= 'SomeOtherString';
Is there something in Delphi which can do this? If not, how do I do this? Everything I find when searching for this just leads me to the same basic example:
for i := 0 to ParamCount do
ShowMessage(ParamStr(i));
It also needs to be case sensitive. I'm looking for something in particular like OSQL and similar command-line tools use where '-s' could be different from '-S'.
The problem is that if I use a space as a deliminator, I have no clue how to recognize when it's part of the previous parameter, because it splits them by spaces. How do I get around this?
I'm sure there's a standard term for this too, it's the common formatting of command-line arguments. But I don't know how to read them properly using just ParamStr. It seems ParamStr falls short of what it's usually used for.
To be clear, I don't necessarily need to support every above example - those are just examples I've seen before.
ParamStr() (and consequently FindCmdLineSwitch()) is not flexible enough to handle all of the examples you have shown. You will have to call the Win32 API GetCommandLine() function and parse it manually.
Ironically, just last night I wrote something for this, and just a little bit ago found this got up-voted. Here's a class I just wrote encapsulating this:
unit CmdLine;
(*
Command Line Parser
by Jerry Dodge
Class: TCmdLine
- Parses out a command line into individual name/value pairs
- Concatenates name/value pairs into a command line string
- Property "ModuleFilename" for the current executable path
- Property "OpenFilename" for the file to be opened, if any
- Default property "Values" to read/write name/value pairs
*)
interface
uses
System.Classes, System.SysUtils;
type
TCmdLine = class(TObject)
private
FItems: TStringList;
FModuleFilename: String;
FOpenFilename: String;
function GetAsString: String;
procedure SetAsString(const Value: String);
procedure SetModuleFilename(const Value: String);
procedure SetOpenFilename(const Value: String);
function GetValue(const Name: String): String;
procedure SetValue(const Name, Value: String);
function GetName(const Index: Integer): String;
public
constructor Create;
destructor Destroy; override;
function Count: Integer;
function Exists(const N: String; const IgnoreCase: Boolean = False): Boolean;
property ModuleFilename: String read FModuleFilename write SetModuleFilename;
property OpenFilename: String read FOpenFilename write SetOpenFilename;
property AsString: String read GetAsString write SetAsString;
property Names[const Index: Integer]: String read GetName;
property Values[const Name: String]: String read GetValue write SetValue; default;
end;
implementation
{ TCmdLine }
constructor TCmdLine.Create;
begin
FItems:= TStringList.Create;
end;
destructor TCmdLine.Destroy;
begin
FItems.Free;
inherited;
end;
function TCmdLine.Count: Integer;
begin
Result:= FItems.Count;
end;
function TCmdLine.Exists(const N: String; const IgnoreCase: Boolean = False): Boolean;
var
X: Integer;
begin
Result:= False;
for X := 0 to FItems.Count-1 do begin
if IgnoreCase then begin
if SameText(N, FItems.Names[X]) then begin
Result:= True;
Break;
end;
end else begin
if N = FItems.Names[X] then begin
Result:= True;
Break;
end;
end;
end;
end;
procedure TCmdLine.SetModuleFilename(const Value: String);
begin
FModuleFilename:= Value;
end;
procedure TCmdLine.SetOpenFilename(const Value: String);
begin
FOpenFilename:= Value;
end;
function TCmdLine.GetValue(const Name: String): String;
begin
Result:= FItems.Values[Name];
end;
procedure TCmdLine.SetValue(const Name, Value: String);
begin
FItems.Values[Name]:= Value;
end;
function TCmdLine.GetAsString: String;
var
X: Integer;
Cmd: String;
Val: String;
begin
Result:= '"'+FModuleFilename+'"';
if Trim(FOpenFilename) <> '' then
Result:= Result + ' "'+FOpenFilename+'"';
for X := 0 to FItems.Count-1 do begin
Cmd:= FItems.Names[X];
Val:= FItems.Values[Cmd];
Result:= Result + ' -'+Cmd;
if Trim(Val) <> '' then begin
Result:= Result + ' ';
if Pos(' ', Val) > 0 then
Result:= Result + '"'+Val+'"'
else
Result:= Result + Val;
end;
end;
end;
function TCmdLine.GetName(const Index: Integer): String;
begin
Result:= FItems.Names[Index];
end;
procedure TCmdLine.SetAsString(const Value: String);
var
Str: String;
Tmp: String;
Cmd: String;
Val: String;
P: Integer;
begin
FItems.Clear;
FModuleFilename:= '';
FOpenFilename:= '';
Str:= Trim(Value) + ' ';
//Extract module filename
P:= Pos('"', Str);
if P = 1 then begin
Delete(Str, 1, 1);
P:= Pos('"', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
FModuleFilename:= Tmp;
end else begin
P:= Pos(' ', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
FModuleFilename:= Tmp;
end;
Str:= Trim(Str) + ' ';
//Extract open filename
P:= Pos('"', Str);
if P = 1 then begin
Delete(Str, 1, 1);
P:= Pos('"', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
FOpenFilename:= Tmp;
end else begin
P:= Pos('-', Str);
if P < 1 then
P:= Pos('/', 'Str');
if P < 1 then begin
P:= Pos(' ', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
FOpenFilename:= Tmp;
end;
end;
Str:= Trim(Str) + ' ';
//Extract remaining param switches/values
while Length(Trim(Str)) > 0 do begin
P:= Pos('-', Str);
if P < 1 then
P:= Pos('/', 'Str');
if P > 0 then begin
Delete(Str, 1, 1);
P:= Pos(' ', Str);
Tmp:= Trim(Copy(Str, 1, P-1));
Delete(Str, 1, P);
if Pos('"', Tmp) = 1 then begin
Delete(Tmp, 1, 1);
P:= Pos('"', Tmp);
if P > 0 then
Delete(Tmp, 1, 1);
end;
Cmd:= Tmp;
Str:= Trim(Str) + ' ';
if (Pos('-', Str) <> 1) and (Pos('/', Str) <> 1) then begin
P:= Pos('"', Str);
if P = 1 then begin
Delete(Str, 1, 1);
P:= Pos('"', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
end else begin
P:= Pos(' ', Str);
Tmp:= Copy(Str, 1, P-1);
Delete(Str, 1, P);
end;
Val:= Tmp;
end else begin
Val:= '';
end;
if Val = '' then
Val:= ' ';
FItems.Values[Cmd]:= Val;
end else begin
Str:= '';
raise Exception.Create('Command line parameters malformed ('+Str+')');
end;
Str:= Trim(Str) + ' ';
end;
end;
end.
I just rewrite all my proposed answer in order to add some value to it, thank you for your comments:
function TForm1.ParamExists(sParamName: String;
bIgnoreCase: boolean; equalchar : string = '='): Boolean;
begin
if bIgnoreCase then
Result := Pos(sParamName + equalChar, cmdLine) > 0
else
Result := AnsiPos(sParamName + equalChar, cmdLine) > 0;
end;
function TForm1.ParamValue(sParamName : String; bIgnoreCase : boolean = false;
equalchar : string = '='; delimiter : string = '"'): String;
var
I : Integer;
scmdLine : String;
begin
Result := '';
scmdLine := cmdLine;
if bIgnoreCase then
I := Pos(sParamName, scmdLine)
else
I := AnsiPos(sParamName, scmdLine);
inc(I, Length(sParamName + equalchar));
delete(scmdLine, 1, I-1);
if pos(delimiter, scmdLine) = 1 then
begin
delete(scmdLine, 1, 1);
Result := copy(scmdLine,1, pos(delimiter, scmdLine) -1);
end
else
Result := copy(scmdLine,1, pos(' ', scmdLine));
end;
Supposing this parameters:
project1.exe -name1 value1 /name2 value2 -name3=value3 /name4=value4 -name5value5 /name6value6 -name7 -name8 /name9 /name91 -name10="Value 10" /name11="Value 11"
Example:
ParamExists('-Name1', False, ' '); --> False
ParamExists('-Name1', True, ' '); --> True, ignore case
ParamExists('-name1', False, ' '); --> True
ParamExists('-name1', False, '='); --> False there are no a = after -name1 parameter
ParamExists('-name6', False, ''); --> True this parameter use the form -parameterValue
ParameterValue example:
ParamValue('-name1', false, ' '); --> value1
ParamValue('/name2', false, ' '); --> value2
ParamValue('-name3'); --> value3, using default values
ParamValue('-name4'); --> value4, using default values
ParamValue('-name5', false, ''); --> value5
ParamValue('/name6', false, ''); --> value6
Notice that parameters -name7, -name8, /name9 and /name91 must be evaluated as ParamExists because there are no a value asigned to them.
ParamValue('-name10', false, '=', '"'); --> Value 10
same as:
ParamValue('-name10'); --> Value 10
ParamValue('/name11'); --> Value 11
I have the same problem I made this function it is simple and very useful
function GetCmdLineValue(CmdLine: string; Arg: string; Switch: Char; Separator: Char): string;
var
ArgIndex: Integer; // Index of Argument
SepIndex: Integer; // Index of Separator between Arg and Value ex "=" or ":"
NextSwitchIndex: Integer; // Index of the next Arg ex "-"
LenghtValue: Integer;
begin
ArgIndex:= CmdLine.IndexOf(Arg);
SepIndex:= CmdLine.IndexOf(Separator, ArgIndex);
NextSwitchIndex:= CmdLine.IndexOf(Switch, ArgIndex+1);
{ No Value found then Exit }
// no separator found
if (SepIndex = -1) or
// the separator is located after the next switch
( SepIndex > NextSwitchIndex) and (NextSwitchIndex > -1) then Exit('');
if NextSwitchIndex = -1 then // No switch after Arg
LenghtValue:= CmdLine.Length - SepIndex+2
else
LenghtValue:= NextSwitchIndex - SepIndex-1;
Result:= Copy(CmdLine, SepIndex+2, LenghtValue).Trim;
end;
Demo
uses:
procedure TForm1.Button1Click(Sender: TObject);
begin
edt_Value.Text:= GetCmdLineValue(edt_CmdLine.Text, edt_Argum.Text, '-', '=');
end;
Related
I was assigned a task for university where I have to write a program which deletes all words with more than 4 letters. I really have no clue at all. I would be very thankful for any kind of help.
VAR
UserString: string; //должна быть строка на 40 символов и точку в конце
i, n: byte;
BEGIN
Writeln('Enter the string:');
Readln(UserString);
i:=0;
n:=1;
repeat //MAIN LOOP:
inc(i);
if (UserString[i] = ' ') or (UserString[i] = '.') then
begin
if (i-n<3)then
begin
delete(UserString, n, i-n+1);
i:=n-1;
end;
n:=i+1
end
until (UserString[i] = '.') or (i>length(UserString));
Writeln('Result String: ', UserString);
END.
I tried this. and its working on onlinegdb but not on Delphi... and I don't know why...
You should break up the logic into smaller utility functions for each task you need (finding a word, getting the word's length, deleting the word and any subsequent whitespace, etc). It will make the code easier to read and maintain.
For example:
function FindNextWordStart(const S: string; var Index: Integer): Boolean;
var
Len: Integer;
begin
Len := Length(S);
while (Index <= Len) and (Ord(S[Index]) <= 32) do Inc(Index);
Result := (Index <= Len);
end;
function GetWordLength(const S: string; Index: Integer): Integer;
var
StartIdx, Len: Integer;
begin
Len := Length(S);
StartIdx := Index;
while (Index <= Len) and (Ord(S[Index]) > 32) do Inc(Index);
Result := (Index - StartIdx);
end;
procedure DeleteWord(var S: String; Index, WordLen: Integer);
var
StartIdx, Len: Integer;
begin
Len := Length(S);
StartIdx := Index;
Inc(Index, WordLen);
while (Index <= Len) and (Ord(S[Index]) <= 32) do Inc(Index);
Delete(S, StartIdx, Index - StartIdx);
end;
var
UserString: string;
StartIdx, WordLen: Integer;
begin
Writeln('Enter the string:');
Readln(UserString);
StartIdx := 1;
while FindNextWordStart(UserString, StartIdx) do
begin
WordLen := GetWordLength(UserString, StartIdx);
if WordLen > 4 then
DeleteWord(UserString, StartIdx, WordLen)
else
Inc(StartIdx, WordLen);
end;
Writeln('Result String: ', UserString);
end.
Online Demo
I guess you can solve your task with TStringlist class:
uses Classes;
......
var AStrLst : TStringlist ;
i : Integer ,
begin
AStrLst := TStringlist.Create ;
try
// use this char for separation of words
AStrLst.Delimiter :=' ';
AStrLst.DelimitedText := ' here comes my sample string ';
for I := AStrLst.Count-1 to 0 do
begin
// delete item from list if ...
if length( trim(AStrLst[i])) <= 4 then AStrLst.Delete(i);
end;
finally
// get the complete
writeln ( AStrLst.Text ) ;
AStrLst.Free;
end;
end;
I did not test this code - but hope it helps - to get this code running, your home work
I have this string:
Called=%2B12608883475&ToState=IN&CallerCountry=US&Direction=inbound&CallerState=IN&ToZip=&CallSid=CAb7faaa30058c2347a595630f2475113a&To=%2B12608883475&CallerZip=
I would like to parse it by & to get all the parameters out and then sort the parameters in an alphabetical case-sensitive order.
All of this has to be done in Delphi 5:
Function TwilioSignatureValidate(twilioSignature: string; QueryString: string ; Url: string): boolean;
begin
parameters := QueryString;
List := TStringList.Create;
end;
Here is the C# version that I want to copy:
string input = "Called=%2B12608883475&ToState=IN&CallerCountry=US&Direction=inbound&CallerState=IN&ToZip=&CallSid=CAb7faaa30058c2347a595630f2475113a&To=%2B12608883475&CallerZip=";
Dictionary<string,string> keyValuePairs = input.Split('&')
.Select(value => value.Split('='))
.ToDictionary(pair => pair[0], pair => pair[1]);
string studentId = keyValuePairs["StudentId"];
Using List.Sort;
I got my Delphi List To sort alphebetically but its not in using Unix-style case-sensitive sorting order:
//Delphi Sort
https://ffbc1360.ngrok.io
AccountSidACc6d06b4cb61ccbfa61bf461957a5a626
ApiVersion2010-04-01
Called+12608883475
CalledCity
CalledCountryUS
CalledStateIN
CalledZip
Caller+12602060582
CallerCity
CallerCountryUS
CallerStateIN
CallerZip
CallSidCAb7faaa30058c2347a595630f2475113a
CallStatusringing
Directioninbound
From+12602060582
FromCity
FromCountryUS
FromStateIN
FromZip
To+12608883475
ToCity
ToCountryUS
ToStateIN
ToZip
//Correct c# Sort
https://ffbc1360.ngrok.io
AccountSidACc6d06b4cb61ccbfa61bf461957a5a626
ApiVersion2010-04-01
CallSidCAb7faaa30058c2347a595630f2475113a
CallStatusringing
Called+12608883475
CalledCity
CalledCountryUS
CalledStateIN
CalledZip
Caller+12602060582
CallerCity
CallerCountry
USCaller
StateIN
CallerZip
Directioninbound
From+12602060582
FromCity
FromCountryUS
FromStateIN
FromZip
To+12608883475
ToCity
ToCountryUS
ToStateIN
ToZip
TStringList in Delphi 6 and later has Delimiter, DelimitedText, and CaseSensitive properties. Set the Delimiter to '&' and then assign the query string to DelimitedText, then you can use the Values[] property to extract values by name (since the resulting delimited strings are already in name=value format). No sorting is needed:
Function TwilioSignatureValidate(twilioSignature: string; QueryString: string ; Url: string): boolean;
var
List: TStringList;
begin
List := TStringList.Create;
try
List.Delimiter := '&';
List.DelimitedText := QueryString;
List.CaseSensitive := True;
// use List.Values['param name'] as needed, eg:
// List.Values['Called']
// List.Values['ToState']
// List.Values['CallerCountry']
// List.Values['Direction']
// List.Values['CallerState']
// List.Values['ToZip']
// List.Values['CallSid']
// List.Values['To']
// List.Values['CallerZip']
finally
List.Free;
end;
end;
However, in Delphi 5, none of those properties existed yet, so you will have to do everything manually:
Function TwilioSignatureValidate(twilioSignature: string; QueryString: string ; Url: string): boolean;
var
List: TStringList;
// the following are tweaked from TStrings in later Delphi versions...
procedure MySetDelimitedText(const Value: string);
var
P, P1: PChar;
S: string;
begin
List.BeginUpdate;
try
List.Clear;
P := PChar(Value);
while P^ <> #0 do
begin
if P^ = '"' then
S := AnsiExtractQuotedStr(P, '"')
else
begin
P1 := P;
while (P^ <> #0) and (P^ <> '&') do
P := CharNext(P);
SetString(S, P1, P - P1);
end;
List.Add(S);
if P^ = '&' then
begin
P := CharNext(P);
if P^ = #0 then
List.Add('');
end;
end;
finally
List.EndUpdate;
end;
end;
function MyIndexOfName(const Name: string): Integer;
var
P: Integer;
S: string;
begin
for Result := 0 to List.Count - 1 do
begin
S := List.Strings[Result];
P := AnsiPos('=', S);
if (P <> 0) and (AnsiCompareStr(Copy(S, 1, P - 1), Name) = 0) then Exit;
end;
Result := -1;
end;
function MyGetValue(const Name: string): string;
var
I: Integer;
begin
I := MyIndexOfName(Name);
if I >= 0 then
Result := Copy(List.Strings[I], Length(Name) + 2, MaxInt) else
Result := '';
end;
begin
List := TStringList.Create;
try
MySetDelimitedText(QueryString);
// use MyGetValue('param name') as needed, eg:
// MyGetValue('Called')
// MyGetValue('ToState')
// MyGetValue('CallerCountry')
// MyGetValue('Direction')
// MyGetValue('CallerState')
// MyGetValue('ToZip')
// MyGetValue('CallSid')
// MyGetValue('To')
// MyGetValue('CallerZip')
finally
List.Free;
end;
end;
I'm trying to make a function that can increment a filename.
If last char of the string is a number then increment it.
If last char is a letter then add _1 or _2 or _3(increment this also).
I have to be sure the filename is unique but i cannot use datetime inside filename because all the filenames must be <32 chars without extension.
EX: Apple_99.txt =>Apple_100
Ex: Apple_173 => Apple_174
EX: This_is_my_first_text.txt => This_is_my_first_text_1.txt
Ex: This_is_my_first_text_9.txt => This_is_my_first_text_10.txt
I need to use this in order to rename a file an then upload it to a ftp server.
I've found a function that can do something like this but it only works if the filename contains only uppercase.How can I modify this function in order to access lowercase an uppercase string?
Here is the function:
function IncStr(Str: String; Amount: Integer; Index: Integer = -1): String;
const
MIN_VAL = 65; // 'A'
MAX_VAL = 90; // 'Z'
var
Digit, ToAdd, ToCarry: Integer;
begin
if (Index = 0) and (Amount > 0) then
begin
Result := Char(MIN_VAL + Amount - 1) + Str;
Exit;
end;
if Index = -1 then Index := Length(Str);
ToCarry := 0;
Digit := Ord(Str[Index]);
while not (Digit in [MIN_VAL..MAX_VAL]) do
begin
Dec(Index);
Digit := Ord(Str[Index]);
end;
ToAdd := Digit + Amount;
while (ToAdd > MAX_VAL) do
begin
Dec(ToAdd, 26);
Inc(ToCarry);
end;
Result := Str;
Result[Index] := Char(ToAdd);
if (ToCarry > 0) then
Result := IncStr(Result, ToCarry, Index - 1);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
S: String; // holds string to increment
C: Integer; // amount to increment by
begin
// make sure that Edit1 starts with a valid character
// i.e. 'A' to 'Z'
S := Edit1.Text;
C := StrtoIntDef(Edit2.Text, 0);
// test it, place result in Edit3
Edit3.Text := IncStr(S, C);
{
Example data:
Edit1 := AAZ
Edit2 := 2
= Edit3 := ABB
Edit1 := BZY
Edit2 := 3
= Edit3 := CAB
Edit1 := ZZZ
Edit2 := 1
= Edit3 := AAAA
Edit1 := AA-AC
Edit2 := 3
= Edit3 := AA-AF
Edit1 := AA/Z
Edit2 := 5
= Edit3 := AB/E
... etc
Here's one to try too :-)
Edit1 := ZZZ
Edit2 := 264172
}
end;
Thank you!
Like so many programming problems, the key is to break the problem down into small pieces. First of all, let's write a function to decode the original file name into its constituent parts:
procedure DecodeFileName(const Input: string; out Stem, Ext: string; out Number: Integer);
var
P: Integer;
begin
Ext := TPath.GetExtension(Input);
Stem := TPath.GetFileNameWithoutExtension(Input);
Number := 0;
P := Stem.LastIndexOf('_');
if P = -1 then begin
exit;
end;
if TryStrToInt(Stem.Substring(P+1), Number) then begin
Stem := Stem.Substring(0, P);
end;
end;
The following demonstrates how this works:
DecodeFileName('test.txt', Stem, Ext, Number);
Writeln(Stem, ', ', Number, ', ', Ext);
DecodeFileName('test_dd.txt', Stem, Ext, Number);
Writeln(Stem, ', ', Number, ', ', Ext);
DecodeFileName('test_23.txt', Stem, Ext, Number);
Writeln(Stem, ', ', Number, ', ', Ext);
The output is:
test, 0, .txt
test_dd, 0, .txt
test, 23, .txt
So now you can make a new filename like this:
function IncrementedFileName(const FileName: string): string;
var
Stem, Ext: string;
Number: Integer;
begin
DecodeFileName(FileName, Stem, Ext, Number);
Result := Format('%s_%d%s', [Stem, Number+1, Ext]);
end;
And then we can see how that performs:
Writeln(IncrementedFileName('test.txt'));
Writeln(IncrementedFileName('test_dd.txt'));
Writeln(IncrementedFileName('test_23.txt'));
Writeln(IncrementedFileName('test_28'));
The output is:
test_1.txt
test_dd_1.txt
test_24.txt
test_29
If you don't have access to the string helper methods then you can code it like this:
procedure DecodeFileName(const Input: string; out Stem, Ext: string; out Number: Integer);
var
P: Integer;
begin
Ext := TPath.GetExtension(Input);
Stem := TPath.GetFileNameWithoutExtension(Input);
Number := 0;
P := LastDelimiter('_', Stem);
if P = 0 then begin
exit;
end;
if TryStrToInt(Copy(Stem, P+1, MaxInt), Number) then begin
Stem := Copy(Stem, 1, P-1);
end;
end;
I have not executed this final function, so do not be surprised if it has errors.
i want to get value from two file .txt, one file contain different dimension matrix with other
i have try this code:
procedure TfrmJST.ParseDelimited(const S1: TStrings; const Value: String; const Delimiter: String);
var
dx,cx: integer;
ns,ms: String;
txt: string;
delta,teta: integer;
procedure TfrmJST.ParseDelimited(const S1: TStrings; const Value: String; const Delimiter: String);
var
dx,cx: integer;
ns,ms: String;
txt: string;
delta,teta: integer;
begin
Col := 1;
Delta := Length(Delimiter);
Txt := Value+Delimiter;;
begin
while Length(Txt) > 1 do
begin
Dx := Pos(Delimiter, Txt);
Ns := Trim(Copy(Txt, 1, Dx-1));
// S1.Add('#'+Ns+'*'); //only needed for testing
if Ns <> '' then
begin
Matrix[Row,Col] := StrToFloat(Ns); //for first matrix
Inc(Col);
end;
Txt := Copy(Txt, Dx+Delta, MaxInt);
end;
end;
Col := 1;
teta := Length(delimiter);
txt := value+delimiter;
begin
while Length(txt) > 1 do
begin
cx := Pos(delimiter, txt);
ms := Copy(txt, 1, cx-1);
if ms <> '' then
begin
ref[Row,Col] := StrToFloat(ms); ///for 2nd matrix
Inc(Col);
end;
txt := Copy(txt, cx+teta, MaxInt);
end;
end;
end;
and this is initialize of matrix:
private
{ Private declarations }
Row, Col: integer;
Matrix: array[1..140,1..141] of double;
Ref: array[1..2,1..140] of double ;
this is the implementation:
begin
Temp := TStringList.Create;
MemoSL:= TStringList.Create ;
Temp.LoadFromFile('trainer.txt');
Row := 1;
for I := 0 to Temp.Count-1 do
begin
ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
Inc(Row); //stackoverflow error in this line
end;
Temp.Free;
//parsing second matrix
TempList := TStringList.Create;
Templist.LoadFromFile('refbaru.txt');
row := 1;
for J := 0 to Templist.Count-1 do
begin
T := Templist[J];
ParseDelimited(Memo1.Lines, T, ' ');
Inc(row);
end;
Templist.Free;
i tried that code but give me error,
the error was stackoverflow error in line 'inc(row)' that process first matrix.
and while i gave comment out at the second function that process 2nd matrix, Temp[i] only returns 2 rows of matrix[140x141]. does it mean the code can't process two different file? and why it only return two rows of the matrix?
anyone can help me?
while Length(Txt) > 1 do
begin
Dx := Pos(Delimiter, Txt);
Ns := Trim(Copy(Txt, 1, Dx-1));
// S1.Add('#'+Ns+'*'); //only needed for testing
if Ns <> '' then
begin
Matrix[Row,Col] := StrToFloat(Ns); //for first matrix
Inc(Col);
end;
Txt := Copy(Txt, Dx+Delta, MaxInt);
end;
Looking at this piece of code I see the posibility of an endless loop: what happens if there is no Delimiter found? It will keep running and forever increase your 'col' value. Make sure to have a condition to stop your while loop if no delimeter is found.
It is pointless to look for a specific stack overflow error when many ordinary errors already exist.
If your code is clean programmed and it is still stack overflow, then of course, is time to look deeper into the code.
But first ! As long as you can see obvious errors, you should remove them.
1.) "Row" used in the same procedure on a 140 dimension array and on a only 2 dimension array.
How can that work ?
Matrix: array[1..140,1..141] of double;
Ref: array[1..2,1..140] of double ;
File 'trainer.txt' 140 Lines
File 'refbaru.txt' 2 Lines.
for I := 0 to Temp.Count-1 do // 140 lines
// ParseDelimited() will only run properly if Row < 3
// remember -> Ref: array[1..2,1..140])
// if Row > 2 , with Ref[Row,Col] := , 137 times data is overwritten.
procedure ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
....
Matrix[Row,Col] := StrToFloat(Ns);
....
Ref[Row,Col] := StrToFloat(ms);
....
end;
Inc(Row);
end;
2.) If you run the second loop with refbaru.txt and the two arrays are present together in the procedure ParseDelimited(), then you overwrite 2 values of array Matrix
recommendation
make sure: Loop through trainer.txt, writes values only to the Matrix array.
make sure: Loop through refbaru.txt, writes values only to the Ref array.
Your code could look something like:
[...]
filetoload: String;
[...]
procedure TfrmJST.ParseDelimited(S1: TStrings; Value: String; const Delimiter: String);
var
f:double;
[...]
Col := 1;
txt := Value+Delimiter;
[...]
if filetoload='trainer.txt' then begin
Delta := Length(Delimiter);
while Length(txt) > 1 do
begin
Dx := Pos(Delimiter, txt);
Ns := Trim(Copy(txt, 1, Dx-1));
if Ns <> '' then
begin
if TryStrToFloat(Ns,f) then Matrix[Row,Col]:=f;
Inc(Col);
if Col > MatrixColMax then break;
txt := Copy(txt, Dx+Delta, MaxInt);
end else txt:='';
end;
end;
if filetoload='refbaru.txt' then begin
teta := Length(delimiter);
while Length(txt) > 1 do
begin
cx := Pos(delimiter, txt);
ms := Copy(txt, 1, cx-1);
if ms <> '' then
begin
if TryStrToFloat(ms,f) then Ref[Row,Col]:=f;
Inc(Col);
if Col > RefColMax then break;
txt := Copy(txt, cx+teta, MaxInt);
end else txt:='';
end;
end;
begin
[...]
filetoload:='trainer.txt';
Temp := TStringList.Create;
Temp.LoadFromFile(filetoload);
if Temp.Count > MatrixRowMax then LinesToLoad:=MatrixRowMax-1 else
LinesToLoad:=Temp.Count-1;
for I := 0 to LinesToLoad do
[...]
ParseDelimited(MemoSL, Trim(Temp.Strings[I]), ' ');
[...]
end;
filetoload:='refbaru.txt';
TempList := TStringList.Create;
TempList.LoadFromFile(filetoload);
if TempList.Count > RefRowMax then LinesToLoad:=RefRowMax-1 else
LinesToLoad:=TempList.Count-1;
for J := 0 to LinesToLoad do
[...]
ParseDelimited(Memo1.Lines, T, ' ');
[...]
end;
end;
You should also compare the linesize of the file with the size of the arrays
RefRowMax: integer;
RefColMax: integer;
MatrixRowMax: integer;
MatrixColMax: integer;
LinesToLoad: integer;
....
RefRowMax:=2;
RefColMax:=140;
MatrixRowMax:=140;
MatrixColMax:=141;
....
procedure ParseDelimited()
if filetoload='trainer.txt' then begin
[...]
Inc(Col)
if Col > MatrixColMax then break;
end;
if filetoload='refbaru.txt' then begin
[...]
Inc(Col)
if Col > RefColMax then break;
end;
You should also look for a valid value of Ns , StrToFloat(Ns) before you write to the arrays in ParseDelimited()
function TryStrToFloat(const S: string; out Value: Double): Boolean;
or
Val();
var
f:double;
....
begin
....
if TryStrToFloat(Ns,f) then Matrix[Row,Col]:=f;
....
The OP overwritting many of used data.
And when he has enough data overwritten, he gets a stack overflow error.
Is there a built-in Delphi function which would convert a string such as '3,232.00' to float? StrToFloat raises an exception because of the comma. Or is the only way to strip out the comma first and then do StrToFloat?
Thanks.
Do you exactly know, that '.' is decimal separator and ',' is thousand separator (always)?
If so, then you should fill the TFormatSettings record and pass it to StrToFloat.
FillChar(FS, SizeOf(FS), 0);
... // filling other fields
FS.ThousandSeparator := ',';
FS.DecimalSeparator := '.';
V := StrToFloat(S, FS);
below is what i use. there might be more efficient ways, but this works for me. in short, no, i don't know of any built-in delphi function that will convert a string-float containing commas to a float
{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
safeFloat
Strips many bad characters from a string and returns it as a double.
}
function safeFloat(sStringFloat : AnsiString) : double;
var
dReturn : double;
begin
sStringFloat := stringReplace(sStringFloat, '%', '', [rfIgnoreCase, rfReplaceAll]);
sStringFloat := stringReplace(sStringFloat, '$', '', [rfIgnoreCase, rfReplaceAll]);
sStringFloat := stringReplace(sStringFloat, ' ', '', [rfIgnoreCase, rfReplaceAll]);
sStringFloat := stringReplace(sStringFloat, ',', '', [rfIgnoreCase, rfReplaceAll]);
try
dReturn := strToFloat(sStringFloat);
except
dReturn := 0;
end;
result := dReturn;
end;
function StrToFloat_Universal( pText : string ): Extended;
const
EUROPEAN_ST = ',';
AMERICAN_ST = '.';
var
lformatSettings : TFormatSettings;
lFinalValue : string;
lAmStDecimalPos : integer;
lIndx : Byte;
lIsAmerican : Boolean;
lIsEuropean : Boolean;
begin
lIsAmerican := False;
lIsEuropean := False;
for lIndx := Length( pText ) - 1 downto 0 do
begin
if ( pText[ lIndx ] = AMERICAN_ST ) then
begin
lIsAmerican := True;
pText := StringReplace( pText, ',', '', [ rfIgnoreCase, rfReplaceAll ]); //get rid of thousand incidental separators
Break;
end;
if ( pText[ lIndx ] = EUROPEAN_ST ) then
begin
lIsEuropean := True;
pText := StringReplace( pText, '.', '', [ rfIgnoreCase, rfReplaceAll ]); //get rid of thousand incidental separators
Break;
end;
end;
GetLocaleFormatSettings( LOCALE_SYSTEM_DEFAULT, lformatSettings );
if ( lformatSettings.DecimalSeparator = EUROPEAN_ST ) then
begin
if lIsAmerican then
begin
lFinalValue := StringReplace( pText, '.', ',', [ rfIgnoreCase, rfReplaceAll ] );
end;
end;
if ( lformatSettings.DecimalSeparator = AMERICAN_ST ) then
begin
if lIsEuropean then
begin
lFinalValue := StringReplace( pText, ',', '.', [ rfIgnoreCase, rfReplaceAll ] );
end;
end;
pText := lFinalValue;
Result := StrToFloat( pText, lformatSettings );
end;
Try: StrToFloat(StringReplace('3,232.00', ',', '')
It should get rid of the commas before doing the conversion.
In C# / VB.NET I use would use something like decimal.convert("3,232.00", ",", "");
I know of no way to do the conversion without stripping out the extra characters. In fact, I have a special function in my library that strips out commas and currency symbols. So a actually call MyConverer.decimalConverter("$3,232.00");
I use a function which is able to handle the ',' and the '.' as decimalseparator...:
function ConvertToFloat(aNr: String; aDefault:Integer): Extended;
var
sNr, s3R, sWhole, sCent:String;
eRC:Extended;
begin
sNr:=ReplaceStr(sNr, ' ', '');
if (Pos('.', sNr) > 0) or (Pos(',', sNr) > 0) then
begin
// Get 3rd character from right
s3R:=LeftStr(RightStr(sNr, 3), 1);
if s3R <> DecimalSeparator then
begin
if not IsNumber(s3R) then
begin
s3R := DecimalSeparator;
sWhole := LeftSr(sNr, Length(sNr) - 3);
sCent := (RightStr(sNr, 2);
sNr := sWhole + DecimalSeparator + sCent;
end
else
// there are no decimals... add ',00'
sNr:=sNr + DecimalSeparator + '00';
end;
// DecimalSeparator is present; get rid of other symbols
if (DecimalSeparator = '.') and (Pos(',', sNr) > 0) then sNr:=ReplaceStr(sNr, ',', '');
if (DecimalSeparator = ',') and (Pos('.', sNr) > 0) then sNr:=ReplaceStr(sNr, '.', '');
end;
eRc := StrToFloat(sNr);
end;
I had the same problem when my Users need to enter 'scientific' values such as "1,234.06mV". Here there is a comma, a multiplier (m=x0.001) and a unit (V). I created a 'wide' format converter routine to handle these situtations.
Brian
Myfunction:
function StrIsFloat2 (S: string; out Res: Extended): Boolean;
var
I, PosDecimal: Integer;
Ch: Char;
STrunc: string;
liDots, liComma, J: Byte;
begin
Result := False;
if S = ''
then Exit;
liDots := 0;
liComma := 0;
for I := 1 to Length(S) do begin
Ch := S[I];
if Ch = FormatSettings.DecimalSeparator then begin
Inc (liDots);
if liDots > 1 then begin
Exit;
end;
end
else if (Ch = '-') and (I > 1) then begin
Exit;
end
else if Ch = FormatSettings.ThousandSeparator then begin
Inc (liComma);
end
else if not CharIsCipher(Ch) then begin
Exit;
end;
end;
if liComma > 0 then begin
PosDecimal := Pos (FormatSettings.DecimalSeparator, S);
if PosDecimal = 0 then
STrunc := S
else
STrunc := Copy (S, 1, PosDecimal-1);
if STrunc[1] = '-' then
Delete (S, 1, 1);
if Length(STrunc) < ((liComma * 3) + 2) then
Exit;
J := 0;
for I := Length(STrunc) downto 1 do begin
Inc(J);
if J mod 4 = 0 then
if STrunc[I] <> FormatSettings.ThousandSeparator then
Exit;
end;
S := ReplaceStr (S, FormatSettings.ThousandSeparator, '');
end;
try
Res := StrToFloat (S);
Result := True;
except
Result := False;
end;
end;
Using Foreach loop
public static float[] ToFloatArray()
{
string pcords="200.812, 551.154, 232.145, 482.318, 272.497, 511.752";
float[] spiltfloat = new float[pcords.Split(',').Length];
int i = 0;
foreach (string s in pcords.Split(','))
{
spiltfloat[i] = (float)(Convert.ToDouble(s));
i++;
}
return spiltfloat;
}
using lemda Expression to convert string comma seprated to float array
public static float[] ToFloatArrayUsingLemda()
{
string pcords="200.812, 551.154, 232.145, 482.318, 272.497, 511.752";
float[] spiltfloat = new float[pcords.Split(',').Length];
string[] str = pcords.Split(',').Select(x => x.Trim()).ToArray();
spiltfloat = Array.ConvertAll(str, float.Parse);
return spiltfloat;
}
procedure Edit1Exit(Sender: TObject);
begin
edit1.Text:=stringreplace(edit1.Text,'''','',[rfReplaceAll]);
if not IsValidDecimal( maskedit1.Text ) then
begin
showmessage('The Decimal entered -> '+edit1.Text+' <- is in the wrong format ');
edit1.SetFocus;
end;
end;
function IsValidDecimal(S:string):boolean;
VAR
FS: TFormatSettings;
DC: variant;
begin
//FS := TFormatSettings.Create('it-IT');
FS := TFormatSettings.Create('en-EN');
try
DC:=StrToFloat ( S, FS );
result:=true;
except
on e:exception do
result:=false;
end;
end;