accueilLogicielsDéveloppement et Qualité Logiciels
 

Gestion des caractères accentués, et des caractères non ASCII en général


Licence Creative Commons
Ce(tte) œuvre est mise à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.

Introduction

Dans nos applications, nous devons gérer correctement des caractères qui comportent des accents, ou qui proviennent d’autres alphabets.

Un besoin récurrent est d’effectuer des recherches ou des tris sur les données que nos applications gèrent (rechercher sur un nom, trier sur une ville, etc.)

Pour ces opérations, on souhaite en général que le mot Élise soit trié avant le mot Emmanuel. Or, par défaut, ce n’est pas le cas, car le caractère É a un codage numérique plus grand que celui du caractère E.

Exemple en Groovy :

groovy:000> ["Élise", "Emmanuel"].sort()
===> [Emmanuel, Élise]

On souhaite également qu’une recherche sur toulouse retrouve bien Toulouse, ou encore que mercure soit avant Vénus :

groovy:000> ['mercure', 'Vénus'].sort()
===> [Vénus, mercure]

On a donc deux besoins : ignorer la casse, et ignorer les accents.

Normalisation des chaînes de caractères

Une solution est de stocker une version minuscule et sans accents de la chaîne de caractères dans notre base, de convertir l’entrée utilisateur de la même manière, et d’effectuer la recherche avec ces valeurs.

Par exemple, on aurait dans notre base :

{
 "firstName": "Élise",
 "normalizedFirstName": "elise"
}

Pour l’affichage, la valeur de firstName est utilisée, mais pour les tris et les recherches, c’est la valeur de normalizedFirstName qui sera utilisée.

Il nous faut donc un moyen de passer les données en minuscule, et d’enlever les accents.

Pour ignorer la casse, c’est facile : chaque langage possède une fonction toLowerCase ou équivalente.

groovy:000> "É".toLowerCase()
===> é

A noter toutefois certaines subtilités. Par exemple, le caractère Σ a pour minuscule ς à la fin d’un mot et σ ailleurs.

Pour les accents, on trouve des solutions telles que :

sed -i 'y/āáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜ/aaaaeeeeiiiioooouuuuüüüü/'

Cette solution est simple, mais insuffisante car elle ne couvre pas tous les cas possibles. Il manque par exemple les caractères ç, ñ, ś, etc.

Il faut partir du principe qu’il y aura forcément des cas non prévus.

Il existe un moyen de remplacer tous les caractères accentués par leur équivalent sans accent. Mais avant, regardons comment sont gérés les accents dans Unicode.

Jouons un peu avec le shell (Zsh ici, Bash doit utiliser une syntaxe plus complexe avec echo -e "\xc3\xa9")

La lettre e en unicode est codée par 0065

➜  ~  echo -e "\u0065"
e

La lettre é en unicode est codée par 00E9

➜  ~  echo -e "\u00e9"
é

Cependant, la lettre é est la lettre e avec un accent aigu. Il existe en unicode le caractère accent aigu, qui permet d’ajouter un accent sur la lettre qui le précède. Son petit nom est COMBINING ACUTE ACCENT et a le code 0301.

Que se passe-t-il si on affiche le caractère e avec ce caractère accent ?

➜  ~  echo -e "\u0065\u0301"

On obtient aussi la lettre é ! Cette lettre peut donc être codée de deux manières différentes avec Unicode.

Ces caractères qui modifient le comportement du caractère qui le précède sont appelés caractères diacritiques. On y retrouve évidemment les accents, mais aussi beaucoup d’autres

La lettre Z a le code 005A. On peut s’amuser avec plusieurs caractères diacritiques. Par exemple, on peut avoir un Z accent aigu, Z cédille, ou encore un Z avec un accent circonflexe en dessous (à noter que selon le navigateur et les jeux de caractères installés sur votre ordinateur, les caractères combinés peuvent ne pas s’afficher correctement) :

➜  ~  echo -e "\u005A\u0301"

➜  ~  echo -e "\u005A\u0327"

➜  ~  echo -e "\u005A\u032D"

On voit que si on arrive à décomposer tous les caractères accentués dans leur forme décomposée (autrement dit caractère de base + caractère diacritique), et que l’on supprime ces caractères diacritiques, alors on obtient la version sans accents de notre chaîne de caractères, de manière beaucoup plus exhaustive.

C’est exactement ce que nous allons faire.

Il existe plusieurs normalisations Unicode. Nous allons utiliser la forme NFD, qui décompose le caractère en ses différents éléments.

Ensuite, nous utiliserons une expression régulière pour supprimer les caractères diacritiques, qui font partie d’un bloc Unicode InCombiningDiacriticalMarks.

Exemple en Groovy :

groovy:000> import java.text.Normalizer
===> [import java.text.Normalizer]
groovy:000> Normalizer.normalize("ÀçÜṕñś", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
===> AcUpns

BONUS :

En ligne de commande, on peut utiliser iconv de la manière suivante :

➜  ~  echo "ÀçÜpñś" | iconv -f utf8 -t ascii//TRANSLIT      
AcUpns

Translitération

Cependant, cela n’est pas encore suffisant dans certains cas. Prenons la ville danoise de Køge :

groovy:000> Normalizer.normalize("Køge", Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "")
===> Køge

En fait la lettre ø est une lettre à part entière, pas juste un o avec une barre. Elle n’est donc pas décomposable. Pourtant, on aimerait bien pouvoir retrouver cette ville en tapant simplement koge.

Et de manière évidente, cela ne fonctionne pas non plus avec :

  • 東京 (Tokyo en japonais)
  • Αθήνα (Athènes en grec, même si l’accent sur ή est enlevé)
  • et de manière générale sur les alphabets non latins

Il nous faut alors un autre moyen.

Cet autre moyen s’appelle la translitération. Ce n’est plus juste de la suppression d’accents, mais cela transforme les mots d’un alphabet vers un autre (attention, à ne pas confondre à de la traduction).

C’est une opération extrémement complexe. Heureusement, il existe la librairie ICU qui fait cela. Elle propose des implémentations en C/C++ et Java.

L’idée ici est de partir d’une chaîne de caractères d’un alphabet donné, et de la transformer vers un autre alphabet.

On va le faire en deux étapes selon la philosophie de l’outil :

  • passer de n’importe quel alphabet vers un alphabet latin
  • passer de l’alphabet latin vers un alphabet ascii (qui supprimera les accents)

Avec un script Groovy, cela donne :

@Grab('com.ibm.icu:icu4j:55.1')
import com.ibm.icu.text.Transliterator

def transliterator = Transliterator.getInstance("Any-Latin;Latin-Ascii")
print transliterator.transliterate('Pariß, KØGE, 北京, Αθήνα').toLowerCase()

Résultat :

pariss, koge, bei jing, athena

On voit notamment que :

  • le ß est transformé en deux s
  • le Ø, bien que ce soit une lettre à part entière, a été transformée en O
  • les caractères chinois et grecs ont été transcodés en alphabet Latin puis Ascii

Mot de la fin

La translitération est une opération assez coûteuse, à utiliser donc quand le besoin est présent. Sinon, une simple décomposition avec les caractères diacritiques peut suffire si on connaît bien ou maîtrise les données reçues par notre application.

 

Stéphane DERACO
Envoyer un courriel

 


ARESU
Direction des Systèmes d'Information du CNRS

358 rue P.-G. de Gennes
31676 LABEGE Cedex

Bâtiment 1,
1 Place Aristide Briand
92195 MEUDON Cedex



 

 

Direction des Systèmes d'Information

Pôle ARESU

Accueil Imprimer Plan du site Credits