web-dev-qa-db-fra.com

Obtenir la longueur réelle d'un std :: string encodé en UTF-8?

mon std :: string est encodé en utf-8, donc str.length () renvoie un résultat erroné.

J'ai trouvé cette information mais je ne sais pas comment l'utiliser pour le faire:

Les séquences d'octets suivantes sont utilisées pour représenter un caractère. La séquence à utiliser dépend du numéro de code UCS du caractère:

   0x00000000 - 0x0000007F:
       0xxxxxxx

   0x00000080 - 0x000007FF:
       110xxxxx 10xxxxxx

   0x00000800 - 0x0000FFFF:
       1110xxxx 10xxxxxx 10xxxxxx

   0x00010000 - 0x001FFFFF:
       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

Comment trouver la longueur réelle d'un std :: string encodé en UTF-8? Merci

27
jmasterx

Compter tous les premiers octets (ceux qui ne correspondent pas à 10xxxxxx).

int len = 0;
while (*s) len += (*s++ & 0xc0) != 0x80;
55
Marcelo Cantos

C++ ne connaît rien aux encodages, vous ne pouvez donc pas vous attendre à utiliser une fonction standard Pour le faire.

La bibliothèque standard en effet fait reconnaît l'existence de codages de caractères, sous la forme de paramètres régionaux. Si votre système prend en charge les paramètres régionaux, il est très facile d'utiliser la bibliothèque standard pour calculer la longueur d'une chaîne. Dans l'exemple de code ci-dessous, je suppose que votre système prend en charge les paramètres régionaux en_US.UTF-8. Si je compile le code et l'exécute en tant que "./a.out ー Sony", le résultat est qu'il y avait 13 valeurs de caractère et 7 caractères. Et tout cela sans aucune référence à la représentation interne des codes de caractères UTF-8 ni à l’utilisation de bibliothèques tierces.

#include <clocale>
#include <cstdlib>
#include <iostream>
#include <string>

using namespace std;

int main(int argc, char *argv[])
{
  string str(argv[1]);
  unsigned int strLen = str.length();
  cout << "Length (char-values): " << strLen << '\n';
  setlocale(LC_ALL, "en_US.UTF-8");
  unsigned int u = 0;
  const char *c_str = str.c_str();
  unsigned int charCount = 0;
  while(u < strLen)
  {
    u += mblen(&c_str[u], strLen - u);
    charCount += 1;
  }
  cout << "Length (characters): " << charCount << endl; 
}
18
user2781185

Vous devriez probablement suivre les conseils d’Omry et consulter une bibliothèque spécialisée à cet effet. Cela dit, si vous voulez juste comprendre l’algorithme pour le faire, je le posterai ci-dessous.

Fondamentalement, vous pouvez convertir votre chaîne en un format d'élément plus large, tel que wchar_t. Notez que wchar_t pose quelques problèmes de portabilité, car wchar_t est de taille variable en fonction de votre plate-forme. Sous Windows, wchar_t correspond à 2 octets, ce qui est idéal pour représenter UTF-16. Mais sous UNIX/Linux, il s'agit de quatre octets et est donc utilisé pour représenter UTF-32. Par conséquent, pour Windows, cela ne fonctionnera que si vous n'incluez pas de points de code Unicode supérieurs à 0xFFFF. Pour Linux, vous pouvez inclure toute la gamme de points de code dans un wchar_t. (Heureusement, ce problème sera résolu par les types de caractères Unicode C++ 0x.)

Avec cette mise en garde, vous pouvez créer une fonction de conversion en utilisant l'algorithme suivant:

template <class OutputIterator>
inline OutputIterator convert(const unsigned char* it, const unsigned char* end, OutputIterator out) 
{
    while (it != end) 
    {
        if (*it < 192) *out++ = *it++; // single byte character
        else if (*it < 224 && it + 1 < end && *(it+1) > 127) { 
            // double byte character
            *out++ = ((*it & 0x1F) << 6) | (*(it+1) & 0x3F);
            it += 2;
        }
        else if (*it < 240 && it + 2 < end && *(it+1) > 127 && *(it+2) > 127) { 
            // triple byte character
            *out++ = ((*it & 0x0F) << 12) | ((*(it+1) & 0x3F) << 6) | (*(it+2) & 0x3F);
            it += 3;
        }
        else if (*it < 248 && it + 3 < end && *(it+1) > 127 && *(it+2) > 127 && *(it+3) > 127) { 
            // 4-byte character
            *out++ = ((*it & 0x07) << 18) | ((*(it+1) & 0x3F) << 12) |
                ((*(it+2) & 0x3F) << 6) | (*(it+3) & 0x3F);
            it += 4;
        }
        else ++it; // Invalid byte sequence (throw an exception here if you want)
    }

    return out;
}

int main()
{
    std::string s = "\u00EAtre";
    cout << s.length() << endl;

    std::wstring output;
    convert(reinterpret_cast<const unsigned char*> (s.c_str()), 
        reinterpret_cast<const unsigned char*>(s.c_str()) + s.length(), std::back_inserter(output));

    cout << output.length() << endl; // Actual length
}

L'algorithme n'est pas totalement générique, car InputIterator doit être un caractère non signé. Vous pouvez donc interpréter chaque octet comme ayant une valeur comprise entre 0 et 0xFF. OutputIterator est générique (pour que vous puissiez utiliser std :: back_inserter sans vous soucier de l’allocation de mémoire), mais son utilisation en tant que paramètre générique est limitée: en gros, il doit sortir un tableau d’éléments Caractères UTF-16 ou UTF-32, tels que wchar_t, uint32_t ou les types C++ 0x char32_t. De plus, je n'ai pas inclus de code pour convertir les séquences d'octets de caractères de plus de 4 octets, mais vous devriez comprendre le fonctionnement de l'algorithme à partir de ce qui est posté.

En outre, si vous souhaitez simplement count le nombre de caractères, plutôt que la sortie dans un nouveau tampon de caractères larges, vous pouvez modifier l'algorithme pour inclure un compteur plutôt qu'un OutputIterator. Ou mieux encore, utilisez la réponse de Marcelo Cantos pour compter les premiers octets.

4
Charles Salvia

Ceci est une implémentation naïve, mais il devrait vous être utile de voir comment cela se fait:

std::size_t utf8_length(std::string const &s) {
  std::size_t len = 0;
  std::string::const_iterator begin = s.begin(), end = s.end();
  while (begin != end) {
    unsigned char c = *begin;
    int n;
    if      ((c & 0x80) == 0)    n = 1;
    else if ((c & 0xE0) == 0xC0) n = 2;
    else if ((c & 0xF0) == 0xE0) n = 3;
    else if ((c & 0xF8) == 0xF0) n = 4;
    else throw std::runtime_error("utf8_length: invalid UTF-8");

    if (end - begin < n) {
      throw std::runtime_error("utf8_length: string too short");
    }
    for (int i = 1; i < n; ++i) {
      if ((begin[i] & 0xC0) != 0x80) {
        throw std::runtime_error("utf8_length: expected continuation byte");
      }
    }
    len += n;
    begin += n;
  }
  return len;
}
3
Roger Pate

Je vous recommande d'utiliser UTF8-CPP . C'est une bibliothèque en-tête uniquement pour travailler avec UTF-8 en C++. Avec cette lib, cela ressemblerait à quelque chose comme ça:

int LenghtOfUtf8String( const std::string &utf8_string ) 
{
    return utf8::distance( utf8_string.begin(), utf8_string.end() ); 
}

(Le code vient du haut de ma tête.)

2
Lucas

essayez d’utiliser une bibliothèque de codage telle que iconv . elle a probablement l’API souhaitée.

une alternative consiste à implémenter votre propre utf8strlen, qui détermine la longueur de chaque point de code et itère les points de code au lieu des caractères.

1
Omry Yadan

Une approche légèrement paresseuse consisterait à ne compter que les octets principaux, mais à visiter chaque octet. Cela évite la complexité du décodage des différentes tailles d’octets de plomb, mais il est évident que vous payez pour consulter tous les octets, bien qu’il n’y en ait généralement pas beaucoup (2x-3x):

size_t utf8Len(std::string s)
{
  return std::count_if(s.begin(), s.end(),
    [](char c) { (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
}

Notez que certaines valeurs de code sont illégales en tant qu'octets principaux, celles qui représentent des valeurs supérieures aux 20 bits nécessaires pour unicode étendu, par exemple, mais l'autre approche ne saurait de toute façon pas traiter ce code.

0
Gem Taylor

Juste une autre implémentation naïve pour compter les caractères dans la chaîne UTF-8

int utf8_strlen(const string& str)
{
    int c,i,ix,q;
    for (q=0, i=0, ix=str.length(); i < ix; i++, q++)
    {
        c = (unsigned char) str[i];
        if      (c>=0   && c<=127) i+=0;
        else if ((c & 0xE0) == 0xC0) i+=1;
        else if ((c & 0xF0) == 0xE0) i+=2;
        else if ((c & 0xF8) == 0xF0) i+=3;
        //else if (($c & 0xFC) == 0xF8) i+=4; // 111110bb //byte 5, unnecessary in 4 byte UTF-8
        //else if (($c & 0xFE) == 0xFC) i+=5; // 1111110b //byte 6, unnecessary in 4 byte UTF-8
        else return 0;//invalid utf8
    }
    return q;
}
0
Twissell

La bibliothèque UTF-8 CPP a une fonction qui fait exactement cela. Vous pouvez inclure la bibliothèque dans votre projet (il est petit) ou simplement regarder la fonction. http://utfcpp.sourceforge.net/

char* twochars = "\xe6\x97\xa5\xd1\x88";
size_t dist = utf8::distance(twochars, twochars + 5);
assert (dist == 2);
0
Nemanja Trifunovic

Ce code que je porte de php-iconv vers c ++, vous devez d’abord utiliser iconv, espérons que c'est utile:

// porting from PHP
// http://lxr.php.net/xref/PHP_5_4/ext/iconv/iconv.c#_php_iconv_strlen
#define GENERIC_SUPERSET_NBYTES 4
#define GENERIC_SUPERSET_NAME   "UCS-4LE"

UInt32 iconvStrlen(const char *str, size_t nbytes, const char* encode)
{
    UInt32 retVal = (unsigned int)-1;

    unsigned int cnt = 0;

    iconv_t cd = iconv_open(GENERIC_SUPERSET_NAME, encode);
    if (cd == (iconv_t)(-1))
        return retVal;

    const char* in;
    size_t  inLeft;

    char *out;
    size_t outLeft;

    char buf[GENERIC_SUPERSET_NBYTES * 2] = {0};

    for (in = str, inLeft = nbytes, cnt = 0; inLeft > 0; cnt += 2) 
    {
        size_t prev_in_left;
        out = buf;
        outLeft = sizeof(buf);

        prev_in_left = inLeft;

        if (iconv(cd, &in, &inLeft, (char **) &out, &outLeft) == (size_t)-1) {
            if (prev_in_left == inLeft) {
                break;
            }
        }
    }
    iconv_close(cd);

    if (outLeft > 0)
        cnt -= outLeft / GENERIC_SUPERSET_NBYTES;

    retVal = cnt;
    return retVal;
}

UInt32 utf8StrLen(const std::string& src)
{
    return iconvStrlen(src.c_str(), src.length(), "UTF-8");
}
0
twotrees