Concatenate string by its length COBOL - cobol

Need to concatenate 4 strings to a destination variable in cobol.
Like,
01 WS-S1 X(10) VALUE "HI ".
01 WS-S2 X(10) VALUE "HOW ".
01 WS-S3 X(10) VALUE "ARE ".
01 WS-S4 X(10) VALUE "YOU?".
to a resultant string
"HI HOW ARE YOU?"
Can anyone please help me out?

Here is a working example of the STRING verb that does what you are looking for:
IDENTIFICATION DIVISION.
PROGRAM-ID. EXAMPLE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-S1 PIC X(10) VALUE 'HI '.
01 WS-S2 PIC X(10) VALUE 'HOW '.
01 WS-S3 PIC X(10) VALUE 'ARE '.
01 WS-S4 PIC X(10) VALUE 'YOU?'.
01 WS-CONCAT PIC X(43) VALUE SPACES.
PROCEDURE DIVISION.
MAIN-PARAGRAPH.
STRING WS-S1 DELIMITED BY SPACE
' ' DELIMITED BY SIZE
WS-S2 DELIMITED BY SPACE
' ' DELIMITED BY SIZE
WS-S3 DELIMITED BY SPACE
' ' DELIMITED BY SIZE
WS-S4 DELIMITED BY SPACE
INTO WS-CONCAT
END-STRING
DISPLAY '>' WS-CONCAT '<'
GOBACK
.
Output is:
>HI HOW ARE YOU? <

OpenCOBOL has an intrinsic FUNCTION extension, CONCATENATE.
DISPLAY FUNCTION CONCATENATE(
FUNCTION TRIM(WS-S1); SPACE;
FUNCTION TRIM(WS-S2); SPACE;
FUNCTION TRIM(WS-S3); SPACE;
FUNCTION TRIM(WS-S4))
END-DISPLAY
but I like the STRING verb DELIMITED BY answer, as it'll work with most, if not all, compilers.
As to the reason for semi-colon delimiters inside FUNCTION parameter lists, it isn't strictly necessary, personal preference, as it sometimes avoids potential problems with
SPECIAL-NAMES.
DECIMAL POINT IS COMMA.
and COBOL, being the robust lexical animal that it is
DISPLAY FUNCTION CONCATENATE(WS-S1 WS-S2 WS-S3 WS-S4)
DISPLAY FUNCTION CONCATENATE(WS-S1, WS-S2, WS-S3, WS-S4)
syntax works as well.

There is a problem with 'delimited by space'. If ws-s1 = 'how are' - delimited by space will put only 'how'.
Here are some examples:
01 ws-string-test.
03 y1 pic x(10) value 'y1 a'.
03 y2 pic x(10) value 'y2 b'.
03 y3 pic x(10) value 'y3 c'.
01 ws-work pic x(200).
move spaces to ws-work
string y1 delimited by size
y2 delimited by space
y3 delimited by size
into ws-work.
ws-work = "y1 a y2y3 c "
move spaces to ws-work
string y1
y2
y3
delimited by size into ws-work
ws-work = "y1 a y2 b y3 c "
string y1
y2
y3
delimited by spaces into ws-work.
ws-work = "y1y2y3
string y1 y2 y3 into ws-work by csv-format.
ws-work = "y1 a,y2 b,y3 c "
Hope it will help.
zalek

Give this a whirl. Should be platform independent.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 result-string-text X(100).
01 result-string-length 9(03).
01 result-string-datalength 9(03).
01 new-string-text X(20).
01 new-string-length 9(03).
01 new-string-datalength 9(03).
01 hold-string-text X(100).
01 trailing-space-count 9(03).
PROCEDURE DIVISION.
MOVE SPACES TO result-string-text.
MOVE FUNCTION LENGTH(result-string-text) TO result-string-length.
MOVE FUNCTION LENGTH(new-string-text) TO new-string-length.
MOVE ws-s1 TO new-string-text.
PERFORM 5500-concatenate.
MOVE ws-s2 TO new-string-text.
PERFORM 5500-concatenate.
MOVE ws-s3 TO new-string-text.
PERFORM 5500-concatenate.
MOVE ws-s4 TO new-string-text.
PERFORM 5500-concatenate.
5500-concatenate.
MOVE ZERO TO trailing-space-count
INSPECT FUNCTION REVERSE(result-string-text) TALLYING trailing-space-count FOR LEADING ' '
COMPUTE result-string-datalength = result-string-length - trailing-space-count
IF (result-string-datalength > ZERO)
MOVE ZERO TO trailing-space-count
INSPECT FUNCTION REVERSE(new-string-text) TALLYING trailing-space-count FOR LEADING ' '
COMPUTE new-string-datalength = new-string-length - trailing-space-count
MOVE SPACES TO hold-string-text
STRING
result-string-text(1:result-string-datalength)
' '
new-string-text(1:new-string-datalength)
DELIMITED BY SIZE
INTO
hold-string-text
END-STRING
MOVE hold-string-text to result-string-text
ELSE
MOVE new-string-text TO result-string-text
END-IF.

Related

convert alphanumeric PIC X(02) to hex value 9(01) COMP-3 in cobol

I have Alphanumeric value = '86' and its length is defined as PIC x(02). I need to convert it into hex x'86' and its length is defined as PIC 9(01) comp-3.
example:
01 WS-ALPHANUMERIC PIC X(02) VALUE '86'.
01 WS-HEX PIC 9(01) COMP-3.
PROCEDURE DIVISION.
MOVE WS-ALPHANUMERIC TO WS-HEX.
DISPLAY WS-HEX.
STOP RUN
I am getting x'FF' in my spool. But I am expecting x'86'.
Why your code doesn't produce the output you're expecting
It is just guessing from my part for on my computer it doesn't work that way.
When you MOVE from WS-ALPHANUMERIC to WS-HEX, the string '86' in transformed in the decimal number 86.
However WS-HEX is only one byte long and in the COMP-3 format. This format can only store one decimal digit and the sign.
I'm guessing that on your environment when you move a bigger number than the capacity to a COMP-3 it take the biggest hexadecimal value it can hold : 0xF.
In my environment it would just take the digit 6 of the number 86.
So when you display, it is converted to a usage display so you have your firt 0xF for the usage formatting and then your 0xF for the "overflow" I guess.
On my computer you would just get a 0xF6.
A solution to produce the expected output
Disclaimer : I originally thought that your input would only be decimals, like '87596', '12' or '88'. This solution does not work for hexadecimals input like 'F1' ou '99F'. I built more complete solutions below in items 3 and 4 by improving this one
The solution I propose can take up to 16 digits in the input string if your system is 64bit because it takes 4 bits to store a hexadecimal digit.
Therefore if you want a larger input you'll have to use more than one result variable.
If you want to have it in only one byte, you just have to make Result a PIC 9(1) instead of PIC 9(18)
IDENTIFICATION DIVISION.
PROGRAM-ID. CNVRSN.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RawInput PIC X(02) VALUE '86'.
01 FormattedInput PIC 9(16).
01 FractionedInput REDEFINES FormattedInput
05 Digit PIC 9 OCCURS 16.
01 Shifting PIC 9(18) COMP-5 VALUE 1.
01 I PIC 99 COMP-5.
01 Result PIC 9(18) COMP-5 VALUE 0.
01 DisplayResult REDEFINES Result PIC X(8).
PROCEDURE DIVISION.
MOVE RawInput TO FormattedInput.
PERFORM VARYING I FROM LENGTH OF FractionedInput
BY -1 UNTIL I < 1
COMPUTE Result = Result + Digit(I)*Shifting
MULTIPLY 16 BY Shifting
END-PERFORM
DISPLAY 'DisplayResult : ' DisplayResult
.
END PROGRAM CNVRSN.
The code works by transforming the string in a number of USAGE DISPLAY with the first move MOVE RawInput to FormattedInput.
We use the fact that each digit has the same format as a number of just one digit (PIC 9). This allows us to split the number in elements of an array with the REDEFINES of FomattedInput inFractionedInput
As you can see I traverse the array from the end to start because the least significant byte is at the end of the array (highest address in memory), not at the start (lowest address in memory).
Then we place each the hexadecimal digit in the correct place by shifting them to the left by 2^4 (a nibble, which is the size of a hexadecimal digit) as many times as required.
A solution that accepts the full hexadecimal input range (memory intensive)
Here is the code :
IDENTIFICATION DIVISION.
PROGRAM-ID. CNVRSN.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RawInput PIC X(02) VALUE '86'.
01 FormattedInput PIC X(16).
01 FractionedInput REDEFINES FormattedInput
05 Digit PIC X OCCURS 16.
01 I PIC 99 COMP-5.
01 ConversionTableInitializer.
05 FILLER PIC X(192).
05 TenToFifteen PIC X(06) VALUE X'0A0B0C0D0E0F'.
05 FILLER PIC X(41).
05 ZeroToNine PIC X(10) VALUE X'00010203040506070809'.
01 ConversionTable Redefines ConversionTableInitializer.
05 DigitConverter PIC 99 COMP-5 OCCURS 249.
01 Result PIC 9(18) COMP-5 VALUE 0.
01 DisplayResult REDEFINES Result PIC X(8).
PROCEDURE DIVISION.
MOVE RawInput TO FormattedInput.
PERFORM VARYING I FROM 1 BY 1
UNTIL I > LENGTH OF FractionedInput
OR Digit(I) = SPACE
COMPUTE Result = Result*16 + DigitConverter(Digit(I))
END-PERFORM
DISPLAY 'DisplayResult : ' DisplayResult
.
END PROGRAM CNVRSN.
The idea in this solution is to convert each character (0,1...,E,F) to its value in hexadecimal. For this we use the value of their encoding as a string (0xC1 = 0d193 for A for instance) as the index of an array.
This is very wasteful of memory for we allocate 249 bytes to store only 16 nibbles of information. However to access the element of an array is a very fast operation: We are trading the memory usage for cpu efficiency.
The underlying idea in this solution is a hashtable. This solution is nothing but a very primitive hash table where the hash function is the identity function (A very, very bad hash function).
Another solution that accepts the full hexadecimal input range (CPU intensive)
Disclaimer : This solution was proposed by #Jim Castro in the comments.
IDENTIFICATION DIVISION.
PROGRAM-ID. CNVRSN.
ENVIRONMENT DIVISION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 RawInput PIC X(02) VALUE '86'.
01 FormattedInput PIC X(16).
01 FractionedInput REDEFINES FormattedInput
05 Digit PIC 9 OCCURS 16.
01 ConversionString PIC X(16) VALUE '0123456789ABCDEF'.
01 ConversionTable REDEFINES ConversionString.
05 ConversionEntry OCCURS 16 INDEXED BY Idx.
10 HexDigit PIC X.
01 I PIC 99 COMP-5.
01 Result PIC 9(18) COMP-5 VALUE 0.
01 DisplayResult REDEFINES Result PIC X(8).
PROCEDURE DIVISION.
MOVE RawInput TO FormattedInput.
PERFORM VARYING I FROM 1 BY 1
UNTIL I > LENGTH OF FractionedInput
OR Digit(I) = SPACE
SET Idx To 1
SEARCH ConversionEntry
WHEN HexDigit(Idx) = Digit(I)
COMPUTE Result = Result*16 + Idx - 1
END-SEARCH
END-PERFORM
DISPLAY 'DisplayResult : ' DisplayResult
.
END PROGRAM CNVRSN.
Here the idea is still to convert the string digit to its value. However instead of trading off memory efficiency for cpu efficiency we are doing the converse.
We have a ConversionTable where each character string is located at the index that convey the value they are supposed to convey + 1 (because in COBOL arrays are 0 based). We juste have to find the matching character and then the index of the matching character is equal to the value in hexadecimal.
Conclusion
There are several ways to do what you want. The fundamental idea is to :
Implement a way to convert a character to its hexadecimal value
Traverse all the characters of the input string and use their position to give them the correct weight.
Your solution will always be a trade off between memory efficiency and time efficiency. Sometimes you want to preserve your memory, sometimes you want the execution to be real fast. Sometimes you wand to find a middle ground.
To go in this direction we could improve the solution of the item 3 in terms of memory at the expense of the cpu. This would be a compromise between item 3 and 4.
To do it we could use a modulo operation to restrict the number of possibilities to store. Going this way would mean implementing a real hashtable.
I don't have access to an IBM mainframe to test this code.
When I run the code on an online GnuCOBOL v2.2 compiler, I'm stuck with ASCII instead of EBCDIC.
I've posted the code. Here's what you have to do.
Make sure the top byte comes out to 8 and the bottom byte comes out to 6. You're converting the EBCDIC values to intager values. Values A - F hex will have different EBCDIC values than values 0 - 9.
Make sure the multiply and add are correct
Here's the code. You'll have to fix it to work with EBCDIC.
IDENTIFICATION DIVISION.
PROGRAM-ID. CONVERSION.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ALPHANUMERIC PIC X(02) VALUE '86'.
01 WS-WORK-FIELDS.
05 WS-INPUT.
10 WS-ONE.
15 WS-TOP-BYTE PIC 99 COMP.
10 WS-TWO.
15 WS-BOTTOM-BYTE PIC 99 COMP.
05 WS-ACCUMULATOR PIC S9(4) COMP.
05 FILLER REDEFINES WS-ACCUMULATOR.
10 FILLER PIC X.
10 WS-HEX PIC X.
PROCEDURE DIVISION.
0000-BEGIN.
MOVE WS-ALPHANUMERIC TO WS-INPUT
DISPLAY WS-INPUT
COMPUTE WS-TOP-BYTE = WS-TOP-BYTE - 183
COMPUTE WS-BOTTOM-BYTE = WS-BOTTOM-BYTE - 183
IF WS-TOP-BYTE NOT LESS THAN 16
COMPUTE WS-TOP-BYTE = WS-TOP-BYTE - 57
END-IF
IF WS-BOTTOM-BYTE NOT LESS THAN 16
COMPUTE WS-BOTTOM-BYTE = WS-BOTTOM-BYTE - 57
END-IF
DISPLAY WS-TOP-BYTE
DISPLAY WS-BOTTOM-BYTE
MOVE WS-TOP-BYTE TO WS-ACCUMULATOR
MULTIPLY 16 BY WS-ACUMULATOR
ADD WS-BOTTOM-BYTE TO WS-ACCUMULATOR
DISPLAY WS-ACCUMULATOR
DISPLAY WS-HEX
GOBACK.

How do I remove spaces when creating a CSV document

I have program that creates a CSV document. One field from a database table, Z-ZYSR-MONTAN(IZYSR), has the format NOT NULL NUMBER(11,2).
My code is :
05 H-ZYSR-MONTAN PIC S9(11)V9(2) COMP-3.
* Taux horaire formation
05 W-Z18 PIC -(12),99.
05 FILLER PIC X(001) VALUE ';'.
ALIM-WZ18 SECTION.
MOVE Z-ZYSR-NOMBRE TO IZYSR.
MOVE ZERO TO H-ZYSR-MONTAN.
IF Z-ZYSR-NOMBRE > ZERO
PERFORM VARYING IZYSR FROM Z-ZYSR-NOMBRE BY -1 UNTIL
IZYSR = ZERO
IF Z-ZYSR-CODRUB(IZYSR) = 'THF'
MOVE Z-ZYSR-MONTAN(IZYSR) TO
H-ZYSR-MONTAN
MOVE ZERO TO IZYSR
END-IF
END-PERFORM
IF H-ZYSR-MONTAN < 0
MOVE 0 TO W-Z18
END-IF
IF H-ZYSR-MONTAN >= 0
MOVE H-ZYSR-MONTAN TO W-Z18
END-IF
END-IF.
Results:
2223,55
-10,98
-1,08
82,61
But the problem I have in my CSV document is that there are spaces before the number. As may be seen in the picture in this forum :
https://www.developpez.net/forums/d1940330/autres-langages/autres-langages/cobol/numerique-condense-cobol/#post10895608
So my question how can I obtain the correct results without spaces before the number?
You could try to make a loop (PERFORM UNTIL), for every character, and if it is SPACE, then change it by 0.
You can use the TRIM() function to remove white space if you're using GnuCOBOL(OpenCOBOL) on Linux.
WORKING-STORAGE SECTION.
01.
05 H-ZYSR-MONTAN PIC S9(11)V9(2) COMP-3.
05 W-Z18 PIC -(12).99.
05 H-Z18-C REDEFINES W-Z18 PIC X(15).
05 H-ZYSR-DISPLAY PIC X(15).
PROCEDURE DIVISION.
0000-MAIN.
MOVE 1234.56 TO H-ZYSR-MONTAN.
move H-ZYSR-MONTAN to W-Z18.
MOVE FUNCTION TRIM(H-Z18-C) TO H-ZYSR-DISPLAY.
DISPLAY H-ZYSR-DISPLAY.
STOP RUN.
I did have to change the , to . to match my locale so remember to change it back.
And if you don't have access to that function there is a simple work around.
WORKING-STORAGE SECTION.
01.
05 H-ZYSR-MONTAN PIC S9(11)V9(2) COMP-3.
05 W-Z18 PIC -(12).99.
05 VAR-Z18 PIC X(15).
05 ws-counter pic 9(3) COMP-3 value 0.
01 VARIABLES.
05 VAR1 PIC X(10).
05 VAR2 PIC X(10).
05 VAR3 PIC X(10).
05 VAR4 PIC X(10).
05 VAR5 PIC X(10).
05 VAR6 PIC X(10).
05 VAR7 PIC X(10).
05 VAR8 PIC X(10).
01 WS-DELIMITED-TEXT PIC X(200).
PROCEDURE DIVISION.
0000-MAIN.
INITIALIZE VARIABLES
MOVE 'CEX18' TO VAR6
MOVE -1234.56 TO H-ZYSR-MONTAN
MOVE H-ZYSR-MONTAN TO W-Z18
INSPECT W-Z18 TALLYING WS-COUNTER FOR LEADING SPACES
IF WS-COUNTER > 0
COMPUTE WS-COUNTER = WS-COUNTER + 1
MOVE W-Z18(ws-counter:) TO VAR-Z18
ELSE
MOVE W-Z18 TO VAR-Z18
END-IF
String VAR1 delimited by SPACE
";" delimited by size
VAR2 delimited by SPACE
";" delimited by size
VAR3 delimited by SPACE
";" delimited by size
VAR4 delimited by SPACE
";" delimited by size
VAR5 delimited by SPACE
";" delimited by size
VAR6 delimited by SPACE
";" delimited by size
VAR-Z18 delimited by SPACE
";" delimited by size
VAR7 delimited by SPACE
";" delimited by size
VAR8 delimited by SPACE
";" delimited by size
into WS-DELIMITED-TEXT
DISPLAY WS-DELIMITED-TEXT.
GOBACK.
Using STRING to assemble the final text field was already suggested by the other forum I only include it here to round out the example.
Based on our conversation and other information, I changed the code a little bit. I removed all references to CSV, since that seems to occur outside the COBOL program. I replaced the STRING statement. The data-name for the group containing W-Z18 and the ; was not given in other references, so here I named it, W-Z18-FIELD. I placed the result in W-Z18-TEXT.
environment division.
configuration section.
special-names.
decimal-point is comma.
data division.
working-storage section.
1 w-z18-text pic x(16) value space.
1 w-z18-pointer comp pic 9(4) value 1.
1 w-z18-field.
* Taux horaire formation
3 w-z18 pic -(12),99.
3 pic x value ";".
1 leading-spaces comp pic 9(4) value 0.
1 move-length comp pic 9(4) value 0.
procedure division.
begin.
move -10,98 to w-z18
move 0 to leading-spaces
inspect w-z18 tallying
leading-spaces for leading space
compute move-length =
function length (w-z18 (leading-spaces + 1:))
move space to w-z18-text
move 1 to w-z18-pointer
move w-z18 (leading-spaces + 1:)
to w-z18-text (w-z18-pointer:move-length)
compute w-z18-pointer = w-z18-pointer + move-length
move ";" to w-z18-text (w-z18-pointer:1)
add 1 to w-z18-pointer
display quote w-z18-field quote
display quote w-z18-text quote
stop run
.
Result: (the quotes were added to show the change)
" -10,98;" - what it looked like before, w-z18-field
"-10,98; " - with the changes, w-z18-text
This only moves the spaces from before the number to after the semi-colon. Whether this will be enough depends on whether the next program removes trailing spaces.

How to move the last digit to the first position of a number in cobol

Say I have the number 123456, how can i move the 6 to the beginning so it becomes 612345?
needs to work if the number has fewer digits like 123 becoming 312.
many thanks in advance.
This is a general purpose method that will work with any integer from 2 to 32 digits. The number must be usage display.
working-storage section.
1 a-number pic 9(6) value 123456.
1 b-number pic 9(3) value 123.
1 work pic x(32).
1 len-of-number binary pic 9(4).
procedure division.
begin.
display a-number
move a-number (1:) to work
perform swap-digit
move work to a-number (1:)
display a-number
display space
display b-number
move b-number (1:) to work
perform swap-digit
move work to b-number (1:)
display b-number
stop run
.
swap-digit.
move 0 to len-of-number
inspect work tallying
len-of-number for characters before space
move function reverse (work (1:len-of-number))
to work
move function reverse (work (2:len-of-number - 1))
to work (2:len-of-number - 1)
.
Output:
123456
612345
123
312
This is the solution. Just MOVE your input to the VAR-INPUT. It is a alphanumeric picture clause, but it doesn't matter because you want a string as output.
Also I chose random picture clause lengths, you can choose whatever length you want.
WORKING-STORAGE SECTION.
01 VAR-INPUT PIC X(20).
01 VAR1 PIC X(10).
01 VAR2 PIC X(10).
01 RESULT PIC X(20).
01 L PIC 9(02).
01 OFFSET PIC 9(02).
PROCEDURE DIVISION.
COMPUTE L = LENGTH OF VAR-INPUT
COMPUTE OFFSET = L - 1
MOVE VAR-INPUT(1:L) TO VAR1
MOVE VAR-INPUT(OFFSET:1) TO VAR2
STRING VAR1 DELIMITED BY SPACE
VAR2 DELIMITED BY SPACE
INTO RESULT
Here's a working example from OpenCobolIDE
IDENTIFICATION DIVISION.
PROGRAM-ID. YOUR-PROGRAM-NAME.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 A PIC X(37).
01 B PIC 9(2).
01 C PIC X(1).
01 D PIC 9(2).
01 E PIC 9(2).
PROCEDURE DIVISION.
ACCEPT A.
INSPECT A TALLYING B FOR CHARACTERS BEFORE SPACE.
MOVE A(B:) TO C.
PERFORM VARYING D FROM B BY -1 UNTIL D = 1
MOVE D TO E
SUBTRACT 1 FROM E
MOVE A(E:1) TO A(D:1)
END-PERFORM.
MOVE C TO A(1:1).
DISPLAY A.
END PROGRAM YOUR-PROGRAM-NAME.
Insert a DISPLAY A inside the PERFORM loop to see how the workflow goes.

How do you grab a string in COBOL from a file when the position is unknown?

I'm new to the site as well as COBOL. I am trying to write a program that reads in an 80 byte file, and finds a certain string and grabs another string that is positioned right after that. The only issue I'm having with this is that the starting position of the string is not always in the same byte throughout the file. For example, the string I am trying to find below is the LENGTH(#####) string that appears twice throughout the file:
LENGTH(14909135) FILEID(DD:EDIREC) MSGDATE(130723) MSGDATELONG(20130723)
MSGTIME(091053) MSGSEQO(001390) MSGNAME(00008557) MSGSEQNO(00001)
SESSIONKEY(XXXXXXXX) DELIMITED(E) SYSNAME(XXXXX-XX) SYSLEVEL(XXXX) TIMEZONE(L)
DATATYPE(E) EDITYPE(XXX) SENDERFILE(#####) RECFM(????) RECLEN(#) RECDLM(E)
UNIQUEID(XXXXXXXX) SYSTYPE(##) SYSVER(#);
RECEIVED ACCOUNT(XXXX) USERID(XXXXXXXX) CLASS(#E2) CHARGE(3) LENGTH(14911043)
FILEID(DD:EDIREC) MSGDATE(130723) MSGDATELONG(20130723) MSGTIME(093045)
MSGSEQO(001392) MSGSEQNO(00000) SESSIONKEY(XXXXXXXX) DELIMITED(C)
SYSNAME(XXXXX-XX) SYSLEVEL(XXXX) TIMEZONE(L) DATATYPE(E) EDITYPE(UNFORMATTED)
SENDERFILE(XXXXXXXXXXXXX) RECFM(????) RECLEN(0) RECDLM(C) UNIQUEID(XXXXXXXX)
SYSTYPE(24) SYSVER(5);
Notice the two LENGTH(#####) strings. The below code manages to count the amount of times the length string appears as well as grab the final length string count (what I really want, the numbers within the length string), but only when they are in these two positions:
WORKING-STORAGE SECTION.
01 WS-INPUT-RECORD PIC X(80).
01 WS-STRINGS.
05 LENGTH-STRING PIC X(7) VALUE 'LENGTH('.
01 WS-COUNTERS.
05 WS-MSG-COUNT PIC 9(11).
01 WS-CHAR-TOTALS.
05 CHAR-TOTAL PIC 9(11) VALUE ZEROS.
05 TMP-TOTAL PIC X(11) VALUE ZEROS.
......
PROCEDURE DIVISION.
2200-GET-MSG-TOTAL.
INSPECT WS-INPUT-RECORD
TALLYING WS-MSG-COUNT FOR ALL LENGTH-STRING.
2300-CHAR-TOTAL.
IF WS-INPUT-RECORD(1:7) = LENGTH-STRING
MOVE WS-INPUT-RECORD(8:9) TO TMP-TOTAL
UNSTRING TMP-TOTAL DELIMITED BY ')'
INTO CHAR-TOTAL
END-IF
IF WS-INPUT-RECORD(61:7) = LENGTH-STRING
MOVE WS-INPUT-RECORD(68:9) TO TMP-TOTAL
UNSTRING TMP-TOTAL DELIMITED BY ')'
INTO CHAR-TOTAL
END-IF
The code works great for the two positions shown in the example input above. But it won't work if LENGTH(####) ends up in any other byte position. Other than coding 80 IF statements to check for every byte in the file for the string, is there an easier way to go about getting those values inside of the length parens? I've checked a lot of other posts and I've thought about using pointers or tables but I can't quite seem to figure it out.
Use INSPECT to establish that LENGTH( is on the current record.
Only if present, do the following:
UNSTRING using LENGTH( as a delimiter with two receiving fields.
UNSTRING second receiving field delimited by ) leaving you with the number.
For example:
01 delimiting-field PIC X(7) VALUE "LENGTH(".
01 desitnation-field-1 PIC X.
01 destination-field-2 PIC X(18) JUST RIGHT.
UNSTRING source-field DELIMITED BY delimiting-field INTO desitnation-field-1
destination-field-2
Abandon destination-field-1. Use destination-field-2 for input to the second UNSTRING.
Use meaningful names, rather than those I have shown to illuminate the example.
So,
01 WS-INPUT-RECORD PIC X(80).
01 NUMBER-OF-LENGTHS BINARY PIC 9(4).
01 DELIMITER-COUNT BINARY PIC 9(4).
88 NO-DELIMITERS VALUE ZERO.
88 ONE-DELIMITER VALUE 1.
01 LENGTH-OPEN-PAREN PIC X(7)
VALUE "LENGTH(".
01 DATA-TO-IGNORE PIC X.
01 DATA-WITH-LENGTH-VALUE PIC X(80).
01 CLOSING-PAREN PIC X VALUE ")".
01 VALUE-OF-LENGTH-AN PIC X(18) JUST RIGHT.
THE-STUFF.
SET NO-DELIMITERS TO TRUE
INSPECT WS-INPUT-RECORD TALLYING DELIMITER-COUNT
FOR ALL LENGTH-OPEN-PAREN
EVALUATE TRUE
WHEN NO-DELIMITERS
CONTINUE
WHEN ONE-DELIMITER
PERFORM GET-THE-DATA
WHEN OTHER
PERFORM OH-DEAR-MORE-THAN-ONE
END-EVALUATE
.
GET-THE-DATA.
UNSTRING WS-INPUT-RECORD DELIMITED BY
LENGTH-OPEN-PAREN
INTO DATA-TO-IGNORE
DATA-WITH-LENGTH-VALUE
UNSTRING DATA-WITH-LENGTH-VALUE
DELIMITED BY CLOSING-PAREN
INTO VALUE-OF-LENGTH-AN
DISPLAY "THIS IS WHAT WE FOUND"
DISPLAY ">"
VALUE-OF-LENGTH-AN
"<"
.
OH-DEAR-MORE-THAN-ONE.
DISPLAY "THE FOLLOWING LINE HAS MORE THAN ONE LENGTH("
DISPLAY ">"
WS-INPUT-RECORD
"<"
.
The technique with the INSPECT to see if the "string" is present can be applied to the other solution accepted so that only if the line contains the value desired is it "searched".
You can use a "perform varying" loop to look at each block of the string within each line, where each block is a string the length of the string you are looking for. Here is an example that works in OpenCobol:
IDENTIFICATION DIVISION.
PROGRAM-ID. FIND-STRING.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT IN-FILE ASSIGN TO 'SAMPLE-LEN.TXT'
ORGANIZATION IS LINE SEQUENTIAL.
DATA DIVISION.
FILE SECTION.
FD IN-FILE.
01 IN-RECORD PIC X(80).
WORKING-STORAGE SECTION.
01 END-OF-FILE-SWITCH PIC XXX VALUE 'NO '.
88 END-OF-FILE VALUE 'YES'.
01 STRING-MARKER PIC X(7) VALUE 'LENGTH('.
01 STRING-MARKER-LENGTH PIC 99 VALUE 7.
01 STRING-SOUGHT PIC X(11).
01 STRING-INDEX PIC 99.
01 RECORD-LENGTH PIC 99 VALUE 80.
PROCEDURE DIVISION.
MAIN.
OPEN INPUT IN-FILE
PERFORM UNTIL END-OF-FILE
READ IN-FILE
AT END
SET END-OF-FILE TO TRUE
NOT AT END
PERFORM FIND-STRING
END-READ
END-PERFORM
CLOSE IN-FILE
STOP RUN
.
FIND-STRING.
PERFORM VARYING STRING-INDEX FROM 1 BY 1
UNTIL STRING-INDEX > (RECORD-LENGTH
- STRING-MARKER-LENGTH)
IF IN-RECORD(STRING-INDEX:STRING-MARKER-LENGTH) =
STRING-MARKER
UNSTRING IN-RECORD(STRING-INDEX
+ STRING-MARKER-LENGTH : 10)
DELIMITED BY ')' INTO STRING-SOUGHT
END-UNSTRING
DISPLAY STRING-SOUGHT END-DISPLAY
END-IF
END-PERFORM
.
Based on Bill Woodger's comments, here is a better solution. Thank's Bill, for teaching me not to slouch :) I still like looping through each record as a way to catch multiple matches on one line, so I kept that part.
IDENTIFICATION DIVISION.
PROGRAM-ID. FIND-STRING-2.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT IN-FILE ASSIGN TO 'SAMPLE-LEN.TXT'
ORGANIZATION IS LINE SEQUENTIAL
FILE STATUS IS IN-FILE-STATUS.
DATA DIVISION.
FILE SECTION.
FD IN-FILE.
01 IN-RECORD PIC X(80).
WORKING-STORAGE SECTION.
01 IN-FILE-STATUS PIC XX.
01 END-OF-FILE-SWITCH PIC XXX VALUE 'NO '.
88 END-OF-FILE VALUE 'YES'.
01 STRING-MARKER-LEFT PIC X(7) VALUE 'LENGTH('.
01 STRING-MARKER-RIGHT PIC X VALUE ')'.
01 STRING-MARKER-LENGTH PIC 99 USAGE BINARY.
01 STRING-INDEX PIC 99 USAGE BINARY.
01 START-INDEX PIC 99 USAGE BINARY.
01 END-INDEX PIC 99 USAGE BINARY.
01 RECORD-LENGTH PIC 99 USAGE BINARY.
01 SEARCH-LENGTH PIC 99 USAGE BINARY.
01 IS-END-FOUND PIC XXX VALUE 'NO '.
88 END-FOUND VALUE 'YES'.
88 END-NOT-FOUND VALUE 'NO '.
PROCEDURE DIVISION.
MAIN.
OPEN INPUT IN-FILE
IF IN-FILE-STATUS NOT = '00'
DISPLAY 'FILE READ ERROR ' IN-FILE-STATUS
END-DISPLAY
PERFORM EXIT-PROGRAM
END-IF
PERFORM INITIALIZE-LENGTHS
PERFORM UNTIL END-OF-FILE
READ IN-FILE
AT END
SET END-OF-FILE TO TRUE
NOT AT END
PERFORM FIND-STRING
END-READ
END-PERFORM
PERFORM EXIT-PROGRAM
.
INITIALIZE-LENGTHS.
MOVE FUNCTION LENGTH(IN-RECORD) TO RECORD-LENGTH
COMPUTE STRING-MARKER-LENGTH = FUNCTION LENGTH(
STRING-MARKER-LEFT)
END-COMPUTE
COMPUTE SEARCH-LENGTH = RECORD-LENGTH - STRING-MARKER-LENGTH
END-COMPUTE
.
FIND-STRING.
PERFORM VARYING STRING-INDEX FROM 1 BY 1
UNTIL STRING-INDEX > SEARCH-LENGTH
IF IN-RECORD(STRING-INDEX:STRING-MARKER-LENGTH) =
STRING-MARKER-LEFT
COMPUTE START-INDEX = STRING-INDEX
+ STRING-MARKER-LENGTH
END-COMPUTE
SET END-NOT-FOUND TO TRUE
PERFORM VARYING END-INDEX FROM START-INDEX BY 1
UNTIL END-INDEX > RECORD-LENGTH OR END-FOUND
IF IN-RECORD(END-INDEX:
FUNCTION LENGTH(STRING-MARKER-RIGHT)) =
STRING-MARKER-RIGHT
SET END-FOUND TO TRUE
END-IF
END-PERFORM
COMPUTE END-INDEX = END-INDEX - START-INDEX - 1
END-COMPUTE
DISPLAY IN-RECORD(START-INDEX:END-INDEX)
END-DISPLAY
END-IF
END-PERFORM
.
EXIT-PROGRAM.
CLOSE IN-FILE
STOP RUN
.

Standard way to remove spaces from input in cobol?

I'm just learning COBOL; I'm writing a program that simply echos back user input. I have defined a variable as:
User-Input PIC X(30).
Later when I ACCEPT User-Input, then DISPLAY User-Input " plus some extra text", it has a bunch of spaces to fill the 30 characters. Is there a standard way (like Ruby's str.strip!) to remove the extra spaces?
One would hope for a more elegant way of simply trimming text strings
but this is pretty much the standard solution... The trimming part
is done in the SHOW-TEXT paragraph.
*************************************
* TRIM A STRING... THE HARD WAY...
*************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. TESTX.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 USER-INPUT PIC X(30).
01 I PIC S9(4) BINARY.
PROCEDURE DIVISION.
MOVE SPACES TO USER-INPUT
PERFORM SHOW-TEXT
MOVE ' A B C' TO USER-INPUT
PERFORM SHOW-TEXT
MOVE 'USE ALL 30 CHARACTERS -------X' TO USER-INPUT
PERFORM SHOW-TEXT
GOBACK
.
SHOW-TEXT.
PERFORM VARYING I FROM LENGTH OF USER-INPUT BY -1
UNTIL I LESS THAN 1 OR USER-INPUT(I:1) NOT = ' '
END-PERFORM
IF I > ZERO
DISPLAY USER-INPUT(1:I) '# OTHER STUFF'
ELSE
DISPLAY '# OTHER STUFF'
END-IF
.
Produces the following output:
# OTHER STUFF
A B C# OTHER STUFF
USE ALL 30 CHARACTERS -------X# OTHER STUFF
Note that the PERFORM VARYING statement relies on the left to
right evaluation of the UNTIL clause to avoid out-of-bounds
subscripting on USER-INPUT in the case where it contains only
blank spaces.
Use OpenCOBOL 1.1 or greater.
Identification division.
Program-id. 'trimtest'.
*> Compile:
*> cobc -x -free -ffunctions-all TrimTest.cbl
*>
Data division.
Working-Storage Section.
1 myBigStr Pic X(32768) Value Spaces.
Procedure Division.
Display "Enter Something? " With no advancing.
Accept myBigStr.
Display "[" Trim(myBigStr) "]".
Goback.
The trim function also has the options; Leading or Trailing.
cobc -h formore info.
Here's a solution if you work on OpenVMS:
01 WS-STRING-LENGTH PIC S9(04) COMP.
CALL "STR$TRIM" USING BY DESCRIPTOR user_output,
user_input,
BY REFERENCE WS-STRING-LENGTH.
a more general solution:
01 length pic 99.
perform varying length from 1 by 1
until length > 30 or user-input[length] = space
end-perform.
if length > 30
display user-input 'plus some extra text'
else
display user-input[1:length] 'plus some extra text'
end-if.
untested, I don't have a compiler at hand at the moment
There are three ways you can do this.
Use the COBOL functions to determine the string's "length". This is a mix of a couple functions. This is my preferred method, but requires declaring extra variables.
Write your own function to get the "length".
Use knowledge of a "terminating" string. You have to know what key characters indicates an end-of-string, like three spaces or a low-value character.
This example code demonstrates all three.
IDENTIFICATION DIVISION.
PROGRAM-ID. TESTPROG.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 ONE-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 ONE-A-TLY PIC 9(02) VALUE ZERO.
01 ONE-A-LEN PIC 9(02) VALUE ZERO.
01 ONE-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 ONE-B-TLY PIC 9(02) VALUE ZERO.
01 ONE-B-LEN PIC 9(02) VALUE ZERO.
01 TWO-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 TWO-A-LEN PIC 9(02) VALUE ZERO.
01 TWO-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 TWO-B-LEN PIC 9(02) VALUE ZERO.
01 THREE-A PIC X(20) VALUE 'RALPH WIGGAM'.
01 THREE-B PIC X(20) VALUE 'LIKES LEARNDING'.
01 THREE-C PIC X(80) VALUE SPACES.
PROCEDURE DIVISION.
DISPLAY ' -- METHOD ONE -- '
INSPECT FUNCTION REVERSE(ONE-A)
TALLYING ONE-A-TLY FOR LEADING SPACES.
SUBTRACT ONE-A-TLY FROM LENGTH OF ONE-A GIVING ONE-A-LEN.
INSPECT FUNCTION REVERSE(ONE-B)
TALLYING ONE-B-TLY FOR LEADING SPACES.
SUBTRACT ONE-B-TLY FROM LENGTH OF ONE-A GIVING ONE-B-LEN.
DISPLAY ONE-A(1:ONE-A-LEN)
' ' ONE-B(1:ONE-B-LEN)
'.'.
DISPLAY ' -- METHOD TWO -- '
PERFORM VARYING TWO-A-LEN FROM LENGTH OF TWO-A BY -1
UNTIL TWO-A-LEN < 1 OR TWO-A(TWO-A-LEN:1) > SPACE
END-PERFORM.
PERFORM VARYING TWO-B-LEN FROM LENGTH OF TWO-B BY -1
UNTIL TWO-B-LEN < 1 OR TWO-B(TWO-B-LEN:1) > SPACE
END-PERFORM.
DISPLAY TWO-A(1:TWO-A-LEN)
' ' TWO-B(1:TWO-B-LEN)
'.'.
DISPLAY ' -- METHOD THREE, NAIVE -- '
* DELIMITING BY JUST ANY SPACES ISN'T GOOD ENOUGH.
STRING THREE-A DELIMITED BY SPACES
' ' DELIMITED BY SIZE
THREE-B DELIMITED BY SPACES
'.' DELIMITED BY SIZE
INTO THREE-C.
DISPLAY THREE-C.
DISPLAY ' -- METHOD THREE, OK -- '
STRING THREE-A DELIMITED BY ' '
' ' DELIMITED BY SIZE
THREE-B DELIMITED BY ' '
'.' DELIMITED BY SIZE
INTO THREE-C.
DISPLAY THREE-C.
EXIT-PROG.
STOP RUN.
and the output looks like this:
-- METHOD ONE --
RALPH WIGGAM LIKES LEARNDING.
-- METHOD TWO --
RALPH WIGGAM LIKES LEARNDING.
-- METHOD THREE, NAIVE --
RALPH LIKES.
-- METHOD THREE, OK --
RALPH WIGGAM LIKES LEARNDING.

Resources