terça-feira, 21 de outubro de 2008

Porque usar UTF-8 - Codificando/Decodificando

Bom galera, como prometido venho hoje aqui demonstrar como o UTF-8 realmente funciona por debaixo dos panos, e vamos criar uma implementação do mesmo utilizando JavaScript.

Como eu já havia dito antes, o UTF-8 usa uma codificação de tamanho variável, onde cada caractere pode ocupar entre 1 e 4 bytes, para que isso seja possível, existem bits que são usados para verificar isso, vamos ver o primeiro caso:

Eu falei para vocês anteriormente que para os valores ASCII padrão o UTF-8 mantém a compatibilidade, ou seja, não existe nenhum transformação para os caracteres entre 0 e 127, se você olhar o binário disso, vai perceber que isso varia entre:
00000000
01111111

É importante notar que o primeiro bit do byte não é modificado, isso é importante porque ele é flag que indica se vamos precisar de mais bytes no caractere, então, a partir do valor 128 temos que utilizar algum tipo de transformação.

Antes de proseguirmos com os detalhes vamos iniciar nosso script, comecem ele da seguinte forma:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Conversor de UTF-8</title>
<script type="text/javascript" charset="utf-8">

get = function(id) {
return document.getElementById(id);
};

UTF8 = {
encode: function(content) {
return content;
},

decode: function(content) {
return content;
}
}

encodeFromTo = function(source, destiny, method) {
var value = get(source).value;

get(destiny).value = method(value);
};

window.onload = function() {
get('encode_button').onclick = function() {
encodeFromTo('original_area', 'encoded_area', UTF8.encode);
};

get('decode_button').onclick = function() {
encodeFromTo('encoded_area', 'original_area', UTF8.decode);
};
};

</script>
<style type="text/css" media="screen">

textarea {
width: 300px;
height: 200px;
}

</style>
</head>
<body>
<textarea id="original_area"></textarea><br />
<button type="button" id="encode_button">Codificar para UTF-8</button>
<button type="button" id="decode_button">Decodificar de UTF-8</button><br />
<textarea id="encoded_area"></textarea>
</body>
</html>

Esse é um simples script que por hora não faz nada :P

Mas ele pega o valor do campo de cima e mostra esse valor codificado no campo de baixo, assim como pega o campo de baixo e joga decoficidado no campo de cima. A partir de agora só iremos trabalhar nos métodos encode e decode do objeto UTF8 que criamos acima.

Voltando ao UTF-8, vamos criar primeiro o codificador, então vamos continuar o entendimento dos bytes.

A partir do momento que o primeiro bit deveria ser usado, então a coisa muda, imaginando um caso simples, o caractere de numero 128, se continuassemos usando a maneira simples de somar bits teriamos a seguinte sequencia de bits:
10000000
Mas para o UTF-8 a sequencia correta seria:
11000010 10000000
Parece complicado, mas não é para tanto, quando passamos dos 128 caracteres iniciais temos alguns "moldes" onde devemos encaixar nossos bits, que no caso são os seguintes (por uso de bytes):
1 byte - 0xxxxxxx
2 bytes - 110xxxxx 10xxxxxx
3 bytes - 1110xxxx 10xxxxxx 10xxxxxx
4 bytes - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Esses moldes não são ao acaso, e como pode-se ver eles seguem um padrão, esses moldes são importantes para que o precesso de decodificação, notem que a partir dos primeiros bits do primeiro byte é possivel saber exatamente quantos bytes tem o caractere atual.

Para criamos o processo de encoding, o primeiro passo é descobrir quantos bytes serão nescessários para criar nosso caractere no formato UTF-8, para saber isso é só contar os limites a partir do número de bits que podem ser alocados em cada nível. Mas como eu sou muito legal eu já vou disponibilar esses números para você, e são eles: 128, 2048, 65536

Pra quem nunca executou operações bit a bit pode parecer um pouco complicado sair mexendo eles por ai, mas é facil, primeiro mantenha a calculadora do windows aberta (em modo científico, assim você pode converter entre decimal, hexadecimal e binario sempre que precisar, e você vai precisar), então vamos criar a função de encode:
var encoded = ''; //iniciamos com uma string vazia

for (var i = 0; i < content.length; i++) { //iniciando uma iteracao sobre cada caractere da string
var c = content.charCodeAt(i); //pegando o codigo do caractere na posicao atual

//verificando numero de bytes
if (c < 128) { //1 byte
encoded += String.fromCharCode(c); //simplesmente recolocamos o caractere sem modificações
} else if (c < 2048) { //2 bytes
encoded += String.fromCharCode((c >> 6) | 0xC0); //primeiro caractere
encoded += String.fromCharCode((c & 0x3F) | 0x80); //segundo caractere
} else if (c < 65536) { //3 bytes
encoded += String.fromCharCode((c >> 12) | 0xE0); //primeiro caractere
encoded += String.fromCharCode(((c >> 6) & 0x3F) | 0x80); //segundo caractere
encoded += String.fromCharCode((c & 0x3F) | 0x80); //terceiro caractere
} else { //4 bytes
encoded += String.fromCharCode((c >> 18) | 0xF0); //primeiro caractere
encoded += String.fromCharCode(((c >> 12) & 0x3F) | 0x80); //segundo caractere
encoded += String.fromCharCode(((c >> 6) & 0x3F) | 0x80); //terceiro caractere
encoded += String.fromCharCode((c & 0x3F) | 0x80); //quarto caractere
}
}

return encoded;

Pra quem não está acostumado com manipulação de bits isso pode parecer coisa de louco, mas é simples, vou ilustrar como funcionam os operadores bit a bit utilizados nas operações:

right shift (>>): esse operador simplesmente move bits a direita, caso o bit ultrapasse o primeiro ele é descartado, exemplos:
00011000 >> 2
resulta em: 00000110
01101110 >> 3
resulta em: 00001101

ou seja, eh simplesmente mover bits a direita (se lembre, tudo isso é forma de representação, tudo são bits, desde numeros a caracteres, vc não vai digitar em formato binário nunca, pelo menos não em javascript)

E binário (&): executa uma operação E entre binários, a idéia é simples, você coloca os bits que são comparados em uma listagem de 1 para 1, caso o valor seja 1 nos 2, o resultádo será 1, caso contrário será 0, exemplos:
  00110111
& 01100010
----------
  00100010

  01111000
& 00111110
----------
  00111000

esse operador é muito usado para mascarar bits, por exemplo, você quer apenas os 4 últimos bits de um dado binário, então você opera um E contendo 00001111 sobre esse binário, dessa forma você terá o resultado com os 4 últimos valores.

OU binário (|): esse é parecido com o anterior, mas com uma diferença básica, esse retorn 1 exceto se os 2 operadores forem 0, exemplo:
  00110101
| 11100001
----------
  11110101

Isso são alguns operadores binários, caso não tenha ficado muito claro podem tirar suas dúvidas comigo por comentários ou e-mail.

Dever de casa: pegue os números utilizados no algoritmo anterior em formato hexadecimal (que comecam com 0x) e veja suas formas em binário (use a calculadora do windows ou similar), e va executando o algoritmo como se fosse o computador, dessa forma você terá um melhor entendimento do algoritmo.

O encode está pronto, agora precisamos pegar os códigos Unicode a partir disso que geramos, então vamos criar o decode:
var decoded = ''; //string vazia
var i = 0; //iterador
var n = c1 = c2 = c3 = c4 = 0; //buffers

while (i < content.length) {
c1 = content.charCodeAt(i);

if (c1 < 128) { //1 byte
decoded += String.fromCharCode(c1); //sem conversão, padrão ASCII
i++; //proximo byte
} else if (!(c1 & 0x20)) { //2 bytes
c2 = content.charCodeAt(i + 1);
decoded += String.fromCharCode(((c1 & 0x1F) << 6) | (c2 & 0x3F));
i += 2; //pular 2 bytes
} else if (!(c1 & 0x10)) { //3 bytes
c2 = content.charCodeAt(i + 1);
c3 = content.charCodeAt(i + 2);
decoded += String.fromCharCode(((c1 & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
i += 3; //pular 3 bytes
} else if (!(c1 & 0x8)) { //4 bytes
c2 = content.charCodeAt(i + 1);
c3 = content.charCodeAt(i + 2);
c4 = content.charCodeAt(i + 3);
decoded += String.fromCharCode(((c1 & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F));
i += 4; //pular 4 bytes
}
}

return decoded;


O processo inverso é exatamente a mesma coisa, é só pegar os bits que você jogou no formato e junta-los novamente. Para descobrir quantos bytes são usados eu utilizei a seguinte idéia:

1 - se for menor que 128, então é padrão ASCII, jogar direto
2 - verificar bit zero dentro do primeiro byte, a partir disso é possível descobrir o número (se baseando nos formatos possíveis).

O único operador novo é o left shift (<<) que simplesmente move os bits para esquerda ;)

Bom galera, termino por aqui, qualquer dúvida entrem em contato comigo por e-mail ou comentários.

Mudarei de assunto no próximo post (assunto indefinido até o momento ;).

See yah!

quarta-feira, 10 de setembro de 2008

Porque usar UTF-8

Olá, estou inaugurando esse meu novo blog hoje, esse blog será dedicado a desenvolvimento em geral, irei postar aqui coisas que já aprendi ao longo da minha vida de desenvolvedor e coisas que ainda aprendo (afinal, nesse mundo nós nunca paramos de aprender).

Hoje eu quero falar sobre um assunto do qual acredito que a maioria já deva ter passado, que é o clássico problema de encoding. Muitas pessoas apenas saem usando o encoding que funcionar na hora, mas na verdade não fazem a menor ideia do que os encodings são e muito menos porque eles existem (e pensam, por que simplesmente não adoptam apenas um encoding para acabar com essa pu*****), nesse artigo pretendo explicar o que são os encodings, porque existem, como funcionam, e como diz o título do artigo, porque devemos utilizar UTF-8. Agora chega de enrrolação e vamos ao que interessa :)

Como tudo começou

Para entendermos esse assunto que pode ser um tanto complicado, vamos começar do começo realmente, por que existem tantos formatos de encoding (que diga-se de passagem, normalmente nós brasileiros só lidamos com dois...).

Vamos voltar no tempo e lembrar dos computadores primórdios, naqueles tempos os computadores nem se imaginavam com letras, eles eram utilizados apenas para operações numéricas, então nem existiam caracteres, oque dizer então de processos de encoding.

O padrão ASCII

Muitos já devem no mínimo ter lido em algum lugar a sigla ASCII (American Standard Code for Information Interchange), pois é, o ASCII é um padrão americano para troca de mensagens (para textos) e esse padrão utiliza 7 bits para armazenar cada caractere, então, isso significa que ele suporta até 128 caracteres (que é o máximo número possível de se armazenar em 7 bits). Então essa é uma forma antiga de se conseguir reproduzir textos no computador, pois assim nós temos um mapeamento dos números com os caracteres. Dentro desses 128 caracteres do padrão ASCII, 33 não não imprimíveis, ou seja, caracteres de comando, e os outros 94 são imprimíveis (excepto o espaço), ou seja, são realmente coisas para aparecer na tela.

Para os americanos esse padrão da conta, mas como sabemos a informática cresceu, e esse padrão não dava conta de caracteres de outros locais como nossos acentos latinos por exemplo... Várias soluções devem ter sido tentadas para solucionar esse problema, mas eu sinceramente não tenho tanto conhecimento sobre esse meio tempo, então já irei pular para algo concreto que aconteceu.

Padrões ISO

Os mais entendidos já devem ter percebido algo estranho, o padrão ASCII usa 7 bits para representar os caracteres, mas a menor unidade que o computador trabalha é 1 byte (ou seja, 8 bits), então temos ai um bit sobrando em cada caractere que usamos, e se fizermos uso desse bit podemos duplicar a capacidade de 128 para 256 caracteres por byte, e é nesse ponto que entram os padrões ISO.

De certa forma todos os lugares do mundo usam os caracteres americanos, então eles se mantiveram estáticos nos seus 128 bits como já estavam antes, mas agora vinha o problema do que fazer com os outros 128 que agora seriam utilizados, é lógico que 128 caracteres a mais não são suficientes para comparar todos os caracteres do mundo, então foram criadas várias tabelas ISO, onde cada uma mapeava os 128 bits restantes para certa localidade, nós pode exemplo utilizamos muito o padrão ISO-8859-1 (caracteres latinos). Dessa forma temos um padrões para representar quaisquer caracteres do mundo, mantendo o padrão de 1 byte por caractere.

Então parece que as coisas estão ficando boas, mas resta um problema...

Caracteres de várias localidades juntos

Bom, como vimos ate o momento, e possível usar qualquer tipo de caractere se você estiver com o encoding correto, mas e se você tiver um site internacional, onde você precisa utilizar mais de um idioma ao mesmo tempo?

Para nos salvar então existem as codificações UTF (Unicode Transformation Format) onde e possível utilizar os caracteres diretamente a partir do seu código Unicode. Mas espera ai, o escopo dos códigos Unicode e imenso, seria um abuso gastar tantos bytes com cada caractere para podermos comportar os maiores... E por isso que o UTF existe, ele na verdade e um padrão de codificação de caracteres, existem vários algoritmos diferentes de UTF (UTF-1, UTF-7, UTF-8, UTF-16 e UTF-32), vou me reter nesse artigo ao mais utilizado, o UTF-8.

UTF-8

O UTF-8 utiliza um modo de codificação onde os caracteres tem tamanho variável (podem variar entre 1 e 4 bytes), ele usa um modelo nos bytes para que seja possível descobrir quantos bytes o caractere atual estará utilizando. Com a codificação UTF-8 temos acesso a um alcance da tabela unicode entre os caracteres de código U+0000 ate U+10FFFF, que são todos os caracteres formais da tabela Unicode, e garanto a vocês, é tudo que precisam, heheh.

Outro detalhe interessante do padrão UTF-8 e que ele é compatível com o padrão ASCII, então os caracteres entre 0 e 127 são escritos em UTF-8 de forma exatamente igual ao formato ASCII, dessa forma eles são compatíveis.

No próximo artigo estarei postando em detalhes como funciona a codificação do UTF-8, assim como criaremos um script conversor utilizando Javascript (ninguém tem desculpa pra dizer que não tem como usar, hehehe).

Resumo

Como pudemos ver o UTF-8 e uma evolução, e com a banda que temos hoje em dia o que ele aumenta no tamanho do texto e insignificante, sinceramente não existe motivo para utilizar os velhos padrões ISO, porque mesmo que hoje você não use caracteres fora do latino (e olha que usa, um bom exemplo são aquelas aspas que o word adora colocar, que não são aspas comuns e não estão disponíveis nos caracteres latinos, e pior, seus clientes adoram copiar texto do word) você ira poupar muitas dores de cabeça.

Esperam que tenham gostado do meu primeiro artigo nesse blog, estarei postando muitas coisas dos mais variados assuntos no ramo do desenvolvimento daqui para frente.

Grato a sua atenção, e até a próxima.