Undetermined number of parameters - progress-4gl

Somehow, System.String:Format exists but does not seem to works.
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE System.String:Format("Hello {0}", strValue) VIEW-AS ALERT-BOX.
The result was "Hello C:\temp\run.p" instead of "Hello Sebastien".
So I decided to create an equivalent function.
How is it possible to declare a method with undetermined number of parameters?
Example:
METHOD PUBLIC INTEGER Calculate(
INPUT iMultiply AS INTEGER
,INPUT iInt1 AS INTEGER
,INPUT iInt2 AS INTEGER
...
,INPUT iIntX AS INTEGER):
RETURN iMultiply * (iInt1 + iInt2, ..., iIntX).
END METHOD.
DISPLAY Calculate(10, 1, 2, 3). /* Result: 60 */
DISPLAY Calculate(2, 1, 1, 1, 1, 1). /* Result: 10 */
Thank you!
Sebastien

I'm not entirely sure what you are trying to accomplish here. For your first bit of code, you could simply do this:
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE "Hello " + strValue VIEW-AS ALERT-BOX.
Or sometimes it is useful to use the SUBSTITUTE function...
DEFINE VARIABLE strValue AS CHARACTER NO-UNDO.
strValue = "Sebastien".
MESSAGE SUBSTITUTE("Hello &1", strValue) VIEW-AS ALERT-BOX.
When you used {0} in your code sample, you were using a run-time parameter (an argument, if you like. {0} is the name of the program, {1} is the first argument for the program, and so on. I don't recommend using run-time arguments - you can't compile that code.
With regards a variable number of parameters for a function, that cannot be done in the OpenEdge ABL. However, you can create classes with overloaded methods. It probably isn't as clean and elegant as you'd like, but it will work. You'd create a class with a bunch of overloaded methods like this:
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL):
...do some stuff...
END METHOD.
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL, deValue2 AS DECIMAL):
...do some stuff...
END METHOD.
METHOD PUBLIC VOID Calc(deValue1 AS DECIMAL, deValue2 AS DECIMAL, deValue3 AS DECIMAL):
...do some stuff...
END METHOD.
And so on. The code above will give you the same method (Calc()) with 1, 2, or 3 parameters.
Hope this helps.

You cannot have a method with undetermined number of parameters in ABL.

You should not look for workarounds if you can fix the root cause.
This will work as expected:
MESSAGE System.String:Format("Hello ~{0~}", "Sebastien")
VIEW-AS ALERT-BOX INFO BUTTONS OK.
The difference to your version are the tilde characters before the curly braces. The braces have a special meaning in OpenEdge because they are used for compile time functions (includes, preprocessor directives). {0} is replaced by the procedure name at compile time.
The tilde is used to escape the curly braces.
This is from OpenEdge Help:
{ } Argument reference
References the value of an argument that a procedure passes to a called external procedure file or to an include file.
ABL converts each argument to a character format. This conversion removes the surrounding double-quotes if the parameter was specified as a character string constant in the RUN statement or include file reference.
When one procedure is called from another and arguments are used, ABL recompiles the called procedure, substituting the arguments that the calling procedure passes, and then runs the called procedure.
~ Special character
The tilde (~) is an escape character that causes the AVM to read the following character literally. A tilde followed by three octal digits represents a single character. Use it as a lead-in to enter the special characters shown in Table 2. In a procedure, a tilde followed by something other than the items in Table 2 is ignored. For example, "~abc" is treated as "abc". (This may not work as expected when passing parameters to an include file.) The items in Table 2 are case sensitive.

If all your parameters are of the same data type you could use an "indeterminate array".
Define the method parameter like this:
METHOD PUBLIC VOID Calc(INPUT numberArray AS INTEGER EXTENT):
DEFINE VARIABLE iEntriesInArray AS INTEGER NO-UNDO.
DEFINE VARIABLE iCnt AS INTEGER NO-UNDO.
DEFINE VARIABLE iTemp AS INTEGER NO-UNDO.
iEntriesInArray = EXTENT(numberArray).
DO iCnt = 1 TO iEntriesInArray:
iTemp = numberArray[iCnt].
END.
END METHOD.
And call it like this:
DEFINE VARIABLE numberArray AS INTEGER EXTENT NO-UNDO.
DEFINE VARIABLE arrayExtent AS INTEGER NO-UNDO.
arrayExtent = 5.
EXTENT(numberArray) = arrayExtent.
myClass1:Calc (INPUT numberArray).

Related

How to execute procedure from List with parameters in Progress 4gl?

I have some list like this
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5
INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
but this all procedures with input-output parameters and i want to execute this procedure, How can i do this? I have no idea how to do this.
The base of your solution is the RUN VALUE statement.
The manual states.
VALUE( extern-expression ) An expression that returns the name of the (local or remote) external procedure you want to run....
This basically means that you can input a string with the value of a program (or procedure) into your RUN statement.
If all input-output parameters are exactly the same you can do like this:
DEFINE VARIABLE procedurelist AS CHARACTER EXTENT 5 INITIAL [ "1.p", "2.p", "3.p", "4.p", "5.p"].
DEFINE VARIABLE iExtent AS INTEGER NO-UNDO.
DEFINE VARIABLE cVariable AS CHARACTER NO-UNDO.
DO iExtent = 1 TO EXTENT(procedurelist):
RUN VALUE(procedurelist[iExtent]) (INPUT-OUTPUT cVariable).
END.
If the parameters are different it gets trickier (but not impossible). The CREATE CALL and the Call Object can help you there. In this case you would need some kind of way to keep track of the different parameters as well.
Here's a basic example taken directly from the online help:
DEFINE VARIABLE hCall AS HANDLE NO-UNDO.
CREATE CALL hCall.
/* Invoke hello.p non-persistently */
hCall:CALL-NAME = "hello.p".
/* Sets CALL-TYPE to the default */
hCall:CALL-TYPE = PROCEDURE-CALL-TYPE.
hCall:NUM-PARAMETERS = 1.
hCall:SET-PARAMETER(1, "CHARACTER", "INPUT", "HELLO WORLD").
hCall:INVOKE.
/* Clean up */
DELETE OBJECT hCall.

OpenEdge: how to remove HTML tags from a string?

I have tried doing this:
REPLACE(string, "<*>", "").
but it doesn't seem to work.
REPLACE doesn't work like that. There's no wildcard matching in it.
I've included a simple way of doing this below. However, there's lots of cases that this wont work in - non well formed html etc. But perhaps you can start here and move forward by yourself.
What I do is look for < and > in the text and replace everything between it with a pipe (|) (you could select any character - preferably something not present in the text. When that's done all pipes are removed.
Again, this is a quick and dirty solution and not safe for production...
PROCEDURE cleanHtml:
DEFINE INPUT PARAMETER pcString AS CHARACTER NO-UNDO.
DEFINE OUTPUT PARAMETER pcCleaned AS CHARACTER NO-UNDO.
DEFINE VARIABLE iHtmlTagBegins AS INTEGER NO-UNDO.
DEFINE VARIABLE iHtmlTagEnds AS INTEGER NO-UNDO.
DEFINE VARIABLE lHtmlTagActive AS LOGICAL NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DO i = 1 TO LENGTH(pcString):
IF lHtmlTagActive = FALSE AND SUBSTRING(pcString, i, 1) = "<" THEN DO:
iHtmlTagBegins = i.
lHtmlTagActive = TRUE.
END.
IF lHtmlTagActive AND SUBSTRING(pcString, i, 1) = ">" THEN DO:
iHtmlTagEnds = i.
lHtmlTagActive = FALSE.
SUBSTRING(pcString, iHtmlTagBegins, iHtmlTagEnds - iHtmlTagBegins + 1) = FILL("|", iHtmlTagEnds - iHtmlTagBegins).
END.
END.
pcCleaned = REPLACE(pcString, "|", "").
END PROCEDURE.
DEFINE VARIABLE c AS CHARACTER NO-UNDO.
RUN cleanHtml("This is a <b>text</b> with a <i>little</i> bit of <strong>html</strong> in it!", OUTPUT c).
MESSAGE c VIEW-AS ALERT-BOX.

How to test if string is numeric using Progress 4GL

Does Progress 4GL have a function for testing whether a string is numeric, like PHP's is_numeric($foo) function?
I've seen the function example at http://knowledgebase.progress.com/articles/Article/P148549 to test if a character in a string is numeric. Looks like it has a typo, btw.
But I would think the language would be a built-in function for this.
I was looking at this myself recently. The approved answer given to this doesn't work in 100% situations.
If the user enters any of the following special string characters: ? * - or + the answer won't work.
A single plus or minus(dash) is converted to 0 which you may not want.
A single question mark character is valid value which progress recognises as unknown value at which again you may not want.
A single or group asterisks on their own also get converted to 0.
If you run the following code you'll see what I mean.
DISP DECIMAL("*")
DECIMAL("**")
DECIMAL("?")
DECIMAL("+")
DECIMAL("-").
The following additional code maybe useful to get around this
DEFINE VARIABLE iZeroCode AS INTEGER NO-UNDO.
DEFINE VARIABLE iNineCode AS INTEGER NO-UNDO.
DEFINE VARIABLE chChar AS CHARACTER NO-UNDO.
ASSIGN iZeroCode = ASC("0")
iNineCode = ASC("9")
chChar = SUBSTRING(cNumber,1,1).
IF NOT(ASC(chChar) >= iZeroCode AND ASC(chChar) <= iNineCode) THEN DO:
MESSAGE "Invalid Number..." VIEW-AS ALERT-BOX.
END.
Do not need a function can jsut do a straight conversion.
ASSIGN dNumber = DECIMAL(cNumber) NO-ERROR.
IF ERROR-STATUS:ERROR THEN
DO:
{Handle issues}
END.
or if it is always whole numbers can use INTEGER instead of DECIMAL.
The language does not have a built-in "isNum()" type of function.
An alternative to the kbase method would be:
function isNum returns logical ( input s as character ):
define variable n as decimal no-undo.
assign
n = decimal( s )
no-error
.
return ( error-status:num-messages = 0 ).
end.
display isNum( "123" ) isNum( "xyz" ).
This code handles any numeric strings - even if the used Character is longer than the max Decimal length etc.
FUNCTION isNumeric RETURNS LOGICAL (textvalue AS CHAR):
DEF VAR i AS INT NO-UNDO.
IF textvalue = ? THEN RETURN TRUE.
DO i = 1 TO (LENGTH(textvalue) - 1):
INT(SUBSTRING(textvalue, i, (i + 1))) NO-ERROR.
IF ERROR-STATUS:ERROR THEN RETURN FALSE.
END.
RETURN TRUE.
END FUNCTION.
Works 100% of the time
FUNCTION is-num RETURNS LOGICAL
(INPUT cString AS CHARACTER):
DEFINE VARIABLE iZeroCode AS INTEGER NO-UNDO.
DEFINE VARIABLE iNineCode AS INTEGER NO-UNDO.
DEFINE VARIABLE cChar AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DO iCount = 1 TO LENGTH(cString):
ASSIGN iZeroCode = ASC("0")
iNineCode = ASC("9")
cChar = SUBSTRING(cString,iCount,1).
IF NOT(ASC(cChar) >= iZeroCode AND ASC(cChar) <= iNineCode) THEN DO:
RETURN FALSE.
END.
END.
RETURN TRUE.
END.

Reference a table through a string representation of the name

I'm trying to write a modular procedure, where I pass in a string representation of a table name, and query/update that table.
What I tried was something to this affect:
PROCEDURE foo:
DEFINE INPUT PARAMETER chTableName AS CHARACTER NO-UNDO.
FIND FIRST VALUE(chTableName) NO-LOCK
WHERE blahblahblah NO-ERROR.
IF AVAIL VALUE(chTableName) THEN
ASSIGN
VALUE(chTableName).value = foo.
END PROCEDURE.
This obviously does not work, but hopefully this will get the point across of what I'm trying to accomplish.
Any help or info in this matter would be appreciated. Thanks.
Hopefully you are running version 9 or better where dynamic queries are available.
procedure x:
define input parameter tbl as character no-undo.
define input parameter fld as character no-undo.
define input parameter xyz as character no-undo.
define variable qh as handle no-undo.
define variable bh as handle no-undo.
define variable fh as handle no-undo.
create buffer bh for table tbl.
create query qh.
qh:set-buffers( bh ).
qh:query-prepare( "for each " + tbl ).
qh:query-open.
do transaction:
qh:get-first( exclusive-lock ).
fh = bh:buffer-field( fld ).
display fh:buffer-value.
fh:buffer-value = xyz.
end.
delete object bh.
delete object qh.
return.
end.
run x ( "customer", "name", "fred" ).
find first customer no-lock.
display name.
This can get you started:
DEFINE TEMP-TABLE tt NO-UNDO
FIELD a AS INTEGER.
PROCEDURE foo:
DEFINE INPUT PARAMETER pcTable AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcWhere AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcField AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER piValue AS INTEGER NO-UNDO.
DEFINE VARIABLE hBuffer AS HANDLE NO-UNDO.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
CREATE BUFFER hBuffer FOR TABLE pcTable.
CREATE QUERY hQuery.
hQuery:SET-BUFFERS(hBuffer).
hQuery:QUERY-PREPARE("FOR EACH " + pcTable + " WHERE " + pcWhere).
hQuery:QUERY-OPEN().
hQuery:GET-FIRST().
IF hBuffer:AVAILABLE THEN DO:
ASSIGN
hBuffer:BUFFER-FIELD(pcField):BUFFER-VALUE = piValue NO-ERROR.
IF ERROR-STATUS:ERROR THEN DO:
MESSAGE "Failed" VIEW-AS ALERT-BOX ERROR.
END.
END.
DELETE OBJECT hBuffer.
DELETE OBJECT hQuery.
END PROCEDURE.
CREATE tt.
ASSIGN tt.a = 1.
RUN foo( INPUT "tt"
, INPUT "tt.a = 1"
, INPUT "a"
, INPUT 2).
DISPLAY tt.

How to pass mulitiple data types in parameters to function?

F.e. I want to implement inc function:
FUNCTION inc RETURNS INT (INPUT-OUTPUT i AS INT, AddExpression AS INT):
i = i + AddExpression.
END FUNCTION.
to use it like this:
inc(tt-data.qty,1).
I didn't found how to override my function for DEC data type or how to combine both in one. If it possible I also want my function to deal with CHAR - kind of ADD-ENTRY. Maybe this basic functions are already implemented by someone? Something like this STLib on OEHive.
Plain old user-defined functions can only have a single signature. Your function definition is a bit "off". You are using an input-output parameter (which isn't "wrong" but it is odd) and you aren't returning a value -- which is wrong. It should look like this:
function inc returns integer ( input-output i as integer, addExpression as integer ):
i = i + addExpression.
return i.
end.
Procedures have somewhat more relaxed data-type rules and will do some type conversions automatically (such as an implied decimal to integer conversion). This would, for example, support passing a decimal that gets automatically rounded:
procedure z:
define input-output parameter i as integer no-undo.
define input parameter x as integer.
i = i + x.
return.
end.
You can overload method signatures if you create your function as a method of a class.
Something along these lines (untested):
class X:
method public integer inc( input-output i as integer, input addExpression as integer ):
i = i + addExpression.
return i.
end.
method public integer inc( input-output i as integer, input addExpression as character ):
i = i + integer( addExpression ).
return i.
end.
end.