Blog. Just Blog

Как преобразовать кириллическую строку из UTF-8 в cp1251

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
Как преобразовать кириллическую строку из UTF-8 в cp1251
Как преобразовать кириллическую строку из UTF-8 в cp1251

При разработке одной программы мне понадобилось преобразовать строки на русском языке из формата UTF-8 в формат cp1251. Внезапно выяснилось, что никакие средства WinAPI не позволяются выполнить эту операцию "одной строкой". Пришлось рассматривать даже варианты с табличным преобразованием, но потом нашлось более простое решение задачи. Алгоритм преобразования получился необычный, но зато гарантированно рабочий. Может быть это поможет сохранить время и нервы кому-нибудь еще.

Для начала вспомогательная функция, которая проверяет, является ли строка кириллической строкой в формате UTF-8. В ней должны быть только однобайтовые символы из первой половины таблицы ASCII и двухбайтовые символы, соответствующие русским буквам. Нулевой символ - признак окончания строки, длина строки значения не имеет.
  1. ;--------------------------------------------------------
  2. ; Проверка строки на соответствие формату
  3. ; кириллического UTF-8
  4. ;--------------------------------------------------------
  5. ; Символы [0x00-0x7F] или двухсимвольные конструкции
  6. ; вида 0xD0[0x81|0x90-0xBF] или 0xD1[0x91|0x80-0x8F]
  7. ;--------------------------------------------------------
  8. ; На выходе:
  9. ;    EAX = 1 - строка соответствует UTF-8
  10. ;    EAX = 0 - строка не соответствует формату
  11. ;--------------------------------------------------------
  12. proc    is_utf8 tstr:DWORD
  13.         push    esi ebx
  14.         mov     esi,[tstr]
  15.  
  16.         ; По умолчанию строка соответствует формату
  17.         mov     ebx,1
  18. .loc_scan:
  19.         lodsb
  20.         ; Окончание строки?
  21.         or      al,al
  22.         jz      .loc_ret
  23.  
  24.         ; Проверка символов [0x00-0x7F]
  25.         cmp     al,07Fh
  26.         jbe     .loc_scan
  27.  
  28.         ; Проверка двухсимвольной конструкции
  29.         ; 0xD0[0x81|0x90-0xBF]
  30.         cmp     al,0D0h
  31.         jne     @f
  32.         lodsb
  33.         cmp     al,81h
  34.         je      .loc_scan
  35.         cmp     al,90h
  36.         jb      .loc_fail
  37.         cmp     al,0BFh
  38.         ja      .loc_fail
  39.         jmp     .loc_scan
  40. @@:
  41.         ; Проверка двухсимвольной конструкции
  42.         ; 0xD1[0x91|0x80-0x8F]
  43.         cmp     al,0D1h
  44.         jne     .loc_fail
  45.         lodsb
  46.         cmp     al,91h
  47.         je      .loc_scan
  48.         cmp     al,80h
  49.         jb      .loc_fail
  50.         cmp     al,8Fh
  51.         jbe     .loc_scan
  52.  
  53. .loc_fail:
  54.         ; Строка не соответствует формату
  55.         xor     ebx,ebx
  56.  
  57. .loc_ret:
  58.         mov     eax,ebx
  59.         pop     ebx esi
  60.         ret
  61. endp
И еще одна аналогичная функция для наиболее точной и глубокой проверки соответствия строки формату UTF-8. Корректно обрабатывает даже битые строки, которые теоретически может пропустить предыдущая функция.
  1. ;--------------------------------------------------------
  2. ; Проверка строки на соответствие формату UTF-8
  3. ;--------------------------------------------------------
  4. ; На входе:
  5. ;    lpStr  - указатель на проверяемую строку
  6. ;--------------------------------------------------------
  7. ; На выходе:
  8. ;    EAX = 1 - строка соответствует UTF-8
  9. ;    EAX = 0 - строка не соответствует формату
  10. ;--------------------------------------------------------
  11. proc is_valid_utf8 lpStr:DWORD
  12.         locals
  13.                 cp  dd ?
  14.                 num dd ?
  15.         endl
  16.  
  17.         push    esi ebx ecx
  18.         mov     esi,[lpStr]
  19.  
  20.         ; По умолчанию строка соответствует формату
  21.         mov     ebx,1
  22. .loc_scan:
  23.         lodsb
  24.         ; Окончание строки?
  25.         or      al,al
  26.         jz      .loc_ret
  27.  
  28.         ; U+0000 to U+007F
  29.         mov     ah,al
  30.         and     ah,0x80
  31.         or      ah,ah
  32.         jnz     @f
  33.  
  34.         and     al,0x7F
  35.         movzx   eax,al
  36.         mov     [cp],eax
  37.         mov     [num],1
  38.         jmp     .loc_check
  39. @@:
  40.         ; U+0080 to U+07FF
  41.         mov     ah,al
  42.         and     ah,0xE0
  43.         cmp     ah,0xC0
  44.         jne     @f
  45.  
  46.         and     al,0x1F
  47.         movzx   eax,al
  48.         mov     [cp],eax
  49.         mov     [num],2
  50.         jmp     .loc_check
  51. @@:
  52.         ; U+0800 to U+FFFF
  53.         mov     ah,al
  54.         and     ah,0xF0
  55.         cmp     ah,0xE0
  56.         jne     @f
  57.  
  58.         and     al,0x0F
  59.         movzx   eax,al
  60.         mov     [cp],eax
  61.         mov     [num],3
  62.         jmp     .loc_check
  63. @@:
  64.         ; U+10000 to U+10FFFF
  65.         mov     ah,al
  66.         and     ah,0xF8
  67.         cmp     ah,0xF0
  68.         jne     @f
  69.  
  70.         and     al,0x07
  71.         movzx   eax,al
  72.         mov     [cp],eax
  73.         mov     [num],4
  74.         jmp     .loc_check
  75. @@:
  76.         jmp     .loc_fail
  77. .loc_check:
  78.         mov     ecx,[num]
  79. @@:
  80.         dec     ecx
  81.         or      ecx,ecx
  82.         jz      @f
  83.  
  84.         lodsb
  85.         mov     ah,al
  86.         and     ah,0xC0
  87.         cmp     ah,0x80
  88.         jne     .loc_fail
  89.  
  90.         shl     [cp],6
  91.         and     al,0x3F
  92.         movzx   eax,al
  93.         or      [cp],eax
  94.         jmp     @b
  95. @@:
  96.         ; (cp > 0x10FFFF)
  97.         cmp     [cp],0x10FFFF
  98.         ja      .loc_fail
  99.  
  100.         ; (cp >= 0xD800) && (cp <= 0xDFFF)
  101.         cmp     [cp],0xD800
  102.         jb      @f
  103.         cmp     [cp],0xDFFF
  104.         jbe     .loc_fail
  105. @@:
  106.         ; (cp <= 0x007F) && (num != 1)
  107.         cmp     [cp],0x007F
  108.         ja      @f
  109.         cmp     [num],1
  110.         jne     .loc_fail
  111. @@:
  112.         ; (cp >= 0x0080) && (cp <= 0x07FF) && (num != 2)
  113.         cmp     [cp],0x0080
  114.         jb      @f
  115.         cmp     [cp],0x07FF
  116.         ja      @f
  117.         cmp     [num],2
  118.         jne     .loc_fail
  119. @@:
  120.         ; (cp >= 0x0800) && (cp <= 0xFFFF) && (num != 3)
  121.         cmp     [cp],0x0800
  122.         jb      @f
  123.         cmp     [cp],0xFFFF
  124.         ja      @f
  125.         cmp     [num],3
  126.         jne     .loc_fail
  127. @@:
  128.         ; (cp >= 0x10000) && (cp <= 0x1FFFFF) && (num != 4)
  129.         cmp     [cp],0x10000
  130.         jb      @f
  131.         cmp     [cp],0x1FFFFF
  132.         ja      @f
  133.         cmp     [num],4
  134.         jne     .loc_fail
  135. @@:
  136.         jmp     .loc_scan
  137.  
  138. .loc_fail:
  139.         ; Строка не соответствует формату
  140.         xor     ebx,ebx
  141.  
  142. .loc_ret:
  143.         mov     eax,ebx
  144.         pop     ecx ebx esi
  145.         ret
  146. endp
Теперь пример кода для преобразования строки из UTF-8 в cp1251. Фишка заключается в том, что здесь используется промежуточный шаг с преобразованием UTF-8 в UTF-16 через функцию MultiByteToWideChar, и только после этого полученная промежуточная строка из UTF-16 преобразуется в cp1251 при помощи функции WideCharToMultiByte. Извратно, конечно, но зато работает.
  1.         ; Получить нужную длину строки для конвертирования
  2.         invoke  MultiByteToWideChar,CP_UTF8,0,str8,-1,0,0
  3.         or      eax,eax
  4.         ; Конвертировать строку невозможно
  5.         jz      can_not_convert
  6.         ; Промежуточное конвертирование UTF-8 -> UTF-16
  7.         invoke  MultiByteToWideChar,CP_UTF8,0,str8,-1,buff16,eax
  8.         ; Получить нужную длину строки для конвертирования
  9.         invoke  WideCharToMultiByte,1251,0,buff16,-1,0,0,0,0
  10.         or      eax,eax
  11.         ; Конвертировать строку невозможно
  12.         jz      can_not_convert
  13.         ; Финальное конвертирование UTF-16 -> cp1251
  14.         invoke  WideCharToMultiByte,1251,0,buff16,-1,str1251,eax,0,0
  15.  
  16. successfully_converted:
  17.         ; str1251 = отконвертированная строка
  18.         ...
  19.  
  20. can_not_convert:
  21.         ; Конвертировать строку невозможно
  22.         ...
На каждом этапе конвертирования выполняется дополнительная проверка на возможность преобразования. Если расчетная длина строки получается нулевая, значит преобразовать строку по какой-то причине нельзя. Это не отменяет использование описанной выше функции проверки на соответствие строки формату UTF-8.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

UTF8.cp1251.Demo.zip (2,256 bytes)


Поделиться ссылкой ВКонтакте Поделиться ссылкой на Facebook Поделиться ссылкой на LiveJournal Поделиться ссылкой в Мой Круг Добавить в Мой мир Добавить на ЛиРу (Liveinternet) Добавить в закладки Memori Добавить в закладки Google
Просмотров: 8945 | Комментариев: 9

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
ManHunter (20.09.2022 в 18:29):
Добавил более навороченную функцию проверки на соответствие строки формату UTF-8
ManHunter (31.07.2016 в 17:44):
Примеры приведены. Берешь и используешь в своей программе.
Денис (31.07.2016 в 12:04):
Как это можно использовать для перекодировки?
ManHunter (03.01.2015 в 10:15):
*facepalm*
Igor K, не пиши больше ничего, не надо
Igor K (03.01.2015 в 09:24):
Перевод делается с помощью Notepad++
brute (31.10.2014 в 07:11):
morgot, я имел в виду команды SSE и другие, которые за один такт работают со строками по 128бит (или более).
morgot (29.10.2014 в 17:03):
brute, какой это "32 разрядный mov"? На 64 битах этой команды нет, по вашему? Про функцию вообще промолчу, могу сказать только то, что ваш образ мышления уж явно не Ассемблер.

ManHunter, спасибо, пригодится.
ManHunter (28.10.2014 в 10:35):
Все равно в нормальном софте придется делать обратную совместимость как минимум до WinXP
brute (28.10.2014 в 06:34):
Круто! но я бы искал какую-нибудь сишную библиотеку, чтобы заинклудить нужную функцию. Она наверняка эффективнее, чем "старый" 32 разнрядный "mov". П.С. слышал, что в W7 и W8 появилось много новых api-функций, в том числе есть и новые строковые..

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2022
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.08 сек. / MySQL: 2 (0.0072 сек.) / Память: 4.75 Mb
Наверх