IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

Pierre Fauconnier

[Actualité] Convertir une chaine en utf-8 avec VBA

Note : 2 votes pour une moyenne de 5,00.
par , 05/10/2021 à 09h01 (24626 Affichages)
Vous trouverez ici deux fonctions qui permettent de traiter les caractères utf-8 en VBA, dans les deux sens de conversion


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

Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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:
01011001 => 01011001 AND 00011100 => 00011000

Si l'on souhaite allumer un bit (le passer à 1), on réalise un OU logique avec ce bit à 1:
01001111 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:
01101111 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 : 10120
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


Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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.


Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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


Code vba : Sélectionner tout - Visualiser dans une fenêtre à part
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. On prend ici 2^11 car on aura besoin de 3 bits de contrôle pour l'octet de 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.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
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.


Code : Sélectionner tout - Visualiser dans une fenêtre à part
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).



110xxxxx 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 : 8640
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...)




.

Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Viadeo Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Twitter Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Google Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Facebook Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Digg Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Delicious Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog MySpace Envoyer le billet « Convertir une chaine en utf-8 avec VBA » dans le blog Yahoo

Mis à jour 09/10/2021 à 20h40 par Pierre Fauconnier

Catégories
Programmation , VBA , MS Office

Commentaires

  1. Avatar de curt
    • |
    • permalink
    Bonjour Pierre,

    au hasard d'une recherche, je suis tomber sur cet article.... qui résout pleinement mon besoin.
    C'est nickel. On a a présent la réponse à une question qu'on n'a pas encore posé.... c'est noël avant noël
    J'en profite pour te souhaiter un excellent noël et de très bonnes fêtes de fin d'année.
    Curt
  2. Avatar de g.rullier
    • |
    • permalink
    Bonjour,

    J'utilise l'ensemble de ton code depuis peu et je te remercie pour la mise à disposition.
    Seulement, je rencontre un problème avec le caractère 244 correspondant au ô.
    En effet, la méthode ChrW présente comme exception CharCode < -32768 ou > 65535

    Connais tu un moyen de contourner ?

    Je te remercie pour ton retour.
    Gwen
  3. Avatar de laurent_ott
    • |
    • permalink
    Citation Envoyé par g.rullier
    Bonjour,

    J'utilise l'ensemble de ton code depuis peu et je te remercie pour la mise à disposition.
    Seulement, je rencontre un problème avec le caractère 244 correspondant au ô.
    En effet, la méthode ChrW présente comme exception CharCode < -32768 ou > 65535 :roll:

    Connais tu un moyen de contourner ?

    Je te remercie pour ton retour.
    Gwen
    Bonjour,
    Il faudrait essayer avec l'API : ConvertUTF8ToString (ou inversement : ConvertStringToUTF8)


    Code VBA : Sélectionner tout - Visualiser dans une fenêtre à part
    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
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    Private Const CP_UTF8 = 65001
     
    Private Declare PtrSafe Function MultiByteToWideChar Lib "kernel32" (ByVal CodePage As Long, _
                                                                         ByVal dwFlags As Long, ByVal lpMultiByteStr As LongPtr, _
                                                                         ByVal cchMultiByte As Long, ByVal lpWideCharStr As LongPtr, _
                                                                         ByVal cchWideChar As Long) As Long
     
    Private Declare PtrSafe Function WideCharToMultiByte Lib "kernel32" (ByVal CodePage As Long, ByVal dwFlags As Long, _
                                                                         ByVal lpWideCharStr As LongPtr, ByVal cchWideChar As Long, _
                                                                         ByVal lpMultiByteStr As LongPtr, ByVal cbMultiByte As Long, _
                                                                         ByVal lpDefaultChar As Long, ByVal lpUsedDefaultChar As Long) As Long
     
    '------------------------------------------------------------------------------------------------
    Public Function ConvertStringToUTF8(StrInput As String) As String
    '------------------------------------------------------------------------------------------------
    Dim nBytes As Long, abBuffer() As Byte, i As Long
     
    If Len(StrInput) > 0 Then
        ' Get length in bytes *including* terminating null
        nBytes = WideCharToMultiByte(CP_UTF8, 0&, ByVal StrPtr(StrInput), -1, 0&, 0&, 0&, 0&)
        ' We don't want the terminating null in our byte array, so ask for `nBytes-1` bytes
        ReDim abBuffer(nBytes - 2)  ' NB ReDim with one less byte than you need
        nBytes = WideCharToMultiByte(CP_UTF8, 0&, ByVal StrPtr(StrInput), -1, ByVal VarPtr(abBuffer(0)), nBytes - 1, 0&, 0&)
        ' Génèration d'une chaîne à partir du tableau:
        For i = LBound(abBuffer) To UBound(abBuffer)
            ConvertStringToUTF8 = ConvertStringToUTF8 & Chr(abBuffer(i))
        Next i
    End If
     
    End Function
     
    '------------------------------------------------------------------------------------------------
    Private Function sUTF8ToUni(bySrc() As Byte) As String
    '------------------------------------------------------------------------------------------------
    ' Converts a UTF-8 byte array to a Unicode string
    Dim lBytes As Long, lNC As Long, lRet As Long
     
    lBytes = UBound(bySrc) - LBound(bySrc) + 1
    lNC = lBytes
    sUTF8ToUni = String$(lNC, Chr(0))
    lRet = MultiByteToWideChar(CP_UTF8, 0, VarPtr(bySrc(LBound(bySrc))), lBytes, StrPtr(sUTF8ToUni), lNC)
    sUTF8ToUni = Left$(sUTF8ToUni, lRet)
     
    End Function
     
    '------------------------------------------------------------------------------------------------
    Public Function ConvertUTF8ToString(UTF8String As String) As String
    '------------------------------------------------------------------------------------------------
    Dim bData() As Byte, sData As String, lSize As Long, i As Long
     
    sData = UTF8String
    lSize = Len(sData)
     
    If lSize > 0 Then
        ReDim bData(0 To lSize - 1)
        For i = 1 To lSize
         bData(i - 1) = Asc(Mid(sData, i, 1))
        Next i
        ' Convert all the data to Unicode
        sData = sUTF8ToUni(bData)
    Else
        sData = ""
    End If
     
    ConvertUTF8ToString = sData
    End Function
    '------------------------------------------------------------------------------------------------

    Bonne programmation.