Notas sobre T.61, UCS-2 y UTF-8


Las cadenas con caracteres no ASCII (vocales acentuadas, ñ, etc.) se suelen representar en el directorio en formato T.61 (teletex). En este formato, una á se representa por dos bytes, donde el primero indica que el segundo lleva un acento; por tanto el segundo byte es el código ASCII del carácter 'a' y el primero (una especie de código de escape) es el código que indica el siguiente carácter lleva un acento, lo cual va expresado por el valor 0xC2. Igualmente el carácter 'ñ' va expresado por dos bytes: uno que indica el siguiente carácter lleva una tilde (0xC4), seguido del carácter 'n'. Así por ejemplo, el nombre Fátima de la Peña, en T.61 queda así:
{T.61}F\c2atima de la Pe\c4na
(para representar los caracteres 0xC2 y 0xC4 es escribe un backslash seguido del código hexadecimal). Así, con {T.61} como prefijo figuran las cadenas con caracteres especiales en el Directorio habitualmente.

Si vamos a pasar los datos de la versión 3 a la 5 de Isode usando ficheros LDIF, generados por el comando ldapsearch, en estos ficheros este tipo de cadenas aparecerán codificados en Base64 (por no ser ASCII). Así, supongamos que tenemos, en el directorio ICR3, un atributo:

cn={T.61}F\c2atima de la Pe\c4na
Si obtenemos el fichero LDIF con el ldapsearch de la Universidad de Michigan compilado con traducción de caracteres a iso-8859-1, en el fichero LDIF aparece la siguiente línea:
cn :: RuF0aW1hIGRlIGxhIFBl8WEK
La secuencia :: tras el nombre del atributo (cn) indica que el valor va codificado en Base64. Podemos ver lo que contiene con el comando:
echo "RuF0aW1hIGRlIGxhIFBl8WEK" | metamail -b -c application/octet-stream -E base64 
o, alternativamente, usando el siguiente script Perl:
#!/usr/local/bin/perl

use MIME::Base64;

while(<>)
{
  $d = decode_base64($_);
  print "$d\n";
}
Si llamamos a este script base64_decod, podemos hacer:
echo "RuF0aW1hIGRlIGxhIFBl8WEK" | base64_decod
En cualquier caso, el resultado es:
Fátima de la Peña
O sea, obtenemos una cadena iso-8859-1 (Latin1). inadecuada para la carga de datos al directorio. Lo correcto para la carga usando el comando dbulkload de tcldish es conseguir cadenas T.61. Para conseguir ese tipo de cadenas, yo utilizé el ldapsearch de OpenLDAP compilado por defecto sin traducción de juegos de caracteres.


El formato UTF-8

El estandar Unicode (actualmente practicamente igual al ISO/IEC 10646-1) define un conjunto de caracteres tan amplio que contempla los usados en la mayoría de los lenguajes escritos, en total miles de caracteres. A cada uno de esos caracteres le asigna un código numérico. Puesto que son miles, no es suficiente con un byte por carácter. Al conjunto de caracteres representado se le denomina Universal Character Set (UCS). Puesto que no llegan a 64K caracteres, bastan 16 bits para representarlos todos, aunque el estandar ISO/IEC 10646-1 define una forma de representar aún más caracteres usando 32 bits (esto último no está contemplado en Unicode). A la codificación con 16 bits se la llama UCS-2, y a la de 32, UCS-4. A la serie de los primeros 64K caracteres se le llama BMP (Basic Multilingual Plane).

Lo dicho re refiere a la asignación de códigos numéricos a los distintos caracteres, pero otro tema es el de cómo representar esos códigos en un fichero o en la memoria de un ordenador. Hay varias formas. Lo lógico parece ser almacenar el código binario de cada carácter. Así el carácter que tiene asignado el código 112 sería (usando UCS-2),

00000000 01110000
Esto plantea muchos problemas. Uno muy importante es que los programas existentes no podrían manipular estos ficheros con dos bytes por cada carácter. Además se duplicaría el tamaño del fichero. Una alternativa es no usar un número fijo de bytes para cada carácter (dos en el caso de UCS-2, cuatro para UCS-4), sino un número de ellos dependiendo del código de carácter. Así, a los caracteres del código ASCII (de 7 bits) se les asigna un único byte (cuyo valor es igual a su código ASCII), siendo en este caso el bit más alto igual a 0. El siguiente rango de caracteres (hasta un número determinado) se representa por dos bytes, comenzando ambos por secuencias determinadas, en este caso 110 y 10, respectivamente, lo cual deja 5+6=11 bits para representar el código del carácter. Como hay aún más caracteres, la cosa sigue con 3, 4 y más bytes. El mecanismo es semejante al usado en el protocolo IP para distinguir el tipo de red (A, B, ...) según el valor de los bits más altos de su número.

Tomemos como ejemplo el carácter á, con un código Unicode de 225 (igual que en Latin1 o iso-8859-1, ya que los primeros 256 códigos de Unicode son iguales a los de este juego de caracteres). En binario es:

11100001
En UTF-8 está representado por dos bytes:
11000011 10100001
Donde vemos que si quitamos los bits indicadores (110 y 10) nos queda 000011100001, que es su código Unicode.

A estas formas de codificar los caracteres UCS se le llama UTF (UCS transformation format) y existen varias. La forma descrita es la llamada UTF-8, explicada en el rfc2279.

Una ventaja importante de esta codificación es que si un fichero sólo contiene caracteres ASCII (como es habitual en los paises anglosajones), el fichero UTF-8 resultante será puramente ASCII; si es generado con un software Unicode usando esta codificación, el fichero se podrá cargar en cualquier programa no-Unicode sin problemas. Más aún, no existe ningún byte con valor 0 en un fichero de este tipo (salvo el carácter 0), como ocurriría si usáramos dos bytes por carácter (ver el ejemplo anterior del código 112), lo cual provocaría un caos en la mayoría de los programas de procesamiento de ficheros de texto.

Una forma sencilla de hacer transformaciones de o hacia UTF-8 es el paquete de Perl llamado Unicode::String. El código UTF-8 de á del ejemplo anterior se obtuvo pasando por od la salida de:
  #!/usr/local/bin/perl
  use Unicode::String qw(latin1);
  $u = latin1( "á");
  print $u, "\n";

NOTA

La versión '88 de X.500 (en la que está basado ICR3) sólo permite codificar los caracteres no ASCII en valores de atributos en T.61 (teletex). La versión '93 (en la que se basa ICR5) permite también los tipos BMPString y UniversalString. El primero de ellos usa dos bytes por carácter y el segundo, cuatro (recordemos la definición de BMP anterior). En ICR5, se usan los prefijos {UCS-2} y {UCS-4} respectivamente para esos tipos (ver la página 284 del manual IC-1107).


Luis Meléndez - Servicio de Informática, Universidad de Córdoba.
Noviembre, 1999