0
votes

Nombre de caractères UTF-8

Je programme quelque chose qui compte le nombre de caractères UTF-8 dans un fichier. J'ai déjà écrit le code de base mais maintenant, je suis coincé dans la partie où les caractères sont censés être comptés. Jusqu'à présent, voici ce que j'ai:

Ce qu'il y a à l'intérieur du fichier texte:

#include <stdio.h>

typedef unsigned char BYTE;

int main(int argc, char const *argv[])
{
    FILE *file = fopen("file.txt", "r");
    if (!file)
    {
        printf("Could not open file.\n");
        return 1;
    }
    int count = 0;

    while(1)
    {
        BYTE b;
        fread(&b, 1, 1, file);
        if (feof(file))
        {
            break;
        }
        count++;
    }
    printf("Number of characters: %i\n", count);

    fclose(file);

    return 0;
}

Ce que j'ai codé jusqu'à présent:

黄埔炒蛋
你好
こんにちは
여보세요

Ma question est la suivante: comment coderais-je la partie où les caractères UTF-8 sont comptés? J'ai essayé de chercher des inspirations dans GitHub et YouTube, mais je n'ai encore rien trouvé qui fonctionne bien avec mon code.

Edit: à l'origine, ce code imprime que le fichier texte comporte 48 caractères. Mais compte tenu de l'UTF-8, il ne devrait comporter que 18 caractères.


5 commentaires

Vous pouvez suivre les règles d'encodage . Si multi-octets, continuez à lire le bon nombre d'octets pour satisfaire l'encodage des caractères.


Voulez-vous également vérifier le codage UTF-8 ou voulez-vous supposer que les données sont valides UTF-8?


Je veux supposer que les données sont valides UTF-8.


fread(&b, 1, 1, file); if (feof(file)) - fait amusant, s'il y a une erreur lors de la lecture du fichier, votre code boucle sans fin. Oubliez feof existe! Vérifiez la valeur de retour des fonctions, de fread , pour déterminer si votre opération a réussi. if (fread(&b, 1, 1, file) != 1) { break; }


Mode pédantique: vous comptez les points de code Unicode (les points de code de contrôle ne sont pas toujours des «caractères Unicode», par exemple une nouvelle ligne), et il est contestable si une variante compte pour un ou deux caractères.


4 Réponses :


1
votes

En C, comme en C ++, il n'y a pas de solution toute faite pour compter les caractères UTF-8. Vous pouvez convertir UTF-8 en UTF-16 à l'aide de mbstowcs et utiliser la fonction wcslen , mais ce n'est pas le meilleur moyen pour les performances (surtout si vous n'avez besoin que de compter le nombre de caractères et rien d'autre).

Je pense qu'une bonne réponse à votre question est ici: compter les caractères Unicode en C ++ .

Ð • xample de la réponse sur le lien:

for (p; *p != 0; ++p)
    count += ((*p & 0xc0) != 0x80);


0 commentaires

1
votes

Vous pouvez consulter les spécifications: https://tools.ietf.org/html/rfc3629 .

Le chapitre 3 contient ce tableau:

   Char. number range  |        UTF-8 octet sequence
      (hexadecimal)    |              (binary)
   --------------------+---------------------------------------------
   0000 0000-0000 007F | 0xxxxxxx
   0000 0080-0000 07FF | 110xxxxx 10xxxxxx
   0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Vous pouvez inspecter les octets et créer les caractères Unicode.

Un point différent est de savoir si vous comptez un caractère de base et son accent (marque de combinaison cf. https://en.wikipedia.org/wiki/Combining_character ) comme un ou comme plusieurs caractères.


0 commentaires

2
votes

Voir: https://en.wikipedia.org/wiki/UTF-8#Encoding

Chaque séquence UTF-8 contient un octet de départ et zéro ou plusieurs octets supplémentaires. Les octets supplémentaires commencent toujours par les bits 10 et le premier octet ne commence jamais par cette séquence. Vous pouvez utiliser ces informations pour compter uniquement le premier octet de chaque séquence UTF-8.

    if((b&0xC0) != 0x80) {
        count++;
    }

Gardez à l'esprit que cela se cassera si le fichier contient des séquences UTF-8 non valides. De plus, les «caractères UTF-8» peuvent signifier des choses différentes. Par exemple, "ðŸ '© 🠿" sera compté comme deux caractères par cette méthode.


2 commentaires

Je viens de lire le contenu du lien que vous avez joint. Cependant, je suis assez confus quant à la façon dont j'appliquerai ces choses dans mon code.


@thepajama Pour une question spécifique, créez-en une nouvelle ici sur StackOverflow. Indiquez le point exact qui commence votre confusion et indiquez les choses que vous avez comprises jusqu'à présent.



1
votes

Vous pouvez choisir plusieurs options:

  • vous pouvez dépendre de l'implémentation de votre système de codage large et de codage multi-octets
    • vous pouvez lire le fichier comme un flux large et compter simplement les octets, dépendez du système pour effectuer la conversion de chaîne main1 UTF-8 en chaîne large par lui-même (voir main1 ci-dessous)
    • vous pouvez lire le fichier sous forme d'octets et convertir la chaîne multi-octets en une chaîne large et compter les octets (voir main2 ci-dessous)
  • Vous pouvez utiliser une bibliothèque externe qui fonctionne sur des chaînes UTF-8 et compter les caractères unicode (voir main3 ci-dessous qui utilise libunistring )
  • Ou utf8_strlen votre propre solution utf8_strlen qui fonctionnera sur une propriété de chaîne UTF-8 spécifique et vérifiez vous-même les octets, comme indiqué dans d'autres réponses.

Voici un exemple de programme qui doit être compilé avec -lunistring sous Linux avec une vérification rudimentaire des erreurs avec assert :

#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <assert.h>
#include <stdlib.h>

void main1()
{
    // read the file as wide characters
    const char *l = setlocale(LC_ALL, "en_US.UTF-8");
    assert(l);
    FILE *file = fopen("file.txt", "r");
    assert(file);
    int count = 0;
    while(fgetwc(file) != WEOF) {
        count++;
    }
    fclose(file);
    printf("Number of characters: %i\n", count);
}

// just a helper function cause i'm lazy
char *file_to_buf(const char *filename, size_t *strlen) {
    FILE *file = fopen(filename, "r");
    assert(file);
    size_t n = 0;
    char *ret = malloc(1);
    assert(ret);
    for (int c; (c = fgetc(file)) != EOF;) {
        ret = realloc(ret, n + 2);
        assert(ret);
        ret[n++] = c;
    }
    ret[n] = '\0';
    *strlen = n;
    fclose(file);
    return ret;
}

void main2() {
    const char *l = setlocale(LC_ALL, "en_US.UTF-8");
    assert(l);
    size_t strlen = 0;
    char *str = file_to_buf("file.txt", &strlen);
    assert(str);
    // convert multibye string to wide string
    // assuming multibytes are in UTF-8
    // this may also be done in a streaming fashion when reading byte by byte from a file
    // and calling with `mbtowc` and checking errno for EILSEQ and managing some buffer
    mbstate_t ps = {0};
    const char *tmp = str;
    size_t count = mbsrtowcs(NULL, &tmp, 0, &ps);
    assert(count != (size_t)-1);
    printf("Number of characters: %zu\n", count);
    free(str);
}

#include <unistr.h> // u8_mbsnlen from libunistring

void main3() {
    size_t strlen = 0;
    char *str = file_to_buf("file.txt", &strlen);
    assert(str);
    // for simplicity I am assuming uint8_t is equal to unisgned char
    size_t count = u8_mbsnlen((const uint8_t *)str, strlen);
    printf("Number of characters: %zu\n", count);
    free(str);
}

int main() {
    main1();
    main2();
    main3();
}


0 commentaires