IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

Gestion des locales UTF-8


Sujet :

C

  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    3
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 3
    Par défaut Gestion des locales UTF-8
    Bonjour,

    Plutôt habitué aux langages de script, je tente (péniblement pour le moment) de me mettre au C. Comme mon premier objectif est de développer une "bête" application texte et que mon système (Linux) utilise UTF-8, j'essaie de me figurer comment gérer ça de manière "portable" (qu'au moins le programme se comporte pareillement en locale UTF-8 et en locale ISO-8859-* &cie). Voici pour le moment le laborieux résultat auquel j'arrive :

    Code : 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
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <locale.h>
    #include <wchar.h>
     
    int main (int argc, char *argv[]) {
        setlocale(LC_CTYPE, "");
        mbstate_t state;
        memset(&state, 0, sizeof(state));
        const char *str = argv[1];
        size_t  l = strlen(str);
        wchar_t wstr[l];
        size_t wl = mbsrtowcs(wstr, &str, l, &state);
        if (l > wl) {
            printf("YES %-10ls%s\n", wstr, "ok");
            wprintf(L"OUI %-10s%s\n", wstr, "ok");
        } else {
            printf("NO  %-10s%s\n", argv[1], "ok");
        }
        printf("    %-10s%s\n", "ok", "ok");
     
        return EXIT_SUCCESS;
    }
    Il compile sans émettre d'avertissements, mais se comporte bizarrement à l'exécution :

    $ gcc -Wall -o /tmp/out testlocale.c
    $ /tmp/out dodo
    NO  dodo      ok
        ok        ok
    $ /tmp/out dédé
    YES dédé    ok
        ok        ok
    
    Dans le premier cas, on voit qu'aucun caractère large n'a été détecté dans l'argument et que l'alignement des champs est correct. Dans le second, au contraire, c'est alignement est faut alors que les caractère larges on bien été détectés et qu'en conséquence l'emploi de "%ls" pour imprimer la chaîne devrait assurer une représentation correcte (alors qu'ici les caractères accentués comptent double) ; pire même, l'instruction wprintf a purement et simplement été ignorée.

    Pour le wprintf, j'ai trouvé cet élément de réponse, mais jouer avec fwide amène au mieux à supprimer toute sortie. Bref, je sèche complètement à comprendre comment obtenir une représentation correcte des caractères ici. Est-ce que par hasard quelqu'un connaîtrait la/une bonne manière de s'y prendre ? D'avance merci.

    (En question subsidiaire, sans vouloir abuser du forum, j'aimerais également comprendre pourquoi remplacer argv[1] par str dans l'avant-dernier printf aboutit à l'impression d'un champ de taille correcte mais sans la chaine... )

  2. #2
    Membre Expert
    Avatar de kwariz
    Homme Profil pro
    Chef de projet en SSII
    Inscrit en
    Octobre 2011
    Messages
    898
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet en SSII
    Secteur : Conseil

    Informations forums :
    Inscription : Octobre 2011
    Messages : 898
    Par défaut
    Salut,

    Pour faire court dans un premier temps, un char est codé sur 8 bits donc convient parfaitement aux encodages à longueur fixe d'un octet comme ascii (USASCII sur 7 bits, ou iso8859-X sur 8 bits), un wchar_t est codé sur 16 bits et convient parfaitement aux encodages à longueur fixe de deux octets comme UCS2 par exemple. Quand je dis "convient parfaitement" j'entends par là que la longueur en char/wchar_t est égale à la longueur en "caractères".
    Le problème avec utf8 (et utf16 en utilisant wchar_t) vient de la longueur variable d'octets pour coder certains caractères. En gros utf8=USASCII pour tout les caractères de code ascii<128, ce qui explique son utilisation répandue (pour un fichier source en c, la version utf8 et ascii sont les mêmes). Il n'y a pas de problèmes non plus pour la lecture et l'écriture (comme aucun octet ne peut être nul) si tu es en utf8, par contre strlen te donne la longueur en octet et plus la longueur en caractère, ce qui est ok pour toutes les allocations ; parser une chaîne devient plus problématique car chercher 1 caractère doit se résoudre en cherchant une chaine de 2 octets. Mais en général ça se passe pas trop mal.

    En revanche si tu cherches à développer une application gérant plusieurs charsets, je te conseille de :

    * choisir 1 charset adéquat pour tout ce qui est tambouille interne (de préférence à longueur fixe ou "suffisamment fixe" i.e. fixe sur l'ensemble des caractères que tu vas utiliser)
    * transcoder toute entrée et toute sortie du charset user vers ton charset de travail.

    Pour ce qui est du transcodage tu as plusieurs solutions, iconv da la gnu lib c (exemple), quelques fonctions que tu peux trouver dans la gnulib (sources redistribuées), des bibliothèques plus lourdes comme ICU.

  3. #3
    Rédacteur
    Avatar de Vincent Rogier
    Profil pro
    Inscrit en
    Juillet 2007
    Messages
    2 373
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France

    Informations forums :
    Inscription : Juillet 2007
    Messages : 2 373
    Par défaut
    Citation Envoyé par kwariz Voir le message
    un wchar_t est codé sur 16 bits .
    hum... avec le runtime C de Miscrosoft, oui... Pas ailleurs !

    1/ La norme ne précise en aucun cas la taille d'un wchar_t qui dépend de l'implémentation. (ca c'est vraiment con...)
    2/ sous windows avec le runtime C de MS, wchar_t = unsigned short = 2 bytes
    3 / Sous Unix, c'est disparate, cela peut être 2 ou 4 bytes (short ou long). La plupart de Unix implémentent wchar_t sur 4 bytes...
    4/ Par exemple, gcc utilise 4 bytes par défaut mais on lui fournir un option pour qu'il traite les wchar_t sous 2 bytes....

    Donc, faut faire attention....
    Vincent Rogier.

    Rubrique ORACLE : Accueil - Forum - Tutoriels - FAQ - Livres - Blog

    Vous voulez contribuer à la rubrique Oracle ? Contactez la rubrique !

    OCILIB (C Driver for Oracle)

    Librairie C Open Source multi-plateformes pour accéder et manipuler des bases de données Oracle

  4. #4
    Candidat au Club
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    3
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 3
    Par défaut
    Bon, j'ai compris pourquoi le %-10ls ne donne pas le résultat escompté, en regardant le man de printf :

    If an l modifier is present: The const wchar_t * argument is expected to be a pointer to an array of wide characters. Wide characters from the array are converted to multibyte characters (each by a call to the wcrtomb(3) function, with a conversion state starting in the initial state before the first wide character), up to and including a terminating null wide character. The resulting multibyte characters are written up to (but not including) the terminating null byte. If a precision is specified, no more bytes than the number specified are written, but no partial multibyte characters are written. Note that the precision determines the number of bytes written, not the number of wide characters or screen positions.
    printf continue à compter en octets, si bien que la chaîne UTF-8 dédé vaut à nouveau 6 et non 4 ; la conversion préalable est donc parfaitement inutile.

    Citation Envoyé par kwariz Voir le message
    En revanche si tu cherches à développer une application gérant plusieurs charsets, je te conseille de :

    * choisir 1 charset adéquat pour tout ce qui est tambouille interne (de préférence à longueur fixe ou "suffisamment fixe" i.e. fixe sur l'ensemble des caractères que tu vas utiliser)
    * transcoder toute entrée et toute sortie du charset user vers ton charset de travail.
    [...]
    C'est sûr que c'est LA solution ultime. D'un autre côté, comme tu l'as soulevé, la plupart du temps ce genre de chose est complètement transparent. Le cas que je présente est un des rares où en console j'ai eu besoin de savoir ce qu'est un caractère (l'écrasante majorité de mes scripts shell ont une locale forcée à "C"). J'aimerais donc tant que faire ce peut éviter de sortir l'artillerie lourde...

    De plus, je pense qu'ici ça ne changerait guère mon problème, car je n'ai aucun recours pour imprimer la chaîne, dans la mesure où printf se base sur les octets (alors que wl renvoie la bonne longueur pour la chaîne UTF-8 dédé, il n'y a donc pas de soucis pour obtenir une représentation correcte). Un coup de grep sur le code du GNU AWK montre que ni wprintf ni fwide ne sont employés nulle-part. Or, je sais que le cas que je présente (champ avec une chaîne UTF-8) est géré convenablement par cet interpréteur. Il doit donc y avoir une solution, mais le code de la bête est malheureusement totalement hors de ma portée...

  5. #5
    Membre confirmé
    Profil pro
    amateur
    Inscrit en
    Avril 2012
    Messages
    145
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations professionnelles :
    Activité : amateur
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Avril 2012
    Messages : 145
    Par défaut confusions unicode
    Attention: je crois que ton utilisation initiale des wchar est le signe d'une confusion à propos de Unicode en générale. Par rapport aux charset "historiques", Unicode (ou plutôt le standard ISO UCS sur lequel Unicode se base) introduit plusieurs niveaux de codage supplémentaires, et oblige quasiment à abandonner certains présupposés implicites.

    Avec un charset historique, on présupposait:
    1. Chaque code pour un caractère à la même longueur.
    2. Chaque caractère correspond à un code et réciproquement, "biunivoquement".
    Ces deux présuppositions sont fausses avec Unicode.

    Dans l'ancien temps ;-), les codes pouvaient avoir plusieurs bytes dans certains charsets, mais sauf cas très particuliers comme le fameux Shift-JIS, la représentation d'un caractère prenait toujours la même longueur. Ce n'est plus vrai, et il n'y a même pas en Unicode de longueur maxi en codes (je parle bien de code points Unicode) pour un caractère. Ce que représente un code point et que la doc Unicode appelle malheureusement abstract character n'est pas un caractère au sens courant ni au sens habituel en programmation. Donc, même si tu traduis tes sources textuelles et les encodes en interne avec des tableaux de code points, tu ne retrouveras pas l'équivalence 1 code <--> un caractère. Par exemple "â" prend à la base 2 points de codes en codage UCS (quel que soit l'encoding). Il n'est donc plus possible facilement et légèrement d'indexer un texte en caractères, sauf à entreprendre un grosse machinerie (il faut d'abord normaliser puis trier les codes à l'intérieur de chaque caractère pour retrouver la biunivocité, et enfin "empiler" les codes par caractère: ta chaîne est alors uns séquence de mini-tableaux, chacun pour un caractère).

    Secondo, pour tneter de faire passer la pilule peut-être, et en plus d'appeler les choses encodées "abstract characters", le standard propose des codes prédéfinis pour de très nombreux caractères composés. Cela donne l'illuson de retrouver la correspondance code <-> caractère. Donc il existe aussi, en plus, un code simple pour "â". Et là ce qu'on perd en programmation est encore plus grave: l'unicité de la représentation des caractères, mots, textes en général. Si tu cherches "â" dans un texte, tu trouveras les "â" encodés de la même que ta chaîne de recherche, pas les autres...

    Voilà. Pour revenir à ton problème, le plus simple est sans doute de rester en utf8, à condition d'oublier les idées fausses:
    * Il est possible d'indexer en caractères.
    * Les représentations de caractères sont uniques.
    (Tu n'as donc pas besoin de wchar. En fait, les 16 bits n'étaient possiblement utiles qu'à l'époque du début d'UCS où les gens du standard pensaient pouvoir le faire tenir sur 16 bits. Et c'est aussi la raison pour laquelle ICU travaille est conçu à la base en 16 bits )
    Note: tout cela est indépendant de C. C'est aussi vrai en python par exemple.

    denis

    Denis

  6. #6
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 393
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 393
    Par défaut
    Note: pour le coup des multiples représentations pour un même caractère, je précise que cela est mitigé par les "formes normalisées" d'unicode, et les fonctions de normalisation associées.

    Malheureusement, ces fonctions de normalisation ne font pas partie de la bibliothèque standard du C: Le C reste basé en grande partie sur l'ASCII et les ASCII étendus historiques, malgré la présence de fonctions pour les caractères "larges".
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    Candidat au Club
    Profil pro
    Inscrit en
    Avril 2012
    Messages
    3
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2012
    Messages : 3
    Par défaut
    Citation Envoyé par denispir Voir le message
    Attention: je crois que ton utilisation initiale des wchar est le signe d'une confusion à propos de Unicode en générale. Par rapport aux charset "historiques", Unicode (ou plutôt le standard ISO UCS sur lequel Unicode se base) introduit plusieurs niveaux de codage supplémentaires, et oblige quasiment à abandonner certains présupposés implicites.
    Tout à fait d'accord en ce qui concerne Unicode, qui est à s'arracher les cheveux. C'est pour ça que j'ai parlé de locales UTF-8, qui est une implémentation avec laquelle -- sauf erreur -- on peut au moins tenir pour acquises deux choses :
    • la représentation des caractères ASCII est la même qu'en ASCII ;
    • les caractères non-ASCII sont forcément codés au moins sur deux octets (c'est pour ça que je teste si l est supérieur à wl).


    D'autre part, il ne faut pas oublier que je positionne la locale, ce qui fait que l'encodage de l'utilisateur n'est plus un mystère pour le programme. Voici la version "améliorée" de mon code (elle marche, mais j'ai pourtant l'impression qu'il manque des malloc quelque part) :
    Code : 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
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <locale.h>
    #include <wchar.h>
     
    void wcfill (int space) {
        /* Add spaces to fill a field
         * $f AMOUNT OF SPACE     */
        while (space-- > 0) {
            putchar(' ');
        }
    }
     
    int main (int argc, char *argv[]) {
        setlocale(LC_CTYPE, "");
        mbstate_t state;
        memset(&state, 0, sizeof(state));
        int i;
        for (i = 1; i < argc; i++) {
            const char *str = argv[i];
            size_t  l = strlen(str);
            wchar_t wstr[l];
            size_t wl = mbsrtowcs(wstr, &str, l, &state);
            //printf("%d %d\n", l, wl);
            if (l > wl) {
                printf("YES %ls", wstr);
                wcfill(10 - wl);
                printf("%s\n", "ok");
            } else {
                printf("NO  %-10s%s\n", argv[i], "ok");
            }
            printf("    %-10s%s\n", "ok", "ok");
        }
        return EXIT_SUCCESS;
    }
    $ gcc -Wall -o /tmp/out testlocale.c 
    $ /tmp/out les crêpes sont passées                                                                                 
    NO  les       ok
        ok        ok
    YES crêpes    ok
        ok        ok
    NO  sont      ok
        ok        ok
    YES passées   ok
        ok        ok
    $ LANG=C /tmp/out les crêpes sont passées
    NO  les       ok
        ok        ok
    NO  crêpes   ok
        ok        ok
    NO  sont      ok
        ok        ok
    NO  passées  ok
        ok        ok
    
    La deuxième exécution de /tmp/out en locale "C" provoque l'échec de la fonction mbsrtowcs(), qui retourne -1, si bien que c'est la partie du code "gestion 8bits" qui est utilisée. C'est également le cas avec une locale "fr_FR" (ISO-8859-1), y compris lorsqu'on emploie des caractères codés au-delà de 127.

    Après, il reste effectivement le problème des autres encodages débutant sur plusieurs octets ou n'englobant pas l'ASCII, mais j'ai du mal à voir comment ça pourrait fonctionner côté utilisateur (ce serait par exemple impossible de télécharger et lire le moindre code source ou fichier texte sans le passer par iconv, et probablement aussi galère pour compiler une forme éditée... ) Bref, l'idée que l'utilisateur utilise soit un encodage 8bits ASCII-compatible, soit UTF-8 me paraît un point de départ pas trop moisi.

Discussions similaires

  1. Réponses: 3
    Dernier message: 03/12/2013, 01h16
  2. Gestion des accents et UTF-8
    Par Alpha573 dans le forum Langage
    Réponses: 2
    Dernier message: 10/11/2011, 11h14
  3. Gestion des plugins dans Firefox installé en local
    Par Caduchon dans le forum Applications et environnements graphiques
    Réponses: 1
    Dernier message: 11/09/2011, 16h50
  4. Réponses: 0
    Dernier message: 24/11/2010, 10h13
  5. c: gestion des exceptions
    Par vince_lille dans le forum C
    Réponses: 7
    Dernier message: 05/06/2002, 14h11

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo