UTF-8: Кодирование и декодирование

Dragokas

Angry & Scary Developer
Команда форума
Супер-Модератор
Разработчик
Клуб переводчиков
Сообщения
7,862
Реакции
6,618
Причиной разобраться в том, как же работает UTF-8 и что такое Юникод заставил тот факт, что VBScript не имеет встроенных функций работы с UTF-8. А так как ничего рабочего не нашел, то пришлось писать/дописывать самому. Опыт на мой взгляд полезный в любом случае. Для лучшего понимания начну с теории.

О Юникоде

До появления Юникода широко использовались 8-битные кодировки, главные минусы которых очевидны:
  • Всего 255 символов, да и то часть из них не графические;
  • Возможность открыть документ не с той кодировкой, в которой он был создан;
  • Шрифты необходимо создавать для каждой кодировки.

Так и было решено создать единый стандарт «широкой» кодировки, которая включала бы все символы (при чем сначала хотели в нее включить только обычные символы, но потом передумали и начали добавлять и экзотические). Юникод использует 1 112 064 кодовых позиций (больше чем 16 бит). Начало дублирует ASCII, а дальше остаток латиницы, кирилица, другие европейские и азиатские символы. Для обозначений символов используют шестнадцатеричную запись вида «U+xxxx» для первых 65k и с большим количеством цифр для остальных.

О UTF-8

Когда-то я думал что есть Юникод, а есть UTF-8. Позже я узнал, что ошибался.
UTF-8 является лишь представлением Юникода в 8-битном виде. Символы с кодами меньше 128 представляются одним байтом, а так как в Юникоде они повторяют ASCII, то текст написанный только этими символами будет являться текстом в ASCII. Символы же с кодами от 128 кодируются 2-мя байтами, с кодами от 2048 — 3-мя, от 65536 — 4-мя. Так можно было бы и до 6-ти байт дойти, но кодировать ими уже ничего.

0x00000000 — 0x0000007F: 0xxxxxxx
0x00000080 — 0x000007FF: 110xxxxx 10xxxxxx
0x00000800 — 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 — 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx



Кодируем в UTF-8

Порядок действий примерно такой:
  • Каждый символ превращаем в Юникод.
  • Проверяем из какого символ диапазона.
  • Если код символа меньше 128, то к результату добавляем его в неизменном виде.
  • Если код символа меньше 2048, то берем последние 6 бит и первые 5 бит кода символа. К первым 5 битам добавляем 0xC0 и получаем первый байт последовательности, а к последним 6 битам добавляем 0x80 и получаем второй байт. Конкатенируем и добавляем к результату.
  • Похожим образом можем продолжить и для больших кодов, но если символ за пределами U+FFFF придется иметь дело с UTF-16 суррогатами.
VB.NET / VBA:
Function EncodeUTF8(s)
    Dim i, c, utfc, b1, b2, b3

    For i=1 to Len(s)
        c = ToLong(AscW(Mid(s,i,1)))
 
        If c < 128 Then
            utfc = chr( c)
        ElseIf c < 2048 Then
            b1 = c Mod &h40
            b2 = (c - b1) / &h40
            utfc = chr(&hC0 + b2) & chr(&h80 + b1)
        ElseIf c < 65536 And (c < 55296 Or c > 57343) Then
            b1 = c Mod &h40
            b2 = ((c - b1) / &h40) Mod &h40
            b3 = (c - b1 - (&h40 * b2)) / &h1000
            utfc = chr(&hE0 + b3) & chr(&h80 + b2) & chr(&h80 + b1)
        Else
            ' Младший или старший суррогат UTF-16
            utfc = Chr(&hEF) & Chr(&hBF) & Chr(&hBD)
        End If
 
        EncodeUTF8 = EncodeUTF8 + utfc
    Next
End Function

Function ToLong(intVal)
    If intVal < 0 Then
        ToLong = CLng(intVal) + &H10000
    Else
        ToLong = CLng(intVal)
    End If
End Function



Декодируем UTF-8

  • Ищем первый символ вида 11xxxxxx
  • Считаем все последующие байты вида 10xxxxxx
  • Если последовательность из двух байт и первый байт вида 110xxxxx, то отсекаем приставки и складываем, умножив первый байт на 0x40.
  • Аналогично для более длинных последовательностей.
  • Заменяем всю последовательность на нужный символ Юникода.
VB.NET / VBA:
Function DecodeUTF8(s)
    Dim i, c, n, b1, b2, b3
 
    i = 1
        Do While i <= len(s)
            c = asc(mid(s,i,1))
            If (c and &hC0) = &hC0 Then
                n = 1
                Do While i + n <= len(s)
                    If (asc(mid(s,i+n,1)) and &hC0) <> &h80 Then
                        Exit Do
                    End If
                    n = n + 1
                Loop
                If n = 2 and ((c and &hE0) = &hC0) Then
                    b1 = asc(mid(s,i+1,1)) and &h3F
                    b2 = c and &h1F
                    c = b1 + b2 * &h40
                Elseif n = 3 and ((c and &hF0) = &hE0) Then
                    b1 = asc(mid(s,i+2,1)) and &h3F
                    b2 = asc(mid(s,i+1,1)) and &h3F
                    b3 = c and &h0F
                    c = b3 * &H1000 + b2 * &H40 + b1
                Else
                    ' Символ больше U+FFFF или неправильная последовательность
                    c = &hFFFD
                End if
                s = left(s,i-1) + chrw( c) + mid(s,i+n)
            Elseif (c and &hC0) = &h80 then
                ' Неожидаемый продолжающий байт
                s = left(s,i-1) + chrw(&hFFFD) + mid(s,i+1)
            End If
            i = i + 1
        Loop
    DecodeUTF8 = s
End Function

По теме:
Юникод на Википедии

Источник
 
Последнее редактирование:
Когда-то я думал что есть Юникод, а есть UTF-8. Позже я узнал, что ошибался.
UTF-8 является лишь представлением Юникода в 8-битном виде. Символы с кодами меньше 128 представляются одним байтом, а так как в Юникоде они повторяют ASCII, то текст написанный только этими символами будет являться текстом в ASCII. Символы же с кодами от 128 кодируются 2-мя байтами, с кодами от 2048 — 3-мя, от 65536 — 4-мя. Так можно было бы и до 6-ти байт дойти, но кодировать ими уже ничего.
0x00000000 — 0x0000007F: 0xxxxxxx
0x00000080 — 0x000007FF: 110xxxxx 10xxxxxx
0x00000800 — 0x0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
0x00010000 — 0x001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Не совсем понял принцип...хотя и читал:
UTF-8 — Википедия
 
Если честно я тоже первый раз знакомлюсь с этой темой :)
Попробую объяснить.

Koza Nozdri, первые два числа это просто диапазон (в 16-ричной системе счисления).
В 10-чной смотри ниже мою таблицу.

Кол-во лидирующих единичек (до цифры 0) показывает кол-во битов, которыми кодируется символ (исключение: 1 байт, где первым идет сразу 0):

0......... - 1 байт
110...... - 2 байта
1110.... - 3 байта и т.д. :

Чтобы узнать, сколько писать таких единичек,
нужно узнать ASCII-код кодируемого символа, и посмотреть колонку № 1 таблицы:

Ищем диапазон, которому принадлежит ASCII-код кодируемого символа|Сколько байт будет занимать 1 символ в кодированном виде|Битовая запись кодированного символа|Кол-во значащих битов|Описание
0 — 127|1 байт|0aaa aaaa|7|ASCII, в том числе латинский алфавит, простейшие знаки препинания и арабские цифры
128 — 2047|2 байта|110x xxxx 10xx xxxx|11|кириллица, расширенная латиница, арабский, армянский, греческий, еврейский и коптский алфавит; сирийское письмо, тана, нко; МФА; некоторые знаки препинания
2048 — 65535|3 байта|1110 xxxx 10xx xxxx 10xx xxxx|16|кириллица, расширенная латиница, арабский, армянский, греческий, еврейский и коптский алфавит; сирийское письмо, тана, нко; МФА; некоторые знаки препинания
65536 — 2097151|4 байта|1111 0xxx 10xx xxxx 10xx xxxx 10xx xxxx|21|музыкальные символы, редкие китайские иероглифы, вымершие формы письменности
Не используется|5 байт|1111 10xx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx|26|
Не используется|6 байт|1111 110x 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx 10xx xxxx|31|
где x - это значащие биты.

Например, ты берешь символ +. Вспоминаем таблицу ASCII-кодов.
Его код = [latex]43_1_0 = 101011_2[/latex]
Мы ведь уже научились переводить 10 (Dec) -> 2 (Bin) с помощью калькулятора Windows (вид "Программист") ? :)

Все это - значащие биты (2-чное представление ASCII-кода кодируемого символа).

Ищем диапазон по таблице. Нашли: 0 <= 43 <= 127.
Значит в кодированном виде будет занимать 1 байт. Смотрим правило (выше по таблице).
Первый бит будет = 0. Остальные просто переносим (не забывая дописать лидирующий значащий 0, чтобы получить полный байт (все 8 бит):
00101011
+ дописываем BOM (идентификатор, того что дальше идет кодировка UTF-8)
[latex]EF BB BF_1_6 = 1110 1111 1011 1011 1011 1111_2[/latex]
Итого:
1110 1111 1011 1011 1011 1111 0010 1011 - это и будет символ + в кодировке UTF-8.

Кстати, сам BOM - это тоже закодированное в UTF-8 значение FF FE (по сути 2-байтовый символ с ASCII-кодом 65534, который в закодированном виде занимает 3 байта).
______________________
Теперь давай закодируем символ авторских прав ©.
ASCII код =
[latex]169_1_0 = 1010 1001_2[/latex]

Входит в диапазон по таблице. Нашли: 128 <= 169 <= 2047. А значит кодируется уже в 2 байта.
Кодированный шаблон такой:
110x xxxx 10xx xxxx

Подставляем в шаблон вместо иксов 2-чное представление ASCII-кода символа (справа налево):
1100 0010 1010 1001

не забываем дописать три нуля, чтобы завершить байт.
Дописываем BOM и получаем:
1110 1111 1011 1011 1011 1111 1100 0010 1010 1001

Можно проверить результат в редакторе Notepad++ или AkelPad,
создав символ сначала в кодировке Windows-1251, а затем сохранив как UTF-8 с BOM.
Затем открыть любым HEX-редактором в 2-чном представлении.
 
Назад
Сверху Снизу