Aller au contenu

Encodage de textes

Echec

Tout pour comprendre et éviter les erreurs d'encodage

Des zéros et des uns

🤔 Notre machine ne sait travailler qu'avec des 0 et des 1. Comme les nombres (entiers ou flottants), les textes doivent donc être représentés dans nos machines en binaire.

I. Au commencement était l'ASCII⚓︎

ASCII

💡 Le premier codage existant était donné par le code ASCII, mais ne permettait pas d’écrire en n’importe quelle langue (pas d’accents, ni de caractères spéciaux par exemple), et comportait peu de symboles

Tester

Créer avec un éditeur de texte le fichier ascii.html dont le contenu est le suivant :

HTML
<!DOCTYPE html>
<html>
<head>
    <meta charset="ASCII" />
    <title>ASCII</title>
</head>
<body>
    <p>
    Intérêt de la traçabilité
    </p>
</body>
</html>

Créer avec un éditeur de texte le fichier utf8.html dont le contenu est le suivant :

HTML
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>UTF8</title>
</head>
<body>
    <p>
    Intérêt de la traçabilité
    </p>
</body>
</html>
Ouvrez ces deux fichiers avec le navigateur Firefox. Que constatez-vous ?

Solution

Pour le fichier codé avec "ASCII", il s'affiche "Intérêt de la traçabilité" au lieu de "Intérêt de la traçabilité".

les caractères spéciaux et accents ont été mal encodés.

ASCII : American Standard Code for Information Interchange*, créé en 1960 aux États-Unis. ascii

En ASCII, 127 «points de code» (nombres associés aux caractères) sont disponibles. Les caractères sont donc codés sur 7 bits.

II. Et le reste du monde ?⚓︎

Lorsque d'autres personnes que des americains ou des anglais ont voulu s'échanger des données faisant intervenir du texte, certains caractères (é, è, à, ñ, Ø, Ö, β, 漢...) étaient manquants. Les 127 caractères de l'ASCII étaient largement insuffisants. Il a donc été décidé de passer à... 256 caractères ! Il suffisait pour cela de coder les caractères non plus sur 7 bits mais sur 8 bits.

Ainsi naquît, après de nombreuses modifications successives (la dernière en date rajoutant par exemple le symbole €), la célèbre table ISO 8859-15, dite aussi Latin-9 : iso-latin

Utilisation :⚓︎

Les codes sont donnés en hexadécimal :

  • le caractère € correspond au code hexadécimal A4, donc au nombre décimal 164.
  • le caractère A correspond au code hexadécimal 41, donc au nombre décimal 65.

65... comme en ASCII ! Oui, la (seule) bonne idée aura été d'inclure les caractères ASCII avec leur même code, ce qui rendait cette nouvelle norme rétro-compatible.

Exemple :

Le fichier test.txt contient un texte enregistré avec l'encodage Latin-9. Ce fichier est ensuite ouvert avec un éditeur hexadécimal, qui permet d'observer la valeur des octets qui composent le fichier. (Comme le fichier est un .txt, le fichier ne contient que les données et rien d'autre.) latin

Parfait, mais comment font les Grecs pour écrire leur alphabet ? Pas de problème, il leur suffit d'utiliser... une autre table, appelée ISO-8859-7 :

grec

On retrouve les caractères universels hérités de l'ASCII, puis des caractères spécifiques à la langue grecque... oui mais les Thaïlandais alors ?

Pas de problème, ils ont la ISO-8859-11 :

thai

Évidemment, quand tous ces gens veulent discuter entre eux, les problèmes d'encodage surviennent immédiatement : certains caractères sont remplacés par d'autres.

III. Que fait un logiciel à l'ouverture d'un fichier texte ?⚓︎

Il essaie de deviner l'encodage utilisé... Parfois cela marche, parfois non. erreur

Normalement, pour un navigateur, une page web correctement codée doit contenir dans une balise meta le charset utilisé. twitter

Mais parfois, il n'y a pas d'autre choix pour le logiciel d'essayer de deviner l'encodage qui semble être utilisé.

IV. Unicode : enfin une normalisation avec l'arrivée de l'UTF⚓︎

Unicode

En 1996, le Consortium Unicode décide de normaliser tout cela et de créer un système unique qui contiendra l'intégralité des caractères dont les êtres humains ont besoin pour communiquer entre eux.

slogan

Ils créent l'Universal character set Transformation Format : l'UTF. Ou plutôt ils en créent... plusieurs 😢 :

  • l'UTF-8 : les caractères sont codés sur 1, 2, 3 ou 4 octets.
  • l'UTF-16 : les caractères sont codés sur 2 ou 4 octets.
  • l'UTF-32 : les caractères sont codés sur 4 octets.

Pourquoi est-ce encore si compliqué ? En UTF-32, 32 bits sont disponibles, soit \(2^{32}=4294967296\) caractères différents encodables.

C'est largement suffisant, mais c'est surtout très très lourd !
D'autres encodages plus légers, mais plus complexes, sont donc proposés :

Arrêtons-nous sur l'UTF-8 : utf8

Le principe fondateur de l'UTF-8 est qu'il est adaptatif : les caracères les plus fréquents sont codés sur un octet, qui est la taille minimale (et qui donne le 8 de "UTF-8"). Les autres caractères peuvent être codés sur 2, 3 ou 4 octets au maximum.

V. En Python chr et ord⚓︎

chr

chr(i) Renvoie la chaîne représentant un caractère dont le code de caractère Unicode est le nombre entier i. Par exemple, chr(97) renvoie la chaîne de caractères 'a', tandis que chr(8364) renvoie '€'. Il s'agit de la réciproque de ord().

L'intervalle valide pour cet argument est de 0 à 1114111 (0x10FFFF en base 16). Une exception ValueError est levée si i est en dehors de l'intervalle.

Source : https://docs.python.org/fr/3/library/functions.html#chr

ord

ord(c) Renvoie le nombre entier représentant le code Unicode du caractère représenté par la chaîne donnée. Par exemple, ord('a') renvoie le nombre entier 97 et ord('€') (symbole euro) renvoie 8364. Il s'agit de la réciproque de chr().

Source : https://docs.python.org/fr/3/library/functions.html#ord

Tester

Recopier à la main (sans copié/collé) les instructions suivantes une par une dans la console ci-dessous, les exécuter une par une , et bien observer les résultats.

🐍 Console Python
>>> chr(128512)
>>> chr(9822)
>>> ord("💚")
>>> bin(52)
>>> hex(52)
>>> int("20", 16)
>>> int("101010", 2)
>>> str("42")

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Exercice 1

Utiliser dans la console ci-dessous les syntaxes vues ci-dessus pour répondre aux questions :

  1. Quel est le code unicode de "b" en décimal ? en hexadécimal ?

  2. Quel est le caractère dont le code unicode décimal est 91?

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Solution
  1. Le code unicode de "b" est 98 en décimal, 62 en hexadécimal

  2. Le caractère dont le code unicode décimal est 91 est "["

Exercice 2

Compléter le script ci-dessous pour trouver le message qui a été codé en Unicode.

La réponse devra être affectée à la variable en_clair

Astuce
  • la fonction split(" ") permet de décomposer une chaine de caractères en une liste, en se servant de l'espace " " comme caractère séparateur.
  • int("1101100", 2) permet de récupérer facilement la valeur en base 10 du nombre binaire 1101100.

###(Dés-)Active le code après la ligne # Tests (insensible à la casse)
(Ctrl+I)
Entrer ou sortir du mode "deux colonnes"
(Shift+Esc ; Ctrl pour inverser les colonnes)
Entrer ou sortir du mode "plein écran"
(Esc)
Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier
Évaluations restantes : 5/5

.128013cPw-7 sSf.bgn38k9+vei;(h4l,mdo0qp65_:2)=tay1/ur050D0u0P0Q0v0A0h0g0b0A0Q0h0h0O010P0v0H010406050h0U0C0C0Q0V0R040i0E0A0U0:0E0n050T0`0|0~100^0H04051g191j0T1g0^0D0v0t0(0*0,0.0*0n0m0U0Q0m0u0e0H0R0P0y170g0y0v0m0y0A1L0y0P0?050Z0l0A0u1s0+0-011K1M1O1M0P1U1W1S0P0V1h1G0(130h0H0Q0n0.0M011Y1u010j0#0u0n0Q0C0u1S1@1_1~1!211W24260?0a0g0c0V0E0H0E0h0v160n0g0X1=0V0V0u0b2r19290n1h0T1G2E1.1:1/1T0D2b1v0v0n232o1S1p1r0)1Z2O2Q0n0E2U1S0H2x1h2C2E2+0_1^2s2W1 2!0V0}0A1S0Q1J2x0j0.030K0K0b2#0u1O2Z0E0e0o0e0S0?0g0S190Q2,2/0@2.2a2;1!2?2^2`2|0u2~01303234362R390e1|040g0M3g3i1_3k2C2N013p0Q2_1h2{0y2}2 31330X3z2!3B0o3d0o3H2B3j0^3L3n0.3O3Q053S3U3v3W3y2P3A3a0z3d0z3)1a3+3l2:1t3o0E2@3P3r3T3t3V3x3Y3{3!3a0J3d0J412+3,2/3M3:4b3@3w3X354h383a0I3d0I4n433-463/483q3R3s3u4v3`373B0f3d0f4E3J4p3m4H3N4J4a4L4c4N3_4g4Q3a0p3d0p4V2D4X452X4!493;3?4d3^4f4x4,0e0r3d0r4;3K4q3.4_4K3=4M4e4w3Z4z3b0F0?0S0F561k2)192U2H0D1:2M594w2T1q1h2(0u2*3j3*3J054w5D2a0v0D0.312C3B3c4L5L5N4 5g5Q1}2f0u5U5f4y5X2E3h444r0?260,1A0u5F2D0g5+590E0?0O5=3E5^4Z0h3D020G0U0E0P0w0S5l690F0F3e5l0F6a6e6f693e6c6m6l6g6b6l6j0S6o6j6o6n6b6c6k5}5 4^0h2704095}0^425G3L5T015O2/3B3D5c6N4*503|3C5Y255!6O5V5%3a6S0T5*4G6D3D6s6t6y6h696s6i6g6c6w6{6b6y6e6z6g6_6p6|6d6~6h6B6-1 6E0?6H6K4=794q6U0K5P3a3$5S5M6$5$513$0g5Z5#4P6X7n3)7a1!615k6j726~6z706 776}7F6;7H7O7F73696365676I7h5K7p6P1_3B3~7o7w4+6X3~7u6!7*6W4i0e7(3H6J2-6M7!7k6Q4j3r7j7r6X4k7.267:5W7 5)5J4@2=0?1O0h0P0u0K5.0h5:5}5@7B0.5`045|7f5~8p3N5-0u5/1B7Y8c1!8r0k8C5,041@1O0P8H590=040x8N606264660w0g7U8W8S4^8P0N7X8u5+7j7l0e4B7)7q7x7=4B856#6V888.1S6+3k8*7{5U8-4S8:8`6(0e4S8^8797947A584Z0n0?23320*0v0V8n6C1 8r8t2+8o9f6.0?8Z679y0w8)7`7i7|8-4.956%514.9a8;7+7=9H9e4Y4^0q0?0j489o8w9h04230:0u9n8u9u9S9q0d0?2P9Y9v8d048f8h8j8z8l8B9*9p1!8P0L9C5E916$8-539I827=539M9651a83H0gai9+8D3/9i0n9k0!9)9t9 8q5{9;9,3oanap9mawal018r0saC4r0l0?0b3T8#1 8P8R909=3oaJ042P8MaRax0.aPaNay9#0n9%ara4aSa!0?0BaH590C0v0?3GaYaD8%8(8u7_a,7Z927~5i807|aa5h5l6Z869N7;b85j8aat3NaU2(aWa$a.8Qbm8xa(aAa+6La-018%a35G0T5I5o5C5q5z190P5tbG2K2F0Q1VbD0T5r6J0X0Z0#0h04.

VI. La réponse à une question existentielle⚓︎

Pourquoi le caractère é en UTF-8 devient-il é en ISO 8859-15 ?

Q1. Grâce à la fonction ord puis à la fonction bin, écrire en binaire le nombre associé au caractère é en UTF-8.

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Correction

🐍 Script Python
>>> ord('é')
233
>>> bin(233)
'0b11101001'
Donc en UTF-8, é est associé au nombre 11101001.

Q2. D'après l'explication de fonctionnement de l'encodage adaptatif de l'UTF-8 (voir ci-dessus), les 8 bits nécessaires à l'encodage de é en UTF-8 vont être «encapsulés» dans 2 octets de la forme 110XXXXX 10XXXXXX, où les 11 X représentent les 11 bits d'information disponibles. Écrire ces 2 octets en complétant si nécessaire avec des 0 à gauche.

Correction

Sur 11 bits, le nombre 11101001 va s'écrire 00011101001. En séparant ces 11 bits en deux groupes de 5 bits et 6 bits (00011et 101001), et en les encapsulant, on obtient les deux octets 11000011 10101001.

Q3. Convertir les deux octets obtenus en notation décimale (grâce à int) puis en hexadécimal (grâce à hex).

Tronquer ou non le feedback dans les terminaux (sortie standard & stacktrace / relancer le code pour appliquer)
Si activé, le texte copié dans le terminal est joint sur une seule ligne avant d'être copié dans le presse-papier

Correction
🐍 Script Python
>>> int('11000011', 2)
195
>>> hex(195)
'0xc3'
>>> int('10101001', 2)
169
>>> hex(169)
'0xa9'

Q4. Si un logiciel considère à tort que les deux octets servant à encoder le é en UTF-8 servent à encoder deux caractères en ISO 8859-15, quels seront ces deux caractères ?

Correction

Le premier octet, c3 en hexadécimal, sera perçu en ISO 8859-15 comme le caractère Ã.
Le deuxième octet, a9 en hexadécimal, sera perçu en ISO 8859-15 comme la lettre ©.

Finalement, ce qui aurait dû être un é en UTF-8 se retrouvera être un é en ISO 8859-15.

image

VII. Exercices⚓︎

Conversion de chaînes de caractères

Le chiffre de César

La perfection d'un mot

VIII. Crédits⚓︎

Auteurs : Gilles Lassus, Mireille Coilhac