Конвертировать UTF-16 в UTF-8 под Windows и Linux, в C

Мне было интересно, есть ли рекомендуемый метод "кросс" Windows и Linux для преобразования строк из UTF-16LE в UTF-8? или нужно использовать разные методы для каждой среды?

Мне удалось найти в Google несколько ссылок на iconv, но по некоторым причинам я не могу найти примеры базовых преобразований, таких как - преобразование wchar_t UTF-16 в UTF-8.

Любой может порекомендовать метод, который будет "перекрестным", и, если вы знаете ссылки или руководство с образцами, был бы очень признателен.

Спасибо, Дори Бар

11 ответов

Решение

Спасибо, ребята, вот как мне удалось решить требование "кросс" Windows и Linux:

  1. Скачано и установлено: MinGW, а также MSYS
  2. Скачал libiconv исходный пакет
  3. скомпилированный libiconv с помощью MSYS,

Вот и все.

Измените кодировку на UTF-8 с помощью PowerShell:

powershell -Command "Get-Content PATH\temp.txt -Encoding Unicode | Set-Content -Encoding UTF8 PATH2\temp.txt"

Если вы не хотите использовать ICU,

  1. Windows: WideCharToMultiByte
  2. 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);

Также есть utfcpp, библиотека только для заголовков.

Другая портативная возможность C конвертировать строку между UTF-8, UTF-16, UTF-32, wchar - это библиотека mdz_unicode .

Вы также можете свернуть свой собственный, что имеет несколько преимуществ:

  1. Не подлежит несколько ограничительной лицензии iconv
  2. Нет ограничений на статическую компоновку, чтобы избежать ада версии ICU или других головных болей динамической компоновки.
  3. Избегайте необходимости компоновки очень большой библиотеки (например, 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

Другие вопросы по тегам