【问题标题】:ICU: ucnv_convertEx – detect encoding error on the flyICU:ucnv_convertEx – 即时检测编码错误
【发布时间】:2018-12-29 19:56:21
【问题描述】:

是否可以在转换时使用 ICU 检测编码错误,或者是否有必要在转换前或后检查转换?

鉴于设置了从 UTF8 到 UTF32 的转换的初始化:

#include <stdio.h>
#include "unicode/ucnv.h"     /* C Converter API */

static void eval(UConverter* from, UConverter* to);

int main(int argc, char** argv)
{
    UConverter*  from;
    UConverter*  to;
    UErrorCode   status;

    /* Initialize converter from UTF8 to Unicode ___________________________*/
    status = U_ZERO_ERROR;
    from   = ucnv_open("UTF-8", &status);
    if( ! from || ! U_SUCCESS(status) ) return 1; 
    status = U_ZERO_ERROR;
    to     = ucnv_open("UTF32", &status);
    if( ! to || ! U_SUCCESS(status) ) return 1; 
    /*______________________________________________________________________*/

    eval(from, to);
    return 0;
}

然后,通过ucnv_convertEx 应用转换

static void eval(UConverter* from, UConverter* to) 
{
    UErrorCode  status = U_ZERO_ERROR;
    uint32_t    drain[1024];
    uint32_t*   drain_p = &drain[0];
    uint32_t*   p       = &drain[0];

    /* UTF8 sequence with error in third byte ______________________________*/
    const char  source[] = { "\xED\x8A\x0A\x0A" }; 
    const char* source_p = &source[0];

    ucnv_convertEx(to, from, (char**)&drain_p, (char*)&drain[1024],
                   &source_p, &source[5],
                   NULL, NULL, NULL, NULL, /* reset = */TRUE, /* flush = */TRUE,
                   &status);

    /* Print conversion result _____________________________________________*/
    printf("source_p: source + %i;\n", (int)(source_p - &source[0]));
    printf("status:   %s;\n", u_errorName(status));
    printf("drain:    (n=%i)[", (int)(drain_p - &drain[0]));
    for(p=&drain[0]; p != drain_p ; ++p) { printf("%06X ", (int)*p); }
    printf("]\n");
}

如果source 包含不允许的 UTF8 代码单元序列,该函数应该以某种方式报告错误。将上述片段存储在“test.c”中并使用

编译上述代码
$ gcc test.c $(icu-config --ldflags) -o test

./test 的输出是(令人惊讶的):

source_p: source + 5;
status:   U_ZERO_ERROR;
drain:    (n=5)[00FEFF 00FFFD 00000A 00000A 000000 ]

因此,没有检测到错误的明显迹象。错误检测能比手动检查内容更优雅吗?

【问题讨论】:

  • 您可以设置错误回调。 q.v. userguide.icu-project.org/conversion/…
  • @Eljay,您介意修饰上面的代码片段以应用回调进行错误检测吗?在文档中,我不清楚“oldAction”是什么。
  • 如果您有C++11,您可以使用标准库进行此转换,这确实表示编码错误。
  • (平心而论,关于您的示例,可以轻松检查 UTF8 编码错误,并且它根本不需要外部库。但我想您正在寻找更通用的解决方案。)跨度>
  • 文本元数据(字符编码)通信失败、存储或传输过程中的数据损坏以及之前的 mojibake 错误的可能性与您从一种编码转换为另一种编码的需要完全无关。

标签: c++ c unicode character-encoding icu


【解决方案1】:

正如@Eljay 在 cmets 中建议的那样,您可以使用错误回调。你甚至不需要自己写,因为内置的UCNV_TO_U_CALLBACK_STOP 会做你想做的事(即,对任何坏字符返回失败)。

int TestIt()
{
  UConverter* utf8conv{};
  UConverter* utf32conv{};
  UErrorCode status{ U_ZERO_ERROR };

  utf8conv = ucnv_open("UTF8", &status);

  if (!U_SUCCESS(status))
  {
    return 1;
  }

  utf32conv = ucnv_open("UTF32", &status);

  if (!U_SUCCESS(status))
  {
    return 2;
  }

  const char source[] =  { "\xED\x8A\x0A\x0A" };
  uint32_t target[10]{ 0 };

  ucnv_setToUCallBack(utf8conv, UCNV_TO_U_CALLBACK_STOP, nullptr, 
    nullptr, nullptr, &status);

  if (!U_SUCCESS(status))
  {
    return 3;
  }

  auto sourcePtr = source;
  auto sourceEnd = source + ARRAYSIZE(source);
  auto targetPtr = target;
  auto targetEnd = reinterpret_cast<const char*>(target + ARRAYSIZE(target));

  ucnv_convertEx(utf32conv, utf8conv, reinterpret_cast<char**>(&targetPtr),
    targetEnd, &sourcePtr, sourceEnd, nullptr, nullptr, nullptr, nullptr, 
    TRUE, TRUE, &status);

  if (!U_SUCCESS(status))
  {
    return 4;
  }

  printf("Converted '%s' to '", source);
  for (auto start = target; start != targetPtr; start++)
  {
    printf("\\x%x", *start);
  }
  printf("'\r\n");

  return 0;
}

这应该为无效的 Unicode 代码点返回 4,如果成功则打印出 UTF-32 值。我们不太可能从ucnv_setToUCallBack 收到错误,但我们会检查以防万一。在上面的示例中,我们将 nullptr 传递给上一个操作,因为我们不关心它是什么,也不需要重置它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-29
    • 2015-03-18
    • 2020-05-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多