Qui n’a pas galéré comme moi à recevoir des fichiers provenant de l’extérieur, supposés être en UTF8 mais malheureusement en Ansi.
Et qui n’a pas cherché pendant des heures sur internet comment faire la conversion de tels fichiers ?
Car si php propose des fonctions sur le sujet assez bien documentées, si python offre une bibliothèque également bien documentée, si en language C, on trouve des fonctions de bas niveau permettant de le faire. Quid du Pascal ?
Après avoir chercher durant des heures en vain, et expérimenté différentes solutions voici ce que j’en ai retenu :
- Avec Windows (pas essayé sur un Linux) si on se contente de lire un fichier ligne à ligne, et de transformer chaque ligne en UTF8 pour finir par l’écrire dans un fichier, chaque ligne sera effectivement convertie puis écrite… en Ansi !!. Ne comprenant pas pourquoi je constatais cela, j’en ai été convaincu après avoir finit par afficher la taille en octets de chaque ligne avant écriture (toujours identiques !!)
- Donc on peut lire le fichier en entrée ligne à ligne mais ensuite, il ne faut plus raisonner en chaîne de caractères : il faut la transformer en tableau de bytes, convertir ce tableau de bytes en UTF8, puis, ne surtout pas re-transformer ce tableau en chaîne, on obtiendrait encore de l’Ansi ! il faut écrire tel quel ce tableau dans le fichier de sortie.
D’où l’usage archaïque (choisis parce que facile à comprendre même pour un débutant) de AssignFile – Reset – ReadLn – CloseFile pour la lecture de chaque ligne.
Et surtout, l’usage de TStream pour écrire le tableau de byte converti en UTF-8 tel quel.
Le coeur de tout ça étant la fonction tab:=System.sysutils.TEncoding.UTF8.GetBytes(instring); qui reçoit la ligne instring qui est supposée être en Ansi, et la convertit en tableau de bytes UTF8.
Voici le code de conversion d’un fichier Ansi (Tresultat est un type record contenant : CR, un integer (code retour) et MSG un string (message de retour))
function AnsiFileToUtf8File2(fin,fout:string): tresultat; var sligin:ansistring; fhin:TextFile; i: integer; datain:TBytes; streamout:tstream; begin Result.CR:=0; Result.MSG:=''; if (fin=fout) then begin result.CR:=-1; Result.MSG:='Les fichiers d''entrée et de sortie ne peuvent pas avoir le même nom'; //ceinture, bretelle + capote exit; end; i:=0; if not fileexists(fin) then begin result.CR:=-1; Result.MSG:='Le fichier '+fin+' n''existe pas'; //ceinture, bretelle + capote exit; end; if fileexists(fout) then DeleteFile(fout); AssignFile(Fhin,fin); {$I-} //La vérification d'E/S est désactivée //ceinture, bretelle + capote Reset(Fhin); {$I+} //La vérification d'E/S est activée //ceinture, bretelle + capote if IoResult<>0 then //ceinture, bretelle + capote begin result.CR:=-1; Result.MSG:='Impossible d''ouvrir le fichier '+fin+' en lecture'; exit; end; streamout := TFileStream.Create(fout, fmcreate ); try while not eof(fhin) do begin readln(Fhin,sligin); sligin:=sligin+#13+#10; //Readln retire les caractères de saut de ligne donc je dois les remettre datain:=TEncoding.UTF8.GetBytes(sligin); // conversion en un tableau de bytes UTF8 streamout.Seek(0,soFromEnd); //se positionner à la fin du stream, au cas où (sert à rien je sais, est rester à cause de tests précédent) streamout.Write(Pointer(datain)^, Length(datain)); //écrit tel quel le tableau de bytes dans le stream. inc(i); //j'aime bien connaitre le nombre d'itération end; Finally CloseFile(Fhin); streamout.Free; end; result.MSG:='Terminé, '+inttostr(i)+' lignes traitées'; end;