Можно ли хранить и обрабатывать отдельные символы 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() вряд ли сделает то, что вы хотите.