Конвертировать UTF-16 в UTF-8 под Windows и Linux, в C
Мне было интересно, есть ли рекомендуемый метод "кросс" Windows и Linux для преобразования строк из UTF-16LE в UTF-8? или нужно использовать разные методы для каждой среды?
Мне удалось найти в Google несколько ссылок на iconv, но по некоторым причинам я не могу найти примеры базовых преобразований, таких как - преобразование wchar_t UTF-16 в UTF-8.
Любой может порекомендовать метод, который будет "перекрестным", и, если вы знаете ссылки или руководство с образцами, был бы очень признателен.
Спасибо, Дори Бар
11 ответов
Спасибо, ребята, вот как мне удалось решить требование "кросс" Windows и Linux:
- Скачано и установлено:
MinGW
, а такжеMSYS
- Скачал
libiconv
исходный пакет - скомпилированный
libiconv
с помощьюMSYS
,
Вот и все.
Измените кодировку на UTF-8 с помощью PowerShell:
powershell -Command "Get-Content PATH\temp.txt -Encoding Unicode | Set-Content -Encoding UTF8 PATH2\temp.txt"
Если вы не хотите использовать ICU,
- Windows: WideCharToMultiByte
- Linux: iconv (Glibc)
Библиотека ICU с открытым исходным кодом очень широко используется.
Если у вас установлен MSYS2, то iconv
пакет (установленный по умолчанию) позволяет использовать:
iconv -f utf-16le -t utf-8 <input.txt >output.txt
Я тоже столкнулся с этой проблемой, я решаю ее, используя библиотеку языкового стандарта
try
{
std::string utf8 = boost::locale::conv::utf_to_utf<char, short>(
(short*)wcontent.c_str(),
(short*)(wcontent.c_str() + wcontent.length()));
content = boost::locale::conv::from_utf(utf8, "ISO-8859-1");
}
catch (boost::locale::conv::conversion_error e)
{
std::cout << "Fail to convert from UTF-8 to " << toEncoding << "!" << std::endl;
break;
}
Функция boost::locale::conv::utf_to_utf пытается преобразовать из буфера, который закодирован UTF-16LE, в UTF-8, Функция boost::locale::conv::from_utf пытается преобразовать из буфера, который закодирован UTF-8 для ANSI, убедитесь, что кодировка правильная (здесь я использую кодировку для Latin-1, ISO-8859-1).
Еще одно напоминание: в Linux std::wstring имеет длину 4 байта, но в Windows std::wstring имеет длину 2 байта, поэтому лучше не использовать std:: wstring для хранения буфера UTF-16LE.
wchar_t *src = ...;
int srclen = ...;
char *dst = ...;
int dstlen = ...;
iconv_t conv = iconv_open("UTF-8", "UTF-16");
iconv(conv, (char*)&src, &srclen, &dst, &dstlen);
iconv_close(conv);
Другая портативная возможность C конвертировать строку между UTF-8, UTF-16, UTF-32, wchar - это библиотека mdz_unicode .
Вы также можете свернуть свой собственный, что имеет несколько преимуществ:
- Не подлежит несколько ограничительной лицензии iconv
- Нет ограничений на статическую компоновку, чтобы избежать ада версии ICU или других головных болей динамической компоновки.
- Избегайте необходимости компоновки очень большой библиотеки (например, icu или boost) (которая при статической компоновке может добавить десятки МБ к тому, что в противном случае могло бы быть очень маленьким двоичным файлом)
Примечание: ниже предполагается, что вы установили utf8proc, который очень компактен. однако, если вы предпочитаете, вы можете просто использовать его заголовочный файл и скопировать егоutf8proc_encode_char()
функция, которую использует этот код.
utf16le_to_utf8.h:
#ifndef UTF16LE_TO_UTF8_H
#define UTF16LE_TO_UTF8_H
enum utf816le_status {
utf816le_status_ok = 0,
utf816le_status_malformed_utf6le_input,
utf816le_status_memory,
utf816le_status_unencodable,
utf816le_status_buffsize
};
/*
* @return converted string, or NULL on error
* @param str input string
* @param len length (in bytes) of input string
* @param buff optional user-provided output buffer. if not provided, the returned
* converted string must be freed by caller using free()
* @param buffsize length of user-provided buffer, or 0 if no buffer provider
* @param out_len pointer to length of converted output, in bytes
* @param status pointer to status, set to non-zero in case of error
*/
unsigned char *utf16le_to_utf8(const unsigned char *str, size_t len,
unsigned char *buff, size_t buffsize,
size_t *out_len,
enum utf816le_status *status);
#endif
utf16le_to_utf8.c:
#include <stdlib.h>
#include <string.h>
#include <utf8proc.h>
#include "utf16le_to_utf8.h"
#if defined( __FreeBSD__ ) || defined( __OpenBSD__ ) || defined( __NetBSD__ )
# include <sys/endian.h>
#elif defined( BSD ) && ( BSD >= 199103 ) || defined( __APPLE__ ) || \
defined( __CYGWIN32__ ) || defined( __DJGPP__ ) || defined( __osf__ )
# include <machine/endian.h>
#elif defined( __linux__ ) || defined( __GNUC__ ) || defined( __GNU_LIBRARY__ )
# if !defined( __MINGW32__ ) && !defined( _AIX )
# include <endian.h>
# if !defined( __BEOS__ )
# include <byteswap.h>
# endif
# endif
#endif
static inline uint16_t little_endian_16(uint16_t v) {
#if __BYTE_ORDER == __LITTLE_ENDIAN
return v;
#else
return (v << 8) | (v >> 8);
#endif
}
static utf8proc_int32_t utf16le_next_codepoint(uint16_t *text, unsigned int max_bytes,
unsigned int *bytes_read) {
uint16_t c1 = little_endian_16(text[0]);
if (c1 >= 0xd800 && c1 < 0xdc00) {
if(max_bytes < 4) {
*bytes_read = 0;
return 0;
}
*bytes_read = 4;
uint16_t c2 = text[1];
return ((c1 & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000;
}
if(max_bytes < 2) {
*bytes_read = 0;
return 0;
}
*bytes_read = 2;
return c1;
}
unsigned char *utf16le_to_utf8(const unsigned char *str, size_t len,
unsigned char *buff, size_t buffsize,
size_t *out_len,
enum utf816le_status *status) {
if(!buffsize)
buff = NULL;
if(!buff)
buffsize = 0;
unsigned char *dst = buff;
size_t sizeof_dst = buffsize;
size_t written = 0;
*status = utf816le_status_ok;
unsigned char_len;
for(size_t i = 0; i < len; i+= char_len) {
utf8proc_int32_t codepoint = utf16le_next_codepoint((uint16_t *)(str + i), len - i, &char_len);
if(!char_len) { // error! bad utf
*status = utf816le_status_malformed_utf6le_input;
break;
}
// we need at least 4 bytes to encode to utf8. add 1 for terminal null and 1 for good measure
if(sizeof_dst < written + 6) {
if(buffsize > 0) { // user-provided buffer is too small
*status = utf816le_status_buffsize;
break;
}
size_t new_size = sizeof_dst == 0 ? 64 : sizeof_dst * 2;
unsigned char *new_dst = realloc(dst, new_size);
if(!new_dst) { // out of memory!
*status = utf816le_status_memory;
break;
}
dst = new_dst;
sizeof_dst = new_size;
}
utf8proc_ssize_t want = utf8proc_encode_char(codepoint, dst + written);
if(!want) { // error
*status = utf816le_status_unencodable;
break;
}
written += want;
}
if(*status == utf816le_status_ok) {
*out_len = written;
dst[written] = '\0';
return dst;
}
*out_len = 0;
if(dst != buff)
free(dst);
return NULL;
}
который вы можете использовать так:
...
unsigned char *converted = utf16le_to_utf8(utf16buff, utf16byte_count, NULL, 0, &output_len, &status);
if(!converted || !output_len)
fprintf(stderr, "Error! %i\n", status);
else
fprintf(stdout, "Converted to utf8 with length %zu: %s\n", output_len, converted);
free(converted);
}
}
Это решение работает для меня (взято из Преобразование из UTF-16 в UTF-8 ):
#include <stdint.h>
// Converts UTF-16 string into UTF-8 string.
// If destination string is NULL returns total number of symbols that would've
// been written (without null terminator). However, when actually writing into
// destination string, it does include it. So, be sure to allocate extra byte
// for destination string.
// Params:
// u16_str - source UTF-16 string
// u16_str_len - length of source UTF-16 string
// u8_str - destination UTF-8 string
// u8_str_size - size of destination UTF-8 string in bytes
// Return value:
// 0 on success, -1 if encountered invalid surrogate pair, -2 if
// encountered buffer overflow or length of destination UTF-8 string in bytes
// (without including the null terminator).
long int utf16_to_utf8(const uint16_t *u16_str, size_t u16_str_len,
uint8_t *u8_str, size_t u8_str_size)
{
size_t i = 0, j = 0;
if (!u8_str) {
u8_str_size = u16_str_len * 4;
}
while (i < u16_str_len) {
uint32_t codepoint = u16_str[i++];
// check for surrogate pair
if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
uint16_t high_surr = codepoint;
uint16_t low_surr = u16_str[i++];
if (low_surr < 0xDC00 || low_surr > 0xDFFF)
return -1;
codepoint = ((high_surr - 0xD800) << 10) +
(low_surr - 0xDC00) + 0x10000;
}
if (codepoint < 0x80) {
if (j + 1 > u8_str_size) return -2;
if (u8_str) u8_str[j] = (char)codepoint;
j++;
} else if (codepoint < 0x800) {
if (j + 2 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xC0 | (codepoint >> 6);
u8_str[j + 1] = 0x80 | (codepoint & 0x3F);
}
j += 2;
} else if (codepoint < 0x10000) {
if (j + 3 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xE0 | (codepoint >> 12);
u8_str[j + 1] = 0x80 | ((codepoint >> 6) & 0x3F);
u8_str[j + 2] = 0x80 | (codepoint & 0x3F);
}
j += 3;
} else {
if (j + 4 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xF0 | (codepoint >> 18);
u8_str[j + 1] = 0x80 | ((codepoint >> 12) & 0x3F);
u8_str[j + 2] = 0x80 | ((codepoint >> 6) & 0x3F);
u8_str[j + 3] = 0x80 | (codepoint & 0x3F);
}
j += 4;
}
}
if (u8_str) {
if (j >= u8_str_size) return -2;
u8_str[j] = '\0';
}
return (long int)j;
}
Вы можете проверить это здесь: https://godbolt.org/z/coqTEfsK7