Existe uma maneira de se livrar dos acentos e converter uma sequência inteira em letras regulares?

263

Existe uma maneira melhor de se livrar dos acentos e tornar essas letras regulares, além de usar o String.replaceAll()método e substituir as letras uma a uma? Exemplo:

Entrada: orčpžsíáýd

Resultado: orcpzsiayd

Não é necessário incluir todas as letras com acentos, como o alfabeto russo ou o chinês.

Martin
fonte

Respostas:

387

Use java.text.Normalizerpara lidar com isso para você.

string = Normalizer.normalize(string, Normalizer.Form.NFD);
// or Normalizer.Form.NFKD for a more "compatable" deconstruction 

Isso separará todas as marcas de destaque dos caracteres. Então, você só precisa comparar cada personagem com uma letra e jogar fora os que não são.

string = string.replaceAll("[^\\p{ASCII}]", "");

Se o seu texto estiver em unicode, você deve usá-lo:

string = string.replaceAll("\\p{M}", "");

Para unicode, \\P{M}corresponde ao glifo base e \\p{M}(em minúsculas) corresponde a cada acento.

Agradecemos a GarretWilson pelo ponteiro e regular-expressions.info pelo excelente guia unicode.

Erick Robertson
fonte
7
Isso compila a expressão regular toda vez, o que é bom se você precisar apenas uma vez, mas se precisar fazer isso com muito texto, pré-compilar a regex é uma vitória.
David Conrad
3
Observe que nem todas as letras baseadas em latim são decompostas em toques ASCII +. Isso vai matar, por exemplo. "Latina {maiúscula} letra l com traço" usada em polonês.
Michał Politowski
12
Essa é uma boa abordagem, mas remover todos os caracteres não ASCII é um exagero e provavelmente removerá coisas que você não deseja, como outros indicaram. Seria melhor remover todas as "marcas" Unicode; incluindo marcas de não espaçamento, marcas de espaçamento / combinação e marcas de fechamento. Você pode fazer isso com string.replaceAll("\\p{M}", ""). Veja regular-expressions.info/unicode.html para mais informações.
Garret Wilson
4
Você provavelmente deseja usar o Normalizer.Form.NFKD em vez do NFD - o NFKD converterá coisas como ligaduras em caracteres ascii (por exemplo, fi para fi), o NFD não fará isso.
chesterm8
2
@ chesterm8, curiosamente, o NFKD está convertendo "fi" para "fi", mas não está convertendo "Æ" para "AE". Acho que vou ter que trazer os dados Unicode para descobrir o porquê, mas não era o que eu esperava.
Garret Wilson
136

A partir de 2011, você pode usar o Apache Commons StringUtils.stripAccents (input) (desde 3.0):

    String input = StringUtils.stripAccents("Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ");
    System.out.println(input);
    // Prints "This is a funky String"

Nota:

A resposta aceita (Erick Robertson) não funciona para Ø ou Ł. O Apache Commons 3.5 também não funciona para Ø, mas funciona para Ł. Depois de ler o artigo da Wikipedia sobre Ø , não tenho certeza se deve ser substituído por "O": é uma letra separada em norueguês e dinamarquês, alfabetizada após "z". É um bom exemplo das limitações da abordagem "tira sotaques".

DavidS
fonte
2
Vejo que há um relatório de bug aberto para Ł , @KarolS. Alguém enviou uma solicitação de recebimento, mas falhou em alguns testes e não foi atualizada desde julho do ano passado.
21416 DavidSão Paulo
1
Houve uma atualização há 5 dias e a solicitação de recebimento foi mesclada.
EpicPandaForce
6
O Commons Lang 3.5 foi lançado há alguns dias. Confirmei que funciona agora. Não funciona com Ø. Lendo o artigo da Wiki sobre Ø , não tenho certeza se ele deve ser substituído por "O": é uma letra separada em norueguês e dinamarquês, alfabetizada após "z". É um bom exemplo das limitações da abordagem "tira sotaques".
Davids
2
Se você não deseja incluir a biblioteca, pode usar
lujop
2
Como dinamarquês, o dinamarquês / norueguês ø assim como o francês œ e o alemão / sueco / húngaro / estoniano etc. ö se originam como uma maneira curta de escrever oe. Portanto, dependendo do seu objetivo, essa pode ser a substituição que você deseja.
Ole VV
57

A solução do @ virgo47 é muito rápida, mas aproximada. A resposta aceita usa o Normalizador e uma expressão regular. Gostaria de saber qual parte do tempo foi gasta pelo Normalizer versus a expressão regular, pois a remoção de todos os caracteres não ASCII pode ser feita sem uma expressão regular:

import java.text.Normalizer;

public class Strip {
    public static String flattenToAscii(String string) {
        StringBuilder sb = new StringBuilder(string.length());
        string = Normalizer.normalize(string, Normalizer.Form.NFD);
        for (char c : string.toCharArray()) {
            if (c <= '\u007F') sb.append(c);
        }
        return sb.toString();
    }
}

Pequenas acelerações adicionais podem ser obtidas escrevendo um char [] e não chamandoCharCharrray (), embora eu não tenha certeza de que a diminuição na clareza do código o mereça:

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    string = Normalizer.normalize(string, Normalizer.Form.NFD);
    int j = 0;
    for (int i = 0, n = string.length(); i < n; ++i) {
        char c = string.charAt(i);
        if (c <= '\u007F') out[j++] = c;
    }
    return new String(out);
}

Essa variação tem a vantagem da correção de quem usa o Normalizer e da velocidade de quem usa uma tabela. Na minha máquina, essa é cerca de 4x mais rápida que a resposta aceita e 6,6x a 7x mais lenta que a @ virgo47 (a resposta aceita é 26x mais lenta que a @ virgo47 na minha máquina).

David Conrad
fonte
2
outdeve ser redimensionado para corresponder ao número de caracteres válidos jantes de ser usado para construir o objeto de sequência.
Lefteris E
4
Eu tenho uma objeção a esta solução. Imagine a entrada "æøåá". A corrente flattenToAsciicria o resultado "aa .." onde os pontos representam \ u0000. Isso não é bom. A primeira pergunta é - como representar caracteres "anormais"? Digamos que será?, Ou podemos deixar o caractere NULL lá, mas, em qualquer caso, temos que preservar a posição correta deles (assim como a solução regex). Para isso, o if no loop deve ser algo como: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';Ele diminuirá a velocidade um pouco, mas deve estar correto em primeiro lugar. ;-)
virgo47 17/08/2015
Anúncio meu último comentário (que pena que não pode demorar mais) - talvez a tomada positiva ( isLetter) não seja a correta, mas não achei melhor. Como não sou especialista em Unicode, não sei como identificar melhor a classe do caractere único que substitui o caractere original. As letras funcionam bem para a maioria dos aplicativos / usos.
Virgo47 17/08/2015
1
Você provavelmente deseja usar o Normalizer.Form.NFKD em vez do NFD - o NFKD converterá coisas como ligaduras em caracteres ascii (por exemplo, fi para fi), o NFD não fará isso.
chesterm8
2
Para nós, queríamos remover completamente o personagem. Para garantir que não havia caracteres nulos à direita, os removi com um construtor String alternativo: return new String (out, 0, j);
Mike Samaras
30

EDIT: Se você não está preso ao Java <6 e a velocidade não é crítica e / ou a tabela de conversão é muito limitadora, use a resposta de David. O objetivo é usar Normalizer(introduzido no Java 6) em vez da tabela de conversão dentro do loop.

Embora essa não seja uma solução "perfeita", ela funciona bem quando você conhece o intervalo (no nosso caso, Latin1,2), trabalhou antes do Java 6 (embora não seja um problema real) e é muito mais rápido que a versão mais sugerida (pode ou pode não seja um problema):

    /**
 * Mirror of the unicode table from 00c0 to 017f without diacritics.
 */
private static final String tab00c0 = "AAAAAAACEEEEIIII" +
    "DNOOOOO\u00d7\u00d8UUUUYI\u00df" +
    "aaaaaaaceeeeiiii" +
    "\u00f0nooooo\u00f7\u00f8uuuuy\u00fey" +
    "AaAaAaCcCcCcCcDd" +
    "DdEeEeEeEeEeGgGg" +
    "GgGgHhHhIiIiIiIi" +
    "IiJjJjKkkLlLlLlL" +
    "lLlNnNnNnnNnOoOo" +
    "OoOoRrRrRrSsSsSs" +
    "SsTtTtTtUuUuUuUu" +
    "UuUuWwYyYZzZzZzF";

/**
 * Returns string without diacritics - 7 bit approximation.
 *
 * @param source string to convert
 * @return corresponding string without diacritics
 */
public static String removeDiacritic(String source) {
    char[] vysl = new char[source.length()];
    char one;
    for (int i = 0; i < source.length(); i++) {
        one = source.charAt(i);
        if (one >= '\u00c0' && one <= '\u017f') {
            one = tab00c0.charAt((int) one - '\u00c0');
        }
        vysl[i] = one;
    }
    return new String(vysl);
}

Testes no meu HW com JDK de 32 bits mostram que isso realiza a conversão de àèéľšťč89FDČ para aeelstc89FDC 1 milhão de vezes em ~ 100ms, enquanto o modo Normalizador o torna em 3,7s (37x mais lento). Caso suas necessidades estejam relacionadas ao desempenho e você saiba o intervalo de entrada, isso pode ser para você.

Aproveitar :-)

virgo47
fonte
1
Muita lentidão da versão sugerida se deve à expressão regular, não ao Normalizador. Usar o Normalizer, mas remover os caracteres não ASCII 'manualmente', é mais rápido, embora ainda não tão rápido quanto a sua versão. Mas funciona para todo o Unicode, em vez de apenas latin1 e latin2.
David Conrad
Expandi isso para trabalhar com mais caracteres, pastebin.com/FAAm6a2j . Observe que não funcionará corretamente com caracteres multicharros, como DŽ (DZ). Só produzirá 1 caractere. Além disso, minha função usa char em vez de strings, o que é mais rápido se você estiver lidando com char de qualquer maneira, para que você não precise converter.
James T
Ei, eu não entendo o que significam essas letras no campo tab00c0? por exemplo "AAAAAAACEEEEIIII" ou "lLlNnNnNnnNnOoOo" etc. etc. Nunca os vi antes. Onde você achou eles? Além disso, por que você não usa apenas os códigos de correspondência de cores?
ThanosFisherman
O @ThanosF apenas tenta passar pelo código (com depurador, se necessário). O que isso faz é para cada caractere em uma string: "Esse caractere está entre \ u00c0 e \ u017f? Nesse caso, substitua-o por um caractere ASCII de 7 bits da tabela." A tabela cobre apenas duas páginas de codificação (latino 1 e 2) com seus equivalentes de 7 bits. Portanto, se for um caractere com o código \ u00e0 (à), ele terá sua aproximação de 7 bits da 32ª posição da tabela (e0-c0 = 32) - isto é "a". Alguns caracteres não são letras, são deixados lá com seu código.
precisa saber é o seguinte
Obrigado pela sua explicação. Onde posso encontrar essas páginas de codificação para estender essa variável ao meu idioma? (Grego) Aceitos resposta já faz o trabalho substituindo letras acentuadas gregos, mas eu queria experimentar o seu método e também executar alguns benchmarks :)
ThanosFisherman
22
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

trabalhou para mim. A saída do snippet acima fornece "aee", que é o que eu queria, mas

System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", ""));

não fez nenhuma substituição.

Nico
fonte
1
Confirmando isso ... normalmente o ASCII funciona muito bem, mas encontrei esse problema no Linux (64b) com o JRockit (1.6.0_29 64b). Não posso confirmá-lo com nenhuma outra configuração, não posso confirmar essa corelação, mas posso confirmar que a outra solução sugerida funcionou e, por isso, votei nesta. :-) (BTW: Ele fez alguma substituição, mas não o suficiente, mudou U para U por exemplo, mas não um a um.)
virgo47
1
Você provavelmente deseja usar o Normalizer.Form.NFKD em vez do NFD - o NFKD converterá coisas como ligaduras em caracteres ascii (por exemplo, fi para fi), o NFD não fará isso.
chesterm8
@KarolS Não vejo nenhum deles com sotaques
eis
@eis Uma barra ao longo de uma carta conta como diacrítico: en.wikipedia.org/wiki/Diacritic E se você seguir uma definição mais rigorosa de "sotaque", como na página da Wikipedia, a diaerese não é um sotaque, então a resposta de Nico ainda está errado.
Karol S
6

Dependendo do idioma, esses podem não ser considerados acentos (que alteram o som da letra), mas marcas diacríticas

https://en.wikipedia.org/wiki/Diacritic#Languages_with_letters_containing_diacritics

"Bósnio e croata têm os símbolos č, ć, đ, š e ž, que são considerados letras separadas e são listados como tal em dicionários e outros contextos nos quais as palavras são listadas de acordo com a ordem alfabética."

Removê-los pode estar inerentemente alterando o significado da palavra ou mudando as letras para letras completamente diferentes.

NinjaCat
fonte
5
Acordado. Por exemplo, em sueco: "höra" (ouvir) -> "hora" (prostituta)
Christoffer Hammarström
14
Não importa o que eles significam. A questão é como removê-los.
Erick Robertson
7
Erick: Importa como são chamados. Se a pergunta perguntar como remover acentos, e se não forem acentos, a resposta pode não ser apenas como remover todas as coisas que parecem acentos. Embora isso provavelmente deva ser um comentário e não uma resposta.
Smig 24/10/2013
4
Acho que o caso de uso normal para isso é a pesquisa, principalmente a pesquisa de idiomas mistos, geralmente com um teclado em inglês como entrada; nesse caso, é melhor obter falsos positivos do que falsos negativos.
Nskp 19/09/14
3

Eu enfrentei o mesmo problema relacionado à verificação de igualdade de Strings. Uma das strings de comparação possui o código de caracteres ASCII 128-255 .

ou seja, Espaço sem quebra - [Hex - A0] Espaço [Hex - 20]. Para mostrar espaço sem interrupção sobre HTML. Eu usei o seguinte spacing entities. Seu caráter e seus bytes são como&emsp is very wide space[ ]{-30, -128, -125}, &ensp is somewhat wide space[ ]{-30, -128, -126}, &thinsp is narrow space[ ]{32} , Non HTML Space {}

String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
System.out.format("S1: %s\n", java.util.Arrays.toString(s1.getBytes()));
System.out.format("S2: %s\n", java.util.Arrays.toString(s2.getBytes()));

Saída em bytes:

S1: [77, 121,, 3283, 97, 109, 112, 108, 101,, 3283, 112, 97, 99, 101 32, 68, 97, 116, 97] S2: [77, 121 -30, -128, -125, 83, 97, 109, 112, 108, 101, -30, -128, -12583, 112, 97, 99, 101 -30, -128, -125, 68, 97, 116, 97]

Use o código abaixo para diferentes espaços e seus códigos de bytes: wiki for List_of_Unicode_characters

String spacing_entities = "very wide space,narrow space,regular space,invisible separator";
System.out.println("Space String :"+ spacing_entities);
byte[] byteArray = 
    // spacing_entities.getBytes( Charset.forName("UTF-8") );
    // Charset.forName("UTF-8").encode( s2 ).array();
    {-30, -128, -125, 44, -30, -128, -126, 44, 32, 44, -62, -96};
System.out.println("Bytes:"+ Arrays.toString( byteArray ) );
try {
    System.out.format("Bytes to String[%S] \n ", new String(byteArray, "UTF-8"));
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
  • Transl Transliterações ASCII da cadeia Unicode para Java. unidecode

    String initials = Unidecode.decode( s2 );
  • ➩ usando Guava: Google Core Libraries for Java.

    String replaceFrom = CharMatcher.WHITESPACE.replaceFrom( s2, " " );

    Para codificação de URL para o espaço, use Guava laibrary.

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • ➩ Para superar esse problema usado String.replaceAll()com alguns RegularExpression.

    // \p{Z} or \p{Separator}: any kind of whitespace or invisible separator.
    s2 = s2.replaceAll("\\p{Zs}", " ");
    
    
    s2 = s2.replaceAll("[^\\p{ASCII}]", " ");
    s2 = s2.replaceAll(" ", " ");
  • ➩ Usando java.text.Normalizer.Form . Essa enumeração fornece constantes dos quatro formulários de normalização Unicode descritos no Anexo Padrão 15 do Unicode - Formulários de Normalização Unicode e dois métodos para acessá-los.

    insira a descrição da imagem aqui

    s2 = Normalizer.normalize(s2, Normalizer.Form.NFKC);

Testando String e saídas em diferentes abordagens, como id Unidecode, Normalizer, StringUtils .

String strUni = "Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß";

// This is a funky String AE,O,D,ss
String initials = Unidecode.decode( strUni );

// Following Produce this o/p: Tĥïŝ ĩš â fůňķŷ Šťŕĭńġ Æ,Ø,Ð,ß
String temp = Normalizer.normalize(strUni, Normalizer.Form.NFD);
Pattern pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
temp = pattern.matcher(temp).replaceAll("");

String input = org.apache.commons.lang3.StringUtils.stripAccents( strUni );

Usando Unidecode é o best choice, Meu código final mostrado abaixo.

public static void main(String[] args) {
    String s1 = "My Sample Space Data", s2 = "My Sample Space Data";
    String initials = Unidecode.decode( s2 );
    if( s1.equals(s2)) { //[ , ] %A0 - %2C - %20 « http://www.ascii-code.com/
        System.out.println("Equal Unicode Strings");
    } else if( s1.equals( initials ) ) {
        System.out.println("Equal Non Unicode Strings");
    } else {
        System.out.println("Not Equal");
    }

}
Yash
fonte
3

Eu sugiro Junidecode . Ele manipulará não apenas 'Ł' e 'Ø', mas também funcionará bem para transcrever de outros alfabetos, como o chinês, para o alfabeto latino.

OlgaMaciaszek
fonte
1
Parece promissor, mas eu gostaria que este fosse um projeto mais ativo / mantido e disponível no Maven.
Phil
2

A solução @David Conrad é a mais rápida que tentei usar o Normalizer, mas tem um bug. Ele basicamente retira caracteres que não são acentuados, por exemplo, caracteres chineses e outras letras como æ, são todos removidos. Os caracteres que queremos extrair são marcas sem espaçamento, caracteres que não ocupam largura extra na sequência final. Esses caracteres de largura zero basicamente acabam combinados em algum outro caractere. Se você pode vê-los isolados como um personagem, por exemplo, `` meu palpite é que é combinado com o caractere de espaço.

public static String flattenToAscii(String string) {
    char[] out = new char[string.length()];
    String norm = Normalizer.normalize(string, Normalizer.Form.NFD);

    int j = 0;
    for (int i = 0, n = norm.length(); i < n; ++i) {
        char c = norm.charAt(i);
        int type = Character.getType(c);

        //Log.d(TAG,""+c);
        //by Ricardo, modified the character check for accents, ref: http://stackoverflow.com/a/5697575/689223
        if (type != Character.NON_SPACING_MARK){
            out[j] = c;
            j++;
        }
    }
    //Log.d(TAG,"normalized string:"+norm+"/"+new String(out));
    return new String(out);
}
Ricardo Freitas
fonte
1

Uma das melhores maneiras de usar o regex e o Normalizer se você não tiver uma biblioteca é:

    public String flattenToAscii(String s) {
                if(s == null || s.trim().length() == 0)
                        return "";
                return Normalizer.normalize(s, Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
}

Isso é mais eficiente que replaceAll ("[^ \ p {ASCII}]", "")) e se você não precisar de sinais diacríticos (como no seu exemplo).

Caso contrário, você precisará usar o padrão p {ASCII}.

Saudações.

Zhar
fonte
0

Eu acho que a melhor solução é converter cada caractere para HEX e substituí-lo por outro HEX. É porque existem 2 digitações Unicode:

Composite Unicode
Precomposed Unicode

Por exemplo, "Ồ" escrito por Composite Unicode é diferente de "Ồ" escrito por Precomposed Unicode. Você pode copiar meus caracteres de amostra e convertê-los para ver a diferença.

In Composite Unicode, "Ồ" is combined from 2 char: Ô (U+00d4) and ̀ (U+0300)
In Precomposed Unicode, "Ồ" is single char (U+1ED2)

Desenvolvi esse recurso para alguns bancos para converter as informações antes de enviá-las ao banco central (geralmente não suportam Unicode) e enfrentei esse problema quando os usuários finais usam várias digitações Unicode para inserir os dados. Então eu acho que converter para HEX e substituí-lo é a maneira mais confiável.

Hoang Tran
fonte
-1

Caso alguém esteja lutando para fazer isso no kotlin, esse código funcionará como um encanto. Para evitar inconsistências, também uso .toUpperCase e Trim (). então eu lancei esta função:

   fun stripAccents(s: String):String{

   if (s == null) {
      return "";
   }

val chars: CharArray = s.toCharArray()

var sb = StringBuilder(s)
var cont: Int = 0

while (chars.size > cont) {
    var c: kotlin.Char
    c = chars[cont]
    var c2:String = c.toString()
   //these are my needs, in case you need to convert other accents just Add new entries aqui
    c2 = c2.replace("Ã", "A")
    c2 = c2.replace("Õ", "O")
    c2 = c2.replace("Ç", "C")
    c2 = c2.replace("Á", "A")
    c2 = c2.replace("Ó", "O")
    c2 = c2.replace("Ê", "E")
    c2 = c2.replace("É", "E")
    c2 = c2.replace("Ú", "U")

    c = c2.single()
    sb.setCharAt(cont, c)
    cont++

}

return sb.toString()

}

Para usá-los, transmita o código da seguinte maneira:

     var str: String
     str = editText.text.toString() //get the text from EditText
     str = str.toUpperCase().trim()

     str = stripAccents(str) //call the function
Thiago Silva
fonte