Converting Between Lower and Upper Case - Refined

Article ID: 54908

Writing code that "just works" in the moment is poor programming. I see this all the time; programmers writing stuff that "just works" in one specific instance, and then as soon as it needs to be changed, or the operating system is updated and the call to an API or other interface is changed, or some other thing has been updated (such as the return location of a value in an API data structure), their code blows up.

These programmers often blame IBM for changing things, but as is normally the case when we blame other people for our problems, it is usually our own fault. If you write code that works only in a given situation, then that code better be for a one-time-use situation and not put into daily production. Why? Because code that isn't written to work in all known situations is not good, production-quality code. Many programmers who write one-use code and then put that code into production justify doing so by saying they "don't have time to do it right, only to make it work." Well, the official name for this type of programmer . . . oh, how does it go? Oh yes, they're lazy!

I have seen programmers take two days to do something that should take them two hours if they were inexperienced and perhaps 20 minutes if they knew what they were doing. Then later on when they need to do something similar, the original code is so hard-wired to that old situation that modifying it is more complicated than starting from scratch, and this second situation ends up taking more time than the first.

For example, you need to convert file A to comma-separated values for use in Excel. You write some code to do the conversion. Then two weeks to six months later, the company wants another file, File B, to be converted to comma-separated form for Excel. At this point if it takes you more than 30 minutes to set up File B for conversion, your programming drive is questionable.

If you had written code that works in all known situations, you could have probably, for example, just passed the file name and the output location to a command or program that would do the conversion. But since you hard-coded everything for the original "one-off situation," your company has to throw good money after bad, paying you to do the same job a second time.

Here's an example of making something work in all known situations: converting lower to upper case and upper to lower case.

Case Conversion for One-Off Situations

XLATE and %XLATE can be used to convert between upper and lower case. It is so easy to do that it is rather disappointing it isn't just a built-in function. For example:

     D UP              C                   'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
     D low             C                   'abcdefghijklmnopqrstuvwxyz'

      /free
          myData = %xlate(low:UP : myData);
      /end-free

See my previous article here for more information on this technique.

But what if you have more than one System i on which you run your software? What if your users will sign on and use your application and have a different job CCSID than the one you used when you wrote your application? What if you're an international company or a software vendor with distributions throughout the world? Would the %XLATE technique be the best way to perform a conversion on every System i throughout the known universe?

If you live in North America and want your code to work only with CCSID, or you live in a fishbowl, then the answer would be "yes." This is a sufficient way to convert data between upper and lower case, so you can stop reading.

If you are an international company or ship software around the world, or you simply want your code to work in all known situations, then read on.

The QlgConvertCase API was created to internationalize data conversion. It correctly converts all characters, invariant and extended, to upper and lower case.

If your code is running in a job that is using an extended Latin character set CCSID (e.g., Italy, Spain, France), the grave accent characters may also need to be included. So using %XLATE would not work in all situations. Instead, %XLATE would simply "round-trip" the character, passing the character through without converting it. The QlgConvertCase API, on the other hand, correctly converts the extended characters, such as those with the grave accent. The prototype for QlgConvertCase follows:

QlgConvertCase Prototype

     D QlgCvtCase      PR                  extProc('QlgConvertCase')
     D  ctrlBlock                          LikeDS(FRCB_T) Const
     D  inString                  65535A   Const Options(*VARSIZE)
     D  OutString                 65535A   Options(*VARSIZE)
     D  nLength                      10I 0 Const
     D  APIErrorDS                         LikeDS(QUSEC_T)

The QlgConvertCase API is fairly simple; it has a function request control block parameter that is just a data structure that tells the API to convert to all lowercase or to all uppercase characters. Then it has input and output parameters, followed by a length parameter. Of course, it also has the obligatory API Error Data Structure parameter.

The function request control block or FRCB is defined as follows:

QlgConvertCase FRCB_T Data Structure Template

      /IF NOT DEFINED(FRCB_T)
      /DEFINE FRCB_T
     D FRCB_T          DS                  Align Qualified
     D  ReqType                      10I 0 Inz(1)
     D  CCSID                        10I 0 Inz(0)
     D  case                         10I 0 Inz(LG_TOLOWER)
     D  reserved                     10A   Inz(*ALLX'00')
      /ENDIF

The FRCB_T data structure has four subfields, as follows:

  1. The REQTYPE subfield indicates that conversion is being performed using the CCSID specified in the CCSID subfield.
  2. The CCSID subfield is usually set to 0 to indicate that the conversion is being done using the current running job's CCSID. If a specific CCSID is needed, however, specify it here.
  3. The CASE subfield controls the direction of the conversion. When CASE is 0, data is converted to all uppercase letters. When CASE is 1, data is converted to all lowercase letters.
  4. The RESERVED subfield is not used, but it must contain all hex zeros. This is probably why most programmers abandon this API. They try it and it doesn't work unless you fill this 10-byte field with all X'00' characters.

The standard API exception/error data structure, QUSEC, is typically ignored when calling this API, but it is required and is defined, as follows:

API Exception/Error Data Structure Template: QUSEC_T

      /IF NOT DEFINED(QUSEC_T)
      /DEFINE QUSEC_T
      /IF NOT DEFINED(QUSEC)
      /DEFINE QUSEC
      ********************************************************
      **  QUSEC - API Error Data Structure
      **  From "RPG TnT: 101 Dynamite Tips 'n Techniques with RPG IV"
      **  Specify as a template name via the LIKEDS keyword.
      **  Usage should be with INZ(*LIKEDS) as follows:
      **  D apiError   DS                  LikeDS(QUsec_T)
      **  D                                Inz(*LIKEDS)
      ********************************************************
     D QUSEC_T         DS                  Qualified
     D  bytesProvided                10I 0 Inz(%size(qusec_t))
     D  bytes_Provided...
     D                               10I 0 Overlay(bytesProvided)
     D  bytesProv                    10I 0 Overlay(bytesProvided)
     D  bytesReturned                10I 0 Inz
     D  bytes_Returned...
     D                               10I 0 Overlay(bytesReturned)
     D  bytesRtn                     10I 0 Overlay(bytesReturned)
     D  cpfmsgID                      7A
     D  reserved                      1A   Inz(X'00')
     D  exceptionData                64A   Inz(*ALLX'00')
      /ENDIF
      /ENDIF

Using QlgConvertCase to Convert to Lower or Upper Case

To convert to all lower case or all upper case, simply call the QlgConvertCase API using the above prototype, as follows:

     D myFRCB          DS                  LikeDS(FRCB_T)
     D myUSEC          DS                  LikeDS(QUSEC_T)
      /free
          myUSEC = *ALLX'00';
          myFRCB.case = 1;  // Convert to lower case
          QlgCvtCase(myFRCB : myData : myData : %size(myData): myUSEC);
      /end-free

In this example, the CASE subfield is set to 1 to indicate that the conversion direction is to all lowercase characters. Note also that to avoid using the QUSEC API exception/error data structure, I fill it with all X'00' characters. This not only clears the data structure, but it also sets the bytesProvided subfield to zero. This tells the API that no exception/error data structure space is being provided. Setting the bytesProvided subfield to %size(QUSEC_T) would give the API the area it needs to populate this data structure in the unlikely event of a runtime error.

To simplify things further, I've defined two named constants for use with QlgConvertCase, as follows:

     D LG_TOUPPER      C                   Const(0)
     D LG_TOLOWER      C                   Const(1)

Move one of these named constants into the CASE subfield of your FRCB_T data structure to control the direction of the conversion.

Calling QlgConvertCase, while relatively simple compared with most APIs, can be simplified even future by creating wrapper subprocedures. I've created two wrappers for this API, as follows:

  • LOWER converts the input value to all lower case.
  • UPPER converts the input value to all upper case.

The implementation and prototypes for these two subprocedures can be found in my book RPG TNT: 101 Dynamite Tips n Techniques with RPG IV, but I've reproduced the implementations here for your convenience:

Subprocedure Implementation for LOWER and UPPER

     H NOMAIN OPTION(*NODEBUGIO:*SRCSTMT)

      /INCLUDE RPGTNT/QCPYSRC,cvtcase

     D szOutput        S           2048A   
     D myFRCB          DS                  LikeDS(FRCB_T)  Inz(*LIKEDS)
     D myUsec          DS                  LikeDS(QUSEC_T)
     P lower           B                   EXPORT
     D lower           PI          2048A   Varying
     D  szInput                    2048A   Const Varying
      /free
          if (%len(szInput) = 0);
             return ';
          endif;
          myUsec = *ALLX'00';
          myFRCB.case = LG_TOLOWER;  // Convert to Lower case
          QlgCvtCase(myFRCB : szInput : szOutput :
                     %size(szInput) : myUsec);
          return   %TrimR(%subst(szOutput:1:%len(szInput)));
      /end-free
     P lower           E

     P upper           B                   EXPORT
     D upper           PI          2048A   Varying
     D  szInput                    2048A   Const Varying
      /free
          if (%len(szInput) = 0);
             return ';
          endif;
          myUsec = *ALLX'00';
          myFRCB.case = LG_TOUPPER;  // Convert to UPPER case
          QlgCvtCase(myFRCB : szInput : szOutput :
                     %size(szInput) : myUsec);
          return   %TrimR(%subst(szOutput:1:%len(szInput)));
      /end-free
     P upper           E

Writing and using code that works in all known situations is better than writing code that just works in a specific situation. And it will cost your company less for you to write code this way, since you don't have to reinvent the wheel every time they need another version of that so-called "one-off" project. And last, if you complain you don't have enough time to do it right, only to do it quickly, imagine how much time you'll save doing it more quickly that second time by taking a few extra minutes to do it right in the first place.

About the author: Bob Cozzi is author of RPG TnT: 101 Dynamite Tips 'n Techniques with RPG IV and the pervasive The Modern RPG IV Language. He is host of System iNetwork's iMonthly video podcast and sponsor of the popular RPG World Developer Conference held in May of each year. He can be reached at: rpgcoder@gmail.com

ProVIP Sponsors

ProVIP Sponsors