How to export a text file in for each loop? - openedge

I have written a program for export file to a specific directory and I feel I have written some unwanted logic. so I would like to know short and best way to do export the files. Let me share what I tried
DEFINE VARIABLE cData AS CHARACTER NO-UNDO.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
DEFINE VARIABLE cName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
DEFINE TEMP-TABLE ttdata
FIELD GetName AS CHARACTER
FIELD iValue AS INTEGER.
ASSIGN
icount = 2
cPath = "*******".
DO I = 1 TO icount:
IF I = 1 THEN cName = "David".
IF I = 2 THEN cName = "Macavo".
CREATE ttdata.
ASSIGN
ttdata.GetName = cName
ttdata.iValue = 100.
END.
/** ttdata has two records now*/
FOR EACH ttdata.
RUN CallProc.p (INPUT ttdata.GetName,
INPUT ttdata.iValue).
END.
PROCEDURE CallProc:
DEFINE INPUT PARAMETER getName AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER iValue AS INTEGER NO-UNDO.
OUTPUT TO cPath.
PUT UNFORMATTED ttdata.GetName ttdata.GetName.
OUTPUT CLOSE.
END PROCEDURE.
From my logic its working well and exporting 2 files as I expected but its poor idea to call another procedure.Please help this case.

I am going to use the sports2000 db in my example. Everyone has a copy so it is easy to run the sample.
define stream outFile. /* using a named stream rather than the default, unnamed, stream avoids unintended conflicts if someone else's code is lazily using the unnamed stream */
function mkTemp returns character ( input tmpid as character, input extension as character ):
define variable fname as character no-undo.
run adecomm/_tmpfile.p ( tmpid, extension, output fname ).
/* create the temp file with no content
*/
output stream outFile to value( fname ).
output stream outFile close.
return fname.
end.
procedure doStuff:
define input parameter tmpfile as character no-undo.
define input parameter custid as integer no-undo.
output stream outFile to value( tmpFile ) append. /* open the existing file in append mode */
put stream outFile "customer:" custId skip.
for each order no-lock where order.custNum = custId and orderStatus <> "shipped" and salesRep = "bbb":
put stream outFile orderNum " " promised skip.
end.
output stream outFile close.
return.
end.
define variable i as integer no-undo.
define variable tmpName as character no-undo.
/* tmpName = mkTemp( "xyzzy", ".tmp" ). */ /* if you only need one temp file get the name here and comment it out below */
for each customer no-lock:
tmpName = mkTemp( "xyzzy", ".tmp" ). /* use this if every customer should get a distinct temp file */
run doStuff ( tmpName, custNum ).
/* if there is no good reason to be calling the doStuff() procedure then just remove it and do it inline like this: */
/*
*
output stream outFile to value( tmpFile ) append. /* open the existing file in append mode */
put stream outFile "customer:" customer.custNum skip.
for each order no-lock where order.custNum = customer.CustNum and orderStatus <> "shipped" and salesRep = "bbb":
put stream outFile orderNum " " promised skip.
end.
output stream outFile close.
*/
i = i + 1.
if i >= 3 then leave. /* just do 3 customers for the sample run... */
end.

The program doesn't look too bad at first glance, but there are several issues.
The DEFINE TEMP-TABLE could use a NO-UNDO.
You should probably use "FOR EACH ttdata:" instead of "FOR EACH ttdata." which is old style.
You are running CallProc.p which is an external program and not the internal procedure included in your example. If your code actually runs you would have to show us the code in CallProc.p.
Assuming the code from CallProc, the file you open is named cPath. (I don't get why are saying two files are written.) If you want the file to be named "*******" you have to write value(cPath) instead of cPath, but "*******" is an invalid name in Windows anyway.
It doesn't hurt too much to run a procedure for every line. The bigger issue is that you open and close the file every time. Open the file before the for each and close it afterwards. If you are using a current OpenEdge version you should close it inside a finally block.
Also you are opening the file without APPEND which means that you are overwriting it every time, so only the last record gets written.
As for not using a procedure this should be pretty trivial, especially since you don't use the parameters you pass to the procedure. You are currently outputting ttdata.GetName twice though, which is probably an error. Also you are missing a SKIP at the end of the put statement and a space in between since UNFORMATTED doesn't add any spaces. I suppose you should have written PUT UNFORMATTED getName " " iValue skip.
I suppose this is some kind of homework?

If you want two (or more) separate export files, you'll need to give them unique names. I did that here by reusing your 'I' variable and reassigning cPath each time. And although I don't agree that calling a separate procedure to write the file is a poor idea, I've incorporated it into the single FOR-EACH loop. I've also fixed some of the points that idspispopd made.
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
DEFINE VARIABLE cName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
DEFINE TEMP-TABLE ttdata NO-UNDO
FIELD GetName AS CHARACTER
FIELD iValue AS INTEGER.
ASSIGN
icount = 2.
DO I = 1 TO icount:
/* Using a CASE statement makes it easier to add in other values in the future */
CASE I:
WHEN 1 THEN cName = "David".
WHEN 2 THEN cName = "Macavo".
END CASE.
CREATE ttdata.
ASSIGN
ttdata.GetName = cName
ttdata.iValue = 100.
END.
/** ttdata has two records now*/
I = 1.
FOR EACH ttdata NO-LOCK:
cPath = ".\" + STRING(I) + ".txt".
OUTPUT TO VALUE(cPath).
PUT UNFORMATTED ttdata.GetName ttdata.iValue SKIP.
OUTPUT CLOSE.
I = I + 1.
END.

Related

How can I determine if a character is alpha?

I have a list of phone numbers that sometimes have a person in parenthesis at the end. I need to extract the person's name (and add that as a note in a separate field). Here is an example of the data:
(517)234-6789(Bob)
701-556-2345
(325)663-5977
(215)789-8585
425-557-7745(Pauline)
There is always a () around the person's name, but often there is also a () around the area code, so I can't use the ( as a way to know a name has started. I'd like to create a loop that goes through the phone number string and if it sees alpha characters, builds a string that will be assigned to a variable as the name.
Something like this. I am making up the IS-ALPHA syntax, of course. That is what I am looking for, or something where I don't have to list every letter.
PROCEDURE CreatePhoneNote (INPUT cPhone AS CHARACTER)
DEFINE VARIABLE cPersonName AS CHARACTER NO-UNDO.
DEFINE VARIABLE cThisChar AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DO iCount 1 TO LENGTH(cPhone):
cThisChar = SUBSTRING(cPhone,iCount,1).
IF IS_ALPHA(cThisChar) THEN cPersonName = cPersonName + cThisChar.
END.
//etc.....
END PROCEDURE.
Since these are the fun questions, just one more isAlpha answer that does not use hard-coded ASCII codes but leans on the property / assumption that an alpha character has an upper and lower case version:
function isAlpha returns logical (
i_cc as char
):
return compare( upper( i_cc ), '<>', lower( i_cc ), 'case-sensitive' ).
end function.
With some code to test the function:
// test
def var ic as int.
do ic = 0 to 255:
if isAlpha( chr(ic) ) then
message ic chr( ic ).
end.
And then you see that the hard-coded ASCII answer did not take characters with diacritics into account. :-)
Watch it run on ProgressAblDojo.
Watch it run again on ProgresAblDojo with a fix to help ProgressAblDojo over it's ignorance of it's own codepage.
i can not comment, but a suggestion is to see what character is at the 0th index of the string, if it is a ( then you know how to deal with that condition. Although the next method will only work for usa numbers (it does seem that is what you have), you can check if the length matches a set number (10 since there are 10 digits in a usa number, or 12 since that is how long it would be with 2 parenthesis), and if its not, you know you have a name at the end. You would then split that string at the appropriate index
You don't have to go through each character. You can use the open parenthesis to break up the string and get the data after the last parenthesis. This may run faster if you have a large amount of data.
DEFINE VARIABLE cPhone AS CHARACTER NO-UNDO INITIAL "(517)234-6789(Bob)".
DEFINE VARIABLE cPersonName AS CHARACTER NO-UNDO.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
DEFINE VARIABLE iNum AS INTEGER NO-UNDO.
iCount = NUM-ENTRIES(cPhone, "("). /* See how many open parentheses there are */
cPersonName = ENTRY(iCount, cPhone, "("). /* Get the string after the last open paren */
iNum = INTEGER(SUBSTRING(cPersonName, 1, 1)) NO-ERROR. /* See if the first character is a number */
IF iNum > 0 THEN
cPersonName = "". /* If it's a number, there is no name so blank out the variable */
ELSE
cPersonName = SUBSTRING(cPersonName, 1, LENGTH(cPersonName) - 1). /* Drop the closed paren */
MESSAGE cPersonName VIEW-AS ALERT-BOX INFORMATION.
You can do this using the ABL's ASC() function.
if asc(cThisChar) ge 65
and asc(cThisChar) le 90
and asc(cThisChar) ge 97
and asc(cThisChar) le 122
then
cPersonName = cPersonName + cThisChar.
ASC() works simply enough for all codepages for codepoints between 0 and 255; for others it'll depend on your -cpinternal / session:cpinternal value.

Is it possible to give scope defined value for handle? - PROGRESS 4GL

Is it possible to give something like below?
&SCOPED-DEFINE Tablename "Customer".
DEFINE VARIABLE hFieldBufferHandle AS HANDLE NO-UNDO.
DEFINE VARIABLE icount AS INTEGER NO-UNDO.
hFieldBufferHandle = BUFFER Customer:handle.
/* hFieldBufferHandle = BUFFER {&Tablename}:handle. /*What I need..will be helpful if it has to be defined inside the loop*/ */
do icount = 1 to hFieldBufferHandle:NUM-FIELDS:
DISP buffer Customer:buffer-field (icount):label.
end.
Doing so would be meaningless because the actual value of the handle is not known until runtime.
But you don't need to use pre-processors to abstract that table name. You could make it totally dynamic at runtime like this:
define variable b as handle no-undo.
define variable t as character no-undo.
define variable f as character no-undo.
t = "customer". /* these could just as easily be parameters to a function/procedure/method */
f = "name".
create buffer b for table t no-error.
b:find-first( "", no-lock ) no-error.
message b:buffer-field( f ):buffer-value.
As Tom says, you just create the buffer handle dynamically, since you currently just want the column labels:
def var hb as handle no-undo.
def var ic as int no-undo.
create buffer hb for table 'customer'.
do ic = 1 to hb:num-fields:
message hb:buffer-field( ic ):label.
end.
finally:
delete object hb no-error.
end finally.
Also do not forget, if you create it, you generally need to delete it.
https://abldojo.services.progress.com/?shareId=624ebdd93fb02369b2543eaa

Dynamically parsing the text file

I have an ask to import a text file.
Some of the contents in the text file has static occurrences and some has dynamic.
Example:
H0X004010850TPQ0030030030030032021/03/1710:34:450
H100DEVTEST01
H2PQ003003003003003
H3CP001001001001001PP002002002002002
D1DEVTEST01
D2T-010
S1100000
Out of this H0,H1,H2,H3 and S1 comes only once in a file
But D1 and D2 can come multiple times.
The below works well if I have static number of contents.
But this will fail if my D1 and D2 occurs multiple times.
Could any one help me with some approach to handle this ?
DEFINE TEMP-TABLE TT-File
FIELDS H0 AS CHAR
FIELDS H1 AS CHAR
FIELDS H2 AS CHAR
FIELDS H3 AS CHAR
FIELDS H4 AS CHAR
FIELDS D1 AS CHAR
FIELDS D2 AS CHAR
FIELDS S1 AS CHAR.
DEFINE VARIABLE W-IMPORT-FILE AS CHARACTER NO-UNDO.
DEFINE VARIABLE W-COUNT AS INTEGER NO-UNDO.
ASSIGN W-IMPORT-FILE = "C:\Temp\Manny.txt".
INPUT FROM VALUE(W-IMPORT-FILE).
ASSIGN W-COUNT = 0.
REPEAT TRANSACTION:
CREATE TT-File.
IMPORT UNFORMATTED TT-File.H0.
IMPORT UNFORMATTED TT-File.H1.
IMPORT UNFORMATTED TT-File.H2.
IMPORT UNFORMATTED TT-File.H3.
IMPORT UNFORMATTED TT-File.D1.
IMPORT UNFORMATTED TT-File.D2.
IMPORT UNFORMATTED TT-File.S1.
END.
INPUT CLOSE.
FOR EACH TT-File:
IF SUBSTRING(TT-File.H1,1,2) = "H0":U THEN
MESSAGE "Header 0 is available to parse":U.
END.
Since there's no information on what the headers etc actually do mean it's hard to give a really good answer. If one row/header changes the meaning of another row a temp-table might be a good idea. If every row however is it's own little universe I wouldn't bother with the temp-table but just handle each row by itself.
Something like this (you need to fill in all the blocks of the CASE-statement).
DEFINE VARIABLE W-IMPORT-FILE AS CHARACTER NO-UNDO.
DEFINE VARIABLE W-COUNT AS INTEGER NO-UNDO.
DEFINE VARIABLE cImport AS CHARACTER NO-UNDO.
DEFINE VARIABLE cHeader AS CHARACTER NO-UNDO.
ASSIGN W-IMPORT-FILE = "C:\Temp\Manny.txt".
INPUT FROM VALUE(W-IMPORT-FILE).
ASSIGN W-COUNT = 0.
REPEAT TRANSACTION:
IMPORT UNFORMATTED cImport.
cHeader = SUBSTRING(cImport,1,2).
CASE cHeader:
WHEN "H0" THEN DO:
END.
WHEN "H1" THEN DO:
END.
WHEN "H2" THEN DO:
END.
WHEN "H3" THEN DO:
END.
WHEN "H4" THEN DO:
END.
WHEN "D1" THEN DO:
END.
WHEN "D2" THEN DO:
END.
WHEN "S1" THEN DO:
END.
END.
W-COUNT = W-COUNT + 1.
END.
INPUT CLOSE.
>

How can I access more than 1 element from a field in Progress 4GL

all. I want to get more than one element from a field. If, for instance, the SQL code is as folow:
SELECT cod_emit, nom_abrev FROM emit WHERE cod_emit IN ('101','102','500');
I'm trying to transform it into Progress using temp-tables so code would be as follows
DEF TEMP-TABLE tt-emit NO-UNDO
FIELD cod-emit LIKE emit.cod-emit
FIELD nom-abrev LIKE emit.nom-abrev.
FOR EACH emit
WHERE emit.cod-emit = 101
OR emit.cod-emit = 102
OR emit.cod-emit = 500 NO-LOCK:
CREATE tt-emit.
ASSIGN tt-emit.cod-emit = emit.cod-emit
tt-emit.nom-abrev = emit.nom-abrev.
END.
Later, I would get the temp-table through a JSON and use it on our .php. However, the cod-emit we need to use will be inserted by the user in a .php.
I don't know if we can use a comma in this case (but for what I tried, we can't) or if there's any other solution to this conundrum. Thanks for your time.
You might do something like this:
define temp-table tt_emit no-undo /* avoid using "-" in names */
cod_emit like emit.cod-emit
nom_abrev like emit.nom-abrev
/* it is not required but you really ought to define an index... */
.
define variable elist as character no-undo.
define variable ecode as character no-undo.
define variable i as integer no-undo.
define variable n as integer no-undo.
elist = '101,102,500'.
n = num-entries( elist ).
do i = 1 to n:
ecode = entry( i, elist ).
for each emit no-lock where emit.cod-emit = ecode:
create tt_emit.
assign
tt_emit.cod_emit = emit.cod-emit
tt_emit.nom_abrev = emit.nom-abrev
.
end.
end.

How to empty a multidimensional array?

I wrote an CSV import function. The CSV file gets seperated into columns and rows and is then saved into a multidimensional array.
At one point, I need to set the multidimensional array to Empty, because the following If-condition checks for IsEmpty().
I already tried this:
aMultidimArray = Empty
aMultidimArray = Nothing
ReDim aMultidimArray(0,0)
Erase aMultidimArray
a = Split(sData, sDelimiter)
For Each x In a
'*** Resize and write into multidimensional array
ReDim Preserve aMultidimArray(iFirstDim, iSecondDim)
aMultidimArray(i - 1,iSecondDim) = x
If (i = iFirstDim + 1) Then
i = 0
End If
i = i + 1
Next
'***Empty array here
???
'***
GetDataFromCSV = aMultidimArray
'**** other script
If IsEmpty(GetDataFromCSV) Then
Do stuff
End If
The IsEmpty(GetDataFromCSV) should return true, but I cannot handle it.
It is NOT about clearing the array. It's about getting it uninitialized again. If this is even possible.
As a workaround (or maybe just a better solution) I just called Exit function. As a result the return value is Emptyand the IsEmptycondition will return True.
When in doubt, read the documentation:
IsEmpty returns True if the variable is uninitialized, or is explicitly set to Empty; otherwise, it returns False. False is always returned if expression contains more than one variable.
Since your variable absolutely is initialized, even if you remove all values from the array, why would you expect IsEmpty to return anything but False?
Depending on what you actually want to achieve with your code you could check the dimensions of your array:
If UBound(GetDataFromCSV, 1) = -1 And UBound(GetDataFromCSV, 2) = -1 Then
'array is a 0x0 array
End If
If your array is not zero-sized you probably need to iterate over all fields and check if those are empty.
If you think you need to reset a variable that was defined as an array to Empty I suspect you made a design error somewhere, so I would strongly recommend to go back and fix the design. However, if you absolutely must reset an array variable to Empty for some reason you should be able to do so like this:
ReDim a(2, 5) 'define variable as array
...
Dim a 're-define variable as regular variable; does not clear the value
a = Empty 'clear variable

Resources