Supprimer les accents des caractères accentués

Voilà un grand classique pour nous autres peuples de caractères : comment supprimer les accents des caractères accentués de notre sublime langue en utilisant PHP ? Mais avant de répondre à cette question, une autre se forme, insidieuse : pourquoi supprimer les accents des caractères accentués ? Voici quelques exemples envisageables :

  • Transformer une chaine pour qu'elle soit plus lisible dans une URL  : "severite" au lieu de "s%C3%A9v%C3%A9rit%C3%A9"
  • Transformer une chaine pour obtenir un nom de fichier propre
  • Pouvoir enfin ordonner les clés accentuées de nos tableaux associatifs PHP et ne plus retrouver « événement » après « zéro » !

Solution toute nulle

C'est ce dernier exemple qui m'a conduit à rechercher une solution viable. En effet, malgré tous les efforts de Google je n'ai réussi à trouver que des exemples utilisant des remplacements de caractères du style :

<?php

$str = strtr($str'ÁÀÂÄÃÅÇÉÈÊËÍÏÎÌÑÓÒÔÖÕÚÙÛÜÝ''AAAAAACEEEEEIIIINOOOOOUUUUY');
$str = strtr($str'áàâäãåçéèêëíìîïñóòôöõúùûüýÿ''aaaaaaceeeeiiiinooooouuuuyy');

?>

Évidement, ça a l'air sexy et simple, mais si comme moi vous travaillez en utf-8, les caractères accentués sont bien souvent encodés avec plus de 8bits (un caractère en ASCII), mais jusqu'à 32bits. Ainsi, les techniques basées sur une substitution de caractères sont totalement obsolètes parce qu'elles produisent des résultats erronés. C'est triste, mais c'est vrai.

Comment faire alors ? Sommes-nous condamnés par des siècles d'histoire ?

Solution qui pourrait être géniale

Parce que, comme moi, vous dévorez la documentation de PHP chaque soir avant de vous coucher, vous avez peut-être remarqué la fonction iconv() et vous pensez, comme j'ai pu le faire, que c'est la solution parfaite pour notre affaire… si seulement. La fonction iconv() est donc sensée supporter la translittération, mais il semblerait que le résultat soit très variable d'un système à l'autre. En effet, pour la même version de PHP voici les résultats que j'obtiens à vouloir enlever les accents de « L'été est là »:

<?php

$str = "L'été est là";

iconv($charset'ASCII//TRANSLIT//IGNORE'$str);

// Linux : L'ete est la
// MacOSX et Windows : L''et'e est l`a

?>

Alors voilà, c'est ridicule. Si on ne peut pas compter sur un résultat homogène, autant l'oublier tout de suite.

Solution pas mal

C'est en transformant d'anciens fichiers iso-8859-1 en utf-8 que la solution m'a sauté au visage tel un écureuil farceur : utiliser les entités échappées pour récupérer le caractère non accentué. En effet, "é" une fois échappé donne "&eacute" et "É" donne "&Eacute". Ne reste plus alors qu'à gérer les cas particuliers comme la cédille ou les ligatures, et le tour est joué. Ainsi, on utilise htmlentities() pour échapper les caractères exotiques puis on remplace ces expressions échappées grâce à de savantes expressions rationnelles :

<?php

function wd_remove_accents($str$charset='utf-8')
{
    $str = htmlentities($str, ENT_NOQUOTES, $charset);
    
    $str = preg_replace('#\&([A-za-z])(?:acute|cedil|circ|grave|ring|tilde|uml)\;#''\1'$str);
    $str = preg_replace('#\&([A-za-z]{2})(?:lig)\;#''\1'$str)// pour les ligatures e.g. '&oelig;'
    $str = preg_replace('#\&[^;]+\;#'''$str)// supprime les autres caractères
    
    return $str;
}

?>

Notez tout de même que je préfère supprimer les caractères échappés qui n'ont pas été traités. C'est pas la meilleure solution, mais c'est celle qui me va le mieux…

Et maintenant, que vais-je faire ?

La fonction que je propose n'est évidement pas parfaite. L'idée de départ était de ne pas avoir à définir de tableau de conversion, parce qu'après tout un gros tableau et un mb_strreplace() peut également faire l'affaire.

Dans un prochain billet nous verrons comment utiliser cette fonction pour ordonner les clés accentuées d'un tableau associatif. Que d'aventures !! À la prochaine !

Cet article a frappé votre imagination ? Abonnez-vous pour être informé de ceux à venir.

Commenter

Un avis sur cet article ? Partagez-le et faites vous des amis (ou pas) !

Marquer

Del.icio.us / Google / Live / Ma.gnolia / Yahoo

29 commentaires

Florent V.
Florent V. a écrit : le 09 mai 2008

Hello,

« mais si comme moi vous travaillez en utf-8, les caractères accentués sont bien souvent encodés avec plus qu'un seul caractère (jusqu'à trois en fait) »

Non, un caractère est un caractère et pas potentiellement une suite de plusieurs caractères. Par contre, un caractère peut être écrit avec plusieurs octets. Suivant les caractères, cela va de un à quatre (et non pas trois) pour l'UTF-8.

Attention donc à ne pas confondre octets et caractères. ;)

Olivier
Olivier a écrit : le 10 mai 2008

Tu fais bien de préciser cela Florent. J'ai préféré – à tord – parler de « caractères » plutôt que de « bits » parce que tout le monde n'a pas fait de l'assembleur (680×0 pour moi) et que le PHP est souvent un premier langage de programmation.

J'ai corrigé ces erreurs, je ne sais pas ce qui m'a prit.
Vulgarisation quand tu nous tiens :-)

bibi
Bibi a écrit : le 20 mai 2008

Merci beaucoup pour ton petit bout de code. J'avais le même problème que toi et ça m'a bien aidé! Ton site est vraiment très intéressant on y apprend pleins de choses continue! ^^

6sko59
6sko59 a écrit : le 05 jun 2008

Salut, Je tiens juste à te remercier. Ta fonction est super , elle m'a bien dépanné!!

6sko59

Maxime
Maxime a écrit : le 08 jun 2008

Thanks man,

comme quoi des fois à 2h du mat on trouve du bon sur le net …

K
K a écrit : le 09 jun 2008

À moins d'avoir à utiliser des encodages extra (qui pour la plupart ne sont pas gérés par htmlentities [cf. la doc]), il est conseillé d'utiliser les fonctions natives strtr / str_replace couplées avec utf8_encode/utf8_decode – encore une fois dans le cas où l'on se limite aux encodages utf-8 et latin1 (ce qui est d'ailleurs est le cas dans 99% des cas).

En effet, les expressions régulières sont bien trop gourmandes par rapport à strtr et encore plus par rapport à str_replace.

Si on passe par strtr il est préférable d'utiliser la forme avec un tableau : str( $str, array( 'à' => 'a' , 'é' => 'e' , etc. ); ce qui évite quelques soucis.

Dans le cas où l'on a effectivement besoin d'encodage « extra », par exemple lorsque l'on travaille sur un site japonais utilisant un encodage comme le EUC-JP, on a pas vraiment le choix que de passer par la méthode htmlentities ou autres méthodes plus complexes encore.

Néanmoins, je me permets une petite remarque, il est plus avantageux de trier les éléments « conditionels » dans une expression régulière :

(?:acute|cedil|circ|grave|ring|tilde|uml)

et non :

(?:uml|circ|tilde|acute|grave|cedil|ring)

En effet, la première version permet un gain de performance (même si certains appuient qu'elle est inutile dans le cadre d'une utilisation simple).

Dans tous les cas, tous ces problèmes seront réglés avec l'arrivée de PHP 6.

Il n'y a plus qu'à attendre…

Olivier
Olivier a écrit : le 11 jun 2008

Merci pour ta contribution monsieur K. mais comme tu peux t'en douter, je ne partage pas vraiment ton avis.

D'abord au sujet de strtr(). Alors oui, on peut s'amuser à recopier la centaine de caractères à traduire, en espérant ne pas en oublier. Après une heure de remplissage on obtient surement une fonction pratique.

MAIS.

  1. Il en faut deux versions (ou plus) : une pour traduire les chaines utf-8 (à placer dans un fichier en utf-8), et une pour traduire les chaines iso-8859-1 (à placer dans un fichier iso-8859-1)…
  2. On a plus la chance de tomber sur un caractère non supporté qu'avec ma fonction (qui vire tout ce qui ne lui plait pas dans la dernière phase).
  3. Impossible d'utiliser la même fonction pour supprimer les accents de chaines aux encodages différents. Ce qui est un jeu d'enfant avec ma fonction puisqu'il suffit de lui indiquer l'encodage source.

Ensuite, cette fonction ne fait que ce qu'elle dit : supprimer les accents des caractères. Ce n'est donc pas une très bonne idée de lui donner une chaine en EUC-J (ou Big5 pour ne pas faire de jaloux).

Quand à l'arrivée de PHP6, cela ne changera rien au schmilblick. Ce n'est pas parce qu'il travaille en utf-8 que l'on aura plus besoin de supprimer des accents de temps à autre (pour les noms de fichiers par exemple).

Bref, je conserve ma jolie fonction qui ne fait que quelques lignes et que je trouve si pratique (et jolie).

Je te remercie pour le conseil d'ordonnancement du masque conditionnel. Si ça peut faire gagner quelques micro secondes ;-)

Evan
Evan a écrit : le 16 jun 2008

Bonjour,

je pense aussi que ta solution est trop compliquée et bien trop gourmande. De plus elle est incomplète et suppose que l'encodage est iso-8859-1, ça ne va pas.

La solution complète et la plus rapide est une détection du type d'encodage (mb_detect_string), puis une conversion (iconv), puis un str_replace pour finir.

Pour finir, tu cites l'intégralité des accents des langues à alphabet latin : ÁÀÂÄÃÅÇÉÈÊËÍÏÎÌÑÓÒÔÖÕÚÙÛÜÝáàâäãåçéèêëíìîïñóòôöõúùûüýÿ. C'est tout, il n'yen a pas d'autres. Cette solution est donc complète, je ne comprend pas pourquoi tu parles de « en espérant ne pas oublier de caractère ».

Bonne chance

KissGround
KissGround a écrit : le 16 jun 2008

Super ! Pour ma part, elle me convient pleinement. L'on peut dire que tu as l'exclu… J'ai eu beau chercher, il n'y a rien de mieux.

KissGround ;)

madoxav
Madoxav a écrit : le 01 jui 2008

Waouh! Un grand merci, après deux bonnes heures à batailler, j'ai enfin trouvé ce qu'il me fallait ;) bravo encore et bonne continuation!

jerko
Jerko a écrit : le 23 jui 2008

merci, c'est effectivement mieux que le str() tout nu.

mais c'est lourd en CPU, c'est clairement pas optimisé … pourtant j'ai pas trouvé mieux pour l'instant, la solution d'Evan ne marche pas trop avec PHP4 + UTF8, je ne sais pas pourquoi …

Tib
Tib a écrit : le 16 aoû 2008

Bonne astuce le htmlentities ;)

Intégré sur mon site, merci pour ta fonction

Snoo
Snoo a écrit : le 05 avr 2009

Bonjour, je travaille en utf-8. Voici une proposition de fonction a enregistrer en ISO-8859-1 (important):

<?php

function removeaccent($string)
{
    $string = utf8_decode($string);
    $string = strtr($string,    'àáâãäçèéêëìíîïñòóôõöùúûüýÿÀÁÂÃÄÇÈÉÊËÌÍÎÏÑÒÓÔÕÖÙÚÛÜÝ',
                                'aaaaaceeeeiiiinooooouuuuyyAAAAACEEEEIIIINOOOOOUUUUY');
    $string = utf8_encode($string);                 
    return $string;
};

?>
Kris
Kris a écrit : le 29 avr 2009

Bonjour,

Travaillant actuellement sur un nouveau site orienté game online, j'essaie de respecter les standards et de le faire en Oo (xhtml, css2, php5, mysql5).

Suite à diverse lecture et une mise en place d'une externalisation de la langue du code, j'en suis venu à me dire que pour aller au bout, il fallait tout passer en UTF-8. J'ai donc tout converti (base de données, fichiers, entête HTTP, fonction php (mb_)). Seul problème la version OterAccents que j'avais récupérer et que tu cites qui ne fonctionne plus bien évidemment.

Je te remercie donc pour cette fonction. Je n'ai trouvé que celle-ci en passant par google et encore parce que tu avais posté dans un billet sur un site.

Par contre, je salue la fonction de Snoo sur laquelle j'étais plus ou moins partie avant de chercher une solution « toute faite ». Mon seul problème c'est que je ne l'avais pas enregistré sous la forme ISO-8859-1 d'ou le fait que cela ne fonctionnait pas. En le mettant effectivement dans un fichier à part en ISO-8859-1, cela fonctionne parfaitement.

Merci donc à tous les deux pour votre précieuse aide :)

Kris

Fran6_yo
Fran6_yo a écrit : le 13 mai 2009

Merci beaucoup pour cette fonction, et c'est hallucinant que tout ce qu'on trouve sur le net soit la solution toute nulle.

:)

Thomas
Thomas a écrit : le 14 mai 2009

Bonjour,

Merci pour votre fonction, j'utilisais la fonction « toute nulle » mais là, j'ai bloqué avec l'utf8. Elle fonctionne très bien :)

Bonne continuation!

Lo
Lo a écrit : le 03 aoû 2009

Merci bien pour ta fonction qui m'a sauvé la vie sur un projet encore encodé en ISO-8859-1…

Nico
Nico a écrit : le 07 aoû 2009

Du bon boulot cette fonction, ça fait deux heures que je me prends la tête pour solutionner ce genre de problème. Merci !!!!

R.
R. a écrit : le 16 déc 2009

Bonjour,

merci pour cette fonction, qui m'a été très utile !

lor3m
Lor3m a écrit : le 26 jan 2010

Excellente fonction, marche à la perfection, fini les strtr tout nul !! Merci bien.

cyberbobjr
Cyberbobjr a écrit : le 27 fév 2010

Salut, c'est excellent ! la seule solution viable que j'ai trouvé sur le net. Bravo tu es un champion ! :)

Ham's
Ham's a écrit : le 21 mar 2010

Hello,

J'ai lu attentivement cet article sympa ainsi que tous les commentaires, cependant, je n'ai pas trouvé de réponse à ma petite question :

Qu'en est t-il des caractères encore plus exotiques ? En effet, je souhaiterais par exemple remplacer des caractères croates comme le « Š » par un « S » simple  !

Le fameux : $txt = str_replace(« Š », « s »,$txt); ne fonctionne pas malheureusement…

Si quelqu'un pourrait m'éclairer…

Merci !:)

blueeyes
Blueeyes a écrit : le 28 mar 2010

un seul mot, merci.

Après plusieurs heures de galère à ne pas comprendre pourquoi les exemples trouvés sur le net ne fonctionnaient pas, enfin une solution.

Je vais pouvoir continuer mon développement.

Very good

IllusionPerdu
IllusionPerdu a écrit : le 27 jun 2010

Salut et pourquoi ne pas utiliser une fonction de se style :

<?php

function NoAccentFeed($text$EncIn = 'CP1252')
{
    return iconv($EncIn'ASCII//TRANSLIT//IGNORE'$text);
}

?>
olivier
Olivier a écrit : le 28 jun 2010

Salut IllusionPerdu,

J'avais déjà essayé d'utiliser iconv(), mais cet imbécile produit des résultats très différents d'un système à l'autre même pour des versions de PHP identiques. J'ai ajouté une section à mon article qui illustre cela : « Solution qui pourrait être géniale ».

Merci pour ta contribution.

David Anseaume
David Anseaume a écrit : le 07 aoû 2010

C'est parce que iconv() est soumis au réglage de localisation de la machine, en l'occurrence le paramêtre LC_CTYPE , et donc précéder iconv() par :

setlocale(LC_CTYPE, 'fr_FR.UTF-8');

Clem
Clem a écrit : le 09 aoû 2010

Ça dépend avec quel lib est compiler iconv si c'est glibc (sous debian par ex) ont peux oublier, par contre si c'est sous libiconv ça dépendra de la version

olivier
Olivier a écrit : le 13 aoû 2010

Comme le dit Clem, tout dépend de la bibliothèque utilisée par PHP. Pour cette raison, c'est pour moi une fonction à oublier, le temps que l'équipe de PHP trouve une solution consistante.

Clem
Clem a écrit : le 17 aoû 2010

Ce qui serait envisageable c'est l'implémentation de la class Transliterator d'ICU (ext intl sous PHP). http://userguide.icu-project.org/transforms/general

Laisser un commentaire

Pour ne pas retrouver mon blog noyé de spam à mon retour de vacances, les nouveaux commentaires devront attendre d'être approuvés pour apparaitre sur le blog. Je vous remercie de votre compréhension et vous souhaite un bel été.

 
 Souhaitez-vous être informé par E-Mail d'une réponse à votre message ?