Start of change

Example of a DATA-GEN generator

Note: Detailed explanation is provided only for the aspects of the example that are related to the DATA-GEN operation.

In this example, a generator generates an HTML table for the DATA-GEN operation.

If you want to try running the code in the example, see SQL statements to create the file used by the example for the SQL statements to create the file used by the program.

See Output generated by the DATA-GEN operations in the example for the HTML generated by the program.

RPG program with DATA-GEN operations

The following shows the RPG program that uses the DATA-GEN operation.

Note the following aspects of the program:
  1. The data structures are defined with a subfield for each row expected in the HTML table.

    The externally-described data structure uses EXTFLD statements to set the case of the names required for the column headings in the HTML table. For the ITEMPRICE field, the EXTFLD statement also adds an underscore. The generator replaces underscores with blanks when generating the column headings.

  2. For the first three DATA-GEN operations, the output file is specified in the first operand of the %DATA built-in function. Option "doc=file" indicates that the first operand is the name of a file.
  3. The program that does the generation is specified as the first operand of the %GEN built-in function. See Program to generate an HTML table for the source for the program. The program that does the generation supports an optional character or UCS-2 value as the second operand of the %GEN built-in function. This value is used as the caption for the table.
  4. The DATA-GEN *START operation starts a DATA-GEN sequence.
  5. The next DATA-GEN operation continues the sequence. It writes out a row in the HTML table.
  6. The DATA-GEN *END operation ends the sequence.
  7. The final DATA-GEN is not part of a sequence. The result of the DATA-GEN operation is put in the variable specified as the first operand of the %DATA built-in function.

**free

DCL-C FILENAME 'MYORDERS';
DCL-F orders EXTDESC(FILENAME)
             EXTFILE(*EXTDESC);
DCL-DS order EXTNAME(FILENAME : *INPUT)             //  1 
             QUALIFIED;
   Name extfld('NAME');
   Type extfld('ITEMTYPE');
   Item_Price extfld('ITEMPRICE');
END-DS;
DCL-DS customer QUALIFIED;                          //  1 
   Name VARCHAR(30);
   Address VARCHAR(100);
   Zip_Code PACKED(9);
END-DS;

DCL-S customerTable VARCHAR(1000);

DATA-GEN *START %DATA('order.html' : 'doc=file')    //  2, 4 
                %GEN('GENHTMLTAB'
                   : 'Order for ' + %CHAR(%DATE()));//  3 

READ orders order;
DOW NOT %EOF;
   DATA-GEN order %DATA('order.html'
                      : 'doc=file output=continue') //  2, 5 
                  %GEN('GENHTMLTAB');               //  3 
   READ orders order;
ENDDO;

DATA-GEN *END %DATA('order.html' : 'doc=file')      //  2, 6 
              %GEN('GENHTMLTAB');                   //  3 

customer.Name = 'A. Smith';
customer.Address = '123 Elm Street';
customer.Zip_Code = 11111;
DATA-GEN customer %DATA(customerTable)              //  7 
                  %GEN('GENHTMLTAB');               //  3 

*INLR = '1';

Program to generate an HTML table

Note the following aspects of the initial section of the module:

  1. This generator is a program.
  2. Since the name and value subfields of the parameter passed to the generator have UTF-16 data, it is convenient to set the default CCSID for UCS-2 items to UTF-16.
  3. Copy member QRNDTAGEN in file QOAR/QRPGLESRC defines the parameter passed to the generator and named constants for other information needed by the generator.
  4. The state_t data structure define information used by the generator to keep track of the generation.
  5. Several error codes and matching messages are defined for the errors detected by this generator.

**free

CTL-OPT OPTION(*SRCSTMT);
CTL-OPT MAIN(genHtmlTab);            //  1 
CTL-OPT CCSID(*UCS2 : *UTF16);       //  2 
/IF DEFINED(*CRTBNDRPG)
   CTL-OPT DFTACTGRP(*NO);
/ENDIF

/copy QOAR/QRPGLESRC,QRNDTAGEN       //  3 

DCL-C MAX_CAPTION 10000;

DCL-DS state_t qualified template;   //  4 
   haveHeader IND;
   inDataStructure IND;
   dataStructureName LIKE(QrnDgName_t);
   numSubfields INT(10);
   caption VARUCS2(MAX_CAPTION);
   haveCaption IND;
END-DS;

DCL-DS errorCodes qualified;         //  5 
   DCL-DS nestedStructNotAllowed;
      code INT(10) INZ(1);
      msg VARCHAR(100) INZ('Nested structures are not allowed.');
   END-DS;
   DCL-DS differentStruct;
      code INT(10) INZ(2);
      msg VARCHAR(100)
          INZ('The data structure is not the same.');
   END-DS;
   DCL-DS eventNotSupported;
      code INT(10) INZ(3);
      msg VARCHAR(100) INZ('The event is not supported.');
   END-DS;
   DCL-DS valueNotInStruct;
      code INT(10) INZ(4);
      msg VARCHAR(100) INZ('All values must be subfields.');
   END-DS;
   DCL-DS userParmTypeNotSupported;
      code INT(10) INZ(4);
      msg VARCHAR(100) INZ('The user-parm must be UTF-16 or alphanumeric with the job CCSID.');
   END-DS;
END-DS;

Main procedure

Note the following aspects of the procedure:

  1. A single parameter is passed to the generator.
  2. The state data structure holds state information maintained by the generator for all calls to the generator for the DATA-GEN operation or the sequence of DATA-GEN operations.

    The data structure is based on a pointer, since the storage for the data structure will be allocated from the heap.

  3. Setting pointer pEnv from the env subfield of the parameter passed to the generator allows the generator to call the callback procedures
    Warning: The env pointer is null for the QrnDgEvent_12_Terminate event.

    Do not attempt to call any callback procedures during this event.

  4. The generator needs to deallocate the state pointer when the generation is complete, so it enables the QrnDgEvent_12_Terminate event.
  5. Callbacks are not allowed during the QrnDgEvent_12_Terminate event, so the generator just deallocates the state pointer and returns.
  6. If the state information has not been allocated yet, the generator allocates and initializes the generatorParm pointer in the parameter passed to the generator.
  7. The generator sets the basing pointer for the state information from the generatorParm pointer.
  8. This event signals the beginning of a sequence of DATA-GEN operations.

    If the second operand of %GEN was specified, it is passed in the userParm subfield of the parameter passed to the generator. The generator will call the getCaption procedure to get the value of userParm.

  9. This event signals the end of a sequence of DATA-GEN operations. The generator generates the end of the HTML table.
  10. This event signals the beginning of the events for a DATA-GEN operation related to a variable.

    If the second operand of %GEN was specified, it is passed in the userParm subfield of the parameter passed to the generator. If the caption has not already been determined by a previous call to the generator, the generator will call the getCaption procedure to get the value of userParm.

  11. This event signals the end of the events for a DATA-GEN operation related to a variable. If the DATA-GEN operation is not part of a sequence of DATA-GEN operations, the generator generates the end of the HTML table.
  12. These events signal the beginning of an array of data structures or a single data structure. Since this might be the first time the generator has seen anything related to a data structure, it calls procedure genStartTable to generate the header for the HTML table, if necessary.

    If the genStartTable procedure detects an error in the calls to the generator, it will report the error to DATA-GEN, and control will not return to the statement following the call to the genStartTable procedure.

  13. This event signals the end of an array of data structures. This generator does not need to do anything during this event.
  14. The generator generates the beginning of a row in the HTML table.
  15. This event signals the end of a data structure. This generator generates the end of the row in the HTML table.
  16. This event signals a scalar value. If the generator detects that it is not a subfield, it issues an error.
  17. The generator generates the beginning of the column in the HTML table.
  18. The generator calls the QrnDgAddText callback procedure to generate the value for the column. The value parameter of the parameter passed to the generator points to UTF-16 data and the valueLenChars subfield has the number of double-byte characters in the UTF-16 data. These parameters can be passed directly to the QrnDgAddText procedure, since it expects a pointer to UTF-16 data and the number of double-byte characters.
  19. The generator generates the end of the column in the HTML table.
  20. The generator does not support any other events.

DCL-PROC genHtmlTab;
   DCL-PI *N;
      parm LIKEDS(QrnDgParm_t);                        //  1 
   END-PI;

   DCL-DS state LIKEDS(state_t) based(pState);         //  2 

   pQrnDgEnv = parm.env;                               //  3 

   parm.doTerminateEvent = *ON;                        //  4 

   IF parm.event = QrnDgEvent_12_Terminate;
      DEALLOC(N) parm.generatorState;                  //  5 
      RETURN;
   ENDIF;

   IF parm.generatorState = *NULL;                     //  6 
      parm.generatorState = %ALLOC(%SIZE(state_t));
      pState = parm.generatorState;
      CLEAR state;
   ENDIF;
   pState = parm.generatorState;                       //  7 

   IF parm.event = QrnDgEvent_01_StartMultiple;        //  8 
      IF parm.userParm <> *NULL;
         state.caption = getCaption (parm : state);
         state.haveCaption = *ON;
      ENDIF;

   ELSEIF parm.event = QrnDgEvent_02_EndMultiple;      //  9 
      genEndTable (parm : state);

   ELSEIF parm.event = QrnDgEvent_03_Start;            //  10 
      IF parm.userParm <> *NULL
      AND state.haveCaption = *OFF;
         state.caption = getCaption (parm : state);
         state.haveCaption = *ON;
      ENDIF;

   ELSEIF parm.event = QrnDgEvent_04_End;              //  11 
      IF NOT parm.isPartOfSequence;
         genEndTable (parm : state);
      ENDIF;

   ELSEIF parm.event = QrnDgEvent_09_StartStructArray; //  12 
      IF parm.userParm <> *NULL
      AND state.haveCaption = *OFF;
         state.caption = getCaption (parm : state);
         state.haveCaption = *ON;
      ENDIF;

      genStartTable (parm : state : parm.array.numSubfields);
      // Control may not return here

   ELSEIF parm.event = QrnDgEvent_10_EndStructArray;  //  13 

   ELSEIF parm.event = QrnDgEvent_05_StartStruct;      //  12 
      IF parm.userParm <> *NULL
      AND state.haveCaption = *OFF;
         state.caption = getCaption (parm : state);
         state.haveCaption = *ON;
      ENDIF;

      genStartTable (parm : state : parm.ds.numSubfields);
      // Control might not return here

      state.inDataStructure = *ON;                     //  14 
      writeLine (parm : '<tr>');

   ELSEIF parm.event = QrnDgEvent_06_EndStruct;        //  15 
      writeLine (parm : '</tr>');
      state.inDataStructure = *OFF;

   ELSEIF parm.event = QrnDgEvent_11_ScalarValue;      //  16 
      IF NOT state.inDataStructure;
         error (parm : state
              : errorCodes.valueNotInStruct.code
              : errorCodes.valueNotInStruct.msg);
         // Control will not return here
      ENDIF;

      writeLine (parm : '<td>' : *ON);           //  17 

      // The text for the column is written out in the same
      // UTF-16 CCSID as it was passed to the generator
      QrnDgAddText (parm.handle                        //  18 
                  : parm.scalar.value
                  : parm.scalar.valueLenChars);
      writeLine (parm : '</td>');                //  19 

   ELSE;
      error (parm : state                              //  20 
           : errorCodes.eventNotSupported.code
           : errorCodes.eventNotSupported.msg);
      // Control will not return here
   ENDIF;
END-PROC genHtmlTab;

getCaption procedure

This procedure gets the value for the table's caption from the userParm subfield of the parameter passed to the generator. This subfield is set from the second parameter of the %GEN built-in function for the DATA-GEN operation.

Note the following aspects of the procedure:

  1. The parameter passed to the generator has all the information a generator needs to interpret the userParm subfield if the second operand of %GEN was a string type. See Types of the user-parameter passed to the DATA-GEN generator.

    However, this generator does not support all possible types. It does not support alphanumeric values if the CCSID is not the job CCSID. It does not support UCS-2 values if the CCSID is not UTF-16.

    The userParm data structure is based on the parm.userParm pointer. A subfield is defined at position 1 of the data structure for every type of string that this generator supports.

  2. The %STR built-in function returns the value of a null-terminated string.
  3. If the alphanumeric value is in the job CCSID, the generator determines the length from the parm.userParmSize subfield, and uses that length to control the amount of data returned by the %SUBST built-in function.
  4. If the alphanumeric value is in the job CCSID, the generator does not need to use the parm.userParmSize information, because the varying-length field holds its own length in its varying-length prefix.
  5. For a UCS-2 or UTF-16 value, the parm.userParmSize must be divided by 2 to determine the number of characters for the %SUBST built-in function.
  6. If the type or CCSID of the user-parameter is not supported by the generator, the generator issues an error.

DCL-PROC getCaption;
   DCL-PI *N VARUCS2(10000) EXTPROC(*DCLCASE);
      parm LIKEDS(QrnDgParm_t);
      state LIKEDS(state_t);
   END-PI;

   DCL-DS userParm QUALIFIED BASED(P);         //  1 
      charFixed CHAR(MAX_CAPTION) POS(1);
      charVar2 VARCHAR(MAX_CAPTION) POS(1);
      charVar4 VARCHAR(MAX_CAPTION:4) POS(1);
      utf16Fixed UCS2(MAX_CAPTION) POS(1);
      utf16Var2 VARUCS2(MAX_CAPTION) POS(1);
      utf16Var4 VARUCS2(MAX_CAPTION:4) POS(1);
   END-DS;
   DCL-S CAPTION VARUCS2(MAX_CAPTION);
   DCL-S LEN INT(10);

   p = parm.userParm;
   IF parm.userParmType = QrnUserParmType_nullTerminatedString; //  2 
      caption = %STR(parm.userParm);
   ELSEIF parm.userParmType = QrnUserParmType_char              //  3 
   AND parm.userParmCcsid = QrnDg_JOB_CCSID;
      len = parm.userParmSize;
      caption = %TRIM(%SUBST(userParm.charFixed : 1 : len));
   ELSEIF parm.userParmType = QrnUserParmType_varchar_2         //  4 
   AND parm.userParmCcsid = QrnDg_JOB_CCSID;
      caption = userParm.charVar2;
   ELSEIF parm.userParmType = QrnUserParmType_varchar_4
   AND parm.userParmCcsid = QrnDg_JOB_CCSID;
      caption = userParm.charVar4;
   ELSEIF parm.userParmType = QrnUserParmType_ucs2
   AND parm.userParmCcsid = 1200;
      len = parm.userParmSize / 2;                              //  5 
      caption = %TRIM(%SUBST(userParm.utf16Fixed : 1 : len));
   ELSEIF parm.userParmType = QrnUserParmType_varucs2_2
   AND parm.userParmCcsid = 1200;
      caption = userParm.utf16var2;
   ELSEIF parm.userParmType = QrnUserParmType_varucs2_4
   AND parm.userParmCcsid = 1200;
      caption = userParm.utf16var4;
   ELSE;
      error (parm : state
           : errorCodes.userParmTypeNotSupported.code
           : errorCodes.userParmTypeNotSupported.msg);
      // Control will not return here
   ENDIF;
   RETURN caption;

END-PROC getCaption;

genStartTable procedure

This procedure generates the header for the HTML table, using the subfield names of the RPG data structure.

Note the following aspects of the procedure:

  1. If the generator is already processing a data structure, the generator raises an error condition.
  2. If the generator has already generated the header, and the name or the number of subfields of the current data structure is different from the data structure used to generate the header, the generator raises an error condition.
  3. The generator sets the name and number of subfields of the current data structure in its state information, so it can ensure that the data structures it encounters are all the same.
  4. The generator generates the beginning of the HTML table.
  5. The generator generates a row in the header of the HTML table for each subfield of the data structure.
  6. The generator calls the QrnDgGetSubfieldName callback procedure to obtain the name of the subfield.
  7. The generator generates the end of the header for the HTML table.

DCL-PROC genStartTable;
   DCL-PI *n extproc(*dclcase);
      parm LIKEDS(QrnDgParm_t);
      state LIKEDS(state_t);
      numSubfields int(10) value;
   END-PI;
   DCL-S i INT(10);

   IF state.inDataStructure;                        //  1 
      error (parm : state
           : errorCodes.nestedStructNotAllowed.code
           : errorCodes.nestedStructNotAllowed.msg);
      // Control will not return here
   ENDIF;

   if state.haveHeader;
      if parm.name <> state.dataStructureName      //  2 
      or numSubfields <> state.numSubfields;
         error (parm : state
              : errorCodes.nestedStructNotAllowed.code
              : errorCodes.nestedStructNotAllowed.msg);
      endif;

      return;
   endif;

   state.dataStructureName = parm.name;            //  3 
   state.numSubfields = numSubfields;
   state.haveHeader = *ON;

   writeLine (parm                                 //  4 
            : '<table border="1">');

   if state.haveCaption;
      writeLine (parm : '<caption>' : *on);
      QrnDgAddText (parm.handle
                  : %addr(state.caption : *data)
                  : %len(state.caption));
      writeLine (parm : '</caption>');
   endif;

   writeLine (parm
            : '<thead>'
            + '<tr>');

   FOR i = 1 TO numSubfields;                      //  5 
      writeLine (parm
              : '<td><b>'
              : *ON); // skip the newline for this output
      genColumnName (parm
                   : state
                   : QrnDgGetSubfieldName (parm.handle : i));  //  6 
      writeLine (parm : '</b></td>');
   ENDFOR;

   writeLine (parm                                 //  7 
            : '</tr>'
            + '</thead>'
            + '<tbody>');

END-PROC genStartTable;

genColumnName procedure

This procedure generates the name of a column from the name of a subfield.

Note the following aspects of the procedure:

  1. This procedure expects a subfield to have its words separated by underscores, with the words capitalized as required for the table; for example Column_Heading. To create the column name, this procedure changes the underscores to spaces.

    See the code for the program using this DATA-GEN generator to see how it defines the subfield names.

  2. The columnName variable is a varying length UTF-16 variable, so the generator can use the QrnDgAddText callback procedure to generate the name for the column.

DCL-PROC genColumnName;
   DCL-PI *n extproc(*dclcase);
      parm LIKEDS(QrnDgParm_t);
      state LIKEDS(state_t);
      name like(QrnDgName_T) const;
   END-PI;
   DCL-S columnName LIKE(name);
   DCL-C underscore %UCS2('_');
   DCL-C BLANK %UCS2(' ');

   columnName = %XLATE(UNDERSCORE : BLANK : name);       //  1 

   QrnDgAddText (parm.handle                             //  2 
               : %ADDR(columnName : *DATA)
               : %LEN(columnName));
END-PROC genColumnName;

genEndTable procedure

This procedure generates the end of the HTML table.


DCL-PROC genEndTable;
   DCL-PI *n extproc(*dclcase);
      parm LIKEDS(QrnDgParm_t);
      state LIKEDS(state_t);
   END-PI;

   writeLine (parm
            : '</tbody>'
            + '</table>');
END-PROC genEndTable;

writeLine procedure

This procedure generates text in the job CCSID.

Note the following aspects of the procedure:

  1. The generator determines whether it should generate a new-line after it generates the text. It first uses the outputIsToFile subfield of the parameter passed to the generator to determine whether the generated text is intended for a stream file. If not, then a new-line is not needed.

    However, the caller may have indicated that this procedure should not generate a new-line in the optional skipNewLine parameter.

  2. Since the text is in the job CCSID, the generator can use the simple QrnDgAddTextString callback procedure to output the text.
  3. The generator adds the new-line character using the QrnDgAddTextsNewLine callback procedure.

DCL-PROC writeLine;
   DCL-PI *N EXTPROC(*DCLCASE);
      parm LIKEDS(QrnDgParm_t);
      line pointer VALUE OPTIONS(*STRING);
      skipNewLine IND CONST OPTIONS(*NOPASS);
   END-PI;
   DCL-S doNewLine IND INZ(*ON);

   doNewLine = parm.outputIsToFile;                //  1 
   IF %PARMS() >= %PARMNUM(skipNewLine)
   AND skipNewLine;
      doNewLine = *OFF;
   ENDIF;

   QrnDgAddTextString (parm.handle : line);        //  2 
   IF doNewLine;
      QrnDgAddTextNewline (parm.handle);           //  3 
   ENDIF;
END-PROC writeLine;

error procedure

This procedure raises an error condition which will cause the DATA-GEN operation to fail.

Note the following aspects of the procedure:

  1. This procedure first outputs a trace message using the QrnDgTrace callback procedure. If the user is tracing the DATA-GEN operation, the trace message will explain the error code that will appear next in the trace.
  2. The QrnDgReportError callback procedure is used to report the error condition. *ON is passed for the nested parameter, indicating that the trace message should be nested within other information in the trace.

    Control will not return to this procedure after the call to the QrnDgReportError procedure. The generator will get called again for the final QrnDgEvent_12_Terminate event.


DCL-PROC error;
   DCL-PI *n extproc(*dclcase);
      parm LIKEDS(QrnDgParm_t);
      state LIKEDS(state_t);
      errorCode INT(10) VALUE;
      errorMessage VARCHAR(100) CONST;
   END-PI;

   QrnDgTrace (parm.handle                         //  1 
             : errorMessage
             : '1');

   QrnDgReportError (parm.handle                   //  2 
                   : errorCode);
   // Control will not return here

END-PROC error;

SQL statements to create the file used by the example


CREATE TABLE QGPL/MYORDERS
   (NAME VARCHAR (25 ) NOT NULL WITH DEFAULT,
   ITEMTYPE VARCHAR (25 ) NOT NULL WITH DEFAULT,
   ITEMPRICE DECIMAL (9 , 2) NOT NULL WITH DEFAULT)

INSERT INTO QGPL/MYORDERS
    VALUES('Refrigerator', 'Appliance', 525.95)

INSERT INTO QGPL/MYORDERS
    VALUES('Shirt', 'Clothing', 5.95)

INSERT INTO QGPL/MYORDERS
    VALUES('Rake', 'Gardening', 15.95)

Output generated by the DATA-GEN operations in the example

The following is the HTML table generated by the DATA-GEN sequence.


<table border="1">
<caption>Order 2019-11-15</caption>
<thead><tr>
<td><b>Name</b></td>
<td><b>Type</b></td>
<td><b>Item Price</b></td>
</tr></thead><tbody>
<tr>
<td>Refrigerator</td>
<td>Appliance</td>
<td>525.95</td>
</tr>
<tr>
<td>Shirt</td>
<td>Clothing</td>
<td>5.95</td>
<td>5.95</td>
</tr>
<tr>
<td>Rake</td>
<td>Gardening</td>
<td>15.95</td>
</tr>
</tbody></table>

The following shows how the table appears:

Table 1. x
Name Type Item Price
Refrigerator Appliance 525.95
Shirt Clothing 5.95
Rake Gardening 15.95

The following shows the value of the customerTable variable after the final DATA-GEN operation in the program, as shown in the debugger.


> EVAL customerTable
  CUSTOMERTABLE =
            ....5...10...15...20...25...30...35...40...45...50...55...60
       1   '<table border="1"><thead><tr><td><b>Name</b></td><td><b>Addr'
      61   'ess</b></td><td><b>Zip Code</b></td></tr></thead><tbody><tr>'
     121   '<td>A. Smith</td><td>123 Elm Street</td><td>11111</td></tr><'
     181   '/tbody></table>                                             '

The following shows how the table appears:

Table 2. x
Name Address Zip Code
A. Smith 123 Elm Street 11111
End of change