Tuesday, June 2, 2009

O diabo da acentuação (2)

Com a explosão da Internet, ficou sem sentido continuar usando sistemas de codificação de caracteres diferentes em cada parte do mundo. Mas para unificar, seria preciso uma codificação que não ia caber em um byte. Então a primeira tentativa foi usar dois bytes, o que permite códigos de \x0000 a \xFFFF, ou seja, 65536 caracteres distintos. Era o que propunham as primeiras versões do padrão Unicode. E assim várias linguagens, inclusive Java, adotaram uma representação interna de strings onde cada caractere equivale a dois bytes.

Outra idéia errada: 1 caractere == 2 bytes



Eu aprendi o que era Unicode estudando Java, e sempre achei normal que um caractere Unicode fosse representado por dois bytes em Java, numa codificação chamada UCS2. Até que em 2006 eu comecei a estudar a linguagem Ruby, e constatei que o suporte a Unicode em Ruby era mais primitivo que em Python. Fiquei surpreso porque o criador de Ruby, Yukihiro Matsumoto, é japonês, e a linguagem é muito popular no Japão.

Mas a propaganda aqui no ocidente é que o Unicode veio para resolver o problema das línguas orientais, então porque faltava um bom suporte em Ruby? Quem pesquisar um pouco o assunto vai descobrir que as primeiras versões do Unicode foram rejeitadas pelo chineses, japoneses e coreanos! Os principais motivos foram (1) a unificação de certos ideogramas que embora visualmente parecidos queriam dizer coisas diferentes e (2) a falta de uma especificação de como estender a codificação para além dos 2 bytes. O segundo ponto é interessante: quando se usa um alfabeto, uma nova palavra é apenas um novo arranjo das mesmas letras; mas em chinês, uma nova palavra pode ser um novo ideograma.

Para resolver este problema, é preciso abstrair um pouco mais...

Unicode é uma tabela de codepoints



O foco do padrão Unicode não é estabelecer uma relação entre caracteres e bytes, e sim uma relação entre caracteres e códigos numéricos chamados codepoints. Como estes codepoints serão representados na memória ou em um arquivo é uma questão secundária, até porque diferentes aplicações vão exigir diferentes representações: um formato bom para transmitir pode ser ruim para processar, por exemplo.

Na documentação do Unicode, os codepoints são identificados por números hexadecimais com o prefixo 'U+'. Veja uma pequena amostra de codepoints e caracteres:

U+6C23 氣 CJK UNIFIED IDEOGRAPH-6C23

U+06BF ڿ ARABIC LETTER TCHEH WITH DOT ABOVE

U+2620 ☠ SKULL AND CROSSBONES

U+0D0B ഋ MALAYALAM LETTER VOCALIC R

U+4DF1 ䷱ HEXAGRAM FOR THE CAULDRON

O texto eu caixa alta em cada linha acima é o atributo "name" do caractere, segundo a tabela Unicode. Além de letras de vários idiomas, o Unicode inclui também símbolos matemáticos, naipes do baralho e até hexagramas do I-Ching.

Vale a pena explorar o site oficial do Unicode, em particular as code charts (tabelas de código) onde os milhares de caracteres aparecem agrupados por idioma ou assunto.

O diabo da acentuação

Estamos no meio de uma imensa migração para Unicode, aquela terra prometida onde caracteres acentuados são cidadãos de primeira classe, e até o conceito da energia interior qi pode ser representado na forma original: 氣.

Enquanto não chegamos lá, reina a confusão na terra da acentuação. Em fóruns de programadores a gente vê muita gente perdida, procurando e oferecendo soluções baseadas mais em superstição e mitos do que em razão e fatos.

Este é o primeiro de uma série de posts para tentar esclarecer essa bagunça.

codificação: neste assunto, codicação não tem nada a ver com cifras ou código-fonte. Uma codificação é apenas uma forma de representar caracteres através de códigos numéricos, para armazenagem em computadores digitais.


A idéia errada: 1 caractere == 1 byte



A computação nasceu em países que falam inglês, um idioma onde se usam apenas as 26 letras de A a Z, sem acentos nem cedilhas [1]. Além disso, a memória das máquinas era muito limitada, então depois de alguma briga entre fabricantes americanos e usuários europeus se estabeleceu a idéia equivocada de que um byte corresponde a um caractere.

Essa idéia simplista gerou codificações como esta (clique na figura para ampliar):



Esta figura na verdade representa uma série de gambiarras, uma em cima da outra. Para começar, a faixa preta representa caracteres projetados para controlar um teletipo, um equipamento mais obsoleto até que um mimeógrafo. Por exemplo, o caractere '\x0C' [2], serve para avançar uma folha no papel e o '\x07' é o BELL, faz o teletipo tocar um sino para chamar a atenção do operador!

Apenas três destes caracteres de controle são largamente utilizados hoje: o HORIZONTAL TAB ('\x09'), o LINE FEED ('\x0a') e o CARRIAGE RETURN ('\x0d'). Os 32 caracteres de controle, bem como a faixa azul, formam o padrão ASCII (pronuncia-se ásqui, e não ásqui-2), totalizando 128 caracteres.

A faixa verde é a tabela ISO-8859-1, também conhecida como Latin-1, uma das várias tabelas de caracteres criadas na norma ISO-8859 para padronizar conjuntos de caracteres alfabéticos. Na tabela Latin-1 aparecem todos os caracteres usados nos idiomas da Europa ocidental. A mesma norma ISO inclui outras tabelas, como a ISO-8859-2 (Latin-2), para idiomas da Europa oriental (como Polonês, Tcheco e Húngaro) e a ISO-8859-5 que contém letras do alfabeto cirílico usado na Russia, Bulgaria, Sérvia etc. [3]

As duas fileiras vermelhas são um "puxadinho" feito pela Microsoft. A norma ISO-8859 reservava estas 32 posições para ainda mais caracteres de controle, que nunca "pegaram", então a Microsoft inventou um padrão chamado "CP-1252" [4] que é basicamente a combinação de ASCII com Latin-1 e mais 27 símbolos como o € (euro) o • (bullet) e as aspas assimétricas “assim” que tanto atrapalham a nossa vida de programadores.

Muitos aplicativos, especialmente no mundo Windows, alegam usar a codificação ISO-8859-1 mas na verdade usam CP-1252, violando a lei de Postel [5]. Isso causa alguma confusão porque se você tenta ler um arquivo CP-1252 como se fosse ISO-8859-1, seu programa não vai saber o que fazer com os €, •, “aspas” etc. Mas o inverso não causa problemas: você sempre pode ler um ISO-8859-1 como se fosse CP-1252, porque o primeiro é um sub-conjunto do segundo.

E o 氣?



Realmente não tem espaço naquela tabela para o caractere chinês do "qi" e os outros milhares de caracteres chineses, japoneses, coreanos, ou para dezenas de idiomas que não usam o alfabeto latino, como árabe, hebraico, tailandês e sânscrito. Essa constatação acabou com o princípio de que "1 byte == 1 caractere", e nos conduziu ao Unicode, tema do próximo post.




[1] na verdade, isso já é uma simplificação; qualquer dicionário de inglês tem a palavra façade (fachada), escrita assim mesmo, com cedilha; a palavra virou até um termo técnico em engenharia de software, pois é o nome de um dos padrões de projeto originais.


[2] \x0C é hexadecimal, em decimal seria 12


[3] a ISO-8859-1 (Latin-1) continua importante até hoje, mas a ISO-8859-5 "não pegou". Os russos preferem outros padrões, como KOI-7 ou KOI-8.


[4] conhecido também como "codepage 1252" ou oficialmente "Windows-1252"


[5] "Seja liberal no que aceita conservador no que envia". Procure "Postel Law" no Google e aprenda um princípio realmente importante e útil para a engenharia e para a vida em sociedade.