Os problemas de codificação de caracteres afetam aplicações que lidam com texto internacional. Desde mojibake (caracteres corrompidos) até corrupção de dados, os problemas de codificação podem destruir a integridade dos dados e a experiência do utilizador. Compreender os fundamentos da codificação e conhecer técnicas de recuperação é essencial para qualquer programador que trabalhe com dados de texto. Este guia aborda conversões de codificação na perspetiva de um programador sénior.
Porque a Codificação é Importante
Um tratamento correto da codificação permite:
- Integridade dos Dados: Texto armazenado e recuperado corretamente
- Internacionalização: Suporte para todas as línguas
- Interoperabilidade: Troca de dados entre sistemas
- Experiência do Utilizador: Sem caracteres corrompidos
- Conformidade Legal: Tratamento adequado dos dados do utilizador
Compreender Codificações de Caracteres
Codificações Comuns
| Codificação | Bytes/Carácter | Caracteres | Caso de Uso |
|---|
| ASCII | 1 | 128 | Apenas inglês |
| ISO-8859-1 (Latin-1) | 1 | 256 | Europa Ocidental |
| Windows-1251 (cp1251) | 1 | 256 | Cirílico |
| Windows-1252 | 1 | 256 | Windows Ocidental |
| UTF-8 | 1-4 | 1,112,064 | Universal (recomendado) |
| UTF-16 | 2-4 | 1,112,064 | Internos do Windows |
Padrões de Bytes em UTF-8
O UTF-8 utiliza codificação de comprimento variável:
Range | Binary Pattern
------------------|------------------------------------
U+0000 to U+007F | 0xxxxxxx (1 byte)
U+0080 to U+07FF | 110xxxxx 10xxxxxx (2 bytes)
U+0800 to U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx (3 bytes)
U+10000 to U+10FFFF| 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (4 bytes)
Como Acontece o Mojibake
Original: "Привет" (Russian "Hello")
UTF-8 bytes: D0 9F D1 80 D0 B8 D0 B2 D0 B5 D1 82
Misinterpretation as cp1251:
D0 → Р
9F → (control char)
D1 → С...
Result: "Привет" → "Привет" (mojibake)
Problemas de Codificação em Bases de Dados
Configuração de Codificação no MySQL
-- Verificar as definições atuais
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';
-- Criação correta da base de dados
CREATE DATABASE mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- Tabela com a codificação correta
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
email VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Definições de ligação
SET NAMES 'utf8mb4';
SET CHARACTER_SET_CLIENT = 'utf8mb4';
SET CHARACTER_SET_CONNECTION = 'utf8mb4';
SET CHARACTER_SET_RESULTS = 'utf8mb4';
Codificação no PostgreSQL
-- Verificar a codificação
SHOW server_encoding;
SHOW client_encoding;
-- Criar base de dados com UTF-8
CREATE DATABASE mydb
ENCODING 'UTF8'
LC_COLLATE 'en_US.UTF-8'
LC_CTYPE 'en_US.UTF-8';
-- Definir a codificação do cliente
SET client_encoding TO 'UTF8';
Recuperar Dados Corrompidos
MySQL: Recuperar cp1251 a partir de Mojibake em UTF-8
Quando texto em cirílico foi armazenado com a codificação errada, o padrão «Привет» torna-se «Ð¿Ñ€Ð¸Ð²ÐµÑ‚»:
-- Diagnóstico: verificar o aspeto dos dados
SELECT
id,
text_column,
HEX(text_column) as hex_value
FROM broken_table
LIMIT 10;
-- Consulta de recuperação
SELECT
id,
CAST(
CONVERT(
CAST(CONVERT(text_column USING cp1251) AS BINARY)
USING utf8
)
AS CHAR CHARACTER SET cp1251
) COLLATE cp1251_general_ci AS fixed_text
FROM broken_table
WHERE text_column LIKE '%Ð%'; -- Padrão que indica mojibake
-- Atualizar dados corrompidos
UPDATE broken_table
SET text_column = CAST(
CONVERT(
CAST(CONVERT(text_column USING cp1251) AS BINARY)
USING utf8
)
AS CHAR CHARACTER SET cp1251
) COLLATE cp1251_general_ci
WHERE text_column REGEXP '^[Ð-ß]';
Compreender o Processo de Recuperação
A cadeia de conversão funciona porque:
CONVERT(text_column USING cp1251) - Interpreta bytes UTF-8 como cp1251CAST(... AS BINARY) - Obtém os bytes em brutoCONVERT(... USING utf8) - Reinterpreta os bytes como UTF-8- O cast final restaura para o charset de destino
PostgreSQL: Conversão de Codificação
-- Converter a codificação da coluna
UPDATE broken_table
SET text_column = convert_from(
convert_to(text_column, 'WIN1251'),
'UTF8'
)
WHERE text_column ~ '^[А-Яа-я]';
-- Ou usando bytea como intermédio
UPDATE broken_table
SET text_column = convert_from(
text_column::bytea,
'UTF8'
);
Tratamento de Codificação em PHP
Deteção e Conversão
<?php
// Detetar a codificação
function detectEncoding(string $text): string
{
$encodings = ['UTF-8', 'Windows-1251', 'ISO-8859-1', 'KOI8-R'];
foreach ($encodings as $encoding) {
if (mb_check_encoding($text, $encoding)) {
// Verificar por conversão de ida e volta
$converted = mb_convert_encoding($text, 'UTF-8', $encoding);
$back = mb_convert_encoding($converted, $encoding, 'UTF-8');
if ($back === $text) {
return $encoding;
}
}
}
return 'unknown';
}
// Converter para UTF-8
function toUtf8(string $text, ?string $fromEncoding = null): string
{
if ($fromEncoding === null) {
$fromEncoding = mb_detect_encoding($text, ['UTF-8', 'Windows-1251', 'ISO-8859-1'], true);
}
if ($fromEncoding === false || $fromEncoding === 'UTF-8') {
return $text;
}
return mb_convert_encoding($text, 'UTF-8', $fromEncoding);
}
// Corrigir UTF-8 com dupla codificação
function fixDoubleEncoding(string $text): string
{
// Verificar se parece UTF-8 com dupla codificação
if (preg_match('/[\xC0-\xDF][\x80-\xBF]/', $text)) {
$fixed = mb_convert_encoding($text, 'Windows-1251', 'UTF-8');
if (mb_check_encoding($fixed, 'UTF-8')) {
return $fixed;
}
}
return $text;
}
?>
Ligação à Base de Dados
<?php
// PDO com UTF-8
$pdo = new PDO(
'mysql:host=localhost;dbname=mydb;charset=utf8mb4',
$username,
$password,
[
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]
);
// MySQLi com UTF-8
$mysqli = new mysqli('localhost', $username, $password, 'mydb');
$mysqli->set_charset('utf8mb4');
// Verificar o charset da ligação
if ($mysqli->character_set_name() !== 'utf8mb4') {
throw new RuntimeException('Failed to set UTF-8 encoding');
}
?>
Cabeçalhos HTTP e HTML
<?php
// Definir a codificação da resposta
header('Content-Type: text/html; charset=UTF-8');
// Para respostas JSON
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($data, JSON_UNESCAPED_UNICODE);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
Tratamento de Codificação em Python
# Leitura de ficheiros com codificação
def read_file_safely(path: str) -> str:
encodings = ['utf-8', 'cp1251', 'iso-8859-1', 'koi8-r']
for encoding in encodings:
try:
with open(path, 'r', encoding=encoding) as f:
content = f.read()
# Verificar voltando a codificar
content.encode(encoding)
return content
except (UnicodeDecodeError, UnicodeEncodeError):
continue
raise ValueError(f"Could not decode {path} with any known encoding")
# Converter codificações
def convert_to_utf8(text: bytes, source_encoding: str = None) -> str:
if source_encoding:
return text.decode(source_encoding)
# Tentar deteção
import chardet
detected = chardet.detect(text)
return text.decode(detected['encoding'] or 'utf-8')
# Corrigir mojibake
def fix_mojibake(text: str) -> str:
try:
# Padrão comum: UTF-8 interpretado como cp1251
fixed = text.encode('cp1251').decode('utf-8')
return fixed
except (UnicodeDecodeError, UnicodeEncodeError):
return text
Codificação em JavaScript/Node.js
const iconv = require('iconv-lite');
// Converter o buffer para UTF-8
function toUtf8(buffer, encoding = 'win1251') {
return iconv.decode(buffer, encoding);
}
// Converter string para uma codificação diferente
function convertEncoding(text, from, to) {
const buffer = iconv.encode(text, from);
return iconv.decode(buffer, to);
}
// Leitura de ficheiro com codificação específica
const fs = require('fs');
function readFileWithEncoding(path, encoding = 'utf-8') {
const buffer = fs.readFileSync(path);
return iconv.decode(buffer, encoding);
}
// Middleware do Express.js para UTF-8
app.use((req, res, next) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
next();
});
Ferramentas de Linha de Comandos
iconv
# Converter a codificação do ficheiro
iconv -f CP1251 -t UTF-8 input.txt > output.txt
# Converter com transliteração para caracteres não mapeáveis
iconv -f CP1251 -t UTF-8//TRANSLIT input.txt > output.txt
# Verificar a codificação do ficheiro
file -bi document.txt
# Saída: text/plain; charset=utf-8
# Converter ficheiros em lote
for f in *.txt; do
iconv -f CP1251 -t UTF-8 "$f" > "${f%.txt}_utf8.txt"
done
Linha de Comandos do MySQL
# Importar com codificação
mysql --default-character-set=utf8mb4 -u user -p database < dump.sql
# Exportar com codificação
mysqldump --default-character-set=utf8mb4 database > dump.sql
Boas Práticas de Prevenção
Usar Sempre UTF-8
# Base de dados: UTF-8
# Ficheiros: UTF-8 com BOM para compatibilidade com Windows
# HTTP: Content-Type com charset
# HTML: <meta charset="UTF-8">
# API: JSON com UTF-8
Validar Input
<?php
function validateUtf8Input(string $input): string
{
if (!mb_check_encoding($input, 'UTF-8')) {
// Tentar corrigir ou rejeitar
$fixed = mb_convert_encoding($input, 'UTF-8', 'auto');
if (!mb_check_encoding($fixed, 'UTF-8')) {
throw new InvalidArgumentException('Invalid UTF-8 input');
}
return $fixed;
}
return $input;
}
?>
Principais Conclusões
- Por defeito, UTF-8: Use utf8mb4 no MySQL, UTF-8 em todo o resto
- Defina a codificação explicitamente: Nunca assuma a codificação por defeito
- Faça corresponder cliente e servidor: A ligação à base de dados tem de corresponder à codificação da base de dados
- Valide o input: Verifique a codificação antes de processar
- Teste com dados reais: Inclua caracteres internacionais nos dados de teste
- Documente a codificação: Indique a codificação esperada em APIs e formatos de ficheiro
A codificação de caracteres é um problema resolvido quando é tratada de forma consistente — a complexidade surge ao lidar com sistemas legados e dados corrompidos. Domine estas técnicas de recuperação e poupará inúmeras horas a depurar problemas de codificação.