Creating an RXS Template

To generate XML, RPG API Express relies on what is referred to as the “template engine” or “composition engine”. This engine uses a specially marked up psuedo-XML file divided into sections and with variable fields embedded in it as well as a set of RPG API Express subprocedures. The combination of the template file and the subprocedures allow you to build XML of any complexity or depth needed to meet your business requirements.

This tutorial will demonstrate using the following commands in sequence to generate a compiled XML template:

Once our compiled template is generated, we will also write a short example program.

Generating the IFS Template File (BLDTPL)

To start with creating a template file, we first need to create a file in the IFS. This file will be populated with our sample XML data, which will be used to generate the template. The command below uses QSHELL to create an IFS stream file using CCSID 819. This CCSID is strongly recommended when creating IFS files for use with RXS tools. Note that this command is case-sensitive.

QSH CMD('touch -C 819 /www/myrxs/templates/postadr.xml')

Note that, for the purposes of this tutorial, we’re generating this example file in the /www/myrxs/templates directory. For many of our customers, this directory was created during their initial installation of RPG API Express. This is not the only valid location for template files in the IFS - you can generate and store your XML and template files in any directory.

Next, we need to populate this file with a sample of the XML that we’re going to use as the basis of the template. This sample document should be as complete as possible. If your XML has many optional fields, try to include them all in this sample file, even if it some of them would not logically appear with others in normal usage. To get the XML into our IFS stream file, we’ll use the EDTF command:

EDTF '/www/myrxs/templates/postadr.xml'

Now, paste your sample XML into the resulting editor window. This sample XML may or may not contain data in any or all fields - this will not impact the template output.

<PostAdr residential="true">
  <name title="Mx.">
    <first>Jamie</first>
    <last>Hale</last>    
  </name>
  <street>2886 Veltri Dr</street>
  <city>Hickory Hills</city>
  <state>VA</state>
  <zip>94124</zip>
  <phone>949-555-4671</phone>
  <phone></phone>
</PostAdr>

While in the Edit File editor select F2 to save the document changes. Once the sample XML file is saved, call the BLDTPL command and specify the fully-qualified filepath for the sample XML file in the first parameter, and the fully-qualified filepath for where the generated .tpl template file should be written in the second parameter:

BLDTPL SRCSTMF('/www/myrxs/templates/postadr.xml') OUTSTMF('/www/myrxs/templates/postadr.tpl') INDENT(2)

The third parameter, INDENT, is optional and controls how many spaces are used to indent each nested element in the generated template file. The default value is 2.

There are two primary components to an RPG API Express XML template file:

  1. Variables are placeholders used to indicate where data will be composed into the XML document by the RXS_ComposeVariable() subprocedure. These are declared with the format .:variable:..
  2. Sections are used to denote a “chunk” of the XML document that should be written to the buffer in a single call to RXS_ComposeSection(). Sections are declared with the format ::section. Each template must include a minimum of one section, and the first line in any template must be a section declaration. Sections are not automatically generated in the BLDTPL output - you will need to manually add them yourself based on your knowledge and understanding of how the document is meant to be composed and would best be divided.

Below is the generated template file located at /www/myrxs/templates/postadr.tpl - use EDTF to view it. Note that all elements and attributes have had their values replaced with the variable placeholders respective to the element or attribute’s name. Also note that there are no sections present in the generated output.

<PostAdr residential=".:residential:.">
  <name title=".:title:.">
    <first>.:first:.</first>
    <last>.:last:.</last>
  </name>
  <street>.:street:.</street>
  <city>.:city:.</city>
  <state>.:state:.</state>
  <zip>.:zip:.</zip>
  <phone>.:phone:.</phone>
</PostAdr>

An important thing to note is that in the generated template file, there is only one <phone> element, where before there were two. Repeating elements are generally handled using sections, as described below, rather than duplicating lines in the template.

Now that our template is generated with the variable placeholders, we need to add our sections. We’ll need to add at least one section at the beginning of the document. Since our <phone> element is a repeating element, we will give that its own section as well. Finally, we’ll add a closing section to write the end tags for the document.

Here is an example of our edited template file, including the sections we’ve added:

::PostAdr_beg
<PostAdr residential=".:residential:.">
  <name title=".:title:.">
    <first>.:first:.</first>
    <last>.:last:.</last>
  </name>
  <street>.:street:.</street>
  <city>.:city:.</city>
  <state>.:state:.</state>
  <zip>.:zip:.</zip>
::Phone
  <phone>.:phone:.</phone>
::PostAdr_end
</PostAdr>

By giving the <phone> element a separate section, it gives us the flexibility to write as many of those elements as we need by calling RXS_ComposeVariable() to populate the phone variable, then RXS_ComposeSection() to write the Phone section for that phone number.

Not all sections in an XML document need to be used during XML composition. Sections of your XML may be optional - unused depending upon your business logic or a specific request’s needs. In our example, if we did not have a phone number for a given address, we may not compose the Phone section for that entry at all.

Generating the Compiled RPG Template (CRTRPGTPL)

Now that we have our IFS template file set up with our sections, we are going to use the CRTRPGTPL command to convert our template file into an RPG source member. This RPG source member will be compiled directly into your RPG API Express-enabled program using the /COPY compiler directive.

For demonstration purposes, we will generate the compiled template into the QRPGLETPL source physical file in the RXS library. It is strongly recommended that you never store your code members in any RXS library.

Note: the command will specify a name of *FIRST for the member by default. You need to change this value - we generally recommend specifying the same name as the program you’re going to use this template with, or the name of the IFS template file.

CRTRPGTPL STMF('/www/myrxs/templates/postadr.tpl') FILE(RXS/QRPGLETPL) MBR(POSTADR) SECTIONSDS(S) VARSDS(V)

It’s also important to note that we’re specifying values for SECTIONSDS and VARSDS. These parameters are not set by default, but specifying values allows CRTRPGTPL to build qualified data structures for the section and variable names. This helps improve code readability and helps keep the template engine fields separate from your normal RPG code. This is not required, but very strongly recommended.

Once this command has completed, you can open up the source physical file RXS/QRPGLETPL and look at member POSTADR using PDM or RDi. It should look like this:



 /IF DEFINED(RXS_CREATION_COMMAND)
 *
 * DO NOT MODIFY THIS SOURCE MEMBER!
 * TO MAKE CHANGES, UPDATE YOUR .tpl IFS FILE AND RUN CRTRPGTPL
 *
 * Template Create Date: 2019-08-29
 *
 * This template was last created via:
 *
 * CRTRPGTPL STMF('/ktprod/template/postadr.tpl')
 *   FILE(JEN/QRPGLETPL) MBR(POSTADR)
 *   SECTIONSDS(S)
 *   VARSDS(V)
 *
 /ENDIF

 /IF NOT DEFINED(POSTADR)
 /DEFINE POSTADR
D S...
D                 DS                  Qualified
D  PostAdr_beg...
D                               50A   Varying
D                                     Inz('PostAdr_beg')
D  Phone...
D                               50A   Varying
D                                     Inz('Phone')
D  PostAdr_end...
D                               50A   Varying
D                                     Inz('PostAdr_end')
D V...
D                 DS                  Qualified
D  residential...
D                               30A   Varying
D                                     Inz('residential')
D  title...
D                               30A   Varying
D                                     Inz('title')
D  first...
D                               30A   Varying
D                                     Inz('first')
D  last...
D                               30A   Varying
D                                     Inz('last')
D  street...
D                               30A   Varying
D                                     Inz('street')
D  city...
D                               30A   Varying
D                                     Inz('city')
D  state...
D                               30A   Varying
D                                     Inz('state')
D  zip...
D                               30A   Varying
D                                     Inz('zip')
D  phone...
D                               30A   Varying
D                                     Inz('phone')
 /ELSE
 /FREE
  reset S;
  reset V;

  p  = '';
  p += '::PostAdr_beg' + x'15';
  p += '<PostAdr residential="' + '.:residential:.' + '">' + x'15';
  p += '  <name title="' + '.:title:.' + '">' + x'15';
  p += '    <first>' + '.:first:.' + '</first>' + x'15';
  p += '    <last>' + '.:last:.' + '</last>' + x'15';
  p += '  </name>' + x'15';
  p += '  <street>' + '.:street:.' + '</street>' + x'15';
  p += '  <city>' + '.:city:.' + '</city>' + x'15';
  p += '  <state>' + '.:state:.' + '</state>' + x'15';
  p += '  <zip>' + '.:zip:.' + '</zip>' + x'15';
  p += '::Phone' + x'15';
  p += '  <phone>' + '.:phone:.' + '</phone>' + x'15';
  p += '::PostAdr_end' + x'15';
  p += '</PostAdr>' + x'15';

 /END-FREE
 /ENDIF

As you can see, the resulting template file is quite literally RPG code. The section and variable names have been added to the S and V data structures, and the template content itself is included below in the format expected by the XML composition engine. One of the benefits of this approach is that, because the template section and variable names are RPG fields, it’s very easy to catch typos in your program because the program simply won’t compile.

Note: It is imperative that you never edit or modify the source member generated by the CRTRPGTPL command in any way, unless directed to do so by a member of our support staff. If you need to make changes to your template, first update the corresponding .tpl file in the IFS, then run CRTRPGTPL on that updated .tpl file to regenerate the compiled template member.

Using the RPG Template With the Composition Engine

Now that we have our compiled template, we will bring this template into our RPG program and use the XML composition engine subprocedures to build our XML. Here is an example program using our POSTADR template:



H DEBUG(*YES) DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*NEW)

 /COPY QRPGLECPY,RXSCB
 // This is the first of two places where the compiled template source member
 //  must be pulled into this program using the /COPY directive - first here, 
 //  in the D-specs, and again below in the Template subprocedure.
 /COPY QRPGLETPL,POSTADR

D Template        PR
D  p                                  Like(RXS_TEMPLATE_PARM)

D ComposedXml     S                   Like(RXS_Var64Kv_t)
D Iter            S              3I 0

D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)

 /FREE
   monitor;
     // RXS_ResetDS() must be used to initialize any RXS templated data
     //  structures - this helps ensure that they are correctly initialized
     //  in a backwards-compatible fashion
     RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
     // Specifies the procedure pointer for the Template subprocedure, which
     //  is at the end of this source member
     ComposeDS.TemplateProcedure = %Paddr( Template );
     RXS_StartComposeEngine( ComposeDS );

     RXS_ComposeVariable( V.residential : 'true');
     RXS_ComposeVariable( V.title : 'Mx.' );
     RXS_ComposeVariable( V.first : 'Jamie' );
     RXS_ComposeVariable( V.last : 'Hale' );
     RXS_ComposeVariable( V.street : '2886 Veltri Dr' );
     RXS_ComposeVariable( V.city : 'Hickory Hills' );
     RXS_ComposeVariable( V.state : 'VA' );
     RXS_ComposeVariable( V.zip : '94124' );
     // Composing our first section
     RXS_ComposeSection( S.PostAdr_beg );

     // For demonstration purposes, we will write three Phone sections for 
     //  this XML document
     for Iter = 1 to 3 by 1;
       RXS_ComposeVariable( V.phone : '949-555-467' + %Char( Iter ) );
       RXS_ComposeSection( S.Phone );
     endfor;

     RXS_ComposeSection( S.PostAdr_end );

     // Retrieving the composed XML
     ComposedXml = RXS_GetComposeBuffer();

     // For demonstration purposes, we will write the composed XML to the
     //  job log.
     RXS_JobLog( ComposedXml );
   on-error;
     // Error information can be caught and interrogated
   endmon;

   *INLR = *On;
   return;
 /END-FREE

 // This is the second of two places where the compiled template source member
 //  must be pulled into this program using the /COPY directive - first above, 
 //  in the D-specs, and again here in the Template subprocedure.
P Template        B
D                 PI
D  p                                  Like(RXS_TEMPLATE_PARM)
 /COPY QRPGLETPL,POSTADR
P                 E

The end result of this program will be XML written out to the job log that looks like the following:

<PostAdr residential="true">
  <name title="Mx.">
    <first>Jamie</first>
    <last>Hale</last>    
  </name>
  <street>2886 Veltri Dr</street>
  <city>Hickory Hills</city>
  <state>VA</state>
  <zip>94124</zip>
  <phone>949-555-4671</phone>
  <phone>949-555-4672</phone>
  <phone>949-555-4673</phone>
</PostAdr>