Programação Por Equipe DevBras

Capa do artigo: Acentos quebrados no HTML: como ler o sintoma e achar a camada errada

Acentos quebrados no HTML: como ler o sintoma e achar a camada errada

"ã" e o losango com interrogação contam histórias diferentes. Aprenda a ler cada sintoma, conferir cabeçalho, meta e arquivo, e use entidade só para o que precisa.

O site da Padaria São João (fictícia) mostra no cardápio "Pão de açúcar" e, na página de contato, "Rua da Concei��o". Alguém sugere trocar cada letra acentuada por &atilde; e &ccedil;. Funciona, e transforma o texto numa sopa que ninguém mais consegue editar. A resposta curta: salve os arquivos em UTF-8, declare <meta charset="utf-8"> no começo do <head> e garanta que o servidor envie charset=utf-8 no cabeçalho, ou não envie charset nenhum. Com as três camadas de acordo, "ação" é "ação". Entidade fica só para os poucos caracteres que o HTML reserva.

O mais útil, porém, é aprender a ler o sintoma. "ã" e "�" parecem o mesmo defeito e apontam para causas opostas.

Por que o acento quebra: bytes lidos com a tabela errada

Um arquivo de texto é uma sequência de bytes. A codificação é a tabela que diz qual letra cada byte representa. Em UTF-8, o "ã" ocupa dois bytes, C3 A3. Na codificação antiga ISO-8859-1 (e na sua prima Windows-1252), o mesmo "ã" é um byte só, E3. O navegador não adivinha: ele usa a codificação que alguém declarou e lê os bytes com essa tabela.

  • "ã", "ç", "ú": o arquivo está em UTF-8, mas o navegador leu como Windows-1252. Cada letra acentuada vira duas: o C3 é "Ã" e o A3 é "£". A causa está na declaração, no cabeçalho ou na meta tag.
  • "�" (losango com interrogação): o navegador leu como UTF-8, mas o byte veio em Latin-1. O E3 sozinho não forma um caractere UTF-8 válido, e o navegador o troca pelo caractere de substituição. A causa está no arquivo ou no dado que veio do banco.

Saber isso já corta metade da investigação. No exemplo da padaria, o cardápio (com "ã") é um HTML salvo em UTF-8 e servido com declaração errada. O endereço (com "�") vem do banco, que entrega Latin-1 para uma página UTF-8.

Quem decide a codificação da página

O navegador segue uma ordem de prioridade definida na especificação do HTML:

  1. A marca BOM no início do arquivo, se existir.
  2. O cabeçalho HTTP Content-Type: text/html; charset=....
  3. A meta tag <meta charset>, que precisa estar nos primeiros 1024 bytes do documento.
  4. Se nada disso existir, uma adivinhação do navegador, que varia.

A consequência prática: uma meta tag correta não vence um cabeçalho errado. Se o servidor diz charset=iso-8859-1, a sua meta UTF-8 é ignorada. É por isso que tanta gente "coloca a meta e não resolve".

Como diagnosticar e corrigir, passo a passo

  1. Veja o cabeçalho que o servidor envia.
  2. Veja a meta tag no código-fonte da página (Ctrl+U), logo no início do <head>.
  3. Veja a codificação do arquivo no disco.
  4. Converta o arquivo uma única vez, se ele estiver em Latin-1.
  5. Alinhe o servidor para enviar UTF-8.
  6. Se o defeito só aparece em texto do banco, siga para a conexão e as colunas.
$ curl -sI https://padaria.exemplo.com.br/cardapio | grep -i content-type
Content-Type: text/html; charset=iso-8859-1

$ file -bi cardapio.html
text/html; charset=utf-8

$ printf 'ã' | xxd
00000000: c3a3                                     ..

O diagnóstico do cardápio está na primeira linha: o arquivo é UTF-8, o servidor anuncia ISO-8859-1. O xxd confirma que o seu terminal grava "ã" como c3 a3, ou seja, UTF-8.

Se o arquivo estivesse em Latin-1, a conversão seria feita uma vez, gerando um arquivo novo:

$ iconv -f WINDOWS-1252 -t UTF-8 contato-antigo.html > contato.html

No editor, o VS Code mostra a codificação na barra de status; clicando nela, há as opções de reabrir e de salvar com outra codificação. Não converta um arquivo que já está em UTF-8: o resultado é "ã" gravado de verdade, e aí o defeito deixa de ser de leitura. O post acento não se conserta duas vezes trata desse estrago.

A declaração na página e no servidor

<!DOCTYPE html>
<html lang="pt-BR">
<head>
  <meta charset="utf-8">
  <title>Cardápio da Padaria São João</title>
</head>
# Apache (VirtualHost ou .htaccess)
AddDefaultCharset UTF-8

# nginx (bloco server)
charset utf-8;

Em PHP, a diretiva default_charset já vem como UTF-8 nas versões atuais, e é ela que entra no cabeçalho das páginas geradas. Se algum código antigo chama header('Content-Type: text/html; charset=iso-8859-1'), ele vence tudo; procure por charset no projeto inteiro.

Quando a entidade HTML ainda é necessária

Com UTF-8 de ponta a ponta, qualquer letra de qualquer idioma pode ser escrita diretamente. Entidades continuam necessárias só para caracteres que o HTML usa como marcação:

  • &lt; e &gt; para mostrar os sinais de menor e maior como texto;
  • &amp; para o "e comercial";
  • &quot; para aspas dentro de um atributo delimitado por aspas;
  • &nbsp; quando você quer de propósito um espaço que não quebra linha.

Em PHP, ao imprimir texto vindo do usuário ou do banco, use htmlspecialchars($texto, ENT_QUOTES, 'UTF-8'). Ele escapa exatamente esses caracteres reservados e protege contra injeção de HTML. Evite htmlentities para isso: ele também converte cada letra acentuada em entidade, o que é desnecessário em UTF-8 e deixa o código-fonte ilegível. A diferença entre uma coisa e outra está detalhada em entidade HTML existe para caractere especial.

Quando o problema vem do banco

Se o HTML estático aparece certo e só o texto do banco quebra, a página não é a culpada. A conexão precisa declarar UTF-8 (no MySQL, utf8mb4) e as colunas precisam estar nessa codificação. O guia acentos que quebram só quando vêm do MySQL faz esse caminho com calma. Corrigir na tela, com utf8_encode ou substituições, esconde a coluna errada e quebra de novo no próximo relatório.

Erros comuns

  • Trocar todas as letras por entidades. O texto fica ilegível no código e o problema real, a declaração, continua lá.
  • Colocar a meta charset e ignorar o cabeçalho. O cabeçalho HTTP tem prioridade e a página continua quebrada.
  • Meta charset depois de scripts e estilos longos. Fora dos primeiros 1024 bytes, ela pode não ser considerada.
  • Converter um arquivo que já era UTF-8. O texto corrompido passa a ser gravado em disco e a correção vira trabalho manual.
  • Usar htmlentities para escapar saída. Funciona, mas enche o HTML de entidades sem necessidade.
  • Remendar na tela o texto que vem do banco. A causa continua na conexão ou na coluna e aparece em outro lugar.

Checklist

  • O cabeçalho Content-Type diz charset=utf-8 (confira com curl -I).
  • <meta charset="utf-8"> é a primeira tag do <head>.
  • Os arquivos do projeto estão salvos em UTF-8.
  • Nenhum código do projeto força outro charset por header().
  • A conexão com o banco e as colunas de texto usam utf8mb4.
  • A saída dinâmica passa por htmlspecialchars com ENT_QUOTES e 'UTF-8'.
  • Entidades aparecem só para caracteres reservados.

Escreva uma frase com "ç", "ã" e aspas numa página do seu site e outra num registro do banco, e abra o código-fonte. As duas estão legíveis no fonte e na tela, ou uma delas mostra "ã" e a outra um losango, dizendo exatamente em qual camada procurar?

Avalie este artigo

Referência: http://www.erikasarti.com/html/acentuacao-caracteres-especiais/