Lire un fichier UTF-8


On applique la technique de lecture octet par octet au décodage d'un fichier en UTF-8,
sous l'hypothèse qu'il est écrit correctement.

I. Repérer les groupes d'octets qui constituent des caractères en UTF-8.

  1. Il s'agit de traduire ce que nous savons sur la structure des octets en UTF-8
    en termes de valeurs d'octets - notre seule manière d'appréhender les octets en C.

  2. Imprimer sur une seule ligne les valeurs des octets formant un même caractère UTF-8 :

    Rédaction en C (fichier lirUTF8.c)
    while ((val=fgetc(fp)) !=EOF) {
    if( val < 128 ){ /* ASCII */
    printf("%d\n", val);
    }else{
    val1 = val;
    if( val1 >= 240 ){ /* 4 octets : 240 = 128+64+32+16 */
    val2 = fgetc(fp);
    val3 = fgetc(fp);
    val4 = fgetc(fp);
    printf("%d %d %d %d\n", val1, val2, val3, val4);
    }else{
    if( val1 >= 224 ){ /* 3 octets : 224 = 128+64+32 */
    val2 = fgetc(fp);
    val3 = fgetc(fp);
    printf("%d %d %d\n", val1, val2, val3);
    }else{ /* 2 octets : >192 = 128+64 */
    val2 = fgetc(fp);
    printf("%d %d\n", val1, val2);
    }
    }
    }
    } /* while */


    Exécution du programme sur le fichier don.txt :
    jfp% ./a.out don.txt 
    65
    97
    32
    194 161
    32
    195 169
    195 160
    197 147
    195 185
    195 167
    195 135
    32
    225 130 171
    225 130 171
    32
    195 191
    32
    240 144 140 176
    32
    240 144 140 178
    32
    225 131 147
    32
    122
    90
    jfp%

II. Calculer les numéros Unicode correspondant aux codes UTF-8 lus dans le fichier

  1. Principe.

    Connaissant la structure des octets et la façon de construire le code UTF-8 à partir du numéro Unicode,
    adapter la boucle ci-dessus est un exercice de calcul en base 2.
    Pour chaque octet il faut :
    1. D'abord extraire la valeur des bits utiles, en soustrayant celle de l'en-tête ;
      cet en-tête est 10, ou 110, ou 1110, ou 11110, et sa valeur est donc 128, ou 192, ou 224 ou 240.
    2. Ensuite la multiplier par la puissance de 2 convenable.

    Pour un code sur 2 octets, par exemple, dont les valeurs sont va11 et val2,
    Pour un code sur 3 octets, valeurs va11, val2 et val3,
    Enfin, pour un code sur 4 octets, valeurs va11, val2, val3 et val4,

  2. Pour pouvoir vérifier commodément que nos programmes sont corrects, nous écrirons le numéros Unicode calculés dans un fichier sous forme d'entités HTML, c'est-à-dire dans le format "&#lenuméro-en-décimal;".
    Ce format est directement interprétable par un navigateur Web, il suffira donc de faire lire le fichier-résultat à un navigateur quelconque pour constater de visu que la traduction est exacte.


    Rédaction en C (fichier utf8ToHTMLd.c)

    while ((val=fgetc(fentr)) !=EOF) {
    if( val < 128 ){ /* ASCII */
    fprintf(fsort, "%c", val);
    }else{
    if( val >= 240 ){ /* 4 octets : 240 = 128+64+32+16 */
    contr1 = (val - 240) * 262144;
    contr2 = (fgetc(fentr)-128) * 4096;
    contr3 = (fgetc(fentr)-128) * 64;
    contr4 = fgetc(fentr)-128;
    fprintf(fsort, "&#%d;", contr1+contr2+contr3+contr4);
    }else{
    if( val >= 224 ){ /* 3 octets : 224 = 128+64+32 */
    contr1 = (val - 224) * 4096;
    contr2 = (fgetc(fentr)-128) * 64;
    contr3 = (fgetc(fentr)-128);
    fprintf(fsort, "&#%d;", contr1 + contr2 + contr3);
    }else{ /* 2 octets : >192 = 128+64 */
    contr1 = (val - 192) * 64;
    contr2 = fgetc(fentr)-128;
    fprintf(fsort, "&#%d;", contr1+contr2);
    }
    }
    }
    }/* while */


    Exécution du programme sur le fichier don.txt :
    jfp% ./a.out don.txt don.html
    jfp% cat don.html
    Aa &#161; &#233;&#224;&#339;&#249;&#231;&#199; &#4267;&#4267; &#255; &#66352; &#66354; &#4307; zZ
    jfp%


    Et à présent regardez don.html dans votre navigateur !

  3. Une variante utile est d'imprimer les numéros Unicode non plus en décimal, mais en hexadécimal,
    beaucoup plus utilisé en pratique.
    Pour cela, il suffit de modifier la "chaîne de format" qui est le premier argument de l'appel à fprintf.
    Afin d'obtenir une écriture sur 4 chiffres hexa, conforme à l'habitude, on choisit la chaîne "%04x" : Et c'est tout !
    Réalisation complète : fichier utf8ToHTMLh.c.

  4. Il est encore plus utile, comme on le verra plus loin, d'engendrer la représentation des caractères non-ASCII exigés
    par les compilateurs et par les interprètes des langages de programmation comme Java & JavaScript, à savoir
    des escape-sequences de la forme \ule-numéro-hexa.
    Il suffit d'adopter des ordres d'impression comme : fprintf(fsort, "\\u%04x;", val1+val2+...);
    Voyez utf8ToESC.c pour les détails.

III. Réciproquement, traduire les numéros Unicode en UTF-8

  1. Principe

    La fabrication des octets UTF-8 à partir du numéro Unicode supposé écrit en binaire
    est un exercice sur l'emploi des opérateurs "bit à bit" de C.
    En l'occurrence, on emploiera le 'AND' et le décalage à droite.

  2. Transformation inverse du programme utf8ToHTML

    (fichier utf8ToHTMLd.c) :
    prendre les numéros Unicode en décimal dans les entités HTML qui composent le fichier d'entrée,
    et envoyer les codes UTF-8 dans le fichier de sortie.

    Rédaction en C (fichier htmldToUTF8.c)

    while ((val=fgetc(fentr)) !=EOF) {
    if( val != '&' ){ /* ASCII */
    fputc(val, fsort);
    }else{
    fscanf(fentr, "#%d;", &numuni);
    if( numuni < 2048 ){ /* 11 bits, 2 octets */
    val2 = 128 + (numuni & 63); /* les 6 derniers bits */
    val1 = 192 + (numuni >> 6); /* les 5 bits de poids fort */
    fputc(val1, fsort);
    fputc(val2, fsort);
    }else{
    if( numuni < 65536 ){ /* 16 bits, 3 octets */
    val3 = 128 + (numuni & 63); /* les 6 derniers bits */
    val2 = 128 + ((numuni >> 6) & 63); /* les 6 suivants */
    val1 = 224 + (numuni >> 12); /* les 4 bits de poids fort */
    fputc(val1, fsort);
    fputc(val2, fsort);
    fputc(val3, fsort);
    }else{ /* 4 octets */
    val4 = 128 + (numuni & 63); /* les 6 derniers bits */
    val3 = 128 + ((numuni >> 6) & 63); /* les 6 suivants */
    val2 = 128 + ((numuni >> 12) & 63); /* les 6 suivants */
    val1 = 240 + (numuni >> 18); /* les 3 bits de poids fort */
    fputc(val1, fsort);
    fputc(val2, fsort);
    fputc(val3, fsort);
    fputc(val4, fsort);
    }
    }
    }
    }/* while */


    Vérification : la commande
    ./a.out don.html dan.txt
    redonne bien un fichier dan.txt identique au don.txt d'origine.

  3. Transformation inverse de utf8ToESC

    Pour qu'elle ait quelque intérêt, il faut que cette transformation s'applique à des textes en ASCII substantiels, 
    où se trouvent "dispersés" des caractères Unicode rendus illisibles par leur déguisement en ASCII,
    et dont il s'agit de révéler la vraie nature.

    Ceci suppose qu'on sache distinguer les escape-sequences du texte qui les entoure, par une opération d'analyse lexicale éminemment fastidieuse à programmer. Pour éviter cette complication, nous ferons l'hypothèse que l'apparition d'une contre-oblique "\" signale à coup sur un caractère Unicode codé.
    Sous cette hypothèse forte mais souvent vérifiée, il suffit de retoucher le code précédent en modifiant seulement le test initial sur val et l'ordre de lecture scanf :

    (fichier ESCToutf8.c)

    while ((val=fgetc(fentr)) !=EOF) {
    if( val != '\\' ){ /* ASCII */
    fputc(val, fsort);
    }else{
    fscanf(fentr, "u%x;", &numuni);
    .... le reste sans modification ....
    }
    }/* while */