¿Has necesitado alguna vez, en un programa CL, convertir a mayúsculas una cadena?
No ha sido fácil, ¿verdad? OS/400 no dispone de un mandato CL con el que se pueda
transformar una variable o cadena alfanumérica a mayúsculas o minúsculas.
Sin embargo, en RPG sí que se dispone de la operación XLATE
o de la función
incorporada %xlate()
y el sistema operativo también nos proporciona la
API
Convert Case (QLGCNVCS, QlgConvertCase) para realizar este tipo de conversiones.
En este artículo os presento los mandatos UPPERCASE y LOWERCASE que pretenden subsanar esta pequeña deficiencia, es decir, que se pueda convertir a mayúsculas o minúsculas una cadena de caracteres desde un programa CL de forma sencilla. Ambas utilidades admiten dos parámetros: el primero es la expresión o variable alfanumérica que se quiere transformar y el segundo es la variable que contendrá el resultado.
Su uso es muy simple, el siguiente fragmento de código CL es un ejemplo de ello:
Dcl &var1 *char 30 ('todo en mayúsculas')
Dcl &var2 *char 64
Dcl &var3 *char 64
Dcl &tipo *char 10 ('dsPf')
UpperCase Value( &var1 ) ToVar( &var2 )
LowerCase Value( 'EXPRESIÓN ALFANUMÉRICA' ) +
ToVar( &var3 )
UpperCase Value( &tipo ) ToVar( &tipo )
If (&tipo = 'DSPF') Do
/* ... */
En el primer caso se convierte a mayúsculas el contenido de una variable y se deja el resultado en otra de diferente tamaño. Ambos mandatos saben gestionar correctamente esta circunstancia ya que conocen en todo momento la longitud real de las dos variables.
En el segundo caso se transforma una expresión fija a minúsculas. Los mandatos transforman correctamente las vocales o consonantes con cualquier símbolo especial típicos de las lenguas latinas o del norte de Europa (vocales acentudas, acentos circunflejos, etc.).
En ocasiones es necesario ajustar algún valor de entrada o salida para adecuarlo a un formato estandarizado. En el último caso del ejemplo anterior, se pretende comprobar si la variable &tipo tiene el valor "DSPF", aunque contenga cualquier combinación de mayúsculas y minúsculas de estas cuatro letras. Valores correctos serán: "dspf", "Dspf" o "dsPf" entre otros. En este caso, el resultado se almacena en la misma variable de entrada.
El código fuente de estos mandatos se puede descargar desde la sección de archivos. Encontrarás en el CL de instalación (programa CASECMDI) los mandatos para su compilación.
Algunas técnicas de programación
En la implementación de estas utilidades se han empleado algunas técnicas de programación que considero interesantes explicar y que están relacionadas con la definición de los mandatos o con el uso de las APIs del sistema.
Parámetros como constantes
Los mandatos UPPERCASE y LOWERCASE comparten el mismo programa procesador de mandatos (el programa ULCASEC) ya que los dos tienen los mismos parámetros y la lógica de conversión es casi indéntica en ambos casos. Se podría haber construido un solo mandato con un parámetro que indicara cómo transformar el valor de entrada, pero en esta ocasión, y por claridad, se ha optado por utilizar dos nombres distintos de mandato.
El programa procesador del mandato espera tres parámetros, uno de entrada con la expresión a convertir, otro de salida con el resultado y un tercero indica el tipo de transformación. Se pretende que este último no sea percibido por el usuario.
En una definición de mandato se pueden utilizar parámetros como constantes. Esto significa que se puede especificar un valor para el parámetro directamente en la defición del mandato. Además, no se muestra en la pantalla de solicitud del mandato, trasladando directamente su valor al parámetro correspondiente del programa procesador. Por ejemplo, en el código de UPPERCASE se observa el siguiente fragmento:
PARM KWD(OPTION) +
TYPE(*INT2) +
CONSTANT(0)
Se define el parámetro OPTION con el tipo de datos esperado por el programa procesador y se le pasa un valor 0 (cero) como constante. Para estas utilidades un 0 (cero) indica conversión a mayúsculas y un 1 (uno) a minúsculas.
Parámetros de longitud variable
UPPERCASE y LOWERCASE se tienen que utilizar con cadenas de distintos tamaños, tanto en el parámetro de entrada como en el de salida. El primero bastaría con definirlo del siguiente modo:
PARM KWD(VALUE) +
TYPE(*CHAR) LEN(4096) +
MIN(1) +
EXPR(*YES) +
CASE(*MONO) +
PROMPT('Valor')
Dada una variable de cualquier tamaño en el parámetro VALUE, al llamar al mandato el programa procesador siempre verá una variable de 4096 bytes. Sin embargo, en estas utilidades sería interesante conocer exactamente qué longitud tienen los valores de entrada. Se mejoraría el tiempo de ejecución al convertir sólo lo necesario y se podrían realizar controles internos para evitar corromper posiciones de memoria. La siguiente definición de parámetro es una variación de la anterior:
PARM KWD(VALUE) +
TYPE(*CHAR) LEN(4096) +
MIN(1) +
EXPR(*YES) +
VARY(*YES *INT2) +
CASE(*MONO) +
PROMPT('Valor')
Al utilizar la palabra clave VARY(*YES) se está indicando al programa procesador que junto al valor del parámetro irá pegada la longitud de éste (en formato binario). En esta ocasión, sólo son necesarios dos bytes (*INT2) al principio de la cadena, pero si se requieren podrían ser cuatro.
El parámetro de salida (TOVAR) difiere un poco del anterior, su tamaño máximo ahora es de un solo carácter:
PARM KWD(TOVAR) +
TYPE(*CHAR) LEN(1) +
RTNVAL(*YES) +
MIN(1) +
VARY(*YES *INT2) +
PROMPT('Var CL para datos convertidos')
Este tipo de parámetros tienen una peculiaridad y es que el sistema se tiene que asegurar que, al menos, se pase al programa procesador una variable de un tamaño mayor o igual al especificado en el mandato. Si se hubiera indicado una longitud de 4096 (LEN(4096)) el usuario estaría obligado a utilizar variables de este tamaño en el parámetro de salida. Al definirlo de un sólo caracter de longitud es válido utilizar cualquier variable como receptora del resultado, sea cual sea su tamaño. Esta circunstancia implica la necesidad de conocer la logintud real de la variable receptora, así que aquí se emplea otra vez la opción VARY(*YES) en el parámetro.
La lista de parámetros del programa procesador del mandato se define de la siguiente forma:
D Value_T S 4096A Varying
D Based( Type )
D ULCASEC PR Extpgm( 'ULCASEC' )
D value Const Like( Value_T )
D toVar Like( Value_T )
D o_option Const Like( TypeSmallInt )
D Options( *NOPASS )
Tanto value como toVar se definen de tipo alfanumémericas de
longitud variable (palabra clave Varying). Con este tipo de dato se añaden
dos bytes extras al inicio de la cadena para almacenar la longitud real de ésta, el
tamaño total de estos parámetros es de 4098 bytes. Aunque para el programador
el manejo de estas variables es transparente, es posible obtener y manipular la longitud de la
cadena con la función integrada de RPG %LEN()
. La estructura de
estos dos parámetros encaja con la especificación PARM del mandato. Dentro del
programa se está muy pendiente del tamaño real de cada variable para no
corromper zonas de memoria.
Una API para la conversión
Aunque existen varias fórmulas para realizar una conversión a mayúsculas o minúsculas, en esta ocasión se ha optado por el uso de la API Convert Case (QLGCNVCS, QlgConvertCase), que es enlazable y soporta los distintos juegos de caracteres (CCSID) suministrados con el sistema. Esta es su gran ventaja, ya que en los idiomas latinos o del norte de Europa (entre otros) se utilizan símbolos especiales en vocales y consonantes.
Esta API es una de las más sencillas de usar. Tiene sólo cinco parámetros: el Bloque de Control, el valor a convertir, el convertido, el tamaño de los datos a convertir y el código de error por si falla algo. El prototipo en RPG de la API es el siguiente:
D QlgConvertCase PR ExtProc( 'QlgConvertCase' )
D reqCtlBlk Const Like( NLS_reqCtlBlk_T )
D Options( *VARSIZE )
D inpDta Const Like( TypeBuffer2 )
D Options( *VARSIZE )
D outDta Like( TypeBuffer2 )
D Options( *VARSIZE )
D lenDta Const Like( TypeInt )
D error Like( TypeApiError )
D Options( *VARSIZE )
El Bloque de control (reqCtlBlk) es el más interesante de todos y su función consiste en indicar a la API cual debe ser su comportamiento. En estos mandatos la transformación que se ha elegido es en función del CCSID del trabajo y, por tanto, la estructura del Bloque de control tiene el siguiente aspecto:
D NLS_reqCtlBlk_ccsid_T...La conversión se realiza en función de varios factores:
D DS Based( apityp_ )
D Qualified
D reqType 10I 0
D ccsid 10I 0
D caseReq 10I 0
D 10A
- El tipo de petición (reqType) especifica la estructura que tendrá el bloque de control y define qué se usa para la transformación. En este caso se utiliza el formato CCSID (valor 1).
- El juego de caracteres codificado (ccsid) indica el CCSID de los datos de entrada y determina cómo se hará la conversión. Para estas utilidades se emplea el CCSID del trabajo (valor 0).
- El tipo de conversión (caseReq) informa a la API si se quiere transformar el valor de entrada a mayúsculas (valor 0) o minúsculas (valor 1). Este campo viene determinado por el tercer parámetro del programa procesador.
En el siguiente código se ilustra cómo se inicializa el bloque de control y la llamada a la API de conversión:
/Copy Api,Nls_H
// ...
D rcb DS LikeDs( NLS_reqCtlBlk_ccsid_T )
// ...
// Configurar el bloque de control
rcb = *ALLx'00';
rcb.reqType = 1; // Conversión por CCSID
rcb.ccsid = 0; // Usar el CCSID del trabajo
rcb.caseReq = opcion;
// ...
// Convertir los datos
QlgConvertCase( rcb
: %Subst( value: 1: minLen )
: buffer: minLen
: error
);
El programa procesador de los mandatos es muy sencillo y muy fácil de seguir su ejecución con el depurador. No creo que requiera de más explicaciones sobre su funcionamiento, sólo aclarar que para compilarlo serán necesarios los miembros fuentes de copia contenidos en el archivo api-3.0_final-20100906.zip donde se declaran los prototipos y estructuras de datos de las APIs utilizadas.