The need to convert lowercase to uppercase or vice versa has long been an issue in the RPG world. With the APIs available to RPG developers today, creating a subprocedure to convert between cases is simply a matter of writing a subprocedure to simplify things. In 2002, I published an article on converting between cases using the XLATE opcode. In the last issue, I illustrated a similar technique in "Getting Started with Procedures."
A problem with the technique featured in those articles is that the conversion works only with the North American character set, a.k.a. coded character set ID (CCSID). In this issue, I want to expose how to do case conversion that is not tied to any particular character set or national language.
To do this, we have to write our own subprocedure, create a table for each CCSID, and call either QDCXLATE or the QlgConvertCase API.
According to the IBM Information Center, the QlgConvertCase API provides case conversion for single-byte, mixed-byte, and UCS2 (Universal Multiple-Octet Coded Character Set with 16 bits per character) character sets. For the mixed-byte character set data, only the single-byte portion of the data is converted. QlgConvertCase does not convert DBCS data.
"QlgConvertCase" may not exactly be an easy name to remember, nor easy to guess. To use it, I want to create a wrapper procedure named CVTCASE. The CVTCASE subprocedure is actually one I've used in RPG xTools for a couple of years now to do conversion quickly and without concern for national language support. In fact, the CvtToUpper() and CvtToLower() subprocedures in RPG xTools Version 5.0 call CVTCASE under the covers.
In this article, I'll expose the source code behind the xTools CVTCASE subprocedure and explain how it calls QlgConvertCase. That way, you can use it on your system regardless of whether or not you've licensed RPG xTools.
First Things First
Of course, the first thing we need to do is create a prototype for the QlgConvertCase API itself so that we can call it from our CVTCASE subprocedure. That prototype follows:
** C O N V E R T C A S E
*********************************************************
D QlgCvtCase PR ExtProc('QlgConvertCase')
D ctrlBlock Const LikeDS(FRCB_T)
D inString 65535A Const Options(*VARSIZE)
D OutString 65535A Options(*VARSIZE)
D Length 10I 0 Const
D APIError LikeDS(QUSEC)
Since the QlgConvertCase API is a procedure itself, calling it is faster than calling a typical program. While many APIs that are programs (as opposed to procedures) are also stored in the OS/400 or i5/OS System Entry Point Table (SEPT), making them very fast to call, calling a procedure is always supposed to be faster than calling a program.
One side effect of procedure-based OS/400 APIs is that they often have mixed-case names. In order to prototype this kind of API, the EXTPROC keyword is used, with the name of the API enclosed in quotes, like so:
Make sure these types of API names appear in the proper uppercase/lowercase combination (as documented in the InfoCenter); otherwise, the compiler will not locate them during the bind phase of the compile.
The first parameter of QlgConvertCase is a function control block. The control block is a data structure that tells the API what you want to do. IBM designs this type of parameter in lieu of adding several additional parameters. You use this data structure to control whether you want to convert between cases. The control block data structure can be declared as follows:
D FRCB DS
D ReqType 10I 0 Inz(1)
D CCSID 10I 0 Inz(0)
D CvtTo 10I 0 Inz(0)
D Reserved 10A Inz(*ALLX'00')
Since RPG IV in Version 5 of OS/400 includes qualified data structures and the LIKEDS keyword, it would be best to create a template of sorts rather than just declare the data structure. So the actual definition of this data structure is that of a data structure template, as follows:
D FRCB_T DS QUALIFIED
D BASED(TEMPL_PTR)
D ReqType 10I 0
D CCSID 10I 0
D CvtTo 10I 0
D Reserved 10A
By declaring this data structure as a template, we can now use the LIKEDS keyword to define the data structure we want to use in our application and the parameter itself. Remember, a data structure template is simply a term, not a defined part of the RPG IV language definition. It is, however, so widely used that IBM is designing data structure templates into the language in an upcoming release.
The procedure we are creating is called CVTCASE. It accepts two parameters: (1) The text to be converted and (2) an optional parameter that controls whether to convert cases. If you enter a value of 0 or if the parameter is not specified, CVTCASE converts the input text to uppercase. A value of 1 causes it to convert to lowercase.
The following is the prototype for the CVTCASE subprocedure:
D InString 65535A Const Varying
D nOption 5I 0 Const OPTIONS(*NOPASS)
The first parameter, INSTRING, is a VARYING parameter and is also CONST. This means that the compiler will allow you to specify variable-length or nonvariable-length (i.e., regular) character fields for this parameter. Up to 64 K of data can be converted, although it is unlikely that you'll need to convert such a lengthy value.
To use this subprocedure, include the prototype (above) in the Definition specifications of your applications.
The body or implementation of the CVTCASE procedure isn't very complex, but it does some interesting things. First, it checks the length of the input string to verify that it contains some data. If it doesn't, it simply returns. But it doesn't return "nothing"; it returns the same string that was passed in. This ensures that nothing is destroyed and the caller gets back exactly what was sent in.
Next, it initializes the FRCB data structure. While the INZ keyword is also specified for the FRCB, the EVAL statement is used to copy all hex zeros (X'00') to the data structure. This ensures that the reserved area of the data structure is also set to zeros. The RESERVED subfield must contain hex zeros, according to the API documentation. Otherwise, the API may not function correctly. This is one of the major problems programmers have when calling APIs: not initializing the data structures properly.
Now, it calls the QlgConvertCase to do the conversion. It passes the input variable INSTRING directly to the API. This may seem odd, considering the API requires a fixed-length character field and INSTRING is a varying-length character field. However, the power of prototyping causes the INSTRING value to be formatted according to the called procedure's parameter description. That is, the compiler converts the varying-length field into a fixed-length field. It does this because the INSTRING parameter of the QlgConvertCase API is defined as CONST. The CONST keyword does a lot for you, including converting a variable whose base data type matches the parameter into the format required by the parameter definition; it will convert any type of character value into the fixed-length required by INSTRING. If it's a numeric parameter, as is the case with the LENGTH parameter, the only requirement is that the length specified when calling QlgConvertCase is numeric. The compiler converts it into (in this case) a 10i0 value.
Finally, the input string, now converted by QlgConvertCase, is returned to the caller. The RETURN operation does this, using the %SUBST (substring) built-in function. We only want to return as much data as we received. Otherwise, the return process does additional work copying the extra bytes and slows things down.
** C V T C A S E - Convert between lower/uppercase
** Parameters:
** InString - (Input) Text to convert
** nOption - (Input) Control Option
** 0 = To Upper
** 1 = To Lower
** Return Value: Converted input text.
** (c) Copyright 2002 by Robert Cozzi, Jr.
** All rights reserved.
** Part of the RPG xTools. Used by Permission.
** www.rpgxtools.com
*********************************************************
P CvtCase B Export
D CvtCase PI 65535A Varying
D InString 65535A Const Varying
D nOption 5I 0 Const OPTIONS(*NOPASS)
D OutString 65535A
** Control Block for the call to QglConvertCase.
D FRCB DS LikeDS(FRCB_T) Inz
** API Error Data Structure, required, but ignored.
D APIErrorDS S 16A INZ(*ALLX'00')
C if %Len(%Trim(InString)) <= 0
C return InString
C endif
** Clear the FRCB data structure
C Eval FRCB=*ALLX'00'
** Tell the API that we want case conversion
C Eval FRCB.ReqType = 1
** QlgCvtCase uses 0 to convert to uppercase and 1 for lower,
** which is passed into this procedure on the nOption parm.
** The parm value is copied to the FRCB.CvtTo subfield.
C if %Parms >= 2
C Eval CvtTo = nOption
C endif
C CallP QlgCvtCase(FRCB : InString :
C OutString : %Len(InString) :
C APIErrorDS)
C return %Subst(OutString:1:%Len(InString))
P CvtCase E
Calling CVTCASE from RPG IV
To call the CVTCASE procedure, you need to make sure you've included the prototype for the procedure in your source code. This is best done via a /COPY or /INCLUDE (both work the same). Storing prototypes in a separate source member and /COPYing them into your code is the only correct way to use them.
Next, specify the value you want to convert as the first parameter, and optionally specify 1 or 0 for the second parameter. This controls the conversion effort. If the second parameter is not specified, '0' (convert to uppercase) is used as the default.
Here's an example of how to use CVTCASE to convert to uppercase the data in a field named USRPRF.
Also, the second parameter is optional, so if you avoid specifying 0 or 1 for it, the subprocedure defaults to 0 (convert to uppercase). The following is an example of calling CVTCASE to convert to uppercase without specifying the second parameter.
This is a basic technique that creates a true conversion tool that respects the CCSID of the system on which it is running. So, for example, if it is run with the Spanish or North American CCSID, everything is converted properly.
One Final Word
To compile this procedure or any of your own applications, you will need two things. I've already mentioned that the prototype for CVTCASE needs to be included, but so does the QUSEC IBM-supplied data structure. That data structure is included in the QSYSINC library. To include it in your code, specify the following compiler directive:
If you do not have QSYSINC installed on your system, install it. If you can't install it on a timely basis, you can use the following data structure in lieu of the QSYSINC source member.
D ErrDS_Len 10I 0 Inz(%size(QUSEC))
D ErrReturn_Len 10I 0 Inz
D CPFMSGID 7A
D ErrReserved 1A Inz(X'00')
Bob Cozzi is a programmer/consultant, writer/author, and software developer of the RPG xTools, a popular add-on subprocedure library for RPG IV. His book The Modern RPG Language has been the most widely used RPG programming book for nearly two decades. He, along with others, speaks at and runs the highly-popular RPG World conference for RPG programmers.
LATEST COMMENTS
MC Press Online