c++ - 如何从 C stdio.h getline() 中替换/忽略无效的 Unicode/UTF8 字符?

标签 c++ c c++11 unicode utf-8

在 Python 上,对于 open 有这个选项 errors='ignore' Python 函数:

open( '/filepath.txt', 'r', encoding='UTF-8', errors='ignore' )

有了这个,读取一个包含无效 UTF8 字符的文件将不会用任何东西替换它们,也就是说,它们会被忽略。例如,包含字符 Føö»BÃ¥r 的文件将被读取为 FøöBår

如果从 stdio.h 使用 getline() 读取 Føö»BÃ¥r 行,它将是读作 Føö�Bår:

FILE* cfilestream = fopen( "/filepath.txt", "r" );
int linebuffersize = 131072;
char* readline = (char*) malloc( linebuffersize );

while( true )
{
    if( getline( &readline, &linebuffersize, cfilestream ) != -1 ) {
        std::cerr << "readline=" readline << std::endl;
    }
    else {
        break;
    }
}

如何让 stdio.h getline() 将其读取为 FøöBår 而不是 Føö�Bår ,即忽略无效的 UTF8 字符?

我能想到的一个压倒性的解决方案是遍历读取的每一行上的所有字符,并构建一个没有任何这些字符的新 readline。例如:

FILE* cfilestream = fopen( "/filepath.txt", "r" );
int linebuffersize = 131072;
char* readline = (char*) malloc( linebuffersize );
char* fixedreadline = (char*) malloc( linebuffersize );

int index;
int charsread;
int invalidcharsoffset;

while( true )
{
    if( ( charsread = getline( &readline, &linebuffersize, cfilestream ) ) != -1 )
    {
        invalidcharsoffset = 0;
        for( index = 0; index < charsread; ++index )
        {
            if( readline[index] != '�' ) {
                fixedreadline[index-invalidcharsoffset] = readline[index];
            } 
            else {
                ++invalidcharsoffset;
            }
        }
        std::cerr << "fixedreadline=" << fixedreadline << std::endl;
    }
    else {
        break;
    }
}

相关问题:

  1. Fixing invalid UTF8 characters
  2. Replacing non UTF8 characters
  3. python replace unicode characters
  4. Python unicode: how to replace character that cannot be decoded using utf8 with whitespace?

最佳答案

您混淆了您所看到的与实际发生的事情。 getline 函数不做任何字符替换。 [注1]

您看到的是替换字符 (U+FFFD),因为您的控制台在被要求呈现无效的 UTF-8 代码时会输出该字符。大多数控制台在 UTF-8 模式下都会这样做;也就是说,当前语言环境是 UTF-8。

此外,说文件包含“字符 Føö»BÃ¥r”充其量是不准确的。文件实际上并不包含字符。它包含可以根据某种编码解释为字符的字节序列——例如,通过控制台或其他将它们呈现为字形的用户演示软件。不同的编码产生不同的结果;在这种特殊情况下,您有一个由软件使用 Windows-1252 编码(或大致等同于 ISO 8859-15)创建的文件,并且您正在使用 UTF-8 在控制台上呈现它。

这意味着 getline 读取的数据包含无效的 UTF-8 序列,但它(可能)不包含替换字符代码。根据您提供的字符串,它包含十六进制字符\xbb,它是Windows 代码页1252 中的海鸠(»)。

要在 getline(或任何其他读取文件的 C 库函数)读取的字符串中查找所有无效的 UTF-8 序列,需要扫描字符串,但不需要扫描特定的代码序列。相反,您需要一次解码一个 UTF-8 序列,寻找无效的序列。这不是一项简单的任务,而是 mbtowc功能可以提供帮助(如果您启用了 UTF-8 语言环境)。正如您将在链接的联机帮助页中看到的那样,mbtowc 返回有效“多字节序列”(在 UTF-8 语言环境中为 UTF-8)中包含的字节数,或 -1 表示无效或不完整的序列。在扫描中,您应该通过有效序列中的字节,或者删除/忽略开始无效序列的单个字节,然后继续扫描直到到达字符串的末尾。

这是一些简单测试的示例代码(C 语言):

#include <stdlib.h>
#include <string.h>

/* Removes in place any invalid UTF-8 sequences from at most 'len' characters of the
 * string pointed to by 's'. (If a NUL byte is encountered, conversion stops.)
 * If the length of the converted string is less than 'len', a NUL byte is
 * inserted.
 * Returns the length of the possibly modified string (with a maximum of 'len'),
 * not including the NUL terminator (if any).
 * Requires that a UTF-8 locale be active; since there is no way to test for
 * this condition, no attempt is made to do so. If the current locale is not UTF-8,
 * behaviour is undefined.
 */
size_t remove_bad_utf8(char* s, size_t len) {
  char* in = s;
  /* Skip over the initial correct sequence. Avoid relying on mbtowc returning
   * zero if n is 0, since Posix is not clear whether mbtowc returns 0 or -1.
   */
  int seqlen;
  while (len && (seqlen = mbtowc(NULL, in, len)) > 0) { len -= seqlen; in += seqlen; }
  char* out = in;

  if (len && seqlen < 0) {
    ++in;
    --len;
    /* If we find an invalid sequence, we need to start shifting correct sequences.  */
    for (; len; in += seqlen, len -= seqlen) {
      seqlen = mbtowc(NULL, in, len);
      if (seqlen > 0) {
        /* Shift the valid sequence (if one was found) */
        memmove(out, in, seqlen);
        out += seqlen;
      }
      else if (seqlen < 0) seqlen = 1;
      else /* (seqlen == 0) */ break;
    }
    *out++ = 0;
  }
  return out - s;
}

注意事项

  1. 除了底层 I/O 库可能的行尾转换之外,这将在 Windows 等系统上用单个 \n 替换 CR-LF,其中两个字符的 CR-LF 序列是用作行结束指示。

关于c++ - 如何从 C stdio.h getline() 中替换/忽略无效的 Unicode/UTF8 字符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56604724/

相关文章:

c++ - 如何查找当前系统时区

c++ - Gtk::Notebook 不显示

c++ - 在 Visual C++ 中通过 TCP 发送字符串数据

c++ - 打开 QFile 进行追加

c++ - 使用 mingw-w64 时找不到或未正确包含 float.h

c++ - 构造函数的继承错误调用

C++11 普通旧对象默认值

c++ - 区分STL容器

c++ - sqrt时间复杂度比较

检查结构数组的 NULL