The end of the usefulness RPG's Output specifications, O-specs, was signaled by IBM when they did not give a free format equivalent when they gave us "free format definition" RPG, IBM i 7.1 TR7 in November 2013. If you start programming in "fully free" RPG, 7.2 TR1 and 7.1 TR11 November 2014, you will find that you cannot easily use non-free format code, such as O-specs. So this is the time to move away from O-specs and embrace printer files.
I am writing this post in response to a couple of people who contacted me asking about the differences in coding printer files, to O-specs, and how to write the RPG to use them. I know that there are tools to convert O-specs to externally described printer files, and I have used a couple myself. But this post is going to assume that this is a "vanilla" configuration of IBM i with none of those tools present.
I wrote two programs to produce the same report.
The first was written in RPGIII and used the RPG cycle:
OName++++DFBASbSaN01N02N03Excnam O................N01N02N03Field+YBEnd+PConstant/editword+++++++++ 01 OQSYSPRT H 1 1P 02 O OR OF 03 O PGM 10 04 O USER + 1 05 O 67 'REPORT TITLE' 06 O TIMEX 123 '0 : : ' 07 O UDATE Y + 1 08 O H 1 1P 09 O OR OF 10 O 12 'ACCOUNT NO.' 11 O + 1 'ACCOUNT NAME' 12 O 49 'STATE' 13 O 127 'PAGE:' 14 O PAGE Z + 1 15 O H 1 1P 16 O OR OF 17 O 12 '-----------' 18 O + 1 '------------------------' 19 O + 0 '------' 20 O + 1 '-----' * 21 O E 1 DTL 22 O ACCNBRZ 12 23 O ACCNME + 1 24 O ACCSTE + 3 25 O 01 + 2 '<- INVALID STATE' * 26 O T 2 LR 27 O 21 '*** END OR REPORT ***'
The second was in RPGLE where I use the Except operation code:
OFilename++DF..N01N02N03Excnam++++B++A++Sb+Sa+ O..............N01N02N03Field+++++++++YB.End++PConstant/editword/DTformat 01 OQSYSPRT E HDR 1 02 O PgmDs.Pgm 10 03 O PgmDs.User + 1 04 O 67 'REPORT TITLE' 05 O RptTime 120T*hms 06 O RptDate + 1D*usa 07 O E HDR 1 08 O 12 'Account No.' 09 O + 1 'Account Name' 10 O 49 'State' 11 O 127 'Page:' 12 O PAGE Z + 1 13 O E HDR 1 14 O 12 '-----------' 15 O + 1 '------------------------' 16 O + 0 '------' 17 O + 1 '-----' 18 O E DTL 1 19 O ACCNBR Z 12 20 O ACCNME + 1 21 O ACCSTE + 3 22 O 01 + 1 '<- Invalid state' 23 O E EOR 2 24 O 21 '*** END OR REPORT ***'
When adding the individual fields to the O-specs I have to remember that the number I enter is the end of the field. Fortunately I can position fields in relation others by using the likes of "+ 1". Other codes become a bit cryptic as you have to know which column the code is in to know what it does. For example the "Z" on line 14 of the first example code and line 12 of the second example is the edit code for the numeric field. Or the format I wish to display the time and date, in the second example, on lines 5 and 6.
A printer file's code in DDS is very similar to a display file's. It can contain one or more record formats. In the examples there are three sets of information that should be placed in their own records formats:
- HDR – Page headings that only needs to be printed at the top of each page.
- DTL – The detail line, which is printed many times on each page.
- EOR – Is only printed once at the end of the report.
I am not going to go into details about how to code a printer file. As I said, if you are familiar with the DDS for display files you can guess/assume what most the information you need to provide. IBM also provides a manual called "Programming DDS for printer files" that I have provided a link to at the bottom of this post.
If you are using PDM when you create the source member the source type needs to be "PRTF".
Work with Members Using PDM Opt Member Type Text __ PRINTERF PRTF Test printer file
My equivalent of these O-specs as a Printer file would be:
AAN01N02N03T.Name++++++RLen++TDpBLinPosFunctions++++++++++++++++++++++++ 01 A INDARA 02 A REF(TESTFILE) *----------------------------------------------------------------------- 03 A R PRHEADER 04 A PROGRAM 10A 1SKIPB(001) 05 A USER 10A +1 06 A 56'REPORT TITLE' 07 A 113TIME 08 A +1DATE(*YY) 09 A EDTCDE(Y) *--- 10 A 1'Account No.' 11 A HIGHLIGHT 12 A UNDERLINE 13 A SPACEB(001) 14 A +1'Account Name ' 15 A HIGHLIGHT 16 A UNDERLINE 17 A +1'State' 18 A HIGHLIGHT 19 A UNDERLINE 20 A 123'Page' 21 A +1PAGNBR 22 A EDTCDE(Z)
You will also notice that lines 1 and 2 do not belong to any of the record formats. Like with Display files these keywords apply to the entire file.
Line 1: This is the definition to use the Indicator data area to communicate between the printer file and the program using it. If you are unfamiliar with using the Indicator data area you need to read the post about not using number indicators, it uses Displays as examples but the same rules apply if using Printer files.
Line 2: The REF tells the compile to use the fields in the file TESTFILE for the definitions of the fields on lines 24, 26, and 27.
Line 3: Rather than use the except name HDR I coded what I think is a more meaningful name for the record format, PRHEADER.
Line 4: Most of this line should not come as a surprise to any programmer who has seen the DDS for a Display file. The unique thing is the SKIPB(001). This indicates that I want to skip to the first line of the next page before starting to print this line.
Line 5: Alas, with Printer files I need my program to get the User id and move it to this fields. In Display files USER is a reserved keyword, which it gets from the system without me. But this is not available for Printer files.
Line 7: I do not have to pass the time to the Printer file, as I do with O-specs. By using TIME keyword the Printer file gets the system time for me.
Line 8: The same is true with the date if I use the DATE keyword. DATE(*YY) means that I want to use a date that includes the century, not just the two digit year.
Line 9: As the date field is really a number I am using an Edit code, EDTCDE(Y), to edit the presentation of the value in this field.
Lines 10 – 19: These are the column headings. The first one needs the SPACEB(001) so that the printing operation knows to "advance one line" before printing this line. I have also use the UNDERLINE for each column, I am sure I do not have to describe what this does. HIGHLIGHT means to bold the column heading. I have found that if I just use the underline it hides the column heading text when I view the spool file on the IBM i. If I use the highlight the column heading text is displayed.
Lines 20 – 22: To display the page number I use the keyword PAGNBR, and in this print I want to suppress any leading zeros which is why I have used the edit code "Z".
Rather than use the SKIPB and SPACEB I could have hard coded the line that each of the fields are on, see the example below. The only drawback I have found to this method is that I cannot use the relative positioning, indicated with the "+1", I have to give the specific start position.
AAN01N02N03T.Name++++++RLen++TDpBLinPosFunctions++++++++++++++++++++++ 03 A R PRHEADER 04 A PROGRAM 10A 1 1 05 A USER 10A 1 12 06 A 1 56'REPORT TITLE' 07 A 1113TIME 08 A 1122DATE(*YY) 09 A EDTCDE(Y) *--- 10 A 2 1'Account No.' 11 A HIGHLIGHT 12 A UNDERLINE 13 A 2 18'Account Name 14 A HIGHLIGHT 15 A UNDERLINE 16 A 2 44'State' 17 A HIGHLIGHT 18 A UNDERLINE 19 A 2123'Page' 20 A 2128PAGNBR 21 A EDTCDE(Z)
Personally I prefer using the method shown in the first example.
The record format to print the detail lines is very simple, see below. There is nothing in it that should now be a surprise.
AAN01N02N03T.Name++++++RLen++TDpBLinPosFunctions++++++++++++++++++++++ 23 A R PRDETAIL 24 A ACCNBR R O 5EDTCDE(Z) 25 A SPACEB(001) 26 A ACCNME R O +1 27 A ACCSTE R O +3 28 A 01 +1'<- Invalid state'
The same is true with the PREND member. I always add these to each report. This way the user knows if they have all the report or not by the presence of the "end of report" line.
AAN01N02N03T.Name++++++RLen++TDpBLinPosFunctions+ 29 A R PREND 30 A 1'*** END OF REPORT ***' 31 A SPACEB(002)
Creating printer files is easy using Rational Developer for IBM i (RDi). If you have to use the IBM i based tools you have to either use SEU, to type the lines of DDS code, or you create your desired layout using Report Layout Utility, RLU. I know that there are other tools available from other vendors, but as I mentioned at the top of this post this is for a "vanilla" IBM i.
Personally, I think that RLU is the ugliest and nastiest tool that has ever came out on the IBM i. Having designed something as simple and intuitive as SDA, why did IBM not copy that to create a printer file editor? I have given a link to the RLU manual at the bottom of this post as you are going to need help to use it. Most of the time I would create the printer file's source in SEU, but if I had to use RLU I would edit the source it would generate with SEU to remove all the nonsense RLU inserts into the member and the bad formatting it does of the code.
I use the CRTPRTF command to compile my source into a printer file object.
And now to the RPG code to write my report using the printer file. I wrote this program in fully free RPG. For those of you still stuck with having to use fixed form definitions can see the equivalent of the definitions here
01 **free 02 dcl-f TESTFILE keyed ; 03 dcl-f PRINTERF printer indds(IndDs) ; 04 dcl-ds IndDs qualified ; 05 InvalidState ind pos(1) ; 06 end-ds ; 07 dcl-ds PgmDs psds qualified ; 08 Procedure char(10) pos(1) ; 09 User char(10) pos(254) ; 10 end-ds ; 11 dcl-s PageOverflow ind ; 12 PROGRAM = PgmDs.Procedure ; 13 USER = PgmDs.User ; 14 write PRHEADER ; 15 dow (1 = 1) ; 16 read TESTFILER ; 17 if (%eof) ; 18 leave ; 19 elseif (PageOverflow) ; 20 write PRHEADER ; 21 PageOverflow = *off ; 22 endif ; 23 if (ACCSTE = 'XX') ; 24 IndDs.InvalidState = *on ; 25 else ; 26 IndDs.InvalidState = *off ; 27 endif ; 28 write(e) PRDETAIL ; 29 if (%error) ; 30 PageOverflow = *on ; 31 endif ; 32 enddo ; 33 write(e) PREND ; 34 *inlr = *on ;
Line 3: The printer file does not have to be defined for USAGE(*OUTPUT) as it is assumed. If you think about it printer files are always output only. The INDDS keyword needs to be given as it gives the name of the Indicator data structure I am using for this file.
Lines 4 – 6: This is the indicator data structure. The indicator *IN01 has been defined as the variable InvalidState, which is a much more meaningful name than *IN01.
Lines 7 – 10: This is the Program data structure. I am getting the procedure and user names to use in the printer file from here.
Line 11: I am have defined this indicator variable as its name clearly states its purpose.
Line 14: I write the first page headings by using the WRITE operation code followed by the record format name.
Lines 15 – 32: This is my Do-loop within which all the records are read from TESTFILE.
Line 19: If the indicator Pageoverflow is on the page headings are written and the indicator is set off. If you are unfamiliar with the ELSEIF operation code then you need to read this.
Lines 23 – 27: If the state code is "XX" then the indicator InvalidState is set on, as the Indicator data structure is qualified I need prefix the indicator's name with the data structures name. The value in InvalidState is mapped to *IN01 and will be sent to the printer file.
Line 28: The write to the Detail record format has the error operation extender (E). When the end of the page is encountered during the write the %ERROR special indicator is set on. Without the operation extender an error would happen, and the program will halt.
Lines 29 – 31: If the %ERROR indicator is on set on PageOverflow, to indicate that the program needs to advance to the next page.
Line 33: When all the records have been read from TESTFILE, and the Do loop has been exited, I write the "end of report" record format. I do need the operation extender just in case this line causes the page to overflow.
By using printer files I can use the same file in multiple programs, or I can have variations of the same printer file in different libraries to produce reports with different layouts that all use the same RPG program.
I hope that with this very simple example you will refer to the IBM resources below, and make your own move from O-specs to printer files. You owe it to yourself if you are going to keep your RPG skills current.
You can learn more about this from the IBM website:
This article was written for IBM i 7.2, and should work for earlier releases too.
Fixed format definitions for RPG example
01 FTESTFILE IF E K DISK 02 FPRINTERF O E PRINTER indds(IndDs) 03 D IndDs DS qualified 04 D InvalidState 1 1N 05 D PgmDs SDS qualified 06 D Procedure 1 10 07 D User 254 263 08 D PageOverflow S N /free