Можно ли хранить и обрабатывать отдельные символы UTF-8 на C? Если да, то как?
Я написал программу на C, которая разбивает слова на слоги, сегменты и буквы. Он хорошо работает с символами ASCII, но я хочу сделать версии, которые работают для IPA и арабского языка.
У меня проблемы с сохранением и выполнением функций на отдельных персонажах. Мой редактор и консоль настроены на UTF-8 и могут хорошо отображать текст на арабском языке, если я сохраню его как char *, но когда я пытаюсь напечатать wchars, они отображают случайные знаки препинания.
Моя программа должна быть способна распознавать индивидуальный символ UTF-8 для работы. Например, для слова "хотя" он хранит "t" как слог [1] сегмент [1] письмо [1], h как слог [1] сегмент [1] письмо [2] и т.д. Я хочу иметь возможность сделать то же самое для символов, отличных от ASCII.
В течение всего дня я провел целый день, изучая unicode и тестируя различные методы, и я не могу заставить их записывать арабский символ в качестве персонажа.
Я не уверен, что я просто сделал некоторые глупые синтаксические ошибки на этом пути, если я полностью не понял всю концепцию или если на самом деле просто невозможно сделать то, что я хочу в C и я должен просто отказаться и попробовать другой язык...
Я бы в массовом порядке массово оценил любую помощь, которую вы можете предложить! Я новичок в программировании, но unicode полностью помогает моей работе, поэтому я хочу разобраться, как это сделать с самого начала.
Мое понимание того, как работает Юникод (в случае, если я ошибаюсь):
-
Я ввожу текст в свой редактор. Мой редактор кодирует его в соответствии с установленной мной кодировкой. Поэтому, если я установил его в UFT-8, он будет кодировать арабскую букву ب с 2-байтовой последовательностью 0xd8 0xab, которая указывает кодовую точку U + 0628.
-
Я скомпилирую его, разбив 0xd8 0xab на двоичный файл 11011000 10101000.
-
Я запускаю его в командной строке. Командная строка интерпретирует текст в соответствии с кодировкой, которую я установил, поэтому, если я установил ее в UFT-8, она должна интерпретировать 11011000 10101000 как кодовую точку U + 0628. Алгоритмы Unicode также говорят, какая версия U + 0628 будет отображаться для меня, поскольку персонаж имеет разные формы в зависимости от того, где он находится в слове. Поскольку персонаж один, он покажет мне автономную версию ب
Мое понимание способов обработки Unicode в C:
Вариант A - Используйте одиночные байты, кодированные как UTF-8 (http://www.nubaria.com/en/blog/?p=289)
Используйте одиночные байты, кодированные как UTF-8. Оставьте все мои типы данных как символы и char массивы и введите только символы ASCII в моем коде. Если мне абсолютно необходимо жестко закодировать символ юникода, введите его в виде массива в формате:
const char kChineseSampleText[] = "\xe4\xb8\xad\xe6\x96\x87";
Мои проблемы с этим:
- Мне нужно манипулировать отдельными символами
- Чтобы ввести арабские символы в качестве кодовых точек, мы сделаем мой код полностью нечитаемым и сильно замедляем меня.
Вариант B - используйте wchar и друзей (http://icu-project.org/docs/papers/unicode_wchar_t.html)
Обмен с использованием символов для wchars, которые содержат от 2 до 4 байтов в зависимости от компилятора. Строковые функции, такие как strlen, не будут работать, поскольку они ожидают, что символы будут одним байтом, но существуют w-функции, такие как wprintf, которые я могу использовать вместо этого.
Моя проблема с этим:
Я не могу заставить wchars печатать арабские символы вообще! Я могу заставить их печатать английские буквы в порядке, но арабские символы просто вытягиваются как случайные знаки препинания.
Я попытался ввести кодовую точку Юникода, а также фактический арабский символ, и я попробовал напечатать их как на консоли, так и в текстовом файле с кодировкой UTF-8, и я получаю тот же результат, хотя оба консоль и текстовый файл отображает арабский текст, если он введен как char *. Я включил свой код в конце.
(Его стоит сказать здесь, что я знаю, что многие люди думают, что wchars плохие, потому что они arent очень портативны и потому, что они занимают дополнительное пространство для символов ASCII. Но на этом этапе ни одна из этих вещей не вызывает беспокойства для меня - я просто пишу программу для запуска на своем собственном компьютере, и программа будет обрабатывать только короткие строки.)
Вариант C - использование внешних библиотек
Я читал в различных комментариях, что внешние библиотеки - это путь, поэтому я пробовал:
Библиотека программирования C
http://www.cprogramming.com/tutorial/unicode.htmlпредлагает заменить все символы целыми числами без знака и использовать специальные функции для итерации по строкам и т.д. На сайте даже предоставляется библиотека для загрузки.
Моя проблема:
Пока я могу установить символ как целое число без знака, я не могу его распечатать, потому что функции printf и wprintf не работают, и библиотека также не предоставляется на веб-сайте (я думаю, возможно, библиотека была разработана для Linux? Некоторые типы данных недействительны, и их изменение не работает)
Библиотека ICU
Моя проблема:
Я загрузил библиотеку ICU, но когда я изучал, как ее использовать, я видел, что такие функции, как characterIterator, недоступны для использования в C (http://userguide.icu-project.org/strings). Возможность итерации через персонажи полностью фундаментальна для того, что мне нужно делать, поэтому я не думаю, что библиотека будет работать на меня.
Мой код
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
#include <locale.h>
#include <string.h>
int main ()
{
wchar_t unicode = L'\xd8ac';
wchar_t arabic = L'ب';
wchar_t number = 0x062c;
FILE* f;
f = fopen("unitest.txt","w");
char* string = "ايه الاخبار";
//printf - works
printf("printf - literal arabic character is \"م\"\n");
fprintf(f,"printf - literal arabic character is \"م\"\n");
printf("printf - char* string is \"%s\"\n",string);
fprintf(f,"printf - char* string is \"%s\"\n",string);
//wprintf - english - works
wprintf(L"wprintf - literal english char is \"%C\"\n\n", L't');
fwprintf(f,L"wprintf - literal english char is \"%C\"\n\n", L't');
//wprintf - arabic - doesnt work
wprintf(L"wprintf - unicode wchar_t is \"%C\"\n", unicode);
fwprintf(f,L"wprintf - unicode wchar_t is \"%C\"\n", unicode);
wprintf(L"wprintf - unicode number wchar_t is \"%C\"\n", number);
fwprintf(f,L"wprintf - unicode number wchar_t is \"%C\"\n", number);
wprintf(L"wprintf - arabic wchar_t is \"%C\"\n", arabic);
fwprintf(f,L"wprintf - arabic wchar_t is \"%C\"\n", arabic);
wprintf(L"wprintf - literal arabic character is \"%C\"\n",L'ت');
fwprintf(f,L"wprintf - literal arabic character is \"%C\"\n",L'ت');
wprintf(L"wprintf - literal arabic character in string is \"م\"\n\n");
fwprintf(f,L"wprintf - literal arabic character in string is \"م\"\n\n");
fclose(f);
return 0;
}
Выходной файл
printf - literal arabic character is "م"
printf - char* string is "ايه الاخبار"
wprintf - literal english char is "t"
wprintf - unicode wchar_t is "�"
wprintf - unicode number wchar_t is ","
wprintf - arabic wchar_t is "("
wprintf - literal arabic character is "*"
wprintf - literal arabic character in string is ""
Я использую Windows 10, Notepad ++ и MinGW.
Edit
Это было отмечено как дубликат Light C Unicode Library, но я не думаю, что это действительно отвечает на мой вопрос. Я загрузил библиотеку и посмотрел, и вы можете назвать меня глупым, если хотите, но я действительно новичок в программировании, и я не понимаю большую часть кода в библиотеке, поэтому мне трудно работать как я могу использовать его, достигая того, чего хочу. Я искал библиотеку для функции печати и не смог найти ее...
Я просто хочу сохранить символ UTF-8, а затем распечатать его снова! Мне действительно нужно установить всю библиотеку для этого? Я просто очень признателен, если кто-то пожалеет меня и расскажет мне в детстве, как я могу это сделать... Люди продолжают говорить, что я должен использовать uint_32 или что-то вместо wchar, - но как мне потом напечатать эти типы данных? Могу ли я сделать это с помощью wprintf?!
Ответы
Ответ 1
C и UTF-8 все еще узнают друг друга. In-other-words, IMO, поддержка C для UTF-8 является скудным.
Возможно ли... сохранить и обработать отдельные символы UTF-8...?
Первый шаг состоит в том, чтобы сделать определенную "ايه الاخبار"
кодированную строку UTF-8. C поддерживает это явно с помощью u8"ايه الاخبار"
.
A UTF-8 string
является последовательностью char
. Каждый от 1 до 4 char
представляет символ Unicode. Для кодирования символов Unicode требуется не менее 21 бит. Тем не менее, OP не нуждается в преобразовании части string[]
в символ Unicode столько, сколько захочет сегментировать эту строку на границах UTF-8. Это легко найти, ища байты продолжения UTF-8.
Следующие формы образуют 1 символ Юникода, закодированный как строка UTF-8 с сопровождающим завершающим нулевым символом. Затем печатается короткая строка.
char* string = u8"ايه الاخبار";
for (char *s = string; *s; ) {
printf("<");
char u[5];
char *p = u;
*p++ = *s++;
if ((*s & 0xC0) == 0x80) *p++ = *s++;
if ((*s & 0xC0) == 0x80) *p++ = *s++;
if ((*s & 0xC0) == 0x80) *p++ = *s++;
*p = 0;
printf("%s", u);
printf(">\n");
}
При просмотре вывода на экране с поддержкой UTF8:
<ا>
<ي>
<ه>
< >
<ا>
<ل>
<ا>
<خ>
<ب>
<ا>
<ر>
Ответ 2
Пример с библиотекой utf8proc для итерации:
#include <utf8proc.h>
#include <stdio.h>
int main(void) {
utf8proc_uint8_t const string[] = u8"ايه الاخبار";
utf8proc_ssize_t size = sizeof string / sizeof *string - 1;
utf8proc_int32_t data;
utf8proc_ssize_t n;
utf8proc_uint8_t const *pstring = string;
while ((n = utf8proc_iterate(pstring, size, &data)) > 0) {
printf("<%.*s>\n", (int)n, pstring);
pstring += n;
size -= n;
}
}
Это, вероятно, не самый лучший способ использовать эту библиотеку, но я делаю issue github, чтобы иметь некоторый пример. Потому что я не могу понять, как работать с этой библиотекой.
Ответ 3
Вам нужно четко понимать разницу между кодовой точкой Unicode и UTF-8. UTF-8 представляет собой переменную байтовую кодировку кодовых точек Unicode. Нижний конец, значения 0-127, сохраняется как один байт. Это основной пункт UTF-8 и делает его обратно совместимым с Ascii.
Когда бит 7 установлен, для значений более 127 используется код переменной длины в два байта или более. У ведущего байта всегда есть битовая диаграмма 11xxxxxx.
Здесь код, чтобы получить пропуск (количество символов), также прочитать код и записать его.
static const unsigned int offsetsFromUTF8[6] =
{
0x00000000UL, 0x00003080UL, 0x000E2080UL,
0x03C82080UL, 0xFA082080UL, 0x82082080UL
};
static const unsigned char trailingBytesForUTF8[256] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
int bbx_utf8_skip(const char *utf8)
{
return trailingBytesForUTF8[(unsigned char) *utf8] + 1;
}
int bbx_utf8_getch(const char *utf8)
{
int ch;
int nb;
nb = trailingBytesForUTF8[(unsigned char)*utf8];
ch = 0;
switch (nb)
{
/* these fall through deliberately */
case 3: ch += (unsigned char)*utf8++; ch <<= 6;
case 2: ch += (unsigned char)*utf8++; ch <<= 6;
case 1: ch += (unsigned char)*utf8++; ch <<= 6;
case 0: ch += (unsigned char)*utf8++;
}
ch -= offsetsFromUTF8[nb];
return ch;
}
int bbx_utf8_putch(char *out, int ch)
{
char *dest = out;
if (ch < 0x80)
{
*dest++ = (char)ch;
}
else if (ch < 0x800)
{
*dest++ = (ch>>6) | 0xC0;
*dest++ = (ch & 0x3F) | 0x80;
}
else if (ch < 0x10000)
{
*dest++ = (ch>>12) | 0xE0;
*dest++ = ((ch>>6) & 0x3F) | 0x80;
*dest++ = (ch & 0x3F) | 0x80;
}
else if (ch < 0x110000)
{
*dest++ = (ch>>18) | 0xF0;
*dest++ = ((ch>>12) & 0x3F) | 0x80;
*dest++ = ((ch>>6) & 0x3F) | 0x80;
*dest++ = (ch & 0x3F) | 0x80;
}
else
return 0;
return dest - out;
}
Используя эти функции или аналогичные, вы конвертируете между кодовыми точками и UTF-8
и обратно.
В настоящее время Windows использует UTF-16 для своего apis. В первом приближении UTF-16 является кодовым пунктом в 16-битном формате. Поэтому при написании программы на основе UTF-8 вам необходимо преобразовать UTF-8 в UTF-16 (используя широкие символы) непосредственно перед вызовом выходных функций Windows.
Поддержка UTF-8 с помощью printf() неоднозначна. Передача кодированной строки UTF-8 в printf() вряд ли сделает то, что вы хотите.