El Mínimo Minimorum Que Todo Desarrollador De Software Positiva Y Absolutamente Debe Saber Acerca De Unicode Y Conjuntos De Caracteres. (¡Sin Excusas!)

From The Joel on Software Translation Project

Jump to: navigation, search
Artículo original: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
Fecha de publicación: 8 de octubre de 2003
Traductor: Miguel Farah
Verificado por:
Navegación: Página Principal Volver al idioma Español SpanishFlag.png




¿Alguna vez te has preguntado acerca de ese misterioso tag Content-Type? ¿Ése que supuestamente hay que poner en HTML y que nunca tienes muy claro qué valor debería tener?

¿Has recibido alguna vez un correo electrónico de tus amigos búlgaros con el asunto ("Subject:") "???? ?????? ??? ????"?

Ibm.jpg
Es perturbador descubrir cuántos desarrolladores de software no están al día con lo que deben saber acerca del misterioso mundo de conjuntos de caracteres ("character sets"), codificaciones ("encodings"), Unicode y todo eso. Hace un par de años, un beta tester de FogBUGZ preguntaba si sería capaz de recibir y manejar correo electrónico en japonés. ¿Japonés? ¿Hay correo en japonés? Yo no tenía idea. Cuando miré en detalle el control ActiveX comercial que estábamos usando para analizar correos con componentes MIME, descubrimos que estaba manejando los conjuntos de caracteres de la manera errónea, por lo que tuvimos que escribir código heroico para deshacer la conversión equivocada y rehacerla bien. Cuando miré otra rutinoteca comercial, ésta también tenía una implementación completamente equivocada. Contacté al desarrollador de dicho paquete y éste indicó que creía que "no podía hacer nada al respecto". Como muchos programadores, él deseaba que todo esto se arreglara solo, de alguna manera.

Pero eso no ocurrirá. Cuando descubrí que PHP, la popular herramienta de desarrollo web, tiene una casi completa ignorancia de asuntos de codificación de caracteres, alegremente usando 8 bits para representarlos, y con ello haciendo casi imposible el desarrollar buenas aplicaciones web internacionales, pensé ¡basta!.

Así que tengo un anuncio que hacer: si eres un programador trabajando en el año 2003 y no tienes los conocimientos básicos de caracteres, conjuntos de caracteres, codificaciones y Unicode, y te pillo, te castigaré haciéndote pelar cebollas por seis meses en un submarino. Juro que lo haré.

Y una cosa más:

NO ES TAN DIFÍCIL.

En este artículo te explicaré exactamente lo que todo programador que trabaje como tal debería saber. Todo eso de "texto plano = ascii = caracteres, que tienen ocho bits" no solamente está errado, está irremediablemente equivocado, y si aún estás programando de esa manera, no eres mucho mejor que un médico que no cree en gérmenes. Por favor, no escribas una sola línea de código fuente más hasta que hayas terminado de leer este artículo.

Antes de empezar, debería advertirte que si eres una de esas pocas personas que sabe de internacionalización, vas a encontrar toda esta exposición un tanto sobresimplificada. Estoy tratando de establecer una base mínima para que todos puedan entender qué es lo que ocurre y puedan escribir código que tenga alguna esperanza de funcionar con texto en cualquier idioma que no sea el subconjunto de inglés que no incluye palabras acentuadas. Y debería advertirte que el manejo de caracteres es solamente una pequeña porción de lo que se necesita para crear software que funcione internacionalmente, pero sólo puedo escribir acerca de un tópico a la vez, así que hoy toca conjuntos de caracteres.

Contents

Un poco de historia.

La forma más fácil de entender todo esto es ir cronológicamente.

Probablemente pienses que voy a hablar de conjuntos de caracteres muy antiguos, como EBCDIC. No lo haré. EBCDIC no es relevante para tu vida, y no tenemos que retroceder tanto en el tiempo.

tabla ASCII
En aquella antigua época en que Unix estaba siendo inventado, y K&R estaban escribiendo El Lenguaje de Programación C (The C Programming Language), todo era muy simple. EBCDIC estaba siendo abandonado. Los únicos caracteres que importaban eran las letras del alfabeto inglés (sin acentos ni nada), y teníamos un código para representarlos, llamado ASCII, que era capaz de representar a cada uno utilizando un número entre 32 y 127. El espacio era 32, la letra "A" era 65, etcétera. Estos podían ser convenientemente almacenados en siete bits; la mayor parte de los computadores en esa época utilizaban bytes de ocho bits, por lo que no solamente se podían almacener todos los caracteres ASCII existentes, además sobraba un bit completo, por lo que, si eras retorcido, podías usarlo para tus desviados propósitos: los tontainas en Wordstar lo hicieron, encendiéndolo para indicar la última letra de cada palabra, condenando a Wordstar a manejar solamente texto en inglés. Los códigos inferiores a 32 eran llamandos ininmprimibles y eran usados para maldecir. No, broma. Eran usados para caracteres de control, como el 7, que hacía que el computador emitiera un pitido, y 12, que hacía que la hoja cargada en la impresora saliera volando y hubiese que poner una nueva.

Y todo era bueno, asumiendo que fueses angloparlante.

Oem.png
Como los bytes tienen ocho bits, muchas personas se pusieron a pensar cosas como "Oye, podemos usar los códigos 128-255 para nuestros propósitos.". El problema fue que mucha gente tuvo la misma idea al mismo tiempo, y cada quién tenía sus propias opiniones acerca de qué debería ir dónde en el espacio entre 128 y 255. El IBM-PC tenía algo que llegó a ser conocido como el conjunto de caracteres OEM, que proveía algunos caracteres acentuados para idiomas europeos y un conjunto de caracteres para dibujar líneas: barras horizontales, barras verticales, barras horizontales con pequeñas cositas colgando al lado derecho, etcétera, y podías usar estos caracteres para hacer cajitas y líneas en pantalla, que todavía se pueden ver en acción en ese viejo 8088 en la lavandería. De hecho, tan pronto como se empezaron a vender PCs fuera de Estados Unidos, toda clase de conjuntos de caracteres OEM aparecieron, y todos usaban esos 128 caracteres extra para sus propios propósitos. Por ejemplo, en algunos PCs, el código 130 se desplegaba como "é", pero en los computadores vendidos en Israel, correspondía a la letra hebrea Gimel (Gimel.png), por lo que cuando los estadounidenses enviaban sus currículums (N.T.: "résumé" en inglés), llegaban como "rGimel.pngsumGimel.pngs". En muchos casos, como ocurrió con el ruso, había un montón de distintas ideas acerca de qué hacer con esos 128 caracteres, por lo que no se podía ni compartir confiablemente documentos en el mismo idioma.

Esta anarquía OEM terminó siendo codificada en el estándar ANSI. En él, todos estaban de acuerdo con qué hacer con los códigos bajo 128, que era prácticamente lo mismo que ASCII, pero habían muchas formas distintas de manejar los caracteres 128 y superiores, dependiendo de dónde vivieras. Estos distintos sistemas fueron llamados páginas de códigos ("code pages"). Así, por ejemplo, en Israel MS-DOS usaba la página de códigos 862, mientras que en Grecia se usaba la 737. Eran iguales bajo el código 128, pero diferentes desde el 128 hacia arriba, donde todos los caracteres "raros" residían. Las versiones nacionales de MS-DOS tenían docenas de estas páginas, manejando todo desde inglés hasta islandés, e incluso tenían algunas páginas "multilingües", que podían manejar esperanto y gallego ¡en el mismo computador! ¡Guay! Pero tener, por ejemplo, griego y hebreo a la vez era completamente imposible, a menos que hicieras tu propio programa que desplegara todo usando gráficos, por que estos idiomas requerían distintas páginas de códigos, que interpretaban diferentemente los números altos.

Mientras tanto, en Asia, cosas aún más locas estaban ocurriendo para tomar en cuenta que los alfabetos ideogramas asiáticos tienen miles de caracteres, que jamás iban a caber en ocho bits. Esto usualmente era resuelto mediante el enredado sistema llamado DBCS ("Double Byte Character Set": "conjunto de caracteres de doble byte") en que algunos caracteres eran almacenados en un byte y otros usaban dos. Era fácil avanzar en un string, pero casi imposible retroceder. A los programadores se les recomendaba no usar s++ ni s-- para avanzar y retroceder, y usar en su reemplazo funciones como AnsiNext y AnsiPrev de Windows, que entendían cómo desenmarañar ese enredo.

Con todo, la mayor parte de la gente simplemente pretendía que un byte era un caracter y un caracter tenía ocho bits, y mientras no tuvieses que mover un string de un computador a otro, o hablases más de un idioma, las cosas funcionarían. Por supuesto, tan pronto como apareció la Internet, mover strings de un lugar a otro se convirtió en algo común, y todo el enredo cayó por su propio peso. Afortunadamente, Unicode ya había sido inventado.

Unicode.

Unicode fue un valiente esfuerzo para crear un único conjunto de caracteres que incluyera todos los sistemas de escritura razonables del planeta y también algunos inventados, como el Klingon. Algunas personas creen equivocadamente que Unicode simplemente es un código de 16 bits en que cada caracter usa 16 bits y por lo tanto hay 65.536 caracteres posibles. Esto, de hecho, no es correcto. Este es el mito más común acerca de Unicode, así que si creías eso, no te sientas mal.

De hecho, Unicode tiene una forma distinta de pensar en los caracteres, y debes entenderla, o nada tendrá sentido.

Hasta ahora, hemos asumido que una letra se mapea a un conjunto de bits, que se pueden almacenar en disco o en memoria:

A -> 0100 0001

En Unicode, una letra se mapea a algo llamado punto de código ("code point"), que todavía es un concepto teórico. Cómo ese punto de código vaya a ser representado en memoria o disco es otro cuento.

En Unicode, la letra A es un ideal platónico. Está simplemente flotando por ahí:

A

Esta platónica A es distinta de B y distinta de a, pero idéntica a A, A y A. La idea de que A en la fuente Times New Roman sea el mismo caracter que A en Helvetica, pero diferente de la "a" (minúscula) no parece muy controvertida, pero en algunos idiomas, el determinar qué es una letra puede serlo. ¿Es la letra alemana eszett (ß) una letra real o tan sólo una forma rebuscada de escribir ss? Si la forma de una letra cambia al final de una palabra, ¿es una letra distinta? En hebreo, sí; en árabe, no. De cualquier manera, los cerebros en el consorcio Unicode han estado dilucidando esto durante la última década, acompañados por mucho debate altamente político, y tú no tienes que preocuparte de ello: ellos ya lo han hecho.

El consorcio Unicode asigna un número mágico a cada letra platónica en cada alfabeto, que se escribe así: U+0639. Este número mágico es denominado punto de código. El prefijo U+ significa "Unicode" y los números son hexadecimales. U+0639 es la letra árabe Ain. La letra inglesa A es U+0041. Puedes verlos todos usando el programa charmap en Windows 2000/XP o visitando el sitio web de Unicode.

No hay un límite real al número de letras que Unicode puede definir y, de hecho, han ido más allá de 65.536, por lo que no todas las letras Unicode pueden ser metidas en dos bytes (pero eso era un mito, de cualquier manera).

Vale, digamos que tenemos un string:

Hola

que, en Unicode, corresponde a esta secuencia de cuatro puntos de código:

U+0048 U+006F U+006C U+0061

Simplemente un conjunto de puntos de código. Números, en realidad. Aún no decimos nada acerca de cómo almacenar esto en memoria o representarlo en un correo electrónico.

Codificaciones.

Aquí es donde las codificaciones ("encodings") entran al baile.

La primera idea para codificar Unicode, que creó el mito sobre los dos bytes, fue "Oye, basta con almacenar estos números en dos bytes cada uno.". Así, "Hola" se convierte en

00 48 00 6F 00 6C 00 61

¿Cierto? ¡No tan rápido! ¿No podría también ser:

48 00 6F 00 6C 00 61 00 ?

Técnicamente, sí, creo que podría serlo. De hecho, los primeros implementadores querían poder almacenar sus puntos de código en modo high-endian o low-endian, el que fuese más rápido en su CPU particular: no había pasado ni un día y ya habían dos maneras de almacenar Unicode. La gente se vio forzada a inventar la estrambótica convención de almacenar un FE FF al principio de cada string Unicode; esto es llamado un marcador de orden de bytes ("Unicode Byte Order Mark"); si estás intercambiando tus bytes altos y bajos, se verá como FF FE y la persona que lea tu string sabrá que tiene que intercambiar cada par de bytes. Uf. No todos los strings en Unicode tienen este marcador al principio.

Hummers.jpg

Por un tiempo pareció que con eso bastaría, pero los programadores estaban reclamando: "¡Miren todos esos ceros!", decían, pues eran estadounidenses y veían texto en inglés, que rara vez usa puntos de código superiores a U+00FF. Además, eran hippies liberales procedentes de California, que querían ahorrar (puaj). De haber sido tejanos, no les habría importado gastar el doble de bytes. Pero estos alfeñiques californianos no podían soportar la idea de duplicar el almacenamiento requerido para strings y, además, ya existían todos estos documentos viejos dando vueltas que usaban distintos conjuntos de caracteres ANSI y DBCS y ¿quién se va a dar el trabajo de convertirlos todos? ¿YO? Por este solo motivo la mayor parte de la gente decidió ignorar Unicode durante varios años, y en el intertanto las cosas empeoraron.

Por esto fue inventado el brillante concepto de UTF-8. UTF-8 es otro sistema para almacenar una secuencia de puntos de código Unicode (esos números mágicos U+) en memoria, usando bytes de ocho bits. En UTF-8, cada punto de código entre 0 y 127 es almacenado en un solo byte. Solamente los puntos de código 128 y superiores son almacenados usando dos, tres o hasta seis bytes.

Cómo funciona UTF-8.

Esto tiene el simpático efecto lateral de que el texto en inglés se ve exactamente igual en UTF-8 que en ASCII, por lo que los estadounidenses ni siquiera notan algo raro - solamente el resto del mundo tiene que complicarse. Específicamente, Hola, que era U+0048 U+006F U+006C U+0061, será almacenado como 48 6F 6C 61, que, ¡Oh!, es lo mismo que en ASCII, ANSI y todos los conjuntos de caracteres OEM en el planeta. Ahora bien, si decides usar letras acentuadas o letras griegas o Klingon, tendrás que usar varios bytes para almacenar cada punto de código, pero los gringos nunca se darán cuenta (UTF-8 también tiene la elegante propiedad de que viejo e ignorante código dedicado al procesamiento de strings que quiera usar un solo byte 0 como el terminador ("null-terminator") no truncará los strings).

Hasta aquí te he mostrado tres formas de codificar Unicode. Los métodos tradicionales que almacenan en dos bytes son llamados UCS-2 (por usar dos bytes) o UTF-16 (porque son 16 bits), y además hay que determinar si es UCS-2 high-endian o low-endian. Y está el nuevo y popular estándar UTF-8, que tiene la elegante característica de funcionar bien si tienes texto en inglés y programas descerebrados que no tienen idea que hay codificaciones que no son ASCII.

En realidad hay varias formas más de codificar Unicode. Hay una, llamada UTF-7, que es muy similar a UTF-8, pero que garantiza que el octavo bit siempre sea cero, de modo que si tienes que transferir texto en Unicode a través de algún sistema de correo antediluviano que piensa que 7 bits son suficiente, gracias, igualmente podrá pasar sin daños. También existe UCS-4, que almacena cada punto de código en cuatro bytes, por lo que cada uno de ellos puede ser almacenado usando la misma cantidad de bytes, pero, diantres, ni los tejanos serían tan atrevidos de malgastar tanta memoria.

Ahora que estás pensando en términos de letras platónicas ideales, que son representadas por puntos de código Unicode, estos puntos de código ¡también pueden ser codificados en cualquier esquema de codificación antiguo! Por ejemplo, puedes codificar el string Unicode para "Hola" (U+0048 U+006F U+006C U+0061) en ASCII, o en la antigua codificación OEM griega, o la codificación ANSI hebrea, o cualquiera de los cientos de codificaciones que ya han sido inventados, con un pero: ¡algunas de las letras pueden no aparecer! Si no hay un equivalente para el punto de código Unicode que quieres desplegar en la codificación que estás usando, casi siempre obtendrás un signo de interrogación: ? o, si eres realmente bueno, una cajita. ¿Qué obtuviste? -> �

Hay cientos de codificaciones tradicionales que solamente pueden almacenar algunos puntos de código correctamente y cambian todos los demás a signos de interrogación. Algunas codificaciones populares para texto en inglés son Windows-1252 (el estándar de Windows 9x para idiomas de Europa Occidental) e ISO-8859-1, también conocido como Latin1 (útil, también, para los idiomas mencionados). Pero intenta almacenar letras rusas o hebreas mediante dichas codificaciones y obtendrás un montón de signos de interrogación. UTF-7, 8, 16 y 32 todos tienen la deseable propiedad de ser capaces de almacenar cualquier punto de código correctamente.

El Dato Clave Acerca De Las Codificaciones.

Si olvidas completamente todo lo que acabo de exhibir, por favor recuerda de todas maneras un dato extremadamente importante: no tiene sentido tener un string sin saber qué codificación utiliza. Ya no puedes meter tu cabeza en la arena y pretender que el texto "plano" es ASCII.

El texto plano no existe.

Si tienes un string en memoria, en un archivo o en un correo, tienes que saber qué codificación utiliza o no podrás interpretarlo o desplegarlo a los usuarios correctamente.

Casi todos los estúpidos problemas de tipo "my sitio web se ve como un galimatías" o "ella no puede leer mis correos cuando acentúo las palabras" se reducen a un programador ingenuo que no entendió el simple hecho de que si no me dices si un string dado está codificado usando UTF-8 o ASCII o ISO 8859-1 (Latin1) o Windows-1252, no podré desplegarlo correctamente, o incluso dónde termina. Hay más de cien codificaciones distintas, y no hay forma de saber cuál será la correcta.

¿Cómo conservamos esta información (qué codificación usa un string en particular)? Hay formas estandarizadas de hacerlo. Para un correo electrónico, se espera que incluyas en la cabecera un string como éste:

Content-Type: text/plain; charset="UTF-8"

Para una página web, la idea original era que el servidor web retornara una cabecera http Content-Type, similar a la anterior, con la página misma - no en el contenido HTML per se, sino que como parte de la cabecera de la respuesta que se envía antes de la página HTML.

Esto es problemático. Imagina que tienes un servidor web grande con un montón de sitios y cientos de páginas contribuidas por muchas personas distintas en varios idiomas diferentes, y todas usando la codificación que la copia de Frontpage de cada quién haya decidido generar. El servidor web no sería capaz de realmente saber qué codificación utiliza cada archivo, por lo que no podría enviar la cabecera Content-Type.

Sería conveniente poner el Content-Type del archivo HTML en el archivo HTML mismo, usando alguna clase de tag. Por supuesto, esto volvió loco a los puristas... ¡¿cómo puedes leer el archivo HTML mientras no sabes qué codificación usa?! Afortunadamente, casi todas las codificaciones comúnmente usadas utilizan los caracteres entre 32 y 127 de la misma manera, por lo que se puede leer todo esto en la página HTML antes de empezar a utilizar caracteres raros:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

Pero ese tag meta realmente debe ser lo primero que aparezca en la sección <head>, porque tan pronto como el cliente web lo vea, va a detener el análisis de la página y recomenzará, utilizando la codificación especificada.

¿Qué hacen los clientes web si no encuentran ningún Content-Type, ya sea en la cabecera http o en el tag meta? Internet Explorer hace algo bastante interesante: trata de adivinar, basado en la frecuencia en que ciertos bytes aparecen en textos típicos en codificaciones típicas en varios idiomas, el idioma y la codificación usados. Como las distintas páginas de código antiguas tendían a poner sus letras nacionales en distintos rangos entre 128 y 255, y dado que cada idioma humano tiene un histograma distinto de uso de letras, esto tiene la posibilidad de funcionar. Es verdaderamente extraño, pero funciona lo suficientemente bien para que ingenuos escritores de páginas web que no tienen idea que necesitan la cabecera Content-Type miren sus páginas en un cliente web y se vean bien, hasta que un día escriben algo que no corresponde exactamente a la frecuencia de distribución de letras de su idioma nativo, y entonces Internet Explorer decide que están escribiendo en coreano y despliega esa página en dicho idioma, demostrando, creo yo, que la ley de Postel, esa que dice "Sé conservador en lo que emites y liberal en lo que aceptas.", francamente no es un buen principio de ingeniería. De cualquier manera, ¿qué debe hacer el pobre usuario de este sitio web, que estaba escrito en búlgaro pero aparece en coreano (y ni siquiera en buen coreano)? Debe usar el menú Ver | Codificación e intentar varias codificaciones (hay por lo menos una docena para los idiomas de Europa Oriental) hasta que aparezca la correcta. Si supiese hacerlo, claro - la mayor parte de la gente no sabe.

Rose.jpg

En la versión más reciente de CityDesk, el software para manejo de sitios web, publicado por mi empresa, decidimos hacer todo internamente en Unicode UCS-2 (dos bytes), que es lo que Visual Basic, COM y Windows NT/2000/XP usan en forma nativa con sus strings. En el código C++ simplemente declaramos los strings como wchar_t ("wide char") en vez de char y usamos las funciones wcs en vez de las funciones str (por ejemplo, wcscat y wcslen en vez de strcat y strlen). Para crear un string UCS-2 en código C no hay más que poner una L inmediatamente antes: L"Hola".

Cuando CityDesk publica la página web, la convierte a la codificación UTF-8, que ha sido bien soportada por los clientes web desde hace muchos años. Ésta es la forma en que las 29 versiones localizadas de Joel on Software están codificadas y aún no he recibido una sola queja de alguien que no pueda verlas.

Este artículo está creciendo demasiado, y no puedo cubrir todo lo que se debe saber acerca de codificaciones de caracteres y Unicode, pero espero que si has leído hasta aquí, ya sabrás lo suficiente para volver a la programación, ahora usando antibióticos en vez de sanguijuelas y encantamientos, así que en eso te dejo por ahora.

Personal tools