Pour la première étape, un appel à la fonction lcfirst()
sur la chaîne de caractères considérée résout le problème très efficacement.
Le passage en casse haute de toutes les lettres précédées d'un espace, qui correspond à l'étape 3, est déjà moins évident.
PHP dispose de la fonction inverse de lcfirst()
, à savoir ucfirst()
.
Il reste donc à localiser les lettres sur lesquelles il faut l'appliquer.
Pour cela, preg_replace()
est notre amie, puisque cette fonction est capable d'exécuter une fonction sur chaque remplacement, via le drapeau
.e
Il suffit donc de lui demander de remplacer tous les espaces par la lettre qui le suit en casse haute, de la manière suivante :
<?php $string = preg_replace('/\s+(.)/ue', 'ucfirst(\'\\1\')', $string); ?>
Ma chaîne étant au format Unicode, j'ai pris soins d'activer le drapeau
en plus de u
afin que l'encodage soit pris en compte par la fonction.e
Les plus attentifs auront certainement remarqué que nous venons de résoudre simultanément les problèmes posés par les étapes 3 et 4.
Il ne reste donc plus qu'à trouver une solution pour le problème posé par l'étape 2.
En parcourant Internet, j'ai trouvé différentes solutions, plus ou moins immondes, et les plus intéressantes étaient basées sur intl
.
Mon contexte de mise en œuvre étant strictement limité au français, j'ai décidé de me baser sur ces dernières en les simplifiant fortement.
La logique de fonctionnement est basée sur la normalisation des chaînes Unicode, qui consiste, en très résumé, à remplacer, par exemple, le caractère Unicode é
par la combinaison des caractères e
et ́
dans la chaîne considérée.
Une fois cela réalisé, il ne suffit donc plus que de supprimer les caractères Unicode ajoutant l'accentuation pour obtenir une chaîne sans le moindre accent.
Pour cela, preg_replace()
est une nouvelle fois mis à contribution, via les propriétés des caractères Unicode.
Une fois tout cela combiné, on obtient le magnifique code suivant :
<?php $string = lcfirst(preg_replace('/\s+(.)/ue', 'ucfirst(\'\\1\')', preg_replace('@\pM@u', '', \normalizer::normalize(trim($string), Normalizer::FORM_D)))); ?>
Et si l'on aime le beau code, comme moi, il est possible de pousser l'esthétique jusqu'à cela :
<?php $string = preg_replace(array('@\pM@u', '/^(.)/ue', '/\s+(.)/ue'), array('', 'lcfirst(\'\\1\')', 'ucfirst(\'\\1\')'), \normalizer::normalize(trim($string), Normalizer::FORM_D)); ?>
<ironie>Finalement, Unicode, ça peut servir !</ironie>
[MAJ] Suite à la parution de ce qui précède, il m'a été proposé d'autres solutions, la plupart reposant sur iconv
.
Dans un premier temps, je ne les avais pas prise en compte, car je pensais que cette extension n'était pas native au langage, au contraire de intl
, ce qui aurait été un problème dans mon contexte de mise en œuvre.
Or, après vérification, iconv
est bien livrée en standard avec PHP, et elle a l'avantage de permettre la gestion de tous les alphabets, puisqu'elle permet la conversion d'une chaîne de caractère Unicode vers l'ASCII.
Voici donc une solution basée sur cette extension, conçue à partir de la proposition de Maître Yoda :
<?php $string = lcfirst(preg_replace('/\s+/u', '', ucwords(strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', trim($string)))))); ?>
27 réactions
1 De Savageman - 23/12/2010, 20:55
Pour la suppression des accents, une technique possible (mais qui ne fait pas que simplement supprimer les accents) consiste à passer par iconv() avec le filtre //TRANSLIT ou //IGNORE.
2 De Br|ce - 23/12/2010, 20:56
je crois que ta balise d'ouverture ironie est mal placée, elle devrait être juste avant le premier magnifique :D
3 De mageekguy - 23/12/2010, 21:19
@Br|ce : Le dont tu parles n'a rien à voir avec l'ironie, de mon point de vue, mais chacun voit midi à sa port.
4 De Steuf - 23/12/2010, 22:02
$string = lcfirst(preg_replace(array( '/\s+(a-z{1})/ue', '@\pM@u'), array('strtoupper(\'\\1\')', ''), \normalizer::normalize(trim($string), Normalizer::FORM_D)));
Le lcfirst devrait pour moi être sorti de la regex, il n'est pas utile de l'inclure dedans, c'est un peu plus performant, de même pour la mise en majuscule il n'y a besoin que de trouver un espace suivi d'une et une seule lettre minuscule ce qui est un peu plus performant aussi (Ca limite les combinaison possible que le masque a à tester) en utilisant strtoupper (Comme de toute façon il n'y aura qu'un caractère qui sortira du masque).
Voilà pour ma "petite contribution".
5 De mageekguy - 23/12/2010, 23:01
@Steuf : Et si tu as un espace suivi d'un chiffre, il se passe quoi ?
Sinon, je suis d'accord pour
strtoupper()
, mais la différence en terme de performance tient plus d'une manipulation inadéquate de la partie arrière des drosophiles que d'autre chose, idem en ce qui concerne l'externalisation delcfirst()
.6 De Steuf - 23/12/2010, 23:28
@mageekguy effectivement petit oubli sur les chiffres, :alnum: à la place de a-z, dans tous les cas le point n'est pas adéquat ici à mon sens. Pour ce qui est de la question de la performance c'est sur que sur 2 trois appels sur des petits volumes c'est totalement insignifiant, mais dès qu'on attaque des grosses chaines de caractère et une nombre important d'appels l'optimisation des masques des regex apporte un gain non négligeable Même s'il y a peu de chance que ça soit utilisé sur des gros volumes ici, ça ne mange pas de pain.
7 De Nicolas Grekas - 23/12/2010, 23:42
3 remarques qui me viennent :
- Pourquoi utiliser lcfirst plutôt que ucwords ?
- Les transformations maj./min. devraient être faites après la conversion en ascii, car sinon ton code ne marche pas lorsque la première lettre d'un mot est accentuée.
- Et au delà d'enlever les accents, est ce que ta problématique ne pourrait pas être généralisée à la conversion d'une chaîne utf-8 en ascii (© vers (c), ß vers ss) ?
Pour cette étape, moi j'utilise :
Deux améliorations ici par rapport à ton code : le preg_match nous évite de lancer la grosse artillerie de normalisation unicode si on peut s'en passer, et iconv gère les translittérations restantes.
Du coup, le code complet optimisé serait :
8 De mageekguy - 24/12/2010, 00:09
@Nicolas Grekas : J'avoue ne pas avoir pensé à
ucwords()
, mais elle impose le recours à une variable intermédiaire, et j'aime la solution .Pour la conversion vers la casse haute à faire après la suppression de l'accentuation, c'est effectivement une erreur de ma part, maintenant corrigée, le pire étant que j'y ai pensé, en plus, mais
, va savoir pourquoi.Et enfin, dans mon contexte, le recours à
iconv
n'est pas utile, mais c'est effectivement un complément très efficace.9 De mageekguy - 24/12/2010, 00:20
@Steuf : Et si tu as un espace blanc suivit d'autre chose qu'une lettre ou un chiffre ?
10 De Maxence - 24/12/2010, 09:49
Je déconseille l'utilisation de //TRANSLIT car les résultats différent selon la plateforme (*nix ou Windows)
11 De mageekguy - 24/12/2010, 09:56
@Maxence : En l'occurrence, la plate-forme sera UNIX et aucun portage vers Windows n'est prévu.
12 De Nicolas Grekas - 24/12/2010, 10:22
Vu ta mise à jour, en fait ça ne fait pas ce que tu veux dans le cas générique, à cause de iconv :
Certaines implémentations d'iconv transforment les caractères accentués en deux caractères : "accent" + lettre non accentuée, par exemple :
é => ´e, ê => ^e et même ë => "e (oui, un double quote pour le tréma, comme en latex)
C'est pour ça qu'il faut passer avant iconv par une normalisation KD (KD plutôt que D au fait).
Autre point, l'implémentation glibc (par défaut sous Ubuntu au moins), n'accepte pas à la fois //IGNORE et //TRANSLIT (re-testé à l'instant sous Lucid), ceci explique dans mon code le 'ASCII' . ('glibc' !== ICONV_IMPL ? '//IGNORE' : '') . '//TRANSLIT'.
Mais peut-être que ton implémentation n'a pas ces soucis, alors ton code n'a pas besoin de gérer ça.
13 De Maxence - 24/12/2010, 10:27
En fait quand on utilise //TRANSLIT sur Windows, on a le résultat suivant pour "déjà" par exemple : "d'ej`a". Il utilise les caractères 39 et 96 du code ASCII pour représenter les accents. Ça n'a pas grand intérêt mais bon c'est libiconv qui procède comme ça (ça peut peut être se configurer quelque part, mais je n'ai pas trouvé). Donc si on veut avoir du code portable sur Windows, on peut appliquer :
str_replace(array(''', '`'), '', $str));
sur la chaîne qui sort de iconv pour supprimer ces accents ajoutés.
J'ai remarqué également des différences de comportement avec ² (? sur *nix et ^2 sur Windows).
14 De Steuf - 24/12/2010, 10:30
@mageekguy Dans ce cas je repartirais sur a-z et un str_replace à la fin pour l'espace.
Sinon pour la dernière solution que tu expose, pourquoi tu remplace par le preg_replace pour les espaces par un str_replace ?
$string = lcfirst(str_replace(' ', '', ucwords(strtolower(iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', trim($string))))));
15 De mageekguy - 24/12/2010, 10:38
@Steuf : Parce que des espaces blancs, ça peut être autre chose que , comme par exemple \t, \n, etc.
16 De mageekguy - 24/12/2010, 10:42
@Nicolas Grekas : J'ai le droit de dire qu'
iconv
, c'est de la merde, au vu des informations que tu viens de me donner ?Je déteste lorsqu'une solution élégante est pourrie par une implémentation moisie des outils sous-jacent.
17 De Steuf - 24/12/2010, 10:44
En effet dans ce contexte on est d'accord.
18 De Steuf - 24/12/2010, 10:48
@Maxence C'est même pire que cela :
public function removeDiacritics($str)
{
}
Cf : http://wiip.fr/content/diff%C3%A9re...
19 De Olivier Laviale - 24/12/2010, 12:10
J'ai aussi été confronté au problème de la suppression d'accents, et comme mentionné plus haut "iconv c'est de la merde", je me suis tourné vers htmlentities(). Voici ma solution:
function wd_remove_accents($str, $charset='utf-8')
{
}
Et l'article qui l'accompagne : http://www.weirdog.com/blog/php/sup...
20 De mageekguy - 24/12/2010, 12:37
@Olivier Laviale : tu permet, je vais vomir et je reviens :).
21 De Steuf - 24/12/2010, 14:54
@mageekguy je viens de faire quelques tests avec les divers propositions exposées synthétisés ici : http://pastebin.com/Bvzwbz3i
Ce qui est frappant c'est qu'aucune des solutions ne donne les mêmes résultats, pour la première il n'y a pas de conversion sur les sigles des monnaies, alors que dans la deuxième et troisième ils sont convertis en chaînes.
On notera aussi, que si l'on veut utiliser la deuxième solution elle fonctionne mal sous Windows, et pour assurer la compatibilité on fait main basse sur la ponctuation... Ce n'est pas forcément l'effet souhaité.
A noter que la première solution renvoit une chaîne en UTF8 et on remarquera le superbe support de PHP à ce niveau (nombre de caractères renvoyés par var_dump). Soucis que n'a pas la troisième solution les sigles étant remplacés.
Tout dépend du besoin après.
22 De sunseb - 24/12/2010, 19:51
Après, il y a aussi la solution "liste manuelle et incomplète"...
$avec_accents = array('á', 'â', 'à', 'é', 'ê', 'è', 'î', 'ï', 'ô', 'ö', 'û', 'ü', 'Á', 'Â', 'À', 'É', 'Ê', 'È', 'Î', 'Ï', 'Ô', 'Ö', 'Û', 'Ü');
$sans_accents = array('a', 'a', 'a', 'e', 'e', 'e', 'i', 'i', 'o', 'o', 'u', 'u', 'A', 'A', 'A', 'E', 'E', 'E', 'I', 'I', 'O', 'O', 'U', 'U');
$texte = $str_replace($avec_accents, $sans_accents);
Pour un site US ou français, c'est rare qu'on utilise des caractères accentués exotiques.
<troll>Vous êtes plutôt "camelCase" ou "underscore" pour vos variables ?</troll>
23 De oxman - 25/12/2010, 11:00
Il n'y a pas à dire, écrire du code en une ligne en PHP, ça fait un code super moche crade. Genre Perl quoi.
24 De Olivier Laviale - 26/12/2010, 14:11
@mageekguy Je comprends, c'est la première réaction que j'ai eu en voyant ton blog, et pourtant j'y reviens chaque semaine. Ce qui compte c'est peut-être le rapport cout/résultat. Pour 10000 conversions de la chaine "L'été est dans nos cœurs chauds." voici les temps en microsecondes pour "iconv" et ma fonction : 0.84582805633545 et 2.0533759593964. Question différence de performance, pas de quoi grimper aux rideaux, surtout que ICONV sur PHP 5.3.3-1ubuntu9.1 produit un résultat erroné : "L'?t? est dans nos coeurs chauds.", "œ" est bien décomposé mais les accents sautent... va comprendre...
Alors en attendant que PHP5.3 et intl soit disponible sur une majorité d'hébergements (d'ici 3 ou 5 ans) on fait au mieux, quitte à prendre du Motilium
25 De mageekguy - 27/12/2010, 00:48
@Olivier Laviale : pour ta première phrase.
Et ce n'est pas parce qu'une solution de merde a des performances acceptables (et reste à définir
en fonction du contexte d'utilisation) qu'elle devient une solution acceptable.De la merde, même rapide, ça reste de la merde ;).
À tout prendre, dans mon cas, je préfère encore faire attention à la version de
iconv
en développement et en production plutôt que de recourir à ta solution.26 De webozor - 28/12/2010, 16:16
Personnellement, même si elle est un peu moins élégante je préfère la solution de @Olivier Laviale.
Après quelques test, c'est la seule à fonctionner sans problème sur les diverses plateformes.
Je préfère une solution peu être un peu plus sale mais qui fonctionne à 100%.
Quand
iconv
fonctionnera mieux par contre...27 De stealth35 - 29/12/2010, 19:13
Quand le Transliterator d'Intl sortira ca sera la seule vrai solution à ce probleme :
http://userguide.icu-project.org/tr...
http://svn.php.net/repository/php/p...