Notes - Gateway/400 Group
Transcription
Notes - Gateway/400 Group
Generating XML With RPG Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Notes This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. The author, Jon Paris, is co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i developers. With his partner, Susan Gantner, Jon authors regular technical articles for a number of technical publications including IBM Systems Magazine, and its companion electronic newsletter, Extra and their weekly iDevelop blog (ibmsystemsmag.blogs.com/idevelop). You may view articles in current and past issues and/or subscribe to the free newsletter or the magazine at: www.ibmsystemsmag.com Susan and Jon are also partners in SystemiDeveloper, a company that hosts the RPG & DB2 Summit conferences. See SystemiDeveloper.com for more details. Feel free to contact the author at: jon.paris @ partner400.com or visit the Partner400 web site at www.partner400.com © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 1 of 146 Agenda What is XML - A Brief Refresher • XML's Relationship to HTML and DDS • Writing XML: The Basics ✦ Terminology, syntax and basic rules Creating XML • Doing it the hard way • Templating to the rescue ✦ Using CGIDEV2 • Larry Ducie's XMLi ✦ xmli.sourceforge.net • Henrik Rützou's powerEXT Core ✦ www.powerext.com/pextdoc_CGI.htm There are also now improved options for generation and consumption of XML with DB2 and SQL features • We will look very briefly at the XML generation capabilities later Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 2 of 146 What is XML? eXtensible Markup Language • A markup language for creating other markup languages! Markup languages use tags inserted into the actual information • Examples include HTML (Web pages), UIM (IBM i Menus etc.) The user defines a "language" to format specific information • i.e. "You" can create/define your own tags Defines both the content and nature of the information • Unlike HTML which just defines how to display data in a browser Major focus is to standardize information representation • Thereby enhancing information interchange XML is the syntactic foundation for many technologies • Many web services - including SOAP based services • Microsoft Office files ( Word, Excel, ... ) • Even this presentation "You" means your company or industry body or government or ... Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 3 of 146 HTML Comparison Example <html> <br> <CENTER> <table Border=1 CellSpacing=1 CellPadding=5 Width="600"> <th>Contact <th>Company <th>Address <th>City <tr> <td>Sally Smith</td> <td>Acme Computer Supplies</td> <td>1234 Jones St.</td> <td>Anytown, MN 55901</td> </tr> <tr> <td>John Jones</td> <td>Phones R Us</td> <td>5678 High Street</td> <td>Mytown, GA 30033</td> </tr> <tr> <td>Meanold Miser</td> <td>Suchadeal Bank</td> <td>91011 Important Ave.</td> <td>Jonesville, MN 55901</td> What if we needed to search this document for all people named "Jones" ? Notes In this example you can see that HTML is concerned only with how the data is laid out on the web page. It is difficult to derive from this the nature of the information and as a result it is difficult for computers to associate meaning with the data. The result is that searching contextually can be very difficult. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 4 of 146 HTML Example, continued The HTML would look like this in the browser • Pretty enough, but the data has no context ✦ Only what we associate with it through our personal experience • HTML tags can only give us a presentation "view" ✦ ✦ In other words they only control the appearance They tell us nothing about the content Basic XML Version <?xml version='1.0'?> <Customers> <Customer> Pretty ... <Contact>Sally Smith</Contact> <Company>Acme Computer Supplies</Company> <Address> <Street>1234 Jones St.</Street> <City>Anytown</City> <State>MN</State> <Zip>55901</Zip> </Address> </Customer> Or pretty <Customer> <Contact>John Jones</Contact> <Company>Phones R Us</Company> <Address> <Street>5678 High Street</Street> <City>Mytown</City> <State>GA</State> <Zip>30033</Zip> </Address> </Customer> <Customer> <Contact>Meanold Miser</Contact> <Company>Suchadeal Bank</Company> © Partner400, 2015 messy Gateway/400 Seminar - December 10th, 2015 The meaning is the same with XML Lecture Notes: Page 5 of 146 XML Example, continued Note the descriptive names for the elements • Emphasis is now on data content, not presentation ✦ Now we know what this data represents! • Think how easily you can search for a contact name of "Jones" ✦ Also much easier to share information with other applications or systems • An XML document is roughly analogous to the data in a physical file ✦ But with the field names embedded <Customer> <Contact>Sally Smith</Contact> <Company>Acme Computer Supplies</Company> <Address> <Street>1234 Jones St.</Street> <City>Anytown</City> <State>MN</State> <Zip>55901</Zip> </Address> </Customer> <Customer> <Contact>John Jones</Contact> <Company>Phones R Us</Company> ... XML Terms and Syntax Elements • A combination of a tag and its corresponding data • Elements may contain other elements ✦ The required Root element contains all other elements in the document • XML documents consist of elements, such as: <Street>1234 Jones St.</Street> Opening tag Content Closing tag • Complex elements may contain other elements ✦ Similar to a Data Structures in RPG Attributes • Additional information about an element <Address Type="Shipping"> Attribute Note that all element and attribute names in XML are CaSe SeNsItIvE! © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 6 of 146 What is a Schema? The definition of the customized markup language • It defines the tags and the rules for using them ✦ e.g. Element names, sequence, attributes: optional and required • Used to validate an XML document ✦ i.e. Does the document use the data elements correctly? • Schemas are roughly analogous to the DDS for a PF ✦ Except arrays and data structures can be defined as well as simple fields There are 2 ways to write schemas: • DTD: Document Type Definition ✦ ✦ Most widely used method to date Ugly syntax, not consistent with XML syntax and very limited in function - Will hopefully be replaced over time with XML schemas • XML Schema ✦ ✦ Richer, more robust syntax consistent with XML documents Developed as a standard by W3C One small problem • There is currently no standard validator on IBM i • Although you can now use SQL for the purpose Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 7 of 146 One More Thing - Namespaces XML allows you to define your own "language" • So there is always the possibility of a conflict in element names if you blend documents from multiple sources Namespaces allow you to deal with this by qualifying the element names • Similar to Qualified DS in RPG This example below is based on one from W3 Schools • There is a useful free tutorial on XML on their web site <namespace> Here <table> is used to <h:table> identify a list of items <h:tr> <h:td>Apples</h:td> <h:td>Bananas</h:td> </h:tr> </h:table> But here it relates to furniture! <f:table> <f:name>African Coffee Table</f:name> <f:width>80</f:width> <f:length>120</f:length> </f:table> </namespace> Notes Namespaces are not really an issue when generating XML documents, that comes into play more when processing them. However, you need to understand what they are and how they fit into the picture. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 8 of 146 Creating XML Documents With RPG The Hard Way and the Easier ways Notes I am always surprised by the number of people who build XML documents the "hard way". It is fine to create a simple XML string for a web service call this way, but to create documents for transmission to business partners, government, etc. it is just too much work for me. It is bad enough the first time you do it, but maintenance can be a nightmare - and the requirements for the document _will_ change. I will show you three different tools, all of them free, that you can use to simplify the task. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 9 of 146 Creating XML The Hard Way Concatenate literals/constants with field content • Store in varying length field(s) Use IFS APIs to write data to stream file • Not familiar with those APIs? ✦ Write strings to database and then use CPYTOIMPF to create IFS files • Or use Scott Klement's tutorial to learn how to use the APIs This approach is tedious, error-prone, and just plain ugly! • Not to mention that it is a maintenance nightmare Dcl-s XMLdata VarChar(1000000); Dcl-s len INt(10); ... Dou customersProcessed; XMLdata += '<Customer><CustomerNo>' + %EditC(CUSTNO: 'X') + '</CustomerNo><Address><StreetDetail>' + %TrimR(STREET) + ... '</Address></Customer>'; EndDo; len = write(fHandle: ( %Addr(XMLdata: *Data) ): %Len(XMLdata)); Notes The coding used in the chart makes use of a couple of V6 features. ✦ ✦ The ability to handle variables longer than 64K - here we have defined a variable of 1,000,000 bytes. The ability to specify to %Addr that we want the address of the data portion of the variable length field, not the start of the field itself as that would include the field length definition area (the first four bytes in this case). If you are on an older system then the code would have to be modified to: 1) Reduce the size of the field to 65,535 or less and 2) Modify the code %Addr(XMLdata: *Data) to %Addr(XMLdata) + 2 . Of course you would also probably have to allow for the fact that you might want to issue a write() for every record processed as 64K is not a lot of data and is probably not large enough for many applications. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 10 of 146 Creating XML - The Easy Way Use a templating system • This example uses the one supplied by CGIDEV2 • It was originally designed to produce web pages ✦ But as we've noted XML and HTML are very similar It is far more flexible • The XML template is external to the program ✦ Just like DDS externalizes the details of a display file • Change the template and you have changed the XML ✦ Without even having to recompile The complexity of the process is masked by the CGIDEV2 APIs GethtmlIFS('/www/xmltemplates/Customers.xml'; UpdHTMLVar('CustNo' : %EditC(CUSTNO: 'X') ); UpdHTMLVar('Street' : STREET ); UpdHTMLVar('City' : CITY ); We will be discussing the WrtSection('Customer'); details behind this code on subsequent charts CGIDEV2 What is it? • A set of RPG IV subprocedures developed to simplify web programming • IBM supplied it free of charge and all source code is included ✦ But the best and most up-to-date distribution is from www.easy400.net How do we use it? • You supply a template that defines the "shape" of the XML document • It includes special markers to indicate the "Sections" ✦ i.e. Record format names • Plus markers to indicate where variable data is to be placed ✦ Operates much the same as DDS as we'll see on the next chart • See this article for a complete walk-though of the process ✦ www.ibmsystemsmag.com/ibmi/developer/rpg/Using-CGIDEV2-for-Generating-XML/ /$Customer <Customer> <CustomerNo>/%CustNo%/</CustomerNo> <StreetDetail>/%Street%/</StreetDetail> <CityName>/%City%/</CityName> </Customer> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 11 of 146 Comparing an XML Template to DDS A A A A A A A R CUSTOMER CUSTNO /$Customer 6S 0 STREET 30 CITY 25 5 5 7 7 9 9 11'Customer No' 31 11'Street Detail' 31 11'City Name' 31 Substitution markers CustNo, Street and City are replaced at run time by the actual field values - just as they would be in a display or printer file Literals such as '<CityName>' are output as-is just as they would be in DDS. <Customer> <CustomerNo>/%CustNo%/</CustomerNo> <StreetDetail>/%Street%/</StreetDetail> <CityName>/%City%/</CityName> </Customer> Defining an XML Template This example use the default section identifier "/$" Notice that there are three sections (formats) • The first (Header) will be output at the start of the file • The second (Customer) is used for the repeating customer elements • The third (Trailer) wraps up the document The whole template can be placed in a source physical file or in an IFS file /$Header <?xml version='1.0'?> <Customers> /$Customer <Customer> <Contact>/%Contact%/</Contact> <Company>/%Company%/</Company> <Address> <Street>/%Street%/</Street> <City>/%City%/</City> <State>/%State%/</State> <Zip>/%Zip%/</Zip> </Address> </Customer> /$Trailer </Customers> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 12 of 146 The RPG Code to Process the Template GetHTML loads the template into memory UpdHTMLVar supplies the data for the substitution variables ✦ Note that %Char (or %EditC/W) must be used for all numeric fields WrtSection actually merges the skeleton with the variable content ✦ A substitution variable can appear multiple times just as a DDS variable can WrtHtmlToStmf writes the buffered XML to the IFS ✦ The second parameter identifies the code page of the file to be created // Load HTML from source file and write out the Header Section gethtml('HTMLSRC': '*LIBL': 'CUSTXML'); WrtSection('Header') // Update substitution variables for each record & write it out UpdHTMLVar('Contact': ContactName); UpdHTMLVar('Company': CoName); This portion is UpdHTMLVar('Street' : Address1); repeated for each UpdHTMLVar('City' : City); UpdHTMLVar('State' : StateCode); record to be output UpdHTMLVar('Zip' : %EditC(ZipCode: 'X')); WrtSection('Customer'); // When all records processed write the trailer and output to the IFS WrtSection('Trailer'); WrtHtmlToStmf('/xml/Customers.xml':819); Alternatives to CGIDEV2 Henrik Rützou's PowerEXT (powerext.com) • Developed as part of the PowerEXT web development framework • API driven approach that can handle HTML, JSON, XML and more ✦ Because it is also based on CGIDEV2 you can also use a template approach • XML facilities are part of the free Core features ✦ www.powerext.com/pextdoc_CGI.htm Larry Ducie's XMLi Toolkit (xmli.sourceforge.net) • Designed for XML from the ground up • Two "flavors" of operation ✦ ✦ API driven Template driven • See www.ibmsystemsmag.com/ibmi/developer/rpg/xmli/ for worked examples © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 13 of 146 powerEXT An API based approach • powerExt lets you create an element and add data in a single call ✦ Element attributes can be specified in a similar manner • XMLi is similar but requires you to specify the name of the element being closed. Dcl-Ds customer ExtName('QCUSTCDT') End-Ds; // Declare Internal Variables Dcl-S recordCount Int(5); Dcl-C endOfData '02000'; Dcl-S xmlFilename Varchar(128) Inz('/Customers.xml'); clearSrvPgm(); setContent('*none'); Exec SQL select count(*) into :recordCount from qcustcdt; xmlNode('Customers'); xmlNode('RecordCount': '': %char(recordCount)); Notes powerExt (www.powerExt.com) The powerExt approach is to use individual API calls to add nodes and attributes to the XML tree. As you can see in the example, the same fundamental API call is used to add a node regardless of whether it is an elementary item or a group item. The only difference is whether the element value itself is present. The API XMLendnode() is used to close off open elements. There is no need to specify the name of the element which is ending as the rules fro formulating XML make it possible for the tool to work out for itself the element name to use. Fixed-form D-specs are below in case you haven't fully come to grips with free form definitions yet: d customer E DS ExtName(QCUSTCDT) // Declare Internal Variables d recordCount s 5i 0 d endOfData c '02000' d xmlFilename s 128a Varying Inz('/Customers.xml') © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 14 of 146 powerExt - continued Exec SQL declare customerCursor cursor for select * from QIWS.QCUSTCDT; Exec SQL open customerCursor; DoU SQLSTATE = endOfData; Exec SQL fetch next from customerCursor into :customer; If SQLSTATE <> endOfData; xmlNode('Customer':'ID="'+ %char(cusnum) + '"'); xmlNode('Name':'':LSTNAM); Starts xmlNode('Address'); xmlNode('Street':'':street); element and xmlNode('City':'':city); adds attribute xmlNode('State':'':state); value xmlNode('Zip':'':%editc(zipcod:'X')); xmlEndNode(); xmlEndNode(); Note: No element EndIf; name needed EndDo; Exec SQL close customerCursor; xmlEndNode(); echoToStmf(xmlFilename:1208); Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 15 of 146 XMLi Toolkit - The Template Approach The template text is in a file in the IFS (Template 1.xml) • It uses an XSL transformation style i.e. for-each ... <xmli:template xmlns:xmli="http://www.sourceforge.net/xmli" ccsid="1208"> <Customers> <xmli:sql name="custCount" statement="select count(*) from mcustomers"> <xmli:for-each> <RecordCount><xmli:value-of select="custCount.1" /></RecordCount> </xmli:for-each> <xmli:sql name="custRow" statement="select * from mcustomers"> <xmli:for-each> <Customer ID="${custRow.1}"> <Company><xmli:value-of select="custRow.2" /></Company> <Address> <Street><xmli:value-of select="custRow.3" /></Street> <City><xmli:value-of select="custRow.4" /></City> <State><xmli:value-of select="custRow.5" /></State> <Zip><xmli:value-of select="custRow.6" /></Zip> </Address> </Customer> </xmli:for-each> </Customers> <xmli:write-to-file path="'/Partner400/Redbook/Customers.xml'" /> </xmli:template> Notes The template option offered by XMLi appears at 1st glance to be similar to the template approach of CGIDEV2, and in some respects that is a valid comparison. However XMLi templates go far beyond the capabilities offered by CGIDEV2. XMLi templates can incorporate SQL so that the entire data retrieval and XML build process can be significantly simplified. In this simple example you will see that some aspects of the template look remarkably similar to the CGIDEV2 version. However the code to populate the template is very different, but hopefully mostly self-explanatory. The most significant difference being the way in which the values are set for the parameters. Note that when extracting data for use as the value of an element we use the value-of construct. However, we have to use a slightly different approach when we need to embed a value in the middle of an XML tag. You can see an example of this above with the Customer ID attribute. You code the ${ } sequence to wrap any code that XMLi will interpret and replace. In this case: <Customer ID="${custRow.1}"> We’re just using it to retrieve the first value from the result set. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 16 of 146 XMLi Toolkit - The Template Approach This is all the RPG code needed to run the template • There is a command option which can avoid the need for any RPG logic All selection / processing logic is contained within the template • Selection criteria etc. can be also passed from RPG as parameters /include XMLI_H Dcl-S templateFilename Varchar(128) Inz('/Partner400/Redbook/Template 1.xml'); // Load and run the template... xmli:loadTemplate('CUST'; : templateFilename); xmli:runTemplate('CUST'); // Unload template (Could be left loaded for further use)... xmli:unloadTemplate('CUST'); *InLR = *On; Notes As you can see very little RPG is needed to initiate and run the template. So little in fact that in simple cases such as this the program could have been replaced by a simple command that comes with the package. Fixed-form D-specs: /include XMLI_H D templateFilename... D s D D © Partner400, 2015 128a Varying Inz('/Partner400/Redbook+ /Template 1.xml') Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 17 of 146 XMLi - API Method XMLi's API approach is very similar to that of powerExt • xmli_addElement() adds a simple element with value • xmli_openTag() starts a complex element ✦ Attributes can be added with xmli_addAttribute() • xmli_closeTag() is used to close elements by name ... If SQLSTATE <> endOfData; xmli_openTag('Customer'); xmli_addAttribute('ID': %char(cusnum)); xmli_addElement('Name':'': LSTNAM); xmli_openTag('Address'); xmli_addElement('Street': street); xmli_addElement('City': city); xmli_addElement('State': state); xmli_addElement('Zip': %editc(zipcod:'X')); xmli_closeTag('Address'); xmli_closeTag('Customer'); EndIf; EndDo; Exec SQL close customerCursor; ... Notes The first of XMLi's modes of operation is an API approach which as you will see later is very similar to that used by powerExt. The principal difference is that with XMLi when closing an element it is necessary to specify the name. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 18 of 146 Creating XML with DB2 Quite easy for relatively straightforward tasks such as our examples • But complexity level accelerates very rapidly when multiple tables and selective output is required ✦ Personally find the syntax confusing and completely non-intuitive My take - It really needs a tool to build the SQL statements Dcl-s MyXMLDoc Exec SQL SQLTYPE(XML_CLOB_File); Set Option Commit=*None, DatFmt=*ISO, CloSQLCsr=*EndActGrp; MyXMLDoc_Name = '/Partner400/XMLStuff/WrtSQLXML3.xml'; MyXMLDoc_NL = %Len(%Trim(MyXMLDoc_Name)); MyXMLDoc_FO = SQFOVR; //Replace if file exists Exec SQL <?xml version="1.0" encoding="UTF-8"?> Select XmlDocument <Customers> <Customer> (xmlgroup(CustNo as "ID", <ID>A0011</ID> (Trim)Name as "Cust-Name", <Cust-Name>Acme Best Brew</Cust-Name> (Trim)City as "City", <City>Acme</City> <ZipCode>0</ZipCode> Zip as "ZipCode" </Customer> Order By City, Name <Customer> Option Row "Customer" <ID>J0003</ID> Root "Customers")) <Cust-Name>JB's Brew Pub</Cust-Name> <City>Anaheim</City> into :MyXMLDoc .... From Customers; Notes The generation, and for that matter consumption, of XML using SQL has come a long way over the years but I personally still find the syntax overly complex and confusing. In a simple example such as the one shown above it doesn't matter that much. But for more complex documents, the degree of difficulty seems to increase rapidly and soon approaches what I would consider an example of "write only" logic - i.e. you may be able to write it and get it running, but can you subsequently read and modify it? Here's a more complex example to show you what I mean: EXEC SQL With Address as (Select CustNo, XMLElement(Name "Customer", XMLAttributes(Trim(CustNo) as "CustNo"), XMLConcat(XMLElement(Name "Name", Trim(Trim(CustName1) concat ' ' concat Trim(CustName2))), XMLForest(Trim(Street) as "Street", Trim(ZipCode) as "ZipCode", Trim(City) as "City"))) CustXML From Addressx), Header as (Select Company, OrderNo, CustNo, XMLForest(Company as "Mandant", OrderNo as "OrderNo") OrderNoXML, XMLConcat(XMLElement(Name "DeliveryDate", Char(DelDate, ISO)), XMLElement(Name "OrderType", Case When OrderType = 'DO' Then 'Domestic' When OrderType = 'EX' Then 'Export' When OrderType = 'UO' Then 'Express' Else '???' End), XMLElement(Name "DeliveryTerms", Case When DelTerms = 'CPT' Then 'Delivered Free' When DelTerms = 'EXW' Then 'Ex Works' Else '???' End)) HdrAddXML From OrderHdrX Where DelDate between '2009-12-01' and '2009-12-31'), HdrAddr as (Select Company, OrderNo, XMLConcat(OrderNoXML, CustXML, HdrAddXML) OrderXML from Header Join Address Using(CustNo)), ..... And on it goes. This is just the first half or so of the statement. Note: The actual output of the statement on the chart is not formatted as I have shown it - it comes out as one huge long line. I used the xml tooling in RDi to format it. Many thanks to Birgitta Hauser for these examples - without her help I wouldn't have known where to begin. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 19 of 146 Useful Reading, Tutorials, etc. XML for the World Wide Web - Elizabeth Castro • Low priced, easy to read, general purpose introduction. ✦ Well supported by website with downloadable examples • Peachpit Press - www.peachpit.com (ISBN 0 - 201 - 71098 - 6) XSLT - Doug Tidwell • Not a light read but a comprehensive and entertaining treatment by one of IBM's top XML "apostles" • O'Reilly - www.oreilly.com (ISBN 0 - 596 - 00053 - 7) W3Schools.com - "the best things in life ARE free" • Tutorials on all aspects of XML, XSL, and more • www.w3schools.com ZVON.org - "The Guide to the XML Galaxy" • Tutorials, links and more www.zvon.org And still more information . . . IBM alphaWorks • Latest tools and enablers supporting XML • alphaWorks.ibm.com W3C - XML standards and specifications • Status and detail on various XML-related standards • www.w3.org/XML XML.org - information on XML standards, tools • Repository for industry standard DTDs shared across companies • www.xml.org © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 20 of 146 Any Questions? ? © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 21 of 146 © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 22 of 146 Processing XML with RPG Jon Paris Jon.Paris @ Partner400.com www.Partner400.com Notes This presentation may contain small code examples that are furnished as simple examples to provide an illustration. These examples have not been thoroughly tested under all conditions. We therefore, cannot guarantee or imply reliability, serviceability, or function of these programs. All code examples contained herein are provided to you "as is". THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY DISCLAIMED. The authors, Jon Paris and Susan Gantner, are co-founders of Partner400, a firm specializing in customized education and mentoring services for AS/400 and iSeries developers. Together with Paul Tuohy, they also operate the System i Developer education consortium which runs the RPG & DB2 Summit conferences. See (http:// systemideveloper.com/Summit/conferences.html). Their individual careers include a number of years with IBM, including working at both the Rochester and Toronto laboratories. These days Jon and Susan are devoted to educating developers on techniques and technologies to extend and modernize their applications and development environments. Jon and Susan author regular technical articles for the IBM publication, IBM Systems Magazine, i5 edition, and the companion electronic newsletter, iSeries Extra. You may view articles in current and past issues and/or subscribe to the free newsletter at: http://IBMsystemsMag/IBMi. They also write a weekly blog which you can find at: http:// ibmsystemsmag.blogs.com Feel free to contact the authors: contact @ partner400.com or visit the Partner400 web site at http://www.partner400.com © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 23 of 146 Agenda The Basics The XML processing opcodes and the %XML BIF A simple XML-INTO example Processing a simple document in one step A more realistic XML-INTO example Defining the Nested DS is the hardest part! Post V5R4 updates to XML support Namespaces, identifying optional elements, special characters and more Processing XML in "pieces" using %HANDLER And finally a (very) brief look at XML-SAX Notes This is primarily a journey through the use of the XML op-codes in RPG, but along the way we’ll be touching on some of V5’s support for nested Data Structures (a feature you must understand before you can fully utilize the XML support). We will also talk briefly about call-back processing - a programming technique that is common in other languages but unfamiliar to most RPGers. Why do you need to know about it? Because the technique is utilized by the XML-INTO op-code when you wish to handle large documents and is also a requirement when using XML-SAX. We will not have time to cover XML-SAX in any detail, but have included some charts that will give you a “flavor” of what it is all about. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 24 of 146 The XML Op-codes XML-INTO • The more "RPG Like" of the two opcodes • Processes the document as a whole • Uses Data Structures to contain the extracted data ✦ Defining the DS can be the hardest part of using it • Best when you need to extract all of the data in the document XML-SAX • Processes the document one piece at a time ✦ e.g. Start of element, element data, End of element, etc. • Typically requires a lot more programming • Best when you only need to extract specific elements ✦ Or when size restrictions come into play Notes Although there are only two opcodes involved, as you will see they do a lot of work. XML-INTO is the powerhouse - the most challenging part of using it is in working out the DSs that need to be defined to hold the extracted data. Once you have done that the rest is very simple. XML-SAX is not as commonly used and it is difficult to come up with useful examples as every usage will be different. We have included a few charts at the end to give you an idea of how it is used. But we may not have time to get to them as there is a lot of material to cover. If you have specific questions about XML-SAX and we are not able to get to them during the session please feel free to email me at any time. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 25 of 146 Basic Syntax - XML-INTO variable XML-INTO { ( E H ) } variable %XML ( ... ) • E and H extenders can be specified but there is little point • variable is the target for the operation ✦ ✦ It can be a field, an array, a Data Structure or a Data Structure array If assignment is to a numeric field, the rules of %DEC conversion etc. apply • %XML( ...) this BIF identifies the XML document ✦ It is also used to supply processing options - Which we will discuss later Matching is based on names • The actual names and hierarchy of the fields in the variable must match the names and hierarchy of the elements and attributes in the XML ✦ More on this on the next chart XML-INTO Customer %XML(XML_Input1: 'case=any'); "Matching" Names and Hierarchy This XML document Can be processed using this DS <Customer> <Name> Smith </Name> <City> Toronto </City> </Customer> ... ... ... <Customer> <Name> Jones </Name> <City> Chicago </City> </Customer> Dcl-Ds Customer City Name Dim(99) DIM( ) is used for repeating elements Note: 1. Names MUST match - More later on special characters in names etc. 2. The field sequence in the DS does not have to match the element sequence in the XML 3. Size of array is your choice © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 26 of 146 %XML BIF Details The %XML BIF is used with both XML-INTO and XML-SAX Syntax: %XML ( xml_document : options ) xml_document - Identifies the source of the XML By default the XML is contained within the named variable If option 'doc=file' is specified then the variable contains the IFS file name of the XML document options - Controls the processing of the XML stream Format is optionname1=value1 optionname2=value2 No space allowed between the option name and the equal sign Or between the equal sign and the value Options can be specified in any case. e.g. doc=file or DOC=FILE or Doc=File Can be a literal string, a named constant or a character variable We will look at some of the options as we use them XML Used in Examples 2, and 3 <Customers> <Company>Phones R Us</Company> <Company>Suchadeal Bank</Company> <Company>Rinky Dinky Toy Co.</Company> <Company>Partner400</Company> <Company>BlackHole Navigation</Company> <Company>Bankers Trust</Company> </Customers> This is the XML data that will be used in our initial examples Although very few of the XML documents you see will be this simple! The root element is Customers It contains multiple Company name elements This data is stored in variable XML_Input1 And in the IFS file /partner400/XML_Input1.xml © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 27 of 146 XML-INTO - Example 1 - Fill an Array Dcl-Ds customers; company Char(32) Dim(99); End-Ds; Dcl-Ds progStatus psds; xmlElements Int(20) POS(372); End-Ds; Dcl-S i Int(10); XML-INTO company %XML(XML_Input1: 'case=any' ); dsply ( %Char(xmlElements) + ' elements loaded' ); for i = 1 to xmlElements; dsply company(i); endfor; Results 6 elements loaded Phones R Us Suchadeal Bank Rinky Dinky Toy Co. Partner400 BlackHole Navigation Bankers Trust Extracting into an array • xmlElements is a new field that has been added at position 372 in the PSDS RPG supplies a count of the elements that were loaded into the array %XML Option case=any • Indicates that element names in the XML can be in any case and should be converted to upper case before comparing with the RPG variable names Notes Whenever the target of XML-INTO is an array, RPG ensures that a count of the number of elements processed in the PSDS at offset 372. This is an 8 byte integer (20i) that can contain a really, really, big value! Quite why IBM made it so big we can't say - but you're not ever going to exceed it. Although we can use mixed case names for RPG variables (e.g. CustName, ZipCode, etc.) it is important to understand that the RPG compiler reduces them all to upper case. So custName, CustName, and CUSTNAME are all the same variable - CUSTNAME. As we noted earlier, the RPG variable names are matched against the XML element names while processing the document, so unless the XML element names are all in upper case, we need to specify the %XML option 'case=any' to force the parser to convert the element names to upper case before making the comparison. D-spec version of definitions: d customers d company D progStatus D xmlElements d i © Partner400, 2015 DS SDS s 32a Dim(99) 20i 0 Overlay(progStatus: 372) 10i 0 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 28 of 146 XML-INTO - Example 2 - XML in IFS Dcl-Ds customers; company Char(32) Dim(99); End-Ds; Dcl-Ds progStatus psds; xmlElements Int(20) Pos(372); End-Ds; Dcl-S XML_Input Varchar(256) Inz('/Partner400/XML_Input1.xml'); XML-INTO company %XML(XML_Input: 'case=any doc=file ' ); This is the same basic example as the previous one • But it demonstrates how to handle an XML document in the IFS • Note the option 'doc=file' %XML Option doc=file • Tells the compiler that the variable XML_Input contains the name of the file containing the XML document Notes It surprises many people that the default behavior for XML-INTO is to assume that the XML document is contained within the variable. After all, most people’s first introduction to XML is when someone tells them they have a file of XML data that needs processing. IBM’s assumption is that while this may be the case today, in a very short time, the majority of XML that you process will arrive in your program as a result of a call to a web service. It will therefore be in a variable - and why force the programmer to write it to a stream file just to read it back in again. Whether that assumption is proven correct or not only time will tell. D-spec version of data definitions: D customers D company D progStatus D xmlElements D XML_Input D © Partner400, 2015 DS 32a Dim(99) SDS 20i 0 Overlay(progStatus: 372) s 256a Varying Inz('/Partner400/XML_Input1.xml') Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 29 of 146 Agenda Beyond the Basics of XML-INTO A more complex and realistic example Additional capabilities in V6 and 7 Notes As noted earlier - the example we have been looking at is far more simplistic than any we will encounter in real life. In the following section we will look at how to process a more "normal" XML document and in particular the importance of understanding how the Data Structures we will use to contain the data must be built. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 30 of 146 XML For Example 3 <Customers> <RecordCount> 2 </RecordCount> <Customer type="wholesale"> <Contact>John Jones</Contact> <Company>Phones R Us</Company> <Address> <Street>5678 High Street</Street> <City>Mytown</City> <State>GA</State> <Zip>30033</Zip> </Address> </Customer> <Customer type="retail"> <Contact>Meanold Miser</Contact> <Company>Suchadeal Bank</Company> <Address> <Street>91011 Important Ave.</Street> <City>Bankville</City> <State>MN</State> <Zip>55901</Zip> </Address> </Customer> </Customers> Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 31 of 146 Example 3 - Compound Data Structure Dcl-Ds customers Qualified; recordCount Packed(3); customer LikeDS(customer_T) Dim(99); End-Ds; Dcl-Ds customer_T Qualified Template; type Char(10); company Char(32); discountCode Char(1); address LikeDS(address_T); End-Ds; Dcl-Ds address_T Template; street Char(32); city Char(24); state Char(2); zip Zoned(5); End-Ds; These nested Data Structures map the “shape” of the XML Note that attributes of an element such as "type" are at the same level in the hierarchy as nested elements such as "Company" Notes D-spec version for those not yet fully liberated: Note the use of the keyword Template (V6+) to conserve storage. Since we will never store data in customer_T (the data will actually be stored in customers.customer) why waste storage on it. Templates effectively allow us to define our own data types - in this case a "customer" entry. D customers D recordCount D customer DS D customer_T D type D company D discountCode D address DS D address_T D street D city D state D zip DS © Partner400, 2015 3p 0 Qualified LikeDS(customer_T) Dim(99) Qualified Template 10a 32a 1a LikeDS(address_T) Template 32a 24a 2a 5s 0 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 32 of 146 Example 3 - The Relationships <Customers> <RecordCount> ... <Customer type="wholesale"> <Contact> ... <Company> ... <Address> <Street> ... <City> ... <State> ... <Zip> ... </Address> </Customer> <Customer type="wholesale"> customers Top level DS recordCount customer (1) Nested DS type company discountCode address Nested DS street city state zip customer (2) Nested DS type company discountCode address Nested DS street city state zip This is how the elements in the XML map to the various parts of the customers DS Note that discountCode is present in the DS but not the XML Similarly the element Contact is present in the XML but not the DS Notes This chart is designed to show how the nested structures map out in memory and how the DS relates to the XML document we are processing. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 33 of 146 Example 3 - Program Logic XML-INTO customers %XML(XML_Input : 'case=any allowextra=yes + allowmissing=yes'); Two additional processing options are required for this document: allowextra - Used when the document contains an element not in the DS • The XML element "Contact" in this example allowmissing - Used when the DS includes fields which may not be present in the XML • In other words fields that are optional in the XML document • The field "discountCode" in this example is in the DS but not the XML This particular XML requires "allowmissing" for another reason ... • If there were less than 99 Customer elements, then any unused ones would be considered to be "missing" Notes The biggest problem with these options is the lack of granularity. Ideally what we might want to say is "Allow discountCode to be missing" or "Ignore the additional field Contact, we don't need that information". But we can't do that - and that means that we really should only use these options when we have to. AllowExtra is the least dangerous - and that's good because it is the one for which we have no cure. Using it simply means that if additional elements are added to the document your RPG process will ignore them. If this is a situation that should never occur and you want to know if it happens then _don't_ use this option. AllowMissing presents a much more difficult situation because once you specify the option then even if every single element that you wanted if missing from the document RPG will say it processed just fine. Until IBM released a "cure" for this situation in V6 this was problematic as without the option, many genuine documents would fail. Not just optional elements would cause this, but also any repeating elements that did not have the full number you specified in the DIM present. For example if you allowed up to 5 addresses per customer in the DS ( i.e. Dim(5) ) and only 4 were present, then without the AllowMissing option the parsing would fail. The "cure" to the AllowMissing issue was supplied via V6 and V7 PTFs and we will be looking at how that works on the next chart. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 34 of 146 Handling Optional Elements and More V6+ Optional Elements (A) • In the case below Address can repeat but how many are there in the document? ✦ There may only be one or perhaps as many as ten • Prior to this new option AllowMissing was the only solution ✦ ✦ countprefix provides a versatile and much safer approach You can now have RPG count exactly how many of a specified element are present Data and attributes in the same element (B) • Prior to option datasubf you had to parse the document twice ✦ Once to obtain the element data and once to obtain its attributes • Now you can get all of the data in a single parse <CustomerData> <Customer> (B) <Name category="Retail" ID="J020">Jones and Co.</Name> (A) <Address type="Mailing"> <Street>2550 Main Street</Street> <City>Knoxville</City> There may be as few as 1 <State>TN</State> Address - and as many as 10 </Address> (A) <Address type="Shipping"> We need to keep track of <Street>45 Opryland Drive</Street> how many there are ....... Notes These new capabilities were added via a PTF issued in March of 2009 For full details see www.ibm.com/software/rational/cafe/docs/DOC-2975 We also wrote an article in the April 2009 edition of IBM Systems Magazine Extra Newsletter www.ibmsystemsmag.com/ibmi/enewsletterexclusive/25012p1.aspx Note that this is NOT available for V5R4 - only V6 and later © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 35 of 146 CountPrefix and DataSubf in Action V6+ Dcl-Ds Customer Qualified Dim(99); Name LikeDS(Name_T); Address LikeDS(Address_T) Dim(10); countAddress Int(5) Inz; // Contains the number of Address elements End-Ds; Dcl-Ds Name_T Template; category Char(10); ID Char(4); data Char(32); // Contains data associated with the Name element End-Ds; Dcl-Ds Address_T Template; type Char(10); Street Char(32); City Char(32); State Char(2); End-Ds; XML-INTO Customer %XML(XMLFile: 'datasubf=data + countprefix=count case=any'); In V5R4 ... Name data could not have been retrieved without an additional processing pass Determining number of Address elements required initialization with (say) hi-vals and then testing for them after XML-INTO completed. It also required specifying allowmissing=yes and would not work with %HANDLER Notes If you are not yet familiar with free-form syntax, here is the same code with fixed form D-specs: D Customer D Name D Address DS Qualified Dim(99) LikeDS(Name) LikeDS(Address) Dim(10) // countAddress contains the count of Address elements D countAddress D Name D category D ID 5i 0 Inz DS 10a 4a Template // data contains the data associated with the Name element D data D Address D type D Street D City D State 32a DS Template 10a 32a 32a 2a XML-INTO Customer %XML(XMLFile: 'doc=file datasubf=data + countprefix=count case=any'); © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 36 of 146 Example 3 - Using CountPrefix Dcl-Ds customers Qualified; recordCount Packed(3); customer LikeDS(customer_T) Dim(99); count_customer Int(3); End-Ds; Dcl-Ds customer_T Qualified Template; type Char(10); count_type Int(3); company Char(32); discountCode Char(1); count_discountCode Int(3); address LikeDS(address_T); End-Ds; Dcl-Ds address_T Template; street Char(32); city Char(24); state Char(2); zip Zoned(5); End-Ds; XML-INTO customers %XML(XML_Input: 'case=any allowextra=yes + countprefix=count_' ); I "fixed" the earlier version of this example so that it now allows for: • Less than 99 customers • The type attribute can be omitted (attributes are often defaulted) • The discountCode can be omitted allowmissing for this document should no longer be needed Notes D-spec version for those not yet fully liberated: This is the fixed form version of the declarations on the previous page. D customers DS D recordCount D customer D count_customer Qualified 3p 0 D customer_T DS D type D count_type D company D discountCode D count_discountCode D address D address_T D street D city D state D zip © Partner400, 2015 DS LikeDS(customer_T) Dim(99) 3i Qualified Template 10a 3i 32a 1a 3i 32a 24a 2a 5s 0 LikeDS(address_T) Template Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 37 of 146 Handling Namespaces V6+ This new feature enables XML-INTO to handle namespaces • There are also other fixes that really round out the XML-INTO support What is a "Namespace" ? • It ensures that elements in your XML document are uniquely named ✦ Vital to enable one document to be safely "wrapped" within another • The namespace is associated with a URL thereby making it unique ✦ In the example the namespace P400 is associated with Partner400.com • Element names are qualified with the namespace ✦ The same as RPG's qualified names but using a colon (:) not a period (.) Very common in XML documents • But until now RPG could not cope with it and you had to pre-process the document <P400:customer xmlns:P400="http://www.partner400.com"> <P400:address> <P400:street>61 Kenninghall Cres.</P400:street> <P400:city>Mississauga</P400:city> <P400:postcode>L5N 2T8</P400:postcode> </P400:address> </P400:customer> XML-INTO: Namespaces (V6 & V7) V6+ %XML option ns={ nsOption } • Controls the way in which XML-INTO handles a namespace • Modifies element name before matching it against the names in the DS Two possible values for the nsOption: • remove ✦ Namespace prefix is ignored completely • merge ✦ Replaces colon by an underscore to form a valid RPG name • There is also the new nsprefix={prefix} option ✦ Allows retrieval of the namespace value when ns=remove is used Note: • if path is specified it must use names that result from the use of the ns= option XML-Into P400_Address %XML(MyXMLDoc1 : 'path=P400_customer/P400_address doc=file ns=merge'); XML-Into Address %XML(MyXMLDoc1: 'path=customer/address doc=file + ns=remove nsprefix=ns_'); © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 38 of 146 XML-INTO: ns=remove Example V6+ The namespace is being stripped • So characters up to and including the colon are removed nsprefix is used to access the original prefix value • In this case we only wanted the prefix value for the Street element <P400:customer xmlns:P400="http://www.partner400.com"> <P400:address> <P400:street>61 Kenninghall Cres.</P400:street> <P400:city>Mississauga</P400:city> <P400:postcode>L5N 2T8</P400:postcode> </P400:address> </P400:customer> Dcl-Ds Address; Street Varchar(30); ns_Street Varchar(20); City Varchar(25); Postcode Char(7); End-Ds; XML-Into Address %XML(MyXMLDoc1: 'path=customer/address doc=file + ns=remove nsprefix=ns_'); Notes D-spec version: D Address D Street D ns_Street D City D Postcode © Partner400, 2015 DS 30a 20a 25a 7a Varying Varying Varying Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 39 of 146 XML-INTO: ns=merge Example V6+ The namespace is merged with the element name • The colon being replaced by an underscore ✦ Note the extended names are also used in the path= option <P400:customer xmlns:P400="http://www.P400.com"> <P400:address> <P400:street>61 Kenninghall Cres.</P400:street> <P400:city>Mississauga</P400:city> <P400:postcode>L5N 2T8</P400:postcode> </P400:address> </P400:customer> Dcl-Ds P400_Address; P400_Street Varchar(30); P400_City Varchar(25); P400_Postcode Char(7); End-Ds; XML-Into P400_Address %XML(MyXMLDoc1 :'path=P400_customer/P400_address doc=file ns=merge'); Notes D-spec version: D P400_Address... D DS D P400_Street... D D P400_City... D D P400_Postcode... D D ... © Partner400, 2015 30A Varying 25A Varying 7A Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 40 of 146 XML-INTO - The Latest Updates V6+ A fix for non-RPG characters in element names • case=convert This converts the following: • Hyphens in XML names are converted to underscores • Accented characters such as à, é and ñ convert to their uppercase A-Z equivalent • Any remaining illegal characters are converted to underscores If there is now an underscore at the beginning of a name it is dropped • And multiple consecutive underscores are merged into a single underscore Notes One other issue that several RPG users have encountered concerns the use of characters in element names that aren’t valid RPG field names. For example, it’s not uncommon for element names to include the hyphen but while this character is legal in COBOL field names, it isn’t valid in RPG. As a result, such documents can’t be correctly handled by XML-INTO and require alternative or additional processing. There’s an even bigger problem in European countries, Mexico and the province of Quebec—to name a few. In these areas, many element names will include accented characters such as à, é and ñ. This is now handled by an extension to the group of case= options. Now in addition to the existing values ("upper," "lower" and "any") there’s now a "convert" option. When this is specified, any accented characters in a namespace, element or attribute name are converted to their uppercase A-Z equivalent. If after that conversion any characters remain that would be invalid in an RPG name, they’re converted to underscores. Should this conversion result in an underscore at the beginning of a name, it’s simply dropped. If there are multiple consecutive underscores, these are merged into a single underscore in the final name. If you want to go into these new options in more detail we recommend reading the article we wrote on it for IBM Systems Magazine The title is "New and Improved XML-INTO" www.ibmsystemsmag.com/ibmi/xml-into_namespace/35968p1.aspx © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 41 of 146 Handling Large Documents Using XML-INTO with %Handler XML-INTO Using %HANDLER XML-INTO { ( E H ) } %HANDLER ( ... ) %XML ( ... ) Commonly used prior to V6 when size of data exceeded RPG's limits • i.e. Number of elements exceeded 32,767 or the size of the DS exceeded 64K – There are still limits at 6.1 and later - but they are much, much higher Also useful if you want to process the XML in pieces • For example one order at a time Notice that %Handler replaces the INTO variable Instead of storing the data directly XML-INTO passes it to you to process %HANDLER BIF identifies the subprocedure that will "handle" the processing • It will be called as each "INTO" action is completed. • It can then process the data in any way that you like • This is a technique known as "Call Back" processing • It can also be very useful in your own applications © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 42 of 146 How the %Handler Process Operates This is a "call-back" process C functions such as qsort and bsearch use the same approach XML-INTO (or -SAX) starts the process by calling the XML parser The parser notifies the handler when it has data ready to process The handler processes the data and returns control to the parser The parser returns to step 2 unless parsing is completed at which time ... The parser returns control to the main-line RPG Main Line Code XML-INTO (or XML-SAX) %HANDLER identifes handling routine Passes communications area 1 4 2 RPG Handler Subprocedure Parser processes XML document Receives data passed to it by the parser Plus the original communications area 3 Notes Call-back processing is a somewhat "alien" technique to RPG programmers although widely used in many other languages. It is very useful in circumstances where you have a utility function (such as an XML parser!) but need to customize its operation. Think of it as a bit like user exits in package software. In addition to its use with the %HANDLER BIFs, call-back processing is used in many other utilities. For example the sort and search functions qsort() and bsearch() both make use of call-back processing. You might find the description of these functions in the RPG Redbook "Who Knew You Could Do That With RPG" useful if you find the concept hard to grasp. You can find the Redbook here: www.redbooks.ibm.com/abstracts/sg245402.html © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 43 of 146 %HANDLER BIF - XML-INTO Version %HANDLER ( prototype : comm_area) • The first parameter names the prototype of the handling procedure ✦ This procedure will be called as each "INTO" action completes Handler's Parameter definition is shown below and described here: • First parameter is the comm(unications) area ✦ Use it to communicate between your main program and the handler - i.e. To indirectly pass parameters to the handler subprocedure when it is not be in the same program as the main line code • Second parameter is the area to be filled ( i.e. It is the -INTO target ) ✦ It must be an array - even if it only contains 1 element • Third is an RPG supplied count of the number of array elements filled ✦ It will never be zero - at that point control will be back in your mainline code Dcl-Pr MyHandler commArea company numElements End-Pr; Int(10); LikeDS(myCommArea); Like(companyName) Dim(4) Const; Int(10) Value; Notes Unless you already have a good grasp of call-back concepts, the use of %Handler can be hard to get your head around. We have found that the best way to get a handle on the situation (sorry - couldn't resist!) is to run the program in debug. Step through your program making sure that you either set a breakpoint in your handler routine or use Step-into (not just step) when you step the XML-INTO operation. You will find that you immediately pop-up in the handler subprocedure. If you step through that code to the return, you will find immediately after the return operation you will pop back to the beginning of the subprocedure. This seems a little odd since we would normally expect to return to the point at which we were called. And of course that is just what happens - but we don't see it because the caller was the XML parser! So after the return we went back to the parser - it did more work and then called us again - but we didn't see that happen because we can't "see" inside IBM's code. Eventually the parser will have completed its work, and that is when we will return to the point at which the XML-INTO (or indeed XML-SAX) was executed. D-spec version of data definitions: d MyHandler d commArea d company d numElements © Partner400, 2015 PR 10i 0 LikeDS(myCommArea) Like(companyName) Dim(4) Const 10i 0 Value Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 44 of 146 XML-INTO - Example 4 - %HANDLER This is similar to Example 2 but uses %Handler • This allows us to process documents in pieces ✦ Also helps deal with large XML documents - ✦ Less important since the V6 relaxation of data size limitations Each time the parser has X elements ready the handler routine is called - X being the number of elements we specify on the Dim in the prototype It may be called one last time to handle the final group of elements Dcl-Pr HandleCompany commArea company numElements End-Pr; Int(10); Int(10); Char(32) Dim(4) Const; Int(10) Value; Dcl-Ds customers; company Char(32) Dim(4); End-Ds; Dcl-S count Int(10) Inz; XML-INTO %Handler( HandleCompany: count ) %XML(XML_Input1: 'path=Customers/Company case=any' ); Dsply ('Processed ' + %char(count) + ' total elements'); Notes D-spec version of data definitions: d HandleCompany d commArea d company d numElements PR d customers d company DS d count S © Partner400, 2015 10i 0 10i 0 32a Dim(4) Const 10i 0 Value 32a Dim(4) 10i 0 Inz Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 45 of 146 XML-INTO - Example 4 - The handler numElements contains the count of elements passed on this call • It can only ever be less than the maximum on the final call ✦ And it will never be zero Dcl-Proc HandleCompany; Dcl-Pi *N count company numElements End-Pi; Dcl-S i Int(10); Int(10); Char(32) Dim(4) Const; Int(10) Value; Int(10); for i = 1 to numElements; dsply company(i); endfor; // add current count to total in Comm Area and display current count count += numElements; dsply ( %Char(numElements) + ' elements loaded on this call' ); return 0; End-Proc; Results of %Handler Program <Customers> <Company>Phones R Us</Company> <Company>Suchadeal Bank</Company> <Company>Rinky Dinky Toy Co.</Company> <Company>Partner400</Company> <Company>BlackHole Navigation</Company> <Company>Bankers Trust</Company> </Customers> Sample XML contained the above customer entries, and the array had a maximum capacity of 4. This is the result of running our example program against that XML stream. DSPLY Phones R Us DSPLY Suchadeal Bank DSPLY Rinky Dinky Toy Co. DSPLY Partner400 DSPLY 4 elements loaded on this call DSPLY BlackHole Navigation DSPLY Bankers Trust DSPLY 2 elements loaded on this call DSPLY Processed 6 total elements D-spec version of the definitions: p d d d d HandleCompany count company numElements d i © Partner400, 2015 b pi s 10i 0 10i 0 32a Dim(4) Const 10i 0 Value 10i 0 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 46 of 146 XML-SAX XML-SAX The basics XML-SAX Dcl-Pr myHandler Int(10); commArea LikeDS(myCommArea); event Int(10) VALUE; pstring Pointer VALUE; stringLen Int(20) VALUE; exceptionId Int(10) VALUE; End-Pr; "SAX" stands for Simple API for XML But there's no such thing as a truly "Simple" example of SAX parsing So we will simply demonstrate the basic process of identifying events With XML-SAX the %HANDLER BIF must always be specified Parameters to the Handler routine are not the same as for XML-INTO Except for the return value and the Communications Area Second parameter is a numeric value to notify you of which event occurred ‣ It can be compared with RPG IV Special Words such as *XML_ATTR_NAME The third is a pointer to the string containing the current token ‣ i.e. The attribute or element name, or data content – The fourth is the length of that string – Fifth contains an exception Id. when event *XML_EXCEPTION is signaled © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 47 of 146 XML-SAX Events These include: *XML_START_DOCUMENT Indicates that parsing has begun *XML_START_ELEMENT The name of the XML element that is starting *XML_CHARS The value of the XML element *XML_END_ELEMENT The name of the XML element that is ending *XML_ATTR_NAME The name of the attribute *XML_ATTR_CHARS The value of the attribute *XML_END_ATTR Indicates the end of the attribute *XML_END_DOCUMENT – Indicates the end of the document Notes This is just a small sampling of XML-SAX event codes. There are more than 20 in total although many you will never see in practice. The RPG manual details all of the codes and their meanings. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 48 of 146 XML-SAX Example Dcl-Pr MySAXHandler commArea event pstring stringLen exceptionId End-Pr; Int(10); Like(myCommArea); Int(10) Value; Pointer Value; Int(20) Value; Int(10) Value; Dcl-Ds eventCodeData; *N Int(10) Inz(*XML_START_DOCUMENT); *N Int(10) Inz(*XML_VERSION_INFO); // ... eventCodes Int(10) Dim(25) POS(1); End-Ds; XML-SAX %Handler( MySAXHandler : myCommArea ) %XML( 'XMLSAXDOC.xml' : 'doc=file'); As noted a simple SAX example is a contradiction So our example will simply demonstrate the basic mechanism It is part of a program that reports on the content of an XML document Full source code is here: www.Partner400.com/Examples/XMLSAXTST.HTM Notes D-specs version: D MySAXHandler D commArea D event D pstring D stringLen D exceptionId Pr d eventCodeData d d d d eventCodes DS © Partner400, 2015 10I 0 10I 0 * 20I 0 10I 0 10i 10i 10i 10i 0 0 0 0 Like(myCommArea) Value Value Value Value Inz(*XML_START_DOCUMENT) Inz(*XML_VERSION_INFO) Inz... Dim(25) Overlay(eventCodeData) Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 49 of 146 XML-SAX Processing Dcl-S Dcl-S Dcl-S Dcl-S string UCS2String element eventIndex Char(65535) Based(pstring); Varucs2(16383) Based(pString); Int(10); Int(5); eventIndex = %lookup( event : eventCodes ); // identify event if eventIndex > 0; eventName = eventNames( eventIndex ); if event = *XML_EXCEPTION; data = '*** Exception offset is ' + %char(stringLen); returnCode = -1; elseif stringLen > 0; // Use string length - ptr is unreliable if ( event = *XML_UCS2_REF OR event = *XML_ATTR_UCS2_REF ); UCS2StringLen = ( stringLen / 2 ); data = %char( %subst(UCS2String : 1 : UCS2StringLen ) ); else; data = RmvWhiteSpace(%subst(string : 1 : stringLen)); endif; else; data = '** No Data **'; endif; else; // Unknown event ... Notes D-spec version of definitions: D string s 65535a D UCS2String D element s s 16383a Based(pString) 10i 0 D eventIndex s © Partner400, 2015 Based(pstring) 5i 0 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 50 of 146 The Critical Part ! ... elseif stringLen > 0; // Use string length - ptr is unreliable if ( event = *XML_UCS2_REF OR event = *XML_ATTR_UCS2_REF ); UCS2StringLen = ( stringLen / 2 ); data = %char( %subst(UCS2String : 1 : UCS2StringLen ) ); else; data = RmvWhiteSpace(%subst(string : 1 : stringLen)); ... Use the length of the string to determine if there is any data Manual indicates that the pointer is null when there is no data - but it lies! And also use the length to control the extraction of the data Remember that the field string is big (65,535 characters) and that only the first part is valid - that’s why I used %SUBST Notes D-specs: D D D D string UCS2String element eventIndex © Partner400, 2015 S S s s 65535A 16383C 10i 0 5i 0 Based(pstring) Based(pString) Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 51 of 146 Sample Report This is an extract from the report produced by the program when processing a sample XML document. • Items in RED cannot be retrieved by XML-INTO only by XML-SAX Event Code and RPG name 20 *XML_START_DOCUMENT 25 *XML_VERSION_INFO 10 *XML_ENCODING_DECL 18 *XML_STANDALONE_DEC 9 *XML_DOCTYPE_DECL 6 *XML_COMMENT 21 *XML_START_ELEMENT 5 *XML_CHARS 21 *XML_START_ELEMENT 2 *XML_ATTR_NAME 4 *XML_ATTR_CHARS 26 *XML_END_ATTR 2 *XML_ATTR_NAME 23 *XML_UNKNOWN_ATTR_R 26 *XML_END_ATTR 13 *XML_END_ELEMENT Length 13 8 3 43 34 8 1 5 4 12 18 3 15 Data "** No Data **" "1.0" "ibm-1140" "yes" "<!DOCTYPE page [ <!ENTITY abc "ABC"> ]>" "This document is just an example" "sandwich" "" "bread" "type" "baker's best" "** No Data **" "supplier" "abc" "** No Data **" "bread" Questions ???? E-mail me at Jon.Paris @ Partner400.com Any time! © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 52 of 146 RPG XML-xxxx Error Status Codes 1: The parser found an invalid character while scanning white space outside element content. 2: The parser found an invalid start of a processing instruction, element, comment, or document type declaration outside element content. 3: The parser found a duplicate attribute name. 4: The parser found the markup character '<' in an attribute value. 5: The start and end tag names of an element did not match. 6: The parser found an invalid character in element content. 7: The parser found an invalid start of an element, comment, processing instruction, or CDATA section in element content. 8: The parser found in element content the CDATA closing character sequence ']]>' without the matching opening character sequence '<![CDATA['. 9: The parser found an invalid character in a comment. 10:The parser found in a comment the character sequence '--' (two hyphens) not followed by '>'. 11: The parser found an invalid character in a processing instruction data segment. 12: A processing instruction target name was 'xml' in lowercase, uppercase or mixed case. 13: The parser found an invalid digit in a hexadecimal character reference (of the form �, for example ັ). 14: The parser found an invalid digit in a decimal character reference (of the form &#dddd;). 15: A character reference did not refer to a legal XML character. 16: The parser found an invalid character in an entity reference name. 17: The parser found an invalid character in an attribute value. 18: The parser found a possible invalid start of a document type declaration. RPG XML-xxxx Error Status Codes 19: The parser found a second document type declaration. 20: An element name was not specified correctly. The first character was not a letter, '_', or ':', or the parser found an invalid character either in or following the element name. 21: An attribute was not specified correctly. The first character of the attribute name was not a letter, '_', or ':', or a character other than '=' was found following the attribute name, or one of the delimiters of the value was not correct, or an invalid character was found in or following the name. 22: An empty element tag was not terminated by a '>' following the '/'. 23: The element end tag was not specified correctly. The first character was not a letter, '_', or ':', or the tag was not terminated by '>'. 24: The parser found an invalid start of a comment or CDATA section in element content. 25: A processing instruction target name was not specified correctly. The first character of the processing instruction target name was not a letter, '_', or ':', or the parser found an invalid character in or following the processing instruction target name. 26: A processing instruction was not terminated by the closing character sequence '?>'. 27: The parser found an invalid character following '&' in a character reference or entity reference. 28: The version information was not present in the XML declaration. 29: The 'version' in the XML declaration was not specified correctly. 'version' was not followed by '=', or the value was missing or improperly delimited, or the value specified a bad character, or the start and end delimiters did not match, or the parser found an invalid character following the version information value closing delimiter in the XML declaration. 30: The parser found an invalid attribute instead of the optional encoding declaration in the XML declaration. 31: The encoding declaration value in the XML declaration was missing or incorrect. The value did not begin with lowercase or uppercase A through Z, or 'encoding' was not followed by '=', or the value was missing or improperly delimited or it specified a bad character, or the start and end delimiters did not match, or the parser found an invalid character following the closing delimiter. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 53 of 146 RPG XML-xxxx Error Status Codes 32: The parser found an invalid attribute instead of the optional standalone declaration in the XML declaration. 33: The 'standalone' attribute in the XML declaration was not specified correctly. 'standalone' was not followed by a '=', or the value was either missing or improperly delimited, or the value was neither 'yes' nor 'no', or the value specified a bad character, or the start and end delimiters did not match, or the parser found an invalid character following the closing delimiter. 34: The XML declaration was not terminated by the proper character sequence '?>', or contained an invalid attribute. 35: The parser found the start of a document type declaration after the end of the root element. 36: The parser found the start of an element after the end of the root element. 300: The parser reached the end of the document before the document was complete. 301: The %HANDLER procedure for XML-INTO or XML-SAX returned a non-zero value, causing the XML parsing to end. 302: The parser does not support the requested CCSID value or the first character of the XML document was not '<'. 303: The document was too large for the parser to handle. The parser attempted to parse the incomplete document, but the data at the end of the document was necessary for the parsing to complete. 500-999: Internal error in the external parser. Please report the error to your service representative. 10001-19999: Internal error in the parser. Please report the error to your service representative. RPG XML-xxxx Error Status Codes Limitations of the XML Parser If the parsing is done in a single-byte character CCSID, the maximum number of characters that the parser can handle is 2,147,483,408. If the parsing is done in UCS-2, the maximum number of UCS-2 characters that the parser can handle is 1,073,741,704. The parser does not support every CCSID. If your job CCSID is one of the CCSIDs that the parser does not handle, you must parse your document in UCS-2. The following EBCDIC CCSIDs are supported: 1047, 37, 1140, 273, 1141, 277, 1142, 278, 1143, 280, 1144, 284, 1145, 285, 1146, 297, 1147, 500, 1148, 871, and 1149. The following ASCII CCSIDs are supported: 819, 813, 920. The following Unicode CCSIDs are supported: 1200, 13488, 17584. The parser does not support entity references. When it encounters an entity reference, it generates either an "unknown reference" or "unknown attribute reference" event. The value of the event is the reference in the form "&name;". The parser does not parse the DOCTYPE declaration. The text of the DOCTYPE declaration is passed as the data value for the "DOCTYPE declaration" event. The parser does not generate "start prefix mapping" and "end prefix mapping" events. It ignores the colons in XML element and attribute names. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 54 of 146 "Instant" Web Services and Stored Procedures Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Notes Jon Paris is co-founder of Partner400, a firm specializing in customized education and mentoring services for IBM i * developers. Jon's career in IT spans 40+ years including a 12 year period with IBM's Toronto Laboratory. Together with his partner Susan Gantner, Jon devotes his time to educating developers on techniques and technologies to extend and modernize their applications and development environments. Together Jon and Susan author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i EXTRA. You may view articles in current and past issues and/or subscribe to the free newsletter at: www.IBMSystemsMag.com. Jon and Susan also write a weekly blog on Things "i" - and indeed anything else that takes their fancy. You can find the blog here: ibmsystemsmag.blogs.com/idevelop/ Feel free to contact the author at: Jon.Paris @ Partner400.com * IBM i - aka AS/400, System i, iSeries, i5, etc. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 55 of 146 Agenda How? • XMLSERVICE - the latest IBM toolkit What is it ? • And why did we need a new toolkit ? What is different about it ? • It is Open Source and written in RPG! How is it used ? • From other languages and platforms XMLSERVICE - What Is It ? Created as part of the Zend/IBM partnership • Now the core of Zend's PHP Toolkit for IBM i Allows usage of any program or utility on your IBM i • Programs, Service Programs, CL commands, PASE utilities, ... From anywhere • • Another IBM i Windows, Unix, Linux, ... Once set up nothing else required • New programs can be accessed without any additional work Two forms of access supported • • Web Service Stored Procedure Development site hosted by the Young i Professionals • youngiprofessionals.com © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 56 of 146 Why Did We Need a New Toolkit ? Limitations in existing tooling • Java toolbox does not support return values ✦ ✦ • Except for four byte integers ** Limitation can be avoided in V7 by using the new RTNPARM option Requires use of Java by requesting platform ✦ Alternatively the other platform needs a Java bridge - • • Java programming skills required to enhance toolbox Slow to deploy new functionality ✦ • If Java programmers don't need it there was no incentive to add it New functionality means changes in PCML generation ✦ • In all other cases you are limited to ODBC capability So you have to wait for the compiler team to implement it The result? ✦ It took years to get new data types added to the interface Why Did We Need a New Toolkit ? Limited support for persistence • Problematic if you want to deploy an existing 5250-style application as a web service or browser application Proprietary Technology • Java toolkit originally developed by IBM ✦ • Subsequently released as Open Source i5xxx components of PHP toolkit actually produced by Aura ✦ Known by Aura as EasyCom Manual effort required to deploy new functionality • Have to set up a new web service or stored procedure or ... © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 57 of 146 What is Different About the New Toolkit ? Open Source under BSD License • Completely free to use 100% RPG IV • Any RPG programmer can modify All communications via XML documents • • Easier to debug Automatic conversion between ASCII and EBCDIC Usable in single and two tier environments • • All code on single IBM i Or any other system to IBM i Supports persistent connections • Called RPG programs can retain state information ✦ • i.e. Just as they normally do in a 5250 job Facilitates repurposing of existing 5250 applications as web services or browser jobs What is Different About the New Toolkit ? Supports complex parameter types • Arrays, DS, DS Arrays, Compound DS, ... Program calls and commands can be run in sequence • Ensures that results of one operation are available to the next Directly supports return values • Although RPG's V7 RTNPARM keyword also allows you to avoid this problem Can be invoked from any Client-side language • • As long as they can invoke a web service Or use SQL connections Security • Requires use of valid user name and password ✦ • No matter what protocol is used Facilitates access control to IBM i resources © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 58 of 146 Toolkit Architecture Cross Platform Language Agnostic • Accessible from any language How Is It Used ? - From PHP New Zend Toolkit uses XMLSERVICE under the covers • Supports all previous functionality ✦ • Completely new set of APIs ✦ • Except Record Level Access Plus "wrappers" to allow existing code to use the new APIs Delivered as part of the Zend Server download ✦ Or obtain from Young i Professionals site if you don't want PHP • http://youngiprofessionals.com/wiki/XMLSERVICE Allow PHP access from other platforms • • So you can develop complete PHP applications that invoke IBM i functionality on Linux, Windows and Mac The Zend components are also Open Source All of the new APIs use an OO approach • Still very easy to use for the non-OO programmer Details later if we have time © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 59 of 146 XMLSERVICE: Stored Procedure Interface Supplied stored procedures are the preferred access method • Can be accessed via ODBC, JDBC, DB2Connect, etc. Can be used to access multiple programs • Eliminates need to create one stored procedure per program Accepts four parameters • IPC Key ✦ A path to an IFS folder associated with a specific XMLSERVICE job - • CTL ✦ Parameters to control the service job's behavior - • This is part of the persistence mechanism e.g Debug mode, shut down job, etc. CLOB In and CLOB out ✦ The Input and Output XML documents XMLSERVICE: REST Web Service Interface Takes Seven Parameters ✦ Note that three are the same as for the Stored Procedure interface db2 • Name of database to access (*Local or Database name) uid • User Id pwd • Password ipc and ctl • Same as for the IPC and CTL Stored Procedure parameters xmlin • XML Input document xmlout • Maximum expected size of returned XML output document © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 60 of 146 XML Document Elements <cmd> … </cmd> • Executes the command line command specified between the <cmd> tags <sh> … </sh> • PASE equivalent of the <cmd> tag. <pgm> … </pgm> • Identifies program or subprocedure to be run ✦ ✦ Attributes name= lib= and func= identify program name, library, and subprocedure name Parameters and return values for the program are defined within the <parm> element <parm> … </parm> • Identifies a parameter. More details on next chart <ds> … </ds> • Indicates that the parameter is a data structure. More details on next chart <data> … </data> • Defines the parameter and populates it. <return> … </return> • Identifies the return value on a subprocedure. ✦ Definition of the return value is via <data> and <ds> tags just like parameters XML Document Element Attributes <parm> attributes • • io=... Parameter type "in" "out" or “both” by=... Parameter passing method ✦ “val” (value) or “ref” (reference) <ds> attributes • • dim=... Maximum number of DS array elements dou=... Number of array elements actually populated. <data> attributes • • var=... Names the variable type=... Data type and size ✦ • e.g. 12a, 15p5, 9s2, etc. varying=... *on indicates a varying length field. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 61 of 146 Other Features of the XML Documents A single XML document can contain multiple requests • They will be performed in sequence • Makes it simple to perform tasks like setting the library list Success or failure is indicated in the returned document • See example in document below Tags and attributes not used by XMLSERVICE are allowed • They are simply passed through and included in the XML output • You can add extra markup to assist in parsing returned XML • Or comments that will aid in debugging <?xml version="1.0"?> <mult> <cmd comment='addlible'>+++ success ADDLIBLE LIB(JONSLIB)</cmd> <pgm name='MYPGM' lib='JONSLIB' func='MYPGM'> <parm ..... Simple Example of the Web Service Interface A simple program that takes two parameters • A 15 digit packed numeric and a 30 character • The second parameter will be modified by the called program The User of your web service must send the XML shown below // Proto for XMLSRVTST1 d XMLSrvTst1 pr ExtPgm(‘XMLSRVTST1’) d input 15p 5 d output 30a < ?xml version='1.0'? > < pgm name='XMLSRVTST1' lib='XMLSERVICE' > < parm > < data type='15p5' >5678901234.54321< /data > < /parm > < parm > < data type='30A' >Original value< /data > < /parm > < /pgm > © Partner400, 2015 This is the XML representation of the prototype shown above Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 62 of 146 XML Returned by the Web Service The document returned is almost identical to the one we sent • But the parameter details (length and type) are omitted • And the contents of the second parm have changed ✦ This is highlighted below More on the XML in a moment // Proto for XMLSRVTST1 d XMLSrvTst1 pr ExtPgm(‘XMLSRVTST1’) d input 15p 5 d output 30a // Relevant portion of the XML document returned < ?xml version='1.0'? > < pgm name='XMLSRVTST1' lib='XMLSERVICE' > < parm > < data >5678901234.54321< /data > < /parm > < parm > < data >Input was 5678901234.54321< /data > < /parm > < /pgm > Original field definitions are not returned in the response document. Only the field values More Complex XML Call Document Elements and attributes not required by XMLSERVICE are echoed • Useful in debugging returned data ✦ Note the use of the field names D LookupTest D searchFor D max D count Pr LikeDS(rec_T) Dim(10) 10a 10i 0 10i 0 <pgm name='MYSRVPGM' func='LOOKUPTEST'> <parm comment='search for this name'> <data var='searchFor' type='10A'>Paris</data> </parm> <parm comment='max allowed return'> <data var='max' type='10i0'>5</data> </parm> <parm comment='count of records returned'> <data var='count' type='10i0' enddo='count'>0</data> </parm> <return> <ds var='rec_t' dim='10' dou='count'> <data var='name' type='30A'>na</data> <data var='job' type='40A'>na</data> <data var='pay' type='9p2'>0.0</data> </ds> </return> </pgm> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 63 of 146 Document Returned From Procedure Call <pgm name='MYSRVPGM' func='LOOKUPTEST'> <parm comment='search for this name'> <data var='searchFor'>Paris</data> </parm> <parm comment='max allowed return'> <data var='max'>5</data> </parm> <parm comment='count of records returned'> <data var='count'>3</data> </parm> <return> <ds var='rec_t'> <data var='name'>Jon Paris</data> <data var='job'>Chief Cook and Bottle Washer</data> <data var='pay'>13.42</data> </ds> <ds var='rec_t'> <data var='name'>Susan Gantner-Paris</data> <data var='job'>Manager</data> <data var='pay'>26.84</data> </ds> ... </return> </pgm> Original field definitions are not returned in the response document. Only the field values More info and examples of PHP usage follow but I do not have time to go into them in detail. Contact me in the break or by email after the event if you have questions. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 64 of 146 The New XMLService Web Demo Page Nothing but HTML! • Honestly Not pretty - but pretty amazing nonetheless • Examples of SQL, calling programs, issuing commands, etc. youngiprofessionals.com/wiki/uploads/XMLSERVICE/ DemoHtml.html • Use browser's View Source to see the underlying HTML/XML Example of HTML Used This is the HTML code used to call the subprocedure ZZTIMEUSA • Note how the XML data is defined <!-- XMLSERVICE call a *SRVPGM (time *USA) --> <form name="input" action="/cgi-bin/xmlcgi.pgm" method="post"> <input type="hidden" name="db2" value="*LOCAL"> <input type="hidden" name="uid" value="*NONE"> <input type="hidden" name="pwd" value="*NONE"> Note that the XML <input type="hidden" name="ipc" value="/tmp/rangerhtmlonly"> packet is specified as the value <input type="hidden" name="ctl" value="*sbmjob"> for the xmlin variable <input type="hidden" name="xmlin" value= "<?xml version='1.0'?> <?xml-stylesheet type='text/xsl' href='/wiki/uploads/XMLService/DemoXslt.xsl'?> <script> <pgm name='ZZSRV' lib='XMLSERVICE' func='ZZTIMEUSA'> <parm io='both'> <data type='8A'>09:45 AM</data> </parm> <return> <data type='8A'>nada</data> </return> </pgm> It ends here </script>"> <input type="hidden" name="xmlout" value="5000"> <input type="submit" value="call *SRVPGM (ZZTIMEUSA)" /> </form> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 65 of 146 PHP Methods and Functions in ToolkitService The main class that manages all DB2 settings and communication • • Includes methods to Set Database Connections And Set XML Service Options CLCommand • Run CL commands either in interactive or batch mode PGMCall method • Calls programs (*PGM) or service programs (*SRVPGM) AddParameterxxxx methods • • Define the program parameters. Methods available for all the usual suspects SetParameterValue • Sets new parameter values disconnect interface • • Terminates an XML service job in the ZENDSVR subsystem Don't use this with persistent jobs unless and until you want to PHP Usage - Issue Interactive CL Command Copy User Id, and Password plus default library etc. <?php include_once 'authorization.php'; include_once '../API/ToolkitService.php'; Copy in toolkit script $obj = ToolkitService::getInstance($db, $user, $pass); $parms = array('InternalKey'=>"/tmp/$user",'debug'=>true,'plug' => "iPLUG32K"); $obj->setToolkitServiceParams($parms); $cmd = "addlible ZENDSVR"; $obj->CLCommand($cmd); Initial CL Command is executed followed by the interactive command below Controls the maximum size of message that can be handled $Rows = $obj->CLInteractiveCommand("DSPLIBL"); if($Rows) Interactive command returned a result set var_dump($Rows); the 5250 display data else echo $obj->getLastError(); $obj->disconnect(); ?> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 66 of 146 PHP Usage - Call RPG Program <?php include_once 'authorization.php'; include_once '../API/ToolkitService.php'; $extension='ibm_db2'; $ToolkitServiceObj = ToolkitService::getInstance($db, $user, $pass, $extension); $ToolkitServiceObj->setToolkitServiceParams(array('InternalKey'=>"/tmp/$user")); $code = $_POST ['code']; // Get value of Code field from input $desc = ' '; // Blank the output parameter Set up the parameters $param[] = $ToolkitServiceObj->AddParameterChar('both', 10,'CODE', 'CODE', $code); $param[] = $ToolkitServiceObj->AddParameterChar('both', 10,'DESC', 'DESC', $desc); $result = $ToolkitServiceObj->PgmCall("TESTPGM", "ZENDSVR", $param, null, null); if($result) var_dump($result['io_param']); else echo "Execution failed."; $ToolkitServiceObj->disconnect(); RPG program requires two parameters each 10 characters long. The previous lines placed them in the array ?> PHP Usage - Call Service Program Procedure <?php ... $SysValueName = "QCCSID"; $param[] = $ToolkitServiceObj->AddParameterChar('both', 1,'ErrorCode','errcode', $Err); $param[] = $ToolkitServiceObj->AddParameterChar('both', 10,'SysValName','sysvalname', $SysValueName); $param[] = $ToolkitServiceObj->AddParameterChar('both', 1024,'SysValue','sysvalue', $SysValue); $OutputParams = $ToolkitServiceObj->PgmCall('ZSXMLSRV', "ZENDSVR", $param, NULL, array('func'=>'RTVSYSVAL') ); Function name if( isset($OutputParams['io_param']['sysvalname'])) echo " System value ".$SysValueName." = ".$OutputParams['io_param']['sysvalue']; else Change parameter echo "System value $SysValueName was not retrieved."; value // Change requested value and execute call again ProgramParameter::UpdateParameterValues( $param, array("sysvalname"=>"QLANGID")); $OutputParams = $ToolkitServiceObj->PgmCall('ZSXMLSRV', "ZENDSVR", $param, NULL, array('func'=>'RTVSYSVAL') ); ... ?> © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 67 of 146 iToolkitClass Methods and Functions - Part 1 Data Queue • • • • • • CreateDataQ() DeleteDQ() ReceieveDataQueue() SendDataQueue() SetDataQName() ClearDQ() User Space • • • • • • CreateUserSpace() RetrieveUserSpaceAttr() RetrieveUserSpaceSize() DeleteUserSpace() WriteUserSpace() ReadUserSpace() iToolkitClass Methods and Functions - Part 2 Spooled Files • • GetSPLList() GetSPLF() System Values • • SystemValuesList() GetSystemValue() Job Log • • • JobList() JobLog() GetJobInfo Object List • getObjectList() © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 68 of 146 Questions ? If you think of any questions after the session please email me: Jon.Paris @ Partner400.com © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 69 of 146 © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 70 of 146 RPG in V7 Including the New Free-Form Definitions. Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Notes If you thought that RPGIV had changed over the years - well "You ain't seen nothin' yet!!" V7 brought with it a number of enhancements but the one that has made the biggest impact has undoubtedly been the introduction of free-form data and file definitions. Most of us have been making extensive use of Evals since they were first introduced with RPG IV in V3R1. Perhaps like us you have experienced frustration when forced to split a line because you had run out of space! Maybe you were even tempted to shorten that nice meaningful variable name to avoid having to use a second line! While you were contemplating this dilemma, you might have also noted the fact that there was a huge area of white space to the left of the Eval you couldn't use. V5R1 put an end to that frustration by introducing the notion of completely free-format logic specs. Coupled with a large number of new Built-In Functions (BIFs) this "New" RPG remains familiar, while offering some very powerful new capabilities. Then in V7.1 (with TR7) almost all the rest of the RPG language went free-format - with the addition of free-format replacements for H, F, D and P specs. In this session we will look at: How to code free format RPG logic ✦ How to replace operation codes that aren't supported in free format RPG ✦ The new BIFs that add power to the language ✦ New functions that only work in Free-form form logic How to use the free format replacements for H, F, D and P specs © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 71 of 146 V 7.1 RPG Enhancements Data Definition and Usage Enhancements Sort and Search Data Structure Arrays Support For Alias Names New Scan and Replace BIF Subprocedure related enhancements Prototypes are optional Performance improvements for large return values Process Stored Procedure Result Sets And of course free-form file and data definitions! Notes We don't have time to go into all of these features in depth but we have written articles on many of them and of course you can find more about any that I skip by checking out the V7 RPG manual in the Information Center. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 72 of 146 Sorting Data Structure Arrays Data structure arrays can now be sorted • Using any of the subfields as the key ✦ But only a single subfield can be used - No direct support for sorting multi-dimensional arrays • Asterisk (*) identifies the level at which the array is to be sorted SORTA can now have sequence specified • Using the Op-code extenders A(scending) or D(escending) • Can only be used when no sequence specified in the D-specs D products1 D productCode D description D totalSales D qtyInStock DS Dim(999) Qualified 5a 40a Varying 9p 2 5p 0 SortA products1(*).totalSales; SortA products1(*).description; SortA(A) products1(*).totalSales; // Sort ascending sequence SortA(D) products1(*).description; // Sort descending sequence Notes With the advent of V5R2, it became possible to define Data Structure arrays. i.e. with a DIM keyword at the DS level. But at the time IBM did not provide any means by which such arrays could effectively be sorted. To do that you had to resort to using the qsort function. That shortcoming is removed in V7 and you can now sort on any subfield in the array. For example, given the DS array here, you can perform a sort on qtyInStock or totalSales or any of the other fields in the DS. As you can see from this code, the level of the array to be sorted is indicated by an asterisk (*) in the subscript position. In the first example the DS array is sequenced on the totalSales values, and in the second the description. Another nice addition to the SORTA repertoire is that you can now specify whether the sort is to be in ascending or descending sequence. Previously this was determined by the ASCEND or DESCEND keyword on the array definition and SORTA used the defined sequence - which of course meant that without playing games (re-mapping the array via pointers etc.) any given array could only ever be in ascending _or_ descending order. Now the op-code extenders (A) and (D) can be used to specify the sequence in which the array is sorted. The * is used to indicate the level at which the sorting should occur. Of course, in these examples, it's pretty obvious, since it's the only level where sorting is possible. But this sorting capability also works with nested Data Structures, so even very complicated structures can be sorted. The next chart shows you how. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 73 of 146 Sorting Nested Data Structure Arrays As noted earlier - no direct RPG support exists • But this is one way to do it D products2 D productCode D description D salesByMonth D D qtyInStock DS D salesByMonth D monthNumber D sales DS D i S 5a 40a 5p 0 Dim(999) Qualified Varying LikeDS(salesByMonth) Dim(12) 3p 0 9p 2 5i 0 // Sort each entry in turn into sales value sequence For i = 1 to %elem(products2); SortA products2(i).salesByMonth(*).sales; EndFor; // Once individual entries have been sorted into sales value // sequence sort the whole array into product code sequence SortA products2(*).productCode; Notes Even nested arrays can be sorted as shown in the example below. In this case the inner array (i.e. the monthly sales values) is sorted into ascending sequence and then the outer array (i.e. the products) are sorted into product code sequence. Of course, it doesn’t really matter which version of SORTA statement is done first - i.e., we could have sorted by ProductCode first, followed by the loop to sort the sales figures. Note, however, that there is still no support to sort on 2 (or more) different subfields in a DS array, unless the 2 subfields are contiguous in the DS and in the “correct” sequence. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 74 of 146 Searching DS Arrays %LOOKUP can now also search DS arrays • The same asterisk (*) notation is used to indicate the search level Only the vanilla %LookUp is supported • Not the %LookUpGt etc. versions D productInfo D D productCode D description D unitPrice D qtyInStock DS D element S 5a 40a 5p 2 5p 0 Dim(1000) Qualified 5i 0 element = %LookUp( 'A123C': productInfo(*).productCode); // Sort into price sequence and then find first value above $50. SortA productInfo(*).unitPrice; element = %LookUp( 50.00: productInfo(*).unitPrice); Notes To be truly useful, any enhancement in sorting needs to be matched with corresponding advances in searching, and the RPG developers haven't let us down. They have enhanced the %LOOKUP BIF to allow for searching within DS arrays. At this time only the "exact match" %LookUp is supported. Hopefully %LookUpGt and other members of the family will be supported in future releases. In the meantime you will have to write your own routine to do this. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 75 of 146 Using ALIAS Names ALIAS names can be used in Externally-described data structures • By using the ALIAS keyword on the DS definition ALIAS names can be used for file fields • Use the ALIAS keyword on the File specification ✦ Any LIKEREC or EXTNAME DS based on the file will use the ALIAS name DDS for file CUSTFILE A A A A R CUSTREC CUSTNM CUSTAD ID D custDs D 25A 25A 10P 0 e ds ALIAS(CUSTOMER_NAME) ALIAS(CUSTOMER_ADDRESS) ALIAS QUALIFIED EXTNAME(custFile) custDs.customer_name = 'John Smith'; custDs.customer_address = '123 Mockingbird Lane'; custDs.id = 12345; Notes For many, many years going all the way back to the System/38, the database has supported the use of longer alias names as an alternative to the cryptic 10 character names that we normally use. Indeed many COBOL shops have always taken advantage of them. Usage of alias names has also increased in recent years with the growth in popularity of SQL. But during all this time RPGers were locked out of using these longer names as the language was tied to the old I and O specs and their limited field names. When result field I/O was first introduced for externally described files - back in the V5R2 timeframe - we felt that this might herald the arrival of Alias names into RPG. Well it has taken a few releases, but it is finally here. As from V7.1 you can specify the ALIAS keyword when defining an externally described DS, or any DS defined with LIKEREC. When the ALIAS keyword is used, the compiler uses the longer alias name for the field rather than the short name. In cases where the alias name does not meet RPG naming standards, the compiler reverts to using the short name. Note that you put the ALIAS keyword on the F spec if you choose to create the DS using LIKEREC. However, if you create the DS using EXTNAME, then use specify ALIAS on the DS description on the D spec. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 76 of 146 Scan and Replace New built-in function %SCANRPL %SCANRPL ( scanFor : replaceWith : targetString ) • Replaces all occurrences of scanFor within the target string with the contents of replaceWith ✦ Optional 4th & 5th parameters for scan-start-position and scan-length ✦ %SCANRPL( scanFor : replaceWith : target { : scan start { : scan length } ) Much simpler than having to code it the old way • i.e. a %SCAN and %REPLACE loop ✦ Or %Scan and %Subst or ... string1 = 'See &NAME. See &NAME run. Run &NAME run.'; string2 = %ScanRpl('NAME' : 'Forrest' : string1); // string2 now contains 'See Forrest. string3 = %ScanRpl(' See Forrest run. Run Forrest run.' ' : ' ' : string2); // Change double spaces to single // string3 now contains 'See Forrest. See Forrest run. Run Forrest run.' Notes Note that in the example shown, replacing double spaces with a single space will only work on sets of double spaces on the first pass through the string. In other words, if there were 4 spaces in a row, the new string would still have 2 spaces. If there were 3 spaces initially there would still be 2 spaces after the replacement. But %ScanRpl can be used with a null string as the replacement - does that help? Well it does but it creates the problem that if there were originally an even number of spaces then after the replacement there are none! Since we don't know exactly where the spaces originally were, we have no idea where to put the required space back in. Sometimes it seems you just can't win. But we have shown a possible solution on the next page - it looks odd - but it works. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 77 of 146 Scan and Replace - Example The code below will remove all instances of multiple spaces • And replace them with a single space DSPLY op-codes allow you to see it happening step by step d testString d result s s 40a 40a Inz('4 spc Varying 7 spc 10+ spc ') /free result = %ScanRpl( ' ': '<>': testString ); // replace each space with <> dsply ('Contents: ' + result ); result = %ScanRpl( '><': '': result ); // replace all >< with nothing dsply ('Contents: ' + result ); result = %ScanRpl( '<>': ' ': result ); // replace remaining <> with space dsply ('Contents: ' + result ); dsply ('Length of result is ' + %Char(%Len(result))); Notes This approach to the problem uses a rather strange looking sequence. It really does look "odd". For that reason I have included a number of displays so that you can see the change in the string as each phase progresses. I suspect there may be a better/shorter way of doing this but this one is fun anyway. If you decide to use it in a program PLEASE wrap it up in subprocedure or you will confuse the heck out of those who follow you! Below you can see the displays produced: DSPLY Contents: 4<>spc<><><><>5<>spc<><><><><>6<>spc<> DSPLY Contents: 4<>spc<>5<>spc<>6<>spc<> DSPLY Contents: 4 spc 5 spc 6 spc DSPLY Length of result is 18 © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 78 of 146 Processing Result Sets in RPG We could always write an RPG stored procedure to return a result set • But we could not receive/process that result set in an RPG program This new support may feel a bit "clunky" - But it works - You need to ... • Define a RESULT_SET_LOCATOR (defined on D spec) • Then ASSOCIATE the Result Set Locator with the Procedure • Then ALLOCATE the CURSOR for the result set D CustResultSet S SQLType(RESULT_SET_LOCATOR) Exec SQL Call CustomersByState( :InputState ); Exec SQL Associate Result Set Locator (:CustResultSet) with Procedure CustomersByState; Exec SQL Allocate C1 Cursor for Result Set :CustResultSet; Exec SQL Fetch next from C1 into :CustData; Notes If this syntax seems convoluted to you, we agree. It seems that it shouldn't need to be this complicated. But at least we can now receive and process result sets from a stored procedure in an RPG program. It was always incongruous that we could create the stored procedures that produced the result sets in RPG, but could not actually use those same stored procedures from an RPG program if we chose to. Note that once you associate the result set locator and allocate the result set to a cursor, you can then simply fetch from that cursor as if it were a “normal” SQL cursor. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 79 of 146 Relaxed Prototype Requirements Prior to 7.1, the compiler requires a Prototype when: • A subprocedure is compiled (even if called from a separate module) • A subprocedure is called (even if the subprocedure is in same module) In 7.1+, Prototypes are optional in some specific cases • If the program or procedure is not called by another RPG module ✦ i.e. If not called from RPG, then the prototype will never be used • Examples of optional prototypes include: A program that is only intended to be used as an exit program or as the commandprocessing program for a command ✦ A program that is only intended to be called from a different (non-RPG) language ✦ A procedure that is not exported from the module (not callable externally) ✦ A procedure that is exported from the module but only intended to be called from a different programming language ✦ • In other words, if the compiler can get the parameter list from a PI in the same source member, the PR is optional However, if you put the PR and the PI in the same source member, the compiler will validate the PR to ensure it is correct - Bonus! Notes The RPG compiler required prior to V7.1 that you have a PR in every source member where a PI for that procedure or program existed. The reason for this requirement was so that the compiler could validate that your PR was correct i.e., that it matched your PI. This is good because if the PR matches, then all calls to that program or procedure will be validated by the compiler and parameter mismatches will be a thing of the past. However, it was a bit painful that we had to put PRs in for cases where the PR would never be used by another RPGLE program - it was only called by CL or some other language that couldn’t use our prototype - likewise for internal subprocedures since the compiler could clearly get the parameter information directly from the PI, making the PR seem redundant. You should still include the prototypes in any source members where external subprocedures are coded so that the compiler can check them for you. Use /Copy members for that purpose so that you can be sure that the version that was validated when compiling the subprocedures is the same one used by all the callers of those routines. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 80 of 146 New RTNPARM Prototype Keyword Performance Boost for large return values • Converts the return value to a hidden parameter • Value reported by %PARMS( ) reflects the extra parameter ✦ ✦ But it will otherwise be "invisible" to RPG callers If calling from another language you will see the return value as the 1st parm • Will improve performance when returning large values ✦ Especially very large varying values Has a second unintended use • It allows subprocs with return values to be used by Java or stored procedure ✦ Prior to this change you were limited to a 4 byte integer (10i) D getFileData pr a Varying Len(1000000) D RtnParm D File a Const Varying Len(500) D Data S a Varying Len(1000) /Free Data = getFileData ('/home/mydir/myfile.txt'); Better Support for Optional Parameters In the past, checked %Parms for a hard-coded value • i.e., If %Parms > 1; Now use %ParmNum( ) • Replaces hard-coded value for parm position • %ParmNum returns the sequence of the parameter in the parm list In example below, %ParmNum(Optional2) returns 2 • i.e., Optional2 is the 2nd parameter declared in the PI D OptionalTest D Parm1 D Optional2 PI D Parm2 S 20A 5P 0 Options(*NoPass) 5P 0 Inz(99999) // Check for optional parameter and use value if present If %ParmNum(Optional2) <= %Parms; Parm2 = Optional2; EndIf; If Parm2 ............. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 81 of 146 V7 - TR7 Free Form Enhancements You can now specify an entire program in free form ! Only exception is that I and O specs are not available in free-form No longer any need for /Free or /End-Free ! Just leave columns 6 and 7 blank New free form options for: ! H-specs (CTL-OPT) ! F-specs (DCL-F) ! D-specs (DCL-xx) - Where xx = C, DS, PARM**, PI, PR, S, or SUBF** • ** These are rarely going to be used ! P-specs (DCL-PROC) ctl-opt option(*srcstmt) dftactgrp(*No); Not Technically a part of TR7 but was announced and released at the same time dcl-ds employeeDS; firstName char(16) Inz('James'); lastname char(30) Inz('Joyce'); salary packed(7:2) Inz(12500); end-ds; // Define printer file and associated DS dcl-f qprint printer(80); dcl-ds prtDs len(80) end-ds; Notes Completely free form logic brings RPG more in line with other modern programming languages, all of which use free format. This is important for attracting new developers coming into the marketplace. Traditional fixed format RPG is far less attractive and gives RPG the undeserved appearance of an old-fashioned language not up to modern application tasks. RPG is a powerful and flexible language that many young developers come to prefer over other more popular language options for business applications. But they must first be attracted to learn the language. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 82 of 146 Format of the new Declarations All of the new declaration op-codes follow this basic format: ! First the DCL-xx itself ! Next the name of the item - File, field, procedure, etc. ! Followed by keywords related to other fixed form elements - e.g. File usage, field type and length ! Then keywords from the old spec ctl-opt option(*srcstmt) dftactgrp(*No); We will be looking at a more complete code sample on the next chart dcl-ds employeeDS; firstName char(16) Inz('James'); lastname char(30) Inz('Joyce'); salary packed(7:2) Inz(12500); end-ds; dcl-f qprint printer(80); dcl-ds prtDs len(80) end-ds; .... From original D-spec keyword area Fixed column entries end-ds can be on the same line as dcl-ds in this case. Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 83 of 146 Simple Free Form Program File definitions can be intermixed with data definitions ! Named Constants, Data Structures, etc. Most new options have sensible defaults ! Disk files default to input, Printers to output, Decimals to zero, etc. etc. End of line comments are now useful in definitions! ctl-opt option(*srcstmt) dftactgrp(*No); dcl-ds employeeDS; // Nice to be able to have comments here! firstName char(16) Inz('James'); lastname char(30) Inz('Joyce'); salary packed(7:2) Inz(12500); end-ds; // Define printer file and associated DS dcl-f qprint printer(80); // This printer is program described dcl-ds prtDs len(80) end-ds; dsply ('Hello to our new employee'); dsply ( %TrimR(firstName) + ' ' + lastName ); Note that the Printer is defined together with the DS that it uses for output prtDs = 'The name of our new employee is ' + %TrimR(firstName) + ' ' + %TrimR(lastName) + ' his salary is $' + %Char(salary); write qprint prtds; Notes The idea of mixing file and data definitions will take some getting used to - but it makes sense. After all it makes far more sense to define a set of variables, data structures and constants together with the file that they will be used with that to arbitrarily separate them as we had to before. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 84 of 146 CTL-OPT - The New H-Spec Defaults to DftActGrp(*No) if any ILE specific options are used ! i.e. ACTGRP, BNDDIR, or STGMDL - This is the first of many "sensible" defaults that are added by this support Only other differences are: ! Can start in any column from 8 onwards ! Must be terminated with a semi-colon ! Presence of CTL-OPT stops compiler from looking for H spec data area No other differences between this and the current H-spec ! Can occupy multiple lines ! Multiple CTL-OPT can appear in program H BndDir('UTILBNDDIR') Option(*SRCSTMT: *NODEBUGIO) Ctl-Opt BndDir('UTILBNDDIR'); Ctl-Opt Option(*SRCSTMT: *NODEBUGIO); // Can also be coded as: Ctl-Opt BndDir('UTILBNDDIR') Option(*SRCSTMT: *NODEBUGIO); Notes Not that much has changed about the H spec since it was almost free format before. Ctl-Opt replaces the H and the semi-colon is used at the end. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 85 of 146 Declaring Files In Free-Form File Name listed first - followed by device type keyword (if any) ! Device type defaults to DISK - i.e., a Database table Externally described is the default ! File Keyword *EXT can optionally be specified as a parameter Program described files must specify their record length ! e.g. PRINTER(132) for a program described printer file Defaults for USAGE are based on device type - more in a moment ! *Input, *Output, *Update (implies *Input), *Delete (implies *Update) Add KEYED keyword for keyed database (disk) files FCUSTMR0 UF A E K DISK FREPORT O E PRINTER OFLIND(*IN96) FSCREEN CF E WORKSTN Dcl-F CUSTMR0 DISK USROPN Usage(*Update:*Delete:*Output) Keyed UsrOpn; Dcl-F REPORT PRINTER(*EXT) OFLIND(*IN96); Dcl-F SCREEN WORKSTN Usage(*Input:*Output); Notes In the example here, we have defined 3 files - one is a keyed database table (aka file), a report, and an interactive screen. This example does specify some features that are not required. For example, PRINTER(*EXT) defining an externally described printer did not need the *EXT parameter since externally defined is always the default. Also the USAGE(*Input:*Output) on the Screen file is the default usage value for a display file and could have been omitted. Note that Usage(*Output) is implied by default for the Report file. The OFLIND keyword on the Report file is “Overflow Indicator” which can be used by the program to determine when to go to a new page on the report. There are other keywords that can be used when declaring files. These are the most commonly used features of file declarations. The File name is no longer limited to 10 characters as it was on the F spec. However, since file object names (externally on the system) are limited to 10 characters, that means if a longer name is used, then keyword ExtDesc must be coded to give the real external name for the file. A longer, more meaningful name for the file may be useful for making the logic more readable. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 86 of 146 DCL-F - Helpful Defaults File name no longer limited to 10 characters ! So meaningful file names can be used - EXTDESC is used to specify actual name when different from file name Device type comes next followed by optional parameters ! Device type can be omitted if using an externally described Disk file - A sensible default All defaults are based on device type ! Usage(*Input) for DISK ! Usage(*Output) for PRINTER ! Usage(*Input : *Output) for WORKSTN DCL-F InvoiceMaster ExtDesc('INVMAST'); // Defaults to Input Disk DCL-F CustMaster // Keyed Disk file DCL-F qPrint Usage(*Update) Keyed; Printer(132) OflInd(PageFull); // Program described DCL-F MyDisplay WorkStn; // Workstation Usage(*Input : *Output) Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 87 of 146 DCL-xx - The New D-Spec xx = DS for Data structures ! In most cases there must also be a matching END-DS xx = SUBF for DS subfields - Very Rarely Required ! Code only if field name is a valid free-form op-code - Yes some strange people do use names like READ or SELECT as field names xx = S for Stand-Alone fields xx = C for Named Constants D Address D Street1 D City D State D Zip D ZipPlus DS 30A 30 2 5S 0 4S 0 Dim(20) Qualified dcl-ds Address Dim(20) Qualified; Street1 char(30); City char(30); State char(2); Zip zoned(5); // Zero decimals assumed ZipPlus zoned(4:0); end-ds Address; DS Name optional at end Must match DS name Notes END-DS may be omitted if DS is externally described or has no named subfields. It can even be on the same line as the DCL-DS in such cases, such as the following DS which is externally described based on a table (aka file) named PRODUCT. dcl-ds product Ext end-ds; Name of DS can be specified on end-ds and must match if present. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 88 of 146 DCL-xx - The New D-Spec - Data Types Data types are spelled out instead of using a one character code ! Chart below shows the most commonly used types ! Length or format of item follows in parentheses when required Simpler definition for some data types ! VarChar avoids the need for the Varying keyword ! Date, Time, Timestamp don't need separate DatFmt keyword ! 0 (zero) decimals assumed for all numeric data types if not specified. Data Type Free Keyword Notes A A+ Varying I U P S N D+ DatFmt B Char(len) Varchar(len) Int(len) Uns(len) Packed(len:dec) Zoned (len:dec) Ind Date(format) BinDec (len:dec) Decimals not specified Decimals not specified 0 assumed if decimals not given 0 assumed if decimals not given !! DO NOT USE !! The following is a partial list of RPG data types represented as D-specs: d packedNum s d zonedNum s 7p 2 7s 2 d integer s 10i 0 d unsigned s 10u 0 d float s 8f d character s 20a d varyingChar s 20a d dateMDY s d DatFmt(*MDY) d timeUSA s t TimFmt(*USA) d indicator s n d nastybinary s 9b 0 Varying And here are their free form equivalents. Dcl-S packedNum Packed(7:2); Dcl-S zonedNum Zoned(7:2); Dcl-S integer Int(10); Dcl-S unsigned Uns(10); Dcl-S float Float(8); Dcl-S character Char(20); Dcl-S varyingChar Varchar(20); Dcl-S dateMDY Date(*MDY); Dcl-S timeUSA Time(*USA); Dcl-S indicator Ind; Dcl-S nastybinary Bindec(9); © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 89 of 146 D-Spec DS Example This shows how a fixed form DS would be converted to free-form • I have used my own personal preferences for alignment ✦ At least they were my preferences at the time I coded this example! D MyDS D Address D Street D City D State D Zip D LstOrdDate D ASubfield Dcl-DS MyDs; Address Street City State Zip LstOrdDate ASubfield End-DS; DS 32A 15A 10A 2A 5A D 25A Overlay(Address) Overlay(Address: *Next) Overlay(Address: *Next) Overlay(Address: *Next) DatFmt(*USA) Varying Char(32); Char(15) Overlay(Address); Char(10) Overlay(Address:16); Char(2) Overlay(Address:26); Char(5) Overlay(Address:28); Date(*USA); Varchar(25); Notes I like the idea of aligning the field type definitions even though it is not required. I don't however insist on placing them in a specific column. Rather I start them 2 characters after the end of the longest field name in the block. For me it works but you'll devise your own style - just be consistent and remember that readability is critical. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 90 of 146 D-Spec - Unnamed Fields and Overlays In fixed form no field name was required • In free-form we must specifically inform the compiler ✦ Done by coding *N instead of a field name In free-form we are not permitted to OVERLAY the DS name • IBM decided that it should never have been allowed so they stopped it • So we must use POS(1) instead ✦ POS(nn) can also be used for positioning any field within a DS ✦ For example when specifying specific fields in the PSDS Dcl-DS DayData; *n Char(9) *n Char(9) *n Char(9) *n Char(9) *n Char(9) *n Char(9) *n Char(9) DayArray inz('Monday'); inz('Tuesday'); inz('Wednesday'); inz('Thursday'); inz('Friday'); inz('Saturday'); inz('Sunday'); Char(9) Dim(7) Pos(1); End-ds; Notes The RPG compiler team decided that too many people got confused when the OVERLAY keyword was used against the DS name. To help avoid this confusion, OVERLAY is now limited to subfields within the DS and you must use the POS keyword to reference the DS starting position. This is effectively the same as using a start position on the old D-specs. This is what the DS would have looked like in fixed-form. D DayData DS D 9 Inz('Monday') D 9 Inz('Tuesday') D 9 Inz('Wednesday') D 9 Inz('Thursday') D 9 Inz('Friday') D 9 Inz('Saturday') D 9 Inz('Sunday') D DayArray 9 Overlay(DayData) Dim(7) © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 91 of 146 More on Data Definition Constants can be used in many more places • Including field length, decimal places, array dimensions - just about anywhere you would use a literal Long names don’t require ellipsis … and a continuation line D DateMDY S D D array S 10a dim(10) 50A Varying D CustomerInfo DS D CustomerName D CustomerBalance... D DatFmt(*MDY-) 7P 2 dcl-s DateMDY Date(*MDY-); dcl-S array Char(10) Dim(10); dcl-c dcl-c Digits 7; Decimals 2; dcl-ds CustomerInfo; CustomerName CustomerBalance end-ds; VarChar(50); Packed( Digits: Decimals); Notes Constants can be used for length definition. Makes it really easy to change all currency fields from say 7,2 to 9,2 !!! Note that in the fixed format example here, it made no sense to define Digits and Decimals as constants since they could not be used to define the length and precision in fixed format D specs. However, in the new free format D specs, they can be used - as illustrated in the 2nd example. Note that not only are the ellipsis not required for longer variable names - they are not allowed in most cases. Unless the actual variable name is continued on the next line, ellipsis should not be used, even if the data type and other related keywords are on the next line. If ellipsis are found, the compiler assumes the next line will contain part of the variable name. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 92 of 146 Data Areas This is an area where you may trip up • D specs only allowed literals - constants were not allowed ✦ So quotation marks were not compulsory • Now you will probably get a compiler error if you forget the quotes ✦ It will be looking for a constant named JonsData • And of course you must specify the name all in upper case D myDataArea D lastRunDate D lastSeqNum D lastInvNum ds Dcl-Ds myDataArea d 5i 0 7p 0 DtaAra(JonsData); DtaAra(JonsData) DatFmt(*USA) // Wrong unless // JonsData is a constant Dcl-Ds myDataArea DtaAra('JONSDATA'); lastRunDate Date(*USA); lastSeqNum Int(5); lastInvNum Packed(7); End-Ds; Notes This is one of a number of similar areas where previously the compiler only allowed a literal - so even if quotes were not used the compiler just assumed them and was quite happy. This new support though allows for far more widespread use of constants and subsequently where literals are used they must _be_ literals - complete with quotes and the data in the right case. With great power comes ... © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 93 of 146 Subprocedures, Prototypes and More DCL-PROC declares a subprocedure • Completed by an END-PROC DCL-PI and DCL-PR define procedure interfaces and prototypes • Placeholder *N can be used if you don't want to type the name again • DCL-PARM is the optional equivalent of DCL-SUBF within PIs and PRs ✦ Only needed if name of parameter is the same as an RPG op-code New option for EXTPROC - *DCLCASE • Means the real name is exactly the same as the procedure or prototype name ✦ Avoids having to retype the name when using mixed case procedure names dcl-proc DayOfWeek Export; dcl-pi *N Int(3) ExtProc(*DclCase); InputDate Date(*USA) Value; end-pi; // Omit name - use *N placeholder dcl-s DayNumber int(3); // Do calcs leaving value in DayNumber Return DayNumber; end-proc DayOfWeek; Notes The biggest advantage of the new support is that you no longer have to flip in and out of fixed and free modes when coding subprocedures. No more /End-Free, P-specs, D-Specs, /Free, logic, /End-Free etc. It makes it all look much cleaner and removes a major source of mistakes (and frustration) for those learning RPG. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 94 of 146 Subprocedures, Prototypes and More EXTPGM keyword can be omitted • Providing that the program is non-ILE i.e. DFTACTGRP(*YES) Program name can be omitted from EXTPGM • If the program name is the same as the prototype EXTPGM('PROGNAME') • Parameter only needed when proto name is different from actual program name dcl-pr MyProgram; // Used to call 'MYPROGRAM' from non-ILE program dcl-pr MyProgram ExtPgm; // Calls 'MYPROGRAM' from any program dcl-pr DifferentName Extpgm('MYPROGRAM'); // Call 'MYPROGRAM using the // name DifferentName Notes Once again the new support adds some sensible and useful defaults. The more I use this stuff the more I love it! © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 95 of 146 Converting to Fully Free Form IBM does NOT supply a tool to do this ! Even RDi's free-form converter has not been updated Currently we know of two tools that are available ! Linoma's RPG Toolbox - Which also does a great job of converting old-style fixed-form to free form • Details at linomasoftware.com/products/rpgtoolbox/ - Quick demonstration in a moment ! Arcad Transformer - A far more all-embracing modernization tool - but more expensive • Details at arcadsoftware.com/products/arcad-transformer-ibm-i-refactoring-tools/ - It can even refactor code to the extent of getting rid of GOTOs and other similar ancient constructs. Notes We have been using Linoma's conversion tool since way back in the V3 days when RPG IV was first introduced. Over the years they have steadily added more and more functionality including the ability to apply "aggressive" conversion options that deal with converting various flavours of MOVE operations. With the latest updates they also introduced a low-cost add on to the toolbox that works as a plug-in to RDi and does a great job of converting whole prong ams or just selected pieces. Another nice feature, not related to free-form, is an indenting option that re-establishes correct indentation after you have changed the structure of an If, For, Dox, etc. block. Arcad's tooling is more recent but much more all encompassing. I like what I have seen of it but have not had any long-term experience with the tool. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 96 of 146 Linoma's RPG ToolBox - Side By Side Comparison Linoma's tool does a great job but ... ! It tends to play it safe and includes a lot of extra keywords ! I'm also not a fan of the way it aligns things - it doesn't! - But they are looking at changing this and providing more formatting options After Before Notes My biggest grouse with the Linoma tool is that it converts everything and that means it also adds a bunch of file declaration keywords that are not necessary. The first thing I do after running a conversion with the tool is to manually clean it up. Their conversion of D-specs does not apply any alignment to the resulting code - just inserts a single blank between the components. For me the result looks messy so again I apply some manual clean up after conversion. An example on the next chart. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 97 of 146 Edited Version of Converted Code I removed the superfluous file entries and applied my own alignment style to the data declarations. Notes Here you can see that I have removed the superfluous keywords from the file declaration. I have also added some alignment to the data declarations. Right now my basic alignment rule is that in any given group (i.e. DS, PR, PI, series of standalone fields, etc.) I insert two spaces between the longest data name in the group and its data type/length definition and then align all other entries in the group to that. You can see the effect in the chart. Note that I don't try and make all the entries in all the groups align - that is just too much work and I don't find it necessary - but visually I do like the entries in a specific group to align. Choose your own style - but try to make sure that everyone in the shop uses the same (or very similar) style. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 98 of 146 Time to Update Your Editor This is what free-form coding looks like in SEU ! IBM will NOT be enhancing SEU to handle these new definitions Time you moved up to a real editor - RDi Notes IBM has confirmed that SEU will not be updated to support this new functionality. In fact it was frozen as at V6 and supports none of the V7 functionality. As a result a great many people have started doing what they should have done many years ago and moved their development to RDi. It fully supports the new free-form including excellent code-assist to help you remember the new formats. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 99 of 146 "Gotchas" to Watch Out For OVERLAY keyword cannot be used against DS name ! Use POS(n) instead Names in EXTNAME, EXTFLD, and DTAARA ! Must be in quotes and are case sensitive ! Without quotes, they are treated as a variable or constant name Ellipsis (...) for continuation only allowed when continuing a name ! But not really needed anymore anyway On F-Spec "U" enables update and delete ! In free form *DELETE must be requested explicitly End-DS, End-PR, End-PI are always required ! But may appear on same line as DCL-xx in some cases RDi's "Convert all to Free-Form" means only convert "all logic" ! And will still generate /Free and /End-Free I and O specs remain in fixed form ! Probably forever Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 100 of 146 The High Points Intelligent defaults ! For device type (Defaults to DISK) ! Usage (Default based on device type) ! Decimal places (Defaults to zero) Constants can be used as keyword values ! Including for lengths and decimal places DFTACTGRP(*NO) is optional ! If any other ILE keyword such as ACTGRP or BNDDIR is used File and data declarations can be mixed ! Even in fixed form /Free and /End-Free no longer required /Copy and other compiler directives no longer need to start in col 7 // style end of line comments can be used in data/file declarations Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 101 of 146 Resources Linoma Software's RPG Toolbox • The latest evolution of the first RPG III conversion utility ✦ Accommodates Free-form formatting and much more • www.LinomaSoftware.com and follow the RPG Toolbox links Articles by Jon Paris and Susan Gantner • Search for them from the IBM Systems Magazine site ✦ www.ibmsystemsmag.com/authors/Susan-Gantner/ ✦ www.ibmsystemsmag.com/authors/Jon-Paris/ Free-Format RPG IV: Third Edition ✦ ✦ ✦ by Jim Martin Published by MCPress (www.MC-store.com) Make sure to order the third edition - older versions do NOT contain the V7 free-form additions Notes As noted on the chart - if buying Jim Martin's book make sure you get the third edition or later. The earlier versions do not include the V7 enhancements that we have been discussing in this session. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 102 of 146 Advanced Data Structures Now with new Free Form Definitions!! Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Agenda What do I mean by "Advanced" ? DS stuff that you might not know No-length fields No-name fields Incorporating external fields into a DS Group fields Mapping Indicators to Names Data Structures V5R2 - LikeDS and LikeRec Templates Subfile Sort Example Using many of these capabilities © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 103 of 146 Unnamed Fields and Overlaying a DS DS subfields do not need to be named • But they can still have INZ values! Makes a great alternative to using compile-time data • Initialize the data near the array definition itself • No need to chase to the end of the source member • The fields can have names if you wish but they do not have to Our passionate dislike of compile-time data led us to this technique • It makes life much simpler when you want to see the values used to intialize an array. D Messages D D D D D DS Msg 20a 20a 20a 20a Inz('Invalid Item Code') Inz('Too many selections') Inz('Item Code required') Inz('Huh?') 20a Overlay(Messages) Dim(4) Unnamed Fields and Overlaying a DS DS subfields do not need to be named! • You can use the placeholder *N instead • And they can still have INZ values! In Fixed form the Overlay keyword can apply to the DS • In free-form you must use the POS keyword to position the redefinition Makes a great alternative to using compile-time data • Initialize the data near the array definition itself • No need to chase to the end of the source member dcl-ds Messages; *n Char(20) *n Char(20) *n Char(20) *n Char(20) Msg end-ds; © Partner400, 2015 Free Form Version Inz('Invalid Item Code'); Inz('Too many selections'); Inz('Item Code required'); Inz('Huh?'); Char(20) Dim(4) Pos(1); Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 104 of 146 No-Length Subfields Want to access the fields in a record as an array? The "secret" is to place them in a DS This technique works even if the fields are not contiguous in the record No length definition is required The compiler will use the length supplied by the database Next overlay the array against the DS name Redefining the fields as an array Notice the use of the LIKE keyword D SalesData D Q1 D Q2 D Q3 D Q4 D SalesForQtr D DS R MTHSALESR CUSTOMER STREET CITY STATE DIVISION Q1 Q2 Q3 Q4 K CUSTNO 4 32 24 2 2 7 7 7 7 DDS 2 2 2 2 Overlay(SalesData) Like(Q1) Dim(4) Notes The inspiration for this example comes from one of the most commonly asked questions on RPG programming lists: “How do I directly load fields from a database record into an array?” The question normally arises when each database record contains a series of related values - for example, monthly sales figures. The DDS for the physical file is shown. One solution is depicted here. We'll look at a different solution on the next chart. Our objective is to access the individual fields Q1-Q4 as an array of four elements after reading a record from the file. Notice that we’ve incorporated the Quarterly sales fields into the DS by specifying their names. No length or type definition is required. Instead of allocating storage for the file’s fields arbitrarily, the compiler is told to explicitly place the data in the DS. Because the DS no longer contains the Customer and Division data, we can use the simple form of the Overlay keyword. Otherwise, if we had used an externally described DS, we would have needed to place a starting position on the Overlay keyword. The code for that alternative solution is shown below. Unlike the externally described DS, The example on this chart allows you to see exactly which fields form the DS. In addition, the fields in the DS can come from multiple files. D SalesData E DS ExtName(TestFile) // Define array over Quarterly Sales figures D SalesForQtr Overlay(SalesData: 7) D Like(Q1) Dim(4) © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 105 of 146 No-Length Subfields Want to access the fields in a record as an array? The "secret" is to place them in a DS This technique works even if the fields are not contiguous in the record No length definition is required The compiler will use the length supplied by the database Use the POS keyword to cause the array to overlay the DS Notice the use of the LIKE keyword dcl-f MthSales; dcl-ds SalesData; Q1; Q2; Q3; Q4; SalesForQtr end-ds; Free Form Version Like(Q1) Pos(1) R MTHSALESR CUSTOMER STREET CITY STATE DIVISION Q1 Q2 Q3 Q4 K CUSTNO 4 32 24 2 2 7 7 7 7 DDS 2 2 2 2 Dim(4); Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 106 of 146 Group Fields Sometimes it is convenient to reference a group of fields You might wish to group Street, City and State under the name Address. That way they can be manipulated as a single entity This version of the previous example uses this approach The compiler derives the length of the group field from the combined lengths of the subfields that OVERLAY it D SalesData D Customer D Address D Street D City D State D Division D QuarterData D Q1 D Q2 D Q3 D Q4 D DS Overlay(Address) Overlay(Address: *Next) Overlay(Address: *Next) Overlay(QuarterData) Overlay(QuarterData: *Next) Overlay(QuarterData: *Next) Overlay(QuarterData: *Next) SalesForQtr Overlay(QuarterData) Like(Q1) Dim(4) Notes Note that neither Address or QuarterData have a length, type definition or LIKE keyword. Nor do they exist in any of the program’s files. Normally you’d expect such definitions to result in Field Not Defined errors, but it doesn’t because the subsequent OVERLAY references inform the compiler that these fields represent a group field. If you look in detail at QuaterData, you will see that it comprises the fields Q1 though Q4. If you examine the extract from the compiler cross-reference listing below, you’ll see that QuarterData is defined as 28 characters long (i.e., the combined lengths of Q1, Q2, Q3 and Q4): Q1 Q2 Q3 Q4 QUARTERDATA SALESDATA SALESFORQTR(4) © Partner400, 2015 S(7,2) S(7,2) S(7,2) S(7,2) A(28) DS(34) S(7,2) Gateway/400 Seminar - December 10th, 2015 14D 15D 16D 17D 13D 10D 19D . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lecture Notes: Page 107 of 146 Group Fields Sometimes it can be convenient to reference a group of fields • e.g. To group Street, City and State under the name Address. ✦ That way they can be manipulated as a single entity The compiler derives the length of the group field • It is the sum of all fields that OVERLAY it Free Form Version dcl-f MthSales; dcl-ds SalesData; Customer; Address; Street City State Division; QuarterData; Q1 Q2 Q3 Q4 SalesForQtr end-ds; Overlay(Address); Overlay(Address: *Next); Overlay(Address: *Next); Overlay(QuarterData); Overlay(QuarterData: *Next); Overlay(QuarterData: *Next); Overlay(QuarterData: *Next); Overlay(QuarterData) Like(Q1) Dim(4); Notes © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 108 of 146 Using SORTA with Group Fields Want to sort an array on different keys? Group fields can provide an answer ProductData is a group field It comprises the fields Name, UnitPrice and QtyInStock Notice that the DIM is specified at the group level This allows the array to be sorted on any of the subfields The associated data will "follow along" and stay in sync D ProductInfo DS // Note that Dim is specified at the group field level D ProductData Dim(1000) D Name 20 Overlay(ProductData) D UnitPrice 7p 2 Overlay(ProductData: *Next) D QtyInStock 7p 2 Overlay(ProductData: *Next) /Free SortA Name; // Sort into Name sequence SortA UnitPrice; // Sort in Unit Price sequence Notes Note that when using this technique all of the other fields in the array (i.e. those that are part of the group) will be "pulled along" with their associated values. ASCEND or DESCEND can be specified as normal along with the DIM keyword. So, while you can sort on any of the fields in the group, you can only sort ascending OR descending sequence on any given array. In order to allow alternate sequencing you could use a pointer to base a second version of the array as shown in the example below: D ProductInfo D ProductData D Name D UnitPrice D QtyInStock DS Dim(1000) Acsend 8 Overlay(ProductData) 8 Overlay(ProductData: *Next) 9p 0 Overlay(ProductData: *Next) // Use a Based version of the DS to allow the alternate sorting seq. D AlternateView D ProductDataD D NameD D UnitPriceD D QtyInStock DS D pProductInfo S © Partner400, 2015 Based(pProductInfo) Dim(1000) Descend 8 Overlay(ProductDataD) 8 Overlay(ProductDataD: *Next) 9p 0 Overlay(ProductDataD: *Next) * Inz(%Addr(ProductInfo)) Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 109 of 146 Using SORTA with Group Fields Want to sort an array on different keys? • Group fields can provide an answer ProductData is a group field • It comprises the fields Name, UnitPrice and QtyInStock Notice that the DIM is specified at the group level • This allows the array to be sorted on any of the subfields • The associated data will "follow along" and stay in sync Free Form Version dcl-ds ProductInfo; // Note that Dim is specified at the group field level ProductData Dim(1000); Name char(20) Overlay(ProductData); UnitPrice packed(7:2) Overlay(ProductData: *Next); QtyInStock packed(7:2) Overlay(ProductData: *Next); end-ds; SortA Name; // Sort into Name sequence SortA UnitPrice; // Sort in Unit Price sequence Notes Note that when using this technique all of the other fields in the array (i.e. those that are part of the group) will be "pulled along" with their associated values. Prior to V7 if you needed to sort the array with SORTA you could only specify ASCEND or DESCEND on the actual array definition to control the sequence. In V7 IBM added the ability to specify the required sort sequence as an operation extender to SORTA. So SORTA(A) will sort the array in ascending sequence and SORTA(D) in descending order. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 110 of 146 Mapping The *INnn Indicators to Names You can use a pointer to map the standard indicator array • An easy way to give names to all of your indicators The technique shown associates names with the *INnn indicators • Adding _nn to the name allows you to document the actual indicator number that is associated with the name • Thanks to Aaron Bartell for introducing us to this idea D DspInd DS // Response indicators D Exit_03 D Return_12 D D D // Conditioning indicators Error_31 StDtErr_32 EndDtErr_33 D pIndicators S Based(pIndicators) N N Overlay(DspInd: 3) Overlay(DspInd: 12) N N N Overlay(DspInd: 31) Overlay(DspInd: 32) Overlay(DspInd: 33) * Inz(%Addr(*In)) Notes Unlike the INDDS approach, these named indicators DO directly affect the content of their corresponding *IN indicator. If we EVAL Error = *On, then indicator *IN30 was just turned on. This often makes this a better approach for those who use program described (i.e. O-spec) based files rather than externally described printer files. Those of you who use the *INKx series of indicators to identify function key usage need not feel left out. A similar technique can be used. IN this case the pointer is set to the address of *INKA. The other 23 function key indicators are in 23 bytes that follow. IBM have confirmed many times that for RPG IV this will always be the case. D FunctionKeys DS Based(pKxIndicators) D thisIsKC N Overlay(FunctionKeys: 3) D thisISKL N Overlay(FunctionKeys: 12) * Inz(%Addr(*InKA)) D pKxIndicators © Partner400, 2015 S Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 111 of 146 Mapping The *INnn Indicators to Names You can use a pointer to map the standard indicator array • An easy way to give names to all of your indicators The technique shown associates names with the *INnn indicators • Adding _nn to the name allows you to document the actual indicator number that is associated with the name • Thanks to Aaron Bartell for introducing us to this idea Free Form Version dcl-ds DspInd Based(pIndicators); // Response indicators Exit_03 Ind Pos(3); Return_12 Ind Pos(12); // Conditioning indicators Error_31 Ind Pos(31); StDateError_32 Ind Pos(32); EndDateError_33 Ind Pos(33); end-ds; dcl-s pIndicators The use of POS makes it more readable than OVERLAY Pointer Inz(%Addr(*In)); Notes Unlike the INDDS approach, these named indicators DO directly affect the content of their corresponding *IN indicator. If we EVAL Error = *On, then indicator *IN30 was just turned on. This often makes this a better approach for those who use program described (i.e. O-spec) based files rather than externally described printer files. Those of you who use the *INKx series of indicators to identify function key usage need not feel left out. A similar technique can be used. In this case the pointer is set to the address of *INKA. The other 23 function key indicators are in 23 bytes that follow. IBM have confirmed many times that for RPG IV this will always be the case. dcl-ds FunctionKeys Based(pFunctionKeys); F3_Pressed Ind Pos(3); F12_Pressed Ind Pos(12); end-ds; dcl-s pFunctionKeys © Partner400, 2015 Pointer Inz(%Addr(*InKA)); Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 112 of 146 Data Defintion Update V5R2 and Beyond Data Definition Features These are essential to processing XML with RPG's built-in XML-INTO support Not all charts in this section include a Free Form Version (But hopefully you'll have got the idea by now) Notes V5R2 saw the biggest change in data definition capabilities in the history of RPG. Many new language features are based on these capabilities. For example, both XML processing and RPG Open Access rely on them. You NEED to understand them. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 113 of 146 Qualified Data Names & LIKEDS DS Keyword LIKEDS • The new DS inherits all the fields of the named DS • This includes all field names and their size and data type attributes ✦ But NOT any DIM or OCCURS specifications on the original DS definition • Use of LIKEDS implicitly adds the QUALIFIED keyword to the new DS • Use INZ option *LIKEDS to clone initialization values Avoids the need to use suffixes or prefixes • You can now have multiple versions of the same field D baseYear S 4s 0 D date D year D month D day DS D orderDate DS 4s 0 Inz(2003) 2s 0 Inz(01) 5s 0 Inz(01) LikeDS(date) Inz(*LikeDS) If year > baseYear; ... If orderDate.year > baseYear; ... Notes Previously in RPG one field name = one storage location. As a result if a field name appeared in two different files then reading either file would change the field's content. This often necessitated using prefixes or suffixes on the base name to differentiate between the versions. You might have ARACCNO and CMACCNO for example, both of which are actually the account number (ACCNO). Now that we can use qualified data names in RPG the need for this diminishes significantly. It will take time to adjust the way we deal with files, but in our programs we can now have multiple versions of a field name differentiated by the DS name. The new keyword LIKEDS causes all fields and their definitions in the original DS to be cloned in the new DS. By definition the new DS is automatically qualified. In addition there is also a new parameter value allowed on the INZ keyword for data structures defined with the LIKEDS keyword: INZ(*LIKEDS). This means that any initial values specified in the original data structure should be replicated in the new (LIKEDS) data structure as well. For example, if in our code sample on this chart had an initial value for the Year subfield in Date, such as INZ(2001), that initial value would ONLY be carried over to the OrderDate.Year field IF we added the keyword INZ(*LIKEDS) to the OrderDate D spec. There is a particularly good discussion and example of this support as it relates to passing a Data Structure as a prototyped parameter in the article by Hans Boldt and Barbara Morris in IBM's iSeries Magazine, May 2001 issue. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 114 of 146 Qualified Data Names & LIKEDS - more Free-form works in basically the same way as fixed • Note that the Dim(10) on the original DS is not cloned • In this example the parent DS was qualified ✦ It can help avoid confusion if all instances of a field name are qualified • Note the positioning of the array indices ✦ ✦ They belongs with the name of the item that had the Dim statement More on DS arrays later dcl-ds date year month day end-ds; Dim(10) Qualified; zoned(4) Inz(2003); zoned(2) Inz(01); zoned(5) Inz(01); The DIM here will NOT be cloned - only the subfield definitions dcl-ds orderDate Dim(99) LikeDS(date) Inz(*LikeDS); if date.year > baseYear; ... if orderDate(1).month = orderDate(2).month; ... Notes The free form implementation is basically the same - just prettier! In this chart we have also introduced the concept of Data Structure arrays. These are a major improvement on the old Multiple Occurrence Data Structures (MODS) which are still supported for compatibility reasons. We will be going into more detail on DS arrays in a few minutes. Notice that even though the original array had a DIM statement, this is NOT cloned. The new DS must specify its own DIM if it is to be an array. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 115 of 146 Nested DS The LIKEDS keyword is used to specify the DS to be nested • Any DS that is to contain a nested DS(s) must specify QUALIFIED To reference the fields requires double qualification • e.g. InvoiceInfo.MailAddr.City or InvoiceInfo.ShipAddr.State But wait - there's more !! • Data Structure arrays !!!! D Address D Street1 D Street2 D City D State D Zip D ZipPlus DS D InvoiceInfo D MailAddr D ShipAddr DS 30a 30a 20a 2a 5s 0 4s 0 LikeDS(Address) LikeDS(Address) QUALIFIED Notes Note that when the LIKEDS keyword is used to reference a DS array, the DIM characteristic is not copied by the LIKEDS. Only the individual components of the DS (fields, conventional arrays and nested DSs) are copied. Look at this example: D DSArray DS D Data D NewDS Dim(20) Qualified 12a DS D NewDSArray Qualified LikeDS(DSArray) Even though DSArray is itself an array, when we reference it via the LIKEDS keyword in NewDS, the DIM characteristic is not inherited. In order to establish NewDSArray as a array DS, we explicitly code the DIM keyword, as shown below. D NewDS D NewDSArray © Partner400, 2015 DS Qualified LikeDS(DSArray) Dim(20) Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 116 of 146 Data Structure Arrays The DIM keyword can now also be specified at the DS level • More useful than MODS since all levels are accessible at once • Keyword QUALIFIED is also required ✦ But we're not quite sure why Subscripting works in the same way as for individual fields • Address(5) is the whole of the fifth element of the Address DS array • Address(5).City is the City field within that fifth element D Address D Street1 D Street2 D City D State D Zip D ZipPlus DS DIM(20) QUALIFIED 30a 30a 20a 2a 5a 4a /Free If Address(5).City = 'Rochester'; Address(5).State = 'MN'; EndIf; Notes In V6 a new keyword was introduced - TEMPLATE - to save on storage when we simply want to define our own data types - i.e. DS and fields that are simply going to be used as templates for other DS and fields via LIKEDS and LIKE. We will study that in a moment or two © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 117 of 146 Multidimensional Arrays If your DS contains an array ..... • You now have the effect of a multidimensional array The names are subscripted just as you'd expect them to be • So - Address(5).Street(1) refers to the first element in the Street array within the fifth element of the Address DS array D Address D Street D City D State D Zip D ZipPlus DS 30a 20a 2a 5a 4a DIM(20) QUALIFIED DIM(2) /Free Address(5).Street(1) = *Blanks; : : : If Address(5).Street(2) = *Blanks; : : : Notes Although we can effectively create multidimensional arrays using this technique, things like SORTA and %LOOKUP are limited in what they can do for us. This situation was not resolved until V7 © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 118 of 146 Another Example The DS array SalesByYear has ten elements • Each of which contains a 12 element array and a total field • Both sets of logic will sum up the elements in each Sales4Month array and place the total in the corresponding Total4Year field D SalesByYear D Sales4Month D Total4Year DS Dim(10) QUALIFIED 7p 2 Dim(12) 9p 2 Inz /Free For Y = 1 to %Elem(SalesByYear); SalesByYear(Y).Total4Year = %XFoot(SalesByYear(Y).Sales4Month); EndFor; OR For Y = 1 to %Elem(SalesByYear); For M = 1 to %Elem(SalesByYear.Sales4Month) SalesByYear(Y).Total4Year = SalesByYear(Y).Total4Year + SalesByYear(Y).Sales4Month(M) EndFor EndFor Notes I have included an example of manually looping through the inner array to build the totals because sometimes %XFoot will not do the job. Suppose that you want to create year-to-date values in the array, or quarterly totals. For that you would have to loop thorough manually as shown in the second example. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 119 of 146 Entering the Fourth Dimension You can create as many dimensions as you like but ... • • • • Until V6 a single element at the top level DS could not exceed 64K In the example below changing Departments to DIM(11) broke the limit See the notes page for the size calculations Increasing Divisions to (say) DIM(99) has no impact ✦ Other than the 16Mb storage limit for data items D Divisions D DivCode D Departments DS D DeptData D DeptCode D Products DS D ProductData D ProdCode D MonthsSales DS QUALIFIED Dim(5) 2s 0 LikeDS(DeptData) Dim(10) QUALIFIED 3s 0 LikeDS(ProductData) Dim(99) QUALIFIED 5s 0 9p 2 Dim(12) /free Divisions(1).Departments(1).Products(1).MonthsSales(1) = 0; Notes - DS Size issues prior to V6 To see why the simple change of increasing the dimension of the Department DS array to 11 causes the storage requirement to exceed the V5R4 size limit study the size of the nested DSs. D ProductData D D DS ProdCode MonthsSales QUALIFIED 5s 0 9p 2 Dim(12) ProductData is made up of the 5 byte Department code and a 12 element array. Each element is 5 Bytes long so the size of ProductData is: 5 + (12 x 5) = 65 bytes. D DeptData D DeptCode D Product DS QUALIFIED 3s 0 LikeDS(ProductData) Dim(99) DeptData consists of the 3 byte Department Code and 99 instances of the ProductData DS Array. So the size is: 3 + (99 X 65) = 6,438 bytes. D Divisions D DivCode D Department DS QUALIFIED Dim(5) 2s 0 LikeDS(DeptData) Dim(10) Division consists of the 2 byte Division Code and 10 instances of the DeptData DS array. So its size is: 2 + (10 X 6,438) = 64,382. This is under the V5R4 limit of 65,535, but not by much. Simply increasing the DIM on the Department DS array to 11 raises the requirement to 2 + (11 X 6,438) = 70,820 and this exceeds the pre-V6 compiler limit. Note: We will see later how the TEMPLATE keyword can be used in cases such as this to avoid wasting storage on the DeptData and ProductData structures. You will see an example on the next chart. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 120 of 146 Entering the Fourth Dimension You can create as many dimensions as you like but ... • Until V6 a single element at the top level DS could not exceed 64K ✦ ✦ ✦ In the example below changing Departments to DIM(11) broke the limit See the notes page for the size calculations Increasing Divisions to (say) DIM(99) has no impact - Other than the 16Mb storage limit for data items dcl-ds Divisions DivCode Departments end-ds; Qualified Dim(5); Zoned(2); LikeDS(DeptData_T) Dim(10); dcl-ds DeptData_T DeptCode Products end-ds; Template Qualified; Zoned(3); LikeDS(ProductData_T) Dim(99); dcl-ds ProductData_T ProdCode MonthsSales end-ds; Template; Zoned(5); Packed(9:2) Dim(12); Free Form Version More on the TEMPLATE keyword in a minute Divisions(1).Departments(1).Products(1).MonthsSales = 0; Notes - DS Size issues prior to V6 To see why the simple change of increasing the dimension of the Department DS array to 11 causes the storage requirement to exceed the V5R4 size limit study the size of the nested DSs. dcl-ds ProductData_T Template; ProdCode Zoned(5); MonthsSales Packed(9:2) Dim(12); end-ds; ProductData is made up of the 5 byte Department code and a 12 element array. Each element is 5 Bytes long so the size of ProductData is: 5 + (12 x 5) = 65 bytes. dcl-ds DeptData_T DeptCode Products Template Qualified; Zoned(3); LikeDS(ProductData_T) Dim(99); end-ds; DeptData consists of the 3 byte Department Code and 99 instances of the Products DS Array. So the size is: 3 + (99 X 65) = 6,438 bytes. dcl-ds Divisions DivCode Departments Qualified Dim(5); Zoned(2); LikeDS(DeptData_T) Dim(10); end-ds; Each element in Divisions consists of the 2 byte Division Code and 10 instances of the Departments DS array. So its size is: 2 + (10 X 6,438) = 64,382. This is under the V5R4 limit of 65,535, but not by much. Simply increasing the DIM on the Department DS array to 11 raises the requirement to 2 + (11 X 6,438) = 70,820 and this exceeds the pre-V6 compiler limit. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 121 of 146 Creating "Data Types" - V6's TEMPLATE This code creates a new "Address" data type • But no storage is allocated to it - it is just a "note" to the compiler MailAddr & ShipAddr are defined using this data type • They will contain the same field definitions as Address • Because the LIKEDS keyword is used they are implicitly QUALIFIED Place standard definitions like this in a /COPY member • Then you can include ll of your standard definitions with one simple line 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ /COPY StdTypes dcl-ds Address_T Template; Street1 char(30); Street2 char(30); City char(30); State char(2); Zip zoned(5); ZipPlus zoned(4); end-ds; dcl-ds MailAddr dcl-ds ShipAddr LikeDs(Address_T); LikeDs(Address_T); Notes Most modern programming languages include the ability to effectively create your own data types. V5R1 RPG gave us the QUALIFIED and LIKEDS keywords. That was a first step along the path as they enabled us to define one DS as looking exactly like another. V5R2 gave us the ability to use LIKEDS within a DS - allowing us to nest data structures one within another. In both cases however, the definitions to use memory and the only option which avoided this (using the BASED keyword on the definition) precluded the use of initial values and could lead to errors if the programmer mistakenly referenced one of the fields in the DS. If you are unfamiliar with this technique you'll find an example on a later notes page. With the new TEMPLATE keyword, RPG is all "growed up" and can now define templates for use by LIKE and LIKEDS that do not use memory and can be given initial values. In fact you can even define files with the TEMPLATE keyword - but more on that later. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 122 of 146 "Data Types" - More ... Illegal usage of templates is detected by compiler • e.g. If you try to store anything in them Initial values can be specified and can be cloned via Inz(*LIKEDS) TEMPLATE can also be used with Stand-alone fields and files • Templates can be referenced by %SIZE, %LEN, %ELEM and %DECPOS 2+ 3+ 4+ 5+ 6+ 7+ 8+ 9+ /COPY StdTypes dcl-ds Address_T Template; Street1 char(30); Street2 char(30); City char(30); State char(2); Zip zoned(5); ZipPlus zoned(4); end-ds; dcl-ds MailAddr dcl-ds ShipAddr Free Form Version LikeDs(Address_T); LikeDs(Address_T); If MailAddr.City = 'Rochester'; MailAddr.State = 'MN'; EndIf; Notes You may see pre-V6 examples where templates are defined using the Based keyword. The only reason this was done was to avoid wasting static memory on structures that would never be used to hold data - only to provide a definition to be "cloned". With V6's TEMPLATE keyword we now have an engineered solution to this problem so use it! DOn't use the old method. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 123 of 146 Sorting Subfiles Want to sort subfiles? Here's an example using many new features The LIKEREC keyword DS Arrays I/O Operations using the result field Naming Indicators Notes Requests relating to how to sort subfiles are one of those most commonly posed on Internet lists. This simple demonstration program highlights a very simple approach that can easily be adapted to almost any sorting requirement. The "secret" lies in loading the entire subfile into memory at the start of the process. Sorting it and then redisplaying becomes not only a trivial task but also one that performs very well. You may need to get to release 6.1 before you can define a subfile with 9,999 records in it - but you can adapt the process to use a user space and simulate the array aspects. If you use a page-at-a-time approach to loading your subfile, you will also have to adapt the logic so that the subfile is fully loaded before you attempt to display it after a sort request - but this is not hard to do. As you will see we are using many of the techniques we have described in this session in the program. Note that a full source listing (including the file definitions) is included at the end of this handout. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 124 of 146 Subfile Sort - Basic Flow 1. Load all records into the subfile array 2. Load the subfile from the array 3. Display the subfile 4. If sort requested determine which sequencing routine to use 5. Call the sort (qsort in this case) 6. Clear the subfile 7. Loop back to 2 until the user tells us we are finished (1) Count = LoadArray(); (2) (3) (4) (5) (6) (7) DoU (ExitRequest) LoadSubfile(Count); DisplaySubfile(); Select ... Sort ... ClearSubfile(); EndDo; // Store count of records loaded // Copy data from array into subfile // Determine sort sequence // Clear out subfile ready for reload Notes A common user request is to sort subfile data on the screen. Using this sorting technique, this can be fairly simply accomplished. The data is first loaded into the array, then the subfile is loaded from the array. If/when the user requests a different sequence to the data in the subfile, don't retrieve it again from the file. Instead simply re-sequence the array data using the simple SORTA technique and reload the subfile from the array. The biggest advantage to this approach is that additional sort options can be added to a program in a matter of minutes. Add the indicator to the display file, write the sort routine, add the extra two lines to the SELECT clause and you are pretty much done. This method is of course only good for data that does not need to be up to the minute. Since the data is in the array any changes being made to the data in the database will not be reflected. In our experience though, the user finds it much less confusing if the data is the same after a sort as they saw previously - it confuses them if they sort something into a different sequence and then can't find the record they were looking at before! If it is vital for the data to always be current, then using embedded SQL with an appropriate ORDER BY clause is a better way to go. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 125 of 146 1. The LoadArray Routine The Read loads data directly into the ProductRec DS • ProductRec itself is defined using LikeRec(PRODUCTR) Eval-Corr then populates the subfile array element • Which itself was defined with LikeRec(ProdSfl: *Output) This example uses a single file and no calculations • But as you can imagine any logic you like can be added to the load routine Dcl-Proc LoadArray; Dcl-PI *N Int(10); Dcl-DS ProductRec LikeRec(PRODUCTR) Dcl-S n Int(10); // Read all records and load array of subfile records DoU %EOF(Product); Read ProductR ProductRec; If Not %Eof(Product); n +=1; Eval-Corr SubfileRec(n) = ProductRec; // Load subfile rec EndIf; EndDo; Return n; // Return record count 2. The LoadSubfile Routine If this were any simpler it would have written itself! RecordCount is passed in as a parameter And used to control the For loop Write uses the subfile DS array element as the data source With the RRN being used as the array subscript Dcl-Proc LoadSubfile; Dcl-PI *N; Dcl-S RecordCount Int(10); For RRN = 1 to RecordCount; Write ProdSfl SubfileRec(RRN); EndFor; End-Proc; © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 126 of 146 4 & 5 Sort Selection Portion of Mainline Note use of Named Indicators for testing input F key requests SortDS is the name of the prototype for qsort() (4) Select; // Determine sort option selected by user When ( SortPrCode ); SortDS(SubfileRec(1): // Pass address of first element Count: %Size(SubfileRec): %PAddr( SortProduct ); // Procedure for product code sort When *In(SortDesc); SortDS(SubfileRec(1): Count: %Size(SubfileRec): %PAddr( SortDescr ); Other; // Default to Product sort SortDS(SubfileRec(1): Count: %Size(SubfileRec): %PAddr( SortProduct ); EndSl; (5) (5) (5) 5. The SortProduct Routine The only difference between the sorts is the field comparison Unlike SORTA - qsort() can handle multiple key sorts You just write the RPG code to do it! We use LikeRec to define the parms The qualified individual field name ( e.g. Element2.ProdCd ) can then be used in the comparisons Dcl-Proc SortProduct; Dcl-PI *N Int(10); Element1 LikeRec(ProdSfl: *Output) Element2 LikeRec(ProdSfl: *Output) Select; When Element1.ProdCd > Element2.ProdCd; Return High; When Element1.ProdCd < Element2.ProdCd; Return Low; Other; Return Equal; EndSl; End-Proc; © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 127 of 146 A Sample Multi-Key Sort Routine Unlike SORTA qsort() can use any logic you like for sequencing The most commonly used variant is to accommodate multiple keys • e.g. To sort City in State as in this example Dcl-Proc CityInState; Dcl-PI *N Int(10); Element1 LikeRec(CustAddress: *Output) Element2 LikeRec(CustAddress: *Output) Select; When Element1.State > Element2.State; Return High; When Element1.State < Element2.State; Return Low; When Element1.City > Element2.City; Return High; When Element1.City < Element2.City; Return Low; Other; Return Equal; EndSl; End-Proc; Any Questions ? ? Please e-mail Jon at: Jon.Paris @ Partner400.com for any questions © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 128 of 146 Meeting Users' Needs with Free Software for your IBM i Jon Paris Jon.Paris @ Partner400.com www.Partner400.com www.SystemiDeveloper.com Notes Jon Paris is co-founders of Partner400, a firm specializing in customized education and mentoring services for IBM i (AS/400, System i, iSeries, etc.) developers. Jon's career in IT spans 40+ years including a 12 year period with IBM's Toronto Laboratory. Together with his partner Susan Gantner, Jon devotes his time to educating developers on techniques and technologies to extend and modernize their applications and development environments. Together Jon and Susan author regular technical articles for the IBM publication, IBM Systems Magazine, IBM i edition, and the companion electronic newsletter, IBM i EXTRA. You may view articles in current and past issues and/or subscribe to the free newsletter at: www.IBMSystemsMag.com. Jon and Susan also write a weekly blog on Things "i" - and indeed anything else that takes their fancy. You can find the blog here: ibmsystemsmag.blogs.com/idevelop/ Feel free to contact the author at: Jon.Paris @ Partner400.com © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 129 of 146 Free Software ? By "Free" I mean as in: • No need to obtain budget approval • No direct cost ✦ Although you may choose to opt for a service contract • Free to modify to meet your own requirements ✦ In most cases but not all Note: "Free" does not mean "Cheap and Nasty" • Much of the free software I have used is as good or better than software from conventional sources • For many ISVs this is just a different marketing strategy Notes The first thing you need to understand is that free does not have to mean low quality. I have seen many free software packages that exceed in quality and capability those that are sold for substantial amounts of money. Many of the free packages are available with maintenance contracts so that you have someone to call if you have problems. This also helps if the idea of no (obvious) line of support makes your management nervous. Many of the packages are actually a subset of a fuller featured commercial offering and used as a form of marketing. In other words giving the base software away instead of spending $s on advertising, marketing reps, etc. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 130 of 146 Meeting Users Needs ? I'm using a very broad definition here of the term "Meeting" • What I'm really talking about is ... Anything that will help you to say "YES" to user requests • Part of our problem is that we say "No" far too often • Our counterparts on Windows etc. don't say that as often ... ✦ ✦ So guess who gets to do all the new interesting stuff And who is left defending their platform and maintaining old stuff - As well as dealing with interfacing their new stuff with our old stuff! So we'll talk about everything: • From tools that can help document systems • To tools that help you build apps faster • To complete applications that fill holes in your portfolio Notes For a long time now I have been trying to persuade IBM i shops to "just say Yes". It is all too common to see IBM i being displaced because the staff simply cannot react fast enough to the needs of the business. Switching to Java is not the answer, and switching to a WIndows "solution" isn't either. The answer, in my opinion, is to make more use of free and low cost tooling to give the users what they want. Not everything can be done for free of course, but it is far easier to get budget approval for staff and/or expensive tooling when your users are happy with what you do and supportive of your efforts. Tools such as the ones we will discuss here can help you achieve this. What's even better is that many can even be installed and experimented with on your PC before you deploy them to your IBM i. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 131 of 146 What We’ll Cover ... Updating business practices with new free/low-cost tools • Everything from: ✦ ✦ ✦ ✦ Customer Relationship Management Help Desk software User Documentation Web sites Extending your RPG application set • Free Tools that can help you to: ✦ ✦ Build new applications faster or Extend existing applications into new areas A Few Examples From the PHP World Customer Relationship Management Portal Content Management e-Commerce Content Management Course Management System Bulletin Board © Partner400, 2015 Simple Wiki Wiki Help Desk Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 132 of 146 SugarCRM - Customer Relationship Management www.sugarcrm.com MANTIS Help Desk Software • http://mantis400.com Uses native DB2 • Data usable from RPG, Query, COBOL, SQL... Available under GNU GPL Open Source license • I.e. It is FREE FOREVER Create/manage Internal and External Support Issues • Any web browser, any client operating system • E-mail notification as Issues are progressed Supported by Curbstone Corporation • They have used it in-house to track problems for many years © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 133 of 146 Mantis Main View Not as sophisticated as SugarCRM • But highly customizable • And the price is tough to beat! osTicket - Support Ticket Tracking An alternative to Mantis - osticket.com • Definitely better looking • Doesn't track as deeply into the development process ✦ More oriented to customer facing problem reporting • Requires use of Zend DBi (MySQL) or you can convert to DB2 © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 134 of 146 Yet Another Help Desk Option Hesk - The system currently used by System i Developer www.hesk.com • Simple • Free • Well supported What more could you ask! Part of a complete CRM suite Communication - Internal and External Internal and External Web sites • A web site that is easy to maintain is more likely to be maintained • Consider using a Wiki, or Content Management System (CMS) I'll be discussing MediaWiki • It is so easy to use that your end users can write their own pages! For a CMS how about Drupal? • The latest UI changes significantly simplify implementation How about a Bulletin Board system? • PHPBB is used by huge numbers of Bulletin Boards all over the world ✦ So customizable that you may not recognize them all as being the same software • A great way to share hints, tips, or news of that great new subprocedure • Can also be used as a low-level Help-Desk vehicle ✦ Or as part of a program to gather feedback and communicate with customers © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 135 of 146 Wikis - MediaWiki (mediawiki.org) Sample PMWiki Coding ... You can also do '''bold''' text and ''italic'' but '''''bold italic''''' is really messy! But tables are quite easy to do as you can see in the example below. {| |+ Really Simple Table | Item A1 || Item A2 || Item A3 || Item B1 || Item B2 || Item B3 ... Indented lists are also very simple and can include numbered lists. *First Level [[LinkPage | with a built in link]] **This at the second level **So is this ***Indent a bit more ***And another at that level *Now back to the top level And here we are back in normal text followed by an example of a slightly more complex table with row and cell alignment: {| class="wikitable" |+ More Complex Example WIth Headers and Alignment ! < ---- Header 1 ---- > !! < ---- Header 2 ---- > !! < ---- Header 3 ---- > || Default cell 1 || Default cell 2 || Default cell 3 |- style="text-align:right;" | Row right align 1 || Row right align 2 || Row right align 3 ||Left aligned || style="text-align:center;"| Centered || style="text-align:right;"| Right aligned |} © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 136 of 146 Build Web Sites - Drupal & Drupal Gardens Drupal (drupal.org) • Complete Content Management System ✦ ✦ Features include support for blogs, forums, RSS feeds, etc. Simplified install process, Language support, Update status and more • Version 7 had a heavy focus on usability ✦ ✦ Dealing with many earlier complaints about ease-of-use Comprehensive support for developers brings it really close to being usable as an Application Framework • Version 8 promises to be even better Drupal Gardens gives you a way to try it all out easily • You can build and run a complete site free-of-charge ✦ Samples www.test-drupalgardens.edrupalgardens.com/taxonomy/term/41/0 • Then export it to run it locally on your own system ✦ Or upgrade at very low cost to run it on their web site Drupal - Simple Site Editing options only appear for authorized users © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 137 of 146 And Now ... Extending your RPG application set • Free Tools that can help you to: ✦ ✦ ✦ Test applications more easily Build new applications faster Extend existing applications into new areas Notes In this section we will look at tooling that can help you build new applications faster. We find that remaining relevant to your organization is the best way to guarantee your future. Simply being "indispensable" due to knowledge of a specific piece of software is no good if that package is kicked out an replaced! © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 138 of 146 Generating Test Data With Faker <?php require __DIR__ .'/../src/autoload.php'; $faker = Faker\Factory::create(); print '<table border="1">'; for ($i=0; $i < 10; $i++) { print '<tr><td>' . $faker->firstName . ' ' . $faker->lastName . '</td>'; print '<td>' . $faker->phoneNumber . '</td>'; print '<td>' . $faker->streetAddress . '</td>'; print '<td>' . $faker->city . '</td>'; print '<td>' . $faker->postcode . '</td>'; print '<td>' . $faker->stateAbbr . '</td></tr>'; } print '</table>'; Find it at github.com/fzaninotto/Faker • Names, Addresses, Phone numbers, Zip codes, eMails, Text, etc. Also try generatedata generatedata.com • An on-line offering that produces downloadable test data Notes The sample code is producing an HTML table for demonstration purposes. But it could equally produce a CSV file, or for that matter update a test database directly. The table below is an example of the output of this script. © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 139 of 146 Building New Applications Faster Courtesy of Application Frameworks • Acknowledgement that applications have common requirements • A Framework is a set of sophisticated building blocks ✦ Think of them as providing a more advanced version of program cloning • Most employ Object Oriented design and programming methods ✦ But you don't have to code in an OO style to use them A great way to produce feature-rich applications quickly • Three that I have used and like are: ✦ ✦ ✦ PHPGrid XataFace and iDataGrid - an IBM i specific offering • We will look briefly at each of them in a moment PHPGrid - www.phpgrid.com New tool that simplifies the building of CRUD applications • Can be used by itself Not quite free! • Or embedded in more complex applications Completely OO but still easy to use without OO skills • Only lines marked are required for a working grid require_once("../conf.php"); // Include main phpgrid configuration and routines // Identify table and required columns and default sort column $dg = new C_DataGrid("select customerNumber, customerName, addressLine1, addressLine2, city, state, country from customers", "customerNumber", "customers"); $dg -> set_query_filter("state != '' "); // Set WHERE clause $dg -> set_dimension(1000, 800); $dg -> set_pagesize(40); // Set height and width of grid // Increase page size to 40 $dg -> enable_search(true); $dg -> enable_export('PDF'); // Enable search // Enable pdf export $dg -> display(); // And now show the world! © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 140 of 146 Results of Simple Script Search and PDF Generation XataFace - www.Xataface.com Pronounced Zataface • Originally developed for use at Simon Fraser Univerisity ✦ Faculty of Applied Science Designed for the development of Data Centric Applications • Separates the logic from the presentation • Inherits relationships, sorting, searching etc. from the database ✦ Any fields or relationships added to the database are immediately reflected in the application - often with no additional work Basic Process is: • Design the database • Optionally decorate the application ✦ Done by setting parameters • Optionally design the web pages ✦ ✦ i.e. Modify the templates used The equivalent of the display file DDS © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 141 of 146 Simple Xataface Application This is a simple four table application • Customer Orders, Customers, Sales Reps, and Products It uses Xataface's default look-and-feel • Sorting, searching, paging, import, export, etc. capabilities are all built in Structure of a XataFace Application The top level folder JonstestApp contains the application • conf.ini contains connection information for the database and identifies the tables to be used • index.php is the main driver script - as you will see it is very simple • Directory tables contains one directory for each table to be "decorated" ✦ More on these files in a moment © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 142 of 146 Source Code conf.ini and index.php ; File: conf.ini ; Description: ; Configuration details including database connection ; and tables to be included in the application. [_database] host = "localhost" user = "dbuser" password = "dbuserpw" name = "sampledb" [_tables] orders = "Customer Orders" orderDetails = "Order Details" customers = "Customers" employees = "Sales Reps" /* File: index.php /* Description: /* First line incorporates the XataFace Library /* Second invokes the display method that sets everything in motion <?php require_once '/Applications/MAMP/htdocs/xataface/dataface-public-api.php'; df_init(__FILE__, 'http://localhost:8888/xataface')->display(); ?> Source Code - fields.ini and valuelists.ini Directory tables contains one directory for each table to be "decorated" • ✦ ✦ My directory customer contains these two files fields.ini provides information about the individual fields valuelists.ini contains the data needed to build pull-down lists etc. This is the only customization I have done - everything else is standard [customerNumber] widget:label = "Customer Number" widget:description = "Enter the Customer Number:" [customerName] widget:label = "Customer Name" widget:description = "Enter the Customer's Name:" [salesRepEmployeeNumber] widget:label = "Sales Representative" widget:description = "Enter the Sales Rep's Code:" widget:type=select vocabulary=salesrep [salesrep] __sql__ = "select employeeNumber, lastName from employees order by lastName" © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 143 of 146 iDataGrid - A New Boy in Town iDataGrid - The Application Wizard © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 144 of 146 WOW - Community Edition Java rather than PHP based • Tool is free but no source code supplied • If you like it you can grow into the full commercial version - www.planetjavainc.com While on the Subject of Java Based Tools There's a new kid in town! • Open Legacy (openlegacy.com) This is a re-facing/ re-purposing tool set • Comes complete with a graphical designer for 5250 modernization • Also has tooling to assist in building web services etc. I have not yet had time to "play" • If I like it, expect to see an article in the future ✦ And maybe even a session at the RPG & DB2 Summit © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 145 of 146 Shopping For Free & Low-Cost Software Where to "Shop"? • Other than any specific sites mentioned ... Hotscripts.com • Great place to hunt for utilities, applications, and more • Pay attention to the Pepper Ratings and whether the package is regularly updated For PHP tools and articles look to PHP Publications ✦ Sitepoint's PHP Master zone (sitepoint.com/php/) - Sitepoint also have great books and classes on all things web • Web and PHP Magazine ✦ Free download (webandphp.com) Questions? Comments? Please feel free to contact me: Jon.Paris @ Partner400.com ? © Partner400, 2015 Gateway/400 Seminar - December 10th, 2015 Lecture Notes: Page 146 of 146