Vraag Aantal tekencellen dat wordt gebruikt door tekenreeks


Ik heb een programma dat een teksttabel uitvoert met behulp van UTF-8-reeksen, en ik moet het aantal monospaced-tekencellen meten dat door een reeks wordt gebruikt, zodat ik het op de juiste manier kan uitlijnen. Indien mogelijk zou ik dit willen doen met standaardfuncties.


23
2018-02-25 12:48


oorsprong


antwoorden:


Van UTF-8 en Unicode FAQ voor Unix / Linux:

Het aantal tekens kan op een draagbare manier in C worden geteld met behulp van mbstowcs(NULL,s,0). Dit werkt voor UTF-8 zoals voor elke andere ondersteunde codering, zolang de juiste locale is geselecteerd. Een vaste techniek om het aantal tekens in een UTF-8-reeks te tellen, is alle bytes te tellen, behalve die in het bereik 0x80 - 0xBF, omdat dit gewoon doorlopende bytes zijn en geen eigen tekens. De noodzaak om karakters te tellen komt echter verrassend zelden voor in toepassingen.


31
2018-02-25 12:55



U kunt al dan niet beschikken over een UTF-8-compatibele strlen (3) -functie. Er zijn echter some simple C-functies direct beschikbaar die het werk snel doen.

De efficiënte C-oplossingen onderzoeken de start van het karakter om continuation bytes over te slaan. De eenvoudige code (waarnaar wordt verwezen via de link hierboven) is

int my_strlen_utf8_c(char *s) {
   int i = 0, j = 0;
   while (s[i]) {
     if ((s[i] & 0xc0) != 0x80) j++;
     i++;
   }
   return j;
}

De snellere versie gebruikt dezelfde techniek, maar vooraf haalt gegevens op en vergelijkt meerdere bytes, wat resulteert in een aanzienlijke versnelling. De code is echter langer en complexer.


20
2018-02-25 13:00



Ik ben geschokt dat niemand dit heeft genoemd, dus hier gaat het voor de goede orde:

Als u de tekst in een terminal wilt uitlijnen, moet u de POSIX-functies gebruiken wcwidth en wcswidth. Hier is het juiste programma om de lengte van een string op het scherm te vinden.

#define _XOPEN_SOURCE
#include <wchar.h>
#include <stdio.h>
#include <locale.h>
#include <stdlib.h>

int measure(char *string) {
    // allocate enough memory to hold the wide string
    size_t needed = mbstowcs(NULL, string, 0) + 1;
    wchar_t *wcstring = malloc(needed * sizeof *wcstring);
    if (!wcstring) return -1;

    // change encodings
    if (mbstowcs(wcstring, string, needed) == (size_t)-1) return -2;

    // measure width
    int width = wcswidth(wcstring, needed);

    free(wcstring);
    return width;
}

int main(int argc, char **argv) {
    setlocale(LC_ALL, "");

    for (int i = 1; i < argc; i++) {
        printf("%s: %d\n", argv[i], measure(argv[i]));
    }
}

Hier is een voorbeeld van het draaien:

$ ./measure hello 莊子 cAb
hello: 5
莊子: 4
cAb: 4

Merk op hoe de twee tekens "莊子" en de drie tekens "cAb" (let op de dubbele breedte A) beide 4 kolommen breed zijn.

Zoals utf8everywhere.org plaatst het,

De grootte van de tekenreeks zoals deze op het scherm wordt weergegeven, is niet gerelateerd aan de   aantal codepunten in de reeks. Men moet communiceren met de   rendering engine hiervoor. Coderingspunten bezetten zelfs geen enkele kolom   in monospace-lettertypen en -terminals. POSIX houdt hier rekening mee.

Windows heeft geen ingebouwde wcwidth functie voor console-uitvoer; als u tekens met meerdere kolommen in de Windows-console wilt ondersteunen je moet een draagbare implementatie vinden van wcwidth opgeven omdat de Windows-console Unicode niet ondersteunt zonder gekke hacks.


5
2018-06-29 19:34



Als u bibliotheken van derden kunt gebruiken, bekijkt u de ICU-bibliotheek van IBM:

http://site.icu-project.org/


4
2018-02-25 12:56



De volgende code houdt rekening met slecht gevormde byte-reeksen. het voorbeeld van stringgegevens komt van ""Tabel 3-8 Gebruik van U + FFFD in UTF-8-conversie""in de Unicode-standaard 6.3.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define is_trail(c) (c > 0x7F && c < 0xC0)
#define SUCCESS 1
#define FAILURE -1

int utf8_get_next_char(const unsigned char*, size_t, size_t*, int*, unsigned int*);
int utf8_length(unsigned char*, size_t);
void utf8_print_each_char(unsigned char*, size_t);

int main(void)
{
    unsigned char *str;
    str = (unsigned char *) "\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64";
    size_t str_size = strlen((const char*) str);

    puts(10 == utf8_length(str, str_size) ? "true" : "false");
    utf8_print_each_char(str, str_size);

    return EXIT_SUCCESS;
}

int utf8_length(unsigned char *str, size_t str_size)
{
    int length = 0;
    size_t pos = 0;
    size_t next_pos = 0;
    int is_valid = 0;
    unsigned int code_point = 0;

    while (
        utf8_get_next_char(str, str_size, &next_pos, &is_valid, &code_point) == SUCCESS
    ) {
        ++length;
    }

    return length;
}

void utf8_print_each_char(unsigned char *str, size_t str_size)
{
    int length = 0;
    size_t pos = 0;
    size_t next_pos = 0;
    int is_valid = 0;
    unsigned int code_point = 0;

    while (
        utf8_get_next_char(str, str_size, &next_pos, &is_valid, &code_point) == SUCCESS
    ) {
        if (is_valid == true) {
            printf("%.*s\n", (int) next_pos - (int) pos, str + pos);
        } else {
            puts("\xEF\xBF\xBD");
        }

        pos = next_pos;
    }
}

int utf8_get_next_char(const unsigned char *str, size_t str_size, size_t *cursor, int *is_valid, unsigned int *code_point)
{
    size_t pos = *cursor;
    size_t rest_size = str_size - pos;
    unsigned char c;
    unsigned char min;
    unsigned char max;

    *code_point = 0;
    *is_valid = SUCCESS;

    if (*cursor >= str_size) {
        return FAILURE;
    }

    c = str[pos];

    if (rest_size < 1) {
        *is_valid = false;
        pos += 1;
    } else if (c < 0x80) {
        *code_point = str[pos];
        *is_valid = true;
        pos += 1;
    } else if (c < 0xC2) {
        *is_valid = false;
        pos += 1;
    } else if (c < 0xE0) {

        if (rest_size < 2 || !is_trail(str[pos + 1])) {
            *is_valid = false;
            pos += 1;
        } else {
            *code_point = ((str[pos] & 0x1F) << 6) | (str[pos + 1] & 0x3F);
            *is_valid = true;
            pos += 2;
        }

    } else if (c < 0xF0) {

        min = (c == 0xE0) ? 0xA0 : 0x80;
        max = (c == 0xED) ? 0x9F : 0xBF;

        if (rest_size < 2 || str[pos + 1] < min || max < str[pos + 1]) {
            *is_valid = false;
            pos += 1;         
        } else if (rest_size < 3 || !is_trail(str[pos + 2])) {
            *is_valid = false;
            pos += 2;
        } else {
            *code_point = ((str[pos]     & 0x1F) << 12) 
                       | ((str[pos + 1] & 0x3F) <<  6) 
                       |  (str[pos + 2] & 0x3F);
            *is_valid = true;
            pos += 3;
        }

    } else if (c < 0xF5) {

        min = (c == 0xF0) ? 0x90 : 0x80;
        max = (c == 0xF4) ? 0x8F : 0xBF;

        if (rest_size < 2 || str[pos + 1] < min || max < str[pos + 1]) {
            *is_valid = false;
            pos += 1;
        } else if (rest_size < 3 || !is_trail(str[pos + 2])) {
            *is_valid = false;
            pos += 2;
        } else if (rest_size < 4 || !is_trail(str[pos + 3])) {
            *is_valid = false;
            pos += 3;
        } else {
            *code_point = ((str[pos]     &  0x7) << 18)
                       | ((str[pos + 1] & 0x3F) << 12)
                       | ((str[pos + 2] & 0x3F) << 6)
                       |  (str[pos + 3] & 0x3F);
            *is_valid = true;
            pos += 4;
        }

    } else {
        *is_valid = false;
        pos += 1;
    }

    *cursor = pos;

    return SUCCESS;
}

Wanneer ik code schrijf voor UTF-8, zie ik "Tabel 3-7 Goed gevormde UTF-8-bytesequenties" in de Unicode Standard 6.3.

       Code Points    First Byte Second Byte Third Byte Fourth Byte
  U+0000 -   U+007F   00 - 7F
  U+0080 -   U+07FF   C2 - DF    80 - BF
  U+0800 -   U+0FFF   E0         A0 - BF     80 - BF
  U+1000 -   U+CFFF   E1 - EC    80 - BF     80 - BF
  U+D000 -   U+D7FF   ED         80 - 9F     80 - BF
  U+E000 -   U+FFFF   EE - EF    80 - BF     80 - BF
 U+10000 -  U+3FFFF   F0         90 - BF     80 - BF    80 - BF
 U+40000 -  U+FFFFF   F1 - F3    80 - BF     80 - BF    80 - BF
U+100000 - U+10FFFF   F4         80 - 8F     80 - BF    80 - BF

3
2018-03-02 13:53



U kunt ook glib gebruiken, waardoor uw leven veel eenvoudiger wordt wanneer u met UTF-8 werkt. glib-referentiedocumenten


1
2018-03-17 14:34