You are currently on IBM Systems Media’s archival website. Click here to view our new website.


Generating XML the XMLi Way

Back in 2011, we wrote an article called "There’s an RPG App For That!" There, we mentioned the XMLi package, an open-source toolkit from Larry Ducie for the generation of XML. We said at the time that we planned on doing a full report on this tool, and here it is—or at least a first installment. A little later than planned but hopefully you’ll agree that this is a case of better late than never.

The XMLi package offers two different methods for generating XML:

  • The first, known as XMLi1, provides a set of APIs that allow you create an XML document in memory, and then either write it to the IFS or utilize it directly as you would need to do if you were building the XML to pass to a Web service.
  • The second, not surprisingly known as XMLi2, uses a templating system, which not only contains the XML skeleton but can also include SQL to retrieve the data to be built into the document. This approach is probably harder to learn, but often much simpler to modify to meet changes in the XML document or data selection requirements. As you'll see when you study XMLi's supplied examples, it’s an incredibly powerful approach.

The XML Document

To demonstrate how XMLi works, we’ll generate the same XML document using each of these methods in turn. The document is a very simple one but hopefully will illustrate the basic principles for you. Here’s an extract:

  <Customer ID="938472">               
      <Street>4859 Elm Ave</Street>    
  <Customer ...

Since just about every IBM i system we’ve seen has the QIWS library loaded, we are using the PC Support Customer File - QCUSTCDT from that library as the source of the data for these tests.

Notice that the document consists simply of the root element Customers, followed by a record count and then the Customer element, which will be repeated once for each row in the selected data. We will be using SQL for the data retrieval and this will be equally simple consisting of:

select count(*) from QIWS.QCUSTCDT where STATE = 'TX'  

to retrieve the count of the number of customers and

   where STATE = 'TX'  

to retrieve the data to be incorporated into the document.

In order to simplify the examples, we’ve hard coded the selection value. In reality, we’d parameterize this to allow other states to be selected, but handling parameters in XMLi templates, while not difficult, is beyond the scope of this article and we will cover it in a subsequent piece.

Using the XMLi1 API Method

As you’ll see, the RPG code is not complex, but in the case of an XML document with a large number of elements it can become rather tedious to code.

Beginning at (a) in the following code, we identify the binding directory supplied with XMLi to make it easy for the compiler to locate XMLi's subprocedures. This way a simple CRTSQLRPGI (or CRTBNDRPG if we weren’t using embedded SQL) can be used to compile the program.

(b) includes the XMLi prototypes in the program and (c) defines xmlData, the field to hold the generated XML document, followed by xmlFilename, the name of the file to which it will be written.

(a) H BNDDIR('XMLILIB/XMLI')  DftActGrp(*no)


      // Declare field definitions for use by SQL
    d customer    E DS            ExtName('QIWS/QCUSTCDT')

      // Work Variables
    d recordCount   s        5i 0
    d endOfData     c             '02000'
(c) d xmlData       s    10000a   Inz
    d xmlFilename   s      128a   Varying
    d                             Inz('/Partner400+
    d                                  /CustomersE1.xml')

In the following logic, the call to xmli_useVariable() at (d) identifies the variable in which XMLi is to build the XML document. You can either specify a variable as we’ve done, or have XMLi provide “managed memory” automatically for you.

We next (e) set the format for the generated XML. The PRETTY option causes the XML to be indented to make it easier to read and is a good option to use when testing. For production purposes, you might want to use SIMPLE (no indentation). Other options are available when using the XMLi2 templating approach.

(d)    xmli_useVariable(xmlData);

(e)    xmli_setFormat(XML_FORMAT_PRETTY);

       Exec SQL
          select count(*)
            into :recordCount
            from qiws/qcustcdt
            where STATE = 'TX';

Now we begin the process of generating the actual XML. At (f), we call the xmli_openTag() API to generate the <Customers> tag. Then (g) calls xmli_addElement() to generate the complete <RecordCount> nnn </RecordCount> element.

The actual SQL directives (h) set up the cursor so that we can subsequently loop through the result set. The actual loop begins at (i) in the next code example.

(f)  xmli_openTag('Customers');

(g)  xmli_addElement('RecordCount': %char(recordCount));

(h)  Exec SQL
        declare customerCursor cursor for
          from QIWS.QCUSTCDT
          where STATE = 'TX';
     Exec SQL
        open customerCursor;

Now that all the setup is complete, we can loop through the result set and build the body of the XML document. This process begins at (j) where we create the opening tag for the <Customer> complex element. Since the Customer element includes the attribute ID, we need to call xmli_addAttribute() to add the attribute value (k). xmli_addAttribute always adds the attribute to the last element opened, so the sequence is important here.

We then proceed to add all of the individual simple elements before finally at (l) using the xmli_closeTag() API to close out the Address and Customer elements.

Once all rows in the result set have been processed, we can proceed to close the Customers element (m) and then call the API xmli_writeToFileWithVar() to write the XML document that has been built in the xmlData variable to the IFS file named in the xmlFilename variable.

(i)    DoU SQLSTATE = endOfData;
         Exec SQL
            fetch next
                  from customerCursor
                  into :CUSNUM, :LSTNAM, :STREET, :CITY, 
                       :STATE, :ZIPCOD;

        If SQLSTATE <> endOfData;
(j)        xmli_openTag('Customer');
(k)        xmli_addAttribute('ID': %char(cusnum));
             xmli_addElement('Name': LSTNAM);
               xmli_addElement('Street': STREET);
               xmli_addElement('City': CITY);
               xmli_addElement('State': STATE);
               xmli_addElement('Zip': %editc(ZIPCOD:'X')); 
(l)          xmli_closeTag('Address');

       Exec SQL
          close customerCursor;

(m)    xmli_closeTag('Customers');

       xmli_writeToFileWithVar( xmlFilename
                              : xmlData
                              : XML_ENCODING_UTF8);

That’s all there is to it. Effectively, we use API calls to open tags (xmli_openTag), add attributes to them (xmli_addAttribute), close them (xmli_closeTag) and to write complete elements (xmli_addElement).

A number of other APIs are available; see the documentation for examples.

Jon Paris is a technical editor with IBM Systems Magazine and co-owner of Partner400.

Susan Gantner is a technical editor with IBM Systems Magazine and co-owner of Partner400.



2019 Solutions Edition

A Comprehensive Online Buyer's Guide to Solutions, Services and Education.

New and Improved XML-INTO

Namespace support makes the opcode a viable option

Authenticating on the Web

The finer points of OpenRPGUI, Part 1

The Microphone is Open

Add your voice: Should IBM i include open-source RPG tools?

IBM Systems Magazine Subscribe Box Read Now Link Subscribe Now Link iPad App Google Play Store
IBMi News Sign Up Today! Past News Letters