So I have entered my second semester of College and they have me doing a course called Advanced COBOL. As one of my assignments I have to my make a program that tests certain things in a file to make sure the input has no errors. I get the general idea but there are just a few things I don't understand and my teacher is one of those people who will give you an assignment and make you figure it out yourself with little or no help. So here is what I need help with.
I have a field that the first 5 columns have to be numbers, the 6th column a capital letter and the last 2 numbers in a range of 01-68 or 78-99.
one of my fields has to be a string of numbers with a dash in it like 00000-000, but some have more than one dash. How can I count the dashes to identify that there is a problem.
Here are a few hints...
Use a hieratical record structure to view the data in different ways. For example:
01 ITEM-REC.
05 ITEM-CODE.
10 ITEM-NUM-CODE PIC 9(3).
10 ITEM-CHAR-CODE PIC A(3).
88 ITEM-TYPE-A VALUE 'AAA' THRU 'AZZ'.
88 ITEM-TYPE-B VALUE 'BAA' THRU 'BZZ'.
05 QUANTITY PIC 9(4).
ITEM-CODE is a 6 character group field, the first part of which is numeric (ITEM-NUM-CODE) and the last part
is alphabetic (ITEM-CHAR-CODE). You can refer to any one of these three variables in your program. When you
refer to ITEM-CODE, or any other group item, COBOL
treats the variable as if it were declared as PIC X. This means you can
MOVE just about anything into it without raising an error. For example:
MOVE 'ABCdef' TO ITEM-CODE
or
MOVE 'ABCdef0005' TO ITEM-REC
Neither one would cause an error even though the elementary data item ITEM-NUM-CODE is definitely not a number.
To verify the validity
of your data after a group move you should validate each elementary data item separately (unless
you know for certain no data type errors could have occurred). There are a variety of ways to do this. For
example if the data item has to be numeric the following would work:
IF ITEM-NUM-CODE IS NUMERIC
CONTINUE
ELSE
DISPLAY 'ITEM-NUM-CODE IS NOT NUMERIC'
PERFORM BIG-BAD-ERROR
END-IF
COBOL provides various class tests which can be applied against a data item. For
example: NUMERIC, ALPHABETIC and ALPHANUMERIC are commonly used.
Another common way to test for ranges of values is by defining various 88 levels - but exercise
caution. In the above
example ITEM-TYPE-A is an 88 level that defines a data range from 'AAA' through 'AZZ' based on
the collating sequence currently in effect. To verify that ITEM-CHAR-CODE contains only alphabetic
characters and the first letter is an 'A' or a 'B', you could do something like:
IF ITEM-CHAR-CODE ALPHABETIC
DISPLAY 'ITEM-CHAR-CODE is alphabetic.'
EVALUATE TRUE
WHEN ITEM-TYPE-A
DISPLAY 'ITEM-CHAR-CODE is in range AAA through AZZ'
WHEN ITEM-TYPE-B
DISPLAY 'ITEM-CHAR-CODE is in range BAA through BZZ'
WHEN OTHER
DISPLAY 'ITEM-CHAR-CODE is in some other range'
END-EVALUATE
ELSE
DISPLAY 'ITEM-CHAR-CODE is not alphabetic'
END-IF
Note the separate test for ALPHABETIC above. Why do that when the 88 level tests
could have done the job? Actually the 88's are not sufficient because they
cover the entire range from AAA through AZZ based on the collating sequence currently
in effect. In
an EBCDIC based environment (a very large number of COBOL shops use EBCDIC) this captures
values such as A}\. the close-brace and backslash characters are non-alpha but
fall into the middle of
the range 'A' through 'Z' (what the #*#! is that all about?). Also note that a value such
as 'aaa' would not satisfy the ITEM-TYPE-A condition because lower case letters fall outside
the defined range. Maybe time to check out an EBCDIC character table.
Finally, you can count the number of occurrences of a character, or string of characters, in
a variable with the INSPECT verb as follows:
INSPECT ITEM-CODE TALLING DASH-COUNT FOR ALL '-'
DASH-COUNT needs to be a numeric item and will contain the number of dash characters in ITEM-CODE. The INSPECT
verb is not so useful if you want to count the number of digits. For this you would need one statement for each digit.
It might be easier to just code a loop something like:
PERFORM VARYING I FROM 1 BY 1
UNTIL I > LENGTH OF ITEM-CODE
EVALUATE ITEM-CODE(I:1)
WHEN '-'
COMPUTE DASH-COUNT = DASH-COUNT + 1
WHEN '0' THRU '9'
COMPUTE DIGIT-COUNT = DIGIT-COUNT + 1
WHEN OTHER
COMPUTE OTHER-COUNT = OTHER-COUNT + 1
END-EVALUATE
END-PERFORM
Now ask yourself why I was comfortable using a zero through 9 range check? Hint: look at the collating sequence.
Hope this helps.
Related
i have string as ' #$rahul ' and i have to calculate number of alpha bates without using inspect verb. Also not using by ord clause for ASCII value. My instructor told me to use empty array but how it is used?? I tried but it counts for symbols also.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TABLE.
05 WS-A OCCURS 3 TIMES INDEXED BY I.
10 WS-B PIC A(2).
10 WS-C OCCURS 2 TIMES INDEXED BY J.
15 WS-D PIC X(3).
PROCEDURE DIVISION.
MOVE '####DEF34GHIJKL56MNOPQR' TO WS-TABLE.
PERFORM A-PARA VARYING I FROM 1 BY 1 UNTIL I >3
STOP RUN.
A-PARA.
PERFORM C-PARA VARYING J FROM 1 BY 1 UNTIL J>2.
C-PARA.
if ws-table(1) equals to spaces
continue
else
add +1 to ws-count
end-if
DISPLAY WS-C(I,J).
Apart from your table-definition and actual use of the table, you have basically got the idea already, except you are not sure what, specifically, to test for.
What you need to do is find the section in your COBOL documentation on class condition and class tests.
I suspect this bit of code:
if ws-table(1) equals to spaces
continue
else
add +1 to ws-count
end-if
Has been added in haste. With your data, ws-table(1) will never be space, and ws-count is not defined.
Back to your definition. You are defining a structure with three parts (WS-A OCCURS 3) each of which consists of a two-byte alphabetic field followed by two three-byte alphanumeric fields. That definition is of no direct use to your task.
01 the-data.
05 FILLER OCCURS 24 TIMES
INDEXED BY data-byte-index.
10 the-data-byte PIC X.
That will allow you to look at each byte individually. Note that you can always use good names, which will make your programs easier to understand, reduce the chance of careless errors, and make people's lives, including your own when you return to a program some time later, generally easier.
Note, you can also use reference-modification and lose out on the readability for the benefit of less typing.
Format of your program
Unless it is dictated to you (and although I've never seen it before in over 30 years, I have seen it a couple of time recently) there is absolutely no point in "indenting" things like the WORKKING-STORAGE section, or even paragraph/SECTION labels. They already have all the indentation they need, and further indentation adds nothing, which requiring more typing, and also causing experienced COBOL programmers to wonder why you are doing that.
Since the 1985 Standard for COBOL, the use of full-stops/periods in the PROCEDURE DIVISION is greatly relaxed. Since a full-stop/period in the wrong place can cause errors, this was a good thing. It will also be good if you take full advantage of it. Commas look far too much like full-stops/periods to be of any use in code. They never have to be there, so having them benefits nothing. Also noise-words like THEN can/should be avoided. Unlike commas, spacing can be a boon to the format of a program.
Here's your code above, reformatted:
MOVE '####DEF34GHIJKL56MNOPQR'
TO WS-TABLE
PERFORM A-PARA
VARYING I
FROM 1
BY 1
UNTIL I > 3
STOP RUN
.
A-PARA.
PERFORM C-PARA
VARYING J
FROM 1
BY 1
UNTIL J > 2
.
C-PARA.
if ws-table ( 1 ) equal to space
continue
else
add +1 to ws-count
end-if
DISPLAY
WS-C ( I J )
.
Use some proper names, and it's start to look like a real program.
Note, not all people agree on how a program should be formatted. Seriously.
The value in variable VAR is -1, and when I am trying to write to a file, it gets displayed as J(character mode), which is equivalent to -1.
The VAR is defined in Cobol program copybook as below:
10 VAR PIC S9(1).
Is there any way, to change the display format from character "J" to -1, in the output file.
The information which I found by googling is below:
Value +0 Character {
Value -0 Character }
Value +1 Character A
To convert the zoned ASCII field which results from an EBCDIC to ASCII character translation to a leading sign numeric field, inspect the last digit in the field. If it's a "{" replace the last digit with a 0 and make the number positive. If it's an "A" replace the last digit with a 1 and make the number positive, if it's a "B" replace the last digit with a 2 and make the number positive, etc., etc. If the last digit is a "}" replace the last digit with a 0 and make the number negative. If it's a "J" replace the last digit with a 1 and make the number negative, if it's a "K" replace the last digit with a 2 and make the number negative, etc., etc. Follow these rules for all possible values. You could do this with a look-up table or with IF or CASE statements. Use whatever method suits you best for the language you are using. In most cases you should put the sign immediately before the first digit in the field. This is called a floating sign, and is what most PC programs expect. For example, if your field is 6 bytes, the value -123 should read " -123" not "- 123".
It might be simpler to move it to an EBCDIC output (display) field so that its just EBCDIC characters, and then convert that to ASCII and write it.
For example
10 VAR PIC S9(1).
10 WS-SEPSIGN PIC S9(1) SIGN IS LEADING SEPARATE.
10 WS-DISP REDEFINES WS-SEPSIGN
PIC XX.
MOVE VAR TO WS-SEPSIGN.
Then convert WS-OUT to ASCII using a standard lookup table and write it to the file.
If you are sending data from an EBCDIC machine to an ASCII machne, or vice versa, by far the best way is to only deal with character data. You can then let the transfer/communication mechanism do the ASCII/EBCDIC translation at record/file level.
Field-level translation is possible, but is much more prone to error (fields must be defined, accurately, for everything) and is slower (many translations versus one).
The SIGN clause is a very good way to do this. There is no need to REDEFINES the field (again you get to issues with field-definitions, two places to change if the size is changed).
There is a similar issue with decimal places where they exist. Where source and data definitions are not the same, an explicit decimal-point has to be provided, or a separate scaling-factor.
Both issues, and the original issue, can also be dealt with by using numeric-edited definitions.
01 transfer-record.
...
05 numeric-edited-VAR1 PIC +9.
...
With positive one, that will contain +1, with negative one, that will contain -1.
Take an amount field:
01 VAR2 PACKED-DECIMAL PIC S9(7)V99.
...
01 transfer-record.
...
05 numeric-edited-VAR2 PIC +9(7).99.
...
For 4567.89, positive, the new field will contain +0004567.79. For the same value, but negative, -0004567.79.
The code on the Source-machine is:
MOVE VAR1 TO numeric-edited-VAR1
MOVE VAR2 TO numeric-edited-VAR2
And on the target (in COBOL)
MOVE numeric-edited-VAR1 TO VAR1
MOVE numeric-edited-VAR2 TO VAR2
The code is the same if you use the SIGN clause for fields without decimal places (or with decimal places if you want the danger of being implicit about it).
Another thing with field-level translation is that Auditors don't/shouldn't like it. "The first thing you do when the data arrives is you change it? Really?" says the Auditor.
I'm in my second quarter of college and taking "Advanced COBOL" we just received an assignment that requires us to code in some validation procedures for different data. I have everything done except on small validation procedure.
There is a field called "PART-NUMBER" that is 8 bytes long. The first 5 columns must be a number. The 6th column must be a capital letter and the last 2 columns must be in the range of 01-68 or 78-99. The only problem I have is figuring out how to validate that the 6th column is capital.
Here is the code I am using:
From working storage:
01 DETAIL-LINE.
05 PART-NUMBER.
10 PART-FIRST-FIVE-DL PIC X(5).
10 PART-LETTER-DL PIC X.
88 CAPITAL-LETTER VALUE 'A' THRU 'Z'.
10 PART-LAST-TWO-DL PIC XX.
From 300-VALIDATE-PART-NUMBER
EVALUATE PART-LETTER-DL ALPHABETIC
WHEN TRUE EVALUATE CAPITAL-LETTER
WHEN FALSE MOVE 'YES' TO RECORD-ERROR-SWITCH
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-EVALUATE
WHEN FALSE MOVE 'YES' TO RECORD-ERROR-SWITCH
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-EVALUATE
I know I'm probably not doing this in a very efficient way but for now I just need to get it to work. I've read the whole chapter on data validation from the book and this is sort of a last minute error (program is due tomorrow) so the teacher is unavailable. I would greatly appreciate any help I can get with this. I'm really lost on how I'm supposed to validate capital letters. The method I'm using now reports an error if anything other than A or Z is in the 6th column of the part number.
I don't see anything fundamentally wrong with your code. I put it into a
driver program, compiled and ran it. I got the expected results: Error reported only
when the 6th character of PART-NUMBER was not an upper case letter.
Your COBOL coding style is very different from what I am used to seeing (not wrong, just
different).
Most veteran COBOL programmers would code something like:
IF PART-LETTER-DL IS ALPHABETIC AND
CAPITAL-LETTER
CONTINUE
ELSE
MOVE 'PART NUMBER' TO FIELD-NAME
MOVE PART-NO-IN TO FIELD-VALUE
MOVE 'YES' TO PART-NO-ERROR
END-IF
The IF applies both of your edit criteria and does nothing if both pass (CONTINUE), otherwise
an error is reported (ELSE part). The above does essentially the same thing your code
example does except using IF as opposed to EVALUATE.
I give you full marks for testing both ALPHABETIC and capital letter
using an 88 level range (THRU). A lot of programmers would only use the 88 level, making the
implicit assumption that 'A' THRU 'Z' covers only alphabetic characters - this is dead wrong
in some environments (EBCDIC character sets in particular).
P.S. I see you guys must have the same teacher that Kimmy had!
One thing you should be concerned about is the "Value 'A' thru 'Z'". It will only work on ASCII machines.
If you actually code Value 'A', 'B', 'C', ... 'Z'. It will work on all platforms.
For capital letters you can test the ALPHABETIC-UPPER condition:
IF PART-LETTER-DL NOT EQUAL SPACE AND PART-LETTER-DL IS ALPHABETIC-UPPER
...
END-IF.
ALPHABETIC-LOWER can be used too, but remember that SPACE is considered ALPHABETIC, so testing SPACE is necessary, if you just want capital letters.
For EBCDIC, drop the ALPHABETIC test and just use the 88:
88 CAPITAL-LETTER VALUE 'A' THRU 'I'
'J' THRU 'R'
'S' THRU 'Z'.
Specifying individual letters works, but generates 26 comparisons! The above generates three. The ALPHABETIC plus 'A' THRU 'Z' only two, but does carry some in-built confusion (space is alphabetic, and the THRU includes non-printable digits in the range X'C1' to X'E9').
I'm running some tests on Cobol pictures and wondering if --- is a valid picture. Am I right in saying that this picture accepts values in the range of -99 through to +99. If it is valid then it is possible for the picture to accept 3 spaces as a value?
For example:
12 would return 12
1 would return 1
Cheers
Yes --- is a valid PICTURE clause. The variable corresponding to this PICTURE will accept assignments of numeric values in the range -99 through to +99. It cannot be assigned non-numerics (space for example). However, if you were to DISPLAY this variable after assigning a numeric value to it, leading zeros will be replaced by spaces. Consequently, if you MOVE ZERO to this item it will DISPLAY only spaces. Attempting to MOVE SPACES to this item will result in a compile error (incompatible data types). This last bit may seem a little counter intutive, but remember that this type of PICTURE clause implies a USAGE of display - basically items defined in this manner are used to 'pretty print' numbers. About the only operations you can preform with USAGE DISPLAY items is MOVE to or from and DISPLAY them.
EDIT - Response to Comment
A PICTURE of ---X(2) is invalid. The chart below illustrates combinations and the order that symbols may appear in a PICTURE string. Notice that parenthesis are not in the chart. Logically you can replace them with the corresponding number of occurences of the preceding character before reading the string. For example X(3) is read as XXX. If you really want to parse out a PICTURE string properly, you can use this chart to construct a BNF grammar specifically for them.
If this is a numeric picture, it won't accept spaces.
I have some csv record which are variable in length , for example:
0005464560,45667759,ZAMTR,!To ACC 12345678,DR,79.85
0006786565,34567899,ZAMTR,!To ACC 26575443,DR,1000
I need to seperate each of these fields and I need the last field which should be a money.
However, as I read the file, and unstring the record into fields, I found that the last field contain junk value at the end of itself. The amount(money) field should be 8 characters, 5 digit at the front, 1 dot, 2 digit at the end. The values from the input could be any value such as 13.5, 1000 and 354.23 .
"FILE SECTION"
FD INPUT_FILE.
01 INPUT_REC PIC X(66).
"WORKING STORAGE SECTion"
01 WS_INPUT_REC PIC X(66).
01 WS_AMOUNT_NUM PIC 9(5).9(2).
01 WS_AMOUNT_TXT PIC X(8).
"MAIN SECTION"
UNSTRING INPUT_REC DELIMITED BY ","
INTO WS_ID_1, WS_ID_2, WS_CODE, WS_DESCRIPTION, WS_FLAG, WS_AMOUNT_TXT
MOVE WS_AMOUNT_TXT(1:8) TO WS_AMOUNT_NUM(1:8)
DISPLAY WS_AMOUNT_NUM
From the display, the value is rather normal: 345.23, 1000, just as what are, however, after I wrote the field into a file, here is what they become:
79.85^M^#^#
137.35^M^#
I have inspect the field WS_AMOUNT_NUM, which came from the field WS_AMOUNT_TXT, and found that ^# is a kind of LOW-VALUE. However, I cannot find what is ^M, it is not a space, not a high-value.
I am guessing, but it looks like you may be reading variable length records from a file into a fixed length
COBOL record. The junk
at the end of the COBOL record is giving you some grief. Hard to say how consistent that junk is going
to be from one read to the next (data beyond the bounds of actual input record length are technically
undefined). That junk ends up
being included in WS_AMOUNT_TXT after the UNSTRING
There are a number of ways to solve this problem. The suggestion I am giving you here may not
be optimal, but it is simple and should get the job done.
The last INTO field, WS_AMOUNT_TXT, in your UNSTRING statement is the one that receives all of the trailing
junk. That junk needs to be stripped off. Knowing that the only valid characters in the last field are
digits and the decimal character, you could clean it up as follows:
PERFORM VARYING WS_I FROM LENGTH OF WS_AMOUNT_TXT BY -1
UNTIL WS_I = ZERO
IF WS_AMOUNT_TXT(WS_I:1) IS NUMERIC OR
WS_AMOUNT_TXT(WS_I:1) = '.'
MOVE ZERO TO WS_I
ELSE
MOVE SPACE TO WS_AMOUNT_TXT(WS_I:1)
END-IF
END-PERFORM
The basic idea in the above code is to scan from the end of the last UNSTRING output field
to the beginning replacing anything that is not a valid digit or decimal point with a space.
Once a valid digit/decimal is found, exit the loop on the assumption that the rest will
be valid.
After cleanup use the intrinsic function NUMVAL as outlined in my answer to your
previous question
to convert WS_AMOUNT_TXT into a numeric data type.
One final piece of advice, MOVE SPACES TO INPUT_REC before each READ to blow away data left over
from a previous read that might be left in the buffer. This will protect you when reading a very "short"
record after a "long" one - otherwise you may trip over data left over from the previous read.
Hope this helps.
EDIT Just noticed this answer to your question about reading variable length files. Using a variable length input record is a better approach. Given the
actual input record length you can do something like:
UNSTRING INPUT_REC(1:REC_LEN) INTO...
Where REC_LEN is the variable specified after OCCURS DEPENDING ON for the INPUT_REC file FD. All the junk you are encountering occurs after the end of the record as defined by REC_LEN. Using reference modification as illustrated above trims it off before UNSTRING does its work to separate out the individual data fields.
EDIT 2:
Cannot use reference modification with UNSTRING. Darn... It is possible with some other COBOL dialects but not with OpenVMS COBOL. Try the following:
MOVE INPUT_REC(1:REC_LEN) TO WS_BUFFER
UNSTRING WS_BUFFER INTO...
Where WS_BUFFER is a working storage PIC X variable long enough to hold the longest input record. When you MOVE a short alpha-numeric field to a longer one, the destination field is left justified with spaces used to pad remaining space (ie. WS_BUFFER). Since leading and trailing spaces are acceptable to the NUMVAL fucnction you have exactly what you need.
I have a reason for pushing you in this direction. Any junk that ends up at the trailing end of a record buffer when reading a short record is undefined. There is a possibility that some of that junk just might end up being a digit or a decimal point. Should this occur, the cleanup routine I originally suggested would fail.
EDIT 3:
There are no ^# in the resulting WS_AMOUNT_TXT, but still there are a ^M
Looks like the file system is treating <CR> (that ^M thing) at the end of each record as data.
If the file you are reading came from a Windows platform and you are now
reading it on a UNIX platform that would explain the problem. Under Windows records
are terminated with <CR><LF> while on UNIX they are terminated with <LF> only. The
UNIX file system treats <CR> as if it were part of the record.
If this is the case, you can be pretty sure that there will be a single <CR> at the
end of every record read. There are a number of ways to deal with this:
Method 1: As you already noted, pre-edit the file using Notepad++ or some other
tool to remove the <CR> characters before processing through your COBOL program.
Personally I don't think this is the best way of going about it. I prefer to use a COBOL
only solution since it involves fewer processing steps.
Method 2: Trim the last character from each input record before processing it. The last
character should always be <CR>. Try the following if you
are reading records as variable length and have the actual input record length available.
SUBTRACT 1 FROM REC_LEN
MOVE INPUT_REC(1:REC_LEN) TO WS_BUFFER
UNSTRING WS_BUFFER INTO...
Method 3: Treat <CR> as a delimiter when UNSTRINGing as follows:
UNSTRING INPUT_REC DELIMITED BY "," OR x"0D"
INTO WS_ID_1, WS_ID_2, WS_CODE, WS_DESCRIPTION, WS_FLAG, WS_AMOUNT_TXT
Method 4: Condition the last receiving field from UNSTRING by replacing trailing
non digit/non decimal point characters with spaces. I outlined this solution a litte earlier in this
question. You could also explore the INSPECT statement using the REPLACING option (Format 2). This should be able to do pretty much the same thing - just replace all x"00" by SPACE and x"0D" by SPACE.
Where there is a will, there is a way. Any of the above solutions should work for you. Choose the one you are most comfortable with.
^M is a carriage return.
Would Google Refine be useful for rectifying this data?