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!