miércoles, 21 de mayo de 2014

Charsets y Codepages. UNICODE

No siempre tenemos que preocuparnos de la codificación de caracteres de los ficheros que abrimos y cerramos en R/3, pero en ocasiones sí. Por ejemplo, cuando destinados a un proyecto en Japón debemos hacer una carga de clientes con nombres etruscos.

Si realmente no estás familiarizado/a con el concepto de juegos de caracteres, es muy importante superar el mito extendidísimo de que un caracter de texto ocupa siempre un byte. Eso es cierto para la mayoría de textos en inglés que no usan caracteres extraños, o cuando utilizamos textos de windows y nuestro SAPGUI está enterado de por donde se mueve. Pero no siempre ocurre así.
Podría extenderme sobre las diversas variaciones de juegos de caracteres, sistemas de codificación, etc… pero no lo voy a hacer porque ya hay un estupendo artículo que explica en que consisten y, esencial, porque llegaron a ser como son: Lo Absolutamente Mínimo que cada Desarrollador de Software, Absoluta y Positivamente, Debe Saber sobre Unicode y Juegos de Caracteres. Este texto, muy clarificador, unido a este FAQ sobre UTF, debería ser bastante para entender lo esencial sobre este asunto.

El código que adjunto es una demostración de cómo se pueden utilizar objetos ABAP para convertir textos de unos sistemas de codificación a en otros. Para facilitar las pruebas, hago uso de la interesante función SCMS_STRING_TO_XSTRING para transformar el texto del parámetro de entrada del report en xstring. ¿Por qué?
Obviamente no podemos trabajar con strings puros, ya que de ese modo su contenido real, sus bytes, permanecen opacos a nuestra vista durante su tratamiento. Por eso se hace absolutamente necesario el uso de xstrings, con lo que a la hora de cargar y grabar ficheros codificados de modo extraño lo haremos en modo binario.
Es importante señalar que ABAP no utiliza los diversos nombres y alias de los distintos juegos de caracteres para realizar las conversiones, ya que utiliza un código numérico interno para definirlos. Existe una función que traduce, más bien intenta, los nombres estándar a estos códigos internos. Pero funciona tan horrorosamente mal y es tan poco práctica que ni la menciono. Es mucho mejor mirar directamente el contenido de la tabla TCP00, cruzar los dedos y deducir, a base de pruebas (este mismo programa puede ayudar) cual es el código numérico que necesitamos. Digo lo de las pruebas porque las descripciones son espantosas y muy poco clarificadoras.
Como curiosidad: el programa RSCPINST muestra los juegos de caracteres utilizados en SAPGUI, servidor y base de datos. Quizás sirva de ayuda.

A continuacion muestro un pequeño ejemplo:

REPORT  zzcharsetsycodepages NO STANDARD PAGE HEADING.
"As seen on http://cranf.com

DATA windows1252 TYPE xstring. "xcadena en windows1252
DATA utf8 TYPE xstring. "xcadena en UTF-8
DATA utf16 TYPE xstring. "xcadena en UTF-16
DATA conversionutf8 TYPE REF TO cl_abap_conv_x2x_ce. "instancia de la conversión
DATA conversionutf16 TYPE REF TO cl_abap_conv_x2x_ce. "instancia de la conversión
DATA longitud TYPE i. "lo necesitamos, queramos o no

"parámetro de entrada
PARAMETERS cadena TYPE string LOWER CASE DEFAULT 'España Cañí y Olé'.

"**********************************************************************
"en un primer paso tiramos de la función de conversión de string en xstring,
"pasándole el código interno de charset ABAP (están en la tabla TCP00)
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text     = cadena
    encoding = '1160' "windows1252, véase tabla TCP00 para deducir número
  IMPORTING
    buffer   = windows1252
  EXCEPTIONS
    failed   = 1
    OTHERS   = 2.
"esta función nos puede bastar para conversión de un string en la cadena hexadecimal
"con el charset que nos dé la gana. estupenda, ¿verdad?
"pero ahora vamos a pasar de un xstring a otro xstring con distinta codificación
"instanciamos primero el objeto conversor


TRY.
    CALL METHOD cl_abap_conv_x2x_ce=>create
      EXPORTING
        in_encoding  = '1160' "windows1252
        ignore_cerr  = abap_false
        out_encoding = '4110' "código utf-8, véase tabla TCP00
        input        = windows1252
      RECEIVING
        conv         = conversionutf8. "es la instancia de un objeto, ojo!
  CATCH cx_parameter_invalid_type .
  CATCH cx_parameter_invalid_range .
  CATCH cx_sy_codepage_converter_init .
ENDTRY.

"convertimos occidental a UTF-8
TRY.
  CALL METHOD conversionutf8->convert_c
    IMPORTING
      len = longitud.
ENDTRY.

"y obtenemos el valor UTF8 como xstring
CALL METHOD conversionutf8->get_out_buffer
  RECEIVING
    buffer = utf8.

"**********************************************************************
"para rizar el rizo, pasamos de UTF8 a UTF16 little endian
TRY.
    CALL METHOD cl_abap_conv_x2x_ce=>create
      EXPORTING
        in_encoding  = '4110' "código utf-8, véase tabla TCP00
        ignore_cerr  = abap_false
        out_encoding = '4103' "utf-16 little endian (4102 sería big endian)
        out_endian   = 'L' "indica opcionalmente big endian / little endian
        input        = utf8
      RECEIVING
        conv         = conversionutf16. "instancia del objeto
  CATCH cx_parameter_invalid_type .
  CATCH cx_parameter_invalid_range .
  CATCH cx_sy_codepage_converter_init .
ENDTRY.

"hacemos la conversión de UTF8 a UTF16
TRY.
  CALL METHOD conversionutf16->convert_c
    IMPORTING
      len = longitud.
ENDTRY.

"obtenemos finalmente el valor UTF16 como xstring
CALL METHOD conversionutf16->get_out_buffer
  RECEIVING
    buffer = utf16.

"**********************************************************************
"y aquí los resultados
WRITE: / 'String      :', cadena.
WRITE: / 'windows1252 :', windows1252.
WRITE: / 'UTF-8       :', utf8.
WRITE: / 'UTF-16      :', utf16.


El resultado que obtenemos es el siguiente:

String      : España Cañí y Olé
windows1252 : 45737061F161204361F1ED2079204F6CE9
UTF-8       : 45737061C3B161204361C3B1C3AD2079204F6CC3A9
UTF-16      : 4500730070006100F1006100200043006100F100ED002000790020004F006C00E900

No hay comentarios:

Publicar un comentario