Existe-t-il un moyen de se débarrasser des accents et de convertir une chaîne entière en lettres régulières?

263

Existe-t-il un meilleur moyen de se débarrasser des accents et de rendre ces lettres régulières en dehors de l'utilisation de la String.replaceAll()méthode et du remplacement des lettres une par une? Exemple:

Contribution: orčpžsíáýd

Production: orcpzsiayd

Il n'a pas besoin d'inclure toutes les lettres avec des accents comme l'alphabet russe ou le chinois.

Martin
la source

Réponses:

387

Utilisez java.text.Normalizerpour gérer cela pour vous.

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

Cela séparera toutes les marques d'accent des caractères. Ensuite, il vous suffit de comparer chaque caractère à une lettre et de jeter ceux qui ne le sont pas.

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

Si votre texte est en unicode, vous devez utiliser ceci à la place:

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

Pour unicode, \\P{M}correspond au glyphe de base et \\p{M}(en minuscules) correspond à chaque accent.

Merci à GarretWilson pour le pointeur et regular-expressions.info pour le grand guide unicode.

Erick Robertson
la source
7
Cela compile l'expression régulière à chaque fois, ce qui est bien si vous n'en avez besoin qu'une seule fois, mais si vous devez le faire avec beaucoup de texte, la pré-compilation de l'expression régulière est une victoire.
David Conrad
3
Notez que toutes les lettres latines ne se décomposent pas en accents ASCII +. Cela tuera par exemple. "Latin {majuscule, petite} lettre l avec trait" utilisé en polonais.
Michał Politowski
12
C'est une bonne approche, mais supprimer tous les caractères non ASCII est exagéré et supprimera probablement les choses que vous ne voulez pas, comme d'autres l'ont indiqué. Il serait préférable de supprimer toutes les "marques" Unicode; y compris les marques sans espacement, les marques d'espacement / de combinaison et les marques de délimitation. Vous pouvez le faire avec string.replaceAll("\\p{M}", ""). Voir regular-expressions.info/unicode.html pour plus d'informations.
Garret Wilson
4
Vous voudrez probablement utiliser Normalizer.Form.NFKD plutôt que NFD - NFKD convertira des choses comme les ligatures en caractères ascii (par exemple fi en fi), NFD ne le fera pas.
chesterm8
2
@ chesterm8, fait intéressant, NFKD convertit "fi" en "fi", mais ne convertit pas "Æ" en "AE". Je suppose que je devrai faire apparaître les données Unicode pour savoir pourquoi, mais ce n'était pas ce à quoi je m'attendais.
Garret Wilson du
136

Depuis 2011, vous pouvez utiliser Apache Commons StringUtils.stripAccents (entrée) (depuis 3.0):

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

Remarque:

La réponse acceptée (celle d'Erick Robertson) ne fonctionne pas pour Ø ou £. Apache Commons 3.5 ne fonctionne pas non plus pour Ø, mais il fonctionne pour Ł. Après avoir lu l'article Wikipedia pour Ø , je ne suis pas sûr qu'il devrait être remplacé par "O": c'est une lettre distincte en norvégien et danois, alphabétisée après "z". C'est un bon exemple des limites de l'approche "strip accents".

DavidS
la source
2
Je vois qu'il y a un rapport de bogue ouvert pour £, @KarolS. Quelqu'un a soumis une demande d'extraction, mais elle a échoué à certains tests et n'a pas été mise à jour depuis juillet de l'année dernière.
DavidS
1
Il y a eu une mise à jour il y a 5 jours et la demande d'extraction a été fusionnée.
EpicPandaForce
6
Commons Lang 3.5 a été publié il y a plusieurs jours. J'ai confirmé que cela fonctionne maintenant sur Ł. Cela ne fonctionne pas sur Ø. En lisant l'article Wiki pour Ø , je ne suis pas sûr qu'il devrait être remplacé par "O": c'est une lettre séparée en norvégien et danois, alphabétisée après "z". C'est un bon exemple des limites de l'approche "strip accents".
DavidS
2
Si vous ne souhaitez pas inclure la bibliothèque , vous pouvez prendre les deux méthodes impliquées dans cette fonctionnalité facilement de la source à commons.apache.org/proper/commons-lang/apidocs/src-html/org/...
lujop
2
En tant que Danois, le ø danois / norvégien tout comme le français œ et l'allemand / suédois / hongrois / estonien etc. ö est un moyen rapide d'écrire oe. Donc, selon votre objectif, cela peut être la substitution que vous souhaitez.
Ole VV
57

La solution de @ virgo47 est très rapide, mais approximative. La réponse acceptée utilise Normalizer et une expression régulière. Je me demandais quelle partie du temps était prise par Normalizer par rapport à l'expression régulière, car la suppression de tous les caractères non ASCII peut se faire sans expression régulière:

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();
    }
}

De petites accélérations supplémentaires peuvent être obtenues en écrivant dans un char [] et en n'appelant pas toCharArray (), bien que je ne suis pas sûr que la diminution de la clarté du code le mérite:

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);
}

Cette variation a l'avantage de la justesse de celle qui utilise le normalisateur et d'une partie de la vitesse de celle qui utilise une table. Sur ma machine, celle-ci est environ 4x plus rapide que la réponse acceptée, et 6,6x à 7x plus lente que celle de @ virgo47 (la réponse acceptée est environ 26 fois plus lente que @ virgo47 sur ma machine).

David Conrad
la source
2
outdoit être redimensionné pour correspondre au nombre de caractères valides javant d'être utilisé pour construire l'objet chaîne.
Lefteris E
4
J'ai une objection à cette solution. Imaginez l'entrée "æøåá". Le courant flattenToAsciicrée le résultat "aa .." où les points représentent \ u0000. Ce n'est pas bon. La première question est - comment représenter les personnages "non normalisables"? Disons que ce sera?, Ou nous pouvons laisser NULL car là, mais dans tous les cas, nous devons conserver la position correcte de ceux-ci (tout comme la solution regex). Pour cela, le if dans la boucle doit être quelque chose comme: if (c <= '\u007F') out[j++] = c; else if (Character.isLetter(c)) out[j++] = '?';Il va ralentir un peu, mais il doit être correct en premier lieu. ;-)
virgo47
Annonce mon dernier commentaire (dommage qu'ils ne puissent pas être plus longs) - peut-être que la prise positive ( isLetter) n'est pas la bonne, mais je n'ai pas trouvé mieux. Je ne suis pas un expert Unicode, donc je ne sais pas comment mieux identifier la classe du caractère unique qui remplace le caractère original. Les lettres fonctionnent bien pour la plupart des applications / utilisations.
virgo47
1
Vous voudrez probablement utiliser Normalizer.Form.NFKD plutôt que NFD - NFKD convertira des choses comme les ligatures en caractères ascii (par exemple fi en fi), NFD ne le fera pas.
chesterm8
2
Pour nous, nous voulions supprimer complètement le personnage. Pour m'assurer qu'il n'y avait pas de caractère nul à la fin, je les ai supprimés avec un autre constructeur String: return new String (out, 0, j);
Mike Samaras
30

EDIT: Si vous n'êtes pas coincé avec Java <6 et que la vitesse n'est pas critique et / ou que la table de traduction est trop limitative, utilisez la réponse de David. Le point est d'utiliser Normalizer(introduit dans Java 6) au lieu de la table de traduction à l'intérieur de la boucle.

Bien que ce ne soit pas une solution "parfaite", cela fonctionne bien lorsque vous connaissez la gamme (dans notre cas Latin1,2), fonctionnait avant Java 6 (ce n'est pas un vrai problème cependant) et est beaucoup plus rapide que la version la plus suggérée (peut ou peut pas un problème):

    /**
 * 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);
}

Les tests sur mon HW avec JDK 32 bits montrent que cela effectue la conversion de àèéľšťč89FDČ à aeelstc89FDC 1 million de fois en ~ 100 ms tandis que le mode Normalizer le fait en 3,7 secondes (37 fois plus lentement). Si vos besoins concernent les performances et que vous connaissez la plage d'entrée, cela peut être pour vous.

Prendre plaisir :-)

virgo47
la source
1
Une grande partie de la lenteur de la version suggérée est due à l'expression régulière, pas au Normalizer. Utiliser Normalizer mais supprimer les caractères non-ASCII `` à la main '' est plus rapide, bien que toujours pas aussi rapide que votre version. Mais cela fonctionne pour tout Unicode au lieu de seulement latin1 et latin2.
David Conrad
J'ai développé cela pour travailler avec plus de caractères, pastebin.com/FAAm6a2j , notez que cela ne fonctionnera pas correctement avec des caractères multichargés tels que DŽ (DZ). Il n'en produira qu'un seul personnage. De plus, ma fonction utilise char au lieu de chaînes, ce qui est plus rapide si vous manipulez de toute façon char, donc vous n'avez pas besoin de convertir.
James T
Hé, je ne comprends pas à quoi correspondent ces lettres sur le champ tab00c0? par exemple "AAAAAAACEEEEIIII" ou "lLlNnNnNnnNnOoOo" etc. Jamais vu auparavant. Où les avez-vous trouvés? Aussi pourquoi n'utilisez-vous pas simplement les codes correspondants?
ThanosFisherman
@ThanosF essaie simplement de parcourir le code (avec un débogueur si nécessaire). Ce que cela fait, c'est pour chaque caractère d'une chaîne: "Ce caractère est-il compris entre \ u00c0 et \ u017f? Si oui, remplacez-le par un caractère ASCII 7 bits du tableau." Le tableau ne couvre que deux pages d'encodage (Latin 1 et 2) avec leurs équivalents 7 bits. Donc, si c'est un caractère avec le code \ u00e0 (à), il prendra son approximation à 7 bits de la 32ème position de la table (e0-c0 = 32) - c'est-à-dire "a". Certains caractères ne sont pas des lettres, ceux-ci y sont laissés avec leur code.
virgo47
Merci pour votre explication. Où puis-je trouver ces pages d'encodage afin de pouvoir étendre cette variable à ma langue? (Grec) La réponse acceptée fait déjà le travail en remplaçant les lettres accentuées grecques mais je voulais aussi essayer votre méthode et exécuter des
tests
22
System.out.println(Normalizer.normalize("àèé", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", ""));

travaillé pour moi. La sortie de l'extrait ci-dessus donne "aee" qui est ce que je voulais, mais

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

n'a fait aucune substitution.

Nico
la source
1
Confirmer cela ... normalement ASCII fonctionne très bien, mais j'ai rencontré ce problème sur Linux (64b) avec JRockit (1.6.0_29 64b). Je ne peux pas le confirmer avec une autre configuration, je ne peux pas confirmer cette corellation, mais je peux confirmer que l'autre solution suggérée a fonctionné et pour cela je vote pour celle-ci. :-) (BTW: Il a fait un remplacement, mais pas assez, il a changé Ú en U par exemple, mais pas á en a.)
virgo47
1
Vous voudrez probablement utiliser Normalizer.Form.NFKD plutôt que NFD - NFKD convertira des choses comme les ligatures en caractères ascii (par exemple fi en fi), NFD ne le fera pas.
chesterm8
@KarolS Je ne vois aucun d'entre eux contenant des accents
eis
@eis Une barre oblique sur une lettre compte comme un signe diacritique: en.wikipedia.org/wiki/Diacritic Et si vous optez pour une définition plus stricte d'un "accent" comme sur cette page Wikipédia, la tréma n'est pas un accent, donc la réponse de Nico est toujours faux.
Karol S
6

Selon la langue, ceux-ci peuvent ne pas être considérés comme des accents (qui changent le son de la lettre), mais comme des signes diacritiques

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

"Le bosniaque et le croate portent les symboles č, ć, đ, š et ž, qui sont considérés comme des lettres distinctes et sont répertoriés comme tels dans les dictionnaires et autres contextes dans lesquels les mots sont répertoriés par ordre alphabétique."

Les supprimer peut être intrinsèquement changer le sens du mot, ou changer les lettres en des lettres complètement différentes.

NinjaCat
la source
5
D'accord. Par exemple en suédois: "höra" (entendre) -> "hora" (putain)
Christoffer Hammarström
14
Peu importe ce qu'ils signifient. La question est de savoir comment les supprimer.
Erick Robertson
7
Erick: Ça compte comme on les appelle. Si la question demande comment supprimer les accents, et si ce ne sont pas des accents, la réponse n'est peut-être pas simplement comment supprimer toutes ces choses qui ressemblent à des accents. Bien que cela devrait probablement être un commentaire et non une réponse.
Smig
4
Je pense que le cas d'utilisation normal pour cela est la recherche, en particulier la recherche de langues mixtes, souvent avec un clavier anglais en entrée, auquel cas il vaut mieux obtenir des faux positifs que des faux négatifs.
nilskp
3

J'ai rencontré le même problème lié à la vérification de l'égalité des chaînes, l'une des chaînes de comparaison a le code de caractère ASCII 128-255 .

ie, Espace insécable - [Hex - A0] Espace [Hex - 20]. Pour afficher l'espace insécable sur HTML. J'ai utilisé ce qui suit spacing entities. Leur caractère et ses octets sont comme&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()));

Sortie en octets:

S1: [77, 121,, 3283, 97, 109, 112, 108, 101,, 3283, 112, 97, 99, 101,, 3268, 97, 116, 97] S2: [77, 121,, -30, -128, -12583, 97, 109, 112, 108, 101,, -30, -128, -12583, 112, 97, 99, 101,, -30, -128, -12568, 97, 116, 97]

Utilisez le code ci-dessous pour différents espaces et leurs codes d'octets: 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 Translittérations ASCII de la chaîne Unicode pour Java. unidecode

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

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

    Pour le codage d'URL pour l'espace, utilisez la bibliothèque de goyaves.

    String encodedString = UrlEscapers.urlFragmentEscaper().escape(inputString);
  • ➩ Pour surmonter ce problème utilisé String.replaceAll()avec certains 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(" ", " ");
  • ➩ Utilisation de java.text.Normalizer.Form . Cette énumération fournit des constantes des quatre formulaires de normalisation Unicode qui sont décrits dans l' Annexe 15 de la norme Unicode - Formulaires de normalisation Unicode et deux méthodes pour y accéder.

    entrez la description de l'image ici

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

Test de String et de sorties sur différentes approches comme ➩ 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 );

Utiliser Unidecode est le best choice, Mon code final illustré ci-dessous.

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
la source
3

Je suggère Junidecode . Il gérera non seulement «Ł» et «Ø», mais il fonctionne également bien pour la transcription d'autres alphabets, tels que le chinois, en alphabet latin.

OlgaMaciaszek
la source
1
Semble prometteur, mais je souhaite que ce soit un projet plus actif / maintenu et disponible sur Maven.
Phil
2

La solution @David Conrad est la plus rapide que j'ai essayée en utilisant le Normalizer, mais elle a un bug. Il supprime essentiellement les caractères qui ne sont pas des accents, par exemple les caractères chinois et d'autres lettres comme æ, sont tous supprimés. Les caractères que nous voulons supprimer sont des marques sans espacement, des caractères qui ne prennent pas de largeur supplémentaire dans la chaîne finale. Ces caractères de largeur nulle finissent essentiellement par être combinés dans un autre caractère. Si vous pouvez les voir isolés en tant que personnage, par exemple comme ceci `, je suppose que c'est combiné avec le caractère espace.

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
la source
1

L'une des meilleures façons d' utiliser l'expression régulière et le normaliseur si vous n'avez pas de bibliothèque est:

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

C'est plus efficace que replaceAll ("[^ \ p {ASCII}]", "")) et si vous n'avez pas besoin de signes diacritiques (comme votre exemple).

Sinon, vous devez utiliser le modèle p {ASCII}.

Cordialement.

Zhar
la source
0

Je pense que la meilleure solution est de convertir chaque caractère en HEX et de le remplacer par un autre HEX. C'est parce qu'il existe 2 types Unicode:

Composite Unicode
Precomposed Unicode

Par exemple, "Ồ" écrit par Composic Unicode est différent de "Ồ" écrit par Precomposed Unicode. Vous pouvez copier mes exemples de caractères et les convertir pour voir la différence.

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

J'ai développé cette fonctionnalité pour que certaines banques convertissent les informations avant de les envoyer à la banque principale (ne prennent généralement pas en charge Unicode) et j'ai rencontré ce problème lorsque les utilisateurs finaux utilisent plusieurs types de caractères Unicode pour saisir les données. Je pense donc que la conversion en HEX et son remplacement est le moyen le plus fiable.

Hoang Tran
la source
-1

Si quelqu'un s'efforce de le faire dans kotlin, ce code fonctionne comme un charme. Pour éviter les incohérences, j'utilise également .toUpperCase et Trim (). alors je jette cette fonction:

   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()

}

pour utiliser ces amusants, lancez le code comme ceci:

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

     str = stripAccents(str) //call the function
Thiago Silva
la source