Wednesday, May 18, 2016

Moving from Output specs to Printer files

printer files versus rpg output specifications

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:

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:

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:

  1. HDR – Page headings that only needs to be printed at the top of each page.
  2. DTL – The detail line, which is printed many times on each page.
  3. 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:

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.

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.

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.

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



  1. Print files is still DDS which really need to go away. A better solution is waiting since IBM introduced DDL to replace DDS for PF and LF. Just like IBM need a better solution to GUI than Display Files using DDS that need those gawd-awful screen scrapers.

    1. There is always UIM as an alternative to display files. It has been 15+ years since I last built a UIM list panel.

    2. Will DDL really replace DDS? As I was learning the "code first" method for Microsoft's Entity Framework recall thinking "this is like DDS!". It's helpful, I think, to have "source" definition for a file (granted DDL contains a definition but it doesn't seem as absolute, somehow). Sorry that this went off topic.

  2. there won't be a replacement ..... if you want to generate an other printable output you should write a stmf

  3. Nice overview, Simon. Thanks for sharing it here.
    Finally! I can see the post that I was looking for last three years...!
    Just to share with you, I have also created an APIs to convert the internal PRTF and make them External PRTF.
    Also, did the same for Internal DB files.

  4. Another way to handle page overflow is to use the OFLIND keyword on the printer file F spec, so in your example the printer file definition would be: dcl-f PRINTERF printer indds(IndDs) oflind(PageOverflow). This eliminates the need to check for an error on the Write statement and manually turn on the overflow indicator since it will be turned on automatically when the page reaches the overflow line number.

  5. I sometimes use sda to design a printer file, then tweak the dds to make it a prtf.
    Simple reports I use qmqry and forms.

  6. RDi does a very nice job with PRTF, it will manage all of the relative SKIP and SPACE keywords and you can just design the report in a WYSWYG fashion.

    Or for all the details

    Edmund Reinhardt

  7. Hi Simon,

    you wrote, that there are tools to convert O-specs to externally described printer files. could you mention which are they?

    1. I always refrain from mentioning third party tools in this blog as I do not want to it to be perceived as a platform for promoting software, rather than a source of information about IBM i.

      If you contact me, via the Contact Form, I will reply to you with the tool I use.

  8. Hi Simon, pgmds doesnt have psds ? does it still work to get the pgm name and user name?

  9. Simon, i verified, without PSDS, program and user cant be moved !

    1. That is a typo on my part. You need the PSDS so the compiler know that this is the program data structure.

  10. You can create a program described printer file samw way as diak file for e.g. dcl-f printerf1 printer(132) ind...;
    And declare a ds over printer file and its sub field as per your usage . Use the ds to write record in printer file . This is helpfull if yoy have to create a simple spool file. For detailed one best to create a seperate printer file as use that.


To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.