Carnet Wiki

utf8 vrac de notes à deboguer

Version 17 — Novembre 2018 YannX

Interviennent les paramètres suivants, dont il faut s’assurer qu’ils restent toujours en phase :

Pour les données :
-  le codage d’interclassement / / utilisé par MySQL pour les tris de colonnes /
/
-* celui le codage déclaré pour la base de données (est-ce utile vraiment, si on connait le codage de chaque table ?)

  • le codage d’interclassement déclaré pour chaque la table dans la base de données
    -  le codage du fichier texte de transfert, lorsqu’il y a export ou import Adminer/PhpMyAdmin, (c’est quoi cette notion au juste ? l’encodage du fichier !)
    -  le codage réel du CONTENU de la table, qui en cas d’erreur à une étape peut être différent de celui théorique (indiqué par le codage de la table)

Dans les données SPIP


-  la valeur de la méta charset : Configuration / Langue / Jeu de caractères
c’est normalement à ? utf8 utf-8] !
-  les valeurs de charset_sql_base et charset_sql_connexion sont normalement à utf8
-  la valeur charset_collation_sql_base reprend l’interclassement utf8_general_ci

Pour le source :
-  le codage du fichier (ISO, utf8 avec ou sans bom...). Pour SPIP (càd les .html ?) il faut le BOM, mais il ne le faut pas pour les fichiers php. A noter que php n’élimine pas le BOM lors d’un include().
-  la manière avec laquelle l’éditeur de texte comprend l’encodage du fichier.

Gildas C : Le dénominateur commun des projets / fichiers libres multilingues... : Il
faut encoder en UTF-8. C’est la norme aussi pour les fichiers de langue donc pas besoin de s’embêter avec les entités numériques ou nommées...
et sans BOM bien sûr (ça perturbe PHP par exemple et c’est difficile à déceler)
Si le système ou l’éditeur de code n’est pas compatible Unicode (ou fait de l’Unicode mais encodé autrement), il faut utiliser ISO Latin-1 qui est entièrement compatible.


Erreurs possibles
-  déclarer quelque part quelquepart utf-8 alors qu’il faut écrire UTF-8 ou UTF8 ou utf8
-  l’inverse et toutes les variations possibles

À php on dit mb_internal_encoding("UTF-8");
mais à MySql on dit SET NAMES 'utf8'

-  des données ou du code en utf8 dans un contenant déclaré en ISO
-  des données ou du code en ISO dans un contenant déclaré en uf8

-  un contenant en utf8 lu ou analysé par un outil qui croit que c’est de l’ISO
-  un contenant en ISO lu ou analysé par un outil qui croit que c’est de l’utf8

Par ailleurs, les fichiers déclarés comme utf8 peuvent avoir un BOM ou ne pas en avoir.

Cela introduit une autre source d’erreur :
-  un fichier créé sans BOM ou analysé par un outil qui croit qu’il y a un BOM ou qui a besoin qu’il y en ait un, alors que le fichier n’en a pas.
-  l’inverse et toutes les variations possibles.

En conséquences vous pouvez perdre des affichages, ou pire, tronquer vos données dans certains champs...


Outils de dépatouillage avancé

  1. Le témoignage de Fil : http://zzz.rezo.net/Reparer-le-charset-d-une-base-SPIP.html pour corriger un site où des données utf8 ont été enregistrées dans des tables ISO (= latin1)
  1. Faire connaissance avec des caractères accenctués (ou d’autres caractères spéciaux ?) et la manière symptomatique avec laquelle ils se présentent en cas d’erreur.
    C’est à dire : apprendre à lire les hiéroglyphes.
    Se documenter sur comment l’ISO et l’utf8 codent les caractères accenctués et autres caractères spéciaux.

Données :
-  regarder le contenant : codage déclaré pour une table
-  regarder le contenu (comment ? avec quel outil et comment régler cet outil pour qu’il n’introduise pas de biais supplémentaires ? ) : y a t il des caractères bizzares bizares ? les caractères bizarres ressemblent ils à des hiéroglyphes connus, comme de l’utf8 regardé dans une page ISO ou le contraire ?

Code :
-  idem

Code fourni sur IRC (par cédric)

Migration de charsets dans une base SPIP vers UTF8

Code (from cerdic)

/**
 * Migrer le charset de chaque champ en ne machouillant pas le contenu
 * @param string $serveur
 */
function monplugin_migre_charset_champs($serveur=''){
	$res = sql_showbase('%',$serveur);
	while($row = sql_fetch($res,$serveur)){
		$table = reset($row);
		#var_dump($table);


$desc = sql_showtable($table,false,$serveur);
		#var_dump($desc);


foreach($desc['field'] as $champ=>$d){
			if (strpos($d,'CHARACTER SET latin1')!==false){
				//ALTER TABLE <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c3BpcF9hcnRpY2xlczwvY29kZT4="></span> CHANGE <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c291c3RpdHJlPC9jb2RlPg=="></span> <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c291c3RpdHJlPC9jb2RlPg=="></span> TEXT CHARACTER SET BINARY NOT NULL
				//ALTER TABLE <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c3BpcF9hcnRpY2xlczwvY29kZT4="></span> CHANGE <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c291c3RpdHJlPC9jb2RlPg=="></span> <span class="base64" title="PGNvZGUgY2xhc3M9InNwaXBfY29kZSBzcGlwX2NvZGVfaW5saW5lIiBkaXI9Imx0ciI+c291c3RpdHJlPC9jb2RlPg=="></span> TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL


$d_binary = str_ireplace('CHARACTER SET latin1','CHARACTER SET BINARY',$d);
				$d_utf8 = str_ireplace('CHARACTER SET latin1','CHARACTER SET utf8 COLLATE utf8_general_ci',$d);


// on passe le champ en binary
				sql_alter("TABLE $table CHANGE $champ $champ $d_binary",$serveur);
				// puis en utf8
				sql_alter("TABLE $table CHANGE $champ $champ $d_utf8",$serveur);


#var_dump($champ);
				#var_dump($d_utf8);
				#die();
			}
			if (time()>_TIME_OUT) return;
		}


if (time()>_TIME_OUT) return;
	}


// mettre a jour la meta de connexion via sql_xx car ecrire_meta ne gere pas la base distante
	$r = array('nom' => 'charset_sql_connexion', 'valeur' => 'utf8');
	$desc = sql_showtable("spip_meta",false,$serveur);
	sql_insertq('spip_meta', $r, $desc, $serveur);
	sql_updateq('spip_meta', $r,"nom=" . sql_quote($r['nom']),$desc,$serveur);


} --------------------

Discussion sur IRC

<cerdic> Suske: tadam
 http://spip.pastebin.fr/36883
 migration d'une base mysql iso en utf
 in situ
<Suske> (oh)
<cerdic> sans aucun export de données et robuste


<cerdic> en fait mysqlfait des conversions de charset quand on passe de binary a utf8
 il suffit de faire un double alter sur chaque champ
 (mais fait un backup avant quand même pour verifier)


<Suske> oué je suis en train d'injecter le backup là
 ça prend déjà un peu de temps ^^
 merci merci
<cerdic> si jamais le contenu est encodé en UTF8 mais la base est en iso il faut passer par binary
<Suske> je crois que j'ai de quoi jouer entre chaque sieste de la journée
<cerdic> si le contenu est iso dans une base iso et qu'il faut juste passer en utf8
 il faut pas passer par binary
<Suske> non le contenu est iso (et le site est encore en 2.1)
<cerdic> donc voila
<Suske> je me disais que j'allais juste tenter le convert de spip2.1
<cerdic> en general je joue une premiere fois pour voir si c'est bon
<Suske> enfin il y a un peu de pourriture dans le charset comme même...
<cerdic> et en fonction du resultat j'adapte les alter

--------------

Fonctions PHP

Voir http://php.net/manual/fr/ref.mbstring.php

-  quelles fonctions de chaine classiques fonctionnent bien avec l’utf8 ?

  • str_replace ?
  • preg_replace ?
  • trim ?
  • strlen ?
  • strpos ?
  • etc ?

SPIP a créé une fonction spip_strlen indépendante du charset, qui appelle strlen en ISO, sinon mb_strlen si elle existe, ou sinon l’émule avec strlen(preg_replace(',[\x80-\xBF],S', '', $c));.
Cela pourrait indiquer que preg_replace fonctionne bien avec l’utf8.
Dans quel cas peut on utiliser des fonctions ’classiques’ et quand faut il utiliser les mb_ ?

-  quelles fonctions exigent par contre une fonction mb_truc ?

On peut supposer que toutes les fonctions qui ont une contrepartie mb_truc ne sont pas compatibles avec l’utf8.

-  faut il traduire preg_replace en mb_ereg_replace ? Apparament non vu que spip_strlen fait appel à preg_replace sur de l’utf8 (mais dans un cas où la chaine à remplacer ne contient pas d’utf8. Peut être est-ce nécessaire dans le cas contraire...)

-------------------- --------------

Recommandations pour de bonnes pratiques

-  Regarder en particulier la fonction mb_internal_encoding(« UTF-8 ») ; http://php.net/manual/fr/function.mb-internal-encoding.php

-  Aussi : mb_http_output http://php.net/manual/fr/function.mb-http-output.php

--------------------

--------------------

les caractères

-  voir http://www.lookuptables.com/
-  http://en.wikipedia.org/wiki/Non-breaking_space
-  http://en.wikipedia.org/wiki/Space_(punctuation)
-  http://en.wikipedia.org/wiki/Non-breaking_space

-  \n == &#13;

-   également appelée U+2009 est ici directement compris entre les pipes : | | c’est une espace fine (à comparer à une espace normale ici : | | ; dans ce contexte on voit pas beaucoup la différence. Accessible aussi par &thinsp en HTML comme ici : | | . Et pour finir une espace « sixième de m » : | | ah on voit mieux. Mais les tailles peuvent dépendre des polices du rendu, qui définissent, ou non, ces espaces )
<!ENTITY thinsp  CDATA " " -- thin space, U+2009 ISOpub -->

-  0xA0 est un non breaking space en ISO, mais c’est U+00A0 en unicode, ou 0xC2 0xA0 en UTF8.