# RPG API Express 3.5 Documentation


---

# RPG API Express 3.5.x System Requirements

> Lists supported IBM i OS versions (7.3-7.6), required features (Apache, QShell), and installation prerequisites for RPG API Express 3.5.x.

## Installed Feature Requirements 
- RPG API Express requires Apache which is installed on 99% of all machines we come in contact with. Note that Apache only needs to be started if you are offering web services from your IBM i. If you are only acting as the client role and making a request to another server *from* the IBM i, then you don't need to start the Apache server.
- We use QShell during the install so that is also required and is already installed on most machines. It is **5722SS1 opt 30**.

## Operating System Level Support 
RPG API Express 3.5.x can be used on the following operating system versions:

- IBM i OS 7.6
- IBM i OS 7.5
- IBM i OS 7.4
- IBM i OS 7.3

---

# Installing RPG API Express 3.5.x

> Step-by-step installation guide covering file transfer, library restoration, HTTP server creation, license activation, and verification.

RPG API Express is available as a fully-featured 30 day free trial. To request a trial and a free customized proof of concept program, please [contact us](https://www.rpg-xml.com/contact-us/).

1. Unzip the downloaded file to `C:\temp` (or the directory of your choice).

2. Create the \*SAVF to upload to on your IBM i:

    `CRTSAVF FILE(QGPL/RXS) AUT(*ALL)`

3. FTP the file RXS.SAVF from your PC to the IBM i. Open a DOS prompt (Start -> Run -> enter 'cmd' and hit enter). Type the following into the DOS prompt, replacing the IP address with that of your IBM i. When prompted, enter your IBM i username and password.

    1. `ftp 123.456.789.123`
    2. `binary`
    3. `lcd c:\temp`
    4. `quote site namefmt 0`
    5. `cd QGPL`
    6. `put RXS.savf RXS.savf`
    7. `quit`

4. Issue the following IBM i commands. The value 'RXS' is used to denote where the base install of RPG API Express should reside. Note that 8181 is the default port your RXS runs under in Apache. Change it to meet your needs. The default of 8181 should be fine 99% of the time.

    1. `RSTLIB SAVLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS) RSTLIB(RXS)`
    2. `ADDLIBLE RXS`
    3. `CRTRXSSVR HTTPSVR(*RXSLIB) RXSLIB(RXS) PORT(8181)`

5. Registration - When you downloaded RXS, you should have been prompted to enter in a valid email address. An email should have been sent to that address specifying an APYLIC command similar to the one below. Issue the emailed command using copy-and-paste to avoid input errors.

    `APYLIC LICKEY(<<insert key that was emailed to you>>) PRODUCT(RXS)`

6. Start the HTTP server using the below command:

    `HTTP HTTPSVR(RXS) OPTION(*START)`

7. Please check the value of the QSECURITY system value:

    `DSPSYSVAL SYSVAL(QSECURITY)`

    If the value is 30 or lower, then you can proceed to the next step.

    If the value is 40 or above, please ensure that the value of QALWUSRDMN is \*ALL, or optionally add RXS to the list of libraries:

    `WRKSYSVAL SYSVAL(QALWUSRDMN)`

8. Open your internet browser and enter the following while substituting your IBMi IP address:

    `http://yourIBMiIP:8181/debug`

    You should see a page used for general debugging of the RPG API Express Router. If you see this page, then RPG API Express is installed properly.

---

# Upgrading RPG API Express 3.5.x

> Upgrade procedures for single and multiple library installations, including RSTOBJ commands, library list management, and Apache instance handling.

To apply a permanent or temporary license key, you will use the included command APYLIC. The upgrade process for RPG API Express is dependent on how many libraries contain RPG API Express objects. You can determine if RPG API Express is installed in multiple libraries with this command:

`WRKOBJ OBJ(*ALL/RXS*) OBJTYPE(*SRVPGM)`

If the WRKOBJ command lists only library RXS, you should use the upgrade instructions for **Single Library Installation** below.

If the WRKOBJ command lists libraries other than RXS, please use the upgrade instructions for **Multiple Library Installation** below.

Additionally, if you have a library named KTLIC on your IBM i, please follow the instructions for **KTLIC Library Deprecation** below before beginning any upgrade.

### Single Library Installation

**Note:**
- `@@@` is the version number of the source/current version level (example: 220)
- `###` is the version number of the target/new version level (example: 350)

For clarity, you may wish to copy these instructions to a text editor and then perform a scan and replace for the above values before beginning the upgrade.

1. Unzip the downloaded upgrade file to `C:\temp` (or the directory of your choice).

2. Issue the IBM i command:

    `CRTSAVF FILE(QGPL/RXS###) AUT(*ALL)`

3. FTP the file rxs.savf from your PC to the IBM i in BINARY mode into the save file that was just created:

    1. Open a DOS prompt (Start -> Run -> type 'cmd' and hit Enter)
    2. Type the following into the DOS prompt, replacing the IP address with that of your IBM i: `ftp 127.0.0.1`
    3. When prompted, enter your user profile and password
    4. Execute the following commands in the DOS prompt to transfer the file:
        1. `binary`
        2. `lcd C:\temp`

            **Note:** Replace `C:\temp` with the location of the rxs.savf on your local computer.

        3. `quote site namefmt 0`
        4. `cd QGPL`
        5. `put rxs.savf rxs###.savf`
        6. `quit`

4. End all RPG API Express Apache instances. Note that this will mean your web services are **NOT** accessible! Execute the following IBM i command to end the Apache instances:

    `ENDTCPSVR SERVER(*HTTP) HTTPSVR(RXS)`

    **Note:** Be sure to execute this command for any other Apache instances that also use RPG API Express.

5. Perform a full backup of library RXS.

6. Execute the following IBM i commands in sequence to upgrade the library RXS. Please be careful to enter the commands EXACTLY as listed, except for adjusting the SAVF parameter:

    1. `RSTOBJ OBJ(*ALL) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) OMITOBJ((RXSUNQ) (RXSCFG) (LICP) (RXSRTRCTL) (RXSRTRLOG) (EXAMPLE)) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

        **Note:** The above RSTOBJ command may report that file RXS was not restored. If this occurs, please rename the existing RXS file to RXS@@@ where @@@ is the pre-upgrade version level. Then retry the RSTOBJ command.

    2. `RSTOBJ OBJ(RXSUNQ RXSCFG LICP RXSRTRCTL RXSRTRLOG) SAVLIB(RXS) OPTION(*NEW) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

        **Note:** The above RSTOBJ command may report that no objects were restored. This is not an error condition and you should continue with the next step.

    3. `RNMOBJ OBJ(RXS/EXAMPLE) OBJTYPE(*FILE) NEWOBJ(EXAMPLE@@@)`

        **Note:** The above RNMOBJ command may optionally be replaced with `DLTF FILE(RXS/EXAMPLE)` to delete the EXAMPLE source physical file. You should only do this if you are confident that your company has not stored your own source code inside the EXAMPLE source physical file.

    4. `RSTOBJ OBJ(EXAMPLE) OBJTYPE(*FILE) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

7. Execute the following IBM i commands when files RXS/RXSRTRCTL and RXS/RXSRTRLOG do **NOT** already have a RXSRTR member:

    1. `RNMM FILE(RXS/RXSRTRCTL) MBR(RXSRTRCTL) NEWMBR(RXSRTR)`
    2. `RNMM FILE(RXS/RXSRTRLOG) MBR(RXSRTRLOG) NEWMBR(RXSRTR)`
    3. `CHGPF FILE(RXS/RXSRTRCTL) MAXMBRS(32)`
    4. `CHGPF FILE(RXS/RXSRTRLOG) MAXMBRS(32)`

8. Execute the following IBM i commands when your pre-upgrade version was previous to version 2.70:

    1. `RMVLIBLE LIB(RXS)`
    2. `ADDLIBLE LIB(RXS) POSITION(*FIRST)`
    3. `APYLIC LICKEY(<<insert key that was provided during your upgrade planning>>) PRODUCT(RXS)`

        **Note:** If you need a temporary license key, please email our support team at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

9. Restart all RPG API Express Apache instances:

    `STRTCPSVR SERVER(*HTTP) HTTPSVR(RXS)`

    **Note:** Be sure to also execute this command for any custom Apache instances that also use RPG API Express.

10. Initialize RXS Router:

    `INZRXSRTR ENV(*ALL)`

### Multiple Library Installation

If you haven't yet, run the following command: `WRKOBJ OBJ(*ALL/RXS*) OBJTYPE(*SRVPGM)`

Using the libraries output by the WRKOBJ command, please make a list of the libraries which need to be upgraded, but do not include the library RXS on the list. There should be an Apache instance for each library and the instance name should be the same as the library. As you continue with these instructions, this list will be referred to as the "Upgrade List". These instructions will have you first perform the upgrade to your RXS library, then to each library on the Upgrade List.

These instructions will reference the library and Apache instance named MYRXS. You may or may not have a MYRXS library and Apache instance on your Upgrade List. Regardless, when upgrading each library on your Upgrade List, substitute that library's name in place of MYRXS in the following instructions.

**Note:**
- `@@@` is the version number of the source/current version level (example: 220)
- `###` is the version number of the target/new version level (example: 350)

For clarity, you may wish to copy these instructions to a text editor and then perform a scan and replace for the above values before beginning the upgrade.

1. Unzip the downloaded upgrade file to `C:\temp` (or the directory of your choice).

2. Issue the IBM i command:

    `CRTSAVF FILE(QGPL/RXS###) AUT(*ALL)`

3. FTP the file rxs.savf from your PC to the IBM i in BINARY mode into the save file that was just created:

    1. Open a DOS prompt (Start -> Run -> type 'cmd' and hit Enter)
    2. Type the following into the DOS prompt, replacing the IP address with that of your IBM i: `ftp 127.0.0.1`
    3. When prompted, enter your user profile and password
    4. Execute the following commands in the DOS prompt to transfer the file:
        1. `binary`
        2. `lcd C:\temp`

            **Note:** Replace `C:\temp` with the location of the rxs.savf on your local computer.

        3. `quote site namefmt 0`
        4. `cd QGPL`
        5. `put rxs.savf rxs###.savf`
        6. `quit`

4. End all RPG API Express Apache instances. Note that this will mean your web services are **NOT** accessible! Execute the following IBM i command to end the Apache instances, while substituting the instance names from the Upgrade List for MYRXS:

    `ENDTCPSVR SERVER(*HTTP) HTTPSVR(RXS)`

    **Note:** Be sure to execute this command for any other Apache instances that also use RPG API Express.

5. Perform full backups of library RXS and all libraries on the Upgrade List.

6. Execute the following IBM i commands in sequence to upgrade the library **RXS**. Please be careful to enter the commands EXACTLY as listed, except for adjusting the SAVF parameter:

    1. `RSTOBJ OBJ(*ALL) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) OMITOBJ((RXSUNQ) (RXSCFG) (LICP) (RXSRTRCTL) (RXSRTRLOG) (EXAMPLE)) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

        **Note:** The above RSTOBJ command may report that file RXS was not restored. If this occurs, please rename the existing RXS file to RXS@@@ where @@@ is the pre-upgrade version level. Then retry the RSTOBJ command.

    2. `RSTOBJ OBJ(RXSUNQ RXSCFG LICP RXSRTRCTL RXSRTRLOG) SAVLIB(RXS) OPTION(*NEW) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

        **Note:** The above RSTOBJ command may report that no objects were restored. This is not an error condition and you should continue with the next step.

    3. `RNMOBJ OBJ(RXS/EXAMPLE) OBJTYPE(*FILE) NEWOBJ(EXAMPLE@@@)`

        **Note:** The above RNMOBJ command may optionally be replaced with `DLTF FILE(RXS/EXAMPLE)` to delete the EXAMPLE source physical file. You should only do this if you are confident that your company has not stored your own source code inside the EXAMPLE source physical file.

    4. `RSTOBJ OBJ(EXAMPLE) OBJTYPE(*FILE) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) RSTLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

7. Execute the following IBM i commands when file RXS/RXSRTRCTL **does NOT** already exist:

    `RSTOBJ OBJ(RXSRTRCTL RXSRTRLOG) SAVLIB(RXS) DEV(*SAVF) SAVF(QGPL/RXS###) RSTLIB(RXS)`

8. Execute the following IBM i commands when files RXS/RXSRTRCTL and RXS/RXSRTRLOG **do NOT** already have a RXSRTR member:

    1. `RNMM FILE(RXS/RXSRTRCTL) MBR(RXSRTRCTL) NEWMBR(RXSRTR)`
    2. `RNMM FILE(RXS/RXSRTRLOG) MBR(RXSRTRLOG) NEWMBR(RXSRTR)`
    3. `CHGPF FILE(RXS/RXSRTRCTL) MAXMBRS(32)`
    4. `CHGPF FILE(RXS/RXSRTRLOG) MAXMBRS(32)`

9. Execute the following IBM i commands when your pre-upgrade version was previous to version 2.70:

    1. `RMVLIBLE LIB(RXS)`
    2. `ADDLIBLE LIB(RXS) POSITION(*FIRST)`
    3. `APYLIC LICKEY(<<insert key that was provided during your upgrade planning>>) PRODUCT(RXS)`

        **Note:** If you need a temporary license key, please email our support team at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

10. Execute the following IBM i commands **once for each library in the Upgrade List**, replacing **MYRXS** in each command with a library name from the Upgrade List:

    1. **Note:** The following string is a <u>single command</u> - you must copy the entire string. You may need to use the extended command line accessed via STRPDM.

        `RSTOBJ OBJ(*ALL) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) OMITOBJ((RXSUNQ) (RXSCFG) (LICP) (INSTALL) (NEWENV) (DSPMCH*) (DSPLIC) (RXSRTRCTL) (RXSRTRLOG) (RXSURI*) (DSPVER) (VER*) (RXSMENU*) (HTTP*) (INZ*) (WRK*) (CRT*) (DLT*) (*ALL *PNLGRP)) RSTLIB(MYRXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

        **Note:** The above RSTOBJ command may report that file RXS was not restored. If this occurs, please rename the existing RXS file to RXS@@@ where @@@ is the pre-upgrade version level. Then retry the RSTOBJ command.

    2. `CPYF FROMFILE(RXS/LICP) TOFILE(MYRXS/LICP) MBROPT(*REPLACE) CRTFILE(*YES)`
    3. `RNMOBJ OBJ(MYRXS/EXAMPLE) OBJTYPE(*FILE) NEWOBJ(EXAMPLE@@@)`

        **Note:** The above RNMOBJ command may optionally be replaced with `DLTF FILE(MYRXS/EXAMPLE)` to delete the EXAMPLE source physical file. You should only do this if you are confident that your company has not stored your own source code inside the EXAMPLE source physical file.

    4. `RSTOBJ OBJ(EXAMPLE) OBJTYPE(*FILE) SAVLIB(RXS) MBROPT(*ALL) ALWOBJDIF(*FILELVL *OWNER) RSTLIB(MYRXS) DEV(*SAVF) SAVF(QGPL/RXS###)`

11. Restart all RPG API Express Apache server instances by executing the following command, replacing **MYRXS** with a library name from the Upgrade List:

    `STRTCPSVR SERVER(*HTTP) HTTPSVR(MYRXS)`

    **Note:** Be sure to execute this command for each instance name on the Upgrade List and for any custom instances that also use RPG API Express.

12. Initialize RXS Router:

    `INZRXSRTR ENV(*ALL)`

### KTLIC Library Deprecation

Library KTLIC was installed with version level 2.7x only and has been deprecated since version 2.80. KTLIC is no longer needed after upgrading to version 2.80 or higher. If your pre-upgrade version level is already 2.80 or higher, do not run the CPYF command below and instead note the reference to saving and deleting KTLIC.

If your pre-upgrade version is 2.7x, execute the following IBM i commands before beginning any upgrade:

`CPYF FROMFILE(KTLIC/LICP) TOFILE(RXS/LICP) MBROPT(*REPLACE) CRTFILE(*YES)`

After your upgrade to a version 2.80 or greater is complete and proven successful, please save and then delete library KTLIC.

---

# Uninstalling RPG API Express

> Covers removing RPG API Express by terminating Apache instances, deleting the library, and removing IFS directories.

RPG API Express licenses may be transferred to a different IBM i, or a different partition. Part of this process will involve uninstalling the product from the original machine/partition.

1. End the Apache server instance:

    `ENDTCPSVR SERVER(*HTTP) HTTPSVR(RXS)`

2. Delete the RPG API Express install library:

    `DLTLIB RXS`

3. Run QShell command to recursively remove the RXS directory and everything in it:

    `QSH CMD('rm -Rf /www/RXS')`

4. Run QShell command to recursively remove the /krengeltech directory and everything in it:

    `QSH CMD('rm -Rf /krengeltech')`

5. Remove the Apache server instance from OS/400:

    `RMVM FILE(QUSRSYS/QATMHINSTC) MBR(RXS)`

---

# Replication Instructions

> Specifies objects to exclude from replication (QGPL/RXSBASE, LICP files) to prevent licensing errors on replicated systems.

If the following objects exist in your RPG API Express installation, they should be excluded from replication. Failure to do this will cause licensing errors.

- QGPL/RXSBASE *DTAARA
- RXS/LICP *FILE
- Any other LICP *FILE objects present in RXS instance libraries (ex. MYRXS)

---

# License Transfer

> Explains how to transfer an RPG API Express license from one IBM i system or partition to another.

RPG API Express licenses may be transferred to a different IBM i, or a different partition. To do this, you will need to provide us with screenshots of the following information with the RXS library in your library list:

- The currently installed version of RPG API Express:
`CALL RXS/VERRXSBASE`

- Machine info for each LPAR a license is being moved from:
`DSPMCHINF`

- Machine info for each LPAR a license is being moved to:
`DSPMCHINF`

If you don't currently have access to the system you're migrating to, please let us know and we can provide a temporary license until you can provide information for us to generate a permanent license key.

Please email this information to <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

---

# RXS Changelog

> Version history for RPG API Express, detailing changes per release.

## 3.5.3 
- [New] **New feature:** Added new parameter AUTOSTART to CRTRXSSVR command to allow configuring the HTTP server's autostart property at creation time
- [New] **New feature:** Added new value *CFGAUTO for HTTP command parameter OPTION to enable new parameter AUTOSTART which allows modifying the HTTP server's autostart property after creation
- [New] **New feature:** Added new parameter STMFCCSID to RXSURI command to allow configuring the CCSID used to create response IFS stream files
- Enhanced subprocedures with more aggressive checking of input parameter data structure types to avoid unintended behavior. **If an existing program is incorrectly relying on using the wrong data structure for an RXS subprocedure, you may experience new runtime errors after upgrading.**
- Enhanced RXS_HMACDS_t with new subfield ReturnMode which accepts the constants RXS_RETURN_MODE_BYTES, RXS_RETURN_MODE_CHAR_UPPER, and RXS_RETURN_MODE_CHAR_LOWER to allow the value returned from RXS_HMAC to be returned as raw bytes, uppercase characters, or lowercase characters. ReturnMode replaces/overlays the existing subfield ReturnAsChar which only allowed raw bytes or uppercase characters to be returned by RXS_HMAC
- Enhanced RXS_ParseJsonDS_t with new subfield ParseMode which accepts the constants RXS_JSON_PARSE_DEFAULT, RXS_JSON_PARSE_NATIVE, and RXS_JSON_PARSE_NULL_EMPTY to allow the parsed values made available in a parsing handler subprocedure to be returned as either converted to character data, provided as native RPG data types, or converted to character data but with null values converted to an empty string. ParseMode replaces/overlays the existing subfield ConvertDataToString which only allowed values to be converted to character data or native RPG data types
- Enhanced RXS Router to pull Initial ASP group from a *JOBD specified for -liblst or -dftliblst to provide further compatibility with iASPs
- Enhanced RXS Router debug mode page to now display instructions to disable debug mode
- Enhanced RXS_HTTPResponseDS_t data structure with new subfield ResponseLength to provide the actual length of the response
- Enhanced RXSURI command TIMEOUT parameter with new special value *NONE to indicate no maximum timeout
- Enhanced HTTP command to allow parameter OPTION values *CONF, *LOGS, *DOCS, *CURLOG, and *DIR to more accurately retrieve the HTTP server path to support HTTP servers created on iASPs
- Enhanced BLDPRS command to now include ExtProc(*DclCase) in generated prototypes and procedure interfaces
- Enhanced BLDPRS, BLDCMP, and BLDTPL commands to throw an error when the same IFS filepath is provided for both input and output
- Corrected an issue with RXS_FormatJson where the RXS_FormatJsonDS_t data structure parameter could become incorrectly reinitialized
- Corrected an issue with RXS_ComposeJsonString where passing in an indicator value like RXS_JSON_TRUE or RXS_JSON_FALSE would lead to the JSON containing 16mb of 1s or 0s
- Corrected an issue with RXS_PutStdOut where input parameter size was being calculated multiple times
- Corrected an issue with CRTRXSSVR command PORT parameter help text displaying the wrong parameter name
- Corrected an issue with RXSURI command where output would not be displayed interactively if an input IFS file was used
- Corrected an issue with RXSURI where the help text did not accurately reflect all available options
- Corrected a typo in error message KTA120B
- Updated XML being composed as part of example programs EX3 and FX_EX3
- Removed legacy INSTALL *PGM object from installation


## 3.5.2

- Corrected an issue where an empty LICP file would result in an incorrectly thrown KTE0002 message instead of KTE0001 as intended
- Corrected an issue with RXS_ParseDomToDom where the returned RXS_ParseDomDS_t data structure would not be properly initialized if any error occured during the call to RXS_ParseDomToDom
- Corrected an issue with WRKRXSRTRE where the environment library being displayed was incorrectly truncated
- Corrected an issue with WRKRXSRTRE which could prevent modifying or adding routing entry records
- Corrected an issue with RXS_Transmit where a response with charset ISO-8859-1 could be improperly handled in certain circumstances


## 3.5.1

- Enhanced RXS Router to allow up to 250 libraries to be specified for -liblst or -dftliblst
- Enhanced WRKRXSRTRE to show the current active mode when displaying, modifying, or deleting a routing entry record
- Enhanced WRKRXSRTRE to no longer attempt validation for routing entry records for records with DEBUG as the routing ID
- Corrected an issue with library list handling in RXS Router that prevented libraries specified in -liblst or -dftliblst from being added to the library list in certain circumstances
- Corrected an issue where RXS Router's debugmode page would truncate routing entry record data for large routing entry records


## 3.5.0

- **The product name has been formally changed to RPG API Express.** This change is purely cosmetic, impacting descriptive text, help panel text, and some message text. This change has no functional impact on the usage of the product.
- **The minimum supported operating system version is now IBM i 7.3**


### Code Generation Tools 
- [New] **New feature:** BLDCMP is a new code generation command that can be passed an IFS JSON file and will generate the appropriate RPG source code which would allow for composing identical JSON using the RXS JSON composition APIs
- [New] **New feature:** Added a new optional parameter INCCRTCMD to command CRTRPGTPL which allows a user to omit the section of a generated template that contains the creation command information
- Enhanced BLDTPL to preserve namespace attributes when generating templates, rather than converting them to variable placeholders
- Enhanced CRTRPGTPL to support whitespace in front of section delimiters
- Enhanced CRTRPGTPL to better handle escaping single quotation marks when wrapping lines
- Corrected an issue where CRTRPGTPL would generate creation command information where the IFS filepath was truncated
- Corrected an issue where CRTRPGTPL would retain a lock on a source member provided as input


### HTTP Communication - RXS_Transmit and RXS_getURI() 
- [New] **New feature:** Added new subfields HeaderAuthScheme and HeaderAuthCredentials to RXS_TransmitDS_t to allow RXS_Transmit to more broadly support a variety of authentication mechanisms without needing to set custom HTTP headers. This support allows for credentials/tokens up to 8kb in length to better support complex/large JWT tokens when used as part of a Bearer authentication scheme.
- [New] **New feature:** Added new subfield HeaderAcceptEncoding to RXS_TransmitDS_t to allow RXS_Transmit to request that the remote server provide gzip encoded response data via the [Accept-Encoding HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding), and RXS_Transmit will automatically handle decoding. This should result in an overall performance increase when calling any web APIs which support gzip encoding. **Requesting that the remote server provide the response via gzip encoding is now the default behavior**
- [New] **New feature:** Added a new constant RXS_TIMEOUT_UNLIMITED to allow setting an unlimited timeout when calling RXS_Transmit
- Enhanced RXS_Transmit to allow output to be directed both to a response field as well as to a response IFS file
- Enhanced RXS_Transmit IFS log file output to properly display the response when the remote server provided the response using chunked encoding. Previously, the IFS log file would display the response split into chunks along with the chunk encoding length bytes
- Corrected a rare issue where RXS_Transmit ending in error with an IFS log file specified could attempt to generate an IFS log file on a subsequent successful RXS_Transmit call which did not request a log file
- Corrected an issue with RXS_getURI preventing zero length POST requests


### Encryption and Hashing APIs 
- [New] **New feature:** Added RXS_HMAC to perform HMAC calculation utilizing the SHA1, SHA256, SHA384, SHA512, and MD5 algorithms


### Offering Web Services/APIs 
- [New] **New feature:** Added -aspgrp flag to RXS Router to allow for swapping the ASP Group prior to any library list changes or program calls
- Enhanced the CRTRXSSVR command to support 5-digit port numbers up to 65535
- Enhanced the CRTRXSSVR command with new *HTTP and *HTTPS options for setting the port number which will cause port 80 and port 443 to be used
- Corrected an issue with the HTTP command where a validation message was not properly displayed when attempting to use parameter HTTPSVR(*ALL) with an OPTION other than *WRK, or when attempting to use HTTPSVR(*ADMIN) with an option other than *WRK, *START, *END, or *RESTART


### JSON Composition APIs 
- Corrected an issue where RXS_GetJsonString could enter a looping condition if certain hexadecimal characters were present in composed JSON data


### JSON Parsing APIs 
- Enhanced RXS_ParseJson to check input JSON for possible truncation before attempting to parse, and added specific error output to differentiate truncation as a cause of parsing failure from malformed JSON


### XML Parsing APIs 
- Corrected an issue where RXS_XPath was incorrectly escaping @ symbols that were part of a formatted XPath
- Corrected a potential memory leak present in RXS_Parse when parsing XML stored in an IFS file


### Conversion APIs 
- Enhanced RXS_Convert Base64 and Base64URL decoding to ignore whitespace characters including space, tab, CR, LF, and NL
- Corrected an issue with RXS_Convert Base64URL decoding where it would fail on certain string lengths that could not be accurately internally padded
- Corrected an issue with RXS_Convert Base64 and Base64URL decoding where line break characters could cause a failed conversion loop


### Example Programs 
- Included example programs are now provided in two forms - fully free format RPG as well as traditional/fixed format RPG
- Included example programs have been updated to reflect our team's latest recommended standards for calling and using various RXS APIs
- To provide clarity and work within the 10 character object name limit, existing example programs have been renamed - see table below:

  | ORIGINAL EXAMPLE NAME | NEW FREE FORMAT NAME | NEW FIXED FORMAT NAME |
  | --- | --- | --- |
  | CELSIUS | EX1 | FX_EX1 |
  | CELSIUSDOM | EX2 | FX_EX2 |
  | DOMRECUR | EX3 | FX_EX3 |
  | EXCOMPOSE1 | EX4 | FX_EX4 |
  | EXPARSE1 | EX5 | FX_EX5 |
  | XSDVALID | EX6 | FX_EX6 |



### Other Changes 
- New constants have been added to the RXSCB copybook for commonly used HTTP status codes
- Display file CONFIRMD has been renamed to RXSCFMD based on a user-reported conflict with a display file named CONFIRMD present in another vendor's product. The existing CONFIRMD may be safely deleted
- Enhanced the DSPMCHINF command to now show the partition UUID in preparation for future licensing enhancements
- Enhanced the APYLIC command to no longer interactively prompt for license key replacement when the APYLIC command is run as part of a batch job
- Enhanced internal licensing code to reduce unnecessary LICP database file access and reduce necessity of using UPDPROD(*YES) in some situations
- Corrected an issue where WRKRXSRTRE would display an incorrect date when run from a long-running job


## 3.4.7

### Code Generation Tools 
- Enhanced BLDPRS to allow a base envelope value to be specified when generating XML parsing handlers, allowing for shorter XPaths within the handler subprocedure
- Corrected an issue in BLDPRS where code for XML content events would sometimes fail to be generated if some event options were disabled
- Corrected an issue preventing errors triggered within CRTRPGTPL from properly propagating to the user, causing cryptic error messaging when the template creation process failed


### JSON APIs 
- Corrected an issue with the RXS_CreateJson() TrimVariables option incorrectly handling leading whitespace
- Corrected an issue where RXS_GetJsonStringLen() was occasionally returning an incorrect and larger value


### Other Changes 
- The QRPGLECPY,RXSCB copybook is now provided in fully free format RPG. We now also provide a traditional/fixed format RPG equivalent which can be found in QRPGLECPY,RXSCB_FX for RPG developers who are more comfortable with the older RPG code format. Going forward, any changes made to RXSCB will be reflected in RXSCB_FX to the degree that the RPG language allows. RXSCB_FX is intended primarily for reference purposes for developers unfamiliar with fully free format RPG. We don't recommend changing your existing programs to use RXSCB_FX instead of RXSCB, and we don't recommend using RXSCB_FX to write new programs
- Corrected an issue that occasionally caused inconsistent Base64 encoding with RXS_Convert() when the provided input field was not a 4-byte varying field
- Enhanced RXS_XPath() with better error messaging for when string replacements are specified but there are insufficient provided input parameter values to fill them
- Updated RXSURI to be consistent with RXS_Transmit() with regards to trimming input parameters


## 3.4.6

### Conversion APIs 
- [New] Added RXS_ConvertBase64UrlDS_t as option to RXS_Convert() to allow for Base64Url encoding/decoding


### HTTP Communication - RXS_Transmit() 
- Corrected issue with RXS_Transmit() treating HTTP response headers as if they were UTF-8 data instead of ISO-8859-1 as per [RFC 5987](https://tools.ietf.org/html/rfc5987) and [RFC 8187](https://tools.ietf.org/html/rfc8187)
- Corrected issue using custom HTTP headers with RXSURI which could result in the custom headers not being sent


### CGI APIs 
- Corrected issue with RXS_GetUrlVar() that could cause a retrieved URL variable to be truncated instead of retrieved fully


### Code Generation 
- Corrected issue with CRTRPGTPL resulting in a 3029 error when attempting to use a source member for the input instead of an IFS file


### Other Changes 
- Corrected issues with internal licensing code which could cause errors when running on partitions with an LPAR ID above 100


## 3.4.5

### JSON APIs 
- Corrected issue with RXS_ComposeJsonNumber() handling of decimal values that failed to include zeros before the decimal point
- [New] Added new error message KTA120C to RXS_ComposeJsonNumber() to more clearly indicate when a provided input value was unable to be parsed as a valid JSON number


### CGI APIs 
- Corrected issue with RXS_PutStdOut() that could cause a "Pointer not set for location referenced" error to occur when providing the optional data structure parameter. This issue was introduced as part of changes made in RXS 3.4.3


### Other Changes 
- Removed internal license checks from commonly used subprocedures RXS_STR(), RXS_GetJobCCSID(), and RXS_JobLog() to improve overall performance


## 3.4.4

### JSON APIs 
- Corrected issue with using TrimVariables where values composed to child objects/arrays would not be properly trimmed


### RXS_Crypt() 
- Corrected issue with RXS_Crypt() silently failing when not provided with an algorithm when performing SHA hashing. Error KTA0308 will now be thrown


## 3.4.3
### HTTP Communication - RXS_Transmit() 
- Enhanced RXS_TransmitDS_t data structure subfield array CustomHeaderValue to increase the size from 1KB to 4KB to allow for longer custom headers when calling RXS_Transmit()
- Enhanced RXS_HTTPResponseDS_t data structure subfield array HeaderValue to increase the size from 1KB to 4KB to allow retrieving longer headers from the response
- Updated RXS_TransmitDS_t data structure subfield array HeaderCookieFiles to decrease the number of array elements from 50 to 10. This change was based on customer feedback and should help reduce total memory usage


### JSON APIs 
- Updated RXS_ComposeJsonBoolean() to properly treat values other than RXS_JSON_TRUE or RXS_JSON_FALSE as invalid input rather than treating anything other than RXS_JSON_TRUE as 'false'. A KTA120B error will now be thrown when invalid input is provided to RXS_ComposeJsonBoolean()


### Template Variables and Constants 
- [New] Added new RXS_Var4K_t varying 4KB template variable to RXSCB
- [New] Added new RXS_Var1M_t non-varying 1MB template variable to RXSCB


### Other Changes 
- Modified RXS_ResetDS() to increase parameter size to allow for larger data structures
- Corrected text description of `-prdlib` flag in WRKRXSRTRE help text


## 3.4.2
### HTTP Communication - RXS_Transmit() 
- Enhanced RXS_TransmitDS_t data structure with new subfield FollowRedirects to control if RXS_Transmit() will automatically follow HTTP 3XX status code redirects. The default value is `RXS_YES` to maintain compatibility with prior releases
- Enhanced RXS_Transmit() logfile output to indicate that SSLCertStore will use the *SYSTEM certificate store by default


### JSON APIs 
- Enhanced RXS_CreateJsonDS_t with new subfield TrimVariables to control if RXS_ComposeJsonString() will trim excess whitespace from composed JSON values. The default value is `RXS_NO` to maintain compatibility with prior releases
- Corrected issues that could occur when passing JSON names as fields instead of literals when composing JSON
- Corrected issue with RXS_CreateJson() that could cause an `KTA120A: RXS_CreateJson() was provided an invalid data structure parameter` error when using `/DEFINE RXSV6R1`


### STMF APIs 
- Corrected issue with RXS_PutStmf() silently failing when not provided with a filepath


### XML Composition APIs 
- [New] Added RXS_GetComposeBufferLen() API based on customer feedback. This API can be used to retrieve the current length of the compose buffer similar to the functionality of RXS_GetJsonStringLen()


### Other Changes 
- Corrected text description of PORT parameter on CRTRXSSVR command


## 3.4.1
### RXS Router 
- Corrected issues preventing RXS Router `-passwd` flag from working correctly when the QPWDLVL system value was set to 2 or 3
- Enhanced RXS Router `-passwd` flag to support passwords up to 128 characters and removed character restrictions
- Enhanced `-passwd` flag for RXS Router to support `*NOPWD`, `*NOPWDSTS`, and `*NOPWDCHK` to allow profile swapping to IBM i user profiles with their password set to `*NONE`
- Enhanced RXS Router to send a message to the CGI joblog on start to show which version of RPG-XML Suite is being used
- Enhanced RXS Router debug report page to show which version of RPG-XML Suite is being used


### HTTP Communication - RXS_Transmit() and RXS_getUri() 
- Updated RXSURI command to internally call RXS_Transmit() instead of RXS_getUri(). The log file generated by RXSURI now reflects the RXS_Transmit() log format as a result
- Updated default values specified in the RXSURIP file used by the RXSURI command. Requests will now use HTTP 1.1 and CCSID 1208 by default. These values replace the defaults of HTTP 1.0 and CCSID 819 used in previous versions
- Corrected issue which could cause intermittent SSL timeout errors to appear in the RXS_Transmit() log file in rare cases


### RXS_Crypt() 
- [New] Added support for AES-128, AES-192, and AES-256 encryption to RXS_Crypt()


### RXS_Convert() 
- Corrected issue with unexpected output when using RXS_Convert() to perform URL percent encoding where multibyte characters were present
- Enhanced RXS_ConvertURLPercentDS_t with two new subfields, InputCCSID and OutputCCSID, both of which are defaulted to the CCSID of the current job
- Corrected issue which could occur rarely when using RXS_Convert() to perform CCSID conversion with empty input


### JSON APIs 
- Corrected issue with how the JSON compose APIs handle CCSID conversion of some non-printable characters to UTF-8, which could result in incorrect output of multibyte characters after conversion


### XML Composition APIs 
- Corrected issue introduced in 3.4.0 which could cause RXS_GetComposeBuffer() to retain previously retrieved composed data


### XML DOM Parsing APIs 
- Corrected issue with the RXS3 DOM APIs which could prevent loading XML documents larger than 4MB


### XML Event Parsing APIs 
- Corrected issue introduced in 3.4.0 which caused RXS2 RXS_parse() error messages to be partially returned in an incorrect CCSID



## 3.4.0
- **The minimum supported operating system version is now IBM i 7.1**
- RPG-XML Suite's version number now uses the format MAJOR.MINOR.PATCH to be more in line with commonly used semantic versioning standards
- The RXS 2 and RXS 3 APIs are no longer separately versioned as they were in previous releases. Output provided by the DSPVER and DSPMCHINF commands as well as the VERRXSBASE program will now only report a single version number


### Example Programs 
- Example source code for the RXS 2 APIs is no longer provided in the included EXAMPLE source physical file. We recommend all new applications are written using the RXS 3 APIs. Example code for the RXS 3 APIs has been moved to the EXAMPLE source physical file
- Example programs are no longer provided in a compiled state during the installation. This change was made based on customer feedback


### RXS 2 Copybook 
- The RXSCP copybook containing the RXS 2 API prototypes, data structures, and constants which was previously located in the included RXS source physical file has been moved to the included QRPGLECPY source physical file. Existing programs using the RXS 2 APIs will continue to work without modifications, but before any future recompilation you will need to change the `/copy RXS,RXSCP` line in your programs to be `/copy QRPGLECPY,RXSCP`


### HTTP Communication - RXS_Transmit() and RXS_getUri() 
- Changed RXS_Transmit() and RXS_getUri() to use the IBM GSKit SSL APIs for SSL/TLS communication. This allows for SNI support and other improved capabilities. Note that this may cause RXS_TransmitDS_t.SSLVerifyPeer and RXS_TransmitDS_t.SSLVerifyHost subfields to no longer ignore missing/invalid SSL certificates in all circumstances, and you may begin receiving errors as a result after upgrading. If you do, you should [install the SSL certificates](https://isupport.katointegrations.com/rxs/installing_certificate_authorities.md) for the endpoint you are calling
- Corrected issues with RXS_Transmit() which could cause a generated log file to be empty
- Enhanced the RXS_TransmitDS_t data structure to include a new LocalInterface subfield. LocalInterface can be set to an IP address. When set to an IP address, RXS_Transmit() will use this value to determine the proper network interface to use.
- Enhanced the RXS_TransmitDS_t data structure to include a new EnableIPv6 subfield. EnableIPv6 can be set to RXS_YES or RXS_NO (default: RXS_NO). When set to RXS_YES this option will allow RXS_Transmit() to resolve a host name to either the IPv4 or IPv6 address instead of being limited to only resolving to the IPv4 address.
- Corrected issue with RXS_Transmit() when performing a HTTP DELETE operation
- Corrected issue preventing RXSURI command from properly sending custom HTTP headers in some circumstances


### JSON APIs 
- Enhanced JSON parsing and composing API internal code, which corrects a number of issues with memory usage and CCSID handling as well as JVM conflicts. Any existing programs that are parsing or composing JSON should perform substantially better
- Previously, the JSON APIs relied on files in the IFS stored in a /krengeltech directory. This directory is no longer required starting with RXS 3.4.0 and can be safely removed
- Calling RXS_DestroyJson() is no longer required after RXS_ParseJson(). RXS_DestroyJson() now only needs to be called on the RXS_CreateJsonDS_t that was originally used in a call to RXS_CreateJson(). Existing code which calls RXS_DestroyJson() after RXS_ParseJson() will continue to function without any changes or recompilation required, but we recommend reviewing your code to remove any unnecessary RXS_DestroyJson() calls
- [New] Added RXS_FormatJson() API based on customer feedback. This API can be used to format a JSON document or STMF to either minify the document, or to expand it and control indentation and whitespace
- [New] Added RXS_GetJsonStringLen() API based on customer feedback. This API will return the length of character field needed to store the JSON document composed in the provided RXS_CreateJsonDS_t
- Enhanced JSON APIs to support duplicate keys within objects per JSON RFC 7159
- Enhanced RXS_ComposeJsonString() to allow *Omit for parameter 2. *Omit is treated as equivalent to passing a 0 length empty string
- Enhanced error messages for all RXS JSON composition and parsing APIs to provide more detail
- Enhanced validation for RXS_ParseJson() settings. As a result, programs that are calling RXS_ParseJson() without specifying a parsing handler will now throw error message KTA1207
- Enhanced validation for input provided to RXS_ParseJson(). As a result, programs that are calling RXS_ParseJson() and providing an empty JSON document will now throw error message KTA1208
- Corrected issue when using RXS_ParseJson() to parse a JSON document which contained empty child JSON objects


### XML Composition APIs 
- Enhanced the RXS_ComposeDS_t data structure to include a new TrimVariables subfield. TrimVariables can be set to RXS_YES or RXS_NO (default: RXS_NO). When set to RXS_YES this option will cause RXS_ComposeVariable() to remove all leading and trailing whitespace from the passed value equivalent to if the value had been wrapped by the RPG %Trim() built-in function
- Enhanced the RXS_ComposeDS_t data structure to include a new EncodeVariables subfield. EncodeVariables can be set to RXS_YES or RXS_NO (default: RXS_NO). When set to RXS_YES this option will cause RXS_ComposeVariable() to encode all XML reserved characters (`&` `'` `"` `>` `<`) with the appropriate entities (`&amp;` `&apos;` `&quot;` `&gt;` `&lt;`) in the passed value
- Enhanced RXS_ComposeVariable() to allow `*Omit` to be passed for parameter 2. `*Omit` is treated as equivalent to passing a 0 length empty string
- Corrected issue preventing RXS_ComposeVariable(), RXS_ComposeSection(), and RXS_GetComposeBuffer() from throwing an error when RXS_StartComposeEngine() had not previously been called


### RXS_Convert() 
- Corrected issue when using RXS_Convert() to encode XML entities that could lead to a memory leak in certain scenarios
- Corrected issue causing RXS_Convert() to throw an error when provided with an empty character field for input


### RXS_Crypt() 
- Corrected issue preventing `*Omit` from being used as the first parameter for RXS_Crypt()
- Corrected issue with RXS_Crypt() MD5 and SHA hashing memory usage and IFS file access


### Code Generation 
- Enhanced the BLDPRS command to support generating RXS 2 XML parsing code, RXS 3 XML parsing code, or JSON parsing code. BLDPRS can now also generate either mixed or column-limited full free format RPG. As part of this update, the names of some existing parameters were changed and new parameters were added
- Enhanced the BLDTPL command to provide better CCSID handling and performance. As part of this update, the names for some parameters have been changed
- Enhanced the BLDTPL command to no longer remove the XML prolog during template generation based on customer feedback
- Enhanced the CRTRPGTPL command to write a comment section in the generated template member that contains the full command used to generate the template for future reference


### Other Changes 
- [New] Added RXS_RenameStmfDS_t as option to RXS_ProcessStmf() to allow for easier IFS file renaming
- Corrected intermittent error message caused by RXSRTR appearing in Apache job log
- Corrected issue preventing RXS_GetUrlVar() from handling empty URL variables properly
- All included commands now provide help via included *PNLGRP objects


## 3.36
- Internal changes to allow initial SNI support.


## 3.35
- Corrected an issue with ScriptAliasMatch directives when building a new httpd.conf file.


## 3.34
- RXS_Convert() now works properly when URL encoding/decoding strings over 64K


## 3.33
- RXS_GetJsonString() now works properly in all situations with JSON prettification disabled.
- RXS_RESETDS() now works correctly with RXS_CreateJsonDS_t, RXS_JsonStructureDS_t, RXS_ParseJsonDS_t
- RXS_GetEnvVar() now correctly returns an empty string for variables which do not exist
- RXS_Parse() no longer requires programs compiled under v3.31 or earlier to be recompiled to avoid a decimal data error. This affected only programs running under v3.32 after last being compiled with v3.31 or earlier.


## 3.32
- RXS_ResetDS() now supports: RXS_DS_TYPE_CREATEJSON, RXS_DS_TYPE_JSONSTRUCTURE and RXS_DS_TYPE_PARSEJSON
- RXS_GetStdIn() now properly handles binary output to a stream file
- Performance enhancements to RXS_Parse()
- Performance enhancements to RXS_ComposeVariable() and RXS_ComposeSection()
- Improvements to RXS_OpenDom() to prevent XML declarations from conflicting with the DOM parser's internal character set handling
- Improvements to error handling in RXS_Throw() and RXS_Catch()
- Improved memory handling and performance in RXS_Convert()
- Improvements to error reporting in RXS_OpenDom(), RXS_Parse() and RXS_Convert()
- Improvements to RXS_GetUrlVar() and RXS_GetStdIn() when processing URL encoded content
- Improved BLDTPL to output variable names that conform to RPG's naming rules
- Performance enhancements to speed licensing functions
- RXS_ParseJson() prototype changed to use Const instead of Value. **All programs parsing JSON will need to be updated and recompiled.**


## 3.31
- Improved error reporting when parsing a JSON string with incorrect syntax
- Improved error reporting when processing JSON without authority to required IFS objects
- Correction to RXS_ComposeJsonBoolean() so that composed values are correct
- Improved memory handling when composing very large XML strings
- Improved performance when parsing large stream files
- Improved support for calling non-CGI programs from RXSRTR function


## 3.30
- [New] Added JSON parsing API [RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.3/rxs_parsejson.md)
- [New] Added JSON composition APIs:
  - [RXS_CreateJson()](https://isupport.katointegrations.com/rxs/3.3/rxs_createjson.md)
  - [RXS_DestroyJson()](https://isupport.katointegrations.com/rxs/3.3/rxs_destroyjson.md)
  - [RXS_ComposeJsonNull()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonnull.md)
  - [RXS_ComposeJsonString()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonstring.md)
  - [RXS_ComposeJsonBoolean()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonboolean.md)
  - [RXS_ComposeJsonNumber()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonnumber.md)
  - [RXS_ComposeJsonObject()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonobject.md)
  - [RXS_ComposeJsonArray()](https://isupport.katointegrations.com/rxs/3.3/rxs_composejsonarray.md)
  - [RXS_GetJsonString()](https://isupport.katointegrations.com/rxs/3.3/rxs_getjsonstring.md)


## 3.22
- Corrected a problem with multi-byte characters in request data causing a false timeout condition when calling RXS_Transmit()
- Corrected a problem with multi-byte characters in XML data causing RXS_Validate() to fail when it should have been successful
- Changes to the Compose engine to correctly handle repeating data when using the OmitUncomposedLines option with multiple calls to RXS_ComposeSection()
- Improvement to RXS_ComposeVariable() to allow `*BLANKS` to be passed on the second parameter, without composing 16MB's of blanks/spaces and instead the composed content will be the same as if a zero length value had been passed


## 3.21
- Corrected an issue that required programs using RXS_Transmit() to be recompiled. Recompiling is no longer needed
- Improvements to CRTRPGTPL to better handle templates that have invalid formatting or characters that are not allowed in RPG variables names


## 3.20
- RESTful HTTP Methods such as PUT, DELETE, HEAD, OPTIONS and PATCH (POST and GET continue to be available)
- Easy parsing of content from RESTful URI’s such as http://www.example.com/customer/53874
- Support for dynamic as well as compiled templates
- Extended length templates up to 256K
- Automatic compression of whitespace when composing from templates
- Automatic omission of uncomposed template content
- New function RXS_ResetDS() for easily initializing data structures used by the RXS3 API
- Enhanced RXS_GetStmf() to allow easy reading of stream file content in “chunks” within a loop
- Enhanced RXS_PutStdOut() for easy output of HTTP Status, Content Type and other HTTP headers
- Extended support for very large XML stream files when using the event based parser
- Improvements to CRTRPGTPL command
- Read STDIN over 16MB directly to a stream file
- Output large stream files over 16MB in size to STDOUT
- Improved logging from RXS_Transmit()
- Improved RXS_Transmit() when using Basic Authority so that only one request is made to the remote server
- Improved STMF handling to inherit authority from the host directory when creating new STMFs


## 3.11
- Introduced [RXS_Validate()](https://isupport.katointegrations.com/rxs/3.1/rxs_validate.md) for use with XSD Validation


## 3.10
- Full support for XPath 1.0 compliant Parsing
- [New] Added DOM parsing APIs:
  - [RXS_OpenDom()](https://isupport.katointegrations.com/rxs/3.1/rxs_opendom.md)
  - [RXS_CloseDom()](https://isupport.katointegrations.com/rxs/3.1/rxs_closedom.md)
  - [RXS_ParseDomToText()](https://isupport.katointegrations.com/rxs/3.1/rxs_parsedomtotext.md)
  - [RXS_ParseDomToXml()](https://isupport.katointegrations.com/rxs/3.1/rxs_parsedomtoxml.md)
  - [RXS_ParseDomToDom()](https://isupport.katointegrations.com/rxs/3.1/rxs_parsedomtodom.md)


## 3.00
- **The minimum supported operating system is now IBM i 6.1**
- Completely new RXS3 API introduced. This API has been redesigned from the ground up and hands the RPG developer an even more powerful tool.
- Full support for 16 MB in-memory variables
- Base 64 encoding
- Full UTF-8 support and improved CCSID handling
- Enhanced data security


## 2.88
- Improved compatability with proxy configuration in previous versions.
- Improved availability of joblog messages in CGI jobs running via RXSRTR and also using activation groups


## 2.87
- Expanded number of variables allowed in one section of a template
- Allowing only IPv4 when using RXS_getUri(). IPv6 will require use of RXS_Transmit()


## 2.86
- Resumed support for LocalIP and LocalPort settings when calling RXS_getUri()
- Improved backwards compatability when using RXS_getUri() with HTTP Get method


## 2.85
- Improved support for conversion of XML entities during parsing


## 2.84
- Improved parsing of content that contains XML entities in combination with multi-byte character sets such as UTF-8
- New options for RXS_getUri() that allow host and peer verification to be turned off


## 2.83
- Improved STMF handling to inherit authority from the host directory when creating new STMF's
- Improved RXS_getUri() to handle URI's that do not start with http or https
- Improved RXS_getUri() when using Basic Authority so that only one request is made to the remote server


## 2.82
- Improvements to CCSID handling when outputting to existing stream files with multi-byte character sets


## 2.81
- Correction to template engine when outputing stream files encoded as UTF-8 or other multi-byte character sets


## 2.80
- Enhancements to BLDPRS command
- Ignore default namespaces in DOM parser unless DOM_INCNSATR set as an option
- Updates to HTTP client to correct output to debug files when multiple communications are transmitted
- Created RXSMENU, DSPVER, DSPLIC and APYLIC commands for menu driven version and licensing tasks
- Seperate licensing library KTLIC no longer required


## 2.71
- Enhancement to allow multiple members in RXSCFG to allow member names to reflect RXSRTR environment names when desired. (The RXSCFG member is the default when RXSRTR is not in use or a member matching the environment name is not found.)
- When creating Apache instances, changed logs directory authority to `*RWX`
- Licensing updates for certain IBM serial numbers and for BLDTPL and BLDPRS commands


## 2.70
- **The minimum supported operating system is now IBM i V5R4**
- Enhancements to RXS Router allowing single library installation for simplified configuration and streamlined upgrades.
- Improved CCSID handling to support character sets outside of North America
- Various performance improvements and bug fixes
- New licensing system
- New [RXS_getUri()](https://isupport.katointegrations.com/rxs/2.x/rxs_geturi.md) communications client


## 2.51
- Correction to allow case insensitive XPath's when using the DOM parser


## 2.50
- Enhancements to RXS Router allowing single library installation for simplified configuration and streamlined upgrades. Improved CCSID handling to support character sets outside of North America


## 2.40
- Fixed bug in DOM parser to intialize storage used in previous parses
- Improved limits in DOM parser allowing increased element count in parsed XML
- Enhanced RXS Router to allow mixed case for RXSRTRCTL option switches
- Fixed bug in [RXS_getUrlVar()](https://isupport.katointegrations.com/rxs/2.x/rxs_geturlvar.md) to allow encoded ampersands to be decoded correctly (%26)
- Enhancement to [RXS_getUrlVar()](https://isupport.katointegrations.com/rxs/2.x/rxs_geturlvar.md) to decode percent encoded characters correctly when CGIConvMode is set to MIXED
- Fixed parser bug to allow UTF-8 double-byte characters in attribute data


## 2.30
- Fixed bug in [RXS_getUrlVar()](https://isupport.katointegrations.com/rxs/2.x/rxs_geturlvar.md) when the last variable's value in QUERY_STRING was a single character.
- Fixed bug where [RXS_getUrlVar()](https://isupport.katointegrations.com/rxs/2.x/rxs_geturlvar.md) was returning the **&** char in the event of an empty QUERY_STRING variable.


## 2.20
- Enhancement: Options used when calling [RXS_DOMSetOpt()](https://isupport.katointegrations.com/rxs/2.x/rxs_domsetopt.md) or [RXS_DOMBuild()](https://isupport.katointegrations.com/rxs/2.x/rxs_dombuild.md) have been simplified.
- Fixed bug: Modified the template engine to ignore other forms of whitespace at the end of sections names. Previously, if invisible characters existed due to copying template code from Word or PDF documents, the section name would not be detected properly and would require the template to be modified.
- Modified RXS Router added in v2.10 to exist in base RXS library, rather than a separate library.
- Modified RXS Router command INZRXSRTR to be executable by users with authority to RXSRTRCTL and INZRXSRTR.
- Modified RXS Router httpd.conf to allow multiple types of CGI invocations. This allows you to configure URLs which bypass the router if needed.
- Fixed bug where DOM Parser functions [RXS_DOMGetData()](https://isupport.katointegrations.com/rxs/2.x/rxs_domgetdata.md) and [RXS_DOMGetDataCount()](https://isupport.katointegrations.com/rxs/2.x/rxs_domgetdatacount.md) would return incorrect values when using repeating elements.
- Fixed bug where sometimes during install, data area RXSBASE was not being given appropriate authority, and would falsely report that an RPG-XML Suite license key was invalid immediately after installation.


## 2.10
- NEW FEATURE: Added new "RPG-XML Suite Router" functionality. This will ease the offering of web services as it takes care of altering the library list and user profile before your web service is invoked.
- NEW FEATURE: RXS HTTP Command to make it easier, and safer, to end, start, and configure your Apache server instances.
- Enhancement: When processing template files (i.e. IFS .tpl files), the section names will have `*Blanks` trimmed from the end of their name. Previously, if blanks existed, the template engine wouldn't consider the name and the .tpl would have to be modified.
- Enhancement: Allow [RXS_getEnvVar()](https://isupport.katointegrations.com/rxs/2.x/rxs_getenvvar.md) to be called multiple times. Normally in the case of GET based web services there has been a single URL parameter named "xml" that would contain the inbound XML. With RESTFul web services making a mark in the web services world we have expanded the RXS_getEnvVar() API to facilitate multiple calls for differently named variables.
- Enhancement: Traditionally when there were unmatched delimiters in a template file (i.e. IFS .tpl files) it would throw a MCH0603 error. Now a more informative message will be relayed to the job log.
- Enhancement: Speed improvements have been made in the RXS_DOM* based parsing API's.


## 2.00
- NEW FEATURE: Added DOM features for simplified parsing of XML documents. New APIs include:
  - [RXS_DOMBuild()](https://isupport.katointegrations.com/rxs/2.x/rxs_dombuild.md)
  - [RXS_DOMGetData()](https://isupport.katointegrations.com/rxs/2.x/rxs_domgetdata.md)
  - [RXS_DOMGetDataCount()](https://isupport.katointegrations.com/rxs/2.x/rxs_domgetdatacount.md)
  - [RXS_DOMCleanup()](https://isupport.katointegrations.com/rxs/2.x/rxs_domcleanup.md)
  - [RXS_setXPath()](https://isupport.katointegrations.com/rxs/2.x/rxs_setxpath.md)
  - [RXS_getXPath()](https://isupport.katointegrations.com/rxs/2.x/rxs_getxpath.md)


## 1.42
- Fixed bug where RXSCFG was being read for each call to RXS_getUri() or RXS_initTplEng() and now it is just called the first time in each job and then the data is cached to better performance.
- Fixed bug that was causing issues when RXS was used in conjunction with DDM files or RPG being front ended by stored procedures. This was a bug that affected few customers in very specific usage scenarios.


## 1.41
- Modified RXS_getUri() to use 65535 VARYING vs. 65535 without VARYING. This addresses issues when trying to pass by reference and not having the exact same string type.
- RXSCP fields RXS_getUriOut, RXS_getUriHead, and RXS_getUriData have been deprecated and commented out. They will be physically removed from RXSCP in v1.5. If you had version 1.4 installed you will need to recompile your programs to use this latest version.
- Modified RXS_getUri() to default to port 443 if port is 0 and SSL=RXS_YES was specified.


## 1.40
- NEW FEATURE: RXS_setParseEnc()
- NEW FEATURE: RXS_ignElemNamSpc()
- Modified commands BLDPRS and BLDTPL to better handle long XPaths
- Modified default delimiters to be `::` for section names and `.:var:.` for variable names. In previous versions they were `/$` for section names and `/%var%/` for variable names. This was changed for variety of reasons with the biggest being usage in foreign countries. By using colons and periods the code can be typed faster for manual modification of templates. If you are upgrading you should do an UPDDTA RXSCFG and change the defaults to `/$` for section begin delimiter, `/%` for var begin delimiter and `%/` for var end delimiter.


## 1.30
- NEW FEATURE: Added RXS_addLibLE()
- NEW FEATURE: Added RXS_libLEExists()
- NEW FEATURE: Added RXS_rmvLibLE()
- NEW FEATURE: Added RXS_getBuffLen() to get a count of bytes in the Template Engine buffer to know if it is over 65535 and thus to big to retrieve with RXS_getBuffData().
- NEW FEATURE: Added RXS_getBuffData() to get the Template Engine data that is currently buffered so it can be put into a 65535 VARYING field and used on RXS_getUri().
- Changed HTTPD.txt to not use $ signs (also changed NEWENV command)
- Fixed bugs in BLDPRS that were making it not work right with long xPaths.
- [New] Added delim fields to RXSCFG
- Changed RXS_initTplEng() to operate off of default delimiters in RXSCFG and change default delimiters in program to not be constants.
- Changed RXS_updVar() to be able to do 65535 varying, was set at 1024 at an inner level of code.
- Enhancing RXS_soapDecode() for speed and accuracy.
- Adding code to BLDPRSR and BLDTPLR to not delete user index unless it exists. It was putting messages in job log uncessarily
- [New] Added DftTransDir to RXS_readToFile() if no path was specified.
- [New] Added DftTransDir to RXS_outFromFile() if no path was specified.
- [New] Added DftTransDir to RXS_deleteFile() if no path was specified.
- When using RXS_readToFile() extra spaces were sometimes added to the end of the files contents. This has been addressed to not have additional spaces.
- Change error text in RXS_cmpTransFile() to be RXSCFG instead of CONFIG.
- [New] Added defaults for RXS_getUri(): This will save typing when using RXS_getUri() as not as many data structure fields will need to be filled.
  - ds.ReqType=RXS_POST
  - ds.SprHead=RXS_YES
  - ds.Debug=RXS_NO
  - ds.RspType=RXS_VAR
  - ds.ReqType=RXS_VAR
  - ds.ContType='text/xml'
- When a POST with zero content is sent and RXS_readToFile() is used an error was inappropriately thrown. Now no error is thrown.
- Changed RXS_readToFile() to truncate content on IFS open.
- Changed RXS_getUriOut and RXS_getUriHead from 32767 to 65535 in RXSCP (main RXS copybook)
- [New] Added field RXS_getUriData to RXSCP in relation to new RXS_getUri() functionality
- Changed RXS_getUri() to be able to send and receive 65535 bytes of data if not using IFS files. Previous limitation was 2048.
- [New] Added new OutType of RXS_VAR to RXS_initTplEng(). This should be used in conjunction with RXS_getBuffData().
- **!!RECOMPILE!!** Changed RXS_getUri() to have the second, third and newly added forth parms as `OPTIONS(*OMIT)` for ease of use (so you don't have to specify them if you aren't using them)
- [New] Added example TPLENG3 to show how to use RXS_getBuffData().
- The field RXS_GetUriHead (i.e. response HTTP Headers) will always be returned when a variable is specified vs. only being returned when SprHead (Seperate Headers) is specified. This pertains to RXS_getUri().
- Modified client app Web Service Tester to save responses to a stream file and retain URLs that have been accessed.


## 1.20
- Renamed RPG-XML Suite config PF, MYRXS/CONFIG, to MYRXS/RXSCFG.
- Changed RXS_parse() to `%TRIM` input file name
- Changed RXS_parse() to throw an error if file to parse doesn't exist.
- [New] Added RXS_soapDecode()
- Increased RXS_updVar() input to 65535.
- Changed RXS_initTplEng() to `%TRIM` out file name
- [New] Added example GETURI4 to show Template Engine overriding of sections/variables and also ability to override event handler type values.
- [New] Added RXS_charToTimestamp()
- [New] Added RXS_timestampToChar()
- [New] Added RXS_charToBln()
- Modified RXS_charToNbr() to have default value parameter.
- [New] Added parsing code generator BLDPRS(Build RPG Parsing Subprocedure)
- [New] Added template generator BLDTPL (Build Template)
- [New] Added RXS_getUri() PUser and PPW (Proxy User and Proxy Password)
- [New] Added DSPMCHINF for ease of displaying machine information.


## 1.10
- Changed RXS_loadTpl() to allow overriding abilities for section and variable delimiters. For internationalization purposes.
- Changed RXS_parse() to allow the passing of handler event type value overrides (i.e. ELEMBEGIN, ELEMCONTENT, ELEMEND, ATTR). For internationalization purposes.
- [New] Added example RXS6 to show Template Engine overriding of sections/variables and also ability to override event handler type values.
- Modified install *SAVF to be V5R1 instead of V5R3


## 1.00
- Initial release.

---

# RXS_GetEnvVar()

> Retrieves Apache environment variables and HTTP request headers for use in hosted web service programs.

This subprocedure is used to retrieve Apache server's environment variables as well as some incoming HTTP headers. This is typically used in a scenario when you are hosting a web service.

- AUTH_TYPE
- CGI_ASCII_CCSID
- CGI_EBCDIC_CCSID
- CONTENT_LENGTH
- CONTENT_TYPE
- GATEWAY_INTERFACE
- HTTP_ACCEPT
- HTTP_USER_AGENT
- PATH_INFO
- PATH_TRANSLATED
- QUERY_STRING
- REMOTE_ADDR
- REMOTE_HOST
- REMOTE_IDENT
- REQUEST_METHOD
- REMOTE_USER
- SCRIPT_NAME
- SERVER_NAME
- SERVER_PORT
- SERVER_PROTOCOL
- SERVER_SOFTWARE

This subprocedure can also be used to retrieve environment variables set with [RXS_PutEnvVar()](https://isupport.katointegrations.com/rxs/3.5/rxs_putenvvar.md).

## Subprocedure Prototype

```rpgle
      // Returns the data retrieved from the environment variable.
     D RXS_GetEnvVar...
     D                 PR                  Extproc('RXS_GetEnvVar') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Specify the name of the environment variable to retrieve the value of.
     D  pEnvVar                      30A   Value
```


## Example Code

### Ex. 1: Retrieving an Environment Variable

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves an environment variable. 
      * The environment variable being retrieved, REQUEST_METHOD, would 
      * be set by the Apache server, which means that it can only be 
      * retrieved if this program is used as a web service.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D Data            S                   Like(RXS_Var1Kv_t)

      /free
       Data = RXS_GetEnvVar( 'REQUEST_METHOD' ); 
       RXS_JobLog( 'REQUEST METHOD? %s' : Data );
       *INLR = *ON;
      /end-free
```


---

# RXS_GetStdIn()

> Reads the HTTP request body (stdin) into an RPG variable; used to receive POST data in hosted web service programs.

This subprocedure is used to read data sent to you via HTTP POST into a field up to 16MB, or into an IFS STMF file.

This is typically used when you are offering a web service.

## Subprocedure Prototype

```rpgle
      // Returns the data retrieved from STDIN unless a STMF was specified in a
      //   RXS_GetStdInDS_t data structure passed in the DS parameter.
     D RXS_GetStdIn...
     D                 PR                  Extproc('RXS_GetStdIn') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Optional RXS_GetStdInDS_t data structure used to convert received data
      //   to a specified CCSID.
     D  pDS                                LikeDS(RXS_GetStdInDS_t)
     D                                     Options(*Varsize:*Nopass)
```


## Example Code

### Ex. 1: Read STDIN To Character Field

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves data from standard in and stores it
      *  in the field gXmlRequest. 
      * This process might be used when offering a web service.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXmlRequest     S                   Like(RXS_Var64Kv_t)
      /free
       gXmlRequest = RXS_GetStdIn();

       *INLR = *ON;
      /end-free
```

### Ex. 2: Read STDIN To IFS STMF

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves data from STDIN and write it 
      *  to the specified IFS file.
      * This process might be used when offering a web service.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /define RXSV6R1
      /copy QRPGLECPY,RXSCB

     D GetStdInDS      DS                  LikeDS(RXS_GetStdInDS_t)
      /free
       RXS_ResetDS( GetStdInDS : RXS_DS_TYPE_GETSTDIN ); 
       GetStdInDS.Stmf = '/tmp/over16mb.xml';
       RXS_GetStdIn(GetStdInDS);

       *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_GetStdInDS_t

```rpgle
     D RXS_GetStdInDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_GETSTDIN)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID to convert the inbound data into.
     D   Ccsid                       10I 0

      // Specifies the STMF to load with the contents of STDIN.
     D   Stmf                              Like(RXS_Var1Kv_t)
```



---

# RXS_GetUrlVar()

> Extracts a named variable from the URL query string; used to read GET parameters in hosted web service programs.

This subprocedure is used to retrieve variables from the URL that was used to call the current program as a web service.

Variables on a URL are specified in name-value pairs. For example:

`http://mysite.com/rxs/mywebservice?customer=1&code=3`

In this example, there are two variables: **customer** and **code**, and each of them has a value of 1 and 3 respectively.

**Note:** This API is only meant to be used in a program acting as a webservice. Using it in other programs will throw errors.

## Subprocedure Prototype

```rpgle
      // Returns the data retrieved from the URL variable.
     D RXS_GetUrlVar...
     D                 PR                  Extproc('RXS_GetUrlVar') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Specify the name of the URL variable which is to be retrieved.
     D   UrlVar                     300A   Value

     D   Input                             Like(RXS_Var16Mv_t)
     D                                     Options(*Nopass:*Varsize)
```


## Example Code

### Ex. 1: Retrieve Values from the Querystring of a URL

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves variable values from the URL that 
      * was used to call this program.
      * For example, if this program were called with the following URL:
      * http://mysite.com/rxs/mywebservice?customer=1&code=3
      * The output in the job log would be:
      * Customer: 1
      * Code: 3
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /define RXSV6R1
      /copy QRPGLECPY,RXSCB

     D VarCustomer     S                   Like(RXS_Var1Kv_t)
     D VarCode         S                   Like(RXS_Var1Kv_t)

      /free

       VarCustomer = RXS_GetUrlVar( 'customer' );

       VarCode = RXS_GetUrlVar( 'code' );

       RXS_JobLog( 'Customer: %s' : VarCustomer ); 
       RXS_JobLog( 'Code: %s' : VarCode ); 

       *INLR = *ON;
      /end-free
```

### Ex. 2: Retrieve Values from the Path of a URL

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves variable values from the URL that 
      * was used to call this program.
      * For example, if this program were called with the following URL:
      * http://mysite.com/rxs/mywebservice/customer/15/code/43
      * The output in the job log would be:
      * Customer: 5
      * Code: 43
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /define RXSV6R1
      /copy QRPGLECPY,RXSCB

     D VarCustomer     S                   Like(RXS_Var1Kv_t)
     D VarCode         S                   Like(RXS_Var1Kv_t)

      /free

       VarCustomer = RXS_GetUrlVar( 'customer/' );

       VarCode = RXS_GetUrlVar( 'code/' );

       RXS_JobLog( 'Customer: %s' : VarCustomer );
       RXS_JobLog( 'Code: %s' : VarCode );

       *INLR = *ON;
      /end-free
```


---

# RXS_PutEnvVar()

> Sets an Apache environment variable from within an RPG program.

This subprocedure is used to set a job-level environment variable, or to modify an already existing environment variable.

## Subprocedure Prototype

```rpgle
     D RXS_PutEnvVar...
     D                 PR                  Extproc('RXS_PutEnvVar') Opdesc

      // Specifies the environment variable to be set. Must be in name=value
      //   format.
     D  pEnvVar                            Like(RXS_Var16Mv_t) Const
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Setting an Environment Variable

```rpgle
      *--------------------------------------------------------------
      * This example code sets an environment variable. You can view 
      * the results by running the following command:
      *  WRKENVVAR
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D EnvVar          S                   Like(RXS_Var1Kv_t)
     D Data            S                   Like(RXS_Var1Kv_t)

      /free

       EnvVar = 'IS_RPG_AWESOME=Yes';
       RXS_PutEnvVar( EnvVar );
       *INLR = *ON;
      /end-free
```


---

# RXS_PutStdOut()

> Writes data to the HTTP response body (stdout); used to send output from hosted web service programs.

This subprocedure is used to output (to standard out) data. Typically, this would be used in a scenario where you are providing a webservice, and would be used to output XML retrieved via [RXS_GetComposeBuffer()](https://isupport.katointegrations.com/rxs/3.5/rxs_getcomposebuffer.md).

## Subprocedure Prototype

```rpgle
     D RXS_PutStdOut...
     D                 PR                  Extproc('RXS_PutStdOut') Opdesc

      // Data to be written to "standard out", i.e. to a web browser or to a web
      //   service which initiated the incoming request as a client.
     D  pInput                             Like(RXS_Var16Mv_t)
     D                                     Options(*Varsize:*Omit) Const

      // Optional RXS_PutStdOutDS_t to allow for CCSID conversion during output.
     D  pDS                                LikeDS(RXS_PutStdOutDS_t)
     D                                     Options(*Varsize:*Nopass)
```


## Example Code

### Ex. 1

```rpgle
      *--------------------------------------------------------------
      * This example code writes the content of the field gXml to STDOUT.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXML            S                   Like(RXS_Var1Kv_t)
      /free
       gXml = '<element>Example XML</element>';
       RXS_PutStdOut( gXml );

       *INLR = *ON;
      /end-free
```

### Ex. 2

```rpgle
      *--------------------------------------------------------------
      * This example code specifies the header content type as XML,
      *  then sends the data to STDOUT.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D PutStdOutDS     DS                  LikeDS(RXS_PutStdOutDS_t)

     D gXML            S                   Like(RXS_Var1Kv_t)
      /free
       RXS_ResetDS( PutStdOutDS : RXS_DS_TYPE_PUTSTDOUT ); 

       gXml = '<element>Example XML</element>';
       
       PutStdOutDS.HeaderContentType = 'text/xml';
       RXS_PutStdOut( gXml : PutStdOutDS );

       *INLR = *ON;
      /end-free
```

### Ex. 3

```rpgle
      *--------------------------------------------------------------
      * This example code specifies the header content type as XML, and
      *  specifies the IFS file from which data will be read to STDOUT.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D PutStdOutDS     DS                  LikeDS(RXS_PutStdOutDS_t)
      /free
       RXS_ResetDS( PutStdOutDS : RXS_DS_TYPE_PUTSTDOUT ); 
       PutStdOutDS.HeaderContentType = 'text/xml';
       PutStdOutDS.Stmf = '/tmp/over16mb.xml';
       RXS_PutStdOut( *Omit : PutStdOutDS );

       *INLR = *ON;
      /end-free
```

### Ex. 4

```rpgle
      *--------------------------------------------------------------
      * This example code specifies the header content type as XML, and 
      *  sets a header status code and text to communicate an HTTP error,
      *  then sends the XML to STDOUT.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D PutStdOutDS     DS                  LikeDS(RXS_PutStdOutDS_t)

     D gXML            S                   Like(RXS_Var1Kv_t)
      /free
       RXS_ResetDS( PutStdOutDS : RXS_DS_TYPE_PUTSTDOUT );

       gXml = '<element>Example XML</element>';
       
       PutStdOutDS.HeaderStatusCode = 500;
       PutStdOutDS.HeaderStatusText = 'SOAP Fault'; 
       PutStdOutDS.HeaderContentType = 'text/xml';
       RXS_PutStdOut( gXml : PutStdOutDS );

       *INLR = *ON;
      /end-free
```

### Ex. 5

```rpgle
      *--------------------------------------------------------------
      * This example code specifies the header content type as XML, and 
      *  sets a sets a custom header "Prefer", then sends the XML to STDOUT.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D PutStdOutDS     DS                  LikeDS(RXS_PutStdOutDS_t)

     D gXML            S                   Like(RXS_Var1Kv_t)
      /free
       RXS_ResetDS( PutStdOutDS : RXS_DS_TYPE_PUTSTDOUT );

       gXml = '<element>Example XML</element>';
       
       PutStdOutDS.HeaderContentType = 'text/xml';
       PutStdOutDS.CustomHeaderName(1) = 'Prefer'; 
       PutStdOutDS.CustomHeaderValue(1) = 'Return=representation';
       RXS_PutStdOut( gXml : PutStdOutDS );

       *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_PutStdOutDS_t

```rpgle
     D RXS_PutStdOutDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PUTSTDOUT)

     D   OnErrorMessageType...
     D                                5I 0

      // The contents of the Input parameter are converted from the job CCSID to
      //   this CCSID before being sent to standard out.
     D   Ccsid                       10I 0

      // Specifies IFS path to a stream file whose contents are to be sent to
      //   STDOUT.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // The HTTP Status code to be sent in the response. A sucessful response is
      //   typically "200".
     D   HeaderStatusCode...
     D                               10I 0

      // The HTTP Status text to be sent in the response. This is also known as
      //   the reason phrase. A sucessful response is typically "OK".
     D   HeaderStatusText...
     D                                     Like(RXS_Var1Kv_t)

      // The HTTP content type to be sent with the response. The most common
      //   content type for web services is "text/xml". There is no assumed default
      //   value and if this value is not supplied via this data structure, then
      //   the content type is usually required to be supplied in the Input
      //   parameter and it must be followed by two line control characters.
     D   HeaderContentType...
     D                                     Like(RXS_Var1Kv_t)

      // Specifies the name of up to 50 custom HTTP headers sent with the
      //   response.
     D   CustomHeaderName...
     D                               64A   Varying Dim(50)

      // Specifies the value of up to 50 corresponding CustomHeaderNames.
     D   CustomHeaderValue...
     D                                     Like(RXS_Var1Kv_t) Dim(50)
```



---

# RXS_Convert()

> Performs common data conversions including Base64/Base64Url encoding and decoding, hex encoding, and CCSID character set conversion.

RXS_Convert is a flexible subprocedure allowing you to perform many commonly required text and data conversions:

- Convert to/from Base64 encoding
- Convert to/from Base64Url encoding
- Perform XML entity encoding/decoding to replace characters with the appropriate XML entities:
    - `&` to `&amp;`
    - `"` to `&quot;`
    - `'` to `&apos;`
    - `<` to `&lt;`
    - `>` to `&gt;`
- Encode or decode a URL's querystring
- Convert between CCSIDs (character sets)
- Convert to uppercase or lowercase in a CCSID-aware manner - preferable to the common practice of using the %XLATE built-in function
- General find & replace - you can specify your own characters/strings to perform replacements with. Essentially, this allows developers on IBM i OS 6.1 to access functionality similar to that of the %SCANRPL built-in function added in IBM i OS 7.1.

## New in RXS 3.5.0 

### Enhancements 

- Corrected an issue with Base64URL decoding where certain string lengths could not be accurately internally padding, causing decoding to fail.
- Corrected an issue with Base64 and Base64URL decoding where a looping condition and/or decoding failure could occur when certain line break characters were present in the source string.

## Subprocedure Prototype

```rpgle
      // The output of the specified conversion operation is returned.
     D RXS_Convert...
     D                 PR                  Extproc('RXS_Convert') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Field to hold the input of the selected conversion operation. You can
      //   pass a field smaller than 16M without any additional effort.
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Varsize:*Omit)

      // Holds settings which control how RXS_Convert operates. This parm can
      //   accomodate a number of possible data structures, listed below.
      // Valid values: RXS_ConvertCcsidDS_t, RXS_ConvertCaseDS_t,
      //   RXS_ConvertBase64DS_t, RXS_ConvertBase64UrlDS_t,
      //   RXS_ConvertXMLEntitiesDS_t, RXS_ConvertUrlPercentDS_t,
      //   RXS_ConvertUserDefinedDS_t
     D  pDS                                Like(RXS_Var64K_t)
     D                                     Options(*Varsize)
```


## Example Code

### Base64 Encoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to encode data to Base64.
      * Base64 is commonly used to transfer binary data such as images
      * or cryptographic keys/hashes, but may be used on any data.
      * Important to note is that no CCSID conversion is performed on
      * the decoded data. If you are sending text, the receiver will
      * most likely not expect the data to be in EBCDIC, so you may
      * need to use RXS_Convert() to perform CCSID conversion prior
      * to performing Base64 encoding.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gRawData        S                   Like(RXS_Var8Kv_t)
     D gBase64         S                   Like(RXS_Var8Kv_t)

     D gConvBase64DS   DS                  LikeDS(RXS_ConvertBase64DS_t)
      /free
       gRawData = 'hello world!';

       RXS_ResetDS( gConvBase64DS : RXS_DS_TYPE_CONVERTBASE64 );
       gConvBase64DS.EncodeDecode = RXS_ENCODE;
       gBase64 = RXS_Convert( gRawData : gConvBase64DS );

       // After encoding, gBase64 will contain:
       //  aGVsbG8gd29ybGQh

       *INLR = *ON;
      /end-free
```

### Base64 Decoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to decode Base64 encoded data.
      * Base64 is commonly used to transfer binary data such as images
      * or cryptographic keys/hashes, but may be used on any data.
      * Important to note is that no CCSID conversion is performed on
      * the decoded data. If you are receiving ASCII data that was
      * Base64 encoded, you may need to use RXS_Convert() to convert
      * it to a non-ASCII CCSID.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gBase64         S                   Like(RXS_Var8Kv_t)
     D gDecoded        S                   Like(RXS_Var8Kv_t)

     D gConvBase64DS   DS                  LikeDS(RXS_ConvertBase64DS_t)
      /free
       gBase64 = 'aGVsbG8gd29ybGQh';

       RXS_ResetDS( gConvBase64DS : RXS_DS_TYPE_CONVERTBASE64 );
       gConvBase64DS.EncodeDecode = RXS_DECODE;
       gDecoded = RXS_Convert( gBase64 : gConvBase64DS );

       // After decoding, gDecoded will contain:
       //  hello world!

       *INLR = *ON;
      /end-free
```

### XML Entity Encoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to encode XML entites in
      * character data. Note that because < and > would need to be
      * encoded, you cannot use RXS_Convert() to encode a complete
      * XML document. Each item being composed into an XML element
      * would need to be individually encoded.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gData            S                  Like(RXS_Var8Kv_t)
     D gEncoded         S                  Like(RXS_Var8Kv_t)

     D gConvXMLEntDS    DS                 LikeDS(RXS_ConvertXMLEntitiesDS_t)
      /free
       gData = 'Bob & Mary';

       RXS_ResetDS( gConvXMLEntDS : RXS_DS_TYPE_CONVERTXMLENTITIES );
       gConvXMLEntDS.EncodeDecode = RXS_ENCODE;
       gEncoded = RXS_Convert( gData : gConvXMLEntDS );

       // After encoding, gEncoded will contain:
       //  Bob &amp; Mary

       *INLR = *ON;
      /end-free
```

### XML Entity Decoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to decode XML entites back
      * to the corresponding character data. If you have an XML
      * document with encoded characters, it is generally safe to use
      * RXS_Convert() on the entire XML document. One exception to this
      * is when the XML document contains a nested complete XML document
      * which frequently occurs with .Net SOAP services. In that scenario,
      * you need to first parse the inner XML document out, and then
      * perform conversion. Failure to do this may result in an invalid
      * XML document.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXML             S                  Like(RXS_Var8Kv_t)
     D gDecoded         S                  Like(RXS_Var8Kv_t)

     D gConvXMLEntDS    DS                 LikeDS(RXS_ConvertXMLEntitiesDS_t)
      /free
       gXML = '<xml>Bob &amp; Mary</xml>';

       RXS_ResetDS( gConvXMLEntDS : RXS_DS_TYPE_CONVERTXMLENTITIES );
       gConvXMLEntDS.EncodeDecode = RXS_DECODE;
       gDecoded = RXS_Convert( gXML : gConvXMLEntDS );

       // After decoding, gDecoded will contain:
       //  <xml>Bob & Mary</xml>

       *INLR = *ON;
      /end-free
```

### URL Percent Encoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to URL-encode data
      * (also known as 'percent encoding'). This is generally used
      * when passing values in a querystring within a URL. Note that
      * it is not possible to perform URL encoding on a complete URL
      * as many of the characters that make up a URL would be
      * erroneously converted. Instead, each value must be encoded
      * and concatenated to the URL.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gUrl            S                   Like(RXS_Var1Kv_t)
     D gRawData        S                   Like(RXS_Var8Kv_t)
     D gEncoded        S                   Like(RXS_Var8Kv_t)

     D gConvUrlDS      DS                  LikeDS(RXS_ConvertUrlPercentDS_t)
      /free
       gURL = 'http://www.example.com?key='

       gRawData = 'hello world!';

       RXS_ResetDS( gConvUrlDS : RXS_DS_TYPE_CONVERTURLPERCENT );
       gConvUrlDS.EncodeDecode = RXS_ENCODE;
       gEncoded = RXS_Convert( gRawData : gConvUrlDS );

       // After encoding, gEncoded will contain:
       //  hello%20world%21

       gURL += gEncoded;

       // gURL now contains:
       //  http://www.example.com?key=hello%20world%21

       *INLR = *ON;
      /end-free
```

### URL Percent Decoding

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to decode URL-encoded data
      * (also known as 'percent encoded').
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gEncoded        S                   Like(RXS_Var8Kv_t)
     D gDecoded        S                   Like(RXS_Var8Kv_t)

     D gConvUrlDS      DS                  LikeDS(RXS_ConvertUrlPercentDS_t)
      /free
       gEncoded = 'key%3Dhello%20world%21';

       RXS_ResetDS( gConvUrlDS : RXS_DS_TYPE_CONVERTURLPERCENT );
       gConvUrlDS.EncodeDecode = RXS_DECODE;
       gDecoded = RXS_Convert( gEncoded : gConvUrlDS );

       // After decoding, gDecoded will contain:
       //  key=hello world!

       *INLR = *ON;
      /end-free
```

### CCSID Conversion

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to perform CCSID conversion to
      * convert from ISO 8859-1 (CCSID 819) to EBCDIC (CCSID 37).
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gData819        S                   Like(RXS_Var8Kv_t)
     D gData37         S                   Like(RXS_Var8Kv_t)

     D gConvCcsidDS    DS                  LikeDS(RXS_ConvertCcsidDS_t)
      /free
       gData819 = x'48656C6C6F20576F726C6421'; 
       RXS_ResetDS( gConvCcsidDS : RXS_DS_TYPE_CONVERTCCSID );
       gConvCcsidDS.From = RXS_CCSID_ISO88591;
       gConvCcsidDS.To = RXS_CCSID_EBCDIC;
       gData37 = RXS_Convert( gData819 : gConvCcsidDS );

       // After conversion, gData37 will contain:
       //  Hello World!

       *INLR = *ON;
      /end-free
```

### Convert to Uppercase

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to convert lowercase characters
      *  in gData to uppercase. This conversion happens in a CCSID
      *  aware manner. For example, ü will be converted to Ü. This
      *  is recommended over the common practice of using the %Xlate
      *  function with A-Z/a-z constants.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gData           S                   Like(RXS_Var8Kv_t)
     D gUppercase      S                   Like(RXS_Var8Kv_t)

     D gConvCaseDS     DS                  LikeDS(RXS_ConvertCaseDS_t)
      /free
       gData = 'Hello Günter';

       RXS_ResetDS( gConvCaseDS : RXS_DS_TYPE_CONVERTCASE );
       gConvCaseDS.UpperLower = RXS_UPPERCASE;
       gUppercase = RXS_Convert( gData : gConvCaseDS );

       // After conversion, gLowercase will now contain
       //  HELLO GÜNTER

       *INLR = *ON;
      /end-free
```

### Convert to Lowercase

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to convert uppercase characters
      *  in gData to lowercase. This conversion happens in a CCSID
      *  aware manner. For example, Ü will be converted to ü. This
      *  is recommended over the common practice of using the %Xlate
      *  function with A-Z/a-z constants.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gData           S                   Like(RXS_Var8Kv_t)
     D gLowercase      S                   Like(RXS_Var8Kv_t)

     D gConvCaseDS     DS                  LikeDS(RXS_ConvertCaseDS_t)
      /free
       gData = 'HELLO GÜNTER';

       RXS_ResetDS( gConvCaseDS : RXS_DS_TYPE_CONVERTCASE );
       gConvCaseDS.UpperLower = RXS_LOWERCASE;
       gLowercase = RXS_Convert( gData : gConvCaseDS );

       // After conversion, gLowercase will now contain
       //  hello günter

       *INLR = *ON;
      /end-free
```

### User Defined Conversion (Scan & Replace)

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_Convert() to replace instances of
      * specified characters or bytes with the provided replacements.
      * This was originally introduced to provide customers on IBM i
      * 6.1 with something similar to the %ScanRpl built-in function
      * which was only available on IBM i 7.1 or higher. Generally
      * if you are on IBM i 7.1 or higher, you can just use %ScanRpl.
      * The only advantage offered by using RXS_Convert() is that
      * up to 50 replacements may be specified, but each From and To
      * are limited to 32 bytes.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gInput          S                   Like(RXS_Var8Kv_t)
     D gOutput         S                   Like(RXS_Var8Kv_t)

     D gConvUsrDfnDS   DS                  LikeDS(RXS_ConvertUserDefinedDS_t)
      /free
       gInput = 'This is a sample message';

       RXS_ResetDS( gConvUsrDfnDS : RXS_DS_TYPE_CONVERTUSERDEFINED );
       gConvUsrDfnDS.From(1) = 'sample';
       gConvUsrDfnDS.To(1) = 'cool';
       gConvUsrDfnDS.From(2) = 'message';
       gConvUsrDfnDS.To(2) = 'message!';
       gOutput = RXS_Convert( gInput : gConvUsrDfnDS );

       // gOutput now contains:
       //  This is a cool message!

       *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_ConvertBase64DS_t

```rpgle
     D RXS_ConvertBase64DS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTBASE64)

     D   OnErrorMessageType...
     D                                5I 0

      // Set to RXS_ENCODE or RXS_DECODE.
      // Valid values: RXS_ENCODE, RXS_DECODE
      // Default: RXS_DECODE
     D   EncodeDecode                  N

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_ConvertBase64UrlDS_t

```rpgle
     D RXS_ConvertBase64DS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTBASE64)

     D   OnErrorMessageType...
     D                                5I 0

      // Set to RXS_ENCODE or RXS_DECODE.
      // Valid values: RXS_ENCODE, RXS_DECODE
      // Default: RXS_DECODE
     D   EncodeDecode                  N

      // Set to RXS_YES or RXS_NO.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   PadEncodedOutput...           N   Inz(RXS_NO)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0
```


### RXS_ConvertCaseDS_t

```rpgle
     D RXS_ConvertCaseDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTCASE)

     D   OnErrorMessageType...
     D                                5I 0

      // The CCSID of the input string. Defaults to the CCSID of the current job.
     D   Ccsid                       10I 0

      // Set to RXS_UPPERCASE or RXS_LOWERCASE.
      // Valid values: RXS_UPPERCASE, RXS_LOWERCASE
      // Default: RXS_UPPERCASE
     D   UpperLower                    N

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2050A
```


### RXS_ConvertCcsidDS_t

```rpgle
     D RXS_ConvertCcsidDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTCCSID)

     D   OnErrorMessageType...
     D                                5I 0

      // CCSID to convert the input data from.
     D   From                        10I 0

      // CCSID to convert the input data to.
     D   To                          10I 0

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_ConvertUrlPercentDS_t

```rpgle
     D RXS_ConvertUrlPercentDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTURLPERCENT)

     D   OnErrorMessageType...
     D                                5I 0

      // Set to RXS_ENCODE or RXS_DECODE.
      // Valid values: RXS_ENCODE, RXS_DECODE
      // Default: RXS_DECODE
     D   EncodeDecode                  N

      // Determines whether the returned encoded hexadecimal values are returned
      //   as ASCII bytes (RXS_ASCII) or converted to EBCDIC (RXS_EBCDIC).
      // Valid values: RXS_ASCII, RXS_EBCDIC
      // Default: RXS_ASCII
     D   AsciiEbcdic                   N

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2050A
```


### RXS_ConvertUserDefinedDS_t

```rpgle
     D RXS_ConvertUserDefinedDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTUSERDEFINED)

     D   OnErrorMessageType...
     D                                5I 0

     D   From                        32A   Varying Dim(50)

     D   To                          32A   Varying Dim(50)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_ConvertXmlEntitiesDS_t

```rpgle
     D RXS_ConvertXMLEntitiesDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CONVERTXMLENTITIES)

     D   OnErrorMessageType...
     D                                5I 0

      // Set to RXS_ENCODE or RXS_DECODE.
      // Valid values: RXS_ENCODE, RXS_DECODE
      // Default: RXS_DECODE
     D   EncodeDecode                  N

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2049A
```



---

# RXS_Crypt()

> Encrypts or decrypts data using symmetric algorithms with configurable key, IV, and padding options.

This subprocedure performs cryptographic operations like hashing and encryption. Currently supported operations are:

- MD5
- SHA1
- SHA256
- SHA384
- SHA512
- AES128
- AES192
- AES256

For AES encryption, block modes ECB, CBC, and CTR are supported.

## Subprocedure Prototype

```rpgle
      // Returns the output of the specified hashing/crytographic operation.
     D RXS_Crypt...
     D                 PR                  Extproc('RXS_Crypt') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Field to hold the input of the selected hashing/crytographic operation.
      //   You can pass a field smaller than 16M without any additional effort.
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Varsize:*Omit)

      // Holds settings which control how RXS_Crypt operates. This parm can
      //   accomodate a number of possible data structures, listed below.
     D  pDS                                Like(RXS_Var64K_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: MD5 Hash of Character Field

```rpgle
**FREE
// This example code calculates an MD5 hash from a character field.
Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds MD5DS LikeDS(RXS_MD5CryptDS_t);
Dcl-S Data Like(RXS_Var1Kv_t);
Dcl-S Hash Like(RXS_Var1Kv_t);

Data = 'Hello World';

RXS_ResetDS( HashMD5DS : RXS_DS_TYPE_MD5CRYPT );
Hash = RXS_Crypt( Data : HashMD5DS );

RXS_JobLog( 'Hash: %s' : Hash ); // Output: Hash: B10A8DB164E0754105B7A99BE72E3FE5

return;
```

### Ex. 2: AES128 Encryption of ISO88591 data

```rpgle
**FREE
// This example code performs AES128 encryption on an ISO88591 string
Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds AESDS LikeDS(RXS_AESCryptDS_t);
Dcl-S Input Like(RXS_Var1Kv_t);
Dcl-S Output Like(RXS_Var1Kv_t);

Input = 'abc';

RXS_ResetDS( AESDS : RXS_DS_TYPE_AESCRYPT );

AESDS.PadInput = RXS_YES;
AESDS.Algorithm = RXS_CRYPT_AES128_ALGORITHM;
AESDS.BlockMode = RXS_CRYPT_AESBLOCKMODE_CBC;
AESDS.EncryptDecrypt = RXS_ENCRYPT;

// We're providing the input string in our job CCSID but we want
//  RXS_Crypt to convert it to the ISO88591 equivalent and then
//  encrypt. If you plan to exchange data with non-IBM i systems
//  this is usually the route you'll want to go, though you may
//  instead need to encrypt as UTF-8 by using RXS_CCSID_UTF8.
AESDS.InputCCSID = RXS_CCSID_JOB;
AESDS.EncryptAsCcsid = RXS_CCSID_ISO88591;

AESDS.Key128 = x'00000000000000000000000000000000';
AESDS.KeyFormat = RXS_CRYPT_AESKEYFORMAT_BIN;
AESDS.OutputPadOption = RXS_CRYPT_PADOUTPUT_NONE;
AESDS.InitializationVector = x'00000000000000000000000000000000';

// After encryption, the Output field will contain a binary string with the hex
//  value: d29999ea2b049ae79cb86ba27b1a5f55
Output = RXS_Crypt( Input : AESDS );

//  RXS_JobLog will attempt to treat this as a string which will lead to
//   the output looking like garbage characters.
RXS_JobLog( 'Encrypted output: %s' : Output );

// The easiest way to view and verify the encryption worked correctly is
//  to look at the value of the Output field in debug in hex mode.
//  Using STRDBG, you would view Output in hex mode by typing in this:
//
//    eval Output:x 50
return;
```

### Ex. 3: AES128 Decryption of ISO88591 data

```rpgle
**FREE
// This example code performs AES128 decryption on an ISO88591 string
Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds AESDS LikeDS(RXS_AESCryptDS_t);
Dcl-S Input Like(RXS_Var1Kv_t);
Dcl-S Output Like(RXS_Var1Kv_t);

// We have a sequence of hex bytes and not a string to decrypt
Input = x'd29999ea2b049ae79cb86ba27b1a5f55';

RXS_ResetDS( AESDS : RXS_DS_TYPE_AESCRYPT );

// Any non-IBM i system you work with isn't going to deal with data in EBCDIC so
//  RXS_Crypt is set up to facilitate handling conversion to/from non-EBCDIC
//  character sets. In this case the string is in ISO8859-1, so we indicate
//  that here:
AESDS.InputCcsid = RXS_CCSID_ISO88591;
AESDS.EncryptAsCcsid = RXS_CCSID_ISO88591;

// Indicate that we're performing decryption and not encryption
AESDS.EncryptDecrypt = RXS_DECRYPT;

AESDS.PadInput = RXS_YES;
AESDS.Algorithm = RXS_CRYPT_AES128_ALGORITHM;
AESDS.BlockMode = RXS_CRYPT_AESBLOCKMODE_CBC;

AESDS.Key128 = x'00000000000000000000000000000000';
AESDS.KeyFormat = RXS_CRYPT_AESKEYFORMAT_BIN;

// Make sure you set the key CCSID appropriately
AESDS.KeyCCSID = RXS_CCSID_JOB;

// For output generally RXS_CRYPT_PADOUTPUT_CHAR is what you should use.
//  This will make RXS_Crypt pad the output with null bytes, which is typically
//  what you want.
AESDS.OutputPadOption = RXS_CRYPT_PADOUTPUT_CHAR;

AESDS.InitializationVector = x'00000000000000000000000000000000';

// After encryption, the Output field will contain a binary string with the hex
//  value: 616263
// This corresponds to the ISO88591 characters 'abc' as expected.
Output = RXS_Crypt( Input : AESDS );

//  RXS_JobLog will attempt to treat this as a string which will lead to
//   the output looking like garbage characters.
RXS_JobLog( 'Decrypted output: %s' : Output );

// The easiest way to view and verify the encryption worked correctly is
//  to look at the value of the Output field in debug in hex mode.
//  Using STRDBG, you would view Output in hex mode by typing in this:
//
//    eval Output:x 50
return;
```

### Ex. 4: AES128 Encryption of EBCDIC data

```rpgle
**FREE
// This example code performs AES128 encryption on an EBCDIC string
Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds AESDS LikeDS(RXS_AESCryptDS_t);
Dcl-S Input Like(RXS_Var1Kv_t);
Dcl-S Output Like(RXS_Var1Kv_t);

Input = 'abc';

RXS_ResetDS( AESDS : RXS_DS_TYPE_AESCRYPT );

AESDS.PadInput = RXS_YES;
AESDS.Algorithm = RXS_CRYPT_AES128_ALGORITHM;
AESDS.BlockMode = RXS_CRYPT_AESBLOCKMODE_CBC;
AESDS.EncryptDecrypt = RXS_ENCRYPT;

// We're providing the input as our job CCSID, and we want it encrypted
//  using EBCDIC CCSID 37. This is rarely something you want to do when you
//  will be exchanging encrypted data with other non-IBM i systems as most
//  other systems will expect to be working with ISO88591 or UTF-8 data.
AESDS.InputCCSID = RXS_CCSID_JOB;
AESDS.EncryptAsCCSID = RXS_CCSID_EBCDIC;

AESDS.Key128 = x'00000000000000000000000000000000';
AESDS.KeyFormat = RXS_CRYPT_AESKEYFORMAT_BIN;
AESDS.OutputPadOption = RXS_CRYPT_PADOUTPUT_NONE;
AESDS.InitializationVector = x'00000000000000000000000000000000';

// After encryption, the Output field will contain a binary string with the hex
//  value: 81743865f4a9ce13349d67f5642fd971
Output = RXS_Crypt( Input : AESDS );

//  RXS_JobLog will attempt to treat this as a string which will lead to
//   the output looking like garbage characters.
RXS_JobLog( 'Encrypted output: %s' : Output );

// The easiest way to view and verify the encryption worked correctly is
//  to look at the value of the Output field in debug in hex mode.
//  Using STRDBG, you would view Output in hex mode by typing in this:
//
//    eval Output:x 50
return;
```

## Data Structures

### RXS_MD5CryptDS_t

```rpgle
     D RXS_MD5CryptDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_MD5CRYPT)

     D   OnErrorMessageType...
     D                                5I 0

      // Optional IFS stream file to use as input data for hash operation.
     D   InputStmf                         Like(RXS_Var1Kv_t)

      // CCSID of input data. Used when the input data is of a different CCSID of
      //   the job CCSID. Not necessary if using InputStmf.
      // Default: RXS_CCSID_JOB
     D   InputCcsid                  10I 0 Inz(RXS_CCSID_JOB)

      // CCSID to convert the input data into before hashing. Note that this is
      //   typically going to be RXS_CCSID_ISO88591 or RXS_CCSID_UTF8.
      // Default: RXS_CCSID_ISO88591
     D   EncryptAsCcsid...
     D                               10I 0 Inz(RXS_CCSID_ISO88591)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_SHACryptDS_t

```rpgle
     D RXS_SHACryptDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_SHACRYPT)

     D   OnErrorMessageType...
     D                                5I 0

      // Determines which SHA algorithm/block size to use when hashing.
      // Valid values: RXS_CRYPT_SHA1_ALGORITHM, RXS_CRYPT_SHA256_ALGORITHM,
      //   RXS_CRYPT_SHA384_ALGORITHM, RXS_CRYPT_SHA512_ALGORITHM
     D   Algorithm                    4P 3

      // Optional IFS stream file to use as input data for hash operation.
     D   InputStmf                         Like(RXS_Var1Kv_t)

      // CCSID of input data. Used when the input data is of a different CCSID of
      //   the job CCSID. Not necessary if using InputStmf.
      // Default: RXS_CCSID_JOB
     D   InputCcsid                  10I 0 Inz(RXS_CCSID_JOB)

      // CCSID to convert the input data into before hashing. Note that this is
      //   typically going to be RXS_CCSID_ISO88591 or RXS_CCSID_UTF8.
      // Default: RXS_CCSID_ISO88591
     D   EncryptAsCcsid...
     D                               10I 0 Inz(RXS_CCSID_ISO88591)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2049A
```


### RXS_AESCryptDS_t

```rpgle
     D RXS_AESCryptDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_AESCRYPT)

     D   OnErrorMessageType...
     D                                5I 0

      // Determines which AES algorithm to use when encrypting.
      // Valid values: RXS_CRYPT_AES128_ALGORITHM, RXS_CRYPT_AES192_ALGORITHM,
      //   RXS_CRYPT_AES256_ALGORITHM
     D   Algorithm                    3I 0

      // Determines which AES block mode to use when encrypting.
      // Valid values: RXS_CRYPT_AESBLOCKMODE_ECB, RXS_CRYPT_AESBLOCKMODE_CBC,
      //   RXS_CRYPT_AESBLOCKMODE_CTR
     D   BlockMode                    3I 0

      // Specifies whether input data should be encrypted or decrypted.
      // Valid values: RXS_ENCRYPT, RXS_DECRYPT
     D   EncryptDecrypt...
     D                                 N

      // AES standard requires that input data be exactly divisible into block
      //   lengths of 16 - specify this value as RXS_YES if you'd like RXS to
      //   perform this padding automatically, or to RXS_NO if you are manually
      //   padding the input data.
      // Valid values: RXS_YES, RXS_NO
     D   PadInput                      N

      // If PadInput is set to RXS_YES, the hex byte value in this field will be
      //   used for padding the input data.
      // Default: x'00'
     D   InputPadCharacter...
     D                                1A   Inz(x'00')

      // Specifies if output data should be padded, and which padding method to
      //   use.
      // Valid values: RXS_CRYPT_PADOUTPUT_NONE, RXS_CRYPT_PADOUTPUT_CHAR,
      //   RXS_CRYPT_PADOUTPUT_COUNTER
      // Default: RXS_CRYPT_PADOUTPUT_NONE
     D   OutputPadOption...
     D                                3I 0 Inz(RXS_CRYPT_PADOUTPUT_NONE)

      // If OutputPadOption is set to RXS_CRYPT_PADOUTPUT_CHAR, the hex byte
      //   value in this field will be used for padding the output data.
      // Default: x'00'
     D   OutputPadCharacter...
     D                                1A   Inz(x'00')

      // The data format of the encryption key. Currently only binary keys are
      //   fully supported.
      // Valid values: RXS_CRYPT_AESKEYFORMAT_BIN, RXS_CRYPT_AESKEYFORMAT_BER
      // Default: RXS_CRYPT_AESKEYFORMAT_BIN
     D   KeyFormat                    3I 0 Inz(RXS_CRYPT_AESKEYFORMAT_BIN)

      // The encryption key to be used for AES256. Only the Key### field matching
      //   your AES algorithm needs to be specified.
     D   Key256                      32A   Inz(*Allx'00')

      // The encryption key to be used for AES192. Only the Key### field matching
      //   your AES algorithm needs to be specified.
     D   Key192                      24A   Overlay(Key256)

      // The encryption key to be used for AES128. Only the Key### field matching
      //   your AES algorithm needs to be specified.
     D   Key128                      16A   Overlay(Key256)

      // Initialization vector used for optional additional randomization. If
      //   this value is not null, both the encrypting party and the decrypting
      //   party need to know this value.
     D   InitializationVector...
     D                               16A   Inz(*Allx'00')

      // CCSID of input data. Used when the input data is of a different CCSID
      //   than the job CCSID. Not necessary if using InputStmf.
      // Default: RXS_CCSID_JOB
     D   InputCcsid                  10I 0 Inz(RXS_CCSID_JOB)

      // When performing encryption, this field will specify the CCSID in which
      //   the data should be encrypted. If this value is different than the
      //   InputCcsid value, the data will be converted before encryption. When
      //   performing decryption, this field will specify the CCSID of the data
      //   that was encrypted.
      // Default: RXS_CCSID_ISO88591
     D   EncryptAsCcsid...
     D                               10I 0 Inz(RXS_CCSID_ISO88591)

      // Specify the CCSID of the encryption key. Used when the key is of a
      //   different CCSID than the job CCSID. If this value is different than the
      //   EncryptAsCcsid value, the data will be converted before
      //   encryption/decryption.
      // Default: RXS_CCSID_JOB
     D   KeyCcsid                    10I 0 Inz(RXS_CCSID_JOB)

      // Optional IFS stream file to use as input data for encryption/decryption.
     D   InputStmf                         Like(RXS_Var1Kv_t)

      // Optional IFS stream file where encrypted/decrypted output will be
      //   written.
     D   OutputStmf                        Like(RXS_Var1Kv_t)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Internal use only
     D   Reserved                  2049A
```



---

# RXS_HMAC()

> Computes an HMAC digest using a secret key and a configured RXS_HMACDS_t data structure.

Performs HMAC calculation.

Requires both a secret as well as a configured RXS_HMACDS_t data structure to be passed in as parameters.

## New in RXS 3.5.3 

### Enhancements 

- Enhanced RXS_HMACDS_t with new subfield ReturnMode which accepts the constants RXS_RETURN_MODE_BYTES, RXS_RETURN_MODE_CHAR_UPPER, and RXS_RETURN_MODE_CHAR_LOWER to allow the value returned from RXS_HMAC to be returned as raw bytes, uppercase characters, or lowercase characters. ReturnMode replaces/overlays the existing subfield ReturnAsChar which only allowed raw bytes or uppercase characters to be returned by RXS_HMAC

## Subprocedure Prototype

```rpgle
      // Returns a calculated HMAC string. Note that if OutputStmf is set to an
      //   IFS path in the configuration data structure, the HMAC string will be
      //   written there instead.
     D RXS_HMAC...
     D                 PR                  Extproc('RXS_HMAC') Opdesc
     D                                     Like(RXS_Var1Kv_t)

      // Holds the data to be encrypted as part of HMAC calculation. Note that if
      //   InputStmf is set to an IFS path in the configuration data structure, the
      //   input will be read from there instead.
     D pInput                              Like(RXS_Var16Mv_t) Const
     D                                     Options(*Varsize:*Omit)

      // The secret used for HMAC calculation.
     D pSecret                             Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize)

      // Controls how HMAC calculation is performed. Uses RXS_HMACDS_t as a
      //   template.
     D pDS                                 LikeDS(RXS_HMACDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### SHA 1 Calculation

```rpgle
**FREE
*--------------------------------------------------------------
* This example demonstrates how to perform a simple SHA-1 HMAC
* calculation with RXS_HMAC. This example returns the HMAC string
* as text.
* This example is based on a test case from RFC 2202: 
*  https://www.rfc-editor.org/rfc/rfc2202
*--------------------------------------------------------------
Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds HMACDS LikeDS(RXS_HMACDS_t);

Dcl-S Input Like(RXS_Var1Kv_t) Inz;
Dcl-S Secret Like(RXS_Var1Kv_t) Inz;
Dcl-S Output Like(RXS_Var1Kv_t) Inz;

RXS_ResetDS( HMACDS : RXS_DS_TYPE_HMAC );

Input = 'what do ya want for nothing?';
Secret = 'Jefe';

HMACDS.InputCcsid = RXS_CCSID_EBCDIC;
HMACDS.SecretCcsid = RXS_CCSID_EBCDIC;
HMACDS.EncryptAsCcsid = RXS_CCSID_ISO88591;

HMACDS.Algorithm = RXS_HMAC_SHA1;

Output = RXS_HMAC( Input : Secret : HMACDS );
```

## Data Structures

### RXS_HMAC_t

```rpgle
     D RXS_HMACDS_t...
     D                 DS                  Qualified Template Inz

      // If an error occurs during processing, additional error information may
      //   be returned in this data structure.
     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_HMAC)

     D   OnErrorMessageType...
     D                                5I 0

      // Determines which algorithm to use when encrypting.
      // Valid values: RXS_HMAC_MD5, RXS_HMAC_SHA1, RXS_HMAC_SHA256,
      //   RXS_HMAC_SHA384, RXS_HMAC_SHA512
     D   Algorithm                    3I 0

      // CCSID of input data. Used when the input data is of a different CCSID
      //   than the job CCSID. Not necessary if using InputStmf.
      // Default: RXS_CCSID_JOB
     D   InputCcsid                  10I 0 Inz(RXS_CCSID_JOB)

      // CCSID of secret used for HMAC calculation. Used when the secret is of a
      //   different CCSID than the job CCSID.
      // Default: RXS_CCSID_JOB
     D   SecretCcsid                 10I 0 Inz(RXS_CCSID_JOB)

      // When performing HMAC calculation, this field will specify the CCSID in
      //   which the data should be encrypted. If this value is different than the
      //   InputCcsid value, the data will be converted before encryption.
      // Default: RXS_CCSID_ISO88591
     D   EncryptAsCcsid...
     D                               10I 0 Inz(RXS_CCSID_ISO88591)

      // Deprecated as of RXS 3.5.3, replaced by ReturnMode
      // Specifies whether to return the character representation of the
      //   hexadecimal binary data.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   ReturnAsChar...
     D                                 N   Inz(RXS_YES)

      // Optional IFS stream file to use as input data for HMAC calculation.
     D   InputStmf                         Like(RXS_Var1Kv_t)

      // Optional IFS stream file where HMAC output will be written.
     D   OutputStmf                        Like(RXS_Var1Kv_t)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // Specifies the method used to handle returned. Added in RXS 3.5.3.
      //   Replaces ReturnAsChar.
      // Valid values: RXS_RETURN_MODE_CHAR_UPPER, RXS_RETURN_MODE_CHAR_LOWER,
      //   RXS_RETURN_MODE_BYTES
      // Default: RXS_RETURN_MODE_CHAR_UPPER
     D   ReturnMode...
     D                                1A   Overlay(ReturnAsChar)
     D                                     Inz(RXS_RETURN_MODE_CHAR_UPPER)
```



---

# RXS_Catch()

> Catches an RXS exception thrown by RXS_Throw(), returning error details for structured error handling.

This subprocedure can be used to catch an error thrown by [RXS_Throw()](https://isupport.katointegrations.com/rxs/3.5/rxs_throw.md) or via other methods. The error information is retrieved into a RXS_CatchThrowErrorDS_t data structure.

Typical usage involves this subprocedure being called inside the ON-ERROR section of a MONITOR group.

## Subprocedure Prototype

```rpgle
      // Returns RXS_CatchThrowErrorDS_t data structure containing the error
      //   information that was previously thrown.
     D RXS_Catch...
     D                 PR                  Extproc('RXS_Catch')
     D                                     Likeds(RXS_CatchThrowErrorDS_t)
```


## Example Code

### Ex. 1: Catch Error

```rpgle
**FREE
// This example demonstrates calling RXS_Catch() within an ON-ERROR block
//  to catch an error message.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

Dcl-S NumA Int(10);
Dcl-S NumB Int(10);
Dcl-S Result Int(10);

monitor;
  // To ensure we trigger an error, we're going to divide by 0.
  NumA = 5;
  NumB = 0;
  Result = NumA / NumB;
on-error;
  // Note that you don't have to use RXS_ResetDS to initialize ErrorDS before
  //  using it - this is one of the few RXS data structures where
  //  this is safe and correct.
  ErrorDS = RXS_Catch();

  // At this point ErrorDS should contain the error information for an
  //  MCH1211 "divide by zero" error.
endmon;

return;
```

## Data Structures

### RXS_CatchThrowErrorDS_t

```rpgle
     D RXS_CatchThrowErrorDS_t...
     D                 DS                  Qualified Template Inz

      // The message ID that was received.
     D   MessageId                    7A

      // The message file used to store the message ID.
     D   MessageFile                 20A

      // The message data to be merged with the text of the message ID.
     D   MessageData               1024A   Varying

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CATCHTHROWERROR)

      // The type of message that was sent.
      // Valid values: RXS_MESSAGE_TYPE_DIAG, RXS_MESSAGE_TYPE_COMP,
      //   RXS_MESSAGE_TYPE_INFO, RXS_MESSAGE_TYPE_INQ, RXS_MESSAGE_TYPE_RQS,
      //   RXS_MESSAGE_TYPE_NOTIFY, RXS_MESSAGE_TYPE_ESCAPE,
      //   RXS_MESSAGE_TYPE_STATUS
     D   MessageType                  5I 0

      // The full message text that combines the value of the message ID's text
      //   and the merged message data.
     D   MessageText               4096A   Varying

      // Not used. Used by RXS_Throw() only.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   ThrowToCaller...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_Throw()

> Throws an RXS exception with a message and code, to be caught by RXS_Catch().

This subprocedure can be used to throw an error. The error information is populated into a data structure which is then passed to RXS_Throw. The error can be caught by [RXS_Catch()](https://isupport.katointegrations.com/rxs/3.5/rxs_catch.md).

## Subprocedure Prototype

```rpgle
     D RXS_Throw...
     D                 PR                  Extproc('RXS_Throw') Opdesc

      // The RXS_CatchThrowErrorDS_t data structure that contains the error
      //   information to be thrown.
     D  pDS                                Likeds(RXS_CatchThrowErrorDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
**FREE
// This example code demonstrates throwing a diagnostic error by assigning
//  values to a RXS_CatchThrowErrorDS_t datastructure and throwing the error
//  to the calling program.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

// Send a diagnostic error message

RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
ErrorDS.MessageId = 'CPF9898';
ErrorDS.MessageFile = 'QCPFMSG';
ErrorDS.MessageData = 'This is a *DIAG error being thrown';
ErrorDS.MessageType = RXS_MESSAGE_TYPE_DIAG;
RXS_Throw( ErrorDS );

return;
```

## Data Structures

### RXS_CatchThrowErrorDS_t

```rpgle
     D RXS_CatchThrowErrorDS_t...
     D                 DS                  Qualified Template Inz

      // The message ID for the message you wish to send.
     D   MessageId                    7A

      // The message file used to store the message ID.
     D   MessageFile                 20A

      // The message data to be merged with the text of the specified message
     D   MessageData               1024A   Varying

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CATCHTHROWERROR)

      // The type of message that was sent.
      // Valid values: RXS_MESSAGE_TYPE_DIAG, RXS_MESSAGE_TYPE_COMP,
      //   RXS_MESSAGE_TYPE_INFO, RXS_MESSAGE_TYPE_INQ, RXS_MESSAGE_TYPE_RQS,
      //   RXS_MESSAGE_TYPE_NOTIFY, RXS_MESSAGE_TYPE_ESCAPE,
      //   RXS_MESSAGE_TYPE_STATUS
     D   MessageType                  5I 0

      // The full message text that combines the value of the message ID's text
      //   and the merged message data.
     D   MessageText               4096A   Varying

      // Set to RXS_YES when you want the error sent to the program that called
      //   the program which is throwing the error. By default, the program
      //   throwing the error will be the target of the error.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   ThrowToCaller...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_Transmit()

> Performs HTTP/HTTPS requests with configurable headers, authentication, encoding, and timeout options.

Performs a HTTP/HTTPS request - allowing you to exchange data with a remote web service.

To properly communicate with a web service, you will need to configure the RXS_TransmitDS_t data structure passed in as a parameter.

## New in RXS 3.5.0 

### New Fields 

- HeaderAuthScheme: Used to specify an authorization scheme to be used by RXS_Transmit. Valid values are `RXS_HTTP_AUTH_SCHEME_BASIC` and `RXS_HTTP_AUTH_SCHEME_BEARER`.
- HeaderAuthCredentials: Used to provide authorization credentials to be used in conjunction with HeaderAuthScheme. Basic authorization credentials should be set in `username:password` format; encoding will be performed automatically by RXS_Transmit. This field can support Bearer tokens of up to 8KB in length, which better allows for complex/large JWT tokens.
- HeaderAcceptEncoding: Used to request that the remote server provide gzip-encoded data, which is automatically decoded by RXS_Transmit. **This is the new default behavior for both existing and new programs.**

### New Constants 

- `RXS_TIMEOUT_UNLIMITED`: this value can be used with both the Timeout and SSLTimeout fields to set an unlimited timeout duration for the call to RXS_Transmit.
- Several new constants for common HTTP status codes have been added to RXSCB and can be referenced when checking TransmitDS.HTTPResponse.StatusCode to determine the outcome of a call to RXS_Transmit:
  - `RXS_HTTP_STATUS_OK` (200)
  - `RXS_HTTP_STATUS_BAD_REQUEST` (400)
  - `RXS_HTTP_STATUS_UNAUTHORIZED` (401)
  - `RXS_HTTP_STATUS_FORBIDDEN` (403)
  - `RXS_HTTP_STATUS_NOT_FOUND` (404)
  - `RXS_HTTP_STATUS_METHOD_NOT_ALLOWED` (405)
  - `RXS_HTTP_STATUS_SERVER_ERROR` (500)
  - `RXS_HTTP_STATUS_METHOD_NOT_IMPLEMENTED` (501)
  - `RXS_HTTP_STATUS_BAD_GATEWAY` (502)
  - `RXS_HTTP_STATUS_SERVICE_UNAVAILABLE` (503)

### Other Changes 

- RXS_Transmit has been enhanced to now direct output to both a response field and a response IFS file.
- Logfile output for chunked responses is no longer split and is now shown without separator bytes.

## Subprocedure Prototype

```rpgle
      // Returns the response data. Note that if RXS_TransmitDS_t.ResponseStmf is
      //   set to an IFS path, the response will be stored there instead.
     D RXS_Transmit...
     D                 PR                  Extproc('RXS_Transmit') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // Holds the request data. Note that if RXS_TransmitDS_t.RequestStmf is set
      //   to an IFS path, the request will be read from there instead.
     D  pRequest                           Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // Controls how RXS_Transmit performs the HTTP/HTTPs request. Uses
      //   RXS_TransmitDS_t as a template.
     D  pDS                                LikeDS(RXS_TransmitDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### HTTP GET

```rpgle
      *--------------------------------------------------------------
      * This example performs a simple HTTP GET operation against the URL example.com and stores the retrieved response data in the field Response.
      * Because this is a HTTP GET, we don't need to pass a Request parameter.
      * During the execution of RXS_Transmit(), the API will create a log file at '/tmp/rxs_transmit1_log.txt'
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'https://example.com/';
        TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
        TransmitDS.LogFile = '/tmp/rxs_transmit1_log.txt';
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

### HTTP POST

```rpgle
      *--------------------------------------------------------------
      * This example performs a HTTP POST against a public web service that provides conversion between Fahrenheit and Celsius.
      * This is a HTTP POST, so we need to provide a Request parameter.
      * Because this is using SOAP, we need to specify Content-type and SOAPAction headers.
      * Lastly, this example uses RXS_JobLog() to write the full response XML to the job log.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Request         S                   Like(RXS_Var64Kv_t)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        Request = '<?xml version="1.0" encoding="utf-8"?>' +
         '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'+
         ' xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="' +
         'http://schemas.xmlsoap.org/soap/envelope/"><soap:Body>' +
         '<FahrenheitToCelsius xmlns="http://www.w3schools.com/webservices/">'+
         '<Fahrenheit>100</Fahrenheit></FahrenheitToCelsius>' +
         '</soap:Body></soap:Envelope>';

        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI =
          'http://www.w3schools.com/webservices/tempconvert.asmx';
        TransmitDS.HTTPMethod = RXS_HTTP_METHOD_POST;
        TransmitDS.LogFile = '/tmp/rxs_transmit2_log.txt';
        TransmitDS.HeaderContentType = 'text/xml; charset=utf-8';
        TransmitDS.HeaderSOAPAction =
          '"http://www.w3schools.com/webservices/FahrenheitToCelsius"';
        Response = RXS_Transmit( Request : TransmitDS );

        RXS_JobLog( Response );

        *INLR = *ON;
      /end-free
```

### HTTP Custom Headers

```rpgle
      *--------------------------------------------------------------
      * This example demonstrates how to set custom HTTP headers with RXS_Transmit(). 
      * You may specify up to 50 custom HTTP headers sent with a request.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'https://example.com/';
        // Set a custom HTTP header that looks like this:
        // X-API-Token: [your-api-token]
        TransmitDS.CustomHeaderName(1) = 'X-API-Token';
        TransmitDS.CustomHeaderValue(1) = '[your-api-token]'; 
        // Set a custom HTTP header that looks like this:
        // ExampleHeader: hello world
        TransmitDS.CustomHeaderName(2) = 'ExampleHeader';  
        TransmitDS.CustomHeaderValue(2) = 'hello world';
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

### HTTP Basic Authentication

```rpgle
      *--------------------------------------------------------------
      * This example demonstrates how to specify a username and
      * password for HTTP Basic Authentication with RXS_Transmit().
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'https://example.com/';
        TransmitDS.BasicAuthUser = 'username';
        TransmitDS.BasicAuthPassword = 'password';    
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

### HTTP Bearer Authentication

```rpgle
      *--------------------------------------------------------------
      * This examples showcases using RXS_Transmit to set an Authorization header. 
      * When using CustomHeaderName and CustomHeaderValue arrays with RXS_Transmit()
      * to set an Authorization header, the Bearer portion needs to go in the Value 
      * field, followed by a space before the API key is concatenated to the Value field. 
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'https://example.com/';
        TransmitDS.HeaderAuthScheme = RXS_HTTP_AUTH_SCHEME_BEARER;
        TransmitDS.HeaderAuthCredentials = '[your-api-token]';
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

### HTTP Cookie Header Data

```rpgle
      *--------------------------------------------------------------
      * This example showcases sending cookie data. However, instead of 
      * reading the data from a file, you may specify it by utilizing
      * HeaderCookieData.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'example.com';
        TransmitDS.HeaderCookieData = 'your cookie data';
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

### HTTP Cookie Header File

```rpgle
      *--------------------------------------------------------------
      * This example showcases sending cookie data, utilizing an IFS
      * file which contains said data.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D                                     Inz(*LikeDS)
     D Response        S                   Like(RXS_Var64Kv_t)
      /free
        RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
        TransmitDS.URI = 'example.com';
        TransmitDS.HeaderCookieFiles(1) = '/tmp/cookie.txt';
        Response = RXS_Transmit( *Omit : TransmitDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_TransmitDS_t

```rpgle
     D RXS_TransmitDS_t...
     D                 DS                  Qualified Template Inz

      // If an error occurs during processing, additional error information may
      //   be returned in this data structure.
     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_TRANSMIT)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify the full URI used for the request. If a non-standard port (e.g.
      //   other than 80 for an HTTP request, and 443 for an HTTPS request) is
      //   needed, this must be specified in the request.
      // Example: https://testuri.org:9001/webservice
     D   URI                               Like(RXS_Var8Kv_t)

      // An IFS path can be specified in this subfield - if it is, it will be
      //   used as the request data instead of the value of the pRequest parm.
     D   RequestStmf                       Like(RXS_Var1Kv_t)

      // Specify the CCSID of the data to be sent to the remote server. If
      //   required, the data in the Request parameter will be converted to this
      //   CCSID before transmission to the remote server.
      // Default: RXS_CCSID_UTF8
     D   RequestCcsid                10I 0 Inz(RXS_CCSID_UTF8)

      // An IFS path can be specified in this subfield - if it is, any output of
      //   the RXS_Transmit operation will be stored in the specified IFS file.
     D   ResponseStmf                      Like(RXS_Var1Kv_t)

      // If the response is being stored in an IFS stream file (determined by
      //   whether or not the ResponseStmf subfield is occupied), the response data
      //   will be converted to this CCSID. The stream file will also be created
      //   with this CCSID.
      // Default: RXS_CCSID_UTF8
     D   ResponseStmfCcsid...
     D                               10I 0 Inz(RXS_CCSID_UTF8)

      // Specify a value in seconds for a timeout on the request.
      // Valid values: number, RXS_TIMEOUT_UNLIMITED
      // Default: 60
     D   Timeout                     10U 0 Inz(60)

      // Determines the HTTP request method used.
      // Valid values: RXS_HTTP_METHOD_GET, RXS_HTTP_METHOD_POST,
      //   RXS_HTTP_METHOD_PUT, RXS_HTTP_METHOD_DELETE, RXS_HTTP_METHOD_PATCH,
      //   RXS_HTTP_METHOD_HEAD, RXS_HTTP_METHOD_OPTIONS
      // Default: RXS_HTTP_METHOD_POST
     D   HTTPMethod                   3I 0 Inz(RXS_HTTP_METHOD_POST)

      // An IFS path can be specified to log the request & response, as well as
      //   additional HTTP request debugging information. This option is disabled
      //   by default, and should not be left enabled in production environments
      //   due to increased execution time and privacy concerns.
     D   LogFile                           Like(RXS_Var1Kv_t)

      // Specify a value in seconds to cause a timeout during an SSL handshake.
      // Valid values: number, RXS_TIMEOUT_UNLIMITED
      // Default: 300
     D   SSLTimeout                  10U 0 Inz(300)

      // Used to assign an application ID for RXS_Transmit.
     D   SSLApplicationID...
     D                                     Like(RXS_Var1Kv_t)

      // If a SSL certificate store other than the *SYSTEM store should be used,
      //   the IFS path can be specified here.
     D   SSLCertStore...
     D                                     Like(RXS_Var1Kv_t)

      // If using a non-*SYSTEM SSL certificate store, the password can be
      //   specified here.
     D   SSLCertStorePassword...
     D                                     Like(RXS_Var1Kv_t)

      // This option determines whether RXS_Transmit verifies the authenticity of
      //   the peer's certificate. Setting this to RXS_NO this in a production
      //   capacity is strongly discouraged.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SSLVerifyPeer...
     D                                 N   Inz(RXS_YES)

      // This option determines whether RXS_Transmit verifies that the server
      //   certificate is for the server it is known as. Setting this to RXS_NO
      //   this in a production capacity is strongly discouraged.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SSLVerifyHost...
     D                                 N   Inz(RXS_YES)

      // Specify the HTTP protocol version being used.
      // Valid values: RXS_HTTP_VERSION_ANY, RXS_HTTP_VERSION_11,
      //   RXS_HTTP_VERSION_10
      // Default: RXS_HTTP_VERSION_ANY
     D   HTTPVersion                  4P 2 Inz(RXS_HTTP_VERSION_ANY)

      // Specify the authorization protocol being used.
      // Valid values: RXS_HTTP_AUTH_SCHEME_BASIC, RXS_HTTP_AUTH_SCHEME_BEARER
     D   HeaderAuthScheme...
     D                               32A   Varying

      // Specify the authorization credentials to be used with the specified
      //   HeaderAuthScheme. Basic authentication credentials will be encoded by
      //   RXS_Transmit.
      // Example: username:password
      // Example:
      //   fEfQzQBnlcOUYlFuxH7zN9v93xgL39HZZcvMNOuLPgiY6caMyPiSV73OCriG0oSf
     D   HeaderAuthCredentials...
     D                                     Like(RXS_Var8Kv_t)

      // Specify the user ID for this parameter if Basic Authentication is used
      //   on this request.
     D   BasicAuthUser...
     D                                     Like(RXS_Var1Kv_t)

      // Specify the password for this parameter if Basic Authentication is used
      //   on this request.
     D   BasicAuthPassword...
     D                                     Like(RXS_Var1Kv_t)

      // Specifies the URI used as a proxy for the request.
     D   ProxyURI                          Like(RXS_Var1Kv_t)

      // If using a proxy on this request that requires a user id and password,
      //   specify the proxy user id in this subfield.
     D   ProxyUser...
     D                                     Like(RXS_Var1Kv_t)

      // If using a proxy on this request that requires a user id and password,
      //   specify the proxy password in this subfield.
     D   ProxyPassword...
     D                                     Like(RXS_Var1Kv_t)

      // Specifies if HTTP headers should be sent with the request. RXS_YES will
      //   tell RXS_Transmit that all HTTP headers as well as the user defined
      //   headers are sent with the request. RXS_NO will tell RXS_Transmit that no
      //   HTTP headers are sent with the request - only the request data is sent.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SendHTTPHeaders...
     D                                 N   Inz(RXS_YES)

      // Specify any cookie data to be sent with this request. Further
      //   instructions for formatting this data is available at RFC2109.
     D   HeaderCookieData...
     D                                     Like(RXS_Var8Kv_t)

      // Paths to IFS files may be specified which contain cookie data. Further
      //   instructions for formatting this data is available at RFC2109. Size of
      //   array reduced from 50 to 10 in RXS 3.4.3.
     D   HeaderCookieFiles...
     D                                     Like(RXS_Var1Kv_t) Dim(10)

      // If calling a SOAP web service, the SOAPAction HTTP header field is
      //   generally required. This is typically surrounded by double quotes.
      // Example: "http://electrocommerce.org/abc#MyMessage"
     D   HeaderSOAPAction...
     D                                     Like(RXS_Var1Kv_t)

      // Some web applications may function differently depending upon the user
      //   agent specified.
      // Default: KrengelTech HTTP Client
     D   HeaderUserAgent...
     D                                     Like(RXS_Var1Kv_t)
     D                                     Inz('KrengelTech HTTP Client')

      // Populates the HTTP Accept header, used to specify which data type(s)
      //   this service will accept in a response.
     D   HeaderAccept                      Like(RXS_Var1Kv_t)

      // Populates the HTTP Accept-Encoding header, used to inform remote server
      //   of acceptable encodings such as gzip.
      // Default: gzip
     D   HeaderAcceptEncoding...
     D                                     Like(RXS_Var1Kv_t)
     D                                     Inz('gzip')

      // Populates the HTTP Host header, which specifies the host address and
      //   port number of the requested resource. This information is generally
      //   obtained from the service URI. This field is required for HTTP/1.1
      //   communications.
     D   HeaderHost                        Like(RXS_Var1Kv_t)

      // Sets the HTTP Referer header, which can be used to specify a URI from
      //   which this service address was obtained.
     D   HeaderReferer...
     D                                     Like(RXS_Var1Kv_t)

      // Sets the HTTP Connection header, which is used to specify
      //   connection-specific options.
     D   HeaderConnection...
     D                                     Like(RXS_Var1Kv_t)

      // The MIME type of the body of the request (used with POST requests).
      // Default: text/xml
     D   HeaderContentType...
     D                                     Like(RXS_Var1Kv_t)

      // Specifies the name of up to 50 custom HTTP headers sent with the
      //   request. These names are paired with the values, below.
     D   CustomHeaderName...
     D                               64A   Varying Dim(50)

      // Specifies the value of up to 50 custom HTTP headers sent with the
      //   request. These values are paired with the names, above. Size increased
      //   from 1k to 4k in RXS 3.4.3.
     D   CustomHeaderValue...
     D                                     Like(RXS_Var4Kv_t) Dim(50)

      // Populated by RXS_Transmit at the end of the request. Contains response
      //   information from the completed HTTP communication.
     D   HTTPResponse                      LikeDS(RXS_HTTPResponseDS_t)
     D                                     Inz(*LikeDS)

      // Internal use only
     D   InputPointer...
     D                                 *

      // Internal use only
     D   InputLength                 10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *

      // Internal use only
     D   OutputLength                10I 0

      // The CCSID of the data supplied in the Request parameter of
      //   RXS_Transmit(). If required, the data will be converted from the
      //   InputCcsid into the RequestCcsid before being transmitted.
      // Default: RXS_CCSID_JOB
     D   InputCCSID                  10I 0 Inz(RXS_CCSID_JOB)

      // The CCSID of the data to be returned in the Response parameter of
      //   RXS_Transmit().
      // Default: RXS_CCSID_JOB
     D   OutputCCSID                 10I 0 Inz(RXS_CCSID_JOB)

      // The CCSID of the data to be written to the logfile, when logfile
      //   creation is enabled.
      // Default: RXS_CCSID_UTF8
     D   LogFileCCSID                10I 0 Inz(RXS_CCSID_UTF8)

      // Controls whether new logging data will be appended to the logfile, or if
      //   any existing data will be overwritten.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   LogFileAppend...
     D                                 N   Inz(RXS_NO)

      // Specify a procedure pointer to a custom logging handler
     D   LogHandler...
     D                                 *   Procptr

      // Internal use only
     D   LogHandlerData...
     D                                 *

      // Internal use only
     D   LogHandlerDataLength...
     D                               10I 0

      // Specifies that the expect header should be sent with the request
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   EnableExpectHeader...
     D                                 N   Inz(RXS_NO)

      // Specify the IP address and optionally the port of the desired interface
      //   to bind to when transmiting to the remote server. When a port is
      //   specified, it will be separated from the IP with a colon: IP:Port
     D   LocalInterface...
     D                               64A   Varying

      // Allow RXS_Transmit() to use IPv6 protocol
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   EnableIPv6...
     D                                 N   Inz(RXS_NO)

      // Controls whether RXS_Transmit() should follow HTTP 3XX redirects
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   FollowRedirects...
     D                                 N   Inz(RXS_YES)
```


### RXS_HTTPResponseDS_t

```rpgle
     D RXS_HTTPResponseDS_t...
     D                 DS                  Qualified Template Inz

      // HTTP status code returned by the remote server
     D   StatusCode...
     D                               10I 0

      // A string containing all headers returned by the server. These headers
      //   are further parsed individually below.
     D   UnparsedHeaders...
     D                                     Like(RXS_Var64Kv_t)

      // An array of the parsed header names. These are paired with the header
      //   values, below.
     D   HeaderName                  64A   Varying Dim(50)

      // An array of the parsed header values. These are paired with the header
      //   names, above. Size increased from 1k to 4k in RXS 3.4.3.
     D   HeaderValue                       Like(RXS_Var4Kv_t) Dim(50)

      // Timestamp for the start of the connection
     D   TimeStart                     Z   Inz(*LOVAL)

      // Timestamp for the end of the connection
     D   TimeEnd                       Z   Inz(*LOVAL)

      // Total duration of the HTTP communication
     D   TimeDuration                 8F   Inz(*ZERO)
```



---

# RXS_GetStmf()

> Reads the contents of an IFS stream file into an RPG variable.

This subprocedure retrieves up to 16MB of data from a specified IFS stream file.

## Subprocedure Prototype

```rpgle
      // Returns data retrieved from the specified IFS stream file.
     D RXS_GetStmf...
     D                 PR                  Extproc('RXS_GetStmf') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // RXS_GetStmfDS_t data structure used to configure the RXS_GetStmf() call
      //   and specify the IFS stream file to read.
     D   DS                                Likeds(RXS_GetStmfDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Read Character String from IFS stream file

```rpgle
      *--------------------------------------------------------------
      * This example code reads the contents of the file 
      * /tmp/rxs_getstmf.txt into the Data field.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D GetStmfDS       DS                  LikeDS(RXS_GetStmfDS_t)
     D Data            S                   Like(RXS_Var64Kv_t)
      /free

       RXS_ResetDS( GetStmfDS : RXS_DS_TYPE_GETSTMF );
       GetStmfDS.Stmf = '/tmp/rxs_getstmf.txt';
       Data = RXS_GetStmf( GetStmfDS );
       *INLR = *ON;

      /end-free
```

### Ex. 2: Read Chunks of Characters from an IFS stream file while looping

```rpgle
      *--------------------------------------------------------------
      * This example code reads the contents of the file 
      * /tmp/rxs_getstmf.txt and outputs the entire file to STDOUT, 
      * even if the length of the file is greater than the Data variable.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D GetStmfDS       DS                  LikeDS(RXS_GetStmfDS_t)
     D Data            S                   Like(RXS_Var64Kv_t)
      /free

       RXS_ResetDS( GetStmfDS : RXS_DS_TYPE_GETSTMF );
       GetStmfDS.Stmf = '/tmp/rxs_getstmf.txt';
       GetStmfDS.ChunkedLength = %size(Data - 4);
       Dou %len(Data) = 0;
         Data = RXS_GetStmf( GetStmfDS );
         RXS_PutStdOut( Data );
       Enddo;
       *INLR = *ON;

      /end-free
```

## Data Structures

### RXS_GetStmfDS_t

```rpgle
     D RXS_GetStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_GETSTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies IFS path to a stream file to retrieve.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // The CCSID to convert the data read from the IFS stream file into.
     D   ToCcsid                     10I 0

      // Internal use only
     D   OutputPointer...
     D                                 *   Inz(*Null)

      // Internal use only
     D   OutputLength                10I 0

      // This is a returned value containing the number of bytes stored in the
      //   stream file.
     D   StmfSize                    10I 0

      // The length to use when "chunks" of the stream file are to be returned.
      //   See example 2.
     D   ChunkedLength...
     D                               10I 0

      // Internal use only
     D   ChunkedOffset...
     D                              128A
```



---

# RXS_ProcessStmf()

> Processes an IFS stream file in chunks, calling a handler subprocedure for each chunk.

This subprocedure can be used to create, copy, move, rename, or delete a specified IFS stream file.

## Subprocedure Prototype

```rpgle
     D RXS_ProcessStmf...
     D                 PR                  Extproc('RXS_ProcessStmf') Opdesc

      // Contains the IFS file path as well as configuration options to control
      //   how the data is written. May be one of a few possible data structures,
      //   listed below.
     D  pDS                                Like(RXS_Var64K_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create an IFS Stream File

```rpgle
      *--------------------------------------------------------------
      * This example code creates a stream file named new_file.xml in 
      * the IFS directory /tmp with CCSID 819.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D CreateStmfDS    DS                  LikeDS(RXS_CreateStmfDS_t)
     D                                     Inz(*LikeDS)
      /free

        RXS_ResetDS( CreateStmfDS : RXS_DS_TYPE_CREATESTMF );
        CreateStmfDS.Stmf = '/tmp/new_file.xml';
        CreateStmfDS.Ccsid = RXS_CCISD_ISO88591;
        RXS_ProcessStmf( CreateStmfDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Copy an IFS Stream File

```rpgle
      *--------------------------------------------------------------
      * This example code copies an IFS file from /tmp/new_file.xml to 
      * /tmp/copied_new_file.xml.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D CopyStmfDS      DS                  LikeDS(RXS_CopyStmfDS_t)
     D                                     Inz(*LikeDS)
      /free

        RXS_ResetDS( CopyStmfDS : RXS_DS_TYPE_COPYSTMF );
        CopyStmfDS.FromStmf = '/tmp/new_file.xml';
        CopyStmfDS.ToStmf = '/tmp/copied_new_file.xml';
        RXS_ProcessStmf( CopyStmfDS );

        *INLR = *ON;
      /end-free
```

### Ex. 3: Move an IFS Stream File

```rpgle
      *--------------------------------------------------------------
      * This example code moves an IFS file from /tmp/new_file.xml 
      * to /tmpdir/new_file.xml. Note that this can only be used
      * to move the file to a new directory, not to rename the file.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D MoveStmfDS      DS                  LikeDS(RXS_MoveStmfDS_t)
     D                                     Inz(*LikeDS)
      /free

        RXS_ResetDS( MoveStmfDS : RXS_DS_TYPE_MOVESTMF );
        MoveStmfDS.Stmf = '/tmp/new_file.xml';
        MoveStmfDS.ToDir = '/tmpdir/new_file.xml';
        RXS_ProcessStmf( MoveStmfDS );

        *INLR = *ON;
      /end-free
```

### Ex. 4: Delete an IFS Stream File

```rpgle
      *--------------------------------------------------------------
      * This example code deletes an IFS file named /tmp/moved_new_file.xml.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D DeleteStmfDS    DS                  LikeDS(RXS_DeleteStmfDS_t)
     D                                     Inz(*LikeDS)
      /free

        RXS_ResetDS( DeleteStmfDS : RXS_DS_TYPE_DELETESTMF );
        DeleteStmfDS.Stmf = '/tmp/new_file.xml';
        RXS_ProcessStmf( DeleteStmfDS );

        *INLR = *ON;
      /end-free
```

### Ex. 5: Rename an IFS Stream File

```rpgle
      *--------------------------------------------------------------
      * This example code renames an IFS file from /tmp/new_file.xml 
      * to /tmp/renamed_new_file.xml. Note that this can only be used
      * to rename the file, not move it to a new directory.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D RenameStmfDS    DS                  LikeDS(RXS_RenameStmfDS_t)
     D                                     Inz(*LikeDS)
      /free

        RXS_ResetDS( RenameStmfDS : RXS_DS_TYPE_RENAMESTMF );
        RenameStmfDS.FromStmf = '/tmp/new_file.xml';
        RenameStmfDS.ToStmf = '/tmp/renamed_new_file.xml';
        RXS_ProcessStmf( RenameStmfDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_CopyStmfDS_t

```rpgle
     D RXS_CopyStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_COPYSTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify the IFS file path to copy from.
     D   FromStmf                          Like(RXS_Var1Kv_t)

      // Specify the IFS file path to copy to.
     D   ToStmf                            Like(RXS_Var1Kv_t)

      // Specify the CCSID the copied file will use.
     D   ToCcsid                     10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_CreateStmfDS_t

```rpgle
     D RXS_CreateStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CREATESTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify IFS path where the file will be created.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // Specify the CCSID to create the file with.
     D   Ccsid                       10I 0

      // Internal use only
     D   Reserved                  2048A
```


### RXS_DeleteStmfDS_t

```rpgle
     D RXS_DeleteStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_DELETESTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify the IFS file path to delete.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // Internal use only
     D   Reserved                  2048A
```


### RXS_MoveStmfDS_t

```rpgle
     D RXS_MoveStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_MOVESTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify the IFS file path to move from.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // Specify an IFS file directory to move to. Note: You should only specify
      //   a directory path, not a full path to a file.
     D   ToDir                             Like(RXS_Var1Kv_t)

      // Internal use only
     D   Reserved                  2048A
```


### RXS_RenameStmfDS_t

```rpgle
     D RXS_RenameStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_RENAMESTMF)

     D   OnErrorMessageType...
     D                                5I 0

      // Specify the IFS file path to rename from.
     D   FromStmf                          Like(RXS_Var1Kv_t)

      // Specify the IFS file path to rename to.
     D   ToStmf                            Like(RXS_Var1Kv_t)

      // Internal use only
     D   Reserved                  2048A
```



---

# RXS_PutStmf()

> Writes data from an RPG variable to an IFS stream file.

This subprocedure writes the contents of a field up to 16MB in size to an IFS stream file.

## Subprocedure Prototype

```rpgle
     D RXS_PutStmf...
     D                 PR                  Extproc('RXS_PutStmf') Opdesc

      // Data to write to the IFS stream file.
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // RXS_PutStmfDS_t used to configure the write operation, such as the file
      //   path to write to.
     D  pDS                                LikeDS(RXS_PutStmfDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Output Character String to IFS stream file

```rpgle
      *--------------------------------------------------------------
      * This example code writes the value <data>The quick brown fox 
      * jumps over the lazy dog</data> to the file /tmp/rxs_putstmf.txt
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D PutStmfDS       DS                  LikeDS(RXS_PutStmfDS_t)
     D                                     Inz(*LikeDS)
     D Data            S                   Like(RXS_Var64Kv_t)
      /free

       Data = '<data>The quick brown fox jumps over the lazy dog</data>';

       RXS_ResetDS( PutStmfDS : RXS_DS_TYPE_PUTSTMF );
       PutStmfDS.Stmf = '/tmp/rxs_putstmf.txt';
       RXS_PutStmf( Data : PutStmfDS );

       *INLR = *ON;
       
      /end-free
```

## Data Structures

### RXS_PutStmfDS_t

```rpgle
     D RXS_PutStmfDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PUTSTMF)

     D   OnErrorMessageType...
     D                               10I 0

      // Specifies an IFS path to a stream file to write to.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // Determines whether to append the data to the file (RXS_YES), or to
      //   overwrite the file (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   Append                        N

      // The CCSID of the IFS stream file to be created/written to.
     D   StmfCcsid                   10I 0

      // The CCSID of the data being passed. Note: This is typically optional as
      //   internally RXS_PutStmf() will assume the data you're passing is the same
      //   CCSID as the current job.
     D   InputCcsid                  10I 0

      // Internal use only
     D   InputPointer...
     D                                 *   Inz(*Null)

      // Internal use only
     D   InputLength                 10I 0
```



---

# RXS_ComposeJsonArray()

> Adds a JSON array element to a parent JSON object or array.

This subprocedure is used to add a JSON array to a parent JSON Object or Array.

## Subprocedure Prototype

```rpgle
     D RXS_ComposeJsonArray...
     D                 PR                  Extproc('RXS_ComposeJsonArray')
     D                                     Opdesc
     D                                     LikeDS(RXS_JsonStructureDS_t)

      // Name to assign to JSON array. If being added to an array, the value of
      //   this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with child array

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a child array 
      * to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D ChildDS         DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        RXS_ResetDS( ChildDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        ChildDS = RXS_ComposeJsonArray( 'child' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"child":[]}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple child arrays

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON array, adds 3 child arrays 
      * to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D ChildDS         DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        RXS_ResetDS( ChildDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 3;
          ChildDS = RXS_ComposeJsonArray( *OMIT : RootDS );
          RXS_ComposeJsonString( *OMIT : %Char(i) : ChildDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // [["1"],["2"],["3"]]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_ComposeJsonBoolean()

> Adds a JSON boolean element (true/false) to a parent JSON object or array.

This subprocedure is used to add a JSON boolean element to a parent JSON Object or Array.

## Subprocedure Prototype

```rpgle
     D RXS_ComposeJsonBoolean...
     D                 PR                  Extproc('RXS_ComposeJsonBoolean')
     D                                     Opdesc

      // Name to assign to JSON boolean data element. If being added to an array,
      //   the value of this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // Pass RXS_JSON_TRUE if the JSON boolean data element should be true,
      //   RXS_JSON_FALSE if the JSON boolean data element should be false.
     D  pBoolean                       N   Const

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with boolean element

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a boolean 
      * data field to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonBoolean( 'value' : RXS_JSON_TRUE : RootDS );
        RXS_ComposeJsonBoolean( 'enabled' : RXS_JSON_FALSE : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"value":true,"enabled":false}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple boolean elements

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON array, adds 5 boolean data 
      * fields to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 5;
          RXS_ComposeJsonBoolean( *OMIT : RXS_JSON_TRUE : RootDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // [true,true,true,true,true]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_ComposeJsonNull()

> Adds a JSON null element to a parent JSON object or array.

This subprocedure is used to add a JSON **null** element to a parent JSON Object or Array.

## Subprocedure Prototype

```rpgle
     D RXS_ComposeJsonNull...
     D                 PR                  Extproc('RXS_ComposeJsonNull')
     D                                     Opdesc

      // Name to assign to JSON null data element. If being added to an array,
      //   the value of this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with null element

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a null data 
      * field to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonNull( 'value' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"value":null}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple null elements

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON array, adds 5 null data 
      * fields to it, retrieves the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 5;
          RXS_ComposeJsonNull( *OMIT : RootDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // [null,null,null,null,null]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_ComposeJsonNumber()

> Adds a JSON numeric element to a parent JSON object or array.

This subprocedure is used to add a JSON number element to a parent JSON Object or Array. The numeric data must be passed as an RPG character field, or converted by %Char().

## Subprocedure Prototype

```rpgle
     D RXS_ComposeJsonNumber...
     D                 PR                  Extproc('RXS_ComposeJsonNumber')
     D                                     Opdesc

      // Name to assign to JSON numeric data element. If being added to an array,
      //   the value of this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // Data to assign to JSON numeric data element. Data should be provided in
      //   a character field or converted by %Char().
      // Example: '1234', '1.76', '-2'
     D  pNumberAsString...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize)

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with number element

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a numeric
      * data field to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D itemprice       S              5P 2
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonNumber( 'id' : '7' : RootDS );

        itemprice = 12.50;
        RXS_ComposeJsonNumber( 'price' : %Char(itemprice) : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"id":7,"price":12.50}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple number elements

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON array, adds 5 numeric data
      * fields to it, retreives the created JSON, and then calls 
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 5;
          RXS_ComposeJsonNumber( *OMIT : %Char(i) : RootDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // [1,2,3,4,5]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_ComposeJsonObject()

> Adds a JSON object element to a parent JSON object or array.

This subprocedure is used to add a JSON object element to a parent JSON Object or Array.

## Subprocedure Prototype

```rpgle
     D RXS_ComposeJsonObject...
     D                 PR                  Extproc('RXS_ComposeJsonObject')
     D                                     Opdesc
     D                                     LikeDS(RXS_JsonStructureDS_t)

      // Name to assign to JSON object. If being added to an array, the value of
      //   this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with object element

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a child object
      * to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D ChildDS         DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        RXS_ResetDS( ChildDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        ChildDS = RXS_ComposeJsonObject( 'child' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"child":{}}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple object elements

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON array, adds 3 child objects 
      * to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D ChildDS         DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        RXS_ResetDS( ChildDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 3;
          ChildDS = RXS_ComposeJsonObject( *OMIT : RootDS );
          RXS_ComposeJsonString( 'value' : %Char(i) : ChildDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // [{"value": "1"},{"value": "2"},{"value": "3"}]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_ComposeJsonString()

> Adds a JSON string element to a parent JSON object or array.

This subprocedure is used to add a JSON string element to a parent JSON Object or Array.

## Subprocedure Prototype

```rpgle
     D JSON_ComposeJsonString...
     D                 PR                  Extproc('JSON_ComposeJsonString')
     D                                     Opdesc

      // Name to assign to JSON string data element. If being added to an array,
      //   the value of this parameter should be *OMIT.
     D  pName                              Const Like(RXS_Var64Kv_t)
     D                                     Options(*Omit:*Varsize)

      // Data to assign to JSON string data element.
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // RXS_JsonStructureDS_t data structure containing a parent JSON Object or
      //   Json Array.
     D  pStructureDS                       Const LikeDS(RXS_JsonStructureDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create JSON object with string element

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a string data
      * field to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonString( 'hello' : 'world' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"hello":"world"}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create JSON array with multiple string elements

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds 5 string data
      * fields to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );

        for i = 1 to 5;
          RXS_ComposeJsonString( *OMIT : %Char(i) : RootDS );
        endfor;

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // ["1","2","3","4","5"]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_CreateJson()

> Initializes a JSON document by creating a root object or array to which other JSON elements are attached.

This subprocedure is used to begin JSON composition. It creates a "root" JSON structure that other JSON objects, arrays, and data elements can be attached to.

## Subprocedure Prototype

```rpgle
      // Returns an RXS_JsonStructureDS_t which can be used by the RXS JSON
      //   composition subprocedures.
     D RXS_CreateJson...
     D                 PR                  Extproc('RXS_CreateJson') Opdesc
     D                                     LikeDS(RXS_JsonStructureDS_t)

      // RXS_CreateJsonDS_t data structure which controls how RXS_CreateJson()
      //   functions.
     D  pCreateJsonDS...
     D                                     LikeDS(RXS_CreateJsonDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create root JSON object

```rpgle
      *--------------------------------------------------------------
      * This example creates a root JSON structure that is a JSON 
      * object. It allows subsequent JSON objects, arrays, or data
      * elements to be attached in order to build a JSON document.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D ItemDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        RXS_ResetDS( ItemDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );
        // RootDS now contains a JSON structure that looks like this
        //  if printed:
        //
        //  { }

        // Create JSON object named "item" and attach it to RootDS
        ItemDS = RXS_ComposeJsonObject( 'item' : RootDS );
        // JSON now looks like this if printed:
        //
        // { "item": {} }

        // Create JSON number named "id" and attach it to ItemDS
        // Note that RXS_ComposeJsonNumber() uses character data,
        //  not numeric data, to allow it to handle the large
        //  numbers that JSON supports
        RXS_ComposeJsonNumber( 'id' : '7' : ItemDS );
        // JSON now looks like this if printed:
        //
        // { "item": { "id": 7 } }

        // Create JSON string named "name" and attach it to ItemDS
        RXS_ComposeJsonString( 'name' : 'headlight fluid' : ItemDS );
        // JSON now looks like this if printed:
        //
        // { "item": { "id": 7, "name": "headlight fluid" } }


        // Create JSON number named "price" and attach it to ItemDS
        RXS_ComposeJsonNumber( 'price' : '12.50' : ItemDS );

        // Enable JSON "prettification" (format to make more readable)
        //  and retrieve JSON into an RPG character data field
        CreateJsonDS.Prettify = RXS_YES;
        JSON = RXS_GetJsonString( CreateJsonDS );
        // Character data retrieved:
        //
        // {
        //    "item": {
        //      "id": 7,
        //      "name": "headlight fluid",
        //      "price": 12.50
        //    }
        // }

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

### Ex. 2: Create root JSON array

```rpgle
      *--------------------------------------------------------------
      * This example creates a root JSON structure that is a JSON
      * array. It allows subsequent JSON objects, arrays, or data
      * elements to be attached in order to build a JSON document.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D i               S              3U 0
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
        RootDS = RXS_CreateJson( CreateJsonDS );
        // RootDS now contains a JSON structure that looks like this
        //  if printed:
        //
        //  "[]"

        for i = 1 to 10;
          // Elements in a JSON array do not have a name associated
          //  with them, so the first parameter of the RXS_ComposeJson
          //  subprocedures should be set to *OMIT
          RXS_ComposeJsonNumber( *OMIT : %Char(i) : RootDS );
        endfor;
        // JSON now looks like:
        //
        // [1,2,3,4,5,6,7,8,9,10]

        // Enable JSON "prettification" (format to make more readable)
        //  and retrieve JSON into an RPG character data field
        CreateJsonDS.Prettify = RXS_YES;
        JSON = RXS_GetJsonString( CreateJsonDS );
        // Character data retrieved:
        //
        // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_CreateJsonDS_t

```rpgle
     D RXS_CreateJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CREATEJSON)

     D   OnErrorMessageType...
     D                                5I 0

      // If set to RXS_YES, JSON retrieved with RXS_GetJsonString() will be
      //   formatted with whitespace to be "pretty" and more human readable. If set
      //   to RXS_NO, JSON will be returned in a compact form with as little
      //   whitespace as possible.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   Prettify                      N   Inz(RXS_NO)

      // If set to RXS_JSON_STRUCTURE_OBJECT, the JSON document being created
      //   will begin with a root JSON object. If set to RXS_JSON_STRUCTURE_ARRAY,
      //   it will begin with a root JSON array.
      // Valid values: RXS_JSON_STRUCTURE_OBJECT, RXS_JSON_STRUCTURE_ARRAY
      // Default: RXS_JSON_STRUCTURE_OBJECT
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Determines whether to trim leading and trailing spaces from each value
      //   passed to RXS_ComposeJsonString() (RXS_YES), or to leave all spaces in
      //   place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimVariables...
     D                                 N   Inz(RXS_NO)
```


### RXS_JsonStructureDS_t

```rpgle
     D RXS_JsonStructureDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_JSONSTRUCTURE)

     D   OnErrorMessageType...
     D                                5I 0

      // Internal use only
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)
```



---

# RXS_DestroyJson()

> Frees memory allocated by RXS_CreateJson(); must be called once JSON composition or parsing is complete.

This subprocedure is used to end JSON composition. It is important to call this subprocedure before your program ends, even if it is ending abnormally.

## Subprocedure Prototype

```rpgle
     D RXS_DestroyJson...
     D                 PR                  Extproc('RXS_DestroyJson') Opdesc

      // RXS_CreateJsonDS_t data structure data structure.
     D  pJsonDS                            LikeDS(RXS_CreateJsonDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Create and Destroy Simple JSON Object

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a data field 
      * to it, and then calls RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonString( 'hello' : 'world' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_CreateJsonDS_t

```rpgle
     D RXS_CreateJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CREATEJSON)

     D   OnErrorMessageType...
     D                                5I 0

      // If set to RXS_YES, JSON retrieved with RXS_GetJsonString() will be
      //   formatted with whitespace to be "pretty" and more human readable. If set
      //   to RXS_NO, JSON will be returned in a compact form with as little
      //   whitespace as possible.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   Prettify                      N   Inz(RXS_NO)

      // If set to RXS_JSON_STRUCTURE_OBJECT, the JSON document being created
      //   will begin with a root JSON object. If set to RXS_JSON_STRUCTURE_ARRAY,
      //   it will begin with a root JSON array.
      // Valid values: RXS_JSON_STRUCTURE_OBJECT, RXS_JSON_STRUCTURE_ARRAY
      // Default: RXS_JSON_STRUCTURE_OBJECT
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Determines whether to trim leading and trailing spaces from each value
      //   passed to RXS_ComposeJsonString() (RXS_YES), or to leave all spaces in
      //   place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimVariables...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_FormatJson()

> Formats a JSON document with indentation and line feeds for readability, or minifies it to remove whitespace.

This subprocedure is used to format JSON. This can either be used to expand the JSON with additional whitespace, lines feeds, and indentation, or it can be used to take an existing JSON document and "minify" it to compress it into the smallest space possible by removing whitespace and line feeds. This subprocedure provides more formatting capabilities than the Prettify option available for [RXS_CreateJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_createjson.md) in the RXS_CreateJsonDS_t data structure.

## Subprocedure Prototype

```rpgle
      // Returns the JSON provided in pInput formatted based on the options set
      //   in pFormatJsonDS.
     D RXS_FormatJson...
     D                 PR                  Extproc('RXS_FormatJson') Opdesc
     D                                     Like(RXS_Var16Mv_t) Rtnparm

      // Input JSON to be reformatted
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // RXS_FormatJsonDS_t data structure which controls how RXS_FormatJson()
      //   functions.
     D  pFormatJsonDS                      LikeDS(RXS_FormatJsonDS_t)
     D                                     Options(*Varsize:*Nopass)
```


## Example Code

### Ex. 1: Prettify a JSON document

```rpgle
      *--------------------------------------------------------------
      * This example expands a JSON document similar to the Prettify
      * option available for RXS_CreateJson().
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D InJSON          S                   Like(RXS_Var64Kv_t)
     D OutJSON         S                   Like(RXS_Var64Kv_t)

     D FormatJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
      /free  

        // Set up a basic JSON document
        InJSON = '{ "item": { "id": 7, "name": "headlight fluid",'
               + ' "price": 12.50 } }';

        RXS_ResetDS( FormatJsonDS : RXS_DS_TYPE_FORMATJSON );
        // We're going to use the default options for FormatJsonDS
        //  which will "Prettify" our JSON similar to RXS_CreateJson().
        OutJSON = RXS_FormatJson( InJSON : FormatJsonDS );
        
        // OutJSON contents:
        //
        // {
        //   "item": {
        //     "id": 7,
        //     "name": "headlight fluid",
        //     "price": 12.50
        //   }
        // }   

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_FormatJsonDS_t

```rpgle
     D RXS_FormatJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_FORMATJSON)

      // Internal use only
     D   OnErrorMessageType...
     D                                5I 0

      // If set to RXS_YES, JSON returned will be formatted based on the
      //   remaining options in this data structure. If set to RXS_NO, JSON will be
      //   returned in a compact form with as little whitespace as possible.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   Prettify                      N   Inz(RXS_YES)

      // If set to RXS_YES, JSON returned will be split into multiple lines for
      //   readability. If set to RXS_NO, JSON will be returned in a single line.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   MultipleLines...
     D                                 N   Inz(RXS_YES)

      // If set to RXS_YES, JSON returned will use both the "carriage return" and
      //   "line feed" (CRLF) for line endings. If set to RXS_NO, JSON will only
      //   use the "line feed" (LF) for line endings.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   UseCRLF                       N   Inz(RXS_NO)

      // If set to RXS_YES, JSON returned will include spaces around the object
      //   brackets { } and the array brackets [ ]. If set to RXS_NO, JSON will not
      //   add spaces around the brackets.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SpaceBrackets...
     D                                 N   Inz(RXS_YES)

      // If set to RXS_YES, JSON returned will include spaces around the commas
      //   used to separate elements in objects and arrays. If set to RXS_NO, JSON
      //   will not add spaces around the commas.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SpaceCommas                   N   Inz(RXS_YES)

      // If set to RXS_YES, JSON returned will include spaces around the colons
      //   used to separate keys and values within an object. If set to RXS_NO,
      //   JSON will not add spaces around the colons.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   SpaceColons                   N   Inz(RXS_YES)

      // If set to RXS_YES, JSON returned will use Tab to indent instead of
      //   spaces. If set to RXS_NO, JSON will use spaces.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   IndentTabs                    N   Inz(RXS_NO)

      // Specifies how the JSON returned will be indented. This interacts with
      //   IndentTabs to control how many Tab or spaces will be used to indent.
      // Default: 2
     D   IndentSize                   3U 0 Inz(2)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies an IFS path to a JSON file to use as input instead of the
      //   pInput parm.
     D   InputStmf...
     D                                     Like(RXS_Var1Kv_t)

      // Specifies an IFS path to write the formatted output to.
     D   OutputStmf...
     D                                     Like(RXS_Var1Kv_t)

      // Internal use only
     D   InputPointer...
     D                                 *   Inz(*Null)

      // Internal use only
     D   InputLength...
     D                               10U 0 Inz(0)

      // Internal use only
     D   OutputPointer...
     D                                 *   Inz(*Null)

      // Internal use only
     D   OutputLength...
     D                               10U 0 Inz(0)
```



---

# RXS_GetJsonString()

> Returns the composed JSON document as a character string.

This subprocedure retrieves JSON composed by [RXS_CreateJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_createjson.md) and the other JSON composition subprocedures.

## New in RXS 3.5.0 

### Enhancements 

- Corrected an issue where retrieving a JSON string could sometimes enter a looping condition when certain hexadecimal characters were present in the composed JSON.

## Subprocedure Prototype

```rpgle
      // Returns the contents of the created JSON structure.
     D RXS_GetJsonString...
     D                 PR                  Extproc('RXS_GetJsonString')
     D                                     Opdesc
     D                                     Like(RXS_Var16Mv_t) Rtnparm

      // RXS_CreateJsonDS_t data structure which controls how RXS_CreateJson()
      //   functions.
     D  pCreateJsonDS...
     D                                     Const LikeDS(RXS_JsonCreateDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Retrieve JSON from JSON structure

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a string data
      * field to it, retrieves the created JSON, and then calls
      * RXS_DestroyJson() to clean up.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JSON            S                   Like(RXS_Var64Kv_t)

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonString( 'hello' : 'world' : RootDS );

        JSON = RXS_GetJsonString( CreateJsonDS );
        // JSON looks like:
        //
        // {"hello":"world"}

        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_CreateJsonDS_t

```rpgle
     D RXS_CreateJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CREATEJSON)

     D   OnErrorMessageType...
     D                                5I 0

      // If set to RXS_YES, JSON retrieved with RXS_GetJsonString() will be
      //   formatted with whitespace to be "pretty" and more human readable. If set
      //   to RXS_NO, JSON will be returned in a compact form with as little
      //   whitespace as possible.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   Prettify                      N   Inz(RXS_NO)

      // If set to RXS_JSON_STRUCTURE_OBJECT, the JSON document being created
      //   will begin with a root JSON object. If set to RXS_JSON_STRUCTURE_ARRAY,
      //   it will begin with a root JSON array.
      // Valid values: RXS_JSON_STRUCTURE_OBJECT, RXS_JSON_STRUCTURE_ARRAY
      // Default: RXS_JSON_STRUCTURE_OBJECT
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Determines whether to trim leading and trailing spaces from each value
      //   passed to RXS_ComposeJsonString() (RXS_YES), or to leave all spaces in
      //   place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimVariables...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_GetJsonStringLen()

> Returns the byte length of the composed JSON document string.

This subprocedure is used to calculate the length of a JSON string that has been composed by [RXS_CreateJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_createjson.md) and the other JSON composition subprocedures.

## Subprocedure Prototype

```rpgle
     D RXS_GetJsonStringLen...
     D                 PR            10U 0 Extproc('RXS_GetJsonStringLen')
     D                                     Opdesc

      // The CreateJson data structure where the JSON data is being composed.
      //   This is the data structure that was specified in the first parameter
      //   when RXS_CreateJson() was called.
     D  pCreateJsonDS                      LikeDS(RXS_CreateJsonDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Get length of JSON string in memory

```rpgle
      *--------------------------------------------------------------
      * This example creates a simple JSON object, adds a string data
      * field to it, and retrieves the length of the string in 
      * memory.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JsonLen         S             10U 0

     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
      /free
        RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
        RXS_ResetDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
        
        CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
        RootDS = RXS_CreateJson( CreateJsonDS );

        RXS_ComposeJsonString( 'hello' : 'world' : RootDS );
        // Composed JSON looks like: {"hello":"world"}

        JsonLen = RXS_GetJsonStringLen( CreateJsonDS );
        // JsonLen is: 17
        
        RXS_DestroyJson( CreateJsonDS );

        *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_CreateJsonDS_t

```rpgle
     D RXS_CreateJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_CREATEJSON)

     D   OnErrorMessageType...
     D                                5I 0

      // If set to RXS_YES, JSON retrieved with RXS_GetJsonString() will be
      //   formatted with whitespace to be "pretty" and more human readable. If set
      //   to RXS_NO, JSON will be returned in a compact form with as little
      //   whitespace as possible.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   Prettify                      N   Inz(RXS_NO)

      // If set to RXS_JSON_STRUCTURE_OBJECT, the JSON document being created
      //   will begin with a root JSON object. If set to RXS_JSON_STRUCTURE_ARRAY,
      //   it will begin with a root JSON array.
      // Valid values: RXS_JSON_STRUCTURE_OBJECT, RXS_JSON_STRUCTURE_ARRAY
      // Default: RXS_JSON_STRUCTURE_OBJECT
     D   JsonStructureType...
     D                                 N   Inz(RXS_JSON_STRUCTURE_OBJECT)

      // Internal use only
     D   JsonStructurePtr...
     D                                 *   Inz(*Null)

      // Specifies the CCSID of the data being passed into the JSON composition
      //   subprocedures. Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID that the JSON will be output as from
      //   RXS_GetJsonString(). Default is job CCSID.
      // Default: RXS_CCSID_JOB
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Determines whether to trim leading and trailing spaces from each value
      //   passed to RXS_ComposeJsonString() (RXS_YES), or to leave all spaces in
      //   place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimVariables...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_ParseJson()

> Event-based JSON parser that calls a handler subprocedure for each token (string, number, boolean, null, array/object start and end).

This subprocedure provides event-based parsing for JSON documents. Using this subprocedure requires a handler subprocedure.

All JSON parsing subprocedures must have the following prototype:

```rpgle
     D JsonHandler     PR              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const
```

or the free format equivalent:

```rpgle
Dcl-Pr JsonHandler Ind;
  pType Int(5) Const;
  pPath Like(RXS_Var64Kv_t) Const;
  pIndex Uns(10) Const;
  pData Pointer Const;
  pDataLen Uns(10) Const;
End-Pr;
```

The JSON parser recognizes the following element events:

| Event | pType Constant | pPath Format | Example |
| ----- | -------------- | ------------ | ------- |
| Array Start | `RXS_JSON_ARRAY` | `[*]` | `/phone[*]` |
| Array End | `RXS_JSON_ARRAY_END` | `[*]` | `/phone[*]` |
| Object Start | `RXS_JSON_OBJECT` | `/` | `/phone` |
| Object End | `RXS_JSON_OBJECT_END` | `/` | `/phone` |

Because the start and end events share the same path structure, you will also need to check the value of the pType parameter.

By default, the JSON parser returns all values as character data. A null value will be returned as the character string `null`. If you would prefer for null values to be returned as an empty character string, set the value of ParseMode to RXS_JSON_PARSE_NULL_EMPTY in the RXS_ParseJsonDS_t data structure.

In order to retrieve values in their respective RPG data types, set the value of ParseMode to RXS_JSON_PARSE_NATIVE (formerly ConvertDataToString to RXS_NO) in the RXS_ParseJsonDS_t data structure.

The JSON parser recognizes the following content types:

| JSON Data Type | pType Constant | RPG Data Type |
| -------------- | -------------- | ------------- |
| String | `RXS_JSON_STRING` | `A` |
| Boolean | `RXS_JSON_BOOLEAN` | `N` |
| Integer | `RXS_JSON_INTEGER` | `20I 0` |
| Double | `RXS_JSON_DOUBLE` | `8F` |
| Null | `RXS_JSON_NULL` | n/a |

For a detailed example of JSON parsing, please see this blog post: [Parsing JSON with RPG API Express](https://katointegrations.com/resources/blog/parsing-json-with-rpg-api-express-3-4/).

## New in RXS 3.5.3 

### Enhancements 

- Enhanced RXS_ParseJsonDS_t with new subfield ParseMode which accepts the constants RXS_JSON_PARSE_DEFAULT, RXS_JSON_PARSE_NATIVE, and RXS_JSON_PARSE_NULL_EMPTY to allow the parsed values made available in a parsing handler subprocedure to be returned as either converted to character data, provided as native RPG data types, or converted to character data but with null values converted to an empty string. ParseMode replaces/overlays the existing subfield ConvertDataToString which only allowed values to be converted to character data or native RPG data types

## New in RXS 3.5.0 

### Enhancements 

- RXS_ParseJson will now attempt to check for possible truncation of the input JSON before attempting to parse.
- Specific error information added to differentiate a parsing failure caused by JSON truncation from a failure caused by malformed JSON.

## Subprocedure Prototype

```rpgle
      // Returns an RXS_JsonStructureDS_t which can be used by the RXS JSON
      //   composition subprocedures.
     D RXS_ParseJson...
     D                 PR                  Extproc('RXS_ParseJson') Opdesc
     D                                     LikeDS(RXS_JsonStructureDS_t)

      // Holds the JSON data to be passed to the parsing subprocedure. Will be
      //   ignored if the Stmf subfield of the pParseJsonDS parameter is set.
     D  pInput                             Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // RXS_ParseJsonDS_t data structure which controls how RXS_ParseJson()
      //   functions.
     D  pParseJsonDS                       LikeDS(RXS_ParseJsonDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Simple Parse

```rpgle
      *--------------------------------------------------------------
      * This example parses a simple JSON structure stored in the
      * field 'JSON' and stores the data contained in fields 'id',
      * 'name', and 'price' in the corresponding global fields.
      *
      * At a high level, the JSON is parsed into the JsonHandler()
      * in small chunks. Then, a SELECT block is used to detect whether
      * or not the current JSON element matches one you'd like to
      * handle. If it does, you can extract the data using RXS_STR().
      *
      * Once the JSON has been entirely passed through JsonHandler(),
      * control returns to the main portion of the program.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JsonHandler     PR              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const

     D ParseJsonDS     DS                  LikeDS(RXS_ParseJsonDS_t)
     D JSON            S                   Like(RXS_Var64Kv_t)
     D id              S                   Like(RXS_Var1Kv_t)
     D name            S                   Like(RXS_Var1Kv_t)
     D price           S              6P 2
      /free
        // JSON structure looks like this:
        // {
        //    "item": {
        //      "id": 7,
        //      "name": "headlight fluid",
        //      "price": 12.50
        //    }
        // }
        JSON = '{ "item": { "id": 7, "name": "headlight fluid",' +
         '"price": 12.50 } }';

        RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );
        ParseJsonDS.Handler = %Paddr( JsonHandler );
        RXS_ParseJson( JSON : ParseJsonDS );

        RXS_JobLog( 'id: %s' : id );
        RXS_JobLog( 'name: %s' : name );
        RXS_JobLog( 'price: %s' : %Char(price) );

        *INLR = *ON;
      /end-free


     P JsonHandler     B                   Export
     D                 PI              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const
      /free

       select;
         when pPath = '/item/id';
           // All data in the JSON being parsed is returned
           //  as a character data field. If you need the data
           //  to be in a different data type, you can use %Int()
           //  or %Dec() to further convert it as shown further
           //  below.
           id = RXS_STR( pData : pDataLen );

         when pPath = '/item/name';
           name = RXS_STR( pData : pDataLen );

         when pPath = '/item/price';
           monitor;
             price = %Dec( RXS_STR( pData : pDataLen ) : 6 : 2 );
           on-error 105;
             RXS_JobLog( 'Error converting price!' );
             return *Off;
           endmon;


       endsl;

       return *On;
      /end-free
     P                 E
```

### Ex. 2: Parse JSON Data Types

```rpgle
      *--------------------------------------------------------------
      * This example parses a simple JSON structure stored in the
      * field 'JSON' and stores the data contained in fields 'id',
      * 'name', and 'price' in the corresponding global fields.
      *
      * This example differs from Example 1 in that instead of
      * returning all values as character data, values will be
      * returned using the RPG equivalents of the JSON data types.
      *
      * Doing this is somewhat more complicated, and may not be useful
      * in many JSON parsing situations, so this is not the default
      * behavior of RXS_ParseJson().
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JsonHandler     PR              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const

     D ParseJsonDS     DS                  LikeDS(RXS_ParseJsonDS_t)
     D JSON            S                   Like(RXS_Var64Kv_t)

     D id              S             20I 0
     D name            S                   Like(RXS_Var1Kv_t)
     D price           S              6P 2
     D sale            S               N

      /free
        // JSON structure looks like this:
        // {
        //    "item": {
        //      "id": 7,
        //      "name": "headlight fluid",
        //      "price": 12.50,
        //      "sale": true
        //    }
        // }
        JSON = '{ "item": { "id": 7, "name": "headlight fluid",' +
         '"price": 12.50, "sale": true } }';

        RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );
        ParseJsonDS.Handler = %Paddr( JsonHandler );

        // For RXS versions prior to 3.5.3:
        // By default this value is RXS_YES, meaning that all data
        //  returned will be converted into character data before
        //  it is available inside JsonHandler. Setting this to RXS_NO
        //  means that JsonHandler will instead be receiving RPG data
        //  types beyond character fields, such as integers, indicators,
        //  and floating point numbers.
        //ParseJsonDS.ConvertDataToString = RXS_NO;

        // For RXS versions 3.5.3 and higher:
        // By default this value is RXS_JSON_PARSE_DEFAULT, meaning that all data
        //  returned will be converted into character data before
        //  it is available inside JsonHandler. Setting this to RXS_JSON_PARSE_NATIVE
        //  means that JsonHandler will instead be receiving RPG data
        //  types beyond character fields, such as integers, indicators,
        //  and floating point numbers.
        ParseJsonDS.ParseMode = RXS_JSON_PARSE_NATIVE;

        RXS_ParseJson( JSON : ParseJsonDS );

        RXS_JobLog( 'id: %s' : %Char(id) );
        RXS_JobLog( 'name: %s' : name );
        RXS_JobLog( 'price: %s' : %Char(price) );
        if sale;
          RXS_JobLog( 'sale: %s' : 'true' );
        else;
          RXS_JobLog( 'sale: %s' : 'false' );
        endif;

        *INLR = *ON;
      /end-free


     P JsonHandler     B                   Export
     D                 PI              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const
      * To return native JSON types, we have to make use of Basing pointers
      *  and pay careful attention to the pType parameter. Please note that
      *  JSON has two types of numbers - integer and float. Floating point
      *  numbers are not precise, and values returned by RXS_ParseJson
      *  for floating-point numbers may differ slighltly from how they
      *  appear in the JSON. If your JSON data is going to contain
      *  decimal values it is advised to parse JSON using the default
      *  behavior where all parsed data is converted to character data
      *  before being returned to avoid a loss of precision.
     D BoolVal         S               N   Based(pData)
     D IntVal          S             20I 0 Based(pData)
     D FloatVal        S              8F   Based(pData)
      /free

       select;
         when pPath = '/item/id';
           // We know that the 'id' field is an integer, so we can simply
           //  do the following:
           id = IntVal;
           // Note that integers are always going to be returned as 8 byte
           //  integers, e.g. 20I 0. Using 3I 0, 5I 0, or 10I 0 will not work.

         when pPath = '/item/name';
           // 'name' is character data, and should be handled the same way
           name = RXS_STR( pData : pDataLen );

         when pPath = '/item/price';
           // 'price' is stored in the JSON as a decimal. We know from context
           //  that price is denoting a monetary value, which is a situation
           //  where the loss of precision using a floating point number brings
           //  is completely unacceptable. For example, instead of returning
           //  12.50 as we would expect, we might get 12.501 or 12.499. This
           //  would make a large difference to a business! However, as this
           //  is example code, below demonstrates returning floating point
           //  numbers:
           price = FloatVal;

         when pPath = '/item/sale';
           // We know that the 'id' field is a boolean value. It's important to
           //  know that a JSON character data value of 'true' is different from
           //  the JSON boolean data value of true. The two items below are not
           //  equivalent:
           //
           //  {
           //    "item1": 'true',
           //    "item2" : true
           //  }
           //  The RPG equivalent of a boolean data type is an indicator field.
           sale = BoolVal;
       endsl;
       return *On;
      /end-free
     P                 E
```

## Data Structures

### RXS_ParseJsonDS_t

```rpgle
     D RXS_ParseJsonDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSEJSON)

     D   OnErrorMessageType...
     D                                5I 0

      // Holds a PROCPTR to a JsonHandler subprocedure to handle all possible
      //   parsed JSON events.
     D   Handler...
     D                                 *   Procptr

      // Internal use only
     D   InputPointer...
     D                                 *   Inz(*Null)

      // Internal use only
     D   InputLength...
     D                               10U 0 Inz(0)

      // Specifies the CCSID of the JSON being parsed.
      // Default: Job CCSID
     D   InputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the CCSID the parsed data will be converted to.
      // Default: Job CCSID
     D   OutputCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies an IFS path to a JSON file to parse instead of the pInput
      //   parm.
     D   Stmf...
     D                                     Like(RXS_Var8Kv_t)

      // Deprecated as of RXS 3.5.3, repalced by ParseMode
      // Internal use only
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   ConvertDataToString...
     D                                 N   Inz(RXS_YES)

      // Specifies the CCSID the JSON paths will be converted to.
      // Default: Job CCSID
     D   PathCcsid...
     D                               10I 0 Inz(RXS_CCSID_JOB)

      // Specifies the method used to handle parsed data. Added in RXS 3.5.3.
      //   Replaces ConvertDataToString.
      // Valid values: RXS_JSON_PARSE_DEFAULT, RXS_JSON_PARSE_NATIVE,
      //   RXS_JSON_PARSE_EMPTY
      // Default: RXS_JSON_PARSE_DEFAULT
     D   ParseMode...
     D                                1A   Overlay(ConvertDataToString)
     D                                     Inz(RXS_JSON_PARSE_DEFAULT)
```



---

# RXS_GetJobCcsid()

> Returns the CCSID of the current job.

This subprocedure retrieves the CCSID of the current job. This may be used in character data conversion, such as with [RXS_Convert()](https://isupport.katointegrations.com/rxs/3.5/rxs_convert.md).

## Subprocedure Prototype

```rpgle
      // Returns the CCSID of the current job.
     D RXS_GetJobCcsid...
     D                 PR            10I 0 Extproc('RXS_GetJobCcsid') Opdesc
```


## Example Code

### Ex. 1: Job CCSID Retrieval

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves the CCSID, and then outputs it to
      * the job log using RXS_JobLog();
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D CCSID           S             10I 0
      /free
        CCSID = RXS_GetJobCCSID();

        RXS_JobLog( 'Job CCSID: %s' : %Char(CCSID) );

        *INLR = *ON;
      /end-free
```


---

# RXS_JobLog()

> Writes a message to the job log.

This subprocedure is used to easily output messages to the job log. A single character field can be passed in to output in a direct and unmodified fashion, or the API can be called with multiple character data parameters and utilize character replacement.

To perform character replacement, you'll need to include one or more placeholders, indicated by the following string: `%s`

Each Replacement# parameter will replace the corresponding `%s`. For example, Replacement1 will replace the first `%s`, while Replacement6 will replace the sixth `%s` present in the string.

## Subprocedure Prototype

```rpgle
     D RXS_JobLog...
     D                 PR                  Extproc('RXS_JobLog') Opdesc

      // Simple character data message to be printed to the job log, or a
      //   formatted character data string to use with replacements.
     D  pFormatString...                   Like(RXS_Var32Kv_t) Const
     D                                     Options(*Varsize)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement1...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement2...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement3...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement4...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement5...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement6...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement7...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement8...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement9...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)

      // Optional: Used as a replacement in the formatted character data string
      //   passed in Output.
     D  pReplacement10...
     D                                     Like(RXS_Var1Kv_t) Const
     D                                     Options(*Varsize: *Nopass)
```


## Example Code

### Ex. 1: Write to Job Log Without Placeholders

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_JobLog() in the most simple form to just
      * write a basic character string to the job log.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JobLogMsg       S                   Like(RXS_Var1Kv_t)
      /free
      
        JobLogMsg = 'Hello world!';
        RXS_JobLog( JobLogMsg );

        //Job Log Output: Hello world!

        *INLR = *ON;
      /end-free
```

### Ex. 2: Write to Job Log With a Single Placeholder

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_JobLog() with a string which contains a
      * single replacement placeholder.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JobLogMsg       S                   Like(RXS_Var1Kv_t)
      /free

        JobLogMsg = 'Hello %s!';
        RXS_JobLog( JobLogMsg : 'Bob');

        //Job Log Output: Hello Bob!

        *INLR = *ON;
      /end-free
```

### Ex. 3: Write to Job Log With Multiple Placeholders

```rpgle
      *--------------------------------------------------------------
      * This example uses RXS_JobLog() with a string which contains
      * multiple replacement placeholders.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D JobLogMsg       S                   Like(RXS_Var1Kv_t)
      /free

        JobLogMsg = 'Hello %s! Your lucky number is: %s';
        RXS_JobLog( JobLogMsg : 'Bob' : %Char(7) );

        //Job Log Output: Hello Bob! Your lucky number is: 7

        *INLR = *ON;
      /end-free
```


---

# RXS_ResetDS()

> Resets all fields in an RXS data structure to their default values.

This subprocedure initializes a data structure with default values. Always intialize a data structure before use and always use this API instead of the RESET opcode.

A full list of DS-type constants is available in the RXSCB copybook.

## Subprocedure Prototype

```rpgle
     D RXS_ResetDS...
     D                 PR                  Extproc('RXS_ResetDS') Opdesc

      // Holds the data structure to be initialized with default values.
     D  pOutputDS                          Like(RXS_Var1M_t)
     D                                     Options(*Varsize)

      // The type of data structure to be initialized with default values.
     D  pTypeOfDS                     5I 0 Const
```


## Example Code

### Ex. 1: Intialize all current RXS3 data structure types

```rpgle
      *--------------------------------------------------------------
      * This example code initializes most of the current RXS3 data 
      * structure types using RXS_ResetDS().
      *--------------------------------------------------------------
     D gComposeDS      DS                  LikeDS(RXS_ComposeDS_t)
     D gTransmitDS     DS                  LikeDS(RXS_TransmitDS_t)
     D gParseDS        DS                  LikeDS(RXS_ParseDS_t)
     D gCatchThrowErrorDS...
     D                 DS                  LikeDS(RXS_CatchThrowErrorDS_t)
     D gConvertBase64DS...
     D                 DS                  LikeDS(RXS_ConvertBase64DS_t)
     D gConvertBase64UrlDS...
     D                 DS                  LikeDS(RXS_ConvertBase64UrlDS_t)
     D gConvertCaseDS  DS                  LikeDS(RXS_ConvertCaseDS_t)
     D gConvertCcsidDS...
     D                 DS                  LikeDS(RXS_ConvertCcsidDS_t)
     D gConvertUrlPercentDS...
     D                 DS                  LikeDS(RXS_ConvertUrlPercentDS_t)
     D gConvertUserDefinedDS...
     D                 DS                  LikeDS(RXS_ConvertUserDefinedDS_t)
     D gConvertXmlEntitiesDS...
     D                 DS                  LikeDS(RXS_ConvertXmlEntitiesDS_t)
     D gCopyStmfDS     DS                  LikeDS(RXS_CopyStmfDS_t)
     D gCreateStmfDS   DS                  LikeDS(RXS_CreateStmfDS_t)
     D gDeleteStmfDS   DS                  LikeDS(RXS_DeleteStmfDS_t)
     D gGetStmfDS      DS                  LikeDS(RXS_GetStmfDS_t)
     D gPutStmfDS      DS                  LikeDS(RXS_PutStmfDS_t)
     D gMoveStmfDS     DS                  LikeDS(RXS_MoveStmfDS_t)
     D gRenameStmfDS   DS                  LikeDS(RXS_RenameStmfDS_t)
     D gOpenDomDS      DS                  LikeDS(RXS_OpenDomDS_t)
     D gParseDomDS     DS                  LikeDS(RXS_ParseDomDS_t)
     D gGetStdInDS     DS                  LikeDS(RXS_GetStdInDS_t)
     D gPutStdOutDS    DS                  LikeDS(RXS_PutStdOutDS_t)
     D gAesCryptDS     DS                  LikeDS(RXS_AesCryptDS_t)
     D gMd5CryptDS     DS                  LikeDS(RXS_Md5CryptDS_t)
     D gShaCryptDS     DS                  LikeDS(RXS_ShaCryptDS_t)
     D gXsdValidationDS...
     D                 DS                  LikeDS(RXS_XsdValidationDS_t)
     D gCreateJsonDS   DS                  LikeDS(RXS_CreateJsonDS_t)
     D gParseJsonDS    DS                  LikeDS(RXS_ParseJsonDS_t)
     D gFormatJsonDS   DS                  LikeDS(RXS_FormatJsonDS_t)
      /free
       RXS_ResetDS( gComposeDS : RXS_DS_TYPE_COMPOSE );
       RXS_ResetDS( gTransmitDS : RXS_DS_TYPE_TRANSMIT );
       RXS_ResetDS( gParseDS : RXS_DS_TYPE_PARSE );
       RXS_ResetDS( gCatchThrowErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
       RXS_ResetDS( gConvertBase64DS : RXS_DS_TYPE_CONVERTBASE64 );
       RXS_ResetDS( gConvertBase64UrlDS : RXS_DS_TYPE_CONVERTBASE64URL );
       RXS_ResetDS( gConvertCaseDS : RXS_DS_TYPE_CONVERTCASE );
       RXS_ResetDS( gConvertCcsidDS : RXS_DS_TYPE_CONVERTCCSID );
       RXS_ResetDS( gConvertUrlPercentDS : RXS_DS_TYPE_CONVERTURLPERCENT );
       RXS_ResetDS( gConvertUserDefinedDS : RXS_DS_TYPE_CONVERTUSERDEFINED );
       RXS_ResetDS( gConvertXmlEntitiesDS : RXS_DS_TYPE_CONVERTXMLENTITIES );
       RXS_ResetDS( gCopyStmfDS : RXS_DS_TYPE_COPYSTMF );
       RXS_ResetDS( gCreateStmfDS : RXS_DS_TYPE_CREATESTMF );
       RXS_ResetDS( gDeleteStmfDS : RXS_DS_TYPE_DELETESTMF );
       RXS_ResetDS( gGetStmfDS : RXS_DS_TYPE_GETSTMF );
       RXS_ResetDS( gPutStmfDS : RXS_DS_TYPE_PUTSTMF );
       RXS_ResetDS( gMoveStmfDS : RXS_DS_TYPE_MOVESTMF );
       RXS_ResetDS( gRenameStmfDS : RXS_DS_TYPE_RENAMESTMF );
       RXS_ResetDS( gOpenDomDS : RXS_DS_TYPE_OPENDOM );
       RXS_ResetDS( gParseDomDS : RXS_DS_TYPE_PARSEDOM );
       RXS_ResetDS( gGetStdInDS : RXS_DS_TYPE_GETSTDIN );
       RXS_ResetDS( gPutStdOutDS : RXS_DS_TYPE_PUTSTDOUT );
       RXS_ResetDS( gAesCryptDS : RXS_DS_TYPE_AESCRYPT );
       RXS_ResetDS( gMd5CryptDS : RXS_DS_TYPE_MD5CRYPT );
       RXS_ResetDS( gShaCryptDS : RXS_DS_TYPE_SHACRYPT );
       RXS_ResetDS( gXsdValidationDS : RXS_DS_TYPE_XSDVALIDATION );
       RXS_ResetDS( gCreateJsonDS : RXS_DS_TYPE_CREATEJSON );
       RXS_ResetDS( gParseJsonDS : RXS_DS_TYPE_PARSEJSON );
       RXS_ResetDS( gFormatJsonDS : RXS_DS_TYPE_FORMATJSON );
      /end-free
```


---

# RXS_STR()

> Converts a numeric value to a trimmed character string.

This subprocedure should be used inside an [RXS_Parse()](https://isupport.katointegrations.com/rxs/3.5/rxs_parse.md) or [RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsejson.md) parsing subprocedure to convert the parsed data pointer into a character data field.

## Subprocedure Prototype

```rpgle
      // Returns the character data field extracted from the Pointer parameter.
     D RXS_STR         PR                  Extproc('RXS_STR') Opdesc
     D                                     Like(RXS_Var16Mv_t) Rtnparm

      // Pointer containing character data parsed using an RXS_Parse() parsing
      //   subprocedure.
     D  pPointer                       *   Const

      // Length of the parsed character data contained in the Pointer parameter.
     D  pPointerLength...
     D                               10I 0 Const
```


## Example Code

### Ex. 1

```rpgle
      *--------------------------------------------------------------
      * This example code converts the contents of the pData pointer to character
      *  data and stores it in the field gCelcius.
      * This process may be used in a RXS_Parse() parsing subprocedure to convert data
      *  retrieved from an XML element to character data for further processing.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gCelcius        S              5A   Varying
     D pData                           *   Value
     D pDataLen                      10I 0 Value
      /free
       gCelsius = RXS_STR( pData : pDataLen );

       *INLR = *ON;
      /end-free
```


---

# RXS_ComposeSection()

> Renders a named section from a loaded template, appending the output to the compose buffer.

This subprocedure writes the named section to the compose engine buffer.

**Note**: This procedure will throw an error if it has been called without the composition engine being initialized via [RXS_StartComposeEngine()](https://isupport.katointegrations.com/rxs/3.5/rxs_startcomposeengine.md).

## Subprocedure Prototype

```rpgle
     D RXS_ComposeSection...
     D                 PR                  Extproc('RXS_ComposeSection')

      // The name of the section to be written to the template engine buffer.
     D  pSection                     50A   Varying Const
```


## Example Code

### Ex. 1: Write a Template Section

```rpgle
      *--------------------------------------------------------------
      * This example demonstrates calling RXS_ComposeSection() to write the template
      *  section 'content'. This section would be defined in the XML template EXAMPLE
      *  like so:
      *   ::content
      *   <element>...</element>
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,EXAMPLE

      /free 
       RXS_ComposeSection( content );

       *INLR = *ON;
      /end-free
```


---

# RXS_ComposeVariable()

> Substitutes a named variable placeholder in the current template section with a supplied value.

This searches the currently loaded template for the specified Variable, and replaces it with the Value parameter. If variable is used multiple times, all occurrences will be replaced.

**Note**: This procedure will throw an error if it has been called without the composition engine being initialized via [RXS_StartComposeEngine()](https://isupport.katointegrations.com/rxs/3.5/rxs_startcomposeengine.md).

## Subprocedure Prototype

```rpgle
     D RXS_ComposeVariable...
     D                 PR                  Extproc('RXS_ComposeVariable')

      // The name of the variable to be replaced in the currently loaded
      //   template.
     D  pVariable                    30A   Varying Const

      // The value which will replace the specified Variable.
     D  pValue                             Const Like(RXS_Var16Mv_t)
     D                                     Options(*Omit)
```


## Example Code

### Ex. 1: Write a Template Variable

```rpgle
      *--------------------------------------------------------------
      * This example demonstrates calling RXS_ComposeVariable() to assign the 
      *  value of the gFahrenheit field to the template variable 'fahrenheit'. 
      *  This variable would be defined in the XML template EXAMPLE like so:
      *   <fahrenheit>.:fahrenheit:.</fahrenheit>
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,EXAMPLE

     D gFahrenheit     S              3P 1

      /free
       gFahrenheit = 78.6;

       RXS_ComposeVariable( fahrenheit : %Char(gFahrenheit) );

       *INLR = *ON;
      /end-free
```


---

# RXS_GetComposeBuffer()

> Returns the current contents of the compose engine output buffer as a character string.

Retrieves the current composition engine buffer into a variable. The buffer is cleared upon successful retrieval.

## Subprocedure Prototype

```rpgle
      // Returns the current contents of the compose engine buffer.
     D RXS_GetComposeBuffer...
     D                 PR                  Extproc('RXS_GetComposeBuffer')
     D                                     Opdesc Like(RXS_Var16Mv_t)
     D                                     Rtnparm
```


## Example Code

### Ex. 1: Retrieve XML from Buffer

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves the composition engine buffer and 
      *  stores it in the field gXmlRequest. This process might be 
      *  used when calling a web service.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXmlRequest     S                   Like(RXS_Var64Kv_t)
      /free 
       gXmlRequest = RXS_GetComposeBuffer();

       *INLR = *ON;
      /end-free
```


---

# RXS_GetComposeBufferLen()

> Returns the byte length of the compose engine output buffer.

Returns the current length of the composed data in the buffer.

## Subprocedure Prototype

```rpgle
      // Returns the current length of the composed data in the buffer
     D RXS_GetComposeBufferLen...
     D                 PR            10U 0 Extproc('RXS_GetComposeBufferLen')
```


## Example Code

### Ex. 1: Retrieve length of XML from Buffer

```rpgle
      *--------------------------------------------------------------
      * This example code retrieves the composition engine buffer and 
      *  length and stores it in the field gXmlRequestLen.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXmlRequestLen     S          10U 0

      /free 
       gXmlRequestLen = RXS_GetComposeBufferLen();

       *INLR = *ON;
      /end-free
```


---

# RXS_StartComposeEngine()

> Initializes the RXS template compose engine, loading a compiled template (.tpl) file.

This subprocedure initializes the compose engine with a specific template file. This is utilized primarily to build XML but could be used to build any type of character data.

## Subprocedure Prototype

```rpgle
     D RXS_StartComposeEngine...
     D                 PR                  Extproc('RXS_StartComposeEngine')
     D                                     Opdesc

      // Pass in an RXS_ComposeDS_t to control how the composition engine
      //   functions.
     D  pDS                                LikeDS(RXS_ComposeDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
      *--------------------------------------------------------------
      * This example code initializes the composition engine for the EXAMPLE template and 
      *  configures it with the procedure address for the Template procedure.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,EXAMPLE

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)
      /free
       RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
       ComposeDS.TemplateProcedure = %Paddr(Template);
       RXS_StartComposeEngine( ComposeDS );

       *INLR = *ON;
      /end-free

     P Template        B
     D                 PI
     D  p                         65535A   Varying
      /copy QRPGLETPL,EXAMPLE
     P                 E
```

### Ex. 2

```rpgle
      *--------------------------------------------------------------
      * This example code initializes the composition engine for the EXAMPLE template and 
      *  configures it with the procedure address for the Template procedure. It also
      *  sets configuration values to trim whitespace from template lines and to
      *  omit line control characters.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,EXAMPLE

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t) 
      /free
       RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
       ComposeDS.TemplateProcedure = %Paddr(Template);
       ComposeDS.TrimTemplateLines = RXS_YES;
       ComposeDS.OmitLineControls = RXS_YES;
       RXS_StartComposeEngine( ComposeDS );

       *INLR = *ON;
      /end-free

     P Template        B
     D                 PI
     D  p                         65535A   Varying
      /copy QRPGLETPL,EXAMPLE
     P                 E
```

### Ex. 3

```rpgle
      *--------------------------------------------------------------
      * This example code initializes the composition engine for the EXAMPLE template and 
      *  configures it with the procedure address for the Template procedure. It also
      *  sets a configuration value to skip composing lines on which there are
      *  uncomposed varaibles.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,EXAMPLE

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t) 
      /free
       RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
       ComposeDS.TemplateProcedure = %Paddr(Template);
       ComposeDS.OmitUncomposedLines = RXS_YES;
       RXS_StartComposeEngine( ComposeDS );

       *INLR = *ON;
      /end-free

     P Template        B
     D                 PI
     D  p                         65535A   Varying
      /copy QRPGLETPL,EXAMPLE
     P                 E
```

### Ex. 4

```rpgle
      *--------------------------------------------------------------
      * This example code initializes the composition engine for the EXAMPLE template and 
      *  configures it with an IFS file containing a template file.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t) 
      /free
       RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
       ComposeDS.Stmf = '/www/RXS/templates/geturi2.tpl';
       RXS_StartComposeEngine( ComposeDS );

       *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_ComposeDS_t

```rpgle
     D RXS_ComposeDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     Like(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_COMPOSE)

     D   OnErrorMessageType...
     D                                5I 0

      // Pointer to the address of the subprocedure that will compose the XML or
      //   other character based content.
     D   TemplateProcedure...
     D                                 *   Procptr

      // IFS location where a log file will be created during composition.
     D   LogFile                           Like(RXS_Var1Kv_t)

      // IFS location where a dynamically processed template file is located. If
      //   specified, the TemplateProcedure option is ignored.
     D   Stmf                              Like(RXS_Var1Kv_t)

      // Determines whether to append the data to the log file (RXS_YES), or to
      //   overwrite the log file (RXS_NO). Default: RXS_NO
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   LogFileAppend...
     D                                 N   Inz(RXS_NO)

      // Determines whether to compose all characters in the CCSID of the Stmf
      //   (RXS_YES), or to use the CCSID of the current job (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   UseStmfCCSID                  N

      // Determines whether to initialize the engine (RXS_YES), or to leave any
      //   previous configuration and composed content in place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_YES
     D   Initialize                    N   Inz(RXS_YES)

      // Determines whether to allow templates up to 256K in length (RXS_YES), or
      //   to use 64K (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   LargeTemplate...
     D                                 N   Inz(RXS_NO)

      // Determines whether to strip the line controls from the end of each
      //   template line (RXS_YES), or to leave line controls in place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   OmitLineControls...
     D                                 N   Inz(RXS_NO)

      // Determines whether to trim leading and trailing spaces from the start
      //   and end of each template line (RXS_YES), or to leave all spaces in place
      //   (RXS_NO). Note that spaces are only trimmed from template content and
      //   not from content passed via RXS_ComposeVariable().
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimTemplateLines...
     D                                 N   Inz(RXS_NO)

      // Determines whether to omit template lines with variables that have not
      //   been composed by RXS_ComposeVariable() (RXS_YES), or to compose all
      //   template lines (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   OmitUncomposedLines...
     D                                 N   Inz(RXS_NO)

      // Determines whether to trim leading and trailing spaces from each
      //   variable passed to RXS_ComposeVariable() (RXS_YES), or to leave all
      //   spaces in place (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   TrimVariables...
     D                                 N   Inz(RXS_NO)

      // Determines whether to perform XML entity encoding on each variable
      //   passed to RXS_ComposeVariable() (RXS_YES), or to leave all entities
      //   unencoded (RXS_NO).
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   EncodeVariables...
     D                                 N   Inz(RXS_NO)
```



---

# RXS_CloseDom()

> Frees memory allocated by RXS_OpenDom(); must be called once DOM parsing is complete.

This subprocedure is used to clean up after utilizing the DOM parsing API [RXS_OpenDom()](https://isupport.katointegrations.com/rxs/3.5/rxs_opendom.md). It only needs to be called on the primary DOM data structure. You should not attempt to close a DOM data structure created as a result of calling RXS_ParseDomToDom().

This subprocedure must be called at the end of all DOM parsing to properly free up memory and avoid performance impacts.

## Subprocedure Prototype

```rpgle
     D RXS_CloseDom    PR                  Extproc('RXS_CloseDom') Opdesc

      // The RXS_ParseDomDS_t data structure previously initialized and used as
      //   part of RXS_OpenDom()
     D  pDS                                LikeDS(RXS_ParseDomDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Closing DOM Parsing

```rpgle
**FREE
// This example demonstrates completing a DOM parsing session by using
//  RXS_CloseDom on the RXS_ParseDomDS_t datastructure originally returned
//  by RXS_OpenDom. It's important to call RXS_CloseDom once DOM parsing has
//  been completed to free memory used by the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

Dcl-S gXML Like(RXS_Var64Kv_t);

gXML = '<?xml version="1.0" encoding="UTF-8"?>'
     + '<bookstore xmlns:test="testnamespace">'
     + '<book category="children">'
     + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
     + '<author>E. B. White</author>'
     + '<year>1952</year>'
     + '<price>5.99</price>'
     + '<review><text>What a great book!</text></review>'
     + '<review><text>Highly recommended.</text></review>'
     + '</book>'
     + '<book category="cooking">'
     + '<title lang="en">Everyday Italian</title>'
     + '<author>Giada De Laurentiis</author>'
     + '<year>2005</year>'
     + '<price>30.00</price>'
     + '</book>'
     + '</bookstore>';

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );
RootDomDS = RXS_OpenDom( gXML );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToText( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
endfor;

RXS_CloseDom( RootDomDS );

return;
```

## Data Structures

### RXS_ParseDomDS_t

```rpgle
     D RXS_ParseDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSEDOM)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)

      // Contains the current count of XML nodes tracked by this data structure.
     D   NodeCount                   10U 0

     D   NodeType                    10I 0

      // Internal use only
     D   xmlPtr                        *

      // Internal use only
     D   docPtr                        *

      // Internal use only
     D   contextPtr                    *

      // Internal use only
     D   dictPtr                       *

      // Internal use only
     D   currentPtr                    *

      // Internal use only
     D   parentPtr                     *

      // Internal use only
     D   Reserved                  4096A
```



---

# RXS_OpenDom()

> Initializes DOM-based XML parsing from a character field or IFS stream file, enabling XPath-based node navigation.

This subprocedure initializes DOM parsing for a specified chunk of XML.

## Subprocedure Prototype

```rpgle
      // Returns an RXS_ParseDOMDS_t which contains information and pointers used
      //   by subsequent DOM parsing APIs.
     D RXS_OpenDom     PR                  Extproc('RXS_OpenDom') Opdesc
     D                                     Like(RXS_ParseDOMDS_t)

      // Chunk of XML to be used for subsequent DOM operations.
     D  pXML                               Like(RXS_Var16Mv_t) Const
     D                                     Options(*Omit:*Varsize)

      // Holds RXS_OpenDomDS_t used to set up initial DOM parsing options.
     D  pDS                                LikeDS(RXS_OpenDomDS_t)
     D                                     Options(*Nopass:*Varsize)
```


## Example Code

### Ex. 1: Begin DOM Parse on Character Field

```rpgle
**FREE
// This example demonstrates starting a DOM parsing session by using
//  RXS_OpenDom and then performing additional queries on the RXS_ParseDomDS_t
//  datastructure it returned. It's important to call RXS_CloseDom once DOM
//  parsing has been completed to free memory used by the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

Dcl-S gXML Like(RXS_Var64Kv_t);

gXML = '<?xml version="1.0" encoding="UTF-8"?>'
     + '<bookstore xmlns:test="testnamespace">'
     + '<book category="children">'
     + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
     + '<author>E. B. White</author>'
     + '<year>1952</year>'
     + '<price>5.99</price>'
     + '<review><text>What a great book!</text></review>'
     + '<review><text>Highly recommended.</text></review>'
     + '</book>'
     + '<book category="cooking">'
     + '<title lang="en">Everyday Italian</title>'
     + '<author>Giada De Laurentiis</author>'
     + '<year>2005</year>'
     + '<price>30.00</price>'
     + '</book>'
     + '</bookstore>';

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );
RootDomDS = RXS_OpenDom( gXML );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToText( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
endfor;

RXS_CloseDom( RootDomDS );

return;
```

### Ex. 2: Begin DOM Parse on IFS STMF

```rpgle
**FREE
// This example demonstrates starting a DOM parsing session by using
//  RXS_OpenDom to parse an XML file stored in an IFS STMF, and then performing
//  additional queries on the RXS_ParseDomDS_t datastructure it returned. It's
//  important to call RXS_CloseDom once DOM parsing has been completed to free
//  memory used by the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );
RXS_ResetDS( OpenDomDS : RXS_DS_TYPE_OPENDOM );

OpenDomDS.Stmf = '/tmp/bookstore.xml';
// For this example, the IFS file /tmp/bookstore.xml contains
//  XML that looks like this:

// <?xml version="1.0" encoding="UTF-8"?>
// <bookstore xmlns:test="testnamespace">
// <book category="children">
// <title lang="en"><![CDATA[Charlotte's Web]]></title>
// <author>E. B. White</author>
// <year>1952</year>
// <price>5.99</price>
// <review><text>What a great book!</text></review>
// <review><text>Highly recommended.</text></review>
// </book>
// <book category="cooking">
// <title lang="en">Everyday Italian</title>
// <author>Giada De Laurentiis</author>
// <year>2005</year>
// <price>30.00</price>
// </book>
// </bookstore>

RootDomDS = RXS_OpenDom( *Omit : OpenDomDS );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToText( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
endfor;

RXS_CloseDom( RootDomDS );

return;
```

## Data Structures

### RXS_OpenDomDS_t

```rpgle
     D RXS_OpenDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_OPENDOM)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)
```


### RXS_ParseDomDS_t

```rpgle
     D RXS_ParseDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

     D   OnErrorMessageType...
     D                               10I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)

      // Contains the current count of XML nodes tracked by this data structure.
     D   NodeCount                   10U 0

     D   NodeType                    10I 0

      // Internal use only
     D   xmlPtr                        *

      // Internal use only
     D   docPtr                        *

      // Internal use only
     D   contextPtr                    *

      // Internal use only
     D   dictPtr                       *

      // Internal use only
     D   currentPtr                    *

      // Internal use only
     D   parentPtr                     *

      // Internal use only
     D   Reserved                  4096A
```



---

# RXS_ParseDomToDom()

> Navigates the DOM to a node matching an XPath expression and returns a child DOM handle for further traversal.

This subprocedure retrieves a new RXS_ParseDomDS_t data structure from a parent XML structure, allowing you to retrieve and work with a subset of a larger XML document.

## Subprocedure Prototype

```rpgle
      // Returns an RXS_ParseDomDS_t which contains information and pointers used
      //   by subsequent DOM parsing APIs.
     D RXS_ParseDomToDom...
     D                 PR                  Extproc('RXS_ParseDomToDom') Opdesc
     D                                     Like(RXS_ParseDomDS_t)

      // XPath used to determine which nodes are retrieved into the
      //   RXS_ParseDomDS_t return data structure.
     D  pXPath                             Like(RXS_Var8Kv_t) Const
     D                                     Options(*Varsize)

      // RXS_ParseDomDS_t data structure, e.g. the "parent" structure.
     D  pDS                                LikeDS(RXS_ParseDomDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
**FREE
// This example demonstrates starting a DOM parsing session by using
//  RXS_OpenDom and then retrieving a subsection of the DOM structure. This is
//  usually helpful when working with large documents as it can make it easier
//  to specify the correct XPaths to work with the sections of the document
//  that you want to reach. It's important to call RXS_CloseDom once DOM
//  parsing has been completed to free memory used by the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

Dcl-S gXML Like(RXS_Var64Kv_t);

gXML = '<?xml version="1.0" encoding="UTF-8"?>'
     + '<bookstore xmlns:test="testnamespace">'
     + '<book category="children">'
     + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
     + '<author>E. B. White</author>'
     + '<year>1952</year>'
     + '<price>5.99</price>'
     + '<review><text>What a great book!</text></review>'
     + '<review><text>Highly recommended.</text></review>'
     + '</book>'
     + '<book category="cooking">'
     + '<title lang="en">Everyday Italian</title>'
     + '<author>Giada De Laurentiis</author>'
     + '<year>2005</year>'
     + '<price>30.00</price>'
     + '</book>'
     + '</bookstore>';

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );

RootDomDS = RXS_OpenDom( gXML );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

// Any subsequent XPaths used to query BookDomDS no longer
//  need to include the /*:bookstore/ portion of the XPath.
for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToText( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
  // This will output:
  // Title: Charlotte's Web
  // Title: Everyday Italian
endfor;

RXS_CloseDom( RootDomDS );

return;
```

## Data Structures

### RXS_ParseDomDS_t

```rpgle
     D RXS_ParseDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSEDOM)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)

      // Contains the current count of XML nodes tracked by this data structure.
     D   NodeCount                   10U 0

     D   NodeType                    10I 0

      // Internal use only
     D   xmlPtr                        *

      // Internal use only
     D   docPtr                        *

      // Internal use only
     D   contextPtr                    *

      // Internal use only
     D   dictPtr                       *

      // Internal use only
     D   currentPtr                    *

      // Internal use only
     D   parentPtr                     *

      // Internal use only
     D   Reserved                  4096A
```



---

# RXS_ParseDomToText()

> Extracts the text content of an XML node matching an XPath expression.

This subprocedure retrieves the text result of a specific XPath.

## Subprocedure Prototype

```rpgle
      // Returns the text retrieved from the specified XPath.
     D RXS_ParseDomToText...
     D                 PR                  Extproc('RXS_ParseDomToText') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // XPath used to determine which nodes are retrieved into the
      //   RXS_ParseDomDS_t return data structure.
     D  pXPath                             Like(RXS_Var8Kv_t) Const
     D                                     Options(*Varsize)

      // RXS_ParseDomDS_t data structure, e.g. the "parent" structure.
     D  pDS                                LikeDS(RXS_ParseDomDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
**FREE
// This example demonstrates using RXS_ParseDomToText to retrieve text data
//  from an element within a parsed DOM structure. It's important to call
//  RXS_CloseDom once DOM parsing has been completed to free memory used by
//  the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

Dcl-S gXML Like(RXS_Var64Kv_t);

gXML = '<?xml version="1.0" encoding="UTF-8"?>'
     + '<bookstore xmlns:test="testnamespace">'
     + '<book category="children">'
     + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
     + '<author>E. B. White</author>'
     + '<year>1952</year>'
     + '<price>5.99</price>'
     + '<review><text>What a great book!</text></review>'
     + '<review><text>Highly recommended.</text></review>'
     + '</book>'
     + '<book category="cooking">'
     + '<title lang="en">Everyday Italian</title>'
     + '<author>Giada De Laurentiis</author>'
     + '<year>2005</year>'
     + '<price>30.00</price>'
     + '</book>'
     + '</bookstore>';

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );

RootDomDS = RXS_OpenDom( gXML );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToText( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
  // This will output:
  // Title: Charlotte's Web
  // Title: Everyday Italian
endfor;

RXS_CloseDom( RootDomDS );

return;
```

## Data Structures

### RXS_ParseDomDS_t

```rpgle
     D RXS_ParseDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSEDOM)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)

      // Contains the current count of XML nodes tracked by this data structure.
     D   NodeCount                   10U 0

     D   NodeType                    10I 0

      // Internal use only
     D   xmlPtr                        *

      // Internal use only
     D   docPtr                        *

      // Internal use only
     D   contextPtr                    *

      // Internal use only
     D   dictPtr                       *

      // Internal use only
     D   currentPtr                    *

      // Internal use only
     D   parentPtr                     *

      // Internal use only
     D   Reserved                  4096A
```



---

# RXS_ParseDomToXml()

> Serializes an XML node matching an XPath expression back to an XML string.

This subprocedure retrieves the raw XML from a specific XPath.

## Subprocedure Prototype

```rpgle
      // Returns the XML subsection retrieved by the specified XPath.
     D RXS_ParseDomToXml...
     D                 PR                  Extproc('RXS_ParseDomToXml') Opdesc
     D                                     Like(RXS_Var16Mv_t)
     D                                     Rtnparm

      // XPath used to determine which nodes are retrieved into the
      //   RXS_ParseDomDS_t return data structure.
     D  pXPath                             Like(RXS_Var8Kv_t) Const
     D                                     Options(*Varsize)

      // RXS_ParseDomDS_t data structure, e.g. the "parent" structure.
     D  pDS                                LikeDS(RXS_ParseDomDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
**FREE
// This example demonstrates using RXS_ParseDomToXml to retrieve a section
//  of a parsed DOM structure as XML. It's important to call RXS_CloseDom once
//  DOM parsing has been completed to free memory used by the DOM parser.

Ctl-Opt ActGrp(*New) BndDir('RXSBND');

/COPY QRPGLECPY,RXSCB

Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds BookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-S XPath Like(RXS_Var8Kv_t);
Dcl-S Data Like(RXS_Var1Kv_t);

Dcl-S x Uns(10);

Dcl-S gXML Like(RXS_Var64Kv_t);

gXML = '<?xml version="1.0" encoding="UTF-8"?>'
     + '<bookstore xmlns:test="testnamespace">'
     + '<book category="children">'
     + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
     + '<author>E. B. White</author>'
     + '<year>1952</year>'
     + '<price>5.99</price>'
     + '<review><text>What a great book!</text></review>'
     + '<review><text>Highly recommended.</text></review>'
     + '</book>'
     + '<book category="cooking">'
     + '<title lang="en">Everyday Italian</title>'
     + '<author>Giada De Laurentiis</author>'
     + '<year>2005</year>'
     + '<price>30.00</price>'
     + '</book>'
     + '</bookstore>';

RXS_ResetDS( RootDomDS : RXS_DS_TYPE_PARSEDOM );

RootDomDS = RXS_OpenDom( gXML );

XPath = RXS_XPath( '/*:bookstore/*:book' );
RXS_ResetDS( BookDomDS : RXS_DS_TYPE_PARSEDOM );
BookDomDS = RXS_ParseDomToDom( XPath : RootDomDS );

for x = 1 to BookDomDS.NodeCount;
  XPath = RXS_XPath( '*:book[%u]/*:title' : x );
  Data = RXS_ParseDomToXml( XPath : BookDomDS );
  RXS_JobLog( 'Title: %s' : Data );
  // This will output:
  // Title: <title lang="en"><![CDATA[Charlotte's Web]]></title>
  // Title: <title lang="en">Everyday Italian</title>
endfor;

RXS_CloseDom( RootDomDS );

return;
```

## Data Structures

### RXS_ParseDomDS_t

```rpgle
     D RXS_ParseDomDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSEDOM)

     D   OnErrorMessageType...
     D                                5I 0

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)

      // Contains the current count of XML nodes tracked by this data structure.
     D   NodeCount                   10U 0

     D   NodeType                    10I 0

      // Internal use only
     D   xmlPtr                        *

      // Internal use only
     D   docPtr                        *

      // Internal use only
     D   contextPtr                    *

      // Internal use only
     D   dictPtr                       *

      // Internal use only
     D   currentPtr                    *

      // Internal use only
     D   parentPtr                     *

      // Internal use only
     D   Reserved                  4096A
```



---

# RXS_XPath()

> Evaluates an XPath expression against a DOM and returns the matching node value as a string.

This subprocedure assists with building an XPath string. This is recommended over simply manually building an XPath string.

In addition to providing an easy way to include numeric replacement values, it also allows for including a wildcard namespace. The wildcard namespace syntax is translated to its' XPath 1.0 equivalent and returned along with any replacements that were passed.

## New in RXS 3.5.0 

### Enhancements 

- Corrected an issue where RXS_XPath was incorrectly escaping @ symbols that were part of a properly formatted XPath.

## Subprocedure Prototype

```rpgle
      // Returns the constructed XPath string.
     D RXS_XPath...
     D                 PR                  Extproc('RXS_XPath') Opdesc
     D                                     Like(RXS_Var8Kv_t)

      // Holds the initial XPath string template
     D  pXPath                             Like(RXS_Var8Kv_t) Const
     D                                     Options(*Varsize)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement1                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement2                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement3                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement4                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement5                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement6                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement7                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement8                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement9                10U 0 Value Options(*Nopass)

      // Holds a numeric replacement value. Replaces the first unreplaced %u
      //   found in the XPath string template.
     D  pReplacement10...
     D                               10U 0 Value Options(*Nopass)
```


## Example Code

### Ex. 1

```rpgle
      *--------------------------------------------------------------
      * This example code demonstrates creating an XPath that can be used for
      *  further processing.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D gXPath          S                   Like(RXS_Var1Kv_t)
      /free
       gXPath = RXS_XPath( '/*:bookstore/*:book' );

       *INLR = *ON;
      /end-free
```


---

# RXS_Parse()

> Event-based XML parser that calls a handler subprocedure for element begin, content, end, and attribute events.

This subprocedure is used to set up XML parsing. Use of this subprocedure requires an XML handler subprocedure to also be written.

The events recognized by the XML parser are:

| Event | Format | Example |
| ----- |-| ------ |
| Element Begin | `>` | `Request>` |
| Element Content | `/` | `Request/Item/` |
| Element End | `/>` | `Request/>` |
| Attribute | `@` | `Request/Item@Attribute` |

The Element Content and Attribute events will return data, which can be retrieved with [RXS_STR()](https://isupport.katointegrations.com/rxs/3.5/rxs_str.md).

## New in RXS 3.5.0 

### Enhancements 

- Corrected a possible memory leak condition present when parsing XML stored in an IFS file.

## Subprocedure Prototype

```rpgle
     D RXS_Parse...
     D                 PR                  Extproc('RXS_Parse') Opdesc

      // Holds the XML data to be passed to the parsing subprocedure(s). Will be
      //   ignored if the Stmf subfield of the DS parameter is set.
     D  pInput                             Like(RXS_Var16Mv_t)
     D                                     Options(*Omit:*Varsize)

      // Holds a RXS_ParseDS_t data structure which controls how RXS_Parse()
      //   functions.
     D  pDS                                LikeDS(RXS_ParseDS_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1: Simple Parse

```rpgle
      *--------------------------------------------------------------
      * This example parses a simple XML structure stored in the field
      * 'XML' and stores the data contained in nodes <Node1> and <Node2>
      * in the corresponding global fields.
      *
      * At a high level, the XML is passed into the XmlHandler() 
      * subprocedure in small chunks. Then, a SELECT block is used to
      * detect whether or not the current chunk of XML matches one
      * you'd like to handle. If it does, you can extract the data from
      * an element of attribute using RXS_STR().
      *
      * Once the XML has been entirely passed through XmlHander(), control
      * returns to the main part of the program.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D XmlHandler      PR
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value

     D ParseDS         DS                  LikeDS(RXS_ParseDS_t)
     D                                     Inz(*LikeDS)
     D XML             S                   Like(RXS_Var64Kv_t)
     D Node1           S                   Like(RXS_Var1Kv_t)
     D Node2           S                   Like(RXS_Var1Kv_t)
      /free
        XML = '<?xml version="1.0" encoding="utf-8"?>' +
         '<Root><Node1>Hello</Node1><Node2>World</Node2></Root>';

        RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
        ParseDS.GlobalHandler = %Paddr( XmlHandler );
        RXS_Parse( XML : ParseDS );

        RXS_JobLog( 'Node 1: %s' : Node1 );
        RXS_JobLog( 'Node 2: %s' : Node2 );

        *INLR = *ON;
      /end-free

     P XmlHandler      B
     D XmlHandler      PI
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value
      /free
        select;
          when pXPath = '/Root/Node1/';
            Node1 = RXS_STR( pData : pDataLen );
          when pXPath = '/Root/Node2/';
            Node2 = RXS_STR( pData : pDataLen );
        endsl;
      /end-free
     P                 E
```

## Data Structures

### RXS_ParseDS_t

```rpgle
     D RXS_ParseDS_t   DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     Like(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_PARSE)

     D   OnErrorMessageType...
     D                                5I 0

      // Holds a PROCPTR to an XmlHandler subprocedure used to handle all
      //   possible XML events.
     D   GlobalHandler...
     D                                 *   Procptr

      // Holds a PROCPTR to an XmlHandler subprocedure which will only respond to
      //   XML content events.
     D   ElementContentHandler...
     D                                 *   Procptr

      // Holds a PROCPTR to an XmlHandler subprocedure which will only respond to
      //   XML begin events.
     D   ElementBeginHandler...
     D                                 *   Procptr

      // Holds a PROCPTR to an XmlHandler subprocedure which will only respond to
      //   XML end events.
     D   ElementEndHandler...
     D                                 *   Procptr

      // Holds a PROCPTR to an XmlHandler subprocedure which will only respond to
      //   XML attribute events.
     D   AttributeHandler...
     D                                 *   Procptr

      // Controls whether namespaces are factored into XML parsing - if RXS_NO,
      //   they will be ignored.
      // Valid values: RXS_YES, RXS_NO
      // Default: RXS_NO
     D   IncludeNamespaces...
     D                                 N

      // Specifies the CCSID of the XML being parsed.
     D   InputCcsid...
     D                               10I 0

      // Specifies the CCSID the parsed data will be converted to.
     D   OutputCcsid...
     D                               10I 0

      // Specifies an IFS path to an XML file to parse instead of the Input parm.
     D   Stmf...
     D                                     Like(RXS_Var1Kv_t)
```



---

# RXS_Validate()

> Validates an XML document against an XSD schema, returning validation errors if the document does not conform.

This subprocedure performs validation options on a given character string.

Currently it can perform XSD validation against XML.

## Subprocedure Prototype

```rpgle
     D RXS_Validate...
     D                 PR              N   Extproc('RXS_Validate') Opdesc

      // Character data to be validated.
     D  pData                              Like(RXS_Var16Mv_t)
     D                                     Options(*Omit: *Varsize) Const

      // This field can contain either an RXS_XsdValidationDS_t data structure,
      //   or the XSD stored in a character field.
     D  pDS                                Like(RXS_Var64K_t)
     D                                     Options(*Varsize)
```


## Example Code

### Ex. 1

```rpgle
      *--------------------------------------------------------------
      * This example code validates XML data against a specified XSD.
      *--------------------------------------------------------------
     H DFTACTGRP(*NO) BNDDIR('RXSBND') ACTGRP(*CALLER)

      /copy QRPGLECPY,RXSCB

     D XsdValidateDS...
     D                 DS                  LikeDS(RXS_XsdValidationDS_t)
     D                                     Inz(*LikeDS)
     D goodXml         S                   Like(RXS_Var64Kv_t)
      /free
       // This example assumes that goodXml contains XML data.
       //  This data could be retrieved from RXS_GetStdIn(), or composed
       //  using the RXS composition engine.

       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdLocation =
       'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
       if RXS_Validate( goodXml : XsdValidateDS );
         RXS_JobLog( 'XML 1 Validated successfully');
       else;
         RXS_JobLog( 'XML 1 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s' :
           %Char( XsdValidateDS.ValidationErrorCode ) :
           XsdValidateDS.ValidationErrorText );
       endif;

       *INLR = *ON;
      /end-free
```

## Data Structures

### RXS_XsdValidationDS_t

```rpgle
     D RXS_XsdValidationDS_t...
     D                 DS                  Qualified Template Inz

     D   ReturnedErrorInfo...
     D                                     LikeDS(RXS_ReturnedErrorInfoDS_t) Inz

      // Internal use only
     D   DataStructureType...
     D                                5I 0 Inz(RXS_DS_TYPE_XSDVALIDATION)

     D   OnErrorMessageType...
     D                                5I 0

      // The IFS path or URI where the XSD is located.
     D   XsdLocation...
     D                                     Like(RXS_Var1Kv_t)

      // Error code returned by the validation operation.
     D   ValidationErrorCode...
     D                               10I 0

      // Error message returned by the validation operation.
     D   ValidationErrorText...
     D                                     Like(RXS_Var1Kv_t)

      // CCSID of the XSD data.
     D   XsdCcsid                    10I 0

      // Internal use only
     D   XsdPointer                    *

      // Internal use only
     D   XsdLength                   10I 0

      // The CCSID of the XML data.
     D   XmlCcsid                    10I 0

      // Internal use only
     D   XmlPointer                    *

      // Internal use only
     D   XmlLength                   10I 0

      // Internal use only
     D   Reserved                  4096A
```



---

# Apply License (APYLIC)

> Applies a permanent or temporary license key to the RPG API Express installation.

To apply a permanent or temporary license key, you will use the included command APYLIC. Your license key will typically be supplied via email in a command string:

`APYLIC LICKEY(6E2222898982C8F1380F4DF08A23922C) PRODUCT(RXS)`

Or, you can copy and paste the received key into the command:

![Apply License (APYLIC)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/apylic.png "Apply License (APYLIC)")

In either case after running the APYLIC command, you should receive a message that says **License applied successfully**

If you don't receive that message or receive an error message, please contact us at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

---

# Display License (DSPLIC)

> Legacy alias for DSPMCHINF; displays machine and license information needed for activation key generation.

DSPLIC is used to retrieve system information as well as license status.

**Note:** [Display Machine Info (DSPMCHINF)](https://isupport.katointegrations.com/rxs/3.5/dspmchinf.md) is an alias for this command. These commands may be used interchangeably.

The command does not have any parameters:

`DSPLIC`

The command should display a screen similar to the one below. This screen indicates all the information necessary to generate your license key, as well as the current status of any applied license keys.

![Display License (DSPLIC)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/dsplic.png "Display License (DSPLIC)")

If you don't receive that message or receive an error message, please contact us at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

---

# Display Machine Info (DSPMCHINF)

> Displays system machine information and license key status required by support to generate activation keys.

DSPMCHINF is used to retrieve system information as well as license status.

**Note:** DSPMCHINF is an alias for the [Display License (DSPLIC)](https://isupport.katointegrations.com/rxs/3.5/dsplic.md) command. These commands may be used interchangeably.

The command does not have any parameters:

`DSPMCHINF`

The command should display a screen similar to the one below. This screen indicates all the information necessary to generate your license key, as well as the current status of any applied license keys.

![Display Machine Info (DSPMCHINF)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/dspmchinf.png "Display Machine Info (DSPMCHINF)")

If you don't receive that message or receive an error message, please contact us at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

---

# Display Version (DSPVER)

> Displays the currently installed version of RPG API Express.

DSPVER can be used to retrieve the version number of the currently installed product.

The command does not have any parameters:

`DSPVER`

You should receive a message indicating the currently installed version as well as the library in which the product is installed.

![Display Version (DSPVER)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/dspver.png "Display Version (DSPVER)")

If you don't receive that message or receive an error message, please contact us at <a href="mailto:isupport@katointegrations.com">isupport@katointegrations.com</a>.

---

# RXSMENU

> Menu interface for viewing RXS installation details and managing product license keys.

The RXSMENU menu can be used to retrieve information about your RPG API Express installation, or to work with your product license keys.

![RXSMENU](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/rxsmenu.png)

---

# Create RXS Server (CRTRXSSVR)

> Creates an Apache HTTP server instance configured for use with RPG API Express.

The Create RXS HTTP Server (CRTRXSSVR) command allows the user to create an HTTP server instance for use by RPG API Express.

## New in RXS 3.5.3 

### New feature 

- Added new parameter AUTOSTART to CRTRXSSVR command to allow configuring the HTTP server's autostart property at creation time

## New in RXS 3.5.0 

### Enhancements 

- Added support for 5-digit port numbers up to 65535.
- New special values for the PORT parameter - `*HTTP` and `*HTTPS` - which will specify ports 80 or 443, respectively.

## Parameters

### HTTP server instance (HTTPSVR)
This is a required parameter.

Specifies the name of the HTTP server instance to create.

If this parameter is specified with any value other than HTTPSVR(*RXSLIB), an RPG API Express development library is also created, with the same name as the HTTP server instance. Several objects are copied into it from the RPG API Express product library. It is these objects (files, programs service programs, etc.) that are used when you develop your applications which use RPG API Express.

If you specify HTTPSVR(*RXSLIB), only the HTTP server instance is created with the same name as the RPG API Express product library. You should use this option if you only wish to use the RXS Router product, and are not intending to do any other RPG API Express development.

#### Possible Values:
`name`
: An HTTP server instance is created and an RPG API Express development environment library is also created with the same name.

`RXSLIB`
: An HTTP server instance is created with the same name as the RPG API Express product library. No RPG API Express development environment library is created.

### RXS product library (RXSLIB)
Specifies the name of the RPG API Express product library to use when creating the RPG API Express environment.

#### Possible Values:
`RXS`
: The default RPG API Express product library RXS will be used.

`library-name`
: The named RPG API Express product library will be used.

### Port number (PORT)
Specifies the port number to use for the HTTP server instance.

**Note: No checks are performed that the specified port number is not already in use, either by an existing HTTP server instance or by another application.**

#### Possible Values:
`DFT`
: The default port number 8181 is used.

`*HTTP`
: The HTTP-standard port 80 will be used.

`*HTTPS`
: The HTTPS-standard port 443 will be used.

`port-number`
: The specified port number is used.

---

# Process HTTP Server Instance (HTTP)

> Manages Apache HTTP server instances with options to start, end, restart, view logs, inspect configuration, and manage autostart.

The barebones approach to ending an Apache HTTP server is to use the ENDTCPSVR command. The ENDTCPSVR command has a default of *ALL for the SERVER parameter which can obviously shut down all TCP related services, making you very unpopular. For this reason we have created a command that can help you with your Apache HTTP server needs. The command is named "HTTP". Simply type HTTP on the command line and select F4 to see the various options.

## New in RXS 3.5.3 

### New feature 

- Added new value *CFGAUTO for HTTP command parameter OPTION to enable new parameter AUTOSTART which allows modifying the HTTP server's autostart property after creation

## New in RXS 3.5.0 

### Enhancements 

- Corrected an issue with the HTTP command where a validation message was not properly displayed when attempting to use parameter `HTTPSVR(*ALL)` with an OPTION other than `*WRK`, or when attempting to use `HTTPSVR(*ADMIN)` with an option other than `*WRK`, `*START`, `*END`, or `*RESTART`

## Parameters

### HTTP Server Instance (HTTPSVR)
Specify the Apache HTTP server instance to perform actions upon.

#### Possible Values:

`*ALL`
: Work with all HTTP instances. The only valid values for the Option parameter that can be used with this is *WRK.

`*ADMIN`
: Work with all *ADMIN HTTP instances. The only valid values for the Option parameter that can be used with this are *WRK, *START, *RESTART, and *END

`name`
: Specify the name of a specific HTTP instance to work with. All values are valid for the Option parameter.

`generic-name`
: Specify the generic name of the HTTP server instance to be processed. A generic name is a character string that contains one or more characters followed by an asterisk (*). All values are valid for the Option parameter.

### Option (OPTION)
Specify the operation which should be performed on the specified HTTP server instance(s).

#### Possible Values:

`*WRK`
: Works with the specified HTTP server instance(s). This displays the Work with Active Jobs (WRKACTJOB) screen, subsetted to show only the selected HTTP server instance(s).

`*START`
: Starts the specified HTTP server instance.

`*END`
: Ends the specified HTTP server instance.

`*INZRTR`
: Intializes the RXSRTR cache.

`*RESTART`
: Restarts the specified HTTP server instance.

`*CONF`
: Edits the configuration file for the specified HTTP server instance. If the user does not have sufficient authority to edit the configuration file, it will be displayed.

`*LOGS`
: Displays a list of the the error logs for the specified HTTP server instance.

`*CURLOG`
: Displays the current day's error log for the specified HTTP server instance.

`*DOCS`
: Displays the contents of the '/htdocs/' folder for the specified HTTP server instance.

`*DIR`
: Displays the entire directory structure for the specified HTTP server instance.

---

# RXS Router (RXSRTR)

> CGI routing program that dispatches HTTP requests to RPG programs, supporting library lists, user profile swapping, audit logging, and ASP group selection.

The RXS Router (RXSRTR) is a program included since RXS v2.10+ that runs on the IBM i behind the Apache HTTP server, acting as a 'router' for CGI requests which use the HTTP protocol.

RXSRTR enables you to both simplify your HTTP processing and add functionality which is not directly available through the HTTP server configuration. Once installed, RXSRTR sits seamlessly between your HTTP server and your CGI programs; no changes are required to existing CGI program functionality and no changes are required to any existing URLs you use to access your CGI programs (such as user favorites or links from webpages).

RXSRTR enables you to perform the following additional functionality:

- Route all CGI requests through a single URL format
- Specify a library list to use when calling programs
- Swap to a different user profile when calling programs
- Log all information about the request to an audit log file
- Call non-CGI programs, passing parameters and receiving responses
- Specify an activation group allowing files to remain open between calls, while maintaining separation between environments
- Specify product libraries which will be placed ahead of libraries in the user library list

All this functionality is data-driven - that is, it is controlled via data held in a single control file used by RXSRTR. If you need to add or change the programs you need to call, you simply update the RXSRTR control file.

## New in RXS 3.5.0 

### New Configuration Flag 

- `-aspgrp` allows for swapping the ASP Group prior to any library list changes or program calls.

## HTTP Configuration Without RXSRTR

Below is an example HTTP server instance configuration fragment, showing how three different URL formats match different CGI program calls in different libraries:

```apache
ScriptAliasMatch ^/cgi-bin/(.*) /qsys.lib/public.lib/$1.pgm
ScriptAliasMatch ^/cust/(.*) /qsys.lib/customers.lib/initial.pgm
ScriptAliasMatch ^/db2wse/(.*) /qsys.lib/db2wse.lib/db2wse.pgm
<Directory /qsys.lib/public.lib/>
  Options +ExecCGI
  Allow From all
</Directory>
<Directory /qsys.lib/customers.lib/>
  Options +ExecCGI
  Allow From all
</Directory>
<Directory /qsys.lib/db2wse.lib/>
  Options +ExecCGI
  Allow From all
</Directory>
```

In this case, multiple ScriptAliasMatch directives route different URLs to different CGI programs in different libraries. For instance, assuming this HTTP server is on a IBM i with an IP address of 192.168.0.1, below is a list of example URLs and the resulting program which will be called:

| URL used | CGI Program Called |
|----------|---|
| `http://192.168.0.1/cgi-bin/mypgm` | PUBLIC/MYPGM |
| `http://192.168.0.1/cgi-bin/yourpgm` | PUBLIC/MYPGM |
| `http://192.168.0.1/cust/john` | CUSTOMERS/INITIAL |
| `http://192.168.0.1/cust/` | CUSTOMERS/INITIAL |
| `http://192.168.0.1/db2wse/string` | DB2WSE/DB2WSE |


This can be confusing, since it's not always clear which program will eventually be called when a given URL is used and the configuration can grow very large and complex, since each library specified has to have its own <Directory> section in the configuration, specifying authorities etc. Additionally, if any changes are required, the HTTP server must be restarted. Finally, the above configuration is also insecure, since it allows any user to call any program in the PUBLIC library.

## HTTP Configuration With RXSRTR

By contrast, RXSRTR allows you to replace all the above configuration with a single ScriptAliasMatch and <Directory> section in your HTTP server instance configuration file which can be used for all CGI program calls:

```apache
ScriptAliasMatch ^/(([^/.]+/)*[^/.]+)$ /qsys.lib/RXS.lib/rxsrtr.pgm
<Directory /qsys.lib/RXS.lib/>
  Options +ExecCGI
  Allow From all
</Directory>
```

Using the above directive, every URL will call RXSRTR and by configuring RXSRTR, you can ensure that all existing URLs (which may exist as links in web pages, user's bookmarks etc.) will result in the same programs being called as before.

Within the RXSRTR configuration itself, you define which programs should be called and how. You can dynamically change these configuration settings without needing to change the HTTP server configuration or restart the HTTP server.

This means that you can have a much simpler HTTP configuration. Because RXSRTR works at the program level, you can easily configure RXSRTR to specify exactly which programs can be called, immediately making your system more secure.

When a request is matched by the HTTP server which results in RXSRTR being called, RXSRTR analyzes the URL to determine a 'request routing identifier' (RRID), which will be used to determine the routing call processing for the request. This processing is described in detail in the [RXS Router Configuration](#rxs-router-configuration) section.

Once the RRID has been determined for a request, RXSRTR retrieves the control data for that RRID. The control data specifies not only the actual program to call, but also the following characteristics of the call:

- Which additional libraries to add to the library list prior to calling the program
- Which user profile to swap to for the call

RXSRTR control data is held in the RXSRTRCTL file. Each record has a key of the RRID and contains a 'routing data' field. Within the routing data field, various flag/value pairs can be specified to control what program will be called for the given RRID.

The format of the DATA field is as follows:

`{-flag value -flag value -flag value...}`

where flag can be one of a number of special values as follows:

| Flag | Description of associated value |
|------|---|
| `pgm` | Name of application program to be called **(required)** |
| `lib` | Name of application library |
| `usrprf` | 	User profile to run as |
| `passwd` | 	Password for user profile |
| `liblst` | 	Comma-separated library list (10 libraries max) or qualified job description name |
| `cvtopt` | 	Comma-separated conversion options |
| `response` | Response type for non-CGI programs |
| `actgrp` | 	Activation group |
| `prdlib` | 	Product library |
| `aspgrp` | Name of the ASP group to be used |


Note the following specifications related to control data flags and the way they must be specified in the DATA field in the RXSRTRCTL file:

- Each flag must be prefixed with a minus-sign, e.g. -pgm
- Values are automatically converted to upper-case for processing
- Flag/value pairs can be specified in any order
- Except for the pgm flag, all flag/value pairs are optional
- Comma-separated lists (for the liblst and cvtopt flags) must contain no embedded blanks

Note that there is a special *CONFIG record shipped in the RXS router control file which contains different valid flag values. See [RXS Router Configuration](#rxs-router-configuration) for more details.

## Control Flag Details

### pgm 
The **pgm** flag defines the program to be called. It can either be a valid i5/OS name or a special value. Valid special values are \*0-\*9. If a special value is used, the corresponding SCRIPT_NAME path segment is used as the program name, where 0 is the RRID itself, 1 is the next segment and so on. For instance, if RXSRTR is called with the following URL:

`http://192.168.0.1/test/john/mike/fred`

then TEST will be used as the RRID. If the TEST record in RXSRTRCTL has -pgm *1 specified, then JOHN will be used as the program name, if -pgm *2 is specified, MIKE will be used as the program name, and so on.

### lib 
The **lib** flag defines the library of the program to be called. If the library is not in the library list, it will be added to the top of the library list. If the lib flag is not specified, *LIBL will be used.

### usrprf/passwd 
The **usrprf** and **passwd** flags define the user profile which the job should use whilst calling the program. If **usrprf** is specified, **passwd** must also be specified and vice-versa. If either the specified user profile or password are invalid or if the user profile is disabled, RXS Router will return an error to the browser and will not call the program.

### liblst 
The **liblst** flag defines the library list which should be used for the request. The liblst flag can be specified in one of two ways:

- liblst library1,library2,library3
- liblst library/jobd

If a comma-separated list of libraries is specified, they are each added in order to the bottom of the library list. If a qualified job description (*JOBD) is specified, the initial library list (INLLIBL) of the *JOBD is retrieved and the libraries are each added in order to the bottom of the library list. All libraries added as a result of the liblst flag are removed when control is returned from the called program.

### cvtopt 
See the [Control Data Conversion Options](#control-data-conversion-options) section for details of how the **cvtopt** flag should be specified.

### response 
See the [Calling non-CGI programs](#calling-non-cgi-programs) section for details of how the **response** flag should be specified.

### actgrp 
The **actgrp** flag defines the name of the activation group in which the program specified with the **pgm** flag will run. This value is ignored unless the program is specified with ACTGRP(\*CALLER). If the special value **-actgrp \*dftactgrp** is specified, the value specified for the **dftactgrp** flag in the \*CONFIG record will be used. If the **actgrp** flag is not specified and the program is defined with ACTGRP(\*CALLER), it will run in the same activation group as RXS Router - by default, this is the RXSRTR activation group.

### prdlib 
The **prdlib** flag specifies the name of the product library which is added to the library list prior to the program specified in the **pgm** flag being called. The product library is added above all user libraries, including the current library. This library will be removed after the program is called. If a value is specified for the **dftprdlib** flag in the *CONFIG record, that library will be overwritten by this value while the program is being called, and will be replaced afterwards.

If the **prdlib** flag is not specified, but the **dftprdlib** flag is specified in the \*CONFIG record, that product library will be used for the duration of the program call.

If the **prdlib** flag is not specified and the **dftprdlib** flag is not specified in the \*CONFIG record, no product library will be used for the duration of the program call.

### aspgrp 
The **aspgrp** flag specifies the ASP Group to be applied when building the library list. The ASP Group will be swapped prior to any library list changes or program calls.

## Example RXSRTRCTL Record

| ID | DATA |
|---|---|
| ZAP | -pgm zappgm -liblst qgpl/zapjobd -lib qgpl -passwd donald -usrprf goofy |

## Control Data Conversion Options

Depending on the value of the cvtopt control flag, RXSRTR may perform certain steps prior to calling the specified program. Some of the conversion options are designed to allow existing CGI programs which expect a particular value for the SCRIPT_NAME or QUERY_STRING environment variables to continue to be called as before, even though you may have changed the calling URL to implement RXSRTR control. Multiple conversion options can be combined in a comma-separated list. Each conversion option will be processed in the order it is found. The following sections describe the conversion options.

### rm_sn (Remove RRID from SCRIPT_NAME) 
The rm_sn conversion option indicates that if the RRID is included in the URL, it will be removed from the SCRIPT_NAME environment variable prior to the CGI program being called.

If the rm_sn conversion option is specified and the URL used to call RXSRTR is:

`http://192.168.0.1/rxsrtr/db2wse/mylib/myfile`

then if the CGI program retrieves the SCRIPT_NAME environment variable, it will retrieve a value of

`/db2wse/mylib/myfile`

rather than the expected value of

`/rxsrtr/db2wse/mylib/myfile`

### rm_qs (Remove RRID from QUERY_STRING) 
The rm_qs conversion option indicates that if the RRID is included in the URL, it will be removed from the QUERY_STRING environment variable prior to the CGI program being called.

If the rm_qs conversion option is specified and the URL used to call RXSRTR is

`http://192.168.0.1/cust/?fname=john&rxsrtr=mickey&lname=smith`

then if the CGI program retrieves the QUERY_STRING environment variable, it will retrieve a value of

`fname=john&lname=smith`

rather than the expected value of

`fname=john&rxsrtr=mickey&lname=smith`

### sn_pr (Parse parameters from SCRIPT_NAME) 
The sn_pr conversion option indicates that the program being called will be passed a number of parameters, as specified in the SCRIPT_NAME environment variable, where every path segment after the RRID will be treated as a separate character parameter.

If the URL used to call RXSRTR is:

`http://192.168.0.1/zap/mickey/mouse`

and the ZAP control record has the following value:

`-pgm zappgm -lib qgpl -cvtopt sn_pr`

then RXSRTR will call program QGPL/ZAPPGM as follows:

`CALL PGM(QGPL/ZAPPGM) PARM('MICKEY' 'MOUSE')`

All parameters passed to the specified program will be passed as string pointers. In RPG, this means that they should be defined as follows:

```rpgle
     D ZAPPGM          PR                  Extpgm('ZAPPGM')
     D   fname                         *   Const Options(*String)
     D   lname                         *   Const Options(*String)
```

## Calling Non-CGI Programs

CGI programs work by writing their output (typically the HTML for a web page) to 'standard output'. When the CGI program ends and control is returned to the HTTP server, the standard output is sent to the browser, displaying as a web page. However, if you use the HTTP server to call a non-CGI 'application' program, because it doesn't write to standard output, an HTTP 500 error page will appear.

However, RXSRTR allows you to call a non-CGI application program, with the following limitations:

- The program must be a batch (non-interactive) program
- A maximum of 10 parameters can be passed to the program
- All parameters must be defined in the program as character strings with a maximum length of 256 bytes

If you wish to pass parameters to the program, you must specify the **sn_pr** conversion option, which will pass each of the SCRIPT_NAME path segments following the RRID as a character parameter. See the [Control Data Conversion Options](#control-data-conversion-options) section for details on how to specify this conversion option and the calling URL. When calling a non-CGI program, you must configure RXSRTR to send a response to the browser as an ad-hoc web-page, by specifying the response flag. It can be specified in one of the following two ways:

`-response *confirm`

or

`-response *msg`

After RXSRTR has called the program, it checks the value of the response flag. If -response *confirm is specified in the control record, RXSRTR will send a generic "Program called successfully" message to the browser. If -response *msg is specified, RXSRTR will retrieve the last message sent to it from the application program and will write it out to the browser.

For instance, if an application program is typically called from a command-line or from a green-screen menu and sends a completion message (e.g. "Audit record created") to the screen, calling this program from RXSRTR and specifying -response *msg in the RXSRTRCTL record will cause this same message to be sent to the browser.

## Initializing RXS Router

For performance reasons, when the RXS Router is first called within a job, it reads the records from the RXS Router control file RXSRTRCTL and caches the data into a user space called RXSRTRCTLU in the same library where the RXS Router itself exists. On subsequent calls within the same job, it uses the cached values, thus eliminating costly file I/O. However, if records in the RXSRTRCTL file are added, deleted or updated, the RXS Router cache must be updated.

A command called [INZRXSRTR (Initialize RXS Router)](https://isupport.katointegrations.com/rxs/3.5/inzrxsrtr.md) is supplied with the RXS Router. When this command is run from a command line, the RXS Router cache is updated to contain the current values from the RXSRTRCTL file.

This command can also be used to refresh the cached call pointers in Apache CGI jobs. This is useful because normally OS/400 will cache programs for reuse, but when you are doing web service development you will want Apache to pick up the latest version of the program after compiling it. Normally one would have to either recompile the entry level program to clear all cached call pointers or restart Apache. Neither of those options are optimal, so that is why we introduced the INZRXSRTR command to effectively refresh all cached call pointers.

## RXS Router Configuration

Additional RXS Router configuration can be controlled by changing the options in the shipped *CONFIG record in the RXS Router control file RXSRTRCTL. The various control options are as follows:

`routingid`
: Routing identifier

`loglvl`
: Logging level (0, 1, or 2)

`allowreset`
: Allow *RESET option from client (*YES or *NO)

`debugmode`
: Run in debug mode(*YES or *NO)

`dftactgrp`
: Specifies the default activation group for CGI programs

`dftliblst`
: Specifies the default library list for CGI programs

`dfterrpgm`
: Specifies the default error-handling program

`dftprdlib`
: Specifies the default product library to use

The *CONFIG record in the RXSRTRCTL file is shipped as follows:

`-routingid rxsrtr -loglvl 0 -allowreset *no -debugmode *no`

### Control Option: routingid
The **routingid** flag defines the type of routing that should be performed by RXSRTR. Depending on its value, RXSRTR will use either 'static' or 'dynamic' routing to determine the value of the request routing identifier (RRID), which actually defines the call definition. If the **routingid** flag is not specified, a value of 'RXSRTR' is assumed.

For more information about how RXS Router retrieves and uses the routingid, see the page [Configuring RXS Router](https://isupport.katointegrations.com/rxs/3.5/configuring_rxs_router.md).

### Control Option: loglvl
The **loglvl** flag defines the level of logging that will be performed by RXSRTR.

There are three different logging levels available within RXSRTR:

- **Log Level 0 (Error)** - Only errors encountered by RXSRTR during initialization or when processing a request are logged.
- **Log Level 1 (Basic)** - All initialization information is logged, as well as basic information about each request, including user profile swaps, addition of libraries to the library list, routing identifiers used and the SCRIPT_NAME and QUERY_STRING values used for the request
- **Log Level 2 (Full)** - Complete information about each request is logged, including parameter values and response data (where appropriate)

All log entries are written to the RXSRTRLOG file, which contains a 1024-byte free-format field called DATA. Each log record consists of the following:

| Start positionin DATA field | Length of data | Data |
|:--------------------------:|---|:--------------:|
| 1 | 1 | Log level ('0', '1', or '2') of log entry |
| 3 | 26 | Qualified HTTP server job name |
| 30 | 26 | Log entry timestamp |
| 57 | 967 | Log entry data |


If logging level 1 is specified, all logging at level 0 is also included. If logging level 2 is specified, all logging at logging level 0 and logging level 1 is also included.

If the **loglvl** flag is not specified or is set to a value other than 0, 1 or 2, logging level 0 is assumed.

### Control Option: allowreset
If the **allowreset** flag is set to \*YES, a user can reset the RXS Router from the browser by specifying a value of '\*RESET' in the URL (e.g. 'http://192.168.0.1/rxsrtr/*reset'). This will cause the RXS Router to reload all control records from the RXSRTRCTL file. It is recommended that a value of *YES is specified only if the RXS Router is being used on a development machine, since there is no checking to ensure which client used the reset option.

If the **allowreset** flag is not specified or is set to a value other than *YES, a value of *NO is assumed.

### Control Option: debugmode
If the **debugmode** flag is set to *YES, RXSRTR will run in debug mode. In this mode, instead of calling the specified program, RXSRTR will return a page displaying details of the program which would be called, as well as any the values of the QUERY_STRING and SCRIPT_NAME environment variables (if they have been changed due to any conversion options specified with the cvtopt flag).

If the **debugmode** flag is not specified or is set to a value other than *YES, a value of *NO is assumed.

### Control Option: dftactgrp
If the **dftactgrp** flag is specified, all programs called by RXS Router which are compiled with ACTGRP(*CALLER) will run using the specified activation group (unless the actgrp flag is specified in the RRID record for the program being called, in which case that value will be used instead).

If the **dftactgrp** flag is not specified, all programs called by RXS Router which are compiled with ACTGRP(*CALLER) will run in the same activation group as RXS Router - by default this activation group is called RXSRTR.

All programs called by RXS Router which are compiled with ACTGRP(*NEW) or ACTGRP(named-activation-group)) will run in the specified activation group.

### Control Option: dftliblst
If the **dftliblst** flag is specified, the defined library list or list of libraries will be used for all CGI program calls. This flag can be provided either a comma-separated list of libraries or a qualified job description. If a comma-separated list of libraries is specified, there must be no spaces between each library and each comma. If a qualified job description is specified, e.g. -dftliblst qgpl/jobd, the initial library list (INLLIBL) for that job description is used. In either case, the specified list of libraries will replace the user portion of the CGI job's library list. No validation is performed when this flag is specified to check whether the job description or libraries exist, but runtime errors will occur.

If the **dftliblst** flag is not specified, CGI program calls will use the default library list defined for the HTTP server (although see also the dftprdlib flag, which can alter the library list for CGI program calls).

### Control Option: dfterrpgm
If the **dfterrpgm** flag is specified, the specified error-handling program will be called if the RXS Router encounters an error either when calling a specified program or during other processing.

See the RXS Router user manual for details of the interface to the error handler and an example error handler.

If the **dfterrpgm** flag is not specified, no default error handling program will be used.

### Control Option: dftprdlib
If the **dftprdlib** flag is specified, the specified product library will be added to the library list when the RXS Router is called and is removed when the RXS Router returns control to the HTTP server. The product library is added above all user libraries, including the current library.

If a value is specified for the prdlib flag in a user (non-*CONFIG) control record, this library will be overwritten by that value while the program is being called, and will be replaced afterwards.

If the **dftprdlib** flag is not specified, no default product library will be used.

---

# Create RXSRTR Environment (CRTRXSRTRE)

> Creates a new RXS Router environment for use with RXSRTR.

The Create RXSRTR Environment (CRTRXSRTRE) command allows the user to create a new environment for use with the [RXS Router](https://isupport.katointegrations.com/rxs/3.5/rxsrtr.md).

## Parameters

### Environment (ENV)
This is a required parameter.

Specify the name of the RXS Router environment to create.

#### Possible Values:
`character-value`
: Name of the RXS Router environment to create.

### Text 'description' (TEXT)
Describes the environment being created.

#### Possible Values:
`*BLANK`
: No description.

`character-value`
: Description of the RXS Router environment being created.

### Copy from environment (FROMENV)
Specify the name of the existing environment from which to copy the control records.

#### Possible Values:
`*DFT`
: The new environment is copied from the default RXSRTR environment.

`character-value`
: Specify the name of the environment to copy.

### Records to copy (CPYENVOPT)
Specify which records to copy from the existing environment.

#### Possible Values:
`*ALL`
: All control records are copied from the specified environment.

`*CFG`
: Only the *CONFIG record is copied from the specified environment.

### Data library (DTALIB)
Specify the RXS Router data library. The RXS Router data library contains data objects required by the RXS Router runtime, including copies of the control file, log file and data cache.

#### Possible Values:
`*LIBL`
: The first library in the library list of the current job which contains the objects specified above will be used.

`data-library`
: The specified library will be used.

---

# Work with RXSRTR Entries (WRKRXSRTRE)

> Interactive command to create, modify, and delete RXS Router routing entries in RXSRTRCTL.

The Work with RXSRTRCTL Entries (WRKRXSRTRE) command allows the user to create, modify, and delete configuration entries for use with the RXS Router.

The command consists of two screens - the main entry display screen, and the entry detail display screen.

From the main entry display screen, you can select one or more entries to be displayed, edited or deleted, using the subfile selector field on the left side. You can also use the F6 key to create a new entry. You can use the input field at the top of the screen to position to an entry.

![Work with RXSRTRCTL Entries](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/maindisplay.png)

On the main entry display screen, only the first few characters of each entry are displayed.

On the entry detail display screen, you can display the full entry details and edit them if necessary. Each entry consists of a string comprised of one or more 'flags' followed by a related value.

![RXSRTRCTL Entry Detail](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/entrydetail.png)

For more information about how to create a valid RXS Router entry to meet your specific needs, please read more about [RXS Router](https://isupport.katointegrations.com/rxs/3.5/rxsrtr.md).

## New in RXS 3.5.0 

### Enhancements 

- Corrected an issue where the Work with RXSRTRCTL Entries screen would display an incorrect date when run from a long-running job.

## Parameters

### Environment (ENV)
Specifies the name of the RXS Router environment to edit. This corresponds to same-named members in the RXSRTRCTL and RXSRTRLOG files and a user space of the same name.

#### Possible Values:
`*DFT`
: The default environment called RXSRTR will be used.

`character-value`
: Name of the RXS Router environment to work with.

### Data library (DTALIB)
Specify the RXS Router data library. The RXS Router data library contains data objects required by the RXS Router runtime, including copies of the control file, log file and data cache.

#### Possible Values:
`*LIBL`
: The first library in the library list of the current job which contains the objects specified above will be used.

`data-library`
: The specified library will be used.

### Routing Identifier (ID)
Specify the routing control entry to work with.

#### Possible Values:
`*ALL`
: All routing control entries will be displayed. The user can specify entries for display, edit or delete or can create a new entry.

`routing-identifier`
: The specified routing control entry will be displayed in edit mode.

---

# Initialize RXS Router (INZRXSRTR)

> Refreshes the RXS Router cache from RXSRTRCTL and clears cached call pointers in Apache CGI jobs.

The INZRXSRTR (Initialize RXS Router) command is supplied with the RXS Router. When this command is run from a command line, the RXS Router cache is updated to contain the current values from the RXSRTRCTL file.

This command can also be used to refresh the cached call pointers in Apache CGI jobs. This is useful because normally OS/400 will cache programs for reuse, but when you are doing web service development you will want Apache to pick up the latest version of the program after compiling it. Normally one would have to either recompile the entry level program to clear all cached call pointers or restart Apache. Neither of those options are optimal, so that is why we introduced the INZRXSRTR command to effectively refresh all cached call pointers.

## Parameters

### Environment (ENV)
Specifies the name of the RXS Router environment to initialize. This corresponds to same-named members in the RXSRTRCTL and RXSRTRLOG files and a user space of the same name.

Note: If the RXSRTR environment does not exist, no error message will be displayed.

#### Possible Values:
`*ALL`
: All RXSRTR environments will be initialized.

`*DFT`
: The default environment called RXSRTR will be initialized.

`character-value`
: Name of the RXSRTR environment to initialize.

### Data library (DTALIB)
Specify the RXS Router data library. The RXS Router data library contains data objects required by the RXS Router runtime, including copies of the control file, log file and data cache.

#### Possible Values:
`*LIBL`
: The first library in the library list of the current job which contains the objects specified above will be used.

`data-library`
: The specified library will be used.

---

# Build Template (BLDTPL)

> Generates a compiled template (.tpl) file from a sample XML document for use with the RXS composition engine.

The BLDTPL command aids in composing RPG API Express template (*.tpl) files. These are then converted using the CRTRPGTPL Command, which are in turn used with the Template/Composition Engine subprocedures (RXS_StartComposeEngine(), RXS_ComposeSection, RXS_ComposeVariable(), and RXS_GetComposeBuffer()) to compose XML documents.

**Note: You will need to make manual modifications to the generated .tpl file prior to using it with the CRTRPGTPL Command.**

**Please follow our example on [Creating an RXS Template](https://isupport.katointegrations.com/rxs/3.5/creating_an_rxs_template.md) to understand this process.**

## New in RXS 3.5.0 

### Enhancements 

- The template generation process now supports whitespace in front of section delimiters - sections added to .tpl files before calling CRTRPGTPL no longer need to be fully left-aligned.
- BLDTPL will now preserve namespace attributes when generating templates from sample XML, rather than converting them to variable placeholders.

## Parameters

### XML Input Stream File (SRCSTMF)
This is a required parameter.

Specify the fully-qualified file path of the XML stream file that will be processed to generate the template.

#### Possible Values:
`character-value`
: IFS path to XML file that will be read to generate the template file.

### Template Output Stream File (OUTSTMF)
This is a required parameter.

Specify the fully-qualified file path to the output stream file in the IFS. If this file does not exist, it will be created. If this file does exist, the generated XML template will overwrite any existing content. The user profile must have write authority to the file and directory.

#### Possible Values:
`character-value`
: IFS path where the .tpl file will be written.

### Number of Spaces to Indent (INDENT)
Specify how many spaces should be used when indenting nested elements in the generated .tpl file. If a value of 0 is specified, no elements will be indented.

#### Possible Values:
`2`
: Nested elements will be indented by 2 spaces inside of parent elements. This is the default value.

`numeric-value`
: Specify the number of spaces to indent each level of nesting, up to 9 spaces.

---

# Create RPG Template (CRTRPGTPL)

> Compiles an RXS template source file (.tpl) into a bound service program for use in RPG composition engine calls.

The CRTRPGTPL command is used to generate a compiled source member template used by the RPG API Express 3 XML composition engine. Unlike with RXS 2, newer versions of RPG API Express do not reference template files in the IFS at runtime. Intead, the template source member generated with CRTRPGTPL is included in your programs using the `/COPY` compiler directive and is compiled directly into your program. By using these compiled templates, we remove this additional external runtime dependency, reducing the risk of errors caused by file changes and allowing your programs to be more self-contained. For more information, see our guide for [creating an RXS template](https://isupport.katointegrations.com/rxs/3.5/creating_an_rxs_template.md).

The CRTRPGTPL command is designed to be run against either your existing .tpl template files in the IFS, or new template files that are first created with [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md) and have been modified to add sections as needed. To generated a compiled template member, your .tpl template file requires at least one section.

As with all of your program source code members, the generated template source members should not be stored in the RXS library.

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

## New in RXS 3.5.0 

### New Parameter 

- INCCRTCMD: used to specify whether or not to include the creation command in the generated template output.

### Enhancements 

- The template generation process now supports whitespace in front of section delimiters - sections added to .tpl files before calling CRTRPGTPL no longer need to be fully left-aligned.
- Better support for wrapping escaped single quotation marks in generated output.
- Corrected an issue where the creation command could be generated with a truncated filepath.
- Corrected an issue where the command was sometimes retaining a lock on a source member that was provided as input.

## Parameters

### Input Stream File Template (STMF)
This is a required parameter.

Specify the path name of the template stream file that will be processed to generate the RPG template. This template stream file would have been previously created using the [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md) command.

#### Possible Values:
`character-value`
: Qualified filepath of IFS stream file.

### Output Source File (FILE)
This is a required parameter.

Specify the source physical file where the template source member will be created. The specified source physical file must exist, and the user profile must have authority to the object.

#### Possible Values:
`character-value`
: Name of the source physical file in which the template member will be generated.

#### Qualifier Values:
`*LIBL`
: The library list for the job will be used to located the file. This is the default value.

`character-value`
: Specify the library name where the file is located.

### Output Member Name (MBR)
This is a required parameter.

Specify the name of the source member where the RPG template will be generated. If this source member does not exist, it will be created. If this source member does exist, the generated RPG template will overwrite
any existing content.

Note that the default value of this parameter - `*FIRST` - is a placeholder and must be overwritten.

#### Possible Values:
`character-value`
: Name of the source member.

### Sections DS Name (SECTIONSDS)
This is a required parameter.

Specify the name of the data structure within the RPG template that containsthe template section names.

It is strongly advised to set this value to improve the readability of your program code and allow for sections and variables that may share a name.

#### Possible Values:
`*NONE`
: No name is given to the Sections data structure.

`character-value`
: This name will be assigned to the Sections data structure.

### Variables DS Name (VARSDS)
This is a required parameter.

Specify the name of the data structure within the RPG template that contains the template variable names.

It is strongly advised to set this value to improve the readability of your program code and allow for sections and variables that may share a name.

#### Possible Values:
`*NONE`
: No name is given to the Variables data structure.

`character-value`
: This name will be assigned to the Variables data structure.

### Coded Character Set ID (CCSID)
Specify the CCSID for which the RPG template will be generated. This will additionally control the output of the template source member to support multi-byte character sets when `*STMF` is selected.

#### Possible Values:
`*JOB`
: The CCSID of the current job is used.

`*STMF`
: The CCSID of the IFS stream file is used. This setting must be selected with multi-byte character sets (eg. CCSID 1208).

### Trim Template Lines (TRIMLINES)
Specify whether leading and trailing whitespace should be trimmed from the template. Line control characters - carriage returns and line feeds - will always be preserved.

#### Possible Values:
`*NO`
: All whitespace will be preserved.

`*YES`
: Leading and trailing whitespace will be trimmed.

### Include Creation Command (INCCRTCMD)
Specifies whether the command used to create this template member will be included in generated template output. Including the creation command may make updating and maintaining the template easier in the future.

Including the creation command will not impact the XML composed by the template - it is only included for reference.

#### Possible Values:
`*YES`
: The template creation command will be included within the created template member. This is the default value.

`*NO`
: The template creation command will not be included.

---

# Build Parsing Subprocedure (BLDPRS)

> Generates an RPG handler subprocedure for XML or JSON event-based parsing from a sample document.

The BLDPRS command is used to aid in writing RPG API Express XML or JSON parsing code to be used with the [RXS_Parse()](https://isupport.katointegrations.com/rxs/3.5/rxs_parse.md) and [RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsejson.md) subprocedures. These APIs rely on a "handler" subprocedure which captures various XML or JSON parsing events, and gives you access to the data in the document being parsed.

When provided with a sample XML or JSON file in the IFS, BLDPRS can generate a handler subprocedure for that document. It can generate in either mixed-format (fixed D-specs) or free-format (column limited) RPG. Once generated, this parsing handler can be copied into your program code.

**Note:** You will need to make manual modifications to the generated parsing handler in addition to importing it into your program. The generated code does not include any data handling logic.

## Parameters

### XML or JSON Stream File (SRCSTMF)
This is a required parameter.

Specify the path name of the XML or JSON stream file that will be processed to generate the parsing handler subprocedure.

#### Possible Values:
`character-value`
: IFS path to XML or JSON file that will be read to generate the parsing handler subprocedure.

### Output Source File (OUTFILE)
This is a required parameter if the parsing handler subprocedure is being generated in a source member.

Specify the source physical file (and optionally the library) for the output source member. The specified source physical file must exist, and the user profile must have authority to the object.

#### Possible Values:
`character-value`
: Physical file name in which the source member exists, or will be created.

#### Qualifier Values:
`*LIBL`
: The library list for the job will be used to located the file. This is the default value.

`character-value`
: Specify the library name where the file is located.

### Output Member Name (OUTMBR)
This is a required parameter if the parsing handler subprocedure is being generated in a source member.

Specify the name of the source member to which the generated code will be written. This source member will be created if it does not already exist. If it does exist, the generated parsing handler subprocedure will be appended to any content that is already present in the member. This behavior can be controlled with APPENDOUT.

#### Possible Values:
`character-value`
: Source member that either exists or will be created.

### Output Stream File (OUTSTMF)
This is a required parameter when the generated code is to be written to a stream file in the IFS.

Specify the fully-qualified file path to the output stream file in the IFS. If this file does not exist, it will be created. If the file exists, the generated parsing handler subprocedure will be appended to any content that is already present in the file. This behavior can be controlled with APPENDOUT.

The user profile must have write authority to the file.

#### Possible Values:
`character-value`
: IFS path to the file where the parsing handler code will be written.

### Append Output (APPENDOUT)
This is a required parameter.

Specifies whether the generated parsing handler subprocedure should be appended to the existing contents of the output source member or file, or if the generated code should overwrite any existing content.

#### Possible Values:
`*YES`
: The generated parsing handler subprocedure will append to any existing content in the output source member or file. This is the default value.

`*NO`
: The contents of the output source member or file will be overwritten by the generated parsing handler subprocedure.

### Output RPG Code Format (CODEFORMAT)
This is a required parameter.

Specify the RPG coding format of the generated parsing handler subprocedure.

#### Possible Values:
`*FREE`
: Generate full-free (columns 8-80) RPG code. This is the default value.

`*MIXED`
: Generate mixed format (fixed D-specs, free C-specs) RPG code.

### Parsing Handler Type (PARSETYPE)
This is a required parameter.

Specify the type of parsing handler subprocedure to generate.

#### Possible Values:
`*RXS2XML`
: Generate an XML parsing handler subprocedure for use with the RXS 2 XML parsing API, RXS_parse().

`*RXS3XML`
: Generate an XML parsing handler subprocedure for use with the RXS 3 XML parsing API, RXS_Parse().

`*JSON`
: Generate a JSON parsing handler subprocedure for use with the RXS 3 JSON parsing API, RXS_ParseJson().

### Base XPath/Envelope (BASEENV)
This is an optional parameter, and is only used with XML parsers.

Specify the base XPath (ie. SOAP envelope) that should be used within the parsing handler. The path should not end in XML element characters (< or >). This XPath segment will be defined as a constant within the parsing handler subprocedure that is concatenated onto the XPaths used in the select block where applicable.

#### Possible Values:
`character-value`
: XPath to be used as the base envelope.

### XML Content Event Parsing (XMLCONTENT)
This is a required parameter if an XML parsing handler subprocedure is being generated.

Specify if the XML parsing handler subprocedure should be generated with content parsing events.

XML content events are triggered when an XML element contains data, and have XPaths that end in '/'. For example, the XPath `Request/Item/` would retrieve the contents of the `<Item>` element.

#### Possible Values:
`*YES`
: The parsing handler subprocedure will be generated with content parsing events. This is the default value.

`*NO`
: Content parsing events will not be generated.

### XML Begin Elem. Event Parsing (XMLBEGIN)
This is a required parameter if an XML parsing handler subprocedure is being generated.

Specify if the XML parsing handler subprocedure should be generated with begin element parsing events.

XML begin element events are triggered by the opening tag of an XML element, and have XPaths that end in '&gt;'. For example, the XPath `Request/Item>` would be triggered by the opening tag of the `<Item>` element. These events are typically used to initialize record processing and reset
working fields.

#### Possible Values:
`*YES`
: The parsing handler subprocedure will be generated with begin element parsing events. This is the default value.

`*NO`
: Begin element parsing events will not be generated.

### XML End Elem. Event Parsing (XMLEND)
This is a required parameter if an XML parsing handler subprocedure is being generated.

Specify if the XML parsing handler subprocedure should be generated with end element parsing events.

XML end element events are triggered by the closing tag of an XML element, and have XPaths that end in '/&gt;'. For example, the XPath `Request/Item/>` would be triggered by the closing tag of the `<Item>` element, `</Item>`. These events are typically used to finalize data processing and write
records to physical files.

#### Possible Values:
`*YES`
: The parsing handler subprocedure will be generated with end element parsing events. This is the default value.

`*NO`
: End element parsing events will not be generated.

### XML Attribute Event Parsing (XMLATTR)
This is a required parameter if an XML parsing handler subprocedure is being generated.

Specify if the XML parsing handler subprocedure should be generated with attribute parsing events.

XML attribute events are triggered by the presence of an attribute in an XML element tag. XPaths for attribute events list the XML element, followed by an '@' symbol and the name of the attribute. For example, the XPath `Request/Item@Id` would be triggered by the attribute 'Id' on the `<Item Id="12345">` element.

#### Possible Values:
`*YES`
: The parsing handler subprocedure will be generated with attribute parsing events. This is the default value.

`*NO`
: Attribute parsing events will not be generated.

---

# Build JSON Composition Code (BLDCMP)

> Generates compilation-ready RPG source for composing a JSON document from a sample JSON file, using the RXS JSON composition subprocedures.

The BLDCMP command is used to assist with generating RPG code for composing JSON documents using the RXS JSON composition subprocedures. When provided with a sample JSON file in the IFS, BLDCMP can generate compilation-ready RPG source that will output that JSON in full using the RXS JSON composition subprocedures. It can generate in either mixed-format (fixed D-specs) or free-format (column limited) RPG, and can output to either a source member or a file in the IFS. Once generated, this code can either be used as the foundation for your program or copied into an existing program.

**Note:** You will need to replace all of the content values in the composition subprocedures with appropriate source inputs, since these are populated with the values from the input document. You will also likely want to make manual modifications to the generated code to handle conditions where objects or fields are repeating, ie. arrays. The generated code does not include any data handling logic.

## Parameters

### JSON Input Stream File (SRCSTMF)
This is a required parameter.

Provide the fully-qualified file path to your sample JSON document in the IFS.

#### Possible Values:
`character-value`
: IFS path to JSON file that will be read to generate the RPG composition code.

### Output Source File (OUTFILE)
This is a required parameter when the generated code is to be written to a source member.

Specify the source physical file (and optionally the library) for the output source member.

#### Possible Values:
`character-value`
: Physical file name in which the source member exists, or will be created.

### Output Member Name (OUTMBR)
This is a required parameter when the generated code is to be written to a source member.

Specify the name of the source member to which the generated code will be written. This source member will be created if it does not already exist.

#### Possible Values:
`character-value`
: Source member that either exists or will be created.

### Output Stream File (OUTSTMF)
This is a required parameter when the generated code is to be written to a stream file in the IFS.

Specify the fully-qualified file path to the output stream file in the IFS.

#### Possible Values:
`character-value`
: IFS path to the file where the parsing handler code will be written.

### Append Output (APPENDOUT)
This is a required parameter.

Specify whether the generated code should be appended to the existing contents of the output source member or file, or if the existing content should be overwritten.

#### Possible Values:
`*YES`
: Append generated code to existing content. This is the default value.

`*NO`
: Existing content will be overwritten.

### Code Format (CODEFORMAT)
This is a required parameter.

Specify whether the generated code should be mixed- or free-format RPG.

#### Possible Values:
`*FREE`
: Generate free-format code. This is the default value.

`*MIXED`
: Generate mixed-format code.

---

# Creating an RXS Template

> Tutorial for creating a compiled XML template using BLDTPL and CRTRPGTPL, covering variable placeholders and section declarations.

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

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

- [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md)
- [CRTRPGTPL](https://isupport.katointegrations.com/rxs/3.5/crtrpgtpl.md)

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

## Generating the IFS Template File (BLDTPL)

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

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

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

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

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

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

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

While in the Edit File editor select F2 to save the document changes. Once the sample XML file is saved, call the [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md) command and specify the fully-qualified filepath for the sample XML file in the first parameter, and the fully-qualified filepath for where the generated .tpl template file should be written in the second parameter:

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

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

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

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

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

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

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

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

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

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

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

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

## Generating the Compiled RPG Template (CRTRPGTPL)

Now that we have our IFS template file set up with our sections, we are going to use the [CRTRPGTPL](https://isupport.katointegrations.com/rxs/3.5/crtrpgtpl.md) command to convert our template file into an RPG source member. This RPG source member will be compiled directly into your RPG API Express-enabled program using the `/COPY` compiler directive.

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

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

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

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

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

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

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

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

      /END-FREE
      /ENDIF
```

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

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

## Using the RPG Template With the Composition Engine

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

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

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

     D Template        PR
     D  p                                  Like(RXS_TEMPLATE_PARM)

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

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)

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

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

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

          RXS_ComposeSection( S.PostAdr_end );

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

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

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

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

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

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

---

# Creating an XML Parsing Subprocedure

> Tutorial for generating an XML event handler subprocedure using BLDPRS, covering event type selection and integration into RPG programs.

The RPG-XML Suite subprocedure [RXS_Parse()](https://isupport.katointegrations.com/rxs/3.5/rxs_parse.md) is used to read through an XML document and trigger processing based on XML events detected in that document. These events represent data and elements in the XML document and can used to tailor processing of the XML document to meet your business needs through a customized parsing handler subprocedure. This parsing handler subprocedure is defined within your program, and is specified in the ParseDS.Handler field on the RXS_Parse() configuration data structure parameter.

The following events within an XML document will trigger a call out to the parsing handler subprocedure:

| Event | | Format | | Example |
| --- | --- | --- | --- | --- |
| Element Begin | | `>` | | `Request>` |
| Element Content | | `/` | | `Request/Item/` |
| Element End | | `/>` | | `Request/>` |
| Attribute | | `@` | | `Request/Item@Attribute` |

Depending on the size and complexity of your XML document, there may be many individual events that you need to capture to properly retrieve and handle your data. To help simplify this process, the command [BLDPRS](https://isupport.katointegrations.com/rxs/3.5/bldprs.md) can be used to generate a basic parsing handler for your XML document, into which you can add your custom programming and data handling.

## Sample XML Document

In order to generate a parsing handler, you will first need a sample XML document that is as complete as possible - that is, you should include even elements and attributes which are optional, even if some of them would not logically appear with others in normal usage.

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

`QSH CMD('touch -C 819 /home/user/postadr.xml')`

The file now exists but without content. To add content we will use the EDTF command and add in our sample XML.

`EDTF '/home/user/postadr.xml'`

For demonstration purposes, we'll use the sample XML below. This sample XML may or may not contain data in any or all fields - this will not impact the parser output.

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

While in the Edit File editor select F2 to save the document changes. Now it is time to invoke the BLDPRS command to generate the parsing code.

## Generating the Parsing Handler (BLDPRS)

BLDPRS can generate parsing handlers for both the XML parser and the RXS 3 JSON parser ([RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsejson.md)). The command can generate either free- or mixed-format (fixed D-specs) RPG code, and this code can be output into either a source member or an IFS file. Prompting the BLDPRS command will present the following screen:

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_1.png)

The first parameter will be the fully-qualified filepath for our sample XML document in the IFS. For demonstration purposes, we're going to output the generated parsing handler code to a source member, which means we'll be specifying the Output Source File, Library, and member fields. When writing output to a source member, we do not specify a value in the Output Stream File field.

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_2.png)

The Append Output parameter controls whether any existing content in the specified output member or stream file will be preserved, or whether it will be overwritten. By default, this value is set to `*YES` to preserve existing content. In our demonstration, we'll set it to `*NO` to overwrite any content.

The final parameter - Parsing Handler Type - has three possible values and controls what type of parsing handler is generated. We'll input *RXS3XML to indicate that an RXS 3-formatted XML parsing handler, for use with [RXS_Parse()](https://isupport.katointegrations.com/rxs/3.5/rxs_parse.md), should be generated.

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_3.png)

When we press enter, four additional fields will be brought up.

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_4.png)

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_5.png)

These fields can be used to specify which XML events should have parsing code generated for them. These events are all enabled - the values set to `*YES` - by default. In most situations, you will not need to change these four values. Press enter again to submit the command.

## The Parsing Handler Subprocedure

Once the command finishes executing, you'll see a status message similar to the following:

`Generated parsing handler in USERLIB/QRPGLESRC, POSTADRPRS`

If we open the generated source member in RDi or SEU, we'll see this RPGLE code:

```rpgle
**FREE
       Dcl-Pr XMLHandler;
         pType Char(10) Value;
         pXPath VarChar(1024) Value;
         pData Pointer Value;
         pDataLen Int(10) Value;
       End-Pr;

      //=======================================================================
      //  Remember to update the handler to account for your program logic.
      //=======================================================================

       Dcl-Proc XMLHandler;
         Dcl-Pi *N;
           pType Char(10) Value;
           pXPath VarChar(1024) Value;
           pData Pointer Value;
           pDataLen Int(10) Value;
         End-Pi;

        Dcl-S ParsedData Like(RXS_Var1Kv_t);

        select;

          when pXPath = '/PostAdr>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr@residential';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/name>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/name@title';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/name/first>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/name/first/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/name/first/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/name/last>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/name/last/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/name/last/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/name/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/street>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/street/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/street/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/city>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/city/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/city/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/state>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/state/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/state/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/zip>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/zip/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/zip/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/phone>';
            RXS_JobLog( '***Element Begin: ' + pXPath );

          when pXPath = '/PostAdr/phone/';
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pXPath + ': ' + ParsedData );

          when pXPath = '/PostAdr/phone/>';
            RXS_JobLog( '***Element End: ' + pXPath );

          when pXPath = '/PostAdr/>';
            RXS_JobLog( '***Element End: ' + pXPath );

        endsl;

       End-Proc;
```

Note that, even with the Code Format parameter set to `*FREE`, the generated free-format RPG code is still limited to columns 8-80. This is for compatibility with customers that are using SEU for development, or for customers on 7.1 that do not have the PTFs required for fully free-format RPG code. Though our example XML document does not have any, long XPaths will wrap automatically at or before the 80th column.

The generated code member contains the parsing handler subprocedure prototype, which is intended to be copied into your program code in the main D-specs, and the parsing subprocedure code itself. You should not modify the prototype or the subprocedure declarations, except to change the subprocedure name from XMLHandler if desired.

The body of the parsing handler subprocedure contains a `select` block, composed of `when` statements for each event in the XML document. These when statements are checking the value of the `pXPath` parameter to determine what XML event was detected, and processing that event accordingly. For example, this when statement is checking if the `pXPath` parameter is indicating that the event for the `title` attribute on the `name` element has been detected, and if so it is retrieving the value of the attribute using [RXS_STR()](https://isupport.katointegrations.com/rxs/3.5/rxs_str.md):

```rpgle
when pXPath = '/PostAdr/name@title';
  ParsedData = RXS_STR( pData : pDataLen );
  RXS_JobLog( pXPath + ': ' + ParsedData );
```

For each of these statements, the generated code also includes a call to [RXS_JobLog()](https://isupport.katointegrations.com/rxs/3.5/rxs_joblog.md). This is intended to be for demonstrative purposes, so that you can copy the parsing handler into your program and immediately compile and run the program to see sample output in the job log. These calls should be replaced by your actual program logic to handle the data in the XML document.

## Customizing the Parsing Handler Subprocedure

Once the generated parsing handler subprocedure code has been copied into your program, you will need to modify the programming logic in the handler subprocedure to meet the needs of your specific program. This may involve retrieving data from elements and attributes, performing numeric or data-type conversions, and writing records to one or more physical files. You may also need to store the data within the program for further processing once parsing is complete. The parsing handler subprocedures are very flexible and allow for a high level of customization.

The [ORDERSVC](https://isupport.katointegrations.com/rxs/3.5/examples/ordersvc.md) example program demonstrates using a parsing handler subprocedure to retrieve data from an XML document and write records to several physical files. To do so, the program uses the element begin events to trigger clearing of fields and data structures, and element end events to trigger writing records to the files.

The generated parsing handler subprocedure will contain a `when` statement for each XML event detected in the document. In practice, you will likely not need many of these events for processing. You can safely comment out or remove any unneeded events.

## Sample Program

Here is a full example that demonstrates parsing our sample XML using a fully-customized parsing handler subprocedure, based on the same code we generated in this tutorial:

```rpgle
**FREE
      Ctl-Opt ActGrp(*New) BndDir('RXSBND') Text('RXS XML Handler Example');

        /COPY QRPGLECPY,RXSCB

       Dcl-Pr XMLHandler;
         pType Char(10) Value;
         pXPath VarChar(1024) Value;
         pData Pointer Value;
         pDataLen Int(10) Value;
       End-Pr;

       // Global fields for data output
       Dcl-Ds FullName Qualified;
         Title VarChar(5);
         First VarChar(25);
         Last VarChar(25);
       End-Ds;
       Dcl-Ds Address Qualified;
         Residential Ind;
         Street VarChar(50);
         City VarChar(50);
         State Char(2);
         Zip VarChar(20);
       End-Ds;
       Dcl-S Phone VarChar(20) Dim(3);
       Dcl-S FormattedName VarChar(57);
       Dcl-S FormattedAddress VarChar(125);

       // Processing fields
       Dcl-S XML Like(RXS_Var8Kv_t);

       // RXS templated data structures
       Dcl-Ds ParseDS LikeDS(RXS_ParseDS_t);


       // Load the XML to be parsed - for demonstration purposes, we're manually
       //  setting the XML in this field. In your program, you may be reading
       //  the XML from a stream file in the IFS, or receiving it as part of a
       //  web service request (from RXS_GetStdIn) or response (from
       //  RXS_Transmit) instead.
       reset XML;
       XML = '<PostAdr residential="true"><name title="Mx."><first>Jamie'
           + '</first><last>Hale</last></name><street>2886 Veltri Dr</street>'
           + '<city>Hickory Hills</city><state>VA</state><zip>94124</zip>'
           + '<phone>949-555-4671</phone><phone></phone></PostAdr>';

       // Many RPG API Express APIs accept a configuration data structure
       //  parameter. These templated data structures must be initialized
       //  using RXS_ResetDS - they cannot be initialized with the reset
       //  operation.
       // RXS_ResetDS helps ensure that RXS templated data structures are
       //  initialized in a backwards-compatible fashion
       RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
       
       // Specify the procedure pointer for the parsing handler subprocedure,
       //  using the %Paddr built-in function
       ParseDS.GlobalHandler = %Paddr( XMLHandler );

       monitor;
         // Parse the XML document
         RXS_Parse( XML : ParseDS );

         // Format the parsed data
         reset FormattedName;
         if FullName.Title <> *Blanks;
           FormattedName += FullName.Title + ' ';
         endif;
         FormattedName += FullName.First + ' ' + FullName.Last;

         reset FormattedAddress;
         FormattedAddress = Address.Street + ' '
                          + Address.City + ' '
                          + Address.State + ' '
                          + Address.Zip;

         // Outputting the parsed and formatted data
         RXS_JobLog( 'Name: ' + FormattedName );
         RXS_JobLog( 'Address: ' + FormattedAddress );
         if Phone(1) <> *Blanks;
           RXS_JobLog( 'Phone 1: ' + Phone(1) );
         endif;
         if Phone(2) <> *Blanks;
           RXS_JobLog( 'Phone 2: ' + Phone(2) );
         endif;
         if Phone(3) <> *Blanks;
           RXS_JobLog( 'Phone 3: ' + Phone(3) );
         endif;
       on-error;
         // If an error occurs during parsing, error messages and information
         //  can be found in the ParseDS parameter data structure
         RXS_JobLog( 'Error: ' + ParseDS.ReturnedErrorInfo.MessageText );
       endmon;

       *INLR = *On;
       return;


       // This is a customized parsing handler subprocedure that was initially
       //  generated with BLDPRS. Additional code has been added to support our
       //  program logic, and we've removed the when blocks for XPaths that
       //  we do not need to process.
       // We have also removed the calls to RXS_JobLog - we do not recommend
       //  leaving logging operations in place in a production environment, due
       //  to the additional overhead
       Dcl-Proc XMLHandler;
         Dcl-Pi *N;
           pType Char(10) Value;
           pXPath VarChar(1024) Value;
           pData Pointer Value;
           pDataLen Int(10) Value;
         End-Pi;

        Dcl-S ParsedData Like(RXS_Var1Kv_t);
        // This is a static field used to track the index of the phone array
        Dcl-S PhoneIdx Uns(3) Inz Static;

        select;

          // This is the element begin event for the parent <PostAdr> element
          //  in our XML document. This is a good place to perform any
          //  initialization steps needed, ie. resetting iterators and fields
          when pXPath = '/PostAdr>';
            reset PhoneIdx;
            reset FullName;
            reset Address;

          // This XPath is searching for the attribute content event associated
          //  with the residential attribute on the PostAdr element
          when pXPath = '/PostAdr@residential';
            // Retrieve the data from the attribute
            ParsedData = RXS_STR( pData : pDataLen );
            // Process the parsed data
            if ParsedData = 'true';
              // The RXSCB copybook includes constants that can be easily used
              //  to set indicator values
              Address.Residential = RXS_YES;
            else;
              Address.Residential = RXS_NO;
            endif;

          when pXPath = '/PostAdr/name@title';
            ParsedData = RXS_STR( pData : pDataLen );
            FullName.Title = ParsedData;

          when pXPath = '/PostAdr/name/first/';
            ParsedData = RXS_STR( pData : pDataLen );
            FullName.First = ParsedData;

          when pXPath = '/PostAdr/name/last/';
            ParsedData = RXS_STR( pData : pDataLen );
            FullName.Last = ParsedData;

          when pXPath = '/PostAdr/street/';
            ParsedData = RXS_STR( pData : pDataLen );
            Address.Street = ParsedData;

          when pXPath = '/PostAdr/city/';
            ParsedData = RXS_STR( pData : pDataLen );
            Address.City = ParsedData;

          when pXPath = '/PostAdr/state/';
            ParsedData = RXS_STR( pData : pDataLen );
            Address.State = ParsedData;

          when pXPath = '/PostAdr/zip/';
            ParsedData = RXS_STR( pData : pDataLen );
            Address.Zip = ParsedData;

          when pXPath = '/PostAdr/phone/';
            // Phone number content event detected - increasing the phone
            //  number index
            PhoneIdx += 1;
            ParsedData = RXS_STR( pData : pDataLen );
            Phone(PhoneIdx) = ParsedData;

          when pXPath = '/PostAdr/>';
            // This is the element end event for the parent <PostAdr> element,
            //  and will be the last event triggered in our document. We don't
            //  have any special processing in our program for this event.

        endsl;

       End-Proc;
```

---

# Creating a JSON Parsing Subprocedure

> Tutorial for generating a JSON event handler subprocedure using BLDPRS, covering JSON event types and handler configuration.

The RPG API Express subprocedure [RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsejson.md) is used to read through a JSON document and trigger processing based on JSON events detected in that document. These events represent objects, arrays, and fields in the JSON document and can used to tailor processing of the JSON document to meet your business needs through a customized parsing handler subprocedure. This parsing handler subprocedure is defined within your program, and is specified in the ParseJsonDS.Handler field on the RXS_ParseJson() configuration data structure parameter.

Depending on the size and complexity of your JSON document, there may be many individual events that you need to capture to properly retrieve and handle your data. To help simplify this process, the command [BLDPRS](https://isupport.katointegrations.com/rxs/3.5/bldprs.md) can be used to generate a basic parsing handler for your JSON document, into which you can add your custom programming and data handling.

## JSON Parsing Events

The following element event types within a JSON document will trigger a call out to the parsing handler subprocedure:

| Event | | pType Constant | | pPath Format | | Example |
| --- | --- | --- | --- | --- | --- | --- |
| Array Start | | `RXS_JSON_ARRAY` | | `[*]` | | `/phone[*]` |
| Array End | | `RXS_JSON_ARRAY_END` | | `[*]` | | `/phone[*]` |
| Object Start | | `RXS_JSON_OBJECT` | | `/` | | `/phone` |
| Object End | | `RXS_JSON_OBJECT_END` | | `/` | | `/phone` |

These events are triggered by objects and arrays in the JSON document. These events will not contain data to be retrieved - instead, these events are most often used to control program flow by initializing values or writing records out to physical files.

Additionally, there are multiple types of content events that can be triggered by data found within the JSON document:

| Data Type | | pType Constant | | RPG Data Type |
| --- | --- | --- | --- | --- |
| String | | `RXS_JSON_STRING` | | `A` |
| Boolean | | `RXS_JSON_BOOLEAN` | | `N` |
| Integer | | `RXS_JSON_INTEGER` | | `20I 0` |
| Double | | `RXS_JSON_DOUBLE` | | `8F 0` |
| Null | | `RXS_JSON_NULL` | | |

The default behavior of RXS_ParseJson() is that all content, regardless of data type, will be returned as an **RXS_JSON_STRING** event type and as RPG character data. This is the recommended approach to parsing, especially for documents that contain decimal or currency data. This behavior can be controlled with the ParseJsonDS.ConvertDataToString subfield in the configuration data structure when calling RXS_ParseJson().

## Sample JSON Document

In order to generate a parsing handler, you will first need a sample JSON document that is as complete as possible - that is, you should include even objects and fields which are optional, even if some of them would not logically appear with others in normal usage.

To start, we first need to create a file in the IFS. This file will be populated with our sample JSON data, which will be used to generate the parser. The command below uses QSHELL to create an IFS stream file in the `/tmp` directory using CCSID 819. Note that this command is **case-sensitive**.

`QSH CMD('touch -C 819 /tmp/leads.json')`

The file now exists but without content. To add content you can use the EDTF command, or you can edit the file using Rational Developer for i or a number of other IBM i tools.

`EDTF '/tmp/leads.json'`

For demonstration purposes, we'll use the sample JSON below.

```json
{
  "leads": [
    {
      "name": "Dona Franks",
      "salesProspect": true,
      "address": {
        "streetNumber": 20391,
        "apartment": "177",
        "street": "Central Avenue",
        "city": "Waikele",
        "state": "MN",
        "postalCode": "60247"
      },
      "phone": [
        "+1 (971) 596-2501",
        "+1 (948) 493-2985"
      ],
      "email": "donafranks@example.com"
    },
    {
      "name": "Ortega Stuart",
      "salesProspect": false,
      "address": {
        "streetNumber": 76308,
        "apartment": null,
        "street": "Delmonico Place",
        "city": "Fillmore",
        "state": "VA",
        "postalCode": "94862"
      },
      "phone": [
        "+1 (977) 470-3280"
      ],
      "email": "ortegastuart@example.com"
    }
  ]
}
```

While in the Edit File editor, select F2 to save the document changes. Now it is time to call the BLDPRS command to generate the parsing code.

## Generating the Parsing Handler (BLDPRS)

BLDPRS can generate parsing handlers for both RXS 2 and RXS 3 XML parsers as well as the RXS 3 JSON parser. The command can also generate either free- or mixed-format (fixed D-specs) RPG code, and this code can be output into either a source member or an IFS file. Prompting the BLDPRS command will present the following screen:

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_json_1.png)

The first parameter will be the fully-qualified filepath for our sample JSON document in the IFS. For demonstration purposes, we're going to output the generated parsing handler code to a source member, which means we'll be specifying the Output Source File, Library, and member fields. When writing output to a source member, we do not specify a value in the Output Stream File field.

The Append Output parameter controls whether any existing content in the specified output member or stream file will be preserved, or whether it will be overwritten. By default, this value is set to *YES to preserve existing content. In our demonstration, we'll set it to *NO to overwrite any content.

The final parameter - Parsing Handler Type - has three possible values and controls what type of parsing handler is generated. We'll input *JSON to indicate that a JSON parsing handler, for use with [RXS_ParseJson()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsejson.md), should be generated.

![Build RPG Parse Subprocedure (BLDPRS)](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/bldprs_json_2.png)

## The Parsing Handler Subprocedure

Once the command finishes executing, you'll see a status message similar to the following:

`Generated parsing handler in USERLIB/QRPGLESRC, LEADPRS`

If we open the generated source member in RDi or SEU, we'll see this RPGLE code:

```rpgle
**FREE
       Dcl-Pr JsonHandler Ind;
         pType Int(5) Const;
         pPath Like(RXS_Var64Kv_t) Const;
         pIndex Uns(10) Const;
         pData Pointer Const;
         pDataLen Uns(10) Const;
       End-Pr;

      //=======================================================================
      //  Remember to update the handler to account for your program logic.
      //=======================================================================

       Dcl-Proc JsonHandler;
         Dcl-Pi *N Ind;
           pType Int(5) Const;
           pPath Like(RXS_Var64Kv_t) Const;
           pIndex Uns(10) Const;
           pData Pointer Const;
           pDataLen Uns(10) Const;
         End-Pi;

        Dcl-S ParsedData Like(RXS_Var1Kv_t);

        select;

          when pPath = '/'
           and pType = RXS_JSON_OBJECT;
            RXS_JobLog( '***Object Start: ' + pPath );

          when pPath = '/leads[*]'
           and pType = RXS_JSON_ARRAY;
            RXS_JobLog( '***Array Start: ' + pPath );

          when pPath = '/leads[*]'
           and pType = RXS_JSON_OBJECT;
            RXS_JobLog( '***Object Start: ' + pPath );

          when pPath = '/leads[*]/name'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/salesProspect'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address'
           and pType = RXS_JSON_OBJECT;
            RXS_JobLog( '***Object Start: ' + pPath );

          when pPath = '/leads[*]/address/streetNumber'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address/apartment'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address/street'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address/city'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address/state'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address/postalCode'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/address'
           and pType = RXS_JSON_OBJECT_END;
            RXS_JobLog( '***Object End: ' + pPath );

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_ARRAY;
            RXS_JobLog( '***Array Start: ' + pPath );

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_ARRAY_END;
            RXS_JobLog( '***Array End: ' + pPath );

          when pPath = '/leads[*]/email'
           and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( pPath + ': ' + ParsedData );

          when pPath = '/leads[*]'
           and pType = RXS_JSON_OBJECT_END;
            RXS_JobLog( '***Object End: ' + pPath );

          when pPath = '/leads[*]'
           and pType = RXS_JSON_ARRAY_END;
            RXS_JobLog( '***Array End: ' + pPath );

          when pPath = '/'
           and pType = RXS_JSON_OBJECT_END;
            RXS_JobLog( '***Object End: ' + pPath );

        endsl;

        return RXS_JSON_CONTINUE_PARSING;

       End-Proc;
```

Note that, even with the Code Format parameter set to *FREE, the generated free-format RPG code is still limited to columns 8-80. This is for compatibility with customers that are using SEU for development, or for customers on 7.1 that do not have the PTFs required for fully free-format RPG code. Though our example JSON document does not have any, long paths will wrap automatically at or before the 80th column.

The generated code member contains the parsing handler subprocedure prototype, which may be copied into your program code in the main D-specs (though this is not generally required anymore), and the parsing subprocedure code itself. You should not modify the prototype or the subprocedure declarations, unless it is to change the subprocedure name from JSONHandler.

The body of the parsing handler subprocedure contains a `select` block, composed of `when` statements for each event in the JSON document. These `when` statements are checking the value of the pPath parameter to determine what event was detected, and processing that event accordingly.

For example, the below `when` statement is checking if the pPath parameter contains the path to the **name** attribute within the **leads** object, and that the pType is `RXS_JSON_STRING`. pPath can be identical in parts of the JSON document, so checking pType helps us ensure that this element has character data we can then retrieve with [RXS_STR()](https://isupport.katointegrations.com/rxs/3.5/rxs_str.md):

```rpgle
when pPath = '/leads[*]/name'
 and pType = RXS_JSON_STRING;
  ParsedData = RXS_STR( pData : pDataLen );
```

For each of these statements, the generated code also includes a call to [RXS_JobLog()](https://isupport.katointegrations.com/rxs/3.5/rxs_joblog.md). This is intended to be for demonstrative purposes, so that you can copy the parsing handler into your program and immediately compile and run the program to see sample output in the job log. These RXS_JobLog calls should be removed and replaced by your actual program logic to handle the data in the JSON document.

## Customizing the Parsing Handler Subprocedure

Once the generated parsing handler subprocedure code has been copied into your program, you will need to modify the programming logic in the handler subprocedure to meet the needs of your specific program. This may involve retrieving data from attributes, performing numeric or data-type conversions, or writing records to one or more physical files. You may also need to store the data within the program for further processing once parsing is complete. The parsing handler subprocedures are very flexible and allow for a high level of customization.

The generated parsing handler subprocedure will contain a `when` statement for each JSON event detected in the document. In practice, you will likely not need most of these events for processing. You can safely comment out or remove any unneeded events.

## Sample Program

Here is a full example that demonstrates parsing our sample JSON using a fully-customized parsing handler subprocedure, based on the same code we generated in this tutorial:

```rpgle
**FREE
       Ctl-Opt ActGrp(*New) BndDir('RXSBND') Text('RXS JSON Handler Example');

        /COPY QRPGLECPY,RXSCB

       // Global data structure we're going to use
       //  to store our parsed JSON data
       Dcl-Ds Leads Qualified Dim(5) Inz;
         Name VarChar(30);
         SalesProspect Ind;
         AddressNumber Int(10);
         Apartment VarChar(30);
         Street VarChar(50);
         City VarChar(50);
         State Char(2);
         PostalCode VarChar(10);
         PhoneNumbers VarChar(20) Dim(5);
         Email VarChar(256);
       End-Ds;

       // Global field we'll use to count how many total leads
       //  we parsed from our JSON.
       Dcl-S LeadCount Uns(5);

       // Used when we're looping through our Leads data structure
       //  to output parsed data.
       Dcl-S i Uns(5);

       // RXS templated data structures
       Dcl-Ds ParseJsonDS LikeDS(RXS_ParseJsonDS_t);

       // Many RPG API Express APIs accept a configuration data structure
       //  parameter. These templated data structures must be initialized
       //  using RXS_ResetDS - they cannot be initialized with the reset
       //  operation.
       // RXS_ResetDS helps ensure that RXS templated data structures are
       //  initialized in a backwards-compatible fashion
       RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );

       // For demonstration purposes, we're going to load the JSON from the
       //  /tmp/leads.json file created in the IFS earlier in this process.
       ParseJsonDS.Stmf = '/tmp/leads.json';

       // Specify the procedure pointer for the parsing handler subprocedure,
       //  using the %PAddr built-in function
       ParseJsonDS.Handler = %PAddr( JSONHandler );

       monitor;
         // Parse the JSON document. Because we're loading the JSON from an
         //  IFS file we can *Omit the first parameter - otherwise, that's
         //  where we would pass JSON in as a field.
         RXS_ParseJson( *Omit : ParseJsonDS );

         // We're just going to output the name and email for each lead
         //  to demonstrate that we parsed the data.
         for i = 1 to LeadCount;
           RXS_JobLog( 'Lead #: %s' : %Char(i) );
           RXS_JobLog( 'Name: %s' : Leads(i).Name );
           RXS_JobLog( 'Email: %s' : Leads(i).Email );
         endfor;

       on-error;
         // If an error occurs during parsing, error messages and information
         //  can be found in the ParseDS parameter data structure
         RXS_JobLog( 'Error: ' + ParseJsonDS.ReturnedErrorInfo.MessageText );
       endmon;

       *INLR = *On;
       return;


       // This is a customized parsing handler subprocedure that was initially
       //  generated with BLDPRS. Additional code has been added to support our
       //  program logic, and we've removed the when blocks for XPaths that
       //  we do not need to process.
       // We have also removed the calls to RXS_JobLog - we do not recommend
       //  leaving logging operations in place in a production environment, due
       //  to the additional overhead
       Dcl-Proc JsonHandler;
         Dcl-Pi *N Ind;
           pType Int(5) Const;
           pPath Like(RXS_Var64Kv_t) Const;
           pIndex Uns(10) Const;
           pData Pointer Const;
           pDataLen Uns(10) Const;
         End-Pi;

        Dcl-S ParsedData Like(RXS_Var1Kv_t);

        select;

          // This will detect the root object for our JSON document.
          //  Typically an event like this may be used to reset
          //  data structures or open files.
          when pPath = '/'
           and pType = RXS_JSON_OBJECT;
            // We'll initialize our Leads data structure here.
            reset Leads;

          // This is detecting the start of our 'leads' array. Array
          //  start events are also a good place to perform resets
          //  or file opens, typically for data structures or files
          //  closely related to the array being handled.
          when pPath = '/leads[*]'
           and pType = RXS_JSON_ARRAY;
            // We'll initialize our LeadCount field here.
            reset LeadCount;

          // Note that this has the same '/leads[*]' pPath as the
          //  above event, but this has a pType of RXS_JSON_OBJECT
          //  instead of RXS_JSON_ARRAY. This is because JSON arrays
          //  do not contain key/value pairs like JSON objects do,
          //  and instead just contain a list of values without names.
          //  Because each object within our 'leads' array doesn't
          //  have a name, there's no way to make pPath more unique,
          //  so pType lets us make the distinction between the array
          //  and the object.
          when pPath = '/leads[*]'
           and pType = RXS_JSON_OBJECT;
            // We're keeping a count of how many total leads we parsed,
            //  so we're going to use this event to increment our
            //  LeadCount field.
            LeadCount += 1;

            // As we've defined it, our Leads data structure array can
            //  only hold so many records - if we receive more, we're
            //  going to print a message to the job log and stop
            //  parsing.
            if LeadCount > %Elem(Leads);
              RXS_JobLog( 'Too many leads to store!' );
              // This is explained further at the end of this subproc.
              return RXS_JSON_STOP_PARSING;
            endif;

          when pPath = '/leads[*]/name'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).Name = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]/salesProspect'
           and pType = RXS_JSON_STRING;
            monitor;
              // We're getting 'true' or 'false' back as a string
              //  so we need to do some logic to convert that to
              //  an indicator. We'll first retrieve the data
              //  into ParsedData as a work field.
              ParsedData = RXS_STR( pData : pDataLen );
              if ParsedData = 'true';
                Leads(LeadCount).SalesProspect = RXS_YES;
              else;
                Leads(LeadCount).SalesProspect = RXS_NO;
              endif;
            on-error;
              RXS_JobLog( 'Failed to convert salesProspect!' );
            endmon;

          // In this case, the 'address' object was just used
          //  to group a number of related properties together
          //  in the JSON document. We don't really need to
          //  do anything with the object start or end events
          //  as a result.
          when pPath = '/leads[*]/address'
           and pType = RXS_JSON_OBJECT;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          when pPath = '/leads[*]/address/streetNumber'
           and pType = RXS_JSON_STRING;
            monitor;
              // This is a numeric JSON field, specifically an
              //  integer, so we need to do special handling to
              //  convert from string to int.
              ParsedData = RXS_STR( pData : pDataLen );
              Leads(LeadCount).StreetNumber = %Uns( ParsedData );
            on-error;
              RXS_JobLog( 'Failed to convert streetNumber!' );
            endmon;

          when pPath = '/leads[*]/address/apartment'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).Apartment = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]/address/street'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).Street = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]/address/city'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).City = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]/address/state'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).State = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]/address/postalCode'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).PostalCode = RXS_STR( pData : pDataLen );

          // In this case, the 'address' object was just used
          //  to group a number of related properties together
          //  in the JSON document. We don't really need to
          //  do anything with the object start or end events
          //  as a result.
          when pPath = '/leads[*]/address'
           and pType = RXS_JSON_OBJECT_END;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_ARRAY;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_STRING;
            // We haven't used the pIndex parameter yet, but it's
            //  going to be helpful here. pIndex contains the
            //  index of our current JSON property within the
            //  parent object or array starting from 1. So for this
            //  'phones' array, the first value in 'phones' would have
            //  a pIndex of 1, the second would have a pIndex of 2,
            //  and so on. In this case we just have to be careful
            //  that we aren't trying to store more phone numbers than
            //  our data structure has space for.
            if pIndex <= %Elem(Leads(LeadCount).PhoneNumbers);
              Leads(LeadCount).PhoneNumbers(pIndex) =
                RXS_STR( pData : pDataLen );
            else;
              RXS_JobLog( 'Too many phone numbers for this lead!' );
            endif;

          when pPath = '/leads[*]/phone[*]'
           and pType = RXS_JSON_ARRAY_END;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          when pPath = '/leads[*]/email'
           and pType = RXS_JSON_STRING;
            Leads(LeadCount).Email = RXS_STR( pData : pDataLen );

          when pPath = '/leads[*]'
           and pType = RXS_JSON_OBJECT_END;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          // This is detecting the end of our 'leads' array. Array
          //  end events are typically a good place to close files.
          when pPath = '/leads[*]'
           and pType = RXS_JSON_ARRAY_END;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

          // This will detect the end of the root object for our
          //   JSON document. Typically an event like this may be
          //  used to close files.
          when pPath = '/'
           and pType = RXS_JSON_OBJECT_END;
            // We don't have to do anything here in this example. This
            //  'when' statement could be removed from this program.

        endsl;

        // A parsing handler must always return RXS_JSON_CONTINUE_PARSING
        //  or RXS_JSON_STOP_PARSING. RXS_JSON_CONTINUE_PARSING will cause
        //  the parser to continue to the next event within the JSON
        //  document being parsed, while RXS_JSON_STOP_PARSING will cause
        //  the parser to stop parsing the document. This is useful when
        //  performing error handling as well as if you know you only data
        //  from a specific portion of a JSON document. By returning
        //  RXS_JSON_STOP_PARSING you can ignore the rest of the JSON
        //  document, helping your program run more efficiently.
        return RXS_JSON_CONTINUE_PARSING;

       End-Proc;
```

---

# Offering a Web Service

> End-to-end tutorial for writing an RXS-hosted web service: parsing POST stdin data, composing an XML response with the template engine, and writing output with RXS_PutStdOut().

The following example program demonstrates how to offer a web service using RPG API Express APIs. We will be receiving and transmitting XML data, and we will parse the request data using the DOM parser.

Throughout this tutorial, we will reference code samples. Some lines may have been omitted in these samples, and you should reference the [full program source code](#full-program-code) at the bottom of this page.

Here are the APIs we will be using:

| API Name | Description |
| --- | --- |
| [RXS_ResetDS()](https://isupport.katointegrations.com/rxs/3.5/rxs_resetds.md) | Resets and initializes RXS datastructures. |
| [RXS_GetStdIn()](https://isupport.katointegrations.com/rxs/3.5/rxs_getstdin.md) | Retrieves POST request data from stdin. |
| [RXS_OpenDom()](https://isupport.katointegrations.com/rxs/3.5/rxs_opendom.md) | Opens a DOM parsing session. |
| [RXS_XPath()](https://isupport.katointegrations.com/rxs/3.5/rxs_xpath.md) | Builds and formats an XPath for use in DOM parsing. |
| [RXS_ParseDomToText()](https://isupport.katointegrations.com/rxs/3.5/rxs_parsedomtotext.md) | Retrieves the data from the specified XPath. |
| [RXS_CloseDom()](https://isupport.katointegrations.com/rxs/3.5/rxs_closedom.md) | Ends a DOM parsing session and frees associated memory. |
| [RXS_StartComposeEngine()](https://isupport.katointegrations.com/rxs/3.5/rxs_startcomposeengine.md) | Opens an XML composition session and initializes the composition engine. |
| [RXS_ComposeSection()](https://isupport.katointegrations.com/rxs/3.5/rxs_composesection.md) | Writes a template section to the composition buffer. |
| [RXS_ComposeVariable()](https://isupport.katointegrations.com/rxs/3.5/rxs_composevariable.md) | Assigns a value to a template variable. |
| [RXS_GetComposeBuffer()](https://isupport.katointegrations.com/rxs/3.5/rxs_getcomposebuffer.md) | Retrieves the composed XML from the composition engine buffer. |
| [RXS_PutStdOut()](https://isupport.katointegrations.com/rxs/3.5/rxs_putstdout.md) | Sends response data to stdout. |
| [RXS_JobLog()](https://isupport.katointegrations.com/rxs/3.5/rxs_joblog.md) | Prints a formatted message to the job log. |

There are three main parts to a web service program:

1. [Parsing](#parsing) - retrieving data from the body of a POST request.
2. [Composing](#composing) - using a template member to compose response XML.
3. [Transmitting](#transmitting) - sending the HTTP response.

## Parsing 

When offering a web service, request data will be retrieved from stdin:

```rpgle
     gXmlRequest = RXS_GetStdIn();
```

Our gXmlRequest field now contains the XML content from the body of the POST request. We are expecting the following XML structure:

```xml
<GetOrdersRequest>
  <Customer>...</Customer>
</GetOrdersRequest>
```

We need to retrieve the data from our XML. Based on our expected request document structure, we will use the DOM parser to drill down to the specific target node and retrieve the raw data into gCUST:

```rpgle
     gRootDomDS = RXS_OpenDom( gXmlRequest );
     gXPath = RXS_XPath( '/GetOrdersRequest/Customer' );
     gCUST = RXS_ParseDomToText( gXPath : gRootDomDS);
```

## Composing 

In order to build our response XML, we need a compiled RXS template member. This example uses the following template file (built in the IFS using [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md)):

```xml
::GetOrders_Open
<GetOrdersResponse>
::Order_Line
  <Order>.:OrderNumber:.</Order>
::GetOrders_Close
</GetOrdersResponse>
```

which is compiled into a template source member using [CRTRPGTPL](https://isupport.katointegrations.com/rxs/3.5/creating_an_rxs_template.md). This template source member must be /COPY-ed into our program code twice. First, above the D-specs:

```rpgle
      /copy QRPGLETPL,GETORDS
```

and second, within our Template subprocedure:

```rpgle
     P Template        B
     D                 PI

     D  p                                  Like(RXS_TEMPLATE_PARM)

      /copy QRPGLETPL,GETORDS

     P                 E
```

The TemplateProcedure field in gComposeDS tells the composition engine where to find the Template subprocedure. We use a combination of RXS_ComposeSection() and RXS_ComposeVariable() to build the XML document:

```rpgle
     gComposeDS.TemplateProcedure = %paddr(Template);
     RXS_StartComposeEngine( gComposeDS );
     RXS_ComposeSection( GetOrders_Open );

     read QORDFILE;
     dou %eof(QORDFILE);
       if gCUST = CUST;
         RXS_ComposeVariable( OrderNumber : %trim(%editc(Order:'Z')) );
         RXS_ComposeSection( Order_Line );
       endif;
       read QORDFILE;
     enddo;

     RXS_ComposeSection( GetOrders_Close );
```

Variables within a section must be composed using RXS_ComposeVariable() before the section is composed with RXS_ComposeSection().

## Transmitting 

We will be returning our response data using stdout, as "text/xml" content. We are using RXS_GetComposeBuffer() to retrieve the composed XML data and release the associated memory:

```rpgle
     gPutStdOutDS.HeaderContentType = 'text/xml';
     RXS_PutStdOut( RXS_GetComposeBuffer() : gPutStdOutDS );
```

Once the response data is sent to stdout, your Apache instance will handle the rest of the communication.

## Full Program Code 

### QRPGLESRC/GETORDS.RPGLE

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND')

     FQORDFILE  IF   E             DISK

      /copy QRPGLECPY,RXSCB
      /copy QRPGLETPL,GETORDS


     D gComposeDS      DS                  LikeDS(RXS_ComposeDS_t)
     D gRootDomDS      DS                  LikeDS(RXS_ParseDomDS_t)
     D gXPath          S                   Like(RXS_Var8Kv_t)
     D gPutStdOutDS    DS                  LikeDS(RXS_PutStdOutDS_t)

     D gXmlRequest     S                   Like(RXS_Var64Kv_t)
     D gCUST           S                   Like(CUST)

       /free

       *inlr = *on;

       monitor;

         gXmlRequest = RXS_GetStdIn();

         // Parse

         // <GetOrdersRequest>
         //   <Customer>1234</Customer>
         // </GetOrdersRequest>

         RXS_ResetDS( gRootDomDs : RXS_DS_TYPE_OPENDOM );
         gRootDomDS = RXS_OpenDom( gXmlRequest );
         gXPath = RXS_XPath( '/GetOrdersRequest/Customer' );
         gCUST = RXS_ParseDomToText( gXPath : gRootDomDS);
         RXS_CloseDom( gRootDomDS );

         // Compose

         RXS_ResetDS( gComposeDS : RXS_DS_TYPE_COMPOSE );
         gComposeDS.TemplateProcedure = %paddr(Template);
         RXS_StartComposeEngine( gComposeDS );

         RXS_ComposeSection( GetOrders_Open );

         read QORDFILE;
         dou %eof(QORDFILE);
           if gCUST = CUST;
             RXS_ComposeVariable( OrderNumber : %trim(%editc(Order:'Z')) );
             RXS_ComposeSection( Order_Line );
           endif;
           read QORDFILE;
         enddo;

         RXS_ComposeSection( GetOrders_Close );

         // Transmit

         RXS_ResetDS( gPutStdOutDS : RXS_DS_TYPE_PUTSTDOUT );
         gPutStdOutDS.HeaderContentType = 'text/xml';
         RXS_PutStdOut( RXS_GetComposeBuffer() : gPutStdOutDS );

       on-error;

         RXS_Joblog('Unexpected error occurred. See previous messages.');

       endmon;

      /end-free

     P Template        B
     D                 PI

     D  p                                  Like(RXS_TEMPLATE_PARM)

      /copy QRPGLETPL,GETORDS

     P                 E
```

### QRPGLETPL/GETORDS.RPGLE

```rpgle
      /IF NOT DEFINED(GETORDS)                                            
      /DEFINE GETORDS                                                     
     D  GetOrders_Open...                                                 
     D                 C                   'GetOrders_Open'               
     D  Order_Line...                                                     
     D                 C                   'Order_Line'                   
     D  GetOrders_Close...                                                
     D                 C                   'GetOrders_Close'              
     D  OrderNumber...                                                    
     D                 C                   'OrderNumber'                  
      /ELSE                                                               
      /free                                                               
       p  = '';                                                           
       p += '::GetOrders_Open' + x'15';                                   
       p += '<GetOrdersResponse>' + x'15';                                
       p += '::Order_Line' + x'15';                                       
       p += '  <Order>' + '.:OrderNumber:.' + '</Order>' + x'15';         
       p += '::GetOrders_Close' + x'15';                                  
       p += '</GetOrdersResponse>' + x'15';  

      /end-free 
      /ENDIF
```

---

# Configuring RXS Router

> Two-stage guide to configuring Apache httpd.conf for RXS CGI routing and setting up static or dynamic RXSRTR routing entries.

There are two main stages to routing configuration when using RXS Router. The first happens through the Apache configuration file - httpd.conf - which is typically in an IFS path like `/www/rxs/conf/httpd.conf` or `/www/myrxs/conf/httpd.conf`. The second stage happens within RXS Router and the RXS Router environments on your system.

## Apache Configuration
RXS Router determines which environment to load based on the value of the RXSRTR_ENVIRONMENT variable, which is set by Apache. The first step is to add the following directive to your httpd.conf file:

```apache
ScriptAliasMatch ^/(([^/.]+/)*[^/.]+)$ /qsys.lib/RXS.lib/rxsrtr.pgm
<Directory /qsys.lib/RXS.lib/>
  Options +ExecCGI
  Allow From all
</Directory>
```

This directive tells Apache to call RXSRTR for every URL on which it is listening. This will allow Apache to transfer control to RXS Router, which will then run the associated programs before returning any output to Apache.

RXS Router checks the RXSRTR_ENVIRONMENT variable to determine which configuration should be loaded for a given request. This environment variable value is set by Apache. For each path which has a corresponding RXS Router environment, a Location element must be added to httpd.conf:

```apache
<Location /myrxs/>
  SetEnv RXSRTR_ENVIRONMENT 'MYRXS'
</Location>
```

The value of '/myrxs/' should be substituted by your desired URL path, and the value of 'MYRXS' should correspond to the RXS Router environment that should be loaded.

## RXS Router Configuration
The configuration of your RXS Router environment depends on which type of routing you are using - static or dynamic. Configuration records are stored in control files, which are accessible with the [WRKRXSRTRE](https://isupport.katointegrations.com/rxs/3.5/wrkrxsrtre.md) command.

### Routing

#### Static (environment variable) routing
If the **routingid flag** has a value which begins with `ENVVAR:`, RXSRTR will check the specified environment variable on each request to determine the RRID. For instance, if the routingid flag is specified as follows:

`-routingid envvar:http_soapaction`

then whenever RXSRTR is called by the HTTP server, it will retrieve the value of the HTTP_SOAPACTION environment variable and will use that value as the RRID.

This processing will be performed for every request handled by RXSRTR, irrespective of the actual URL used.

If the specified environment variable is a path name, you may specify a qualifier of a colon followed by a slash, immediately following the environment variable name, e.g.:

`-routingid envvar:http_soapaction:/`

and RXSRTR will use only the last path segment of the environment variable as the RRID. For instance, if the value of the HTTP_SOAPACTION variable is

`https://www.mycompany.com/webservices/local/values/focus/getasset`

then the RRID used would be 'GETASSET'.

#### Dynamic routing
If the **routingid** flag has a value which does not begin with `ENVVAR:`, RXSRTR will use 'dynamic' routing, whereby the actual URL used by the client will be parsed to determine the RRID.

RXSRTR uses the following steps to determine the RRID from the URL (as soon as the RRID has been determined, further steps are bypassed):

1. The value of the QUERY_STRING environment variable is retrieved as {querystring}
2. The SCRIPT_NAME environment variable is retrieved as {scriptname} and parsed into its constituent 'segments'. For instance, a SCRIPT_NAME value of /rxsrtr/petstore/item' is parsed into 3 separate segments - 'rxsrtr', 'petstore' and 'item'. All values are converted to upper-case for comparison purposes during parsing.
3. If one of the {scriptname} segments has a value of routingid and the next segment is not blank, that next segment is used as the RRID.
4. If {querystring} is blank, the first {scriptname} segment (retrieved in step 2) is used as the RRID.
5. If one of the variables in {querystring} is routingid, its value is used as the RRID.
6. Otherwise the first {scriptname} segment is used as the RRID.

This means that all of the following example URI's will result in an RRID of 'MICKEY' (assuming that the value of the routingid flag has the default value of 'RXSRTR')

7. `http://192.168.0.1/rxsrtr/mickey/foo/bar`
8. `http://192.168.0.1/mickey/foo/bar`
9. `http://192.168.0.1/foo/bar?fname=john&rxsrtr=mickey&lname=smith`
10. `http://192.168.0.1/mickey/foo/bar?fname=john&lname=smith`

In case 1, RXSRTR is specified as a path segment and the subsequent path segment is not blank, so it is used as the RRID. In case 2, RXSRTR is not specified as any of the path segments, and the QUERY_STRING variable is blank, so the first path segment is used as the RRID. In case 3, RXSRTR is not specified as any of the path segments. However, the QUERY_STRING variable does contain a variable called RXSRTR, so that variable's value is used as the RRID. In case 4, RXSRTR is not specified as any of the path segments, and the none of the QUERY_STRING variables is called 'RXSRTR', so the first path segment is used as the RRID.

### RXSRTRCTL Entries
Configuration entries for RXS Router are managed using [WRKRXSRTRE](https://isupport.katointegrations.com/rxs/3.5/wrkrxsrtre.md). For a given RXS Router control file, there will be two or more individual entries, which are used to determine what programs are called by RXS Router.

Each control file must contain a \*CONFIG entry. This entry is loaded first by RXS Router, and is used to determine which record in the environment is loaded for the final routing stage. More information about the options used by \*CONFIG can be found on the [RXS Router](https://isupport.katointegrations.com/rxs/3.5/rxsrtr.md) page.

The default value for the routing ID is **RXSRTR**, which instructs RXS Router to use dynamic routing to determine which control file record to load, based on the URL. For static routing, RXS Router will refer to the value of the specified environment variable to determine the correct control file record, and the routing ID flag must be specified using the `ENVVAR:` prefix.

Subsequent entries within your RXS Router control file contain configuration information for calling a specific program. RXS Router determines which of these records to invoke using the criteria outlined in the [Routing](#routing) section. There are many flags that can be specified for a given routing ID, but the only required one is `-pgm`, which tells RXS Router which program should be called.

The `-pgm` flag can either contain the name of a program or a special value (\*0-\*9). If a name is specified, RXS Router will search through the library list of the job for a match, or look in the library specified in the `-lib` tag. If a special value is used, RXS Router will refer to the URL path to determine which program should be called.

#### With Name
The following configuration will run the program DEMOSVC in the library MYRXS:

`-pgm DEMOSVC -lib MYRXS`

#### With Special Value
The following configuration will dynamically determine the program to be called, based on the URL, within the library list of the job decription (\*JOBD) specified by the `-liblst` flag:

`-pgm *1 -liblst MYRXS/RTRJOB`

The special value of \*0 corresponds to the routing ID, while \*1-\*9 represent URL path segments. If the service was called using the following URL:

`https:/api.myservice.com/myrxs/demosvc/`

the routing ID will be MYRXS, and RXS Router will call the program DEMOSVC.

---

# Example 1

> RPG example composing an XML request, calling a remote web service, and parsing the XML response with the event-based parser.

This example program demonstrates the basic structure of an RPG program utilizing RXS to compose an XML request, call a remote web service, and parse the response XML with event-based parsing.

### Free Format EX1.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 1 - Celsius SOAP XML');

/COPY QRPGLECPY,RXSCB
/COPY QRPGLETPL,CELSIUS

Dcl-S gFahrenheit Int(10);
Dcl-S gCelsius Int(10);
Dcl-S gXmlRequest Like(RXS_Var64Kv_t);
Dcl-S gXmlResponse Like(RXS_Var64Kv_t);

Dcl-Ds ComposeDS LikeDS(RXS_ComposeDS_t);
Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
Dcl-Ds ParseDS LikeDS(RXS_ParseDS_t);
Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

monitor;
  gFahrenheit = 100;

  exsr compose;
  exsr transmit;
  exsr parse;

  RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
  ErrorDS.MessageId = 'RXS9897';
  ErrorDS.MessageData = 'Celsius Temp: ' + %Trim ( %EditC( gCelsius : '3' ) )
                      + ' degrees' ;
  ErrorDS.ThrowToCaller = RXS_YES;
  RXS_Throw( ErrorDS );
on-error;

endmon;

*INLR = *On;
return;

begsr compose;
  RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
  ComposeDS.TemplateProcedure = %Paddr( Template );
  RXS_StartComposeEngine( ComposeDS );

  RXS_ComposeVariable( fahrenheit : %Char( gFahrenheit ) );
  RXS_ComposeSection( content );

  gXmlRequest = RXS_GetComposeBuffer();
endsr;

begsr transmit;
  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.URI = 'https://www.w3schools.com/xml/tempconvert.asmx';
  TransmitDS.HeaderSOAPAction
    = '"https://www.w3schools.com/xml/FahrenheitToCelsius"';
  TransmitDS.LogFile = '/tmp/celsius.txt';
  TransmitDS.HeaderContentType = 'text/xml; charset=utf-8';
  TransmitDS.RequestCcsid = RXS_CCSID_UTF8;
  // SSL Certificates should always be installed for security reasons,
  //   but for now ignore any missing certificates
  //   See this page for instructions on installing needed certificates:
  //   https://isupport.katointegrations.com/rxs/installing_certificate_authorities/
  // For example purposes only, turnoff SSL Verification.
  TransmitDS.SSLVerifyHost = RXS_NO;
  gXmlResponse = RXS_Transmit( gXmlRequest : TransmitDS );
endsr;

begsr parse;
  RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
  ParseDS.GlobalHandler = %Paddr( XmlHandler );
  RXS_Parse( gXmlResponse : ParseDS );
endsr;


Dcl-Proc XmlHandler;
  Dcl-Pi *N;
    pType Char(10) Value;
    pXPath VarChar(1024) Value;
    pData Pointer Value;
    pDataLen Int(10) Value;
  End-Pi;

  Dcl-S ParsedData Like(RXS_Var1Kv_t);

  select;
    when pXPath = '/Envelope/Body/FahrenheitToCelsiusResponse'
                + '/FahrenheitToCelsiusResult/';
      monitor;
        ParsedData = RXS_STR( pData : pDataLen );
        gCelsius = %Int( ParsedData );
      on-error 105;
        gCelsius = 0;
      endmon;

  endsl;

End-Proc;


Dcl-Proc Template;
  Dcl-Pi *N;
    p Like(RXS_TEMPLATE_PARM);
  End-Pi;

  // Template RPG source was created from the actual template
  //  STMF using the CRTRPGTPL command.
  //
  // The RPG Template source is copied from QRPGLETPL by using
  //  /copy once in the D-specs and again below.

  /COPY QRPGLETPL,CELSIUS
End-Proc;
```

### Fixed Format FX_EX1.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 1 - Celsius SOAP XML')

      /COPY QRPGLECPY,RXSCB
      /COPY QRPGLETPL,CELSIUS

     D Template        PR
     D  p                                  Like(RXS_TEMPLATE_PARM)

     D XmlHandler      PR
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value

     D gFahrenheit     S             10I 0
     D gCelsius        S             10I 0
     D gXmlRequest     S                   Like(RXS_Var64Kv_t)
     D gXmlResponse    S                   Like(RXS_Var64Kv_t)

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)
     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D ParseDS         DS                  LikeDS(RXS_ParseDS_t)
     D ErrorDS         DS                  LikeDS(RXS_CatchThrowErrorDS_t)

      /free

       monitor;

         gFahrenheit = 100;

         exsr compose;

         exsr transmit;

         exsr parse;

         RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
         ErrorDS.MessageId = 'RXS9897';
         ErrorDS.MessageData = 'Celsius Temp: '
           + %Trim ( %EditC( gCelsius : '3' ) + 'degrees');
         ErrorDS.ThrowToCaller = RXS_YES;
         RXS_Throw( ErrorDS );
       on-error;

       endmon;

       *INLR = *On;
       return;


       begsr compose;

         RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
         ComposeDS.TemplateProcedure = %Paddr(Template);
         RXS_StartComposeEngine(ComposeDS);

         RXS_ComposeVariable( fahrenheit : %Char(gFahrenheit) );
         RXS_ComposeSection( content );

         gXmlRequest = RXS_GetComposeBuffer();

       endsr;


       begsr transmit;

         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.URI =
           'https://www.w3schools.com/xml/tempconvert.asmx';
         TransmitDS.HeaderSOAPAction =
           '"https://www.w3schools.com/xml/FahrenheitToCelsius"';
         TransmitDS.LogFile = '/tmp/celsius.txt';
         TransmitDS.HeaderContentType = 'text/xml; charset=utf-8';
         TransmitDS.RequestCcsid = RXS_CCSID_UTF8;
         // SSL Certificates should always be installed for security reasons,
         //   but for now ignore any missing certificates
         //   See this page for instructions on installing needed certificates:
         //   https://isupport.krengeltech.com/rxs/installing_certificate_authorities/
         // For example purposes only, turnoff SSL Verification.
         TransmitDS.SSLVerifyHost = RXS_NO;
         gXmlResponse = RXS_Transmit( gXmlRequest : TransmitDS );

       endsr;

       begsr parse;

         RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
         ParseDS.GlobalHandler = %Paddr( XmlHandler );
         RXS_Parse( gXmlResponse : ParseDS );

       endsr;

      /end-free

     P XmlHandler      B
     D                 PI
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value
     D
     D ParsedData      S                   Like(RXS_Var1Kv_t)
      /free

       select;
         when pXPath = '/Envelope/Body/FahrenheitToCelsiusResponse'
                     + '/FahrenheitToCelsiusResult/';
           monitor;
             ParsedData = RXS_STR( pData : pDataLen );
             gCelsius = %Int( ParsedData );
           on-error 105;
             gCelsius = 0;
           endmon;

       endsl;

      /end-free
     P                 E

     P Template        B
     D                 PI
     D  p                                  Like(RXS_TEMPLATE_PARM)

        // Template RPG source was created from the actual template
        //  STMF using the CRTRPGTPL command.
        //
        // The RPG Template source is copied from QRPGLETPL by using
        //  /copy once in the D-specs and again below.

      /COPY QRPGLETPL,CELSIUS
     P                 E
```

### CELSIUS.tpl

```rpgle
      /IF NOT DEFINED(CELSIUS)
      /DEFINE CELSIUS
     D CONTENT...
     D                 C                   'CONTENT'
     D fahrenheit...
     D                 C                   'fahrenheit'
      /ELSE
      /FREE
       p  = '';
       p += '::CONTENT' + x'15';
       p += '<?xml version="1.0" encoding="utf-8"?>' + x'15';
       p += '<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema';
       p += '-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmln';
       p += 's:soap12="http://www.w3.org/2003/05/soap-envelope">' + x'15';
       p += '  <soap12:Body>' + x'15';
       p += '    <FahrenheitToCelsius xmlns="https://www.w3schools.com/xm';
       p += 'l/">' + x'15';
       p += '      <Fahrenheit>' + '.:fahrenheit:.' + '</Fahrenheit>' + x'15';
       p += '    </FahrenheitToCelsius>' + x'15';
       p += '  </soap12:Body>' + x'15';
       p += '</soap12:Envelope>' + x'15';

      /END-FREE
      /ENDIF
```

---

# Example 2

> RPG example composing an XML request, calling a remote web service, and parsing the XML response with the DOM parser.

This example program demonstrates the basic structure of an RPG program utilizing RXS to compose an XML request, call a remote web service, and parse the response XML with the DOM parser.

### Free Format EX2.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 2 - Celsius SOAP XML DOM');

/COPY QRPGLECPY,RXSCB
/COPY QRPGLETPL,CELSIUS

Dcl-S gData Like(RXS_Var1Kv_t);
Dcl-S gCelsius Int(10);
Dcl-S gFahrenheit Int(10);
Dcl-S gXmlRequest Like(RXS_Var64Kv_t);
Dcl-S gXmlResponse Like(RXS_Var64Kv_t);
Dcl-S gXPath Like(RXS_Var8Kv_t);

Dcl-Ds ComposeDs LikeDS(RXS_ComposeDS_t);
Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
Dcl-Ds RootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

monitor;
  gFahrenheit = 100;

  exsr compose;
  exsr transmit;
  exsr parse;

  RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
  ErrorDS.MessageId = 'RXS9897';
  ErrorDS.MessageData = 'Celsius Temp: ' + %Trim ( %EditC( gCelsius : '3' ) )
                      + ' degrees' ;
  ErrorDS.ThrowToCaller = RXS_YES;
  RXS_Throw( ErrorDS );
on-error;

endmon;

*INLR = *On;
return;

begsr compose;
  RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
  ComposeDS.TemplateProcedure = %Paddr( Template );
  RXS_StartComposeEngine( ComposeDS );

  RXS_ComposeVariable( fahrenheit : %Char( gFahrenheit ) );
  RXS_ComposeSection( content );

  gXmlRequest = RXS_GetComposeBuffer();
endsr;


begsr transmit;
  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.URI = 'https://www.w3schools.com/xml/tempconvert.asmx';
  TransmitDS.HeaderSOAPAction
    = '"https://www.w3schools.com/xml/FahrenheitToCelsius"';
  TransmitDS.LogFile = '/tmp/celsius.txt';
  TransmitDS.HeaderContentType = 'text/xml; charset=utf-8';
  TransmitDS.RequestCcsid = RXS_CCSID_UTF8;
  // SSL Certificates should always be installed for security reasons,
  //   but for now ignore any missing certificates
  //   See this page for instructions on installing needed certificates:
  //   https://isupport.katointegrations.com/rxs/installing_certificate_authorities/
  // For example purposes only, turnoff SSL Verification.
  TransmitDS.SSLVerifyHost = RXS_NO;
  gXmlResponse = RXS_Transmit( gXmlRequest : TransmitDS );
endsr;

begsr parse;
  RootDomDS = RXS_OpenDom( gXmlResponse );

  gXPath = RXS_XPath( '/*:Envelope/*:Body/*:FahrenheitToCelsiusResponse'
                    + '/*:FahrenheitToCelsiusResult' );

  gData = RXS_ParseDomToText( gXPath : RootDomDS );

  RXS_CloseDom( RootDomDS );

  gCelsius = %Int( gData );
endsr;


Dcl-Proc Template;
  Dcl-Pi *N;
    p Like(RXS_TEMPLATE_PARM);
  End-Pi;

  // Template RPG source was created from the actual template
  //  STMF using the CRTRPGTPL command.
  //
  // The RPG Template source is copied from QRPGLETPL by using
  //  /copy once in the D-specs and again below.

  /COPY QRPGLETPL,CELSIUS
End-Proc;
```

### Fixed Format FX_EX2.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 2 - Celsius SOAP XML DOM')

      /COPY QRPGLECPY,RXSCB
      /COPY QRPGLETPL,CELSIUS

     D Template        PR
     D  p                                  Like(RXS_TEMPLATE_PARM)

     D gData           S                   Like(RXS_Var1Kv_t)
     D gCelsius        S             10I 0
     D gFahrenheit     S             10I 0
     D gXmlRequest     S                   Like(RXS_Var64Kv_t)
     D gXmlResponse    S                   Like(RXS_Var64Kv_t)
     D gXPath          S                   Like(RXS_Var8Kv_t)

     D ComposeDS       DS                  LikeDS(RXS_ComposeDS_t)
     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D RootDomDS       DS                  LikeDS(RXS_ParseDomDS_t)
     D ErrorDS         DS                  LikeDS(RXS_CatchThrowErrorDS_t)

      /free
       monitor;

         gFahrenheit = 100;

         exsr compose;

         exsr transmit;

         exsr parse;

         RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
         ErrorDS.MessageId = 'RXS9897';
         ErrorDS.MessageData = 'Celsius Temp: '
           + %Trim ( %EditC( gCelsius : '3' ) + 'degrees');
         ErrorDS.ThrowToCaller = RXS_YES;
         RXS_Throw( ErrorDS );

       on-error;

       endmon;

       *INLR = *On;
       return;


       begsr compose;

         RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
         ComposeDS.TemplateProcedure = %Paddr( Template );
         RXS_StartComposeEngine( ComposeDS );

         RXS_ComposeVariable( fahrenheit : %Char(gFahrenheit) );
         RXS_ComposeSection( content );

         gXmlRequest =  RXS_GetComposeBuffer();

       endsr;


       begsr transmit;

         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.URI =
           'https://www.w3schools.com/xml/tempconvert.asmx';
         TransmitDS.HeaderSOAPAction =
           '"https://www.w3schools.com/xml/FahrenheitToCelsius"';
         TransmitDS.LogFile = '/tmp/celsius.txt';
         TransmitDS.HeaderContentType = 'text/xml; charset=utf-8';
         TransmitDS.RequestCcsid = RXS_CCSID_UTF8;
         // SSL Certificates should always be installed for security reasons,
         //   but for now ignore any missing certificates
         //   See this page for instructions on installing needed certificates:
         //   https://isupport.krengeltech.com/rxs/installing_certificate_authorities/
         // For example purposes only, turnoff SSL Verification.
         TransmitDS.SSLVerifyHost = RXS_NO;
         gXmlResponse = RXS_Transmit( gXmlRequest : TransmitDS );

       endsr;

       begsr parse;

         RootDomDS = RXS_OpenDom( gXmlResponse );

         gXPath =
           RXS_XPath('/*:Envelope/*:Body/*:FahrenheitToCelsiusResponse' +
                     '/*:FahrenheitToCelsiusResult' );

         gData = RXS_ParseDomToText( gXPath : RootDomDS );

         RXS_CloseDom( RootDomDS );

         gCelsius = %Int( gData );

       endsr;

      /end-free


     P Template        B
     D                 PI
     D  p                                  Like(RXS_TEMPLATE_PARM)

        // Template RPG source was created from the actual template
        //  STMF using the CRTRPGTPL command.
        //
        // The RPG Template source is copied from QRPGLETPL by using
        //  /copy once in the D-specs and again below.

      /COPY QRPGLETPL,CELSIUS
     P                 E
```

---

# Example 3

> RPG example using the DOM parser to process an XML document containing multiple repeating elements.

This example program demonstrates the basic structure of an RPG program utilizing the RXS DOM parser to parse an XML document with multiple repeating elements.

### Free Format EX3.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 3 - XML DOM Parsing');

/COPY QRPGLECPY,RXSCB

Dcl-Ds gRootDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds gBookDomDS LikeDS(RXS_ParseDomDS_t);
Dcl-Ds gReviewDomDS LikeDS(RXS_ParseDomDS_t);

Dcl-S gXml Like(RXS_Var64Kv_t);
Dcl-S gData Like(RXS_Var64Kv_t);
Dcl-S gXPath Like(RXS_Var8Kv_t);
Dcl-S x Uns(10);
Dcl-S y Uns(10);

monitor;
  gXml = '<?xml version="1.0" encoding="UTF-8"?>'
       + '<bookstore xmlns:test="testnamespace">'
       + '<book category="children">'
       + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
       + '<author>E. B. White</author>'
       + '<year>1952</year>'
       + '<price>5.99</price>'
       + '<review><text>What a great book!</text></review>'
       + '<review><text>Highly recommended.</text></review>'
       + '</book>'
       + '<book category="cooking">'
       + '<title lang="en">Everyday Italian</title>'
       + '<author>Giada De Laurentiis</author>'
       + '<year>2005</year>'
       + '<price>30.00</price>'
       + '</book>'
       + '<book category="science fiction">'
       + '<title lang="en">Gideon the Ninth</title>'
       + '<author>Tamsyn Muir</author>'
       + '<year>2019</year>'
       + '<price>19.99</price>'
       + '</book>'
       + '</bookstore>';

  gRootDomDS = RXS_OpenDom( gXml );

  gXPath = RXS_XPath( '/*:bookstore/*:book' );
  gBookDomDS = RXS_ParseDomToDom( gXPath : gRootDomDS );

  for x = 1 to gBookDomDS.NodeCount;
    gXPath = RXS_XPath( '*:book[%u]/*:title' : x );
    gData = RXS_ParseDomToText( gXPath : gBookDomDS );
    RXS_JobLog( 'Title: %s' : gData );

    gXPath = RXS_XPath( '*:book[%u]/*:review' : x );
    gReviewDomDS = RXS_ParseDomToDom( gXPath : gBookDomDS );

    for y = 1 to gReviewDomDS.NodeCount;
      gXPath = RXS_XPath( '*:review[%u]/*:text' : y );
      gData = RXS_ParseDomToText( gXPath : gReviewDomDS );
      RXS_JobLog( 'Review: %s' : gData );
    endfor;
  endfor;

  RXS_CloseDom( gRootDomDS );

on-error;

endmon;

*INLR = *On;
return;
```

### Fixed Format FX_EX3.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 3 - XML DOM Parsing')

      /COPY QRPGLECPY,RXSCB

     D gRootDomDS      DS                  LikeDS(RXS_ParseDomDS_t)
     D gBookDomDS      DS                  LikeDS(RXS_ParseDomDS_t)
     D gReviewDomDS    DS                  LikeDS(RXS_ParseDomDS_t)

     D gXml            S                   Like(RXS_Var64Kv_t)
     D gData           S                   Like(RXS_Var64Kv_t)
     D gXPath          S                   Like(RXS_Var8Kv_t)
     D x               S             10U 0
     D y               S             10U 0

      /free

       monitor;

         gXml = '<?xml version="1.0" encoding="UTF-8"?>'
              + '<bookstore xmlns:test="testnamespace">'
              + '<book category="children">'
              + '<title lang="en"><![CDATA[Charlotte''s Web]]></title>'
              + '<author>E. B. White</author>'
              + '<year>1952</year>'
              + '<price>5.99</price>'
              + '<review><text>What a great book!</text></review>'
              + '<review><text>Highly recommended.</text></review>'
              + '</book>'
              + '<book category="cooking">'
              + '<title lang="en">Everyday Italian</title>'
              + '<author>Giada De Laurentiis</author>'
              + '<year>2005</year>'
              + '<price>30.00</price>'
              + '</book>'
              + '<book category="science fiction">'
              + '<title lang="en">Gideon the Ninth</title>'
              + '<author>Tamsyn Muir</author>'
              + '<year>2019</year>'
              + '<price>19.99</price>'
              + '</book>'
              + '</bookstore>';

         gRootDomDS = RXS_OpenDom(  gXml );

         gXPath = RXS_XPath( '/*:bookstore/*:book' );
         gBookDomDS = RXS_ParseDomToDom( gXPath : gRootDomDS );

         for x = 1 to gBookDomDS.NodeCount;

           gXPath = RXS_XPath( '*:book[%u]/*:title' : x );
           gData = RXS_ParseDomToText( gXPath : gBookDomDS );
           RXS_JobLog( 'Title: %s' : gData );

           gXPath = RXS_XPath( '*:book[%u]/*:review' : x );
           gReviewDomDS = RXS_ParseDomToDom( gXPath : gBookDomDS );

           for y = 1 to gReviewDomDS.NodeCount;

             gXPath = RXS_XPath( '*:review[%u]/*:text' : y );
             gData = RXS_ParseDomToText( gXPath : gReviewDomDS );
             RXS_JobLog( 'Review: %s' : gData );

           endfor;

         endfor;

         RXS_CloseDom( gRootDomDS );

       on-error;

       endmon;

       *INLR = *On;
       return;

      /end-free
```

---

# Example 4

> RPG example composing a JSON document with repeating array elements using the JSON composition subprocedures.

This example program demonstrates the basic structure of an RPG program utilizing RXS to compose a JSON string with repeating elements.

### Free Format EX4.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 4 - JSON Compose');

/COPY QRPGLECPY,RXSCB

Dcl-Ds PutStmfDS LikeDS(RXS_PutStmfDS_t);

Dcl-Ds CreateJsonDS LikeDS(RXS_CreateJsonDS_t);
Dcl-Ds RootDS LikeDS(RXS_JsonStructureDS_t);
Dcl-Ds UsersArrayDS LikeDS(RXS_JsonStructureDS_t);
Dcl-Ds UserObjectDS LikeDS(RXS_JsonStructureDS_t);
Dcl-Ds JsonArrayDS LikeDS(RXS_JsonStructureDS_t);

Dcl-S i Uns(3);
Dcl-S JSON Like(RXS_Var64Kv_t);
Dcl-S dateVal Char(10);

monitor;
  // Use RXS_ResetDS to ensure that the CreateJsonDS data structures is
  //  properly initialized. We don't have to use this on the various
  //  RXS_JsonStructureDS_t structures because the other RXS subprocedures
  //  will handle those for us.
  RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );

  // Create root JSON Object
  CreateJsonDS.Prettify = RXS_JSON_TRUE;
  CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
  RootDS = RXS_CreateJson( CreateJsonDS );

  // Create JSON Array named users & attach to RootDS
  UsersArrayDS = RXS_ComposeJsonArray( 'users' : RootDS );

  // Loop through and add multiple objects to a JSON Array named "users".
  //  Typically you'd want to use a normal RPG database read loop here
  //  instead of a 'for' loop.
  for i = 1 to 3;
    // Create object for a customer, attach to array
    UserObjectDS = RXS_ComposeJsonObject( *Omit : UsersArrayDS );

    // Add various character fields & data to the users object we just created.
    RXS_ComposeJsonString( 'firstName' : 'First Name' : UserObjectDS );
    RXS_ComposeJsonString( 'lastName' : 'Last Name' : UserObjectDS );
    RXS_ComposeJsonString( 'email' : 'Email Address' : UserObjectDS );
    RXS_ComposeJsonString( 'username' : 'Username' : UserObjectDS );
    RXS_ComposeJsonString( 'password' : 'Password' : UserObjectDS );
  endfor;

  // The next few parts of this JSON structure are a series of arrays.
  //  We could define D spec data structure for each if we wanted to,
  //  but it's not required. We still need to ensure we're attaching
  //  them to our root object RootDS though.

  JsonArrayDS = RXS_ComposeJsonArray( 'requiredCourseIds' : RootDS );

  // Again, you'd probably be reading one or more child records out of
  //  a physical file here with an RPG read loop instead of a 'for' loop.
  for i = 1 to 10;
    // Note that the key difference between a JSON Object and a
    //  JSON Array is that a JSON Object can contain one or more
    //  name/value pairs (and the values can be of any type - a child
    //  JSON Object, a JSON Array, or just a plain string/int/etc value)

    // A JSON Array can hold one or more values ONLY. The values a JSON
    //  Array holds must all be of the same type (e.g. all JSON Object,
    //  all plain string/int/etc values, or even another JSON Array),
    //  and cannot have a name associated with them.

    // The easiest way to think about these two is that a JSON Object
    //  is like an RPG data structure, whereas a JSON Array is like
    //  an RPG data structure or normal field but with the Dim keyword
    //  used.

    // Because we can't name the values we're putting in our array, the
    //  second parm for RXS_ComposeJsonNumber() must be *OMIT.

    // We're using RXS_ComposeJsonNumber() to ensure that in the JSON
    //  the values we're passing show up as numeric (e.g. not wrapped
    //  in double quotes)

    // To handle large numbers effectively, RXS_ComposeJsonNumber()
    //  actually accepts a character representation of a number as
    //  opposed to an RPG numeric field type.
    RXS_ComposeJsonNumber( *Omit : %Char(i) : JsonArrayDS );
  endfor;

  // We're re-using the JsonArrayDS structure for the next few arrays
  //  as they are very simple arrays but the whole process is otherwise
  //  the same as for requiredCourseIds[]
  JsonArrayDS = RXS_ComposeJsonArray( 'courseIds' : RootDS );
  for i = 1 to 10;
    RXS_ComposeJsonNumber( *Omit : %Char(i * 2) : JsonArrayDS );
  endfor;

  JsonArrayDS = RXS_ComposeJsonArray( 'seriesIds' : RootDS );
  for i = 1 to 10;
    RXS_ComposeJsonNumber( *Omit : %Char(i * 3) : JsonArrayDS );
  endfor;

  JsonArrayDS = RXS_ComposeJsonArray( 'groupIds' : RootDS );
  for i = 1 to 10;
    RXS_ComposeJsonNumber( *Omit : %Char(i * 4) : JsonArrayDS );
  endfor;

  // Next, we need to add a date named datestamp to our RootDS.
  //  JSON doesn't have a 'date' data type, so we add this as a
  //  string and do whatever formatting the API we're calling requires.
  // In this case we're using *ISO YYYY-MM-DD date format
  dateVal = %Char( %Date() : *ISO );
  RXS_ComposeJsonString( 'datestamp' : dateVal : RootDS );

  // Next we're going to add JSON boolean and null values
  RXS_ComposeJsonBoolean( 'processed' : RXS_JSON_TRUE : RootDS );
  RXS_ComposeJsonNull( 'referenceId' : RootDS );

  // Now that we've added all of the data fields to our JSON object,
  //  let's generate our JSON string
  JSON = RXS_GetJsonString( CreateJsonDS );

  // Dump JSON to file
  RXS_ResetDS( PutStmfDS : RXS_DS_TYPE_PUTSTMF );
  PutStmfDS.Stmf = '/tmp/json_excompose1.txt';
  RXS_PutStmf( JSON : PutStmfDS );

on-error;
  RXS_DestroyJson( CreateJsonDS );
endmon;

// Always call RXS_DestroyJson() to free up the memory we used.
// This must be called AFTER we retrieve our string
//  with RXS_GetJsonString(), or there will be nothing to retrieve!
// It is enough to just call RXS_DestroyJson() on the CreateJsonDS data
//  structure - it will free the memory used by all of the child
//  data structures that were linked to it as well.
RXS_DestroyJson( CreateJsonDS );

*INLR = *ON;
return;
```

### Fixed Format FX_EX4.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 4 - JSON Compose')

      /COPY QRPGLECPY,RXSCB

     D PutStmfDS       DS                  LikeDS(RXS_PutStmfDS_t)
     D CreateJsonDS    DS                  LikeDS(RXS_CreateJsonDS_t)
     D RootDS          DS                  LikeDS(RXS_JsonStructureDS_t)
     D UsersArrayDS    DS                  LikeDS(RXS_JsonStructureDS_t)
     D UserObjectDS    DS                  LikeDS(RXS_JsonStructureDS_t)
     D JsonArrayDS     DS                  LikeDS(RXS_JsonStructureDS_t)

     D i               S              3U 0
     D JSON            S                   Like(RXS_Var64Kv_t)
     D dateVal         S             10A
      /FREE
       monitor;

         // Use RXS_ResetDS to ensure that the CreateJsonDS data structures is
         //  properly initialized. We don't have to use this on the various
         //  RXS_JsonStructureDS_t structures because the other RXS
         //  subprocedures will handle those for us.
         RXS_ResetDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );

         // Create root JSON Object
         CreateJsonDS.Prettify = RXS_JSON_TRUE;
         CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_OBJECT;
         RootDS = RXS_CreateJson( CreateJsonDS );

         // Create JSON Array named users & attach to RootDS
         UsersArrayDS = RXS_ComposeJsonArray( 'users' : RootDS );

         // Loop through and add multiple objects to the JSON Array named
         //  "users". Typically you'd want to use a normal RPG database
         //  read loop here instead of a 'for' loop.
         for i = 1 to 3;

           // Create object for a customer, attach to array
           UserObjectDS = RXS_ComposeJsonObject( *Omit : UsersArrayDS );

           // Add various character fields & data to the users object we just
           //  created.
           RXS_ComposeJsonString( 'firstName' : 'First Name' : UserObjectDS );
           RXS_ComposeJsonString( 'lastName' : 'Last Name' : UserObjectDS );
           RXS_ComposeJsonString( 'email' : 'Email Address' : UserObjectDS );
           RXS_ComposeJsonString( 'username' : 'Username' : UserObjectDS );
           RXS_ComposeJsonString( 'password' : 'Password' : UserObjectDS );
         endfor;

         // The next few parts of this JSON structure are a series of arrays.
         //  We could define D spec data structure for each if we wanted to,
         //  but it's not required. We still need to ensure we're attaching
         //  them to our root object RootDS though.

         JsonArrayDS = RXS_ComposeJsonArray( 'requiredCourseIds' : RootDS );

         // Again, you'd probably be reading one or more child records out of
         //  a physical file here with an RPG read loop instead of a
         //  'for' loop.
         for i = 1 to 10;
           // Note that the key difference between a JSON Object and a
           //  JSON Array is that a JSON Object can contain one or more
           //  name/value pairs (and the values can be of any type - a child
           //  JSON Object, a JSON Array, or just a plain string/int/etc value)

           // A JSON Array can hold one or more values ONLY. The values a JSON
           //  Array holds must all be of the same type (e.g. all JSON Object,
           //  all plain string/int/etc values, or even another JSON Array),
           //  and cannot have a name associated with them.

           // The easiest way to think about these two is that a JSON Object
           //  is like an RPG data structure, whereas a JSON Array is like
           //  an RPG data structure or normal field but with the Dim keyword
           //  used.

           // Because we can't name the values we're putting in our array, the
           //  second parm for RXS_ComposeJsonNumber() must be *OMIT.

           // We're using RXS_ComposeJsonNumber() to ensure that in the JSON
           //  the values we're passing show up as numeric (e.g. not wrapped
           //  in double quotes)

           // To handle large numbers effectively, RXS_ComposeJsonNumber()
           //  actually accepts a character representation of a number as
           //  opposed to an RPG numeric field type.
           RXS_ComposeJsonNumber( *Omit : %Char(i) : JsonArrayDS );
         endfor;

         // We're re-using the GenericArrayDS structure for the next few arrays
         //  as they are very simple arrays but the whole process is otherwise
         //  the same as for requiredCourseIds[]
         JsonArrayDS = RXS_ComposeJsonArray( 'courseIds' : RootDS );
         for i = 1 to 10;
           RXS_ComposeJsonNumber( *Omit : %Char(i * 2) : JsonArrayDS );
         endfor;

         JsonArrayDS = RXS_ComposeJsonArray( 'seriesIds' : RootDS );
         for i = 1 to 10;
           RXS_ComposeJsonNumber( *Omit : %Char(i * 3) : JsonArrayDS );
         endfor;

         JsonArrayDS = RXS_ComposeJsonArray( 'groupIds' : RootDS );
         for i = 1 to 10;
           RXS_ComposeJsonNumber( *Omit : %Char(i * 4) : JsonArrayDS );
         endfor;

         // Next, we need to add a date named datestamp to our RootDS.
         //  JSON doesn't have a 'date' data type, so we add this as a
         //  string and do whatever formatting the API we're calling requires.
         // In this case we're using a YYYY-MM-DD date format
         dateVal = %Char( %Date() : *ISO );
         RXS_ComposeJsonString( 'datestamp' : dateVal : RootDS );

         // Next we're going to add JSON boolean and null values
         RXS_ComposeJsonBoolean( 'processed' : RXS_JSON_TRUE : RootDS );
         RXS_ComposeJsonNull( 'referenceId' : RootDS );

         // Now that we've added all of the data fields to our JSON object,
         //  let's generate our JSON string
         JSON = RXS_GetJsonString( CreateJsonDS );

         // Dump JSON to file
         RXS_ResetDS( PutStmfDS : RXS_DS_TYPE_PUTSTMF );
         PutStmfDS.Stmf = '/tmp/json_excompose1.txt';
         RXS_PutStmf( JSON : PutStmfDS );

       on-error;
         RXS_DestroyJson( CreateJsonDS );
       endmon;

       // Always call RXS_DestroyJson() to free up the memory we used.
       // This must be called AFTER we retrieve our string
       //  with RXS_GetJsonString(), or there will be nothing to retrieve!
       // It is enough to just call RXS_DestroyJson() on the CreateJsonDS data
       //  structure - it will free the memory used by all of the child
       //  data structures that were linked to it as well.
       RXS_DestroyJson( CreateJsonDS );

       *INLR = *ON;
       return;
      /END-FREE
```

---

# Example 5

> RPG example parsing a JSON response using an event handler subprocedure.

This example program demonstrates the basic structure of an RPG program utilizing RXS to parse JSON data.

### Free Format EX5.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 5 - JSON Parse');

/COPY QRPGLECPY,RXSCB

Dcl-S JSON Like(RXS_Var64Kv_t);

Dcl-Ds ParseJsonDS LikeDS(RXS_ParseJsonDS_t);

JSON = '{ "glossary": { "title": "example glossary", "GlossDiv": { '
     + '"title": "S", "GlossList": [ { "ID": "SGML", "SortAs": "SGML", '
     + '"GlossTerm": "Standard Generalized Markup Language", "Acronym": '
     + '"SGML", "Abbrev": "ISO 8879:1986", "GlossDef": "A meta-markup '
     + 'language, used to create markup languages such as DocBook.", '
     + '"GlossSeeAlso": ["GML", "XML", "markup", "test"] } ] } } }';

monitor;
  // Use RXS_ResetDS to ensure that RXS templated data structures are
  //  properly configured before use
  RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );

  // The JSON parsing in RXS functions via an "event-based" method.
  //  Basically, you will create a "handler" subprocedure that will
  //  be called by RXS_ParseJson once for each "event" detected in
  //  the JSON being parsed. An event is one of the following:
  //  - beginning of an JSON Array (RXS_JSON_ARRAY_BEGIN)
  //  - end of an JSON Array (RXS_JSON_ARRAY_END)
  //  - beginning of an JSON Object (RXS_JSON_OBJECT_BEGIN)
  //  - end of an JSON Object (RXS_JSON_OBJECT_END)
  //  - a JSON string value (RXS_JSON_STRING)
  //  - a JSON integer value (RXS_JSON_INTEGER)
  //  - a JSON double (float) value (RXS_JSON_DOUBLE)
  //  - a JSON boolean (indicator) value (RXS_JSON_BOOLEAN)
  //  - a JSON null value (RXS_JSON_NULL)
  // By default for convenience any numeric, boolean, or null values
  //  will be converted into character strings and passed as RXS_JSON_STRING
  //  events. This can be controlled with a subfield within ParseJsonDS.
  // Inside the "handler" subprocedure (named JSONHandler here), you
  //  will receive a number of parameters that describe the data
  //  associated with the event detected. JSONHandler() explains this
  //  in more detail
  ParseJsonDS.Handler = %Paddr( JSONHandler );

  RXS_ParseJson( JSON : ParseJsonDS );
on-error;
endmon;

*INLR = *ON;
return;


Dcl-Proc JSONHandler;
  Dcl-Pi *N Ind;
    pType Int(5) Const;
    pPath Like(RXS_Var64Kv_t) Const;
    pIndex Uns(10) Const;
    pData Pointer Const;
    pDataLen Uns(10) Const;
  End-Pi;

  Dcl-S ParsedData Like(RXS_Var1Kv_t);

  // JSONHandler must always be specified with the exact same prototype
  //  - the same number of parameters, the same number of field types, in
  //  the same order, etc. The parameters are:
  //
  // - pType = the event type, e.g. RXS_JSON_STRING, RXS_JSON_ARRAY_BEGIN,
  //    etc.
  // - pPath = XPath like syntax that indicates where in the JSON
  //    structure being parsed we are located. You can "drill down" with
  //    slashes (e.g. /object1/field1 corresponds to a 'field1' inside
  //    a parent object named 'object1', and you can indicate when a JSON
  //    JSON Array is present via [*] - further explanation below.
  // -pIndex = If the JSON object/event we're handling is a child of a
  //    JSON Array, this will indicate the 'index", e.g. the position of
  //    this object within the array, starting from a value of 1. This
  //    parameter is not relevant for objects/events not inside an array
  // -pData = Pointer to the actual data returned. This will only be set
  //    for RXS_JSON_STRING, RXS_JSON_INTEGER, RXS_JSON_DOUBLE, and
  //    RXS_JSON_BOOLEAN. It is technically also set for RXS_JSON_NULL,
  //    but will be an RPG *Null value. For RXS_JSON_OBJECT,
  //    RXS_JSON_OBJECT_END, RXS_JSON_ARRAY, and RXS_JSON_ARRAY_END,
  //    this will also be *Null as there isn't any data associated with
  //    these events.
  // -pDataLen = Length of the object. Typically this is used as the
  //    length of pData as a call to RXS_STR as seen below, but pDataLen
  //    is also set to either the total length of a JSON Array or the
  //    number of "first tier" fields in a JSON Object

  select;

    // For JSON parsing with RXS, a [*] being part of the path indicates
    //  that an array is involved. If you need to know the specific
    //  position in the array you can look at the pIndex value. Note that
    //  for nested arrays pIndex will show the position of the deepest
    //  JSON Array in the path.

    when pPath = '/'
     and pType = RXS_JSON_OBJECT;
      RXS_JobLog( 'Root object (length %s)' : %Char(pDataLen) );

    when pPath = '/glossary'
     and pType = RXS_JSON_OBJECT;
      RXS_JobLog( 'glossary object (length %s)' : %Char(pDataLen) );

    when pPath = '/glossary/title'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'glossary title: %s' : ParsedData );

    when pPath = '/glossary/GlossDiv/title'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossDiv title: %s' : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]'
     and pType = RXS_JSON_ARRAY;
      RXS_JobLog( 'Begin GlossList Array (length %s)'
                : %Char(pDataLen) );

    when pPath = '/glossary/GlossDiv/GlossList[*]/ID'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s ID: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/SortAs'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s SortAs: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/GlossTerm'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s GlossTerm: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/Acronym'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s Acronym: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/Abbrev'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s Abbrev: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/GlossDef'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossList Array#%s GlossDef: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
     and pType = RXS_JSON_ARRAY;
      RXS_JobLog( 'Begin GlossSeeAlso Array (length %s)'
                : %Char(pDataLen) );

    when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
     and pType = RXS_JSON_STRING;
      ParsedData = RXS_STR( pData : pDataLen );
      RXS_JobLog( 'GlossSeeAlso Array#%s value: %s'
                : %Char(pIndex) : ParsedData );

    when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
     and pType = RXS_JSON_ARRAY_END;
      RXS_JobLog( 'End GlossSeeAlso Array (length %s)'
                : %Char(pDataLen) );

    when pPath = '/glossary/GlossDiv/GlossList[*]'
     and pType = RXS_JSON_ARRAY_END;
      RXS_JobLog( 'End GlossList Array (length %s)'
                : %Char(pDataLen) );

  endsl;

  // If you want to stop parsing early for whatever reason, return
  // RXS_JSON_STOP_PARSING

  return RXS_JSON_CONTINUE_PARSING;
End-Proc;
```

### Fixed Format FX_EX5.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 5 - JSON Parse')

      /COPY QRPGLECPY,RXSCBaaaq

     D JSONHandler     PR              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const

     D JSON            S                   Like(RXS_Var64Kv_t)

     D ParseJsonDS     DS                  LikeDS(RXS_ParseJsonDS_t)

      /FREE

       JSON = '{ "glossary": { "title":'
            + ' "example glossary", "GlossDiv": { "title": "S", "'
            + 'GlossList": [ { "ID": "SGML", "SortAs": "SGML", "'
            + 'GlossTerm": "Standard Generalized Markup Language", "Ac'
            + 'ronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDe'
            + 'f": "A meta-markup language, used to create markup langua'
            + 'ges such as DocBook.", "GlossSeeAlso": ["GML", "XML"'
            + ', "markup", "test"] } ] } } }';

       monitor;
         // Use RXS_ResetDS to ensure that RXS templated data structures are
         //  properly configured before use
         RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );

         // The JSON parsing in RXS functions via an "event-based" method.
         //  Basically, you will create a "handler" subprocedure that will
         //  be called by RXS_ParseJson once for each "event" detected in
         //  the JSON being parsed. An event is one of the following:
         //  - beginning of an JSON Array (RXS_JSON_ARRAY_BEGIN)
         //  - end of an JSON Array (RXS_JSON_ARRAY_END)
         //  - beginning of an JSON Object (RXS_JSON_OBJECT_BEGIN)
         //  - end of an JSON Object (RXS_JSON_OBJECT_END)
         //  - a JSON string value (RXS_JSON_STRING)
         //  - a JSON integer value (RXS_JSON_INTEGER)
         //  - a JSON double (float) value (RXS_JSON_DOUBLE)
         //  - a JSON boolean (indicator) value (RXS_JSON_BOOLEAN)
         //  - a JSON null value (RXS_JSON_NULL)
         // By default for convenience any numeric, boolean, or null values
         //  will be converted into character strings and passed as
         //  RXS_JSON_STRING events. This can be controlled with a subfield
         //  within ParseJsonDS.
         // Inside the "handler" subprocedure (named JSONHandler here), you
         //  will receive a number of parameters that describe the data
         //  associated with the event detected. JSONHandler() explains this
         //  in more detail
         ParseJsonDS.Handler = %Paddr( JSONHandler );

         RXS_ParseJson( JSON : ParseJsonDS );
       on-error;
       endmon;

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


     P JSONHandler     B                   Export
     D                 PI              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const
      *
     D ParsedData      S                   Like(RXS_Var1Kv_t)
      /FREE
       // JSONHandler must always be specified with the exact same prototype
       //  - the same number of parameters, the same number of field types, in
       //  the same order, etc. The parameters are:
       //
       // - pType = the event type, e.g. RXS_JSON_STRING, RXS_JSON_ARRAY_BEGIN,
       //    etc.
       // - pPath = XPath like syntax that indicates where in the JSON
       //    structure being parsed we are located. You can "drill down" with
       //    slashes (e.g. /object1/field1 corresponds to a 'field1' inside
       //    a parent object named 'object1', and you can indicate when a JSON
       //    JSON Array is present via [*] - further explanation below.
       // -pIndex = If the JSON object/event we're handling is a child of a
       //    JSON Array, this will indicate the 'index", e.g. the position of
       //    this object within the array, starting from a value of 1. This
       //    parameter is not relevant for objects/events not inside an array
       // -pData = Pointer to the actual data returned. This will only be set
       //    for RXS_JSON_STRING, RXS_JSON_INTEGER, RXS_JSON_DOUBLE, and
       //    RXS_JSON_BOOLEAN. It is technically also set for RXS_JSON_NULL,
       //    but will be an RPG *Null value. For RXS_JSON_OBJECT,
       //    RXS_JSON_OBJECT_END, RXS_JSON_ARRAY, and RXS_JSON_ARRAY_END,
       //    this will also be *Null as there isn't any data associated with
       //    these events.
       // -pDataLen = Length of the object. Typically this is used as the
       //    length of pData as a call to RXS_STR as seen below, but pDataLen
       //    is also set to either the total length of a JSON Array or the
       //    number of "first tier" fields in a JSON Object

       select;

         // For JSON parsing with RXS, a [*] being part of the path indicates
         //  that an array is involved. If you need to know the specific
         //  position in the array you can look at the pIndex value. Note that
         //  for nested arrays pIndex will show the position of the deepest
         //  JSON Array in the path.

         when pPath = '/'
          and pType = RXS_JSON_OBJECT;
            RXS_JobLog( 'Root object (length %s)' : %Char(pDataLen) );

         when pPath = '/glossary'
          and pType = RXS_JSON_OBJECT;
            RXS_JobLog( 'glossary object (length %s)' : %Char(pDataLen) );

         when pPath = '/glossary/title'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'glossary title: %s' : ParsedData );

         when pPath = '/glossary/GlossDiv/title'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossDiv title: %s' : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]'
          and pType = RXS_JSON_ARRAY;
            RXS_JobLog( 'Begin GlossList Array (length %s)'
                      : %Char(pDataLen) );

         when pPath = '/glossary/GlossDiv/GlossList[*]/ID'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s ID: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/SortAs'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s SortAs: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/GlossTerm'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s GlossTerm: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/Acronym'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s Acronym: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/Abbrev'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s Abbrev: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/GlossDef'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossList Array#%s GlossDef: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
          and pType = RXS_JSON_ARRAY;
            RXS_JobLog( 'Begin GlossSeeAlso Array (length %s)'
                      : %Char(pDataLen) );

         when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
          and pType = RXS_JSON_STRING;
            ParsedData = RXS_STR( pData : pDataLen );
            RXS_JobLog( 'GlossSeeAlso Array#%s value: %s'
                      : %Char(pIndex) : ParsedData );

         when pPath = '/glossary/GlossDiv/GlossList[*]/GlossSeeAlso[*]'
          and pType = RXS_JSON_ARRAY_END;
            RXS_JobLog( 'End GlossSeeAlso Array (length %s)'
                      : %Char(pDataLen) );

         when pPath = '/glossary/GlossDiv/GlossList[*]'
          and pType = RXS_JSON_ARRAY_END;
            RXS_JobLog( 'End GlossList Array (length %s)'
                      : %Char(pDataLen) );

        endsl;

        // If you want to stop parsing early for whatever reason, return
        // RXS_JSON_STOP_PARSING

        return RXS_JSON_CONTINUE_PARSING;
      /END-FREE
     P                 E
```

---

# Example 6

> RPG example validating an XML document against an XSD schema, demonstrating both passing and failing validation scenarios.

This example program demonstrates the basic structure of an RPG program utilizing RXS to validate sample XML against an XSD. Several methods are demonstrated, with both pass and fail outcomes.

### Free Format EX6.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex. 6 - XML XSD Validate');

/COPY QRPGLECPY,RXSCB

Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
Dcl-Ds XsdValidateDS LikeDS(RXS_XsdValidationDS_t);
Dcl-Ds PutStmfDS LikeDS(RXS_PutStmfDS_t);
Dcl-Ds DeleteStmfDS LikeDS(RXS_DeleteStmfDS_t);

Dcl-S request Like(RXS_Var64Kv_t);
Dcl-S goodXml Like(RXS_Var64Kv_t);
Dcl-S badXml Like(RXS_Var64Kv_t);
Dcl-S xsd Like(RXS_Var64Kv_t);

exsr Setup;

// Test validation against URI (Expected Success)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdLocation
  = 'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
if RXS_Validate( goodXml : XsdValidateDS );
  RXS_JobLog( 'XML 1 Validated successfully');
else;
  RXS_JobLog( 'XML 1 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

// Test validation against XSD in a field (Expected Success)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdPointer = %Addr( xsd : *DATA );
XsdValidateDS.XsdLength = %Len( xsd );
if RXS_Validate( goodXml : XsdValidateDS );
  RXS_JobLog( 'XML 2 Validated successfully');
else;
  RXS_JobLog( 'XML 2 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

// Test validation against XSD in a file (Expected Success)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdLocation = PutStmfDS.Stmf;
if RXS_Validate( goodXml : XsdValidateDS );
  RXS_JobLog( 'XML 3 Validated successfully');
else;
  RXS_JobLog( 'XML 3 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

// Test validation against URI (Expected Failure)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdLocation
  = 'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
if RXS_Validate( badXml : XsdValidateDS );
  RXS_JobLog( 'XML 4 Validated successfully');
else;
  RXS_JobLog( 'XML 4 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

// Test validation against XSD in a field (Expected Failure)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdPointer = %Addr( xsd : *DATA );
XsdValidateDS.XsdLength = %Len( xsd );
if RXS_Validate( badXml : XsdValidateDS );
  RXS_JobLog( 'XML 5 Validated successfully');
else;
  RXS_JobLog( 'XML 5 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

// Test validation against XSD in a file (Expected Failure)
RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
XsdValidateDS.XsdLocation = PutStmfDS.Stmf;
if RXS_Validate( badXml : XsdValidateDS );
  RXS_JobLog( 'XML 6 Validated successfully');
else;
  RXS_JobLog( 'XML 6 Validation failed');
  RXS_JobLog( 'Validation Error #%s: %s'
            : %Char( XsdValidateDS.ValidationErrorCode )
            : XsdValidateDS.ValidationErrorText );
endif;

exsr Cleanup;

*INLR = *On;
return;

begsr Setup;
  // Get good XML to test validation
  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.Uri = 'http://files.rpg-xml.com/example/xsdvalidate/success.xml';
  TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
  request = *Blanks;
  goodXml = RXS_Transmit( request : TransmitDS );

  // Get bad XML to test validation
  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.Uri = 'http://files.rpg-xml.com/example/xsdvalidate/failure.xml';
  TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
  request = *Blanks;
  badXml = RXS_Transmit( request : TransmitDS );

  // Get XSD from remote URI
  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.Uri = 'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
  TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
  request = *Blanks;
  xsd = RXS_Transmit( request : TransmitDS );

  // Put the XSD in an IFS file
  RXS_ResetDS( PutStmfDS : RXS_DS_TYPE_PUTSTMF );
  PutStmfDS.Stmf = '/tmp/rxs_xsd_validate.xsd';
  PutStmfDS.StmfCcsid = RXS_CCSID_ISO88591;
  RXS_PutStmf( xsd : PutStmfDS );
endsr;

begsr Cleanup;
  RXS_ResetDS( DeleteStmfDS : RXS_DS_TYPE_DELETESTMF );
  DeleteStmfDS.Stmf = PutStmfDS.Stmf;
  RXS_ProcessStmf( DeleteStmfDS );
endsr;
```

### Fixed Format FX_EX6.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex. 6 - XML XSD Validate')

      /COPY QRPGLECPY,RXSCB

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D XsdValidateDS   DS                  LikeDS(RXS_XsdValidationDS_t)
     D PutStmfDS       DS                  LikeDS(RXS_PutStmfDS_t)
     D DeleteStmfDS    DS                  LikeDS(RXS_DeleteStmfDS_t)

     D request         S                   Like(RXS_Var64Kv_t)
     D goodXml         S                   Like(RXS_Var64Kv_t)
     D badXml          S                   Like(RXS_Var64Kv_t)
     D xsd             S                   Like(RXS_Var64Kv_t)

      /FREE

       exsr Setup;

       // Test validation against URI (Expected Success)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdLocation =
         'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
       if RXS_Validate( goodXml : XsdValidateDS );
         RXS_JobLog( 'XML 1 Validated successfully');
       else;
         RXS_JobLog( 'XML 1 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       // Test validation against XSD in a field (Expected Success)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdPointer = %Addr( xsd : *DATA );
       XsdValidateDS.XsdLength = %Len( xsd );
       if RXS_Validate( goodXml : XsdValidateDS );
         RXS_JobLog( 'XML 2 Validated successfully');
       else;
         RXS_JobLog( 'XML 2 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       // Test validation against XSD in a file (Expected Success)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdLocation = PutStmfDS.Stmf;
       if RXS_Validate( goodXml : XsdValidateDS );
         RXS_JobLog( 'XML 3 Validated successfully');
       else;
         RXS_JobLog( 'XML 3 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       // Test validation against URI (Expected Failure)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdLocation =
         'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
       if RXS_Validate( badXml : XsdValidateDS );
         RXS_JobLog( 'XML 4 Validated successfully');
       else;
         RXS_JobLog( 'XML 4 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       // Test validation against XSD in a field (Expected Failure)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdPointer = %Addr( xsd : *DATA );
       XsdValidateDS.XsdLength = %Len( xsd );
       if RXS_Validate( badXml : XsdValidateDS );
         RXS_JobLog( 'XML 5 Validated successfully');
       else;
         RXS_JobLog( 'XML 5 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       // Test validation against XSD in a file (Expected Failure)
       RXS_ResetDS( XsdValidateDS : RXS_DS_TYPE_XSDVALIDATION );
       XsdValidateDS.XsdLocation = PutStmfDS.Stmf;
       if RXS_Validate( badXml : XsdValidateDS );
         RXS_JobLog( 'XML 6 Validated successfully');
       else;
         RXS_JobLog( 'XML 6 Validation failed');
         RXS_JobLog( 'Validation Error #%s: %s'
                   : %Char( XsdValidateDS.ValidationErrorCode )
                   : XsdValidateDS.ValidationErrorText );
       endif;

       exsr Cleanup;

       *INLR = *On;
       return;


       begsr Setup;
         // Get good XML to test validation
         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.Uri =
           'http://files.rpg-xml.com/example/xsdvalidate/success.xml';
         TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
         request = *Blanks;
         goodXml = RXS_Transmit( request : TransmitDS );

         // Get bad XML to test validation
         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.Uri =
           'http://files.rpg-xml.com/example/xsdvalidate/failure.xml';
         TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
         request = *Blanks;
         badXml = RXS_Transmit( request : TransmitDS );

         // Get XSD from remote URI
         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.Uri =
           'http://files.rpg-xml.com/example/xsdvalidate/validate.xsd';
         TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
         request = *Blanks;
         xsd = RXS_Transmit( request : TransmitDS );

         // Put the XSD in an IFS file
         RXS_ResetDS( PutStmfDS : RXS_DS_TYPE_PUTSTMF );
         PutStmfDS.Stmf = '/tmp/rxs_xsd_validate.xsd';
         PutStmfDS.StmfCcsid = RXS_CCSID_ISO88591;
         RXS_PutStmf( xsd : PutStmfDS );
       endsr;

       begsr Cleanup;
         RXS_ResetDS( DeleteStmfDS : RXS_DS_TYPE_DELETESTMF );
         DeleteStmfDS.Stmf = PutStmfDS.Stmf;
         RXS_ProcessStmf( DeleteStmfDS );
       endsr;

      /END-FREE
```

---

# RESTXML

> RPG example consuming a REST XML web service via HTTP GET and parsing the response with the event-based XML parser.

This example program demonstrates the basic structure of an RPG program utilizing RPG API Express to consume a REST based XML web service via HTTP GET, and parse the response XML with event-based parsing.

### Free Format RESTXML.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO)
  Text('Ex - RESTXML');

/COPY QRPGLECPY,RXSCB

Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
Dcl-Ds ParseDS LikeDS(RXS_ParseDS_t);
Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

Dcl-S XML Like(RXS_Var64Kv_t);
Dcl-S gNameList Like(RXS_Var64Kv_t);
Dcl-S gCount Int(10);

// Please note that the REST web service being consumed by this example program
//   is not 100% available due to metering being performed on the
//   "demo" account. Usually you can make multiple attempts over time and
//   get a proper response eventually.

monitor;

  // Transmit

  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.URI = 'http://api.geonames.org/cities'
                 + '?north=44.1&south=-9.9&east=-22.4'
                 + '&west=55.2&lang=de&username=demo';
  TransmitDS.LogFile = '/tmp/log.txt';
  TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
  XML = RXS_Transmit( *Omit : TransmitDS );

  // Parse

  reset gCount;
  reset gNameList;
  RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
  ParseDS.GlobalHandler = %Paddr( XmlHandler );
  RXS_Parse( XML : ParseDS );

  // Output Parse Results as a Thrown Message

  RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
  ErrorDS.MessageId = 'RXS9897';
  ErrorDS.MessageData = gNameList;
  ErrorDS.ThrowToCaller = RXS_YES;
  RXS_Throw( ErrorDS );

on-error;

  RXS_Joblog('Unexpected error occurred. See previous messages.');

endmon;

*INLR = *ON;
return;

Dcl-Proc XmlHandler;
  Dcl-Pi *N;
    pType Char(10) Value;
    pXPath VarChar(1024) Value;
    pData Pointer Value;
    pDataLen Int(10) Value;
  End-Pi;

  Dcl-S base VarChar(256);

  base = '/geonames/geoname';
  select;

    when pXPath = '/geonames/>';
      if gCount > 0;
        gNameList += 'No more names!';
      endif;

    when pXPath = base + '/toponymName>';
      gCount += 1;
      gNameList += %Char(gCount) + ': ';

    when pXPath = base + '/toponymName/>';
      gNameList += ', ';

    when pXPath = base + '/toponymName/';
      gNameList += RXS_STR( pData : pDataLen );

    // If the service is unavailable to the usage metering mentioned earlier,
    //   there will be XML data available for parsing per the below

    when pXPath = '/geonames/status@message';
      gNameList = RXS_STR( pData : pDataLen );

    when pXPath = '/geonames/status@value';
      gNameList += ' (' + RXS_STR( pData : pDataLen ) + ')';

  endsl;
End-Proc;
```

### Fixed Format RESTXML.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H TEXT('Fixed Format Ex - RESTXML')

      /COPY QRPGLECPY,RXSCB

     D XmlHandler      PR
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D ParseDS         DS                  LikeDS(RXS_ParseDS_t)
     D ErrorDS         DS                  LikeDS(RXS_CatchThrowErrorDS_t)

     D XML             S                   Like(RXS_Var64Kv_t)
     D gNameList       S                   Like(RXS_Var64Kv_t)
     D gCount          S             10I 0

      /FREE

       // Please note that the REST web service being consumed by this example program
       //   is not 100% available due to metering being performed on the
       //   "demo" account.  Usually you can make multiple attempts over time and
       //   get a proper response eventually.

       monitor;

         // Transmit

         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.URI = 'http://api.geonames.org/cities'
                        + '?north=44.1&south=-9.9&east=-22.4'
                        + '&west=55.2&lang=de&username=demo';
         TransmitDS.LogFile = '/tmp/log.txt';
         TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
         XML = RXS_Transmit( *Omit : TransmitDS );

         // Parse

         reset gCount;
         reset gNameList;
         RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
         ParseDS.GlobalHandler = %Paddr( XmlHandler );
         RXS_Parse( XML : ParseDS );

         // Output Parse Results as a Thrown Message

         RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
         ErrorDS.MessageId = 'RXS9897';
         ErrorDS.MessageData = gNameList;
         ErrorDS.ThrowToCaller = RXS_YES;
         RXS_Throw( ErrorDS );

       on-error;

         RXS_Joblog('Unexpected error occurred. See previous messages.');

       endmon;

       *INLR = *ON;
       return;

      /END-FREE

     P XmlHandler      B
     D                 PI
     D  pType                        10A   Value
     D  pXPath                     1024A   Value Varying
     D  pData                          *   Value
     D  pDataLen                     10I 0 Value

     D base            S            256A   Varying
      /FREE

       base = '/geonames/geoname';
       select;

         when pXPath = '/geonames/>';
           if gCount > 0;
             gNameList += 'No more names!';
           endif;

         when pXPath = base + '/toponymName>';
           gCount += 1;
           gNameList += %Char(gCount) + ': ';

         when pXPath = base + '/toponymName/>';
           gNameList += ', ';

         when pXPath = base + '/toponymName/';
           gNameList += RXS_STR( pData : pDataLen );

         // If the service is unavailable to the usage metering mentioned earlier,
         //   there will be XML data available for parsing per the below

         when pXPath = '/geonames/status@message';
           gNameList = RXS_STR( pData : pDataLen );

         when pXPath = '/geonames/status@value';
           gNameList += ' (' + RXS_STR( pData : pDataLen ) + ')';

       endsl;

      /END-FREE
     P                 E
```

---

# RESTJSON

> RPG example consuming a REST JSON web service via HTTP GET and parsing the JSON response.

This example program demonstrates the basic structure of an RPG program utilizing RPG API Express to consume a REST based JSON web service via HTTP GET, and parse the JSON response.

### Free Format RESTJSON.rpgle

```rpgle
**FREE
Ctl-Opt ActGrp(*Caller) BndDir('RXSBND') Option(*NoDebugIO) ExtBinInt(*Yes) DecEdit('0.')
  Text('Ex - RESTJSON');

/COPY QRPGLECPY,RXSCB

Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
Dcl-Ds ParseJsonDS LikeDS(RXS_ParseJsonDS_t);
Dcl-Ds ErrorDS LikeDS(RXS_CatchThrowErrorDS_t);

Dcl-S JSON Like(RXS_Var64Kv_t);

Dcl-S gNameList Like(RXS_Var64Kv_t);
Dcl-S gCount Int(10);

// Please note that the REST web service being consumed by this example program
//   is not 100% available due to metering being performed on the
//   "demo" account. Usually you can make multiple attempts over time and
//   get a proper response eventually.

monitor;

  // Transmit

  RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
  TransmitDS.URI = 'http://api.geonames.org/citiesJSON'
                 + '?north=44.1&south=-9.9&east=-22.4'
                 + '&west=55.2&lang=de&username=demo';
  TransmitDS.LogFile = '/tmp/log.txt';
  TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
  JSON = RXS_Transmit( *Omit : TransmitDS );

  // Parse

  reset gCount;
  reset gNameList;
  RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );
  ParseJsonDS.Handler = %Paddr( JSONHandler );
  RXS_ParseJson( JSON : ParseJsonDS );

  // Output Parse Results as a Thrown Message

  RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
  ErrorDS.MessageId = 'RXS9897';
  ErrorDS.MessageData = gNameList;
  ErrorDS.ThrowToCaller = RXS_YES;
  RXS_Throw( ErrorDS );


on-error;

  RXS_Joblog('Unexpected error occurred. See previous messages.');

endmon;

*INLR = *ON;
return;

Dcl-Proc JSONHandler;
  Dcl-Pi *N Ind;
    pType Int(5) Const;
    pPath Like(RXS_Var64Kv_t) Const;
    pIndex Uns(10) Const;
    pData Pointer Const;
    pDataLen Uns(10) Const;
  End-Pi;

  select;

    when pType = RXS_JSON_OBJECT_END
     and pPath = '/geonames[*]';
      gNameList += ', ';

    when pType = RXS_JSON_OBJECT_END
     and pPath = '/';
      if gCount > 0;
        gNameList += 'No more names!';
      endif;

    when pType = RXS_JSON_STRING
     and pPath = '/geonames[*]/toponymName';
      gCount += 1;
      gNameList += %Char(gCount) + ': ';
      gNameList += RXS_STR( pData : pDataLen );

    // If the service is unavailable to the usage metering mentioned earlier,
    //   there will be JSON data available for parsing per the below

    when pPath = '/status/message';
      gNameList = RXS_STR( pData : pDataLen );

    when pPath = '/status/value';
      gNameList += ' (' + RXS_STR( pData : pDataLen ) + ')';

  endsl;

  // If you want to stop parsing early for whatever reason, return
  // RXS_JSON_STOP_PARSING

  return RXS_JSON_CONTINUE_PARSING;
End-Proc;
```

### Fixed Format RESTJSON.rpgle

```rpgle
     H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND') OPTION(*NODEBUGIO)
     H EXTBININT(*YES) DECEDIT('0.')
     H TEXT('Fixed Format Ex - RESTJSON')

      /COPY QRPGLECPY,RXSCB

     D JSONHandler     PR              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const

     D TransmitDS      DS                  LikeDS(RXS_TransmitDS_t)
     D ParseJsonDS     DS                  LikeDS(RXS_ParseJsonDS_t)
     D ErrorDS         DS                  LikeDS(RXS_CatchThrowErrorDS_t)

     D JSON            S                   Like(RXS_Var64Kv_t)

     D gNameList       S                   Like(RXS_Var64Kv_t)
     D gCount          S             10I 0

      /FREE

       // Please note that the REST web service being consumed by this example program
       //   is not 100% available due to metering being performed on the
       //   "demo" account. Usually you can make multiple attempts over time and
       //   get a proper response eventually.

       monitor;

         // Transmit

         RXS_ResetDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
         TransmitDS.URI = 'http://api.geonames.org/citiesJSON'
                        + '?north=44.1&south=-9.9&east=-22.4'
                        + '&west=55.2&lang=de&username=demo';
         TransmitDS.LogFile = '/tmp/log.txt';
         TransmitDS.HTTPMethod = RXS_HTTP_METHOD_GET;
         JSON = RXS_Transmit( *Omit : TransmitDS );

         // Parse

         reset gCount;
         reset gNameList;
         RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );
         ParseJsonDS.Handler = %Paddr( JSONHandler );
         RXS_ParseJson( JSON : ParseJsonDS );

         // Output Parse Results as a Thrown Message

         RXS_ResetDS( ErrorDS : RXS_DS_TYPE_CATCHTHROWERROR );
         ErrorDS.MessageId = 'RXS9897';
         ErrorDS.MessageData = gNameList;
         ErrorDS.ThrowToCaller = RXS_YES;
         RXS_Throw( ErrorDS );

       on-error;

         RXS_Joblog('Unexpected error occurred. See previous messages.');

       endmon;

       *INLR = *ON;
       return;

      /END-FREE

     P JSONHandler     B                   Export
     D                 PI              N
     D  pType                         5I 0 Const
     D  pPath                              Const Like(RXS_Var64Kv_t)
     D  pIndex                       10U 0 Const
     D  pData                          *   Const
     D  pDataLen                     10U 0 Const
      /FREE

       select;

         when pType = RXS_JSON_OBJECT_END
          and pPath = '/geonames[*]';
           gNameList += ', ';

         when pType = RXS_JSON_OBJECT_END
          and pPath = '/';
           if gCount > 0;
             gNameList += 'No more names!';
           endif;

         when pType = RXS_JSON_STRING
          and pPath = '/geonames[*]/toponymName';
           gCount += 1;
           gNameList += %Char(gCount) + ': ';
           gNameList += RXS_STR( pData : pDataLen );

         // If the service is unavailable to the usage metering mentioned earlier,
         //   there will be JSON data available for parsing per the below

         when pPath = '/status/message';
           gNameList = RXS_STR( pData : pDataLen );

         when pPath = '/status/value';
           gNameList += ' (' + RXS_STR( pData : pDataLen ) + ')';

       endsl;

       // If you want to stop parsing early for whatever reason, return
       // RXS_JSON_STOP_PARSING

       return RXS_JSON_CONTINUE_PARSING;

      /END-FREE
     P                 E
```

---

# ORDSVC

> Complete web service example that parses an XML request into ORDSVCHDR/ORDSVCDTL tables, composes an XML response using the template engine, and configures RXS Router for access.

This example program demonstrates how to offer a web service that accepts XML request data, parses the request data into two output tables, then composes and returns XML response data.

The output tables - ORDSVCHDR and ORDSVCDTL - can be found below. ORDSVCHDR will contain order header information, with one record per request. ORDSVCDTL will contain order detail or line item information, with one or more records per request.

### ORDSVCHDR.pf

```dds.pf
     A          R ORDHDR                    TEXT('Order Header Record')
     A            ORDER         15P 0
     A                                      TEXT('Order number')
     A                                      COLHDG('ORDER' 'NUMBER')
     A            CUST           5A
     A                                      TEXT('Customer number')
     A                                      COLHDG('CUSTOMER' 'NUMBER')
     A            ORDDAT         8P 0
     A                                      TEXT('Date order was entered')
     A                                      COLHDG('ORDER' 'DATE')
     A            SHPVIA        15A
     A                                      TEXT('Ship via')
     A                                      COLHDG('SHIP' 'VIA')
     A            ORDSTS         1P 0
     A                                      TEXT('Order status: 1=Open 2=Closed-
     A                                       3=Canceled')
     A                                      COLHDG('ORDER' 'STATUS')
     A            ORDAMT        11P 2
     A                                      TEXT('Order amount')
     A                                      COLHDG('ORDER' 'AMOUNT')
     A            TOTLIN         3P 0
     A                                      TEXT('Total items in order')
     A                                      COLHDG('TOTAL' 'LINES')
     A            INVNUM         5P 0
     A                                      TEXT('Invoice number')
     A                                      COLHDG('INVOICE' 'NUMBER')
     A          K ORDER
     A                                      SIGNED
```

### ORDSVCDTL.pf

```dds.pf
     A          R ORDDTL                    TEXT('Order Detail Record')
     A            ORDER         15P 0
     A                                      TEXT('Order number')
     A                                      COLHDG('ORDER' 'NUMBER')
     A            QTYORD         3P 0
     A                                      TEXT('Order quantity')
     A                                      COLHDG('ORDER' 'QUANTITY')
     A            PARTNO         6P 0
     A                                      TEXT('Part number')
     A                                      COLHDG('PART' 'NUMBER')
     A            PARTDESC      40A
     A                                      TEXT('Part description')
     A                                      COLHDG('PART' 'DESCRIPTION')
     A            UNITAMT        7P 2
     A                                      TEXT('Unit amount')
     A                                      COLHDG('UNIT' 'AMOUNT')
     A            TOTAMT        10P 2
     A                                      TEXT('Total amount')
     A                                      COLHDG('TOTAL' 'AMOUNT')
     A          K ORDER
     A                                      SIGNED
```

## ORDSVC Program

The RPG program consists of three main parts:

1. Retrieving the request XML
2. Composing the response XML
3. Returning the response XML

Additionally, we must configure [RXS Router](https://isupport.katointegrations.com/rxs/3.5/rxsrtr.md) to make our program accessible as a web service.

The request XML is first retrieved with [RXS_GetStdIn](https://isupport.katointegrations.com/rxs/3.5/rxs_getstdin.md), then parsed with [RXS_Parse](https://isupport.katointegrations.com/rxs/3.5/rxs_parse.md) and the accompanying parsing handler subprocedure. The parsing handler subprocedure handles writing data to the two output tables.

The response XML is composed with the XML composition engine. The composition engine relies on a template, which is created using the [CRTRPGTPL](https://isupport.katointegrations.com/rxs/3.5/crtrpgtpl.md) command. For more information, please see our guide on [creating an RXS template](https://isupport.katointegrations.com/rxs/3.5/creating_an_rxs_template.md). The initial template file (the output from [BLDTPL](https://isupport.katointegrations.com/rxs/3.5/bldtpl.md) can be found in the second tab below.

### ORDSVC.rpgle

```rpgle
**FREE

Ctl-Opt ActGrp(*New) BndDir('RXSBND') Main(ORDSVC);

Dcl-F ORDSVCHDR DISK Usage(*Input:*Output) Keyed UsrOpn Prefix('H');
Dcl-F ORDSVCDTL DISK Usage(*Input:*Output) Keyed UsrOpn Prefix('D');

/COPY QRPGLECPY,RXSCB

// This is the first of two times where we need to copy in the template member
//  for our response XML - it is copied a second time in the ResponseTemplate
//  subprocedure

/COPY QRPGLETPL,ORDTPL

// ========================================================================
// Global fields and data structures
// ========================================================================

Dcl-S gOrderNumber Packed(15:0);
Dcl-S gInvoiceTotal Packed(11:2);
Dcl-S gItemsCount Packed(3:0);


Dcl-S gRequestError Ind Inz(*Off);
Dcl-S gDuplicateOrder Ind Inz(*Off);
Dcl-S gInvalidOrder Ind Inz(*Off);

// ========================================================================
// Program main subproc ORDSVC
// ========================================================================
Dcl-Proc ORDSVC;
  // RPG API Express programs used to offer web services generally
  //  don't accept parameters via a parameter list, so this is
  //  intentionally empty.
  Dcl-Pi *N;
  End-Pi;

  Dcl-S RequestXml Like(RXS_Var8Kv_t);
  Dcl-S ResponseXml Like(RXS_Var64Kv_t);

  Dcl-Ds ParseDS LikeDS(RXS_ParseDS_t);
  Dcl-Ds ComposeDS LikeDS(RXS_ComposeDS_t);
  Dcl-Ds PutStdOutDS LikeDS(RXS_PutStdOutDS_t);

  Dcl-S ExitStatus Ind Inz(*Off);

  monitor;

    exsr init;

    exsr getRequest;

    exsr parseRequest;

    if gRequestError;
      exsr composeAndReturnError;
    else;
      exsr composeAndReturnResponse;
    endif;

  on-error;
    exsr composeAndReturnResponse;
  endmon;

  return;

  begsr init;
    reset gOrderNumber;
    reset gInvoiceTotal;
    reset gItemsCount;

    reset gRequestError;
    reset gDuplicateOrder;
    reset gInvalidOrder;

    // Open necessary tables
    if not %Open(ORDSVCHDR);
      open ORDSVCHDR;
    endif;
    if not %Open(ORDSVCDTL);
      open ORDSVCDTL;
    endif;
  endsr;

  begsr getRequest;
    // Retrieve request XML from Apache/RXS Router
    reset RequestXml;
    RequestXml = RXS_GetStdIn();
  endsr;

  begsr parseRequest;
    // Parse request XML and write records to ORDSVCHDR/ORDSVCDTL assuming
    //  there are no errors
    RXS_ResetDS( ParseDS : RXS_DS_TYPE_PARSE );
    ParseDS.GlobalHandler = %Paddr( RequestHandler );
    RXS_Parse( RequestXml : ParseDS );
  endsr;

  begsr composeAndReturnResponse;
    // Compose the response XML
    RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
    ComposeDS.TemplateProcedure = %Paddr( ResponseTemplate );
    ComposeDS.TrimVariables = RXS_YES;
    ComposeDS.EncodeVariables = RXS_YES;
    RXS_StartComposeEngine( ComposeDS );

    // We're going to respond with pretty much the same
    //  data that we accepted and parsed from the request.

    chain gOrderNumber ORDSVCHDR;

    RXS_ComposeVariable( V.OrderNumber : %Char(HORDER) );
    RXS_ComposeVariable( V.OrderDate : %Char(HORDDAT) );
    RXS_ComposeVariable( V.OrderStatus : %Char(HORDSTS) );
    RXS_ComposeVariable( V.CustNum : HCUST );
    RXS_ComposeVariable( V.ShipVia : HSHPVIA );
    RXS_ComposeSection( S.ORDER_HEADER );

    RXS_ComposeVariable( V.InvoiceNumber : %Char(HINVNUM) );
    RXS_ComposeVariable( V.ItemCount : %Char(HTOTLIN) );
    RXS_ComposeVariable( V.Amount : %Char(HORDAMT) );
    RXS_ComposeSection( S.INVOICE_HEADER );

    setll gOrderNumber ORDSVCDTL;
    reade gOrderNumber ORDSVCDTL;
    dow not %Eof(ORDSVCDTL);
      RXS_ComposeVariable( V.ItemNumber : %Char(DPARTNO) );
      RXS_ComposeVariable( V.ItemDesc : DPARTDESC );
      RXS_ComposeVariable( V.ItemQuantity : %Char(DQTYORD) );
      RXS_ComposeVariable( V.UnitPrice : %Char(DUNITAMT) );
      RXS_ComposeVariable( V.TotalPrice : %Char(DTOTAMT) );
      RXS_ComposeSection( S.ITEM );

      reade gOrderNumber ORDSVCDTL;
    enddo;

    RXS_ComposeSection( S.INVOICE_FOOTER );

    RXS_ComposeSection( S.ORDER_FOOTER );

    ResponseXml = RXS_GetComposeBuffer();

    RXS_PutStdOut( ResponseXml );
  endsr;

  begsr composeAndReturnError;
    RXS_ResetDS( ComposeDS : RXS_DS_TYPE_COMPOSE );
    ComposeDS.TemplateProcedure = %Paddr( ResponseTemplate );
    ComposeDS.TrimVariables = RXS_YES;
    ComposeDS.EncodeVariables = RXS_YES;
    RXS_StartComposeEngine( ComposeDS );

    // Set the code and message based on the type of error
    select;
      when gDuplicateOrder;
        RXS_ComposeVariable( V.ErrorCode : 'DUPLICATE_ORDER' );
        RXS_ComposeVariable( V.ErrorMessage
                           : 'A duplicate order exists in ORDSVCHDR' );
      when gInvalidOrder;
        RXS_ComposeVariable( V.ErrorCode : 'INVALID_ORDER' );
        RXS_ComposeVariable( V.ErrorMessage
                           : 'Provided order is invalid' );
      other;
        RXS_ComposeVariable( V.ErrorCode : 'UNKNOWN' );
        RXS_ComposeVariable( V.ErrorMessage
                           : 'An unknown error occurred' );
    endsl;

    RXS_ComposeSection( S.ERROR );

    ResponseXml = RXS_GetComposeBuffer();

    // Because we're returning an error, we're going to leverage
    //  the HTTP status code to more clearly communicate that to the caller.
    //  A status code of 400 indicates a "Bad request" which is a good catch
    //  all for the types of errors we're trying to capture and report. To
    //  send a specific status code other than the default 200, we need to
    //  use the optional data structure parameter that RXS_PutStdOut supports.
    // You can read more about types of HTTP status codes here:
    //   https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
    RXS_ResetDS( PutStdOutDS : RXS_DS_TYPE_PUTSTDOUT );
    PutStdOutDS.HeaderStatusCode = 400;
    PutStdOutDS.HeaderContentType = 'text/xml';
    RXS_PutStdOut( ResponseXml : PutStdOutDS );
  endsr;

On-Exit ExitStatus;
  // Close all open files
  close(E) *ALL;

End-Proc;


// Perform a check against ORDSVCHDR to see if we already have an order
//  with this order number. If we do, we're going to return an error
//  to the calling client.
Dcl-Proc IsDuplicateOrder;
  Dcl-Pi *N Ind;
    pOrderNumber Packed(15:0) Const;
  End-Pi;

  monitor;
    if not %Open(ORDSVCHDR);
      open ORDSVCHDR;
    endif;

    chain pOrderNumber ORDSVCHDR;
    if %Found(ORDSVCHDR);
      return *On;
    else;
      return *Off;
    endif;
  on-error;
    return *On;
  endmon;
End-Proc;


// ========================================================================
// Request XML parsing handler
// ========================================================================
// This subprocedure handles XML events that are detected by the parser.
// The XPath structures for the different events are:
//   '/element>' - element start
//   '/element/>' - element end
//   '/element/' - element content
//   '/element@attribute' - attribute content
Dcl-Proc RequestHandler;
  Dcl-Pi *N;
    pType Char(10) Value;
    pXPath VarChar(1024) Value;
    pData Pointer Value;
    pDataLen Int(10) Value;
  End-Pi;

  // This is checking to see if we've run into an error while parsing
  //  or validating this order, and halting parsing if we have.
  if gRequestError;
    return;
  endif;

  select;
    // This XPath ends in a ">", which means that is it an "Element Begin"
    //  parsing event. These types of events are generally a good trigger
    //  to perform initialization of records or data structures, or reset
    //  counters, etc.
    // Note that "Element Begin" events DO NOT have data.
    when pXPath = '/Order>';
      // At the start of the XML document, reset the ORDSVCHDR record and
      //  zero the invoice total field
      reset ORDHDR;
      gInvoiceTotal = 0;
      gItemsCount = 0;

    when pXPath = '/Order/Items/Item>';
      reset ORDDTL;


    // This XPath is searching for an "Attribute Content" parsing event,
    //  which is indicated by the '@' symbol before the attribute name.
    // Note that "Attribute Content" events DO have data.
    when pXPath = '/Order@Number';
      // Parsed data is retrieved with RXS_STR().
      // Because content is retrieved as character data, we need to
      //  convert the value into the appropriate data type before
      //  writing it to the table.
      monitor;
        HORDER = %Dec( RXS_STR( pData : pDataLen ) : 15 : 0 );
        gOrderNumber = HORDER;
      on-error;
        HORDER = 0;
        gOrderNumber = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endmon;

      // Check to see if this order number already exists. If it does,
      //  stop parsing, and return an error message to the calling client.
      if IsDuplicateOrder( HORDER );
        gRequestError = *On;
        gDuplicateOrder = *On;
        return;
      endif;

    // This XPpath ends in a "/", which is an "Element Content" parsing event.
    //  These events are the main thing you test for when writing an event
    //  handler subprocedure, because this is how you parse content from an
    //  XML document.
    // Note that "Element Content" events DO have data.
    when pXPath = '/Order/Customer/';
      HCUST = RXS_STR( pData : pDataLen );

    when pXPath = '/Order/Ship/';
      HSHPVIA = RXS_STR( pData : pDataLen );

    when pXPath = '/Order/Items/Item>';
      // Safeguard before we try to parse items to confirm we found an
      //  order number in the request
      if gOrderNumber = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endif;

    when pXPath = '/Order/Items/Item/Number/';
      monitor;
        DPARTNO = %Dec( RXS_STR( pData : pDataLen ) : 6 : 0 );
      on-error;
        DPARTNO = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endmon;

    when pXPath = '/Order/Items/Item/Description/';
      DPARTDESC = RXS_STR( pData : pDataLen );

    when pXPath = '/Order/Items/Item/Quantity/';
      monitor;
        DQTYORD = %Dec( RXS_STR( pData : pDataLen ) : 3 : 0 );
      on-error;
        DQTYORD = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endmon;


    when pXPath = '/Order/Items/Item/UnitPrice/';
      monitor;
        DUNITAMT = %Dec( RXS_STR( pData : pDataLen ) : 7 : 2 );
      on-error;
        DUNITAMT = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endmon;

    // We will use this event to calculate the line item total cost
    //  and add it to the invoice total, as well as write the line item
    //  record to the table. We also increment the item count.
    when pXPath = '/Order/Items/Item/>';
      DORDER = HORDER;
      DTOTAMT = DUNITAMT * DQTYORD;
      gInvoiceTotal += DTOTAMT;
      gItemsCount += 1;
      write ORDDTL;

    // This XPath ends in a "/>", which is an "Element End" parsing event.
    //  These events are a good place to perform record write or cleanup
    //  operations.
    // Note that "Element End" events DO NOT have data.
    when pXPath = '/Order/>';
      // Check again to confirm we had a valid order number
      if gOrderNumber = 0;
        gRequestError = *On;
        gInvalidOrder = *On;
        return;
      endif;

      HORDAMT = gInvoiceTotal;
      HTOTLIN = gItemsCount;
      HORDSTS = 1;
      HORDDAT = %Dec( %Char(%Date():*ISO0) : 8 : 0 );
      HINVNUM = HORDER + 1;
      write ORDHDR;

  endsl;

  return;
End-Proc;


Dcl-Proc ResponseTemplate;
  Dcl-Pi *N;
    p Like(RXS_TEMPLATE_PARM);
  End-Pi;

  // Templates are created via a two-step process using BLDTPL and
  //  CRTRPGTPL - full walkthrough can be found at
  // https://isupport.katointegrations.com/rxs/latest/creating_an_rxs_template/

  // The RPG Template source is copied from QRPGLETPL by using
  //  /COPY once at the beginning of the program and again below.

  /COPY QRPGLETPL,ORDTPL
End-Proc;
```

### ORDTPL.tpl

```xml
::ORDER_HEADER
<Order Number=".:OrderNumber:." Date=".:OrderDate:.">
  <Status>.:OrderStatus:.</Status>
  <Customer>.:CustNum:.</Customer>
  <Ship>.:ShipVia:.</Ship>
::INVOICE_HEADER
  <Invoice Number=".:InvoiceNumber:." ItemCount=".:ItemCount:.">
    <Amount>.:Amount:.</Amount>
    <Items>
::ITEM
      <Item>
        <ItemNumber>.:ItemNumber:.</ItemNumber>
        <Description>.:ItemDesc:.</Description>
        <Quantity>.:ItemQuantity:.</Quantity>
        <UnitPrice>.:UnitPrice:.</UnitPrice>
        <TotalPrice>.:TotalPrice:.</TotalPrice>
      </Item>
::INVOICE_FOOTER
    </Items>
  </Invoice>
::ORDER_FOOTER
</Order>
```

### REQUEST XML

```xml
<Order Number="12345">
  <Customer>A23BH</Customer>
  <Ship>Carrier</Ship>
  <Items>
    <Item>
      <Number>7890</Number>
      <Description>Part 1A</Description>
      <Quantity>1</Quantity>
      <UnitPrice>10.00</UnitPrice>
    </Item>
    <Item>
      <Number>7891</Number>
      <Description>Part 1B</Description>
      <Quantity>3</Quantity>
      <UnitPrice>15.00</UnitPrice>
    </Item>
  </Items>
</Order>
```

### RESPONSE XML

```xml
<Order Number="12345" Date="11112021">
  <Status>1</Status>
  <Customer>A23BH</Customer>
  <Ship>Carrier</Ship>
  <Invoice Number="11025" ItemCount="2">
    <Amount>55.00</Amount>
    <Items>
      <Item>
        <Number>7890</Number>
        <Description>Part 1A</Description>
        <Quantity>1</Quantity>
        <UnitPrice>10.00</UnitPrice>
        <TotalPrice>10.00</TotalPrice>
      </Item>
      <Item>
        <Number>7891</Number>
        <Description>Part 1B</Description>
        <Quantity>3</Quantity>
        <UnitPrice>15.00</UnitPrice>
        <TotalPrice>45.00</TotalPrice>
      </Item>
    </Items>
  </Invoice>
</Order>
```

## Configuring RXS Router

With your RXS library in your library list, call the command [WRKRXSRTRE](https://isupport.katointegrations.com/rxs/3.5/wrkrxsrtre.md) to open the Work with RXSRTRCTL Entires screen. The entires on this screen are used to configure the different web services offered by RXS Router in your environment.

Press `F6` to create a new entry. Fill in the `Routing Identifier` and `Routing data` fields like the example configuration below:

![WRKRXSRTRE entry for ordersvc](https://isupport.katointegrations.com/llms/rpg-api-express-3.5/wrkrxsrtre_ordsvc.png)

RXS Router parses the URL to determine which control entry to reference for a given web service call. The `Routing Identifier` value is what RXS Router checks the URL against, and this also determines what the URL will look like when calling the web service. In this case, the URL for the web service will start with:

`[your IBM i address]/orders/`

The flags used in the `Routing data` field control the following:

| | | |
|---|---|---|
| `-pgm *1` | | Tells RXS Router to look at the first URL segment (after the routing identifier) to determine which program to call |
| `-lib ORDSVCDEMO` | | Tells RXS Router that the program being called is in the ORDSVCDEMO library |

There are other configuration flags available - see the [RXS Router](https://isupport.katointegrations.com/rxs/3.5/rxsrtr.md) page for more information. We do not need additional flags for this simple program.

With our program named ORDSVC, the final URL to call the web service will be:

`[your IBM i address]/orders/ordsvc/`
