Пользователь
0,0
рейтинг
16 февраля 2012 в 13:09

Разработка → UTF-8: Кодирование и декодирование из песочницы

Причиной разобраться в том, как же работает 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 суррогатами.

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.
  • Аналогично для более длинных последовательностей.
  • Заменяем всю последовательность на нужный символ Юникода.

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


Ссылки


Юникод на Википедии
Исходник для ASP+VBScript

UPD: Обработка ошибочных последовательностей и ошибка с типом Integer, который возвращает AscW.
Алексей Ваганов @poofeg
карма
6,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (24)

  • –3
    А что, на vbscript кто-то пишет?
    На jscript, кстати, эти функции пишутся тривиально:

    function utf8_decode (str) { return unescape(encodeURIComponent(str)); }
    function utf8_encode (str) { return decodeURIComponent(escape(str)); }

    * идея взята из комментария на phpjs.org
    • +3
      А почему минусуете? Субьективное мнение — тоже мнение!
      • +4
        Это хабр, детка.
        • +3
          ) Ну тогда держитесь!
      • +5
        Завидуют, что в js проблема решается проще =))
  • +2
    Мне недавно как раз потребовалось автоматом перегонять текст из UTF8 в юникод, и я уже почти изобрёл велосипед, но наткнулся на маленькую утилиту uniconv, которая меня очень выручила.
    Может кому-нибудь тоже пригодится.
  • –12
    «Символы же с кодами от 128 кодируются 2-мя байтами, с кодами от 2048 — 3-мя, от 65536 — 4-мя. Так можно было бы и до 6-ти байт дойти, но кодировать ими уже ничего.»

    facepaw.jpg

    1 байт — это 8 бит, следовательно максимальное число, записывание им равно 256 (2 в 8 степени), следовательно 2 байта — это 2 в 16 степени, или 65536. Следовательно 3 байта — это 65536*256 или 16777216.
    • +1
      Да, вот только запись числа, кодируемого 1 байтом информации, в шестнадцатиричном виде занимает 2 байта.
    • +1
      Если один байт полностью использовать под кодирование символа, то разобрать сколько их там еще осталось нельзя будет… Поэтому, если код символа больше 128, то он уже кодируется двумя байтами.
    • +2
      Посмотрите внимательно на схему преобразования. Некоторые биты в UTF-8 представлении символов являются вспомогательными, чтобы можно было при разборе данных понимать, сколько байт считывать для очередного символа.
    • +9
      да, уже прочитал. Минусуйте :)
  • +4
    Спасибо, неплохо написано. Что могу добавить:
    Начало дублирует ANSII, а дальше остаток латиницы, кирилица, другие европейские и азиатские символы


    Это не так, первые 255 unicode code points соответствуют Latin-1 а не ASCII.

    Так можно было бы и до 6-ти байт дойти, но кодировать ими уже ничего.


    Это не так. До 4-х байт UTF-16 обрезали для совместимости по ассортименту code points с UTF-16, это определено RFC3629. Более того, обрезано оно не до 4-х байт, а до 0x10FFFF code points, тоесть четвертый байт используется не целиком.
  • +4
    Ну вот… Начало хорошее, но дальше Википедии вы не дошли и получился не соответствующий стандарту Unicode велосипед. Почему? Например, потому что вы позволяете кодировать в UTF-8 старшие и младшие суррогаты. Также неправильно обрабатываются overlong sequences и ошибочные последовательности. Эти все вещи должны заменяться на специальный кодпоинт и производиться восстановление после ошибок строго так, как написано в стандарте.

    Можете проверять свой декодировщик на тесте:
    www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt
    • 0
      Спасибо, учту. Поспешил конечно, первая статья моя. С суррогатами разберусь.
    • +1
      тест теперь проходит
  • 0
    А так как ничего рабочего не нашел, то пришлось писть/дописывать самому.

    Кхм-кхм :).
    habrahabr.ru/blogs/php/113715/
    • 0
      На VBScript. У него даже побитовый сдвиг отсутствует :).
  • +1
    ничего не сказано про точки кода — базовое понятие utf.
  • 0
    Блин, это ж бейсик! Последний раз лет 15 назад его видел:)
    • 0
      Лучше и не видеть, но всякие задачи встают иногда :).
  • 0
    VBScript имеет в своем распоряжении компонент ADODB.Stream, которым Windows комплектуется по-умолчанию. Это вполне себе почти «встронное» средство для работы с UTF-8.
    • 0
      Практически ровно 3 года прошло, однако.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.