O minimo que todo desenvolvedor deveria saber sobre unicode (Sem desculpas!)

18 January 2019 — 19 min read.

Pilates


Esse artigo é uma tradução adaptada do artigo The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!). por Joel Spolsky Co-Fundador do Trello , Fog Creek Software e CEO do Stack Overflow

Embora o artigo seja consideravelmente antigo, falando em termos tecnológicos, o tema ainda se faz pertinente e o título ainda faz jus ao conteúdo.


Já se perguntou sobre aquela tag misteriosa ‘Content-Type’? Voce sabe, aquela que você deveria por no HTML e nunca soube realmente o que era ?

Você já recebeu um email de um amigo da Bulgária com o título “???? ?????? ??? ????”?

Fico desanimado em descobrir o quão pouco familiarizados os desenvolvedores estão com o misterioso mundo dos character sets, encodings, Unicode, todas essas coisas. Alguns anos atrás, um beta teste da FogBUGZ andava se perguntando se poderia lidar com emails recebidos em japonês. Japonês? Eu não fazia ideia. Quando examinei mais de perto o controler ActiveX que estávamos usando para transformar MIME emails, descobrimos que ele estava fazendo errado o character set, então nós teríamos de escrever um código heróico para desfazer a conversão errada e refazê-la corretamente. Quando olhei em outra biblioteca comercial, ela , também continha um código de implementação de characteres completamente quebrado. Entrei em contato com o desenvolvedor daquele módulo e ele me disse por alto ‘não posso fazer nada por você’. Como muitos programadores, nós apenas desejamos que tudo explodisse de alguma forma.

Mas não explodiu. Quando eu descobri que a popular ferramenta de desenvolvimento web PHP ignorava quase que completamente problemas de codificação , brilhantemente usando 8 bits para os characteres, tornando quase impossível desenvolver aplicações web a nível internacional, eu pensei: agora chega!

Então eu tinha um pronunciamento a fazer: Se você é um programador trabalhando em 2003 você não sabe o básico de characteres, characteres set, encodings, e Unicode e eu vou te pegar, vou fazer você descascar cebolas em um submarino durante 6 meses como punição. Eu juro que vou.

E mais uma coisa:

Não é tão difícil

Nesse artigo eu vou lhe mostrar exatamente o que todo programador deveria saber. Todas as coisas sobre “plain text=ascii=characters são 8 bits” não é apenas errado, é irremediavelmente errado, e se você ainda programa dessa forma, você não é melhor do que um médico que não acredita em germes. Por favor não escreva outra linha de código antes de terminar de ler esse artigo.

Antes de começar, devo avisar que se você é uma daquelas raras pessoas que conhecem sobre internacionalização, voce vai achar toda essa discussão um pouco simples demais. Estou apenas tentando estabelecer o mínimo para que você entenda o que está rolando e para que você possa escrever código que funcione como linguagem de texto além do subgrupo do inglês, que não inclui palavras com acento. E eu devo te alertar que lidar com characteres é apenas uma pequena porção do que é requerido para criar programas que funcionam internacionalmente, mas posso escrever apenas uma coisa de cada vez, então hoje falaremos sobre conjunto de characteres.

Uma perspectiva histórica

A forma mais fácil de entender isso é seguindo cronologicamente.

Você provavelmente acha que eu vou falar sobre conjuntos de caracteres antigos como EBCDIC. Bem , eu não vou. EBCDIC não é relevante para sua vida. Nós não temos de voltar tanto no tempo.

De volta a nosso passado recente, quando UNIX estava sendo inventado e K&R era escrito na linguagem de programação C, tudo era muito simples. EBDCIC era a saída. Os únicos caracteres que importavam eram as velhas e boas desacentuadas letras americanas, e nós tínhamos código para elas chamado ASCII, no qual era possível representar qualquer caractere usando um número entre 32 e 127. Espaço era 32, a letra ‘a’ era 65, etc. Isso, convenientemente, poderia ser guardado em 7 bits. A maioria dos computadores naquele tempo estavam usando bytes de 8-bits, então não apenas era possível guardar todos os caracteres ASCII possíveis, mas você tinha um bit inteiro sobrando, no qual, se você fosse fraco, poderia usar para suas próprias propostas diabólicas. Códigos abaixo de 32 eram chamados unprintable e eram usados para maldições. Brincadeira. Eles eram usados como caracteres de controle, como o 7 que fazia o computador apitar e o 12 que fazia com que a página atual da impressora fosse injetada e uma nova página tomasse seu lugar.

Tudo ia bem, assumindo que você era um falante de ingles.

Porque bytes contém espaço para 8 bits, muitas pessoas se pegaram pensando: “Deus, nós podemos usar os códigos 128-255 para o que bem entendermos”. O prolema era, muitas pessoas tiveram essa mesma ideia ao mesmo tempo, e suas próprias concepções para o que deveria preencher o espaço entre 128 e 255. O IBM-PC tinha algo que ficou conhecido como OEM character set que provinha alguns caracteres acentuados para língua européia e um punhado de linhas … barras horizontais, barras verticais com penduricalhos no lado direito, etc… e você poderia usar essas linhas para desenhar caixas simples. De fato, assim que pessoas de fora da América começaram a comprar computadores, todo tipo de OEM apareceu, nos quais todos usavam os números após 128 para seus próprios propósitos. Por exemplo em algum PC o código 130 pode exibir a letra ‘é’ mas em computadores vendidos em Israel contém a letra hebraica Gimel (ג), então quando americanos mandavam seus résumés para israel eles chegavam como rגsumגs. Em muitos casos, como o Russo, eles tinha muitas ideias diferentes do que fazer com os caracteres acima de 128, o que impossibilitava até mesmo a troca de documentos dentro do país.

Eventualmente essa OEM ‘para todos’ foi certificada sobre o selo ANSI. Sobre o selo ANSI, todos concordavam com o que fazer com o excedente 128, o que era quase a mesma coisa do ASCII, mas haviam muitas formas diferentes de lidar com os caracteres 128 e acima, dependendo de onde você vive. Esses sistemas diferentes eram chamados de code pages. Então por exemplo em Israel, DOS usava um code page chamado 862, enquanto usuários gregos usavam 737. Eles eram os mesmos abaixo de 128 mas diferentes acima de 128, onde todas as letras engraçadas residiam. A versão nacional do MS-DOS continham dúzias dessas code pages, lidando com tudo do Inglês até o Sorvetês e eles tinham até mesmo algumas code pages ‘multilingues’ que conseguiam fazer Esperanto e Galego no mesmo computador! WOW! Mas ter Hebraico e Grego no mesmo computador era impossível a não ser que você escrevesse seu próprio programa para mostrar tudo usando gráficos bitmapeados, porque Hebraico e grego requerem tipos diferentes de code pages com tipos diferentes de interpretações para os números acima de 128.

Enquanto isso, na Ásia, coisas ainda mais doidas estavam rolando uma vez que o alfabeto Asiático contém centenas de letras que nunca iriam caber em 8-bits. Isso era geralmente resolvido pelo confuso sistema chamado DBCS, o “double byte character set” nos quais algumas letras eram gravadas em um bite e outras consumiam dois. Era mais fácil seguir em uma string, mas era quase impossível voltar. Programadores eram encorajados e não usar s++ e s- para se mover para trás e para frente, mas chamar funções como Windows’ AnsiNext and AnsiPrev para lidar com toda a bagunça.

Ainda assim, a maioria das pessoas apenas fingiam que um byte continha um caractere e que um caractere continha 8 bits e contando que você nunca levasse aquela string de um computador para outro, ou falasse outra língua, ia meio que sempre funcionar. Mas é claro, assim que a internet aconteceu, virou meio que senso comum mover string de um computador para outro e a coisa toda começou a desmoronar. Por sorte o unicode foi inventado.

Unicode

Unicode fazia um bravo esforço de criar um único conjunto de caracteres para incluir qualquer sistema de escrita no planeta, até mesmo os inventados como o Klingon. Algumas pessoas têm a micro concepção de que Unicode é simplesmente código de 16-bit onde cada caractere consome 16 bits logo temos 65,536 possíveis caracteres. Não é bem assim. Esse é um mito mais comum sobre Unicode, então se você pensou nisso, não se sinta mal.

Na verdade Unicode tem um jeito diferente de pensar sobre os caracteres, e você tem de entender o jeito Unicode de ver as coisas ou nada fará sentido.

ATé agora, nós assumimos que uma letra é mapeada para alguns bits que podem ser guardados em algum disco na memória:

A -> 0100 0001

Em Unicode, uma letra é mapeada para algo chamado de code point que ainda assim é apenas um conceito teórico. Como esse code point é representado na memória ou em disco é uma outra história maluca.

Em Unicode, a letra A é um ideal platônico. É apenas uma nuvem no céu.

A

Esse A platônico é diferente do B, e diferente do a, mas igual a A e A. A ideia que ‘A’ na fonte Times New Roman é o mesmo caractere que ‘A’ na fonte Helvetica, mas diferente de ‘a’ em minúsculo, não parece controverso, mas em algumas linguagens apenas descobrir o que a letra é pode causar controvérsia. A letra ß em alemão é uma letra de verdade ou só um jeito bonitinho de escrever ss ? Se a forma da letra muda no final da palavra, é uma letra diferente ? Hebraico diz que sim, Árabe diz que não. De qualquer forma, as pessoas inteligentes por trás do Unicode vem pensando nisso pelo menos pela ultima década, acompanhados de um grande debate político no qual você não precisa nem se preocupar. Eles já descobriram.

Cada letra platônica em todo alfabeto é acompanhada de um número mágico como: U+0639. Esse número mágico é chamado de code point. O U+ significa “Unicode” e os números são hexadecimais. U+0639 é a letra árabe Ain. A letra A em inglês é U+0041. Você pode achar todas visitando o site do Unicode.

Não existe limite real no número de letras que o Unicode pode definir, na verdade eles foram além de 65,536 então nem toda letra unicode pode ser espremida em dois bytes, mas isso é um mito de qualquer forma.

Ok , vamos supor que temos uma string:

Hello

no qual em unicode corresponde a esses cinco code poits:

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

Apenas um punhado de code points. Números, na verdade. Nós nem falamos nada sobre como salvar isso em memória ou representar isso em uma mensagem de email.

Codificação

É aí que a codificação entra em ação.

A ideia primária para a codificação Unicode, o que leva ao mito sobre dois bytes, era: vamos apenas armazenar esses números em dois bytes cada. Então Hello vira:

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

Certo ? ainda não! Ainda pode ser:

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

Bem , tecnicamente sim ,eu acredito que poderia as primeiras pessoas que começaram a implementar o Unicode queriam poder guardar seus code points em modo high-endian or low-endian em qualquer que fosse sua CPU, se fosse de manhã ou de tarde, teria de haver dois jeitos de salvar Unicode. Então as pessoas foram forçadas a inventar a bizarra convenção de colocar FF EE no início de toda String Unicode. Isso é chamado Unicode Byte Order Mark e se você trocar entres seus hight-bytes e low-bytes vai obter algo parecido com FF FE e a pessoa lendo sua string que a troca foi executada. Nem toda string unicode mundo afora tem marcação da ordem de byte no início.

Por um tempo pareceu ser bom o suficiente, mas programadores começaram a reclamar. ‘Da uma olhada nesses zeros!’ eles falavam, como americanos eles estavam olhando para texto em inglês que raramente usavam pontos acima de U+00FF. Então a maioria das pessoas decidiram ignorar Unicode por vários anos e nesse meio tempo as coisas pioraram.

Então foi inventado o conceito brilhante do UTF-8. UTF-8 foi outro sistema para armazenar aqueles números mágicos U+ das string Unicode em memória usando 8 bit bytes. Em UTF-8, todo code point do 0-127 é guardado em um único byte. Apenas code points acima de 128 são armazenados usando 2,3, chegando até 6 bytes.

Isso tinha o conveniente efeito colateral no qual textos em inglês permaneceram os mesmo em UFT-8 dos em ASCII, então americanos nem mesmo notariam algo diferente. Apenas o resto do mundo deveria fazer acrobacias. Especificamente, Hello, que era U+0048 U+0065 U+006C U+006C U+006F, seria guardado como 48 65 6C 6C 6F, YAY!! É o mesmo que seria armazenado em ASCII, ANSI, e qualquer outro conjunto de caractere OEM no planeta. Agora, se você é ousado o suficiente para usar letras acentuadas, ou gregas ou em klingon, você vai ter que usar vários bytes para guardar um único code point, mas os Americanos nunca irão notar.

Ate agora eu lhe disse tres formas de codificar Unicode. O tradicional método guardar-em-dois-bytes é chamado UCS-2 (porque tem dois bytes) ou UTF-16 (porque tem 16 bytes), e você ainda tem de adivinhar se é high-endian UCS-2 ou low-endian UCS-2. E tem o novo padrão UTF-8 que tem a propriedade de trabalhar respeitavelmente se você tem a feliz coincidência de lidar com texto em inglês. Programas proprietários são completamente alheios ao fato de que existe qualquer coisa além de ASCII.

Existem atualmente um punhado de outras formas de codificação Unicode. Tem o chamado UTF-7, que é bem parecido com UTF-8 mas garante que os ‘hight_bits’ sempre sejam zero, então se você tem que passar Unicode por algum tipo de sistema de email draconiano que acha que 7 bits é o suficiente você acaba saindo ileso por pouco.

Agora que você está pensando dentro dos termos das letras platônicas ideais que são representadas por code points, esse unicode code point também pode ser codificado usando qualquer codificação old-school. Por exemplo, voce pode codificar a string Hello em unicode (U+0048 U+0065 U+006C U+006C U+006F) em ASCII, ou no antigo OEM Grego, ou Hebraico ANSI, ou qualquer um dois muitos outros sistemas de codificação que foram inventados até agora com uma coisa em mente: Algumas das letras podem não aparecer! Se não existir um code point equivalente a codificação que você está tentando representar, geralmente você obtém um ponto de interrogação: ?, ou se você for muito bom uma caixa. Qual você tirou ? -> �

Existem centenas de codificações tradicionais que podem apenas guardar alguns code points de forma correta e mudar todos os outros code points para pontos de interrogação. Algumas das codificações popular do Inglês são Windows-1252 (O Windows 9x padrão para línguas ocidentais européias) e ISO-8859-1, também conhecido como Latin-1 (muito útil também para qualquer língua ocidental europeia). Mas tente guardar letras em Russo ou Hebraico com essas codificações e você terá um monte de pontos de interrogação. UTF-7,8,16 E 32 todos tem a ótima propriedade de serem capazes de guardar qualquer codepoint de forma correta.

O único fato importante sobre Codificação

Se você esqueceu completamente o que eu acabei de explicar, por favor lembre um fato extremamente importante. Não faz sentido ter uma string sem saber que codificação é usado. Você não pode mais enfiar sua cabeça na areia e fingir que texto “liso” é ascii

Não existe isso de texto liso

Se você tem uma string na memória, em um arquivo , ou em uma mensagem de email, você tem que saber que codificação está contida ali ou você não poderá interpretá-la e exibi-la para o usuário corretamente.

Quase toda frase estúpida como “Meu site parece um rascunho” ou “Ela não consegue ler meus emails quando eu coloco acento” problemas vem de um programador tímido que não entendeu o simples fato que se ele nao e informar que tipo de codificação usado, seja ele UTF-8, ASCII, ISO 8859-1 (Latin 1) ou Windows 1252, (Western European) não será possível exibi-los corretamente.

Como preservamos a informação sobre qual codificação uma string usa ? Bem, existem formas padrão de se fazer isso. Para uma mensagem de email, é esperado receber uma string no header do formulário

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

Para uma página na web, a ideia original era que iria retornar uma tag similar a Content-Type junto a página propriamente dita - Não no html, mas como um dos cabeçalhos que são enviados antes da página HTML.

Isso causa problemas. Suponhamos que você tenha um servidor web com centenas de páginas que recebem contribuição de centenas de pessoas usando um punhado de línguas diferentes e todos eles usando qualquer tipo de codificação copiadas diretamente do microsoft Word usado para redigir os textos. O servidor web não saberia de verdade em que codificação cada arquivo foi escrito, então ele não conseguiria mandar o Content-Type header.

Seria conveniente se você pudesse colocar o Content-Type do HTML no arquivo HTML em si usando algum tipo de tag especial. Obviamente isso levou aqueles desenvolvedores puritanos a loucura … como você vai ler o arquivos html sem saber a codificação contida nele ? Por sorte, quase todos os tipos de codificação usam characters entre o 32 e o 127 em comum, assim você pode pode chegar até aquele ponto no html sem atingir nenhum tipo de letra estranha.

Entretanto a meta tag tem de ser a primeira coisa no <head> porque senão, assim que o browser perceber tal tag ele vai parar de transpor a página para o usuário e começar a carregá-la novamente usando dessa vez o tipo de codificação especificado na meta tag.

O que os navegadores fazer se eles não acharem nenhum Content-Type, nem no header http, nem na meta tag? O Internet explorer na verdade faz uma coisa bem interessante: Ele tenta adivinhar, baseado na frequência que vários bytes aparecem em textos de diferentes tipos de codificação. Porque várias páginas 8 bits antigas tendem a botar seus characetes ‘nacionais’ entre 128 e 255, e porque toda linguagem humana tem diferentes historiografias do uso de letras, isso tem até uma chance de funcionar. É bem esquisito, mas parece funcionar com frequência suficiente para que programadores iniciantes, que nunca souberam que tinham de pôr a tag Content-Type no header, olharem para suas páginas e dizerem: “Parece bom”, até aquele dia em que escreverem alguma coisa que não bate exatamente com a frequência de distribuição de letras de sua lingua materna e o Internet Explorer exibe aquilo em Coreano, provando a lei de Postel sobre “Conservador no que você emite e liberal no que você aceita”. Não é na verdade um conceito de engenharia muito bom. De qualquer forma o que o pobre leitor desse site, que foi escrito em Bulgariano mas está aparecendo em Coreano, faz ? Ele usa o menu ‘Encoding’ (funcionalidade do IE) e tenta diferentes codificações (existem pelo menos uma duzia para línguas européias) até que tudo pareça de acordo. Isso se ele souber como se faz isso, a maioria das pessoas não sabe.

Para a versão mais recente do CitiDesk, o gerenciador de sites publicado pela minha empresa, nós decidimos fazer tudo internamente em UCS-2 (dois bytes) Unicode, o que é o que o Visual Basic e o Windows NT/2000/XP usa como seu tipo interno padrão de string. No C++ apenas declaramos strings como wchar_t (“wide char”) ao invés de char e usamos funções wcs ao invés de funções str (por exemplo wcscat e wcslen ao invés de strcat e strlen). Para criar uma string literal UCS-2 em C você põe apenas a letra L antes, então : L”Hello”.

QUando CityDesk publicou sua página, isso era convertido em codificação UTF-8, que será muito bem suportada pelos navegadores por muitos anos.

Esse artigo já ficou bem grande e eu nao consigo cobrir tudo o que tem para se conhecer sobre Unicode, mas eu espero que se você leu isso tudo, você saiba o suficiente para voltar para programação usando antibióticos ao invés de gambiarras e magias, uma tarefa que vou deixar para você agora.