Преобразование UTF-8 байтов в код Unicode

285
26 марта 2017, 01:35

Как преобразовать 4 байта UTF-8 в код Unicode?

F0 9F 98 81 -> 1F601
Answer 1

Альтернативный вариант — воспользоваться встроенным классом Encoding. Encoding.UTF8.GetString даёт нужную строку, но если перебирать её посимвольно, то мы получим слова UTF-16. Это обычно то, что нужно, кроме случая суррогатной пары. Это именно ваш случай.

Поэтому напишем процедуру, которая будет сканировать строку по символу и объединять суррогатные пары в одно значение.

static void Main(string[] args)
{
    var bytes = new byte[] { 0xF0, 0x9F, 0x98, 0x81 };
    var s = Encoding.UTF8.GetString(bytes);
    foreach (var v in GetCodes(s))
        Console.Write($"{v:X} ");
}
static IEnumerable<int> GetCodes(string s)
{
    char? high = null;
    foreach (var c in s)
    {
        if (high == null)
        {
            if (char.IsHighSurrogate(c))
                high = c;
            else if (char.IsLowSurrogate(c))
                throw new ArgumentException("Unpaired low surrogate");
            else
                yield return c;
        }
        else
        {
            if (char.IsLowSurrogate(c))
                yield return char.ConvertToUtf32(high.Value, c);
            else
                throw new ArgumentException("Unpaired high surrogate");
            high = null;
        }
    }
    if (high != null)
        throw new ArgumentException("Unpaired high surrogate");
}

Очевидный альтернативный подход — сконвертировать строку в UTF32, там-то уж никаких специальных случаев наподобие суррогатных пар нет. (UTF8, можно сказать, весь состоит из этих самых специальных случаев.)

Другие варианты решения обсуждаются здесь (верхний ответ, как пишет сам автор, неправилен).

Answer 2

Стандартного такого перевода я не нашел, поэтому написал свой.

Предположим, что эти байты сохранены в массив:

byte[] utf8Bytes = { 0xF0, 0x9F, 0x98, 0x81 };

Если в массиве всегда хранится ровно один символ (то есть можно опираться на длину массива), то метод перевода будет выглядеть так:

public static int GetUnicode(byte[] utf8Bytes)
{
    byte firstByteMask = 0x7F;
    if (utf8Bytes.Length > 1)
    {
        firstByteMask >>= utf8Bytes.Length;
    }
    int result = utf8Bytes[0] & firstByteMask;
    for (int i = 1; i < utf8Bytes.Length; i++)
    {
        result <<= 6;
        result += utf8Bytes[i] & 0x3F;
    }
    return result;
}

Если же количество байтов в коде символа нужно рассчитывать на основании значения первого байта, то код будет длиннее:

public static int GetUnicode(byte[] utf8Bytes)
{
    byte firstByte = utf8Bytes[0];
    int additionalBytesCount = 0;
    byte firstByteMask = 0x7F;
    if ((firstByte & 0x80) != 0)
    {
        for (byte i = 0x40; (firstByte & i) == i; i >>= 1)
        {
            additionalBytesCount++;
        }
        firstByteMask >>= additionalBytesCount + 1;
    }
    int result = firstByte & firstByteMask;
    for (int i = 1; i <= additionalBytesCount; i++)
    {
        result <<= 6;
        result += utf8Bytes[i] & 0x3F;
    }
    return result;
}

Тест этого кода

byte[] utf8Bytes = { 0xF0, 0x9F, 0x98, 0x81 };
int result = GetUnicode(utf8Bytes);
Console.WriteLine(result.ToString("X"));

выведет в консоль 1F601.

Answer 3

Вот функция, которая преобразует последовательность байт в UCS-32 код
(правда, это С, а не C#)

/*
  Получает адрес памяти с байтами, закодированными в UTF-8.
  Возвращает UCS из одного или нескольких байт в памяти кодированных в UTF-8.
  Во втором аргументе возвращает длину UTF-8 последовательности.
  Третий аргумент индикатор ошибки. Если ошибки нет, то он устанавливается в 0.
  При ошибке (недопустимая UTF-8 последовательность)
  возвращает первый байт, второй параметр устанавливается в 1,
  а третий задает смещение к байту, следующего за ошибочным.
 */
int
utf8_to_ucs (const char *utf, int *step, int *err)
{
  if (step)
    *step = 1;
  if (err)
    *err = 0;
  u_int ucs = *utf & 0xFF, estep = 1;
  int k, n = 5, efl = 0;
  if (ucs > 127) {
    // для любой длины utf-8
    // FF, FE, 10xx xxxx в первом байте - error (not utf-8 !!!). Return it
    if ((ucs & 0xC0) == 0x80 || ucs == 0xFF || ucs == 0xFE) 
      efl = 1;
    else {
      // 1111110x
      while (n && (ucs & (1<<n)))
        n--;
      k = 7-n;
      u_int uc = ucs & mask[n-1];
      n = 6-n;
      while (n--) {
        estep++;
        if ((*(++utf) & 0xC0) != 0x80) {
          efl = 1;
          break;
        }
        uc <<= 6;
        uc |= (*utf & 0x3f);
      }
      if (!efl) {
        if (step)
          *step = k;
        ucs = uc;
      }
    }
  }
  if (efl) {
    if (err)
      *err = estep;
    errno = EILSEQ;
  }
  return ucs;
}
READ ALSO
Не работает запись в файл txt

Не работает запись в файл txt

Учусь по книге разработке на phpТам есть такой код

316
Как вывести запись по совпадению?

Как вывести запись по совпадению?

База создана в phpmyadmin(тестирую на сервере)Версия php 5

266
Хранение Emoji с Инстаграм в таблице mysql

Хранение Emoji с Инстаграм в таблице mysql

Добрый день! Скопирован текст с инстаграм, включающий Emoji:

282