Coder avec Unicode/Conversion

De nos jours, les principales conversions que l'on doit faire sont des conversions entre utf-8 et utf-16, mais certains systèmes utilisent des encodages plus anciens. Il est alors nécessaire de les convertir. Cela ne peut se faire que si l'on sait quel est l'ancien encodage utilisé...

Si les données en entrée ne sont pas valides, une erreur de conversion d'encodage peut se produire.

Exemple

   1. my $foo = decode('UTF-8', get 'http://example.com/');
   2. my $bar = decode('ISO-8859-1', readline STDIN);
   3. my $xyzzy = decode('Windows-1251', $cgi->param('foo'));

  $body = encode('UTF-8', $body);


Sur une page Web, si default_charset = "UTF-8" n'est pas décommenté dans PHP.ini, si $chaine = '&eacute é $ €<br/>'; :

PHP print '<meta charset="UTF-8" />'; print '<meta charset="iso-8859-1" />';
print htmlentities($chaine);
&eacute é $ €<br/>
&eacute é $ €<br/>
print html_entity_decode($chaine);
é é $ €
é é $ €
print htmlspecialchars_decode($chaine);
é é $ €
é é $ €
print htmlspecialchars($chaine);
&eacute é $ €<br/>
&eacute é $ €<br/>
print htmlentities($chaine, ENT_NOQUOTES, 'UTF-8');
&eacute é $ €<br/>
&eacute é $ €<br/>
print html_entity_decode($chaine, ENT_NOQUOTES, "UTF-8");
é é $ €
é é $ €
print htmlspecialchars_decode(htmlentities($chaine, ENT_NOQUOTES, 'UTF-8'));
é é $ €
é é $ €
print mb_convert_encoding($chaine, 'ISO-8859-1');
(anciennement print utf8_decode($chaine))
é � $ ?
é é $ ?
print mb_convert_encoding($chaine, 'UTF-8');
(anciennement print utf8_encode($chaine))
é é $ €
é é $ €
print mb_convert_encoding($chaine, 'HTML-ENTITIES', 'UTF-8');
é é $ €
...
print mb_convert_encoding($chaine, 'UTF-8', 'auto');
TODO
print mb_convert_encoding($chaine, 'ASCII', 'auto');
TODO
print json_encode($chaine);
é \u00e9 $ \u20ac
...
print json_decode($chaine);
...
print json_encode($chaine, JSON_UNESCAPED_UNICODE);
é é $ €
...
print urlencode($chaine);
%26eacute+%C3%A9+%24+%E2%82%AC%3Cbr%2F%3E
...
print rawurlencode($chaine);
%26eacute%20%C3%A9%20%24%20%E2%82%AC%3Cbr%2F%3E
...
print urldecode($chaine);
é é $ €
...
iconv('UTF-8', 'ASCII//IGNORE', $chaine) TODO
mb_encode_mimeheader($chaine, 'UTF-8') TODO
 mb_convert_encoding() peut être précédé d'une condition sur mb_detect_encoding() pour éviter un double encodage.
 Ces valeurs fonctionnent avec des littéraux ou des champs de bases de données, mais dans le cas d'une désérialisation le symbole "€" se comporte différemment (impossible de le chercher dans un fichier texte).

 

Un substr('aàb', 2, 1) peut donner "à" car il compte pour deux caractères, pour avoir "b" il faut utiliser mb_substr('aàb', 2, 1) (pour multi-bytes substring[1]).

 Pour traiter les caractères Unicode considérés comme invalides dans les XML et JSON, utiliser preg_replace()[2].

Pour échapper les symboles Unicode indigestes comme "U+001F" :

public function slugify($string)
{
    $pattern = '/[^a-z0-9]/';
    $string = preg_replace($pattern, '-', strtolower($string));
    while (substr($string, -1) == '-') {
        $string = substr($string, 0, strlen($string) - 1);
    }

    return preg_replace($pattern, '-', $string);
}

Base de données

modifier

Insertion dans une base MySQL avec MySQLi :

  if (strpos($chaine, 'é') > 0) {
    $mysqli->set_charset('utf8');
  }
  return $mysqli->query($chaine);

Insertion dans une base MySQL avec PDO :

    public function connect()
    {
        $options = [
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
        ];

        if (empty($this->connection)) {
            $this->connection = new PDO(
                'mysql:host='.$this->serverName.';dbname='.$this->databaseName,
                $this->userName,
                $this->password,
                $options
            );
        }

        return $this->connection;
    }

Exemple de conversion

modifier

Conversion d'une chaîne Unicode en une chaîne utf-8; cas de la chaîne « âäàéèëê » :

a = u'\xe2\xe4\xe0\xe9\xe8\xeb\xea'    # L'écriture en hexadécimal permet de s'affranchir de l'encodage utilisé par le fichier source
a.encode('utf-8') ## Retourne la séquence d'octets '\xc3\xa2\xc3\xa4\xc3\xa0\xc3\xa9\xc3\xa8\xc3\xab\xc3\xaa'


En Python 3 :

Exemple de création d'un caractère à partir de sa forme encodée en UTF-16 :

b'\xff\xfeW['.decode('utf-16')
#return '字'


Exemple de conversion d'un caractère non latin dans un codage UTF8 ou UTF-16 :

nonlat = '字' # Suppose que l'encodage du fichier est UTF-8.
nonlat = b'\xff\xfeW['.decode('utf-16') # Quel que soit l'encodage du fichier source

bytes(nonlat, 'utf-8')
 # return b'\xe5\xad\x97'

nonlat.encode() # the encode method in Python 3.x uses the UTF-8 encoding by default.
#return '\xe5\xad\x97'
	
nonlat.encode('utf-16')
#return b'\xff\xfeW['

Gestion des erreurs de conversion

modifier

En Python 2, le codage des chaînes littérales dépend de l'environnement logiciel utilisé, dans le cas d'un système Windows.

L'exemple suivant créera donc des erreurs différentes en fonction de l'environnement d'exécution.

 chaine = '&eacute é $ €<br/>'
 print chaine
 print chaine.decode("utf8")
 print chaine.encode("utf8")

 chaine = u'&eacute é $ €<br/>'
 print chaine
 print chaine.decode("utf8")
 print chaine.encode("utf8")


&eacute é $ ?<br/>
UnicodeDecodeError: 'utf8' codec can't decode byte 0x82 in position 8: invalid start byte
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 8: ordinal not in range(128)

&eacute é $ ?<br/>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe9' in position 8: ordinal not in range(128)
&eacute ├® $ ?<br/>

Le message d'erreur «UnicodeDecodeError: 'utf8' codec can't decode byte 0x82 in position 8: invalid start byte» donne deux indications: le contexte de l'erreur et l'erreur elle même.

  • Le contexte de l'erreur: «'utf8' codec can't decode byte 0x82 in position 8» signifie que lors de la lecture d'une chaîne qui devrait être en utf8 «'utf8' codec» l'octet 0x82 a été rencontrée dans la chaîne d'origine.
  • L'erreur: «invalid start byte» indique que l'octet rencontré, ici 0x82, n'est pas un octet qui peut marquer le début d'un caractère, ici en utf8.
Code Erreur Explication Correction des données Correction du code
  print chaine.decode("utf8")
UnicodeDecodeError: 'utf8' codec can't decode byte 0x82 in position 8: invalid start byte Dans la chaîne de départ, à la position 8, se trouve "é". L'octet 0x82 est précisément le codage de ce caractère dans la page de code 850. S'il faut décoder de l'utf8, les données ne devraient pas être codées en code page 850. Si au contraire l'encodage utilisé dans des données source doit être du codepage 850, il serait plus logique d'utiliser decode("cp850") plutôt que decode("utf8")
print chaine.encode("utf8")
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 8: ordinal not in range(128) Dans la chaîne de départ, à la position 8, se trouve "é". L'octet 0xc3 est précisément le premier des deux octets utf8 nécessaire au codages de certains caractères latins accentués en utf8. Lors de la lecture d'une chaîne ascii, les caractères devraient avoir une valeur inférieure à 128 (0x80) ce qui n'est pas le cas ici.
Il faut comprendre que pour encoder un texte sous la forme d'une séquence d'octet, la méthode encode s'attend vraisemblablement à avoir une chaîne unicode en entrée, ce qui peut supposer un appel implicite à la méthode decode de manière à convertir la chaîne d'octet en un texte avec l'encodage par défaut, qui ici semble être l'ascii.
Pour utiliser la méthode encode, il faut que la donnée en entrée soit de type unicode. Si les données d'entrées doivent être de type chaîne d'octets, il ne faut pas utiliser la méthode encode. Cette méthode a d'ailleurs été supprimée de la classe chaîne d'octets en Python3.[3].
print chaine.decode("utf8")
UnicodeEncodeError: ' ascii' codec can't encode character u'\xe9' in position 8: ordinal not in range(128) Dans la chaîne de départ, à la position 8, se trouve le caractère unicode «é» (qui porte bien le numéro U+00e9 en unicode. Lors de la lecture d'une chaîne ascii, les caractères devraient avoir une valeur inférieure à 128 (0x80) ce qui n'est pas le cas ici. La méthode decode sert à décoder des chaînes d'octets, dans ce cas les données d'entrée doivent être une chaîne d'octet et non un texte unicode. Si les données d'entrées doivent être de type unicode, il ne faut pas utiliser la méthode decode. Cette méthode a d'ailleurs été supprimée de la classe unicode en Python3.[4].

En Java, les chaînes de caractères sont déjà Unicode ; le constructeur de la classe java.lang.String permet de décoder une séquence d'octets. La méthode getBytes de cette classe permet d'obtenir les octets d'encodage.

public static String decodeUtf8(byte[] b)
{
    return new String(b, "UTF-8");
}

public static byte[] encodeUtf8(String s)
{
    return s.getBytes("UTF-8");
}

JavaScript propose les fonctions "encodeURI()" et "decodeURI()"[5].

Sinon, une astuce car la conversion UTF8 n'est pas native consiste à[6] :

function encode_utf8(s) {
  return unescape(encodeURIComponent(s));
}

function decode_utf8(s) {
  return decodeURIComponent(escape(s));
}

En Vala, le type string utilise des données au format utf-8. Les données qui ne sont pas à ce format peuvent être mises à ce format à l'aide de la classe CharsetConverter.

Exemple:

public static int main (string[] args) {
	try {
		MemoryOutputStream mostream = new MemoryOutputStream (null, GLib.realloc, GLib.free);
		CharsetConverter oconverter = new CharsetConverter ("utf-16", "utf-8");
		ConverterOutputStream costream = new ConverterOutputStream (mostream, oconverter);
		DataOutputStream dostream = new DataOutputStream (costream);
		dostream.put_string ("ΑαΒβΓγΔδΕεΖζΗηΘθ\n");
		dostream.put_string ("ΙιΚκΛλΜμΝνΞξΟοΠπ\n");
		dostream.put_string ("ΡρΣσΤτΥυΦφΧχΨψΩω\n");
		mostream.close ();

		Bytes bytes = mostream.steal_as_bytes ();

		MemoryInputStream mistream = new MemoryInputStream.from_bytes (bytes);
		CharsetConverter iconverter = new CharsetConverter ("utf-8", "utf-16");
		ConverterInputStream cistream = new ConverterInputStream (mistream, iconverter);
		DataInputStream distream = new DataInputStream (cistream);

		string line = distream.read_line ();
		stdout.puts (line);
		stdout.putc ('\n');

		line = distream.read_line ();
		stdout.puts (line);
		stdout.putc ('\n');

		line = distream.read_line ();
		stdout.puts (line);
		stdout.putc ('\n');

	} catch (IOError e) {
		stdout.printf ("IOError: %s\n", e.message);
	} catch (Error e) {
		stdout.printf ("Error: %s\n", e.message);
	}
	return 0;
}

En PL/SQL des erreurs de conversion peuvent être remplacées par des caractères blancs[7]

  1. http://php.net/manual/fr/function.mb-substr.php
  2. https://magp.ie/2011/01/06/remove-non-utf8-characters-from-string-with-php/
  3. stackoverflow.com/questions/447107/what-is-the-difference-between-encode-decode
  4. stackoverflow.com/questions/447107/what-is-the-difference-between-encode-decode
  5. https://www.w3schools.com/jsref/jsref_decodeuri.asp
  6. http://ecmanaut.blogspot.fr/2006/07/encoding-decoding-utf8-in-javascript.html
  7. docs.oracle.com/cd/B19306_01/server.102/b14225/ch7progrunicode.htm

Voir aussi

modifier