Caracteres invisíveis: por que o texto colado quebra o código
Um e-mail colado que não passa na validação, uma comparação que dá falso entre textos iguais, um JSON que não abre: muitas vezes a culpa é de um caractere invisível, como o espaço de largura zero (U+200B), o BOM (U+FEFF) ou o espaço não separável (U+00A0). Este guia mostra quais são os mais comuns, de onde vêm, como encontrá-los e como removê-los em JavaScript, Python e PHP.
Os suspeitos de sempre
U+00A0, espaço não separável: vem do de páginas e e-mails e de números formatados, como "1 000". Parece um espaço, mas não é igual a ele.U+200B, espaço de largura zero: não aparece. Vem de editores, sites e apps de mensagem, que o usam para permitir quebra de linha em palavras longas.U+FEFF, BOM (marca de ordem de bytes): fica no começo de arquivos salvos em "UTF-8 com BOM", como no Bloco de Notas antigo e no Excel. Quebra JSON e cabeçalhos de CSV.U+00AD, hífen opcional: só aparece quando a palavra quebra no fim da linha, mas continua no texto copiado.U+200DeU+200C: unem e separam caracteres, como os emojis de família. Dentro de um código ou de uma senha, só atrapalham.
Os sintomas
O texto parece certo, mas a comparação falha, a busca não encontra, a validação de e-mail recusa ou o JSON.parse acusa um erro na posição 0. O contador de caracteres dá uma pista: ele conta um caractere a mais do que você vê. Para ver qual é, cole o trecho na ferramenta de informações de caractere, que mostra o código de cada um e destaca os invisíveis.
Encontrando e removendo em código
A estratégia segura tem duas partes: trocar os espaços especiais por um espaço comum (para não grudar palavras) e apagar os caracteres de largura zero. Use isso na entrada de dados que vêm de cópia e cola, como e-mails, nomes de usuário, códigos e chaves.
// Espaços que parecem comuns, mas não são, e caracteres que nem aparecem na tela.
const ESPACOS_ESPECIAIS = /[ - ]/g;
const INVISIVEIS = /[]/g;
// Lista o que encontrou, com o código de cada caractere (U+200B...)
function encontrarInvisiveis(texto) {
return [...texto.matchAll(new RegExp(`${ESPACOS_ESPECIAIS.source}|${INVISIVEIS.source}`, 'g'))].map((m) => ({
posicao: m.index,
codigo: 'U+' + m[0].codePointAt(0).toString(16).toUpperCase().padStart(4, '0'),
}));
}
// Troca os espaços especiais por espaço comum e remove os invisíveis.
function limparInvisiveis(texto) {
return texto.replace(ESPACOS_ESPECIAIS, ' ').replace(INVISIVEIS, '');
}
const colado = 'maria@exemplo.com';
console.log(colado === 'maria@exemplo.com'); // false
console.log(encontrarInvisiveis(colado)); // [ { posicao: 5, codigo: 'U+200B' } ]
console.log(limparInvisiveis(colado) === 'maria@exemplo.com'); // true Lista as posições e limpa o texto.
import re
ESPACOS_ESPECIAIS = re.compile("[ - ]")
INVISIVEIS = re.compile("[]")
def encontrar_invisiveis(texto: str) -> list[tuple[int, str]]:
"""Posição e código (U+200B...) de cada espaço especial ou caractere invisível."""
padrao = re.compile(f"{ESPACOS_ESPECIAIS.pattern}|{INVISIVEIS.pattern}")
return [(m.start(), f"U+{ord(m.group()):04X}") for m in padrao.finditer(texto)]
def limpar_invisiveis(texto: str) -> str:
return INVISIVEIS.sub("", ESPACOS_ESPECIAIS.sub(" ", texto))
colado = "maria@exemplo.com"
print(colado == "maria@exemplo.com") # False
print(encontrar_invisiveis(colado)) # [(5, 'U+200B')]
print(limpar_invisiveis(colado) == "maria@exemplo.com") # True <?php
// Troca os espaços especiais por espaço comum e remove os caracteres invisíveis.
// O modificador u faz o PHP tratar o texto como UTF-8.
function limparInvisiveis(string $texto): string
{
$texto = preg_replace('/[\x{00A0}\x{1680}\x{2000}-\x{200A}\x{202F}\x{205F}\x{3000}]/u', ' ', $texto);
return preg_replace('/[\x{200B}\x{200C}\x{200D}\x{2060}\x{FEFF}\x{00AD}]/u', '', $texto);
}
$colado = "maria\u{200B}@exemplo.com";
var_dump($colado === 'maria@exemplo.com'); // bool(false)
var_dump(limparInvisiveis($colado) === 'maria@exemplo.com'); // bool(true) O modificador u é obrigatório para o PHP ler o texto como UTF-8.
Para limpar um texto sem programar, a ferramenta de remover espaços faz a mesma troca e diz quantos caracteres especiais encontrou.
Cuidados
- Não remova o
U+200Dde textos com emoji: ele junta 👨👩👧 em um só desenho. Limpe só campos técnicos, como e-mails e códigos. - O
U+00A0às vezes é desejado, como entre o número e a unidade ("10 km"), para não quebrar a linha. Em texto para exibição, pode ficar. - Ao ler arquivos, prefira tratar o BOM na leitura: em Python,
open(arquivo, encoding="utf-8-sig")remove o BOM sozinho.
Achou um erro neste guia? Avise por e-mail.
Ferramentas para usar junto
- Informações de CaractereVeja o código Unicode (U+), os bytes UTF-8, o escape para JavaScript, HTML e CSS de cada caractere, e encontre caracteres invisíveis
- Remover EspaçosRemova espaços duplos, espaços no começo e no fim das linhas, espaços invisíveis e linhas em branco
- Contador de CaracteresConte caracteres, com e sem espaços, palavras, linhas e parágrafos enquanto digita
Perguntas frequentes
Por que meu JSON dá erro na posição 0?
Quase sempre por causa do BOM, o caractere U+FEFF no começo do arquivo. Salve o arquivo como UTF-8 sem BOM ou remova o primeiro caractere antes de ler.
Como saber se um texto tem caracteres invisíveis?
Cole o texto na ferramenta de informações de caractere: os invisíveis aparecem destacados, com o código U+ de cada um. Em código, procure pelos códigos listados neste guia.
trim() remove esses caracteres?
Em parte. O trim do JavaScript remove o espaço não separável e o BOM das pontas, mas não remove o espaço de largura zero, e nada do meio do texto. O strip do Python também não remove o U+200B.
Outros guias
- Como gerar slug de URL a partir de texto com acentosComo transformar um título com acentos em slug de URL (Ação e Reação vira acao-e-reacao) em JavaScript, Python e PHP, e os cuidados com slugs repetidos.
- Como formatar e validar JSON em JavaScript, Python e PHPComo deixar JSON legível e descobrir o erro de um JSON inválido em JavaScript, Python e PHP, com os erros mais comuns e as diferenças entre as linguagens.