Apprendre comment convertir une chaine en utf-8 avec VBA, un billet blog de Pierre Fauconnier

Salut.

Après un peu d’histoire sur Unicode et utf-8 dans ce billet, nous avons vu dans ce billet la théorie du codage d’un caractère Unicode en utf-8. Il est temps maintenant de passer à la pratique, et d’écrire deux fonctions en VBA pour coder une chaine de caractères en utf-8, mais également de convertir une chaine reçue en utf-8 en chaine lisible sur notre machine.

En clair, nous allons voir comment passer de Je voudrais un café s’il vous plait. Ça vous fera 4€, Monsieur à Je voudrais un café s’il vous plait. Ça vous fera 4€, Monsieur et vice-versa. Pour réaliser cela, on va manipuler les bits avec des ET, des OU et des décalages.

Il faut bien comprendre ici que lorsqu’on transforme de l’unicode en utf-8, on transforme une chaine de caractère en une valeur binaire (des octets). Lorsque l’on transforme de l’utf-8 en unicode, on transforme une valeur binaire (des octets) en une chaine de caractères

Sachez aussi que certaines bibliothèques permettent de réaliser ces transformations (notamment ADODB avec Stream). Mais il n’existe pas à ma connaissance de fonction native VBA pour réaliser cela. D’où l’utilité d’avoir ces fonctions sous le coude, ou plutôt dans le module Tools à embarquer dans tous vos développements VBA. On notera cependant une limitation des fonctions proposées ci-dessous: Le VBA ne permet pas de récupérer les unicodes au delà de 65535. Dans les faits, ce n’est normalement pas limitant, sauf bien sûr à devoir utiliser des plans unicode qui vont au delà du code 65535.

Mais d’abord, voici les fonctions de conversion


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

Function Uni2Utf(Text As String) As String   Dim v As Long   Dim i As Long     For i = 1 To Len(Text)     v = AscW(Mid(Text, i, 1))     Select Case v       Case Is < 128         Uni2Utf = Uni2Utf & Mid(Text, i, 1)       Case Is < 2048         Uni2Utf = Uni2Utf & Chr(((v And 1984) / 64) Or 192)         Uni2Utf = Uni2Utf & Chr((v And 63) Or 128)       Case Else         Uni2Utf = Uni2Utf & Chr(((v And 61440) / 4096) Or 224)         Uni2Utf = Uni2Utf & Chr(((v And 4032) / 64) Or 128)         Uni2Utf = Uni2Utf & Chr((v And 63) Or 128)     End Select   Next End Function   Function Utf2Uni(Text As String) As String   Dim v As Long   Dim i As Long: i = 1     Do While i <= Len(Text)     v = Asc(Mid(Text, i, 1))     Select Case v       Case Is < 128         Utf2Uni = Utf2Uni & Mid(Text, i, 1)         i = i + 1       Case Is < 224         Utf2Uni = Utf2Uni & ChrW((v And 63) * 64 + (Asc(Mid(Text, i + 1, 1)) And 63))         i = i + 2       Case Else         Utf2Uni = Utf2Uni & ChrW(((v And 31) * 4096) + _           ((Asc(Mid(Text, i + 1, 1)) And 63) * 64) + _           (Asc(Mid(Text, i + 2, 1)) And 63))         i = i + 3     End Select   Loop End Function

Rappels sur l’arithmétique binaire et booléenne

Lorsque l’on décale les bits de n rangs vers la droite, on divise en fait la valeur exprimée par 2n. Lorsque l’on décale les bits de n rangs vers la gauche, on multiplie la valeur exprimée par 2n:

  • 6 / 2 = 3 => 0110 / 10 = 0011;
  • 24 / 8 = 3 => 11000 / 1000 = 0011;
  • 5 * 2 = 10 => 0101 * 10 = 1010;
  • 7 * 4 = 28 => 0111 * 100 = 11100.

Si l’on souhaite isoler certains bits d’une valeur binaire, on réalise un ET logique de cette valeur avec les bits que l’on veut isoler à 1:

010

11001 => 01011001 AND 00011100 => 00011000

Si l’on souhaite allumer un bit (le passer à 1), on réalise un OU logique avec ce bit à 1:

01

001111 OU 00100000 => 01101111 (Ici, cela revient à ajouter 32)

Si l’on souhaite éteindre un bit (le passer le 0), on réalise un ET logique avec ce bit à 0 et tous les autres à 1:

01

101111 ET 11011111 => 01001111 (ici, cela revient à retrancher 32)

Décorticage des fonctions

Si vous avez lu le second billet cité plus haut, vous vous souviendrez de la logique de conversion et du tableau qui illustrait les cas possibles

  1. un seul octet => 0xxxxxxx (7 bits pour le code);
  2. deux octets => 110xxxxx 10xxxxxx (11 bits pour le code);
  3. trois octets => 1110xxxx 10xxxxxx 10xxxxxx (16 bits pour le code);
  4. quatre octets => 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (21bits pour le code).

Nom : 2021-10-04_082049.png  Affichages : 147  Taille : 13,0 Ko

On comprend grâce à ce tableau que pour passer de unicode vers utf-8, on sépare les bits de couleurs dans des octets puis on place les bits de contrôle (les bits de poids fort) qui permettent de savoir de combien d’octets est composé le caractère. Lorsque l’on passe de utf-8 vers unicode, supprime les bits de contrôle et l’on rassemble les bits de couleurs des octets qui composent le caractère en une seule valeur, la valeur unicode du caractère codé. Ces opérations sont effectuées à coup de AND, OR et de décalages (multiplications et divisions).

unicode vers utf-8

v = AscW(Mid(Text, i, 1))

AscW récupère le code unicode d’un caractère. VBA limite les valeurs à 65535 caractères, de sorte que VBA ne permettra pas de traduire les caractères dont l’unicode est supérieur à 65535. Du coup, on n’aura jamais de caractère nécessitant 4 octets. Dans les faits, ce n’est normalement pas pénalisant pour nous, sauf à devoir traduire des caractères vraiment très spécifiques.


1

2

      Case Is < 128         Uni2Utf = Uni2Utf & Mid(Text, i, 1)

Lorsque le code du caractère est <128, il est converti tel quel. On l'ajoute donc simplement à la chaine de résultat


1

2

3

      Case Is < 2048         Uni2Utf = Uni2Utf & Chr(((v And 1984) / 64) Or 192)         Uni2Utf = Uni2Utf & Chr((v And 63) Or 128)

Lorsque le code est >127 et <2048 (2^11), on pourra coder le caractère sur deux octets. prend ici 2^11 car aura besoin de 3 bits contrôle pour l'octet poids fort (110xxxxx) et de deux bits de contrôle pour l’octet de poids faible (10xxxxxx). Il reste donc 11 bits sur les 16 pour coder le caractère. Le premier caractère est codé avec Chr(((v And 1984) / 64) Or 192):

  • v And 1984 => On isole les 5 bits de poids fort sur les 11 (1984dec = 11111000000bin)
  • /64 permet de décaler les 5 bits retenus de 6 rangs vers la droite (64 = 26)
  • Or 192 => +192 pour ajouter le « code » qui indique que l’on aura 2 octets pour le caractère (192dec = 11000000bin)


avec xxxxx000000bin, on obtient donc la valeur 110xxxxxbin

Le second caractère sera obtenu avec Chr((v And 63) Or 128):

  • v And 63 isole les 6 derniers bits de la valeur (63dec = 00111111bin)
  • Or 128 ajoute 128 (128dec = 10000000bin)


Avec yyxxxxxxbin, on obtient 10xxxxxxbin

Je pense qu’il n’est pas nécessaire de développer la suite de la fonction, vous avez compris la manoeuvre.

utf-8 vers unicode

ChrW(Valeur) permet de récupérer le caractère dont on passe le code unicode.


1

2

3

      Case Is < 128          Utf2Uni = Utf2Uni & Mid(Text, i, 1)          i = i + 1

Dans le sens unicode vers utf-8, on a vu qu'un caractère dont l'unicode était inférieur à 128 était repris tel quel. Dans l'autre sens de la conversion, on va donc faire de même (l'octet "vaut" le caractère) puis incrémenter i pour passer à l'octet suivant.


1

2

3

      Case Is < 224          Utf2Uni = Utf2Uni & ChrW((v And 63) * 64 + (Asc(Mid(Text, i + 1, 1)) And 63))          i = i + 2

Lorsque l'octet est supérieur à 127 mais <224, c'est que les trois bits de poids fort sont 110xxxxx. On sait donc qu'il y deux octets pour composer le caractère (110xxxxx 10xxxxxx):

  • (v And 63) * 64 isole les 5 bits de poids faible et les décale de 6 rangs vers la gauche;
  • (Asc(Mid(Text, i + 1, 1)) And 63) passe à l'octet suivant pour isoler les 6 bits de poids faible 10xxxxxx (Dans les faits, vue le deuxième bit de poids fort est à 0, ça revient à retrancher 128 = passe le bit de poids fort à 0 => 00xxxxxx).

110

xxxxx 10xxxxxx devient donc à recomposer la valeur des 11 bits représentés par les x. Bien sûr, vu que l'on a traité 2 octets, on ajoute i à 2 pour "sauter" l'octet de poids faible que l'on vient de traiter.

Vous l'aurez compris, c'est la même logique qui prévaut pour recomposer l'unicode tenant sur 3 octets.

Bien entendu, ces fonctions sont utilisables en Excel:

Nom : 2021-10-05_080039.png  Affichages : 130  Taille : 6,9 Ko

Voilà. J'espère que ces trois billets sur unicode et utf-8 vous ont plu et qu'ils vous ont permis de mieux comprendre le codage utf-8 d'une chaine de caractères unicode. J'espère également que les deux fonctions données vous permettront de mieux faire dialoguer vos données VBA avec le monde extérieur...

Petit plus: Si vous le souhaitez, vous pouvez saisir du texte dans Notepad++ pour le convertir dans un sens ou dans l'autre. Par exemple, créez une nouvelle page dans Notepad++ et assurez-vous qu'elle est en codage utf-8, puis saisissez Bonjour, un café svp. Ça fera 2€, monsieur. En basculant le codage de la page en ANSI, vous obtiendrez Bonjour, un café svp. Ça fera 2€, monsieur (comme avec la fonction Uni2Utf...)

.

Tiri

Je suis un développeur web qui cherche à résoudre les problèmes du monde réel. J'ai la passion d'apprendre et de partager mes connaissances avec les autres, aussi publiquement que possible.