PCF-3

Questões comentadas, artigos e notícias

O mínimo de conhecimento básico que todo desenvolvedor de software deveria saber sobre Unicode e Character sets

Posted by mike em 28/03/2009

Este artigo é uma tradução do original:
The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
http://www.joelonsoftware.com/articles/Unicode.html
por Joel Spolsky
traduzido por mykesh

Já parou para pensar sobre a tag Content-Type? Você sabe, aquela que você deveria colocar no HTML e nunca soube exatamente para que serve?

Você nunca recebeu um e-mail dos seus amigos da Bulgária com o assunto “???? ?????? ??? ????” ?

Eu estava preocupado em descobrir quantos desenvolvedores não compreendem o enigmático mundo dos character set (grupo de caracteres), Unicode e coisa do tipo. Alguns anos atrás, um Analista de testes do FogBuz estava pensando como poderia manipular e-mails em japonês. Japonês? Eles recebem e-mail em japonês? Eu não tinha a menor idéia. Quando eu resolvei acompanhar de perto um ActiveX para analisar o MIME de mensagens de e-mail , nós descobrimos que o ActiveX estava fazendo a coisa errada com os character sets, então tivemos que fazer um código ninja para desfazer a conversão errada que ele estava fazendo e codificar novamente. Uma vez eu verifiquei a implementação de uma biblioteca comercial e esta também tinha uma implementação equivocada de character code (código de caracteres). Eu troquei uma idéia com o desenvolvedor da solução e ele me veio com a idéia “que não poderia fazer nada a respeito”. Como muitos programadores, ele queria que de alguma forma caisse no esquecimento.

Mas não foi bem assim. Quando eu descobri que a popular ferramenta de desenvolvimento web PHP não entende quase nada sobre character encondig , usando sem se importar 8 bits para codificação de caracteres, tornando perto do impossível o desenvolvimento de boas aplicações web internacionais, então pensei, realmente já chega.

Eu tenho um anúncio a fazer: se você é um programador trabalhando em 2003 e você não conhece o básico sobre caracteres, character sets (grupo de caracteres), encodings e Unicode, se eu te pegar, eu vou fazer você descascar cebolas em um submarino por 6 meses. Eu prometo que faço você fazer isso.

E mais uma coisa:

NÃO É DIFÍCIL

Neste artigo eu vou de dar algumas dicas sobre o que todo programador deveria saber. Toda aquela coisa sobre “plain text = ascii = caracteres de 8 bits” não é somente equivocada, é completamente equivocada e se você ainda programa deste jeito, você não é muito melhor que um médico que não acredita em germes. Por favor não escreva nenhuma linha de código antes que você termine de ler este artigo.

Antes de começar, eu gostaria de avisar que se você é uma daquelas pessoas que conhece sobre internacionalização, você vai achar a minha explicação um pouco simplória. Eu estou realmente tentando estabelecer um nível mínimo em que todo mundo possa entender o que está acontecendo e possa escrever códigos que tenham esperanças funcionais . Também deveria te avisar que manipular caracteres é apenas uma pequena fração para criar softwares que funcionam internacionalmente, porém eu só posso escrever sobre um assunto por vez e hoje é sobre character sets (grupo de caracteres).

Perspectiva histórica

Vamos voltar no tempo e rever os fatos cronológicamente.

Você provavelmente deve estar imaginando que eu vou falar sobre EBCDIC. Bem, eu não irei. EBCDIC não é relevante para a sua vida. Não precisamos voltar tanto assim no tempo.

De volta para um tempo não tão antigo, quando o Unix estava sendo inventado e K&R estavam escrevendo a linguaem C, tudo era muito simples. EBCDIC estava sainda de cena. Os únicos caracteres que importavam eram os bons velhos caracteres ingleses sem acento e a gente tinha um código para eles chamado ASCII que podiam ser representados usando números entre 32 e 127. Espaço era 32, a letra “A” era 65, etc. Isso podia ser armazenado em meros 7 bits. Naquele tempo a maioria dos computadores usavam 8-bit bytes, poderia armazenar não só todos os caracteres ASCII, como tinha muitos bits para desperdiçar e se você fosse um zé roela poderia usar a seu bel prazer: os zé roelas da WordStar fizeram com que o hight bit (bit alto) indicasse a última letra de uma palavra, condenando o WordStar exclusivamente a textos em inglês. Códigos abaixo de 32 foram chamados de não-imprimíveis e foram utilizados por pura perversão. To só brincando. Eles foram utilizados para controlar caracteres, por exemplo o 7 fazia o seu computador bipar e o 12 fazia a impressora ‘cuspir’ o papel e usar uma nova.

Tudo era fácil se você fosse uma pessoa que falasse somente em inglês.

Porque em 1 byte tem espaço para até 8 bits, muitas pessoas pensavam, “oras, podemos usar os códigos de 128-255 para as nossas próprias necessidades”.  O problema foi que um monte de gente pensou a mesma coisa ao mesmo tempo e cada um com suas próprias idéia de como utilizar os espaços entre 128 e 255. A IBM-PC teve a idéia que se tornou o grupo de caracteres OEM que proporcionava caracteres acentuados para idiomas europeus e um monte de caracteres de figuras de linha… barras horizontais, barras verticais, barras horizontais com firulas, etc. E você podia usar os caracteres de desenho fazer caixas e linhas fofinhas na tela, que você ainda pode ver rodando em um computador 8088 da sua lavanderia. De fato quando as pessoas começaram a comprar computadores PC fora dos Estados Unidos, todo tipo de grupo de caracteres OEM foram inventados, sendo que usaram os caracteres acima de 128 para suas próprias necessidades. Em alguns computadores por exemplo, o código do caractere 130 podia ser visualizado como é, porém em alguns computadores vendidos em Israel era usado a letra hebraica gimel (גּ), logo caso um americano enviasse um currículo (résumé em inglês) para Israel, o currículo poderia ser visualizado como rגּsumגּs (currículo em inglês). Em muitos casos, como no russo, havia muitas idéias com o que poderia ser feito com os caracteres acima do 128, logo você não poderia trocar documentos em russo de forma confiável.

Eventualmente todos estes OEM gratuitos foram codificados no formato ANSI. No padrão ANSI, todo mundo concordou no que fazer com os caracteres abaixo do 128, o que foi muito parecido com o ASCII, porém dependendo da onde você morava havia várias maneiras de manipular os caracteres acima de 128. Estes diferentes sistemas foram chamados de code pages (páginas de código/sinal). Só para ter uma idéia, em Israel o DOS era usado com o code page 862, enquanto os usuários gregos usavam o code page 737. Eles eram o mesmo abaixo do 128 mas diferente acima do 128, onde todas as letras cômicas eras armazenadas. A versão nacional do DOS tinha um monte destas code pages, manipulando todo tipo de caractere desde o inglês até islandês e eles ainda tinham code pages “multi-linguagens” que podiam interpretar Esperanto e Galiciano no mesmo computador! Opa! Porém, utilizar hebreu e grego no mesmo computador era totalmente impossível a não ser que você escrevesse seu próprio programa personalizado que exibisse tudo usando gráficos bitmap, isso porque hebreu e grego necessitava de code pages com diferente interpretações dos números altos.

Enquanto isso na Ásia,  coisas estranhas ocorriam ainda mais, pelo fato de que alfabetos asiáticos possuem milhares de letras, que nunca poderiam caber em 8 bits. Isto foi resolvido pelo confuso sistema DBCS, o “double byte character set” (grupo de duplo byte de caracteres), em que algumas letras eram armazenadas em um byte e outras em dois bytes. Era fácil correr por uma string, mas praticamente impossível de correr ao contrário. Programadores eram encorajados a usar s++ e s– para andar para frente e para trás, ao invés de usar as funções AnsiNext and AnsiPrev do Windows, que sabia como manipular com a bagunça toda.

A maioria das pessoas ainda fingiam que um caractere equivalia a um byte e um caractere era 8 bits desde que não movesse uma string de um computador para outro ou falasse mais de um idioma,  desta maneira isso poderia funcionar sempre. Porém é claro que assim que a Internet surgiu se tornou um lugar comum para se trocar strings de um computador para o outro, e toda bagunça veio abaixo. Por sorte  o Unicode havia sido inventado.

Unicode

Unicode foi um esforço muito grande para se criar um character set (grupo de caracteres) que incluísse todo tipo de sistema de escrita do planeta e também algumas inventadas como Klingon (Star Trek). Algumas pessoas pensam que Unicode é simplesmente um código/sinal de 16-bits onde cada caractere usa 16 bits e logo há 65.536 caracteres possíveis. Isto não está correto. Este é o mito mais comum sobre Unicode, se você imaginou a mesma coisa, não se sinta mal.

De fato Unicode tem uma idéia própria sobre caracteres e você precisa entender a maneira como o Unicode trata os caracteres ou nada fará sentido.

Até agora, assumimos que uma letra está relacionada com alguns bits que podem ser armazenados na memória ou em disco:

A -> 0100 0001

Em Unicode uma letra está relacionada com uma coisa chamada code point (código/sinal ponto) o que significa apenas um conceito teórico. Como o code point é representado na memória ou no HD é uma outra história .

A letra A em Unicode é um exemplo de ideal de Platão. Está apenas flutuando pelo céu:

A

O A platônico é diferente de B, diferente de a, porém igual a A e A e A. A idéia do A da fonte Times New Roman é a mesma do caractere A na fonte Helvética, porém diferente do “a” minúsculo, isso não soa muito controverso, porém em algumas idiomas só de imaginar o que uma letra significa pode causar controvérsias. A letra ß  em alemão seria uma letra de verdade ou apenas uma forma extravagante de escrever ss? Se a forma de uma letra muda no fim do mundo, ela realmente é uma letra? Os hebreus dizem sim, árabes dizem não. De qualquer forma, a galera esperta do consórcio do Unicode vem pensando nisso desde a última década ou mais, acompanhado de calorosos debates políticos e você não precisa se preocupar com isso. Eles já pensaram em tudo.

Para cada letra platônica de cada alfabeto um número mágico  foi atribuído pelo consórcio Unicode desta maneira: U+0639. Este número mágico é chamado code point (ponto código/sinal). O U+ significa “Unicode” e os números são hexadecimal. U+0639 é a letra árabe Ain. A letra inglesa A é o U+0041. Você pode achar todos através da ferramenta charmap no Windows 2000/XP ou visitando o site do Unicode.

Não há um limite real para os números de letras que o Unicode pode definir e de fato eles já passaram de 65.536, logo nem toda letra pode ser espremida em 2 bytes, mas de qualquer forma isso era um mito.

OK, vamos imaginar a string:

Hello

em Unicode corresponde aos cinco code points:

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

Apenas um monte de code points. Números, só isso. Ainda não disse como armazenar isso na memória ou visualizar em uma mensagem de e-mail.

Encodings (codificação)

Esse é o ponto em que os encodings (codificações) entram.

As primeiras idéias da codificação do Unicode, a qual criou o mito dos 2 bytes, podemos dizer que foi alocar 2 bytes para os números. Logo Hello ficou:

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

Certo? Calma lá!  Também poderia ser:

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

Bom, tecnicamente sim, eu realmente acredito que poderia ser, e de fato os primeiros implementadores queriam poder armazenar os code points Unicodes nos modos ‘high-endian’ ou ‘low-endian’ , não importando se o CPU era extremamente rápida ou lenta de manhã ou a noite e havia duas maneiras para se armazenar o Unicode. Logo as pessoas foram forçadas a usar uma convenção doida de se armazenar um FE FF no começo de cada string Unicode; isto é chamado de Unicode Byte Order Mark (Byte de Marcação de Ordem Unicode) e se você for trocar seu hight e low byte nele irá aparecer um FF FE e a pessoa que estiver lendo a sua string vai saber que vai ter que trocar todos os bytes. Ufa. Nem toda string Unicode utilizada tem um byte order mark no começo.

Por um momente pareceu ser uma boa idéia, mas programadores estavam reclamando. “Olhe todos estes zeros!” eles diziam desde que fosse americano com um texto em inglês que raramente usava code points acima de U+00FF. Além disso eles eram hippies na California que queriam conservar (sacanear). Se eles fossem texanos eles não teriam se importado de se enfartar duas vezes com a quantidade de bytes. Porém aqueles californianos zé roelas não poderiam suportar a idéia de dobrar a quantidade de espaço que uma string teria e além disso, eles tinham uma pilha de documentos espalhados usando os character set ANSI e DBCS e quem iria converter todos eles? Moi (‘eu’ em francês)? Somente por causa disso um monte de gente decidiu ignorar Unicode por muito tempo e enquanto isso as coisas ficaram piores.

Como consequência foi inventado a idéia brilhante do UTF-8. UTF-8 foi um outro sistema para armazenar as suas strings de code points Unicodes, aqueles U +”números mágicos” na memória com 8 bit byte. No UTF-8, cada code point de 0-127 é armazenado em um byte. Apenas os code points acime de 128 são armazenados usando 2, 3 e até 6 bytes.

Uma consequência interessante foi que textos em UTF-8 ficaram iguais em ASCII, logo os americanos não perceberam qualquer diferença. Apenas o resto do mundo teve que ter o terrível trabalho de se adaptar. Com por exemplo, Hello, que era U+0048 U+0065 U+006C U+006C U+006F, se tornou 48 65 6C 6C 6F, perceba uma coisa engraçada: é exatamente como no ASCII, ANSI e qualquer outro grupo de caracteres OEM do planeta. Agora se você for atrevido o suficiente para usar letras com acento ou letras gregas ou letras de Klingon você terá que usar vários bytes para armazenar um único code point e os americanos nem vão perceber (além disso o UTF-8 tem uma característica interessante, as antigas strings boçais que se utilizavam um byte nulo como terminador não serão truncadas).

Até agora eu te contei 3 maneiras de codificar Unicode. O tradicional método armazenar-em-2-bytes chamados UCS-2 (porque eles tem 2 bytes) e o UTF-16 (porque tem 16 bits), que você ainda necessita saber se é UCS-2 com high-endian  ou UCS-2 com low-endian. E também tem o popular padrão UTF-8 que tem a interessante e funcional característica se você tiver a feliz coincidência com textos em inglês e programas desmiolados que não conhecem nada além de ASCII.

Na verdade existem um monte de maneiras de codificar Unicode. Tem um treco chamado UTF-7, que se parece muito com UTF-8, mas que garante sempre que o high bit será sempre zero, e se você tiver que passar Unicode por um sistema de e-mail cruelmente zé roela que pensa que 7 bits são mais que suficientes, agradeça que ele pode passar através dele sem se dar mal. Tem também o UCS-4 que armazena cada code point em 4 bytes e também tem a interessante característica que cada code point pode ser armazenado no mesmo número de bytes, mas vamos ser francos, nem os texanos seriam tão zé roelas em desperdiçar tanta memória.

E se você estiver pensando nas letras na forma do ideal platônico que é representada por code points Unicode, estes code points unicode podem ser codificadas em qualquer esquema antigo de codificação! Por exemplo, se você codificar a string Unicode Hello (U+0048 U+0065 U+006C U+006C U+006F) em ASCII, em qualquer antiga codificação grega OEM, na codificação hebréia ANSI, ou em qualquer sistema de codificação antigo seja qual tempo ele tenha sido inventado, com um único detalhe: algumas letras podem não aparecer! Se não há uma equivalente para um code point Unicode que você tente representar em uma codificação que você tente representar, você terá um pequeno ponto de interrogação: ? ou se você realmente for bom, uma caixa. Como você consegue ver? -> �

Existe um monte de enconding tradicionais que podem armazenar somente alguns code points de forma correta e alteram o resto dos code points em pontos de interrogação. Alguns populares sistema de codificação de texto em inglês são Windows-1252 (padrão Windows 9x para idiomas europeus ocidentais) e o ISO-8859-1, conhecido como Latin-1 (também muito bom para idiomas europeus ocidentais). Mas tente armazenar letras russas ou hebréias nestes encodings e você vai receber um monte de interrogações. UTF 7, 8, 16 e 32 tem a correta capacidade de armazenar qualquer code point de forma correta.

O fato mais importante sobre Encodings

Se você esqueceu tudo o que eu disse, por favor se lembre de um único fato. Não faz o menor sentido ter uma string sem saber qual enconding ela usa. Você não pode mais enfiar a sua cabeça na areia e fingir que um texto “puro” é ASCII.

Não há nada como texto puro

Se você tem uma string na memória, em um arquivo ou em uma mensagem de e-mail, você tem que saber qual sistema de codificação está sendo usado ou você não poderá interpretar ou visualizar para o usuário de forma correta.

Praticamente todos os  problemas do tipo “meu site parece escrito por um analfabeto” ou “ela não pode ler meus e-mails quando eu uso acento”  quase sempre é referente a inocência do programador que não entende o simples fato que se não informar como uma string foi codificada usando UTF-8, ASCII, ISO 8859-1 (Latin 1) ou Windows 1252 (Western European), você não poderá mostrar corretamente ou saber onde está o fim da string. Existem centenas de tipos de encodings diferentes e acima do code point 127, não há como adivinhar.

Como podemos saber qual tipo de codificação uma string utiliza? Bom, existem formas padronizadas de se fazer isso. Para uma mensagem de e-mail, é esperado que existe uma string no cabeçalho

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

A idéia original para uma página web era que o servidor web podesse retornar no cabeçalho http um Content-Type similar junto com a própria página html — não dentro do html em si, desde que um dos cabeçalhos de resposta (response header) fosse enviado antes da página html.

Isso causou problemas. Imagine que você tenha um grande servidor web com muitos sites e páginas feitas por centenas de pessoas em diferentes idiomas e todos usam a respectiva codificação de suas cópias de seus Microsoft ProntPage .O servidor web não teria como saber como cada página foi escrita, logo não poderia enviar o cabeçalho Content-Type.

Seria conveniente se pudesse colocar o cabeçalho Content-Type do arquivo html diretamente no próprio arquivo html, usando uma tag especial. Claro que isso enlouqueceu os puristas… como você poderia ler um arquivo html antes de saber em qual codificação foi feita?! Por sorte, quase todos os sistemas de codificação usam os mesmos caracteres entre 32 e 127, você pode ir até certo ponto da página HTML sem as letras pitorescas aparecerem:

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

Porém a tag meta tem que ser a primeiríssima coisa na seção <head> porque assim que o web browser ver  a tag ele irá parar de analisar a página e depois irá recomeçar a interpretar toda página usando a codificação especificada.

O que os web browser fazem se não encontrarem a tag Content-Type no cabeçalho http ou na tag meta? O Internet Explorer faz uma coisa bem legal: tenta adivinhar baseado na frequência de como os bytes surgem em textos típicos em codificações comuns de várias linguagens, conforme as codificações e idiomas foram utilizados. porque um monte de code pages de 8 bits tendem a determinar suas letras nacionais em diferentes ranges entre 128 e 255 e por causa de cada idioma tem um histograma característico de utilização diferente e isso pode realmente funcionar. É realmente estranho, mas parece funcionar frequentemente bem se você pensar que alguns autores zé roelas de web-page nunca souberam da necessidade de se ter o cabeçalho Content-Type no web browser, até que um dia o mané escreve alguma coisa que não está dentro da conformidade da letra-frequencia-distribuição de seu idioma  e o  Internet Explorer resolve que é coreano e mostra-o, provando,  sem dúvida, na minha opinião, que a lei de Postel referente a ser “conservador naquilo que voce propaga e liberal naquilo que aceita”  não é francamente um bom princípio de engenharia. De qualquer forma, o que o pobre leitor deste blog, que foi escrito em bulgaro mas parece estar em coreano (nem parece um coreano decente), pode fazer? Pode utlizar o menu Visualizar | Codificação e tentar um monte de codificações diferentes (pelo menos uma dúzia de idiomas europeus ocidentais) até que se apareça de forma correta. Se souber fazer isso, pois a maioria não sabe.

Para a última versão do CityDesk, o software de gerenciamento de websites publicado pela minha empresa, decidimos fazer tudo internamento usando UCS-2 (dois bytes) Unicode, que é o que o Visual Basic, COM e o Windows NT/2000/XP nas suas strings nativas. Em código C++ apenas declaramos strings como wchar_t (“wide char”) ao invés de char e usamos a função wcs ao invés da funções str (por exemplo wcscat e wcslen ao invés de strcat and strlen). Para criar strings UCS-2 exatas em código C apenas use um L antes, como: L”Hello”.

Quando o CityDesk publica uma página, coverte tudo para encoding  UTF-8, que tem sido bem suportado pelos web browser por muitos anos. Isso é como todas as 29 versões de idiomas do Joel on Software são codificadas e eu não ouvi uma reclamação sequer de uma única pessoa que tenha tido problemas em visualiza-los.

Este artigo se extendeu bastante e eu não posso cobrir tudo o que existe sobre codificação de caracteres e Unicode, mas eu espero que se você leu até aqui, você sabe o suficiente para voltar a programar usando antibióticos no lugar de sanguessugas e macumba, uma tarefa que eu vou deixar com você.

3 Respostas to “O mínimo de conhecimento básico que todo desenvolvedor de software deveria saber sobre Unicode e Character sets”

  1. papacharliefox3 said

    Boa Japa! Ótimo material!

  2. fusquinha said

    Muito boa a matéria… parabéns.

  3. Anônimo said

    BOM BOM

Deixe um comentário