0

I want to create a function that receive multiples strings as parameters. Like the function printf("Hello %s",name); of C. but I don't want to pass a ready array, it wouldn't be readable.

Edit1.text:=lang('Hello');

Edit2.text:=lang('Welcome to {1} guest',place);

Edit3.text:=lang('Hi {1}, is your {2} time in {3}','Victor','first','Disney');

output should be:

Hello
Welcome to Disney guest
Hi Victor is your first time in Disney

how I create the function TForm1.lang(parameters:String):String;, I did a research, but I can't get it work.

I need to access the parameters[] and the parameters.length also.

I'm needing this to turn my App to multilang.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
Vitim.us
  • 20,746
  • 15
  • 92
  • 109
  • possible duplicate of [How can a function with 'varargs' retrieve the contents of the stack?](http://stackoverflow.com/questions/298373/how-can-a-function-with-varargs-retrieve-the-contents-of-the-stack) (See the accepted answer) – Seth Carnegie Nov 26 '11 at 20:40
  • 2
    You do know Delphi has built-in support for internationalization? It may not be the absolute best, but it's certainly better than rolling your own from scratch. See the help file, and search for "international applications" for a tutorial on using it. – Ken White Nov 26 '11 at 20:54

6 Answers6

4

Here's an example function of how you can do this:

function TForm1.lang(s: String; params: array of String): String;
var
  i: Integer;
begin
  for i := 0 to High(params) do
  begin
    ShowMessage(params[i]);
  end;
end;

Call it like this:

lang('My format string', ['this', 'that']);

or like this:

var
  b: String;
begin
  b := 'this';
  lang('My format string', [b, 'that']);
end;
Marcus Adams
  • 53,009
  • 9
  • 91
  • 143
  • that seems ok to me. But I can make the second parameter optional? – Vitim.us Nov 26 '11 at 21:23
  • Q: Do you mean "can I eliminate passing the array"? A: No. Because of the "Pascal calling convention" mentioned above. – paulsm4 Nov 26 '11 at 21:28
  • 1
    Vitimtk, you can pass as many array elements as you want. Use `[]` to pass in an empty array. With an empty array, `High(params) = -1`. – Marcus Adams Nov 26 '11 at 21:28
  • PS: Delphi Lists are easy; Delphi variable arrays are easy. Both are readable. If in doubt, compare them with C++ STL containers ;) – paulsm4 Nov 26 '11 at 21:31
  • I think I will need just the array parameter like @TonyHopkinson answer. Thank you! – Vitim.us Nov 26 '11 at 21:38
  • Vitimtk, I don't see how Tony's answer is different than mine except I provide more examples and include the format string that you will require. Good luck. – Marcus Adams Nov 26 '11 at 23:31
  • You can always have a second overloaded version of your procedure that does not have the array parameter. – dummzeuch Nov 27 '11 at 08:20
  • I used this same pattern, I overloaded to make a version without with only the firs parameter also. works fine, Thank you! – Vitim.us Nov 27 '11 at 18:54
3

Not sure what you mean by not readable

DoSomething(['Param1','Param2']);

for

procedure DoSomething(args : Array of String);
Var
  Index : Integer;
Begin
  for index := Low(args) to High(args) Do
    ShowMessage(args[Index]);
End;

Seems okay to me. Course if you want to call it from outside delphi then you have an issue.

Quick fix is just to pass in a delimited string and then user TStringList to split it.

You could write a wee function to do that, don't forget to free it when you are done.

Tony Hopkinson
  • 20,172
  • 3
  • 31
  • 39
1

All your three examples could be fixed by using SysUtils.Format:

Edit1.text := format('%s',['Hello']));
Edit1.text := format('Welcome to %s guest',[place]));
Edit1.text := format('Hi %s, is your %s time in %s',['Victor','first','Disney']));

Personally I think it's quite readable. If you can have what you need from a basic sysutils function, you should seriously consider doing that, rather than to write your own version. On the other hand, you may need more complex functionality that doesn't show in your question. If that's the case, I think paulsm4's suggestion of using a stringlist seems like a good way to go.

Svein Bringsli
  • 5,640
  • 7
  • 41
  • 73
  • I didn't know this function, but I want to write my own because, I don't just replace the text to respective %s, but also select the corret language, `lang('Welcome {1}','Svein')` can return `Ben-venido Svein` if your system is in spanish. don't know if you got it... – Vitim.us Nov 26 '11 at 23:39
1

Delphi does not support CREATING functions withvararg-style parameters that work exactly like printf() does. It only supports CONSUMING such functions from external libraries. The closest Delphi comes to supporting the creation of functions with variable parameter lists is to use "open array" parameters, like what SysUtils.Format() uses.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

As Tony mentions above, I also recommend using a deliminated string. Except, a little more than just deliminating, but using more of a parsing technique. If I understand right, this function you're making for formatting shall NOT include an array in the parameters, but technically, that doesn't mean we can't use arrays anywhere at all (arrays are very ideal to use for this scenario for fast performance).

This method will allow virtually anything to be passed in the parameters, including the deliminator, without affecting the output. The idea is to do A) Size of parameter string, B) Deliminator between size and parameter, and C) parameter string... And repeat...

const
  MY_DELIM = '|';  //Define a deliminator

type
  TStringArray = array of String;

/////////////////////////////////

//Convert an array of string to a single parsable string
//  (Will be the first step before calling your format function)
function MakeParams(const Params: array of String): String;
var
  X: Integer;
  S: String;
begin
  Result:= '';
  for X:= 0 to Length(Params)-1 do begin
    S:= Params[X];
    Result:= Result + IntToStr(Length(S)) + MY_DELIM + S;
  end;
end;

//Convert a single parsable string to an array of string
//  (Will be called inside your format function to decode)
//  This is more or less called parsing
function ExtractParams(const Params: String): TStringArray;
var
  S: String;  //Used for temporary parsing
  T: String;  //Used for copying temporary data from string
  P: Integer; //Used for finding positions
  C: Integer; //Used for keeping track of param count
  Z: Integer; //Used for keeping track of parameter sizes
begin
  S:= Params;                   //Because we'll be using 'Delete' command
  C:= 0;                        //Set count to 0 to start
  SetLength(Result, 0);         //Prepare result array to 0 parameters
  while Length(S) > 0 do begin  //Do loop until nothing's left
    P:= Pos(MY_DELIM, S);       //Get position of next deliminator
    if P > 1 then begin         //If deliminator was found...       
      C:= C + 1;                  //We have a new parameter
      SetLength(Result, C);       //Set array length to new parameter count
      T:= Copy(S, 1, P-1);        //Get all text up to where deliminator was found
      Delete(S, 1, P);            //Delete what we just copied, including deliminator
      Z:= StrToIntDef(T, 0);      //Convert T:String to Z: Integer for size of parameter
      T:= Copy(S, 1, Z);          //Get all text up to 'Z' (size of parameter)
      Delete(S, 1, Z);            //Delete what we just copied
      Result[C-1]:= T;              //Assign the new parameter to end of array result
    end else begin              //If deliminator was NOT found...
      S:= '';                     //Clear S to exit loop (possible bad format if this happens)
    end;
  end;
end;

//Main formatting routine
function MyFormat(const Input: String; const Params: String): String;
var
  A: TStringArray;
  X: Integer;
  S: String;
  P: Integer;
  R: String;
begin
  R:= Input;
  A:= ExtractParams(Params);
  //At this point, A contains all the parameters parsed from 'Params'
  for X:= 0 to Length(A)-1 do begin
    S:= A[X];
    P:= Pos('%s', R);
    if P > 0 then begin
      Delete(R, P, 2);
      Insert(S, R, P);
    end;
  end;
  Result:= R;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Pars: String;
begin
  Pars:= MakeParams(['this', 'that', 'something else']);
  Edit1.Text:= MyFormat('%s is %s but not %s', Pars);
end;
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • I used this approach when designing a custom server/client packet system. I started with the raw TServerSocket and TClientSocket and built around them - using the technique above, it becomes 20 times easier to send/receive commands through these sockets. Method can also be re-used for many other things, for example, if you want to save an array of strings to a text file. – Jerry Dodge Nov 27 '11 at 17:20
  • Not sure why you got them either. Looks like a creditable start to me. – Tony Hopkinson Nov 27 '11 at 23:26
-1

As you probably know, SysUtils.Format() implements "varargs" by using a set.

In your case, however, why not just pass a TStringList? The function will simply check "list.Count". Voila - you're done!

paulsm4
  • 114,292
  • 17
  • 138
  • 190
  • PS: The reason C varargs work they way they do is because, in the "C calling convention", the CALLER cleans up the stack. The CALLER knows to pop 5 variables here, and 10 variables there. Delphi uses the "Pascal calling convention", where the CALLEE cleans up the stack. This is slightly more efficient, but it means that subroutines must accept a fixed #/arguments. So you're left with passing some kind of "list" (an array, a list, a set - whatever). But "one thing" as "one argument". 'Hope that helps ... – paulsm4 Nov 26 '11 at 20:47
  • because I would need to declare a variable instance TStringList, and then pass as parameter... It is by far not easy to do, and not even readable. and i'm just a newbie on delphi. – Vitim.us Nov 26 '11 at 20:52
  • I have no clue about how implement "varargs" – Vitim.us Nov 26 '11 at 22:01
  • 5
    "SysUtils.Format() implements "varargs" by using a set" is factually incorrect – David Heffernan Nov 26 '11 at 22:41
  • @paulsm4: By the sounds of it, this function will get some heavy activity, which if that's true, a TStringList will be way too heavy for this. – Jerry Dodge Nov 27 '11 at 16:15