El Mínimo Absoluto Todo Desarrollador de Software Absoluta y Positivamente Debe Saber Sobre Unicode y los Juegos de Caracteres (Sin Excusas!)

11. julio 2019 Sin categoría 0

He aquí una traducción que he realizado de un fantástico artículo realizado por Joel Spolsky (CEO y cofundador de Stack Overflow) y que podéis encontrar en su versión original aquí , espero que le déis la importancia que tiene por que, como bien reza el título, de esto hay que saber si o sí. Ahi va.

El Mínimo Absoluto Todo Desarrollador de Software Absoluta y Positivamente Debe Saber Sobre Unicode y los Juegos de Caracteres (Sin Excusas!)

¿Alguna vez te has preguntado acerca de esa misteriosa etiqueta Content-Type? Ya sabes, el que se supone que debes poner en HTML y nunca sabes bien lo que debería ser?

¿Recibiste alguna vez un correo electrónico de tus amigos de Bulgaria con el asunto "? ?????? ??? ????” ?

Me ha consternado descubrir cuántos desarrolladores de software no están completamente al día en el misterioso mundo de los juegos de caracteres, codificaciones, Unicode, todo eso. Hace un par de años, un probador beta de FogBUGZ se preguntaba si podía manejar el correo electrónico entrante en japonés. ¿Japonés? ¿Tienen correo electrónico en japonés? No tenía ni idea. Cuando miré de cerca el control ActiveX comercial que usábamos para analizar los mensajes de correo electrónico MIME, descubrimos que estaba haciendo exactamente lo incorrecto con los juegos de caracteres, así que tuvimos que escribir código heroico para deshacer la conversión incorrecta que había hecho y rehacerla correctamente. Cuando busqué en otra biblioteca comercial, también tenía una implementación de código de caracteres completamente mal. Me escribí con el desarrollador de ese paquete y él pensó que «no podían hacer nada al respecto». Como muchos programadores, deseaba que todo se resolviera de alguna manera.

Pero no lo hará. Cuando descubrí que la popular herramienta de desarrollo web PHP tiene una ignorancia casi completa de los problemas de codificación de caracteres, usando alegremente 8 bits para los caracteres, haciendo casi imposible el desarrollo de buenas aplicaciones web internacionales, pensé, ya es suficiente.

Así que tengo un anuncio que hacer: si eres un programador que trabaja en 2003 y no conoces los conceptos básicos de caracteres, conjuntos de caracteres, codificaciones y Unicode,  te voy a coger y te voy a castigar haciéndote pelar cebollas durante 6 meses en un submarino. Juro que lo haré.

Y una cosa más:

NO ES TAN DIFÍCIL

En este artículo le explicaré exactamente lo que todo programador de trabajo debe saber. Todo eso de «texto plano = ascii = los caracteres son de 8 bits» no sólo está mal, sino que es totalmente erróneo, y si sigues programando de esa manera, no eres mucho mejor que un médico que no cree en los gérmenes. Por favor, no escribas otra línea de código hasta que termines de leer este artículo.

Antes de empezar, debo advertirte que si eres una de esas personas raras que sabe de internacionalización, vas a encontrar toda mi discusión un poco simplificada. Realmente estoy tratando de establecer un tope mínimo aquí para que todos puedan entender lo que está pasando y puedan escribir código que tenga la esperanza de trabajar con texto en cualquier otro idioma que no sea el subconjunto de inglés que no incluye palabras con acentos. Y debo advertirte que el manejo de caracteres es sólo una pequeña porción de lo que se necesita para crear software que funcione internacionalmente, pero sólo puedo escribir sobre una cosa a la vez, así que hoy en día son los juegos de caracteres.

Una perspectiva histórica

La manera más fácil de entender estas cosas es ir cronológicamente.

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

En los viejos tiempos, cuando Unix estaba siendo inventado y K&R estaba escribiendo El Lenguaje de Programación C, todo era muy simple. EBCDIC estaba a punto de salir. Los únicos caracteres que importaban eran las buenas y antiguas letras inglesas sin acento, y teníamos un código para ellas llamado ASCII que era capaz de representar cada carácter usando un número entre 32 y 127. El espacio era 32, la letra «A» era 65, etc. Esto podría ser convenientemente almacenado en 7 bits. La mayoría de las computadoras en esos días usaban bytes de 8 bits, así que no sólo podías almacenar todos los caracteres ASCII posibles, sino que tenías un poco de sobra, que, si eras malvado, podías usar para tus propios propósitos malvados: las bombillas tenues de WordStar en realidad encendían el bit más alto para indicar la última letra de una palabra, condenando a WordStar al texto en inglés solamente. Los códigos por debajo de 32 se llamaban no imprimibles y se usaban para maldecir. Sólo bromeaba. Se usaban para los caracteres de control, como 7 que hacían sonar el sonido de su ordenador y 12 que hacían que la página de papel actual saliera volando de la impresora y que se introdujera una nueva.

Y todo estaba bien, asumiendo que hablabas inglés.

Debido a que los bytes tienen espacio para hasta ocho bits, mucha gente se puso a pensar, «Dios, podemos usar los códigos 128-255 para nuestros propios propósitos». El problema era que mucha gente tenía esta idea al mismo tiempo, y tenían sus propias ideas de qué debía ir a dónde en el espacio de 128 a 255. El IBM-PC tenía algo que se conocía como el juego de caracteres OEM que proporcionaba algunos caracteres acentuados para los idiomas europeos y un montón de caracteres de dibujo de líneas… barras horizontales, barras verticales, barras horizontales con pequeños ángulos colgando del lado derecho, etc., y se podían usar estos caracteres de dibujo de líneas para hacer cajas y líneas en la pantalla, que todavía se pueden ver corriendo en el ordenador 8088 en la tintorería. De hecho, tan pronto como la gente comenzó a comprar PCs fuera de Estados Unidos, se inventaron todo tipo de juegos de caracteres OEM, todos los cuales utilizaban los 128 mejores personajes para sus propios fines. Por ejemplo, en algunos PC el código de caracteres 130 aparecería como é, pero en los ordenadores vendidos en Israel era la letra hebrea Gimel (ג), por lo que cuando los estadounidenses enviaban su currículum a Israel llegaban como rגsumגs En muchos casos, como el ruso, había muchas ideas diferentes sobre qué hacer con los caracteres superiores a 128, por lo que ni siquiera se podían intercambiar documentos rusos de forma fiable.

Eventualmente este OEM free-for-all fue codificado en el estándar ANSI. En el estándar ANSI, todo el mundo estaba de acuerdo en qué hacer por debajo de 128, que era más o menos lo mismo que ASCII, pero había muchas maneras diferentes de manejar a los personajes de 128 en adelante, dependiendo de dónde vivías. Estos diferentes sistemas se llamaban páginas de código. Así, por ejemplo, en Israel DOS utilizó una página de código llamada 862, mientras que los usuarios griegos utilizaron 737. Eran las mismas por debajo de 128 pero diferentes de 128 en adelante, donde residían todas las letras graciosas. Las versiones nacionales de MS-DOS tenían docenas de estas páginas de código, manejando desde el inglés hasta el islandés, e incluso tenían algunas páginas de código «multilingües» que podían hacer esperanto y gallego en la misma computadora. Wow! Pero obtener, digamos, hebreo y griego en la misma computadora era completamente imposible a menos que escribieras tu propio programa personalizado que mostrará todo usando gráficos en mapa de bits, porque el hebreo y el griego requerían páginas de código diferentes con diferentes interpretaciones de los números altos.

Mientras tanto, en Asia, aún más locuras estaban pasando para tener en cuenta el hecho de que los alfabetos asiáticos tienen miles de letras, que nunca iban a caber en 8 bits. Esto se solucionaba normalmente con el desordenado sistema llamado DBCS, el «juego de caracteres de doble byte» en el que algunas letras se almacenaban en un byte y otras en dos. Era fácil avanzar en una cadena de texto, pero casi imposible retroceder. Se animó a los programadores a no utilizar s++ y s- para retroceder y avanzar, sino para llamar a funciones como AnsiNext y AnsiPrev de Windows, que sabían cómo manejar todo este lío.

Pero aún así, la mayoría de la gente sólo fingía que un byte era un carácter y que un carácter era de 8 bits, y mientras no se moviera una cadena de un ordenador a otro, o se hablara más de un idioma, siempre funcionaría. Pero, por supuesto, tan pronto como se produjo Internet, se convirtió en un lugar bastante común para mover las cadenas de un ordenador a otro, y todo el lío se vino abajo. Afortunadamente, Unicode había sido inventado.

Unicode

Unicode fue un valiente esfuerzo para crear un único juego de caracteres que incluía todos los sistemas de escritura razonables del planeta y algunos de fantasía como el Klingon, también. Algunas personas están bajo el concepto erróneo de que Unicode es simplemente un código de 16 bits donde cada carácter tiene 16 bits y por lo tanto hay 65.536 caracteres posibles. Esto no es, en realidad, correcto. Es el mito más común sobre Unicode, así que si pensaste eso, no te sientas mal.

De hecho, Unicode tiene una forma diferente de pensar acerca de los personajes, y tienes que entender la forma de pensar de Unicode de las cosas o nada tendrá sentido.

Hasta ahora, hemos asumido que una letra se mapea a unos bits que se pueden almacenar en el disco o en la memoria:

A -> 0100 0001

En Unicode, una letra se mapea a algo llamado punto de código que sigue siendo sólo un concepto teórico. Cómo se representa ese punto de código en la memoria o en el disco es toda una historia.

En Unicode, la letra A es un ideal platónico. Está flotando en el cielo:

A

Esta A platónica es diferente de B, y diferente de a, pero igual que A y A y A. La idea de que A en una fuente Times New Roman es el mismo carácter que la A en una fuente Helvética, pero diferente de «a» en minúsculas, no parece muy polémica, pero en algunos idiomas el simple hecho de averiguar qué es una letra puede causar controversia. ¿Es la letra alemana ß una letra real o sólo una forma elegante de escribir ss? Si la forma de una letra cambia al final de la palabra, ¿es una letra diferente? El hebreo dice que sí, el árabe dice que no. De todos modos, la gente inteligente del consorcio Unicode ha estado descubriendo esto durante la última década más o menos, acompañado de un gran debate altamente político, y usted no tiene que preocuparse por ello. Ya se han dado cuenta de todo.

Cada letra platónica en cada alfabeto es asignada un número mágico por el consorcio Unicode que se escribe así: U+0639.  Este número mágico se llama punto de código. El U+ significa «Unicode» y los números son hexadecimales. U+0639 es la letra árabe Ain. La letra A en inglés sería U+0041. Puede encontrarlos todos usando la utilidad de mapas de caracteres en Windows 2000/XP o visitando el sitio web de Unicode.

No hay un límite real en el 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 comprimidas en dos bytes, pero eso era un mito de todos modos.

Bien, digamos que tenemos un texto:

Hello

que, en Unicode, corresponde a estos cinco puntos de código:

U+0048 U+0065 U+006C U+006C U+006F.

Sólo un puñado de puntos de código. Números, en realidad. Todavía no hemos dicho nada sobre cómo almacenar esto en la memoria o cómo representarlo en un mensaje de correo electrónico.

Codificaciones (encoding)

Ahí es donde entran en juego las codificaciones.

La primera idea para la codificación Unicode, que llevó al mito de los dos bytes, fue, oye, vamos a almacenar esos números en dos bytes cada uno. Así que Hello se convierte en

00 48 00 65 00 6C 00 6C 00 6F

¿Verdad? ¡No tan rápido! ¿No podría serlo también?

48 00 65 00 6C 00 6C 00 6F 00 ?

Bueno, técnicamente, sí, creo que podría, y, de hecho, los primeros implementadores querían ser capaces de almacenar sus puntos de código Unicode en modo alto (high-endian) o bajo (low-endian), cualquiera que fuera su CPU más rápida, y he aquí que era por la tarde y era por la mañana y ya había dos formas de almacenar Unicode. Así que la gente se vio obligada a inventar la extraña convención de almacenar un FF de FE al principio de cada cadena Unicode; esto se llama marca de pedido de bytes Unicode y si está intercambiando sus bytes altos y bajos, parecerá un FF FE y la persona que lea su cadena sabrá que tiene que intercambiar cada otro byte. Uf. No todas las cadenas Unicode tienen una marca de orden de bytes al principio.

Durante un tiempo pareció que eso era suficiente, pero los programadores se quejaban. «Miren todos esos ceros», dijeron, ya que eran americanos y estaban mirando un texto en inglés que raramente usaba puntos de código por encima de U+00FF. También eran hippies liberales en California que querían conservar (burlarse). Si fueran tejanos no les habría importado engullir el doble de bytes. Pero esos californianos no podían soportar la idea de duplicar la cantidad de almacenamiento que se necesita para las cadenas de texto, y de todos modos, ya existían todos estos documentos utilizando varios juegos de caracteres ANSI y DBCS y ¿quién los va a convertir a todos? ¿Moi? Sólo por esta razón, la mayoría de la gente decidió ignorar Unicode durante varios años y mientras tanto las cosas empeoraron.

Así se inventó el brillante concepto de UTF-8. UTF-8 era otro sistema para almacenar su cadena de puntos de código Unicode, esos mágicos números U+, en memoria usando bytes de 8 bits. En UTF-8, cada punto de código de 0-127 se almacena en un solo byte. Sólo los puntos de código 128 y superiores se almacenan utilizando 2, 3, de hecho, hasta 6 bytes.

Esto tiene el efecto secundario 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 nada malo. Sólo el resto del mundo tiene que pasar por el aro. Específicamente, Hello, que era U+0048 U+0065 U+006C U+006C U+006F, se almacenará como 48 65 6C 6C 6F, qué es lo mismo que se almacenaba en ASCII, y ANSI, y en todos los juegos de caracteres OEM del planeta. Ahora, si eres tan atrevido como para usar letras acentuadas, griegas o klingon, tendrás que usar varios bytes para almacenar un único punto de código, pero los americanos nunca se darán cuenta. (UTF-8 también tiene la agradable propiedad de que el ignorante código de procesamiento de cadenas que quiere usar un solo byte 0 como terminador nulo no truncará las cadenas).

Hasta ahora te he dicho tres maneras de codificar Unicode. Los métodos tradicionales de almacenamiento en dos bytes se denominan UCS-2 (porque tiene dos bytes) o UTF-16 (porque tiene 16 bits), y todavía tiene que averiguar si se trata de UCS-2 de alto contenido endiano (high-endian) o de UCS-2 de bajo contenido endiano (low-endian). Y está el nuevo y popular estándar UTF-8, que tiene la agradable propiedad de funcionar también de manera respetable si se tiene la feliz coincidencia de texto en inglés y programas con muerte cerebral que no saben que hay algo más que ASCII.

En realidad hay un montón de otras formas de codificar Unicode. Hay algo llamado UTF-7, que es muy parecido a UTF-8 pero garantiza que el bit alto siempre será cero, así que si tienes que pasar Unicode a través de algún tipo de sistema de correo electrónico draconiano de estado policíaco que piensa que 7 bits son suficientes, gracias a ti todavía puede pasar sin problemas. Está el UCS-4, que almacena cada punto de código en 4 bytes, que tiene la agradable propiedad de que cada punto de código puede ser almacenado en el mismo número de bytes, pero, caramba, incluso los tejanos no serían tan audaces como para desperdiciar tanta memoria.

Y de hecho, ahora que estás pensando en las cosas en términos de letras ideales platónicas que están representadas por puntos de código Unicode, esos puntos de código Unicode también pueden ser codificados en cualquier esquema de codificación de la vieja escuela! Por ejemplo, podría codificar la cadena Unicode para Hello (U+0048 U+0065 U+006C U+006C U+006F) en ASCII, o la antigua codificación griega OEM, o la codificación hebrea ANSI, o cualquiera de los cientos de codificaciones que se han inventado hasta ahora, con una sola captura: ¡puede que algunas de las letras no aparezcan! Si no hay equivalente para el punto de código Unicode que estás intentando representar en la codificación en la que estás intentando representarlo, normalmente obtienes un pequeño signo de interrogación: ? o, si eres realmente bueno, una caja. ¿Qué conseguiste? -> �

Hay cientos de codificaciones tradicionales que sólo pueden almacenar correctamente algunos puntos de código y cambiar todos los demás puntos de código en signos de interrogación. Algunas codificaciones populares de texto en inglés son Windows-1252 (el estándar de Windows 9x para los idiomas de Europa Occidental) e ISO-8859-1, también conocido como Latin-1 (también útil para cualquier idioma de Europa Occidental). Pero si intentas almacenar letras rusas o hebreas en estas codificaciones, obtendrás un montón de signos de interrogación. UTF 7, 8, 16 y 32 tienen la agradable propiedad de poder almacenar cualquier punto de código correctamente.

El hecho más importante acerca de las codificaciones

Si olvidas completamente todo lo que acabo de explicar, por favor recuerda un hecho extremadamente importante. No tiene sentido tener una cadena de texto sin saber qué codificación utiliza. Ya no se puede meter la cabeza en la arena y pretender que el texto «simple» es ASCII.

No existe tal cosa como el texto simple.

Si tienes una cadena de texto, en memoria, en un archivo o en un mensaje de correo electrónico, tienes que saber en qué codificación está o no puedes interpretarla o mostrarla a los usuarios correctamente.

Casi todos los estúpidos problemas de «mi sitio web parece un galimatías» o «no puede leer mis correos electrónicos cuando uso acentos» se reducen a un programador ingenuo que no entiende el simple hecho de que si no me dices si una cadena en particular está codificada usando UTF-8 o ASCII o ISO 8859-1 (Latín 1) o Windows 1252 (Europa Occidental), simplemente no puedes mostrarla correctamente o ni siquiera averiguar dónde termina. Hay más de cien codificaciones y por encima del punto 127 del código, todas las apuestas están apagadas.

¿Cómo conservamos esta información sobre el uso de la codificación de una cadena? Bueno, hay formas estándar de hacer esto. Para un mensaje de correo electrónico, se espera que tenga una cadena en el encabezado del formulario

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

Para una página web, la idea original era que el servidor web devolviera un encabezado http similar a Content-Type junto con la propia página web, no en el propio HTML, sino como uno de los encabezados de respuesta que se envían antes de la página HTML.

Esto causa problemas. Suponga que tiene un gran servidor web con muchos sitios y cientos de páginas contribuidas por mucha gente en muchos idiomas diferentes y todo ello utilizando cualquier codificación que su copia de Microsoft FrontPage considere adecuada para generar. El servidor web en sí mismo no sabría realmente en qué codificación estaba escrito cada archivo, por lo que no podía enviar el encabezado Tipo de contenido.

Sería conveniente si pudieras poner el Content-Type del archivo HTML directamente en el archivo HTML mismo, usando algún tipo de etiqueta especial. Por supuesto que esto volvió locos a los puristas… ¡¿Cómo puedes leer el archivo HTML hasta que sepas en qué codificación está?! Afortunadamente, casi todas las codificaciones de uso común hacen lo mismo con caracteres entre 32 y 127, por lo que siempre se puede llegar hasta aquí en la página HTML sin tener que empezar a usar letras graciosas:

<html>

<head>

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

Pero esa metaetiqueta tiene que ser la primera cosa en la sección <head> porque tan pronto como el navegador web vea esta etiqueta va a dejar de analizar la página y empezar de nuevo después de reinterpretar toda la página usando la codificación que usted especificó.

¿Qué hacen los navegadores web si no encuentran ningún Content-Type, ya sea en las cabeceras http o en la metaetiqueta? Internet Explorer en realidad hace algo bastante interesante: intenta adivinar, basándose en la frecuencia con la que aparecen varios bytes en el texto típico en codificaciones típicas de varios idiomas, qué idioma y codificación se utilizó. Debido a que las antiguas páginas de código de 8 bits tendían a poner sus letras de nacionalidad en diferentes rangos entre 128 y 255, y debido a que cada idioma humano tiene un histograma característico diferente de uso de letras, esto realmente tiene una oportunidad de funcionar. Es realmente extraño, pero parece que funciona lo suficientemente a menudo que los escritores ingenuos de páginas web que nunca supieron que necesitaban un encabezado de Content-Type miran su página en un navegador web y parece que está bien, hasta que un día escriben algo que no se ajusta exactamente a la distribución de frecuencia de letras de su idioma nativo, e Internet Explorer decide que es coreano y lo muestra así, demostrando, creo, que el punto de que la Ley de Postel acerca de ser «conservador en lo que emites y liberal en lo que aceptas» francamente no es un buen principio de ingeniería. De todos modos, ¿qué hace el pobre lector de este sitio web, que fue escrito en búlgaro pero que parece ser coreano (y ni siquiera un coreano cohesivo)? Utiliza el menú Ver | Codificación e intenta un montón de codificaciones diferentes (hay al menos una docena para los idiomas de Europa del Este) hasta que la imagen aparece más clara. Si supiera hacer eso, lo que la mayoría de la gente no hace.

Para la última versión de CityDesk, el software de gestión de sitios web publicado por mi empresa, decidimos hacer todo internamente en UCS-2 (dos bytes) Unicode, que es lo que Visual Basic, COM y Windows NT/2000/XP utilizan como su tipo de cadena nativo. En el código C++ sólo declaramos las cadenas como wchar_t («wide char») en lugar de char y usamos las funciones wcs en lugar de las funciones str (por ejemplo wcscat y wcslen en lugar de strcat y strlen). Para crear una cadena UCS-2 literal en código C sólo tienes que poner una L delante de ella como tal: L»Hello».

Cuando CityDesk publica la página web, la convierte a codificación UTF-8, que ha sido bien soportada por los navegadores web durante muchos años. Esa es la forma en que están codificadas todas las versiones de Joel on Software en 29 idiomas y aún no he oído a ninguna persona que haya tenido problemas para verlas.

Este artículo se está haciendo bastante largo, y no puedo cubrir todo lo que hay que saber sobre codificaciones de caracteres y Unicode, pero espero que si has leído hasta aquí, sepas lo suficiente como para volver a la programación, usando antibióticos en lugar de sanguijuelas y hechizos, una tarea a la que te dejaré ahora.


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *