tipos - Número de celdas de caracteres utilizadas por cadena
matriz de caracteres matlab (6)
Tengo un programa que genera una tabla de texto usando cadenas UTF-8, y necesito medir el número de celdas de caracteres monoespaciadas utilizadas por una cadena para poder alinearla correctamente. Si es posible, me gustaría hacer esto con funciones estándar.
De las preguntas frecuentes de UTF-8 y Unicode para Unix / Linux :
El número de caracteres se puede contar en C de forma portátil utilizando
mbstowcs(NULL,s,0)
. Esto funciona para UTF-8 como para cualquier otra codificación compatible, siempre que se haya seleccionado la configuración regional apropiada. Una técnica de cableado para contar el número de caracteres en una cadena UTF-8 es contar todos los bytes excepto aquellos en el rango 0x80 - 0xBF, porque son solo bytes de continuación y no caracteres propios. Sin embargo, la necesidad de contar caracteres surge sorprendentemente raramente en las aplicaciones.
El siguiente código toma en consideración las secuencias de bytes mal formadas. el ejemplo de datos de cadena proviene de " " Tabla 3-8. Uso de U + FFFD en conversión UTF-8 " " en el estándar Unicode 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;
}
Cuando escribo el código para UTF-8, veo la "Tabla 3-7. Secuencias de bytes UTF-8 bien formadas" en el estándar Unicode 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
Me sorprende que nadie haya mencionado esto, así que aquí va para que quede constancia:
Si desea alinear el texto en un terminal, necesita usar las funciones POSIX wcwidth
y wcswidth
. Aquí está el programa correcto para encontrar la longitud en pantalla de una cadena.
#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]));
}
}
Aquí hay un ejemplo de ello corriendo:
$ ./measure hello 莊子 cAb
hello: 5
莊子: 4
cAb: 4
Observe cómo los dos caracteres "莊子" y los tres caracteres "cAb" (observe el ancho doble A) tienen un ancho de 4 columnas.
Como utf8everywhere.org lo pone ,
El tamaño de la cadena tal como aparece en la pantalla no está relacionado con el número de puntos de código en la cadena. Uno tiene que comunicarse con el motor de renderizado para esto. Los puntos de código no ocupan una columna, incluso en fuentes y terminales monoespaciadas. POSIX tiene esto en cuenta.
Windows no tiene ninguna función wcwidth
incorporada para la salida de la consola; Si desea admitir caracteres de varias columnas en la consola de Windows , debe encontrar una implementación portátil de la renuncia a porque la consola de Windows no admite Unicode sin hacks locos. wcwidth
Puede o no tener una función strlen (3) compatible con UTF-8 disponible. Sin embargo, hay algunas funciones C simples disponibles que hacen el trabajo rápidamente.
Las soluciones C eficientes examinan el inicio del carácter para omitir bytes de continuación. El código simple (referenciado en el enlace de arriba) es
int my_strlen_utf8_c(char *s) {
int i = 0, j = 0;
while (s[i]) {
if ((s[i] & 0xc0) != 0x80) j++;
i++;
}
return j;
}
La versión más rápida utiliza la misma técnica, pero realiza una búsqueda previa de datos y realiza comparaciones de múltiples bytes, lo que resulta en una aceleración sustancial. El código es más largo y más complejo, sin embargo.
Si puede utilizar bibliotecas de terceros, eche un vistazo a la biblioteca de ICU de IBM:
También puede usar glib, lo que hace que su vida sea mucho más fácil cuando se trata de UTF-8. documentos de referencia glib