PHP: détecter si l'encodage d'un fichier est en utf8

Billet

Dans mon boulot on travaille uniquement avec des fichiers source en UTF8, notre site principal est en python mais j'ai des outils de maintenance extérieurs au site qui sont en PHP, étant donné que l'on travaille avec une bonne centaine de bénévoles qui ont accès au svn où nous stockons les fichiers de traduction et que nous avons des milliers de fichiers, il arrive qu'un bénévole envoie un fichier dans le mauvais encodage (genre Latin 1 pour certains europeéns et UTF16 pour certains asiatiques) et là, pour Django, c'est le drame :)

En bash on peut lister tous ces fichiers assez facilement et se créer un alias pour ça :

find . -type f -name "*.lang" -exec file --mime {} + | grep -viE "utf-8|us-ascii"

(via mon collègue italien Francesco Lodolo bien meilleur que moi en bash :) )

Ça marche bien mais je préfèrerais pouvoir afficher cette information dans mes tableaux de bords que je consulte en permanence, j'avais donc besoin d'une fonction pour détecter l'encodage des fichiers et j'ai pas mal sué pour trouver quelque chose qui marche correctement.

Ma première solution qui marchait a été celle là :

function isUTF8($file) 
{
    return in_array(exec('file --mime-encoding -b ' . $file), ['utf-8', 'us-ascii']) ? true : false;
}
Je délégais donc au shell la détection du charset, ça marche bien, mais ça a plusieurs inconvénients. Le premier est que ça ne marche pas sous Windows, même si j'avoue que c'est pas ma priorité, l'une des forces de PHP est tout de même sa compatibilité cross-plateforme. Le deuxième problème est que j'aime pas faire des exec, je trouve souvent ça un peu crade et c'est en général l'indice que j'ai pas réussi à le faire en PHP, le troisième problème et c'est le plus grave pour moi c'est que c'est très très lent. En local avec un disque SSD, un script qui mettait 3 secondes à s'éxécuter (et qui parsait déjà un petit millier de fichiers pour chercher des erreurs de variables python dans des chaînes) est passé à 18 secondes avec cette fonction et est interrompu sur le serveur qui n'a probablement pas de ssd pour dépasser les 30 secondes.

Ça marche donc, mais c'est trop lent pour mes besoins et je ne voyais pas comment l'améliorer.

J'ai cherché du côté de mb_detect_encoding() mais j'avais des faux négatifs avec un fichier en UTF8 que je savais corrompu et que php me détectait comme de l'UTF-8 alors que bash me le détectait en fichier binaire et que mon éditeur de texte me donnait en UTF16LE sans BOM. Comme c'est précisément ce genre de problèmes que je cherchais à détecter, après plusieurs heures de recherche du côté des fonctions mb_, j'ai l'aissé tomber.

Finalement ce matin j'ai trouvé une solution qui a des perfs correctes pour mon usage (je suis passé de 3 secondes à 6 secondes et j'ai des idées dans mon contexte précis pour faire baisser ça) en utilisant les fonctions finfo_* que je ne connaissais pas ! la voici :

function isUTF8($filename)
{
$info = finfo_open(FILEINFO_MIME_ENCODING);
$type = finfo_buffer($info, file_get_contents($filename));
finfo_close($info);

return ($type == 'utf-8' || $type == 'us-ascii') ? true : false;
}

La fonction ci-dessus correspond bien à mon besoin: pouvoir détecter si l'encodage d'un fichier est en utf-8 ou compatible utf-8 pour une utilisation sur le Web. Si quelqu'un a une meilleure solution, je suis bien sûr preneur :)

Commentaires

1. Le lundi 4 novembre 2013, 10:49 par tzi

Salut Pascal.

Merci pour cet article :)

Il me semble que les traitements de condition "? true : false" ne sont pas nécessaires et alourdissent un peu la lecture.

Tu peux garder :

 return $type == 'utf-8' || $type == 'us-ascii';

ou

 return in_array($type, ['utf-8', 'us-ascii']);

A bientôt,
Thomas.

2. Le lundi 4 novembre 2013, 11:15 par pascal

Merci Thomas pour ton commentaire :)

Effectivement tes 2 suggestions sont tout à fait valides, j'ai tendance à utiliser des conditions pour écrire explicitement dans la fonction qu'elle retourne un booléen en fait.

3. Le lundi 4 novembre 2013, 18:43 par cahnory

Intéressant.
J'ai fait il y a quelques temps un gros article sur l'utf-8 et j'avoue que je serais pas mal intéressé par un fichier "faux positif" pour faire quelques tests… si tu as ça sous la main ;)

4. Le lundi 4 novembre 2013, 19:10 par Pascal Chevrel
@cahnory en voilà un :) http://viewvc.svn.mozilla.org/vc/projects/mozilla.com/trunk/locales/zh-CN/firefoxtesting.lang?revision=111876&view=co