programing

C에서 윈도우즈 및 Linux에서 UTF-16을 UTF-8로 변환

subpage 2023. 10. 1. 19:28
반응형

C에서 윈도우즈 및 Linux에서 UTF-16을 UTF-8로 변환

문자열을 UTF-16LE에서 UTF-8로 변환할 목적으로 권장되는 '크로스' 윈도우 및 리눅스 방법이 있는지 아니면 환경별로 다른 방법을 사용해야 하는지 궁금합니다.

나는 'iconv'에 대한 몇 가지 참조를 구글에서 검색했지만, 어떤 이유로 wchar_t UTF-16을 UTF-8로 변환하는 것과 같은 기본 변환의 샘플을 찾을 수 없습니다.

누구나 '크로스'할 수 있는 방법을 추천할 수 있으며, 참고 자료나 샘플이 포함된 가이드를 알고 있다면 매우 감사할 것입니다.

고마워요, 두리바

PowerShell을 사용하여 UTF-8로 인코딩 변경:

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

ICU를 사용하고 싶지 않다면,

  1. Windows:WideCharTo멀티바이트
  2. 리눅스: iconv (Glibc)

오픈 소스 ICU 라이브러리는 매우 일반적으로 사용됩니다.

MSYS2가 설치되어 있는 경우iconv패키지(기본적으로 설치됨)를 사용하여 다음을 사용할 수 있습니다.

 iconv -f utf-16le -t utf-8 <input.txt >output.txt
#include <iconv.h>

wchar_t *src = ...; // or char16_t* on non-Windows platforms
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);

도 이 문제를 겪었습니다. 부스트 로케일 라이브러리를 사용하여 해결합니다.

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바이트이므로 UTF-16LE 버퍼를 포함하는 데 std::wstring을 사용하지 않는 것이 좋습니다.

헤더 전용 라이브러리인 utfcpp도 있습니다.

UTF-8, UTF-16, UTF-32, wchar - 사이의 문자열을 변환할 수 있는 또 다른 휴대용 C는 mdz_unicode 라이브러리입니다.

감사합니다 여러분, 이렇게 해서 '크로스' 윈도우와 리눅스 요구사항을 해결할 수 있었습니다.

  1. 다운로드 및 설치:MinGW,그리고.MSYS
  2. 다운로드했습니다.libiconv소스 패키지
  3. 컴파일됨libiconv경유로MSYS.

이상입니다.

자신만의 롤을 만들 수도 있는데, 이 롤은 다음과 같은 몇 가지 이점이 있습니다.

  1. iconv의 다소 제한적인 라이센스 적용 대상이 아님
  2. ICU 버전 헬(hell) 또는 기타 동적 링크 문제를 피하기 위해 정적 링크에 대한 제한 없음
  3. 매우 큰 라이브러리(예: icu 또는 boost)를 연결할 필요가 없습니다(정적으로 연결된 경우 수십 MB 크기를 매우 작은 이진 파일에 추가할 수 있음).

참고: 아래에서는 매우 작은 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

언급URL : https://stackoverflow.com/questions/2867123/convert-utf-16-to-utf-8-under-windows-and-linux-in-c

반응형