ILE RPG procedure to convert a string to upper/lower case – fast

The two procedures described in this article, converts a string to upper or lower case. The conversion is done fast by using a combination of a translation API to provide the correct translation tables, which includes national characters for us with such, and then use the build in function %XLate to perform the actual translation very fast.

The speed of the convert is way faster than using SQL (Set B  = Upper(A) ) or translate APIs. These two methods will do the job if you just need to convert now-and-then. A batch job converting many fields/records will benefit from my fast routines.

The two procedures that convert to upper and lower case, are updated ones of the procedures that I released back in August 2016. After reading a discussion about various methods to translate between upper and lower case, I updated my routines with focus on high speed.

In general, when the %XLate Build In Function is used, people tend to use it with a string that just contains A-Z, omitting National characters which, of cause, is fine if you only want to convert e.g. object names . A solution to use a SQL statement was proposed, but it is slow to do the convert. Download the sources and run LUCASE_EX2 and see the timing on your IBM i.

These updated procedures are coded in Totally Free RPG. The high speed has been archived by translate two tables (actually strings) to upper- and lowercase the first time one of the procedures are called, and then use these two tables to perform the actual conversion, on subsequent calls, by using the %Xlate Build In Function. This way, it is fast and national characters are converted too 🙂

Download:

  • Source members as text files: LUCaseSourcesV3
    The zip file contains the source members. Extract the source files and transfer them to your IBM i.

Please be aware, that these procedures will only work with single byte character sets.

Have fun 🙂

Listing 1, member LUCASE_PR, type RPGLE:

**Free
//
// LUCase_Pr
// ---------
// Prototypes for Lower and Upper case functions.
//
// The source member are delivered 'as is'.
//
// Neither Jesper Wachs nor anyone else who has been involved in
// the creation, production or delivery of this product shall be
// liable for any direct, indirect, consequential or incidental
// damages (including damages for loss of business profits,
// business interruption, loss of business information, and the
// like) arising out of the use or inability to use such product
// even if Jesper Wachs has been advised of the possibility of
// such damages.
//
Dcl-PR LCase VarChar(1024);
  InputStr VarChar(1024) Const;
End-PR;

Dcl-PR ResetCase;
End-PR;

Dcl-PR UCase VarChar(1024);
  InputStr VarChar(1024) Const;
End-PR;

Listing 2, member LUCASE, type RPGLE:

**Free
Ctl-Opt NoMain;
//
// LUCase
// ------
// Functions to convert a string to Lower or Upper case.
//
// Jesper Wachs, Version 3, July 2018.
//   Converted to Totally Free RPG.   
//
// Jesper Wachs, Version 2, May 2018.
//   Changed to retrieve upper- and lowercase tables and use them
//   with %XLate for improving speed when converting strings to
//   upper and lower case.
//
// Jesper Wachs, Version 1, August 2016.
//   Build to call 'QlgConvertCase' to support all chars.
//
// The source member are delivered 'as is'.
//
// Neither Jesper Wachs nor anyone else who has been involved in
// the creation, production or delivery of this product shall be
// liable for any direct, indirect, consequential or incidental
// damages (including damages for loss of business profits,
// business interruption, loss of business information, and the
// like) arising out of the use or inability to use such product
// even if Jesper Wachs has been advised of the possibility of
// such damages.
//
// Copymember for exported functions.
// ----------------------------------
/copy qrpglesrc,lucase_pr

//
// Prototypes for local functions.
// -------------------------------
Dcl-PR Convert VarChar(1024);
  iString VarChar(1024) Const;
  iUpLow Packed(1:0) Const;
End-PR;

Dcl-PR GetTables;
End-PR;

//
// Work fields.
// ------------
Dcl-S lBaseT Char(256) Inz(x'-
                            000102030405060708090a0b0c0d0e0f-
                            101112131415161718191a1b1c1d1e1f-
                            202122232425262728292a2b2c2d2e2f-
                            303132333435363738393a3b3c3d3e3f-
                            404142434445464748494a4b4c4d4e4f-
                            505152535455565758595a5b5c5d5e5f-
                            606162636465666768696a6b6c6d6e6f-
                            707172737475767778797a7b7c7d7e7f-
                            808182838485868788898a8b8c8d8e8f-
                            909192939495969798999a9b9c9d9e9f-
                            a0a1a2a3a4a5a6a7a8a9aaabacadaeaf-
                            b0b1b2b3b4b5b6b7b8b9babbbcbdbebf-
                            c0c1c2c3c4c5c6c7c8c9cacbcccdcecf-
                            d0d1d2d3d4d5d6d7d8d9dadbdcdddedf-
                            e0e1e2e3e4e5e6e7e8e9eaebecedeeef-
                            f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff-
                            ');
Dcl-S lFirstTime Ind Inz(*Off);
Dcl-S lLoT Char(256);
Dcl-S lUpT Char(256);

//
// Exported funtions.
// ------------------
//
// LCase
// -----
// Convert a string to lowercase taking national characters into
// account.
//
Dcl-Proc LCase Export;
Dcl-PI LCase VarChar(1024);
  IString VarChar(1024) Const;
End-PI;

Dcl-S oString VarChar(1024);

  If lFirstTime = *Off;
    CallP GetTables();
  EndIf;

  %Len(oString) = %Len(iString);
  oString = %xlate(lBaseT:lLoT:iString);

  Return oString;

End-Proc LCase;
//
// ResetCase
// ---------
// Reset the flag for tables read. To be used if the job CCSID is
// changed for the tables to be recontructed.
//
Dcl-Proc ResetCase Export;
Dcl-PI ResetCase;
End-PI;

  lFirstTime = *Off;

  Return;

End-Proc ResetCase;
//
// UCase
// -----
// Convert a string to uppercase taking national characters into
// account.
//
Dcl-Proc UCase Export;
Dcl-PI UCase VarChar(1024);
  IString VarChar(1024) Const;
End-PI;

Dcl-S oString VarChar(1024);

  If lFirstTime = *Off;
    CallP GetTables();
  EndIf;

  %Len(oString) = %Len(iString);
  oString = %xlate(lBaseT:lUpT:iString);

  Return oString;

End-Proc UCase;
//
// Internal functions.
// -------------------
//
// Convert
// -------
// Internal function that handles call to API for conversion.
//
Dcl-Proc Convert;
Dcl-PI Convert VarChar(1024);
  iString VarChar(1024) Const;
  iUpLow Packed(1:0) Const;
End-PI;

Dcl-PR QlgCvtCase ExtProc('QlgConvertCase');
  CtrlBlock Const LikeDS(FRCB);
  inString Char(32767) Const Options(*VARSIZE);
  OutString Char(32767) Options(*VARSIZE);
  nLength Int(10:0) Const;
  apiError Char(16);
End-PR;

Dcl-S oString Char(32767);
Dcl-S APIErrorDS Char(16) Inz(*AllX'00');

Dcl-DS FRCB Qualified;
  ReqType Int(10:0);
  CCSID Int(10:0);
  CvtTo Int(10:0);
  Reserved Char(10);
End-DS;

Dcl-DS CtrlBlock LikeDS(FRCB);

  CtrlBlock = *Allx'00';
  APIErrorDS = *AllX'00';
  CtrlBlock.ReqType = 1;
  CtrlBlock.CvtTo = iUpLow;

  CallP QlgCvtCase(CtrlBlock:
                   iString:
                   oString:
                   %Len(iString):
                   APIErrorDS);

  Return %SubSt(oString: 1: %Len(iString));

End-Proc Convert;
//
// GetTables
// ---------
// Gets the uppercase and lowercase tables to use with the
// opcode XLate to convert strings to the desired case.
//
Dcl-Proc GetTables;
Dcl-PI GetTables;
End-PI;

  lLoT = Convert(lBaseT: 1);
  lUpT = Convert(lBaseT: 0);

  lFirstTime = *On;

End-Proc GetTables;

Listing 3, member LUCASE_EX, type RPGLE:

**Free
// LUCase_Ex
// ---------
// Example program for use of LCase() and UCase() functions.
//
// Jesper Wachs, July 2018.
//   Converted to Totally Free RPG.
//
// Jesper Wachs, August 2016.
//
// The source member are delivered 'as is'.
//
// Neither Jesper Wachs nor anyone else who has been involved in
// the creation, production or delivery of this product shall be
// liable for any direct, indirect, consequential or incidental
// damages (including damages for loss of business profits,
// business interruption, loss of business information, and the
// like) arising out of the use or inability to use such product
// even if Jesper Wachs has been advised of the possibility of
// such damages.
//
// Copymember for imported functions.
// ----------------------------------
/copy qrpglesrc,lucase_pr

//
// Work fields.
// ------------
Dcl-S Message Char(25);
Dcl-S Reply Char(25);

Message = 'Enter a string:';
Dsply Message '' Reply;

Message = 'Uppercase: ' +
   UCase(Reply);

Dsply Message;

Message = 'Lowercase: ' +
   LCase(Reply);

Dsply Message;

Message = 'Press ENTER';

Dsply Message;

*InLR = *On;
Return;

Listing 4, member LUCASE_EX2, type SQLRPGLE:

**Free
// LUCase_Ex2
// ----------
// Example program for use of LCase() and UCase() functions.
//
// Jesper Wachs, July 2018.
//   Converted to Totally Free RPG.
//
// Jesper Wachs, May 2018.
//
// The source member are delivered 'as is'.
//
// Neither Jesper Wachs nor anyone else who has been involved in
// the creation, production or delivery of this product shall be
// liable for any direct, indirect, consequential or incidental
// damages (including damages for loss of business profits,
// business interruption, loss of business information, and the
// like) arising out of the use or inability to use such product
// even if Jesper Wachs has been advised of the possibility of
// such damages.
//
// Copymember for imported functions.
// ----------------------------------
/copy qrpglesrc,lucase_pr

//
// Prototypes for local functions.
// -------------------------------
Dcl-PR Convert VarChar(1024);
  iString VarChar(1024) Const;
  iUpLow Packed(1:0) Const;
End-PR;

Dcl-PR OldUCase VarChar(1024);
  iString VarChar(1024) Const;
End-PR;

//
// Work fields.
// ------------
Dcl-S Limit Int(10:0) Inz(10000);
Dcl-S Count Int(10:0);
Dcl-S LowStr Char(20) Inz('Some string');
Dcl-S UpStr Char(20);
Dcl-S StartTime TimeStamp;
Dcl-S EndTime TimeStamp;
Dcl-S Interval Int(10:0);
Dcl-S Message Char(25);

//
// RPG Xlate with national support.
//
//  Round 1: The API 'QlgConvertCase' is only called the first time
//           to fill two helper strings that is then used with XLate.
//
StartTime = %TimeStamp();

For Count = 1 to Limit;
  UpStr = UCase(LowStr);
EndFor;

EndTime = %TimeStamp();
Interval = %diff(EndTime: StartTime: *MS);

Message = %Char(Interval) +
  ' microsec Xlate';

Dsply Message;
//
//  Round 2: My old 'UCase' procedure is called. It will call the API
//           'QlgConvertCase' each time to convert the string.
//
StartTime = %TimeStamp();

For Count = 1 to Limit;
  UpStr = OldUCase(LowStr);
EndFor;

EndTime = %TimeStamp();
Interval = %diff(EndTime: StartTime: *MS);
 
Message = %Char(Interval) +
  ' microsec Xlate2';

Dsply Message;
//
// SQL Upper
//
StartTime = %TimeStamp();

For Count = 1 to Limit;
  Exec SQL
    Set :UpStr = Upper(:LowStr);
EndFor;

EndTime = %TimeStamp();
Interval = %diff(EndTime: StartTime: *MS);

Message = %Char(Interval) +
  ' microsec SQL';

Dsply Message;

// End

Message = 'Press ENTER';

Dsply Message;

*InLR = *On;
Return;
//
// Convert
// -------
// Internal function that handles call to API for conversion.
//
Dcl-Proc Convert;
Dcl-PI Convert VarChar(1024);
  iString VarChar(1024) Const;
  iUpLow Packed(1:0) Const;
End-PI;

Dcl-PR QlgCvtCase ExtProc('QlgConvertCase');
  CtrlBlock Const LikeDS(FRCB);
  inString Char(32767) Const Options(*VARSIZE);
  OutString Char(32767) Options(*VARSIZE);
  nLength Int(10:0) Const;
  apiError Char(16);
End-PR;

Dcl-S oString Char(32767);
Dcl-S APIErrorDS Char(16) Inz(*AllX'00');

Dcl-DS FRCB Qualified;
  ReqType Int(10:0);
  CCSID Int(10:0) Inz(0);
  CvtTo Int(10:0);
  Reserved Char(10) Inz(*ALLX'00');
End-DS;

Dcl-DS CtrlBlock LikeDS(FRCB);

  CtrlBlock = *Allx'00';
  APIErrorDS = *AllX'00';
  CtrlBlock.ReqType = 1;
  CtrlBlock.CvtTo = iUpLow;

  CallP QlgCvtCase(CtrlBlock):
                   iString:
                   oString:
                   %Len(iString):
                   APIErrorDS);

  Return %SubSt(oString: 1: %Len(iString));

End-Proc Convert;
//
// OldUCase
// --------
// Convert a string to uppercase taking national characters into
// account.
//
Dcl-Proc OldUCase;
Dcl-PI OldUCase VarChar(1024);
  IString VarChar(1024) Const;
End-PI;

  Return Convert(iString: 0);

End-Proc OldUCase;

If you have installed Jesper’s ToolBox for IBM i you can use the Make member in Listing 4 to compile the module and the example program.

Listing 5, member LUCASE_MK, type TXT:

;
; LUCase_Mk
; ---------
; MAKE member for LCase() and UCase() module and example program.
;
; Jesper Wachs, Version 3, July 2018.
; Jesper Wachs, Version 2, May 2018.
; Jesper Wachs, Version 1, August 2016.
;
;
; Variables defined once and used though out make member.
;
v &lib    jwtools
v &srclib jwtools
v &debug  *source
;
; Check and possibly build LUCase module.
;
v &mod lucase
o &srclib &mod *module
u &srclib qrpglesrc *file &mod
ci dltmod &srclib/&mod
c crtrpgmod &srclib/&mod srcfile(&srclib/qrpglesrc) dbgview(&debug)
;
; Check and possibly build LUCase_Ex module.
;
v &mod lucase_ex
o &srclib &mod *module
u &srclib qrpglesrc *file &mod
ci dltmod &srclib/&mod
c crtrpgmod &srclib/&mod srcfile(&srclib/qrpglesrc) dbgview(&debug)
;
; Check and possibly build LUCase_Ex2 module.
;
v &mod lucase_ex2
o &srclib &mod *module
u &srclib qrpglesrc *file &mod
ci dltmod &srclib/&mod
c crtsqlrpgi &srclib/&mod srcfile(&srclib/qrpglesrc) dbgview(&debug) +
   objtype(*module)
;
; Build LUCase_Ex program.
;
v &pgm LUCase_Ex
o &lib &pgm *pgm
u &srclib lucase_ex *module
u &srclib lucase    *module
ci DltPgm &lib/&pgm
c CrtPgm &lib/&pgm module(&srclib/&pgm &srclib/lucase)
;
; Build LUCase_Ex2 program.
;
v &pgm LuCase_Ex2
o &lib &pgm *pgm
u &srclib lucase_ex2 *module
u &srclib lucase     *module
ci DltPgm &lib/&pgm
c CrtPgm &lib/&pgm module(&srclib/&pgm &srclib/lucase)