Por Mariana Quirino Rodrigues dos Santos
Você já se deparou com esse caractere ‘�’ no meio de uma palavra e ficou sem saber o porquê disso acontecer? A resposta está num problema comum de encode. O encode nada mais é que um mapeamento de caracteres para um conjunto de bits.
Há alguns anos, os caracteres mais importantes eram representados pelo alfabeto inglês, e utilizavam a tabela ASCII, definida por 7 bits. Desse modo, existiam 128 sequências binárias, havendo assim 128 identificadores disponíveis para mapear cada um desses 128 caracteres, sendo eles: as letras do alfabeto inglês; os números de 0 a 9; alguns símbolos; e outros caracteres de controle. Assim, por exemplo, a palavra ’Hello’ na tabela ASCII, é representada pelos bits: 1001000 1100101 1101100 1101100 1101111; que, em hexadecimal, equivale a: 48 65 6C 6C 6F.
Os computadores da época já tinham suporte a 8 bits, totalizando 256 sequências binárias. Porém, desse total, 128 já estavam sendo ocupadas pela tabela ASCII. Logo, cada empresa começou a preencher as 128 sequências binárias restantes à sua maneira, e foi assim que surgiu o primeiro problema do encode.
Um computador comprado no Brasil exibe as letras acentuadas da língua portuguesa, como por exemplo a letra “é”. Porém, esse mesmo caractere em um computador comprado em outro país, pode acabar exibindo outra letra, e por consequência, a palavra formada com esse caractere será representada com erro. Para solucionar esse tipo de problema, surgiu o ANSI, que mantém a tabela ASCII, e define que cada país tenha uma página de código com uso dos 128 excedentes. Por exemplo, Israel tinha o codepage 862, já a Grécia tinha o codepage 737, e assim por diante.
Entretanto, ainda não era possível representar os caracteres asiáticos em 8 bits. Para solucionar esse problema, foi utilizado um conjunto de caracteres de byte duplo, o DBCS (Double-Byte Character Set). Essa codificação é representada por dois bytes, o que trouxe uma gama maior de possibilidades, muito além dos 256 normalmente disponíveis. No DBCS, podem ser representados até 65.536 caracteres, onde os 128 primeiros caracteres mantêm-se reservados com os valores da tabela ASCII, de modo que ‘Hello’ em ASCII tem os mesmos valores em bits que em DBCS. As línguas japonesa e chinesa são dois exemplos de idiomas que utilizam o conjunto de caracteres de byte duplo.
Outra solução para os problemas advindos da internacionalização dos caracteres, foi a criação do unicode, que também é dividido em páginas, onde para cada página existe uma sequência de caracteres, cada um representado por uma sequência de bits, ou seja, basicamente existe um identificador para cada caractere. Para a palavra ‘Hello’, por exemplo, temos a seguinte representação: U+0048 U+0065 U+006C U+006C U+006F. Porém, os americanos, querendo reduzir a quantidade de zeros, já que eles raramente usavam code points maiores que U+00FF, logo inventaram os formatos UTF (Formato de transformação unicode).
Os UTF-8, UTF-16 e UTF-32 são diferentes, mas codificam o mesmo padrão unicode. O UTF-8 mantém o mesmo padrão unicode, mas todo code point de 0 até 127 é armazenado em um único byte. No entanto, quando passa de 127, são utilizados mais bytes para representar os demais caracteres. Sendo assim, enquanto no unicode a palavra ‘Hello’ equivale a U+0048 U+0065 U+006C U+006C U+006F, em UTF-8 passa a ser 48 65 6C 6C 6F. Logo, um texto em UTF-8 para os americanos era igual a tabela ASCII, ANSI e todos os outros encodes que seguem esse mesmo padrão.
Dadas as diferentes codificações, existem 3 cenários que podem acontecer ao decodificar um texto:
- Você pode decodificar a string Unicode da palavra ‘Olá’ (U+004F U+006C U+C3A1) em ASCII, ou no OEM grego ou no ANSI hebraico, ou em qualquer outra das centenas de codificações que foram criadas até agora, e não encontrando o equivalente você poderá obter um ponto de interrogação (?), ou se você for realmente sortudo, um “?” em uma caixa: ‘�’. Como no exemplo a seguir:
ola = 'Olá' print(ola.decode('ascii', 'replace')) Ol��
Quando é feito o decode em UTF-8, ‘Olá’ é traduzido em hexadecimal como 4F(O) 6C(l) C3A1(á), porém quando é feito em ASCII, somente 4F(O) e 6C(l) são encontrados. Como não existe um caractere para C3 e nem A1 na tabela, é feito o replace com ‘�’.
- A string é decodificada com sucesso, ou seja, sem fazer a substituição do caractere por um ‘�’, porém a palavra não tem sentido pois o encode escolhido não é o correto. Como no exemplo a seguir, que foi decodificado em KOI8-U, quando o encode correto seria o UFT-8.
ola = 'Olá' print(ola.decode('koi8_u', 'replace')) Olц║
- A codificação da string está correta. O trecho de código a seguir mostra uma string decodificada sem erros, onde a palavra ‘Olá’ é a palavra esperada.
ola = 'Olá' print(ola.decode('utf-8', 'replace')) Olá
Com isso, podemos concluir que, para qualquer string que tratamos, é preciso saber a codificação utilizada para exibir corretamente, uma vez que ela está em memória, e-mail ou qualquer outro meio. Quando, para uma string, não está definida sua codificação, o que pode ser feito são tentativas de achar a codificação que garanta o menor número de ‘�’, porém ainda assim não há garantia de que a frase vá fazer sentido, uma vez que a codificação não foi definida.
Vejamos um exemplo no código abaixo, onde existe uma lista de encodes e um texto encodado, e é feito um loop testando cada encode e verificando a quantidade de replace por ‘�’; se esse valor for o menor, essa quantidade será guardada na variável “finalReplace”, e o encode realizado será armazenado. Desse modo, ao fim do loop será possível ter um encode que mais se aproxime do correto.
encodings = ['ascii', 'utf8', 'cp856', 'iso8859_6'] text = 'Ol\xc3\xa1' finalText = '' finalEncode = '' finalReplace = 0 for encoding in encodings: newText = text.decode(encoding, 'replace') replace = newText.find(u"�") if finalReplace == 0 or finalReplace > replace: finalReplace = replace finalEncode = encoding finalText = newText print(u'Encode: {0}, Replace: {1}, Text: {2}'.format(finalEncode, finalReplace, finalText))
Conclusão
Para concluir, ressaltamos a importância de sempre mandar o atributo charset, que serve para indicar o formato de codificação de caracteres utilizado no documento, seja nas requisições (Content-Type: text/plain; charset=”UTF-8″), no corpo de um email (Content-Type: text/plain; charset=”UTF-8″) ou até mesmo como um meta tag no HTML(<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8”>). Desse modo, será mais fácil garantir que os dados sejam processados devidamente, evitando perda de informação.