在Linux中将UTF-32宽字符转换为UTF-16宽字符,用于补充平面字符

时间:2017-03-20 09:02:25

标签: c++ linux icu wchar-t

我们使用ICU在RHEL上部署了一个C ++应用程序。

我们需要在linux上将UChar *转换为wchar_t *。我们使用u_strToWCS来执行转换。

DefaultView = View(
    HSplit(
        Item("figure", editor=MPLFigureEditor(toolbar=True), width=0.95,
             show_label=False),
        VGroup(
            Item("draw_button", show_label=False)),
        show_labels=False
    ),
    width=800, height=600, title="Facial Triangles", resizable=True)

这适用于输入到65535的字符(因为UChar在linux内部实现为uint16_t)。它无法转换基本多语言平面以外的字符(例如CJK统一表意文字扩展B)

有关如何执行转换的任何想法?

更新1 :好的。我看错了方向。 u_strToWCS工作正常。问题出现是因为我需要使用CORBA将该宽字符串传递给Windows上的java应用程序。由于linux中的wchar_t是32bit,我需要找到一种方法将32bit wchar_t转换为16bit wchar_t

更新2 :我可以找到我使用过的代码here

2 个答案:

答案 0 :(得分:2)

在C ++ 11及更高版本中,此转换位于<codecvt>标头中的标准库中。以下是一些在UTF-16,UCS-4和wchar_t之间进行转换的示例代码。 (由于开发树中修复了一个错误,它在libstdc ++ 6.4.9上中断。)

#include <codecvt>
#include <cstdlib>
#include <cstring>
#include <cwctype>
#include <iostream>
#include <locale>
#include <vector>

using std::cout;
using std::endl;
using std::exit;
using std::memcmp;
using std::size_t;

using std::wcout;

int main(void)
{
  constexpr char16_t msg_utf16[] = u"¡Hola, mundo! \U0001F600"; // Shouldn't assume endianness.
  constexpr wchar_t msg_w[] = L"¡Hola, mundo! \U0001F600";
  constexpr char32_t msg_utf32[] = U"¡Hola, mundo! \U0001F600";
  constexpr char msg_utf8[] = u8"¡Hola, mundo! \U0001F600";

  // May vary from OS to OS>  "" is the most standard, but might require, e.g. "en_US.utf8".
  constexpr char locale_name[] = "";
  std::locale::global(std::locale(locale_name)); //
  wcout.imbue(std::locale());

  const std::codecvt_utf16<wchar_t, 0x1FFFF, std::little_endian> converter_w;
  const size_t max_len = sizeof(msg_utf16);
  std::vector<char> out(max_len);
  std::mbstate_t state;
  const wchar_t* from_w = nullptr;
  char* to_next = nullptr;

  converter_w.out( state, msg_w, msg_w+sizeof(msg_w)/sizeof(wchar_t), from_w, out.data(), out.data() + out.size(), to_next );


  if (memcmp( msg_utf8, out.data(), sizeof(msg_utf8) ) == 0 ) {
    wcout << L"std::codecvt_utf16<wchar_t> converts to UTF-8, not UTF-16!" << endl;
  } else if ( memcmp( msg_utf16, out.data(), max_len ) != 0 ) {
    wcout << L"std::codecvt_utf16<wchar_t> conversion not equal!" << endl;
  } else {
    wcout << L"std::codecvt_utf16<wchar_t> conversion is correct." << endl;
  }
  out.clear();
  out.resize(max_len);

  const std::codecvt_utf16<char32_t, 0x1FFFF, std::little_endian> converter_u32;
  const char32_t* from_u32 = nullptr;
  converter_u32.out( state, msg_utf32, msg_utf32+sizeof(msg_utf32)/sizeof(char32_t), from_u32, out.data(), out.data() + out.size(), to_next );

  if ( memcmp( msg_utf16, out.data(), max_len ) != 0 ) {
    wcout << L"std::codecvt_utf16<char32_t> conversion not equal!" << endl;
  } else {
    wcout << L"std::codecvt_utf16<char32_t> conversion is correct." << endl;
  }

  wcout << msg_w << endl;
  return EXIT_SUCCESS;
}

这两个方面将在C ++ 17中弃用,但不是<codecvt>中的所有方面。特别是,标准库将支持std::codecvt<char, char, std::mbstate_t>std::codecvt<char16_t, char, std::mbstate_t>std::codecvt<char32_t, char, std::mbstate_t>std::codecvt<wchar_t, char, std::mbstate_t>

您没有在Linux上查看此UTF-16数据的来源,但这可能暗示了一种方法。如果要处理文件,可以在带有构面的流上使用imbue()来在读取和写入数据时转换数据,如果要使用Qt框架,则QString和{ {1}}提供转换功能。 ICU应该支持整个UTF-16。

更新1

问题实际上是问如何从相反的方向转换,从宽字符串转换为UTF-16。我的例子是这样做的,但是如果你想使用ICU,它有QTextCodexu_strFromWCS()u_strFromUTF32()

如果你更喜欢ICU到STL的理由是STL的转换器方面声称是与语言环境无关的,那么请注意那些ICU转换器功能都声称也是与语言环境无关的。这是因为不同UTF编码之间的转换是完全算法的,并且与区域设置无关! (排序顺序和大小写映射等其他内容不是,但也就是这样。)实际上,如果您愿意,STL允许您使用UnicodeString::fromUTF32()从特定区域设置请求转换器方面,并且不会弃用C ++ 17。但是,只需要以这种方式实现与UTF-8之间的转换。 “In addition, every locale object constructed in a C++ program implements its own (locale-specific) versions of these four specializations.”在我的测试中,库的现有实现不支持locale::use_facet<codecvt<...>>()

更新2

我正在将手册locale().use_facet<std_codecvt<wchar_t,char16_t,mbstate_t>>()转发给wchar_t转换器from my answer here。它需要utf_16并返回std::wstring,但算法可以很容易地适应任何其他容器。但是,std::u16string至少与需要动态内存的任何其他数据结构一样有效。

您可能想要做的一个改变是,在给定输入字符串的长度的情况下,我为最坏的情况分配了足够的内存,然后是u16string。这应该不会浪费比UTF-32首先编码你的字符串更多的内存。但是,你的数据不太可能不在BMP中,所以你可以先做一个初始传递来计算转换所需的内存量,或者假设在现实世界中使用和接受的代理对很少不太可能必须调整目标阵列的大小和复制。

shrink_to_fit()

答案 1 :(得分:0)

以下是将UTF-32编码的宽字符转换为UTF-16

的代码
//Function to convert a Unicode string from platform-specific "wide characters" (wchar_t) to UTF-16.
void ConvertUTF32ToUTF16(wchar_t* source,
                         const uint32_t sourceLength,
                         wchar_t*& destination,
                         uint32_t& destinationLength)
{

  wchar_t wcharCharacter;
  uint32_t uniui32Counter = 0;

  wchar_t* pwszDestinationStart = destination;
  wchar_t* sourceStart = source;

  if(0 != destination)
  {
    while(uniui32Counter < sourceLength)
    {
      wcharCharacter = *source++;
      if(wcharCharacter <= 0x0000FFFF)
      {
        /* UTF-16 surrogate values are illegal in UTF-32
           0xFFFF or 0xFFFE are both reserved values */
        if(wcharCharacter >= 0xD800 && 
           wcharCharacter <= 0xDFFF)
        {
          *destination++ = 0x0000FFFD;
          destinationLength += 1;
        }
        else
        {
          /* source is a BMP Character */
          destinationLength += 1;
          *destination++ = wcharCharacter;
        }
      }
      else if(wcharCharacter > 0x0010FFFF)
      {
        /* U+10FFFF is the largest code point of Unicode Character Set */
        *destination++ = 0x0000FFFD;
        destinationLength += 1;
      }
      else
      {
        /* source is a character in range 0xFFFF - 0x10FFFF */
        wcharCharacter -= 0x0010000UL;
        *destination++ = (wchar_t)((wcharCharacter >> 10) + 0xD800);
        *destination++ = (wchar_t)((wcharCharacter & 0x3FFUL) + 0xDC00);
        destinationLength += 2;
      }

      ++uniui32Counter;
    }

    destination = pwszDestinationStart;
    destination[destinationLength] = '\0';
  }

  source = sourceStart;
} //function ends