Executando verificação de segurança...
24
kht
4 min de leitura ·

Removendo acentos (não só) em JavaScript (ou: Unicode e suas bizarrices)

Um problema relativamente comum é remover os acentos de um texto, e como todo problema comum, basta uma busca rápida pela Internet para encontrar soluções como esta (por exemplo, em JavaScript):

var semAcentos = string.normalize('NFD').replace(/[\u0300-\u036f]/g, "");

Entre outras variações. Mas vc já parou pra pensar como e por que isso funciona?


Formas de Normalização Unicode

O Unicode define várias formas de normalização, e o NFD do código acima é uma delas (as outras são NFC, NFKC e NFKD). Para uma descrição mais detalhada, sugiro que leia aqui (e siga também os links indicados para se aprofundar ainda mais). Inclusive, os próximos parágrafos abaixo foram retirados/adaptados da resposta contida no link, com a devida permissão do autor, que no caso sou eu mesmo :-)

De forma bem resumida, alguns caracteres possuem mais de uma forma de serem representados. Por exemplo, o á (letra a com acento agudo), segundo o Unicode, pode ser representado de duas maneiras:

  1. composta - como o code point U+00E1 (LATIN SMALL LETTER A WITH ACUTE) (á)
  2. decomposta - como uma combinação de dois code points (nesta ordem):

Code point é o valor numérico associado a cada caractere definido pelo Unicode - leia aqui para mais detalhes.

A primeira forma é chamada NFC (Canonical Composition), e a segunda, NFD (Canonical Decomposition). As duas formas acima são consideradas "canonicamente equivalentes", quando se trata de representar a letra a com acento agudo. Ou seja, são duas formas de se representar a mesma coisa.

O acento agudo (U+0301), neste caso, é um dos chamados combining diacritical marks (ou combining characters): caracteres que podem ser combinados com outros (como os acentos do português, por exemplo). Eles sempre aparecem depois do caractere ao qual se aplicam (no exemplo acima, ele aparece depois do a).

Visualmente, não há diferença, e tanto a forma NFC quanto a NFD são mostradas como "á". Somente ao "escovar os bits" e verificar os code points um a um é que podemos saber em qual forma está:

// transformar string em array de code points, mostrar o valor em hexadecimal
function codepoints(s) {
    return Array.from(s).map(c => c.codePointAt(0).toString(16));
}

// NFC, apenas um code point
console.log(codepoints('á')); // [ 'e1' ]

// NFD, dois code points
console.log(codepoints('á')); // [ '61', '301' ]

Ao testar o código acima, faça copy-paste de tudo. Se vc digitar na mão o á, provavelmente não vai funcionar porque - pela minha percepção - os editores de texto costumam dar preferência à forma NFC.

Enfim, por isso a forma ingênua e mais trabalhosa de remover acentos (fazer uma série de replace(/á/g, 'a'), replace(/é/g, 'e'), etc) nem sempre pode funcionar, pois se o primeiro argumento do replace está em uma forma e a string está em outra, nada será substituído:

function removeAcento(str) {
    // remove somente o acento do "a"
    return str.replace(/á/g, 'a');
}

// string em NFC, remove
console.log(removeAcento('á')); // a

// string em NFD, não remove
console.log(removeAcento('á')); // á

Quando normalizamos para NFD, o caractere acentuado é "separado" do acento. Depois, o replace remove os acentos, e o que sobra são as letras sem acentuação.

No caso, o intervalo [\u0300-\u036f] cobre vários caracteres, inclusive acentos que não são usados em português.

Se quiser ser mais específico e considerar apenas os acentos agudo, circunflexo, til e grave, poderia trocar para /[\u0300-\u0303]/g. Ou ainda /[\u0300-\u0303\u0327]/g para que remova também a cedilha (ou seja, ç seria trocado por c). E também daria para ser mais abrangente e considerar qualquer tipo de acento de qualquer idioma, usando /\p{M}/gu - o Unicode Property Escape \p{M} considera qualquer caractere que esteja nas categorias "Mark" (todas que começam com "M" desta lista), nos quais os acentos estão inclusos.

Se vc está mexendo somente com textos em português, não fará tanta diferença usar um ou outro, mas se tiverem outros idiomas envolvidos, verifique as diferentes opções para que incluam os acentos desejados.


Por fim, vale lembrar que as formas de normalização são definidas pelo Unicode e não são exclusividade do JavaScript. Muitas linguagens também possuem formas similares de obter o mesmo resultado. Por exemplo, em Python seria:

from unicodedata import normalize, combining

def remove_acentos(string):
    return ''.join(c for c in normalize('NFD', string) if not combining(c))

print(remove_acentos('sabiá')) # sabia

Em Java:

import java.text.Normalizer;

public class Exemplo {
    public static String removeAcentos(String str) {
        return Normalizer.normalize(str, Normalizer.Form.NFD).replaceAll("\\p{M}", "");
    }
}

E muitas outras também possuem suporte à normalização e formas de verificar se um caractere é combining e pode ser removido.

Carregando publicação patrocinada...
2

Obrigado pela aula!

Geralmente a gente acaba pegando alguma solução pronta na Internet, e acaba não entendendo o que se passa por de baixo dos panos.

1

Eu que agradeço!

Unicode, encodings e como tratar corretamente caracteres é um dos assuntos que muita gente tem dificuldade.

Pena que é um assunto negligenciado, muita gente chega no mercado sem ter a menor ideia de como funciona, e a grande maioria dos cursos também passa batido por isso. Eu mesmo comecei a aprender de verdade só depois de uns anos trabalhando (e apanhando). Se eu tivesse aprendido antes, teria poupado muita dor de cabeça. Espero que seja útil pra mais gente também.

2

Tinha que ter uma área separada para matérias assim, joias de conhecimento 💎 que vamos ter que guardar, pois um dia vamos precisar disso.

Parabéns e obrigado por compartilhar com nós.

1

Obrigado! :-)

Unicode, encodings e como tratar corretamente caracteres é um dos assuntos que muita gente tem dificuldade.

Pena que é um assunto negligenciado, muita gente chega no mercado sem ter a menor ideia de como funciona, e a grande maioria dos cursos também passa batido por isso. Eu mesmo comecei a aprender de verdade só depois de uns anos trabalhando (e apanhando). Se eu tivesse aprendido antes, teria poupado muita dor de cabeça. Espero que seja útil pra mais gente também.

1
1

Não é a mesma coisa.

Dessa forma vc remove não apenas a própria letra acentuada, mas também os espaços e pontuações, que nem sempre é o desejado.

Por exemplo, se a string for "Olá, tudo bem?":

var string = "Olá, tudo bem?";

// usando o método já descrito
console.log(string.normalize('NFD').replace(/[\u0300-\u036f]/g, ""));
// Ola, tudo bem?

// buscando somente letras sem acento e números
console.log([...string.matchAll(/[a-zA-Z0-9]+/g)].join(''));
// Oltudobem

O primeiro remove somente o acento e mantém o restante, e o resultado é Ola, tudo bem?. Já o segundo remove os espaços e pontuações, além da própria letra acentuada, e o resultado é Oltudobem.